| |
| import os |
| import json |
| import requests |
| from flask import Flask, request, Response, stream_with_context |
|
|
| app = Flask(__name__) |
|
|
| |
| GEMINI_KEY_1 = os.environ.get("GEMINI_API_KEY", "") |
| GEMINI_KEY_2 = os.environ.get("GEMINI_CODER_API_KEY", "") or GEMINI_KEY_1 |
| GEMINI_KEY_3 = os.environ.get("GEMINI_ARCHITECT_API_KEY", "") or GEMINI_KEY_1 |
| GEMINI_KEY_4 = os.environ.get("GEMINI_BUGFIXER_API_KEY", "") or GEMINI_KEY_1 |
| CEREBRAS_KEY = os.environ.get("CEREBRAS_API_KEY", "") |
|
|
| CEREBRAS_URL = "https://api.cerebras.ai/v1/chat/completions" |
| GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions" |
|
|
| |
| |
| ROUTING = { |
| |
| "claude-haiku-4-5": { |
| "url": CEREBRAS_URL, |
| "model": "llama3.3-70b", |
| "key": CEREBRAS_KEY, |
| "label": "Cerebras - CEO" |
| }, |
| |
| "claude-sonnet-4-6": { |
| "url": GEMINI_URL, |
| "model": "gemini-2.0-flash", |
| "key": GEMINI_KEY_1, |
| "label": "Gemini Flash - Researcher/DevOps" |
| }, |
| |
| "claude-opus-4-6": { |
| "url": GEMINI_URL, |
| "model": "gemini-2.5-pro-exp-03-25", |
| "key": GEMINI_KEY_2, |
| "label": "Gemini Pro - Coder" |
| }, |
| |
| "claude-sonnet-4-5": { |
| "url": GEMINI_URL, |
| "model": "gemini-2.5-pro-exp-03-25", |
| "key": GEMINI_KEY_3, |
| "label": "Gemini Pro - Architect" |
| }, |
| |
| "claude-haiku-4-6": { |
| "url": GEMINI_URL, |
| "model": "gemini-2.5-pro-exp-03-25", |
| "key": GEMINI_KEY_4, |
| "label": "Gemini Pro - Bug Fixer" |
| }, |
| } |
|
|
| DEFAULT_ROUTE = ROUTING["claude-sonnet-4-6"] |
|
|
| def anthropic_to_openai(data, model): |
| messages = [] |
| system = data.get("system", "") |
| if system: |
| if isinstance(system, list): |
| system = " ".join([s.get("text", "") for s in system if isinstance(s, dict)]) |
| messages.append({"role": "system", "content": str(system)}) |
| for msg in data.get("messages", []): |
| role = msg["role"] |
| content = msg["content"] |
| if isinstance(content, list): |
| content = " ".join([ |
| c.get("text", "") for c in content |
| if isinstance(c, dict) and c.get("type") == "text" |
| ]) |
| messages.append({"role": role, "content": str(content)}) |
| return { |
| "model": model, |
| "messages": messages, |
| "max_tokens": min(data.get("max_tokens", 8096), 8096), |
| "stream": False, |
| "temperature": 0.7 |
| } |
|
|
| @app.route("/v1/messages", methods=["POST"]) |
| def messages(): |
| data = request.json |
| requested = data.get("model", "claude-sonnet-4-6") |
| route = ROUTING.get(requested, DEFAULT_ROUTE) |
|
|
| print(f"[proxy] {requested} → {route['label']} ({route['model']})", flush=True) |
|
|
| payload = anthropic_to_openai(data, route["model"]) |
| headers = { |
| "Authorization": f"Bearer {route['key']}", |
| "Content-Type": "application/json" |
| } |
|
|
| try: |
| resp = requests.post(route["url"], json=payload, headers=headers, timeout=120) |
| print(f"[proxy] status={resp.status_code}", flush=True) |
|
|
| if resp.status_code != 200: |
| print(f"[proxy] error: {resp.text[:300]}", flush=True) |
| return { |
| "type": "error", |
| "error": {"type": "api_error", "message": resp.text} |
| }, 500 |
|
|
| result = resp.json() |
| text = result.get("choices", [{}])[0].get("message", {}).get("content", "") |
| print(f"[proxy] got {len(text)} chars", flush=True) |
|
|
| usage = result.get("usage", {}) |
| anthropic_resp = { |
| "id": result.get("id", "msg_proxy"), |
| "type": "message", |
| "role": "assistant", |
| "content": [{"type": "text", "text": text}], |
| "model": requested, |
| "stop_reason": "end_turn", |
| "stop_sequence": None, |
| "usage": { |
| "input_tokens": usage.get("prompt_tokens", 0), |
| "output_tokens": usage.get("completion_tokens", 0) |
| } |
| } |
|
|
| if data.get("stream"): |
| def generate(): |
| yield f'event: message_start\ndata: {json.dumps({"type":"message_start","message":anthropic_resp})}\n\n' |
| yield f'event: content_block_start\ndata: {json.dumps({"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}})}\n\n' |
| chunk_size = 200 |
| for i in range(0, len(text), chunk_size): |
| chunk = text[i:i+chunk_size] |
| yield f'event: content_block_delta\ndata: {json.dumps({"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":chunk}})}\n\n' |
| yield f'event: content_block_stop\ndata: {json.dumps({"type":"content_block_stop","index":0})}\n\n' |
| yield f'event: message_delta\ndata: {json.dumps({"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":None},"usage":{"output_tokens":usage.get("completion_tokens",0)}})}\n\n' |
| yield f'event: message_stop\ndata: {json.dumps({"type":"message_stop"})}\n\n' |
| return Response(stream_with_context(generate()), mimetype="text/event-stream") |
|
|
| return anthropic_resp |
|
|
| except Exception as e: |
| print(f"[proxy] exception: {e}", flush=True) |
| return { |
| "type": "error", |
| "error": {"type": "api_error", "message": str(e)} |
| }, 500 |
|
|
| @app.route("/v1/models", methods=["GET"]) |
| def models(): |
| return {"data": [ |
| {"id": "claude-haiku-4-5", "object": "model"}, |
| {"id": "claude-sonnet-4-6", "object": "model"}, |
| {"id": "claude-opus-4-6", "object": "model"}, |
| {"id": "claude-sonnet-4-5", "object": "model"}, |
| {"id": "claude-haiku-4-6", "object": "model"}, |
| ]} |
|
|
| @app.route("/", methods=["GET"]) |
| @app.route("/health", methods=["GET"]) |
| def health(): |
| return { |
| "status": "ok", |
| "cerebras": "set" if CEREBRAS_KEY else "missing", |
| "gemini_1": "set" if GEMINI_KEY_1 else "missing", |
| "gemini_2": "set" if GEMINI_KEY_2 else "missing", |
| "gemini_3": "set" if GEMINI_KEY_3 else "missing", |
| "gemini_4": "set" if GEMINI_KEY_4 else "missing", |
| } |
|
|
| if __name__ == "__main__": |
| print(f"[proxy] Cerebras={'SET' if CEREBRAS_KEY else 'MISSING'}", flush=True) |
| print(f"[proxy] Gemini1={'SET' if GEMINI_KEY_1 else 'MISSING'}", flush=True) |
| print(f"[proxy] Gemini2={'SET' if GEMINI_KEY_2 else 'MISSING'}", flush=True) |
| print(f"[proxy] Gemini3={'SET' if GEMINI_KEY_3 else 'MISSING'}", flush=True) |
| print(f"[proxy] Gemini4={'SET' if GEMINI_KEY_4 else 'MISSING'}", flush=True) |
| app.run(host="0.0.0.0", port=8082, debug=False) |