| import os |
| import json |
| import time |
| import http.client |
| from urllib.parse import urlparse |
| from flask import Flask, request, Response |
|
|
| app = Flask(__name__) |
|
|
| |
| RADISON_BASE_URL = "https://radison.yourdomain.com" |
| RADISON_PATH = "/drivers/call" |
| RADISON_INTERFACE = "puter-chat-completion" |
|
|
| |
| RADISON_API_KEYS = [ |
| "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0IjoicyIsInYiOiIwLjAuMCIsInUiOiJXQXdENGpyalQ5ZWVBK2hyVEI3NjRBPT0iLCJ1dSI6ImZnNHlwTGtuVGlTS0VteGpmd01ucXc9PSIsImlhdCI6MTc2OTY5NTM4Nn0.r6YwsvMAImdWa8jL6YqplQ4RpyC5x-6N5jQefSaG5Ck", |
| "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0IjoicyIsInYiOiIwLjAuMCIsInUiOiJQMUozSWdSbFFrcUFHcUJnR0hNcm9nPT0iLCJ1dSI6IlZ1Zi9PYzY0VEo2Smtic3JSZmMzTlE9PSIsImlhdCI6MTc2OTY5NTUxM30.FqLIlwHnsSY-DcySQrPuZwfTzHCWiz-6MR-n21nLk58", |
| "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0IjoicyIsInYiOiIwLjAuMCIsInUiOiJ6Ykk0Y0NZQ1RvS0IyUzY4VGdGSnR3PT0iLCJ1dSI6Im5Uc0tMajVLUUxlTTlxOHMwVGxmT0E9PSIsImlhdCI6MTc3MDMwMDIyMH0.0xKV3xO7jHwcBEW_7gSwIByiDIVLI7VUlEdodANKzpI", |
| "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0IjoicyIsInYiOiIwLjAuMCIsInUiOiJFUnlnYjNoWFFpZTh3cUhZNjhOaElBPT0iLCJ1dSI6Ind3bFZmbHRCVENXUWRpeXFlTjE4YlE9PSIsImlhdCI6MTc3MDMwMDQwMH0.yDbtKux3BhzPlCuFpVD5Cqmoqo3z5k-ED4zFcposTng" |
| "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0IjoicyIsInYiOiIwLjAuMCIsInUiOiJDd3pZRFAvUVNOSzZEQ3FQcUNacGFnPT0iLCJ1dSI6IjZzZEJaN0JFVFRTaWdlL1ZXeWZDV2c9PSIsImlhdCI6MTc3MDMwMDk4MH0.7Xl_Pj1OoLiiJb-keRgAIMYQhKjbBdC7HdRgUcAsyNA" |
| ] |
|
|
| if not RADISON_BASE_URL or not RADISON_API_KEYS: |
| raise RuntimeError("RADISON config missing") |
|
|
| |
| class KeyRotator: |
| def __init__(self, keys): |
| self.keys = keys |
| self.index = 0 |
|
|
| def next(self): |
| key = self.keys[self.index] |
| self.index = (self.index + 1) % len(self.keys) |
| return key |
|
|
| key_rotator = KeyRotator(RADISON_API_KEYS) |
|
|
| |
| def normalize_messages(messages): |
| out = [] |
| for m in messages: |
| role = m.get("role", "user") |
| content = m.get("content", "") |
|
|
| if isinstance(content, list): |
| out.append({"role": role, "content": content}) |
| else: |
| out.append({ |
| "role": role, |
| "content": [{"type": "text", "text": str(content)}] |
| }) |
| return out |
|
|
| |
| def flatten_content(content): |
| if isinstance(content, list): |
| texts = [] |
| for c in content: |
| if isinstance(c, dict) and c.get("type") == "text": |
| texts.append(c.get("text", "")) |
| return "".join(texts) |
| return content or "" |
|
|
| |
| def call_radison_raw(model, messages, extra_args, retries=3): |
| parsed = urlparse(RADISON_BASE_URL) |
| last_error = None |
|
|
| for _ in range(retries): |
| api_key = key_rotator.next() |
|
|
| try: |
| conn = http.client.HTTPSConnection(parsed.netloc, timeout=120) |
|
|
| payload = { |
| "interface": RADISON_INTERFACE, |
| "driver": "ai-chat", |
| "method": "complete", |
| "args": { |
| "model": model, |
| "messages": messages, |
| **extra_args |
| }, |
| "auth_token": api_key |
| } |
|
|
| headers = { |
| "Content-Type": "application/json", |
| "Origin": "https://puter.com", |
| "Referer": "https://puter.com", |
| "User-Agent": "Mozilla/5.0" |
| } |
|
|
| conn.request("POST", RADISON_PATH, json.dumps(payload), headers) |
| res = conn.getresponse() |
| raw = res.read().decode("utf-8", errors="ignore") |
| conn.close() |
|
|
| |
| if res.status in (401, 403, 429): |
| last_error = raw |
| continue |
|
|
| return json.loads(raw) |
|
|
| except Exception as e: |
| last_error = str(e) |
| continue |
|
|
| raise RuntimeError(f"Radison failed after key rotation: {last_error}") |
|
|
| |
| @app.route("/v1/chat/completions", methods=["POST"]) |
| def chat_completions(): |
| body = request.json or {} |
|
|
| messages = body.get("messages") |
| model = body.get("model") |
| stream = body.get("stream", False) |
|
|
| if not messages or not model: |
| return Response( |
| json.dumps({"error": "model and messages are required"}), |
| status=400, |
| mimetype="application/json" |
| ) |
|
|
| passthrough = { |
| k: v for k, v in body.items() |
| if k not in {"messages", "model", "stream"} |
| } |
|
|
| radison_msgs = normalize_messages(messages) |
| radison_resp = call_radison_raw(model, radison_msgs, passthrough) |
|
|
| result = radison_resp.get("result", {}) |
| msg = result.get("message", {}) |
| finish_reason = result.get("finish_reason", "stop") |
| usage = result.get("usage", {}) |
|
|
| |
| if not stream: |
| openai_message = { |
| "role": "assistant", |
| "content": flatten_content(msg.get("content")) |
| } |
|
|
| if msg.get("tool_calls"): |
| openai_message["tool_calls"] = msg["tool_calls"] |
| finish_reason = "tool_calls" |
|
|
| response = { |
| "id": "chatcmpl-radison", |
| "object": "chat.completion", |
| "created": int(time.time()), |
| "model": model, |
| "choices": [ |
| { |
| "index": 0, |
| "message": openai_message, |
| "finish_reason": finish_reason |
| } |
| ], |
| "usage": { |
| "prompt_tokens": usage.get("prompt_tokens", 0), |
| "completion_tokens": usage.get("completion_tokens", 0), |
| "total_tokens": usage.get("prompt_tokens", 0) |
| + usage.get("completion_tokens", 0) |
| } |
| } |
|
|
| return Response( |
| json.dumps(response), |
| status=200, |
| mimetype="application/json" |
| ) |
|
|
| |
| def generate(): |
| content = flatten_content(msg.get("content")) |
|
|
| for token in content.split(" "): |
| chunk = { |
| "id": "chatcmpl-radison", |
| "object": "chat.completion.chunk", |
| "choices": [ |
| { |
| "index": 0, |
| "delta": {"content": token + " "} |
| } |
| ] |
| } |
| yield f"data: {json.dumps(chunk)}\n\n" |
| time.sleep(0.015) |
|
|
| yield "data: [DONE]\n\n" |
|
|
| return Response(generate(), mimetype="text/event-stream") |
|
|
| |
| if __name__ == "__main__": |
| app.run(host="0.0.0.0", port=7860) |
|
|