Wimmboo commited on
Commit
c6b6b9c
·
1 Parent(s): b1024aa

fix streaming: async generator keeps httpx client alive internally

Browse files
Files changed (2) hide show
  1. Dockerfile +1 -1
  2. proxy/client.py +15 -21
Dockerfile CHANGED
@@ -12,4 +12,4 @@ RUN pip install --no-cache-dir --upgrade -r requirements.txt
12
  COPY --chown=user . .
13
 
14
  EXPOSE 7860
15
- CMD uvicorn main:app --host 0.0.0.0 --port 7860
 
12
  COPY --chown=user . .
13
 
14
  EXPOSE 7860
15
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
proxy/client.py CHANGED
@@ -52,35 +52,29 @@ 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_chunks(response: httpx.Response) -> AsyncIterator[bytes]:
56
- async for chunk in response.aiter_raw():
57
- yield chunk
 
 
 
 
 
 
 
 
58
 
59
 
60
  async def forward_request(provider: Provider, real_key: str, request: Request, body: dict[str, Any]) -> Any:
61
  target_url = f"{provider.base_url}/v1/chat/completions"
62
  headers = _build_headers(provider, real_key, dict(request.headers))
63
- stream = bool(body.get("stream"))
 
 
64
 
65
  async with httpx.AsyncClient(timeout=_TIMEOUT, follow_redirects=True) as client:
66
  try:
67
- if stream:
68
- req = client.build_request("POST", target_url, headers=headers, json=body)
69
- upstream = await client.send(req, stream=True)
70
-
71
- if upstream.status_code >= 400:
72
- content = await upstream.aread()
73
- logger.warning("Upstream error from %s: %d %s", provider.name, upstream.status_code, content[:500])
74
- raise HTTPException(status_code=upstream.status_code, detail=content.decode("utf-8", errors="replace"))
75
-
76
- return StreamingResponse(
77
- _stream_chunks(upstream),
78
- status_code=upstream.status_code,
79
- media_type=upstream.headers.get("content-type", "text/event-stream"),
80
- headers={k: v for k, v in upstream.headers.items() if k.lower() in ("cache-control", "x-request-id")},
81
- )
82
- else:
83
- upstream = await client.post(target_url, headers=headers, json=body)
84
  except httpx.TimeoutException as exc:
85
  logger.warning("Upstream timeout for %s: %s", provider.name, exc)
86
  raise HTTPException(status_code=504, detail="Upstream provider timed out")
 
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"
58
+ headers = _build_headers(provider, real_key, {})
59
+ async with httpx.AsyncClient(timeout=_TIMEOUT, follow_redirects=True) as client:
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
+ raise HTTPException(status_code=upstream.status_code, detail=content.decode("utf-8", errors="replace"))
64
+ async for chunk in upstream.aiter_raw():
65
+ yield chunk
66
 
67
 
68
  async def forward_request(provider: Provider, real_key: str, request: Request, body: dict[str, Any]) -> Any:
69
  target_url = f"{provider.base_url}/v1/chat/completions"
70
  headers = _build_headers(provider, real_key, dict(request.headers))
71
+
72
+ if bool(body.get("stream")):
73
+ return StreamingResponse(_stream_response(provider, real_key, body), media_type="text/event-stream")
74
 
75
  async with httpx.AsyncClient(timeout=_TIMEOUT, follow_redirects=True) as client:
76
  try:
77
+ upstream = await client.post(target_url, headers=headers, json=body)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  except httpx.TimeoutException as exc:
79
  logger.warning("Upstream timeout for %s: %s", provider.name, exc)
80
  raise HTTPException(status_code=504, detail="Upstream provider timed out")