141 lines
5.0 KiB
Python
141 lines
5.0 KiB
Python
"""
|
|
OpenAI-compatible proxy on localhost:8080
|
|
Backend: dashscope deepseek-v4-pro (HERMES_DASHSCOPE_API_KEY)
|
|
Auth: Bearer token check
|
|
|
|
Usage:
|
|
python proxy_handler.py
|
|
|
|
Endpoints:
|
|
GET /v1/models — list available models
|
|
POST /v1/chat/completions — forward to dashscope (streaming supported)
|
|
GET /health — health check (no auth needed)
|
|
"""
|
|
import json, sys, os, time, uuid, ssl
|
|
from pathlib import Path
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
import urllib.request, urllib.error
|
|
|
|
PORT = 8080
|
|
TOKEN = [REDACTED]"PROXY_TOKEN", "your-token-here")
|
|
BACKENDS = [
|
|
{
|
|
"url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
|
|
"key": os.environ.get("HERMES_DASHSCOPE_API_KEY", ""),
|
|
"model": "deepseek-v4-pro"
|
|
},
|
|
{
|
|
"url": "https://api.openai.com/v1/chat/completions",
|
|
"key": os.environ.get("OPENAI_API_KEY", ""),
|
|
"model": "gpt-4o"
|
|
}
|
|
]
|
|
|
|
class ProxyHandler(BaseHTTPRequestHandler):
|
|
def do_GET(self): self.proxy()
|
|
def do_POST(self): self.proxy()
|
|
def do_OPTIONS(self):
|
|
self.send_response(200)
|
|
self.send_header('Access-Control-Allow-Origin', '*')
|
|
self.send_header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
|
|
self.send_header('Access-Control-Allow-Headers', '*')
|
|
self.end_headers()
|
|
|
|
def proxy(self):
|
|
auth = self.headers.get('Authorization', '')
|
|
if auth != f'Bearer {TOKEN}':
|
|
self.send_response(401); self.end_headers()
|
|
self.wfile.write(b'{"error":"Unauthorized"}'); return
|
|
|
|
path = self.path
|
|
|
|
if path == '/v1/models':
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.send_header('Access-Control-Allow-Origin', '*')
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps({
|
|
"object": "list",
|
|
"data": [{"id": b['model'], "object": "model", "owned_by": "proxy"}
|
|
for b in BACKENDS if b['key']]
|
|
}).encode())
|
|
return
|
|
|
|
if path == '/v1/chat/completions':
|
|
self.handle_chat(); return
|
|
|
|
if path == '/health':
|
|
self.send_response(200); self.end_headers()
|
|
self.wfile.write(b'{"status":"ok"}'); return
|
|
|
|
self.send_response(404); self.end_headers()
|
|
self.wfile.write(b'{"error":"Not found"}')
|
|
|
|
def handle_chat(self):
|
|
cl = int(self.headers.get('Content-Length', 0))
|
|
body = json.loads(self.rfile.read(cl))
|
|
stream = body.get('stream', False)
|
|
|
|
# Find first working backend
|
|
backend = None
|
|
for b in BACKENDS:
|
|
if b['key']:
|
|
backend = b; break
|
|
|
|
if not backend:
|
|
self.send_response(502); self.end_headers()
|
|
self.wfile.write(b'{"error":"No backend configured"}'); return
|
|
|
|
data = {
|
|
"model": backend['model'],
|
|
"messages": body.get('messages', []),
|
|
"stream": stream,
|
|
"temperature": body.get('temperature', 0.7),
|
|
"max_tokens": body.get('max_tokens', 4096)
|
|
}
|
|
|
|
try:
|
|
ctx = ssl.create_default_context()
|
|
req = urllib.request.Request(
|
|
backend['url'],
|
|
data=json.dumps(data).encode(),
|
|
method='POST'
|
|
)
|
|
req.add_header('Content-Type', 'application/json')
|
|
req.add_header('Authorization', f'Bearer {backend["key"]}')
|
|
|
|
if stream:
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'text/event-stream')
|
|
self.send_header('Cache-Control', 'no-cache')
|
|
self.send_header('Access-Control-Allow-Origin', '*')
|
|
self.end_headers()
|
|
resp = urllib.request.urlopen(req, timeout=120, context=ctx)
|
|
for line_bytes in resp:
|
|
self.wfile.write(line_bytes); self.wfile.flush()
|
|
else:
|
|
resp = urllib.request.urlopen(req, timeout=120, context=ctx)
|
|
rb = resp.read()
|
|
self.send_response(resp.status)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.send_header('Access-Control-Allow-Origin', '*')
|
|
self.end_headers()
|
|
self.wfile.write(rb)
|
|
except urllib.error.HTTPError as e:
|
|
self.send_response(e.code)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.send_header('Access-Control-Allow-Origin', '*')
|
|
self.end_headers()
|
|
self.wfile.write(e.read())
|
|
except Exception as e:
|
|
self.send_response(500); self.end_headers()
|
|
self.wfile.write(json.dumps({"error": str(e)}).encode())
|
|
|
|
def start_proxy():
|
|
print(f"\n[PROXY] http://127.0.0.1:{PORT}")
|
|
print(f"[PROXY] Auth: Bearer {TOKEN[:10]}...")
|
|
print(f"[PROXY] Backends: {[b['model'] for b in BACKENDS if b['key']]}")
|
|
HTTPServer(('0.0.0.0', PORT), ProxyHandler).serve_forever()
|
|
|
|
if __name__ == '__main__':
|
|
start_proxy() |