Spaces:
Running
Running
Wimmboo commited on
Commit ·
8fc20f8
1
Parent(s): 3b86f27
clean error messages, add model validation, add gemini-3.1-flash-lite
Browse files- .graphify_uncached.txt +15 -0
- proxy/client.py +20 -2
- proxy/router.py +11 -2
.graphify_uncached.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\config.py
|
| 2 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\main.py
|
| 3 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\.opencode\opencode.json
|
| 4 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\.opencode\package.json
|
| 5 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\.opencode\plugins\graphify.js
|
| 6 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\proxy\auth.py
|
| 7 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\proxy\client.py
|
| 8 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\proxy\providers.py
|
| 9 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\proxy\router.py
|
| 10 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\proxy\__init__.py
|
| 11 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\DESIGN.md
|
| 12 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\PRODUCT.md
|
| 13 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\README.md
|
| 14 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\requirements.txt
|
| 15 |
+
C:\Users\Rafandra Aydin\Documents\Wimmboo\OpenAIProxy\.opencode\AGENTS.md
|
proxy/client.py
CHANGED
|
@@ -52,6 +52,22 @@ async def _poll_nvidia(client: httpx.AsyncClient, provider: Provider, request_id
|
|
| 52 |
raise HTTPException(status_code=504, detail=f"NVIDIA async job {request_id} timed out after polling")
|
| 53 |
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
async def _stream_response(provider: Provider, real_key: str, body: dict[str, Any]) -> AsyncIterator[bytes]:
|
| 56 |
"""Async generator that manages its own httpx client for streaming."""
|
| 57 |
target_url = f"{provider.base_url}/v1/chat/completions"
|
|
@@ -60,7 +76,8 @@ async def _stream_response(provider: Provider, real_key: str, body: dict[str, An
|
|
| 60 |
async with client.stream("POST", target_url, headers=headers, json=body) as upstream:
|
| 61 |
if upstream.status_code >= 400:
|
| 62 |
content = await upstream.aread()
|
| 63 |
-
|
|
|
|
| 64 |
async for chunk in upstream.aiter_raw():
|
| 65 |
yield chunk
|
| 66 |
|
|
@@ -91,7 +108,8 @@ async def forward_request(provider: Provider, real_key: str, request: Request, b
|
|
| 91 |
if upstream.status_code >= 400:
|
| 92 |
content = await upstream.aread()
|
| 93 |
logger.warning("Upstream error from %s: %d %s", provider.name, upstream.status_code, content[:500])
|
| 94 |
-
|
|
|
|
| 95 |
|
| 96 |
return {
|
| 97 |
"status_code": upstream.status_code,
|
|
|
|
| 52 |
raise HTTPException(status_code=504, detail=f"NVIDIA async job {request_id} timed out after polling")
|
| 53 |
|
| 54 |
|
| 55 |
+
def _upstream_error_message(status_code: int, content: bytes) -> str:
|
| 56 |
+
"""Try to extract a readable message from a provider error response."""
|
| 57 |
+
text = content.decode("utf-8", errors="replace").strip()
|
| 58 |
+
if not text:
|
| 59 |
+
return f"Upstream provider returned HTTP {status_code}"
|
| 60 |
+
try:
|
| 61 |
+
data = json.loads(text)
|
| 62 |
+
if isinstance(data, dict):
|
| 63 |
+
if isinstance(data.get("error"), dict):
|
| 64 |
+
return data["error"].get("message") or json.dumps(data["error"])
|
| 65 |
+
return data.get("message") or data.get("error") or text
|
| 66 |
+
except Exception:
|
| 67 |
+
pass
|
| 68 |
+
return text
|
| 69 |
+
|
| 70 |
+
|
| 71 |
async def _stream_response(provider: Provider, real_key: str, body: dict[str, Any]) -> AsyncIterator[bytes]:
|
| 72 |
"""Async generator that manages its own httpx client for streaming."""
|
| 73 |
target_url = f"{provider.base_url}/v1/chat/completions"
|
|
|
|
| 76 |
async with client.stream("POST", target_url, headers=headers, json=body) as upstream:
|
| 77 |
if upstream.status_code >= 400:
|
| 78 |
content = await upstream.aread()
|
| 79 |
+
detail = _upstream_error_message(upstream.status_code, content)
|
| 80 |
+
raise HTTPException(status_code=upstream.status_code, detail=detail)
|
| 81 |
async for chunk in upstream.aiter_raw():
|
| 82 |
yield chunk
|
| 83 |
|
|
|
|
| 108 |
if upstream.status_code >= 400:
|
| 109 |
content = await upstream.aread()
|
| 110 |
logger.warning("Upstream error from %s: %d %s", provider.name, upstream.status_code, content[:500])
|
| 111 |
+
detail = _upstream_error_message(upstream.status_code, content)
|
| 112 |
+
raise HTTPException(status_code=upstream.status_code, detail=detail)
|
| 113 |
|
| 114 |
return {
|
| 115 |
"status_code": upstream.status_code,
|
proxy/router.py
CHANGED
|
@@ -24,10 +24,11 @@ MODELS = {
|
|
| 24 |
"moonshotai/kimi-k2.5",
|
| 25 |
"google/gemma-4-31b-it",
|
| 26 |
"google/gemma-4-26b-it",
|
| 27 |
-
"google/gemma-3-27b-it",
|
| 28 |
],
|
| 29 |
"google": [
|
|
|
|
| 30 |
"gemini-2.5-pro-001",
|
|
|
|
| 31 |
"gemini-2.0-flash-001",
|
| 32 |
"gemini-2.0-flash-lite-001",
|
| 33 |
"gemini-1.5-pro-001",
|
|
@@ -66,7 +67,15 @@ async def chat_completions(request: Request) -> Response:
|
|
| 66 |
if not isinstance(body, dict):
|
| 67 |
raise StarletteHTTPException(status_code=400, detail="Request body must be a JSON object")
|
| 68 |
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
result = await forward_request(provider, real_key, request, body)
|
| 72 |
|
|
|
|
| 24 |
"moonshotai/kimi-k2.5",
|
| 25 |
"google/gemma-4-31b-it",
|
| 26 |
"google/gemma-4-26b-it",
|
|
|
|
| 27 |
],
|
| 28 |
"google": [
|
| 29 |
+
"gemini-3.1-flash-lite",
|
| 30 |
"gemini-2.5-pro-001",
|
| 31 |
+
"gemini-2.5-flash-001",
|
| 32 |
"gemini-2.0-flash-001",
|
| 33 |
"gemini-2.0-flash-lite-001",
|
| 34 |
"gemini-1.5-pro-001",
|
|
|
|
| 67 |
if not isinstance(body, dict):
|
| 68 |
raise StarletteHTTPException(status_code=400, detail="Request body must be a JSON object")
|
| 69 |
|
| 70 |
+
model = body.get("model")
|
| 71 |
+
allowed = MODELS.get(provider.name, [])
|
| 72 |
+
if model and model not in allowed:
|
| 73 |
+
raise StarletteHTTPException(
|
| 74 |
+
status_code=400,
|
| 75 |
+
detail=f"Model '{model}' is not available for provider '{provider.name}'. Available: {', '.join(allowed)}",
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
logger.info("Proxying request to provider=%s model=%s", provider.name, model or "default")
|
| 79 |
|
| 80 |
result = await forward_request(provider, real_key, request, body)
|
| 81 |
|