-
Notifications
You must be signed in to change notification settings - Fork 0
feat(router): add OpenAI Responses API and Home Assistant model support (#374) #382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1971,6 +1971,131 @@ async def proxy_models(): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=502, detail="Model proxy failed") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @app.api_route("/v1/responses", methods=["POST"]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @app.api_route("/responses", methods=["POST"]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def responses_api(request: Request): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Handle incoming OpenAI Responses API requests (/v1/responses and /responses). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Proxies requests to LiteLLM's /v1/responses endpoint, performing triage classification | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| when an auto model (e.g. llm-routing-auto-free) is requested, while supporting model aliases | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (such as gpt-4o-mini, local-qwen-3.6-hass) and tool/streaming executions. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body = await request.json() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=400, detail="Invalid JSON payload") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await sync_cooldowns_from_valkey() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client_model = body.get("model", "llm-routing-auto-free") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AUTO_MODELS = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "llm-routing-auto-free", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "llm-routing-auto-agy", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "llm-routing-auto-ollama", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "llm-routing-auto-agy-ollama", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_user_message = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| input_field = body.get("input") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(input_field, str): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_user_message = input_field | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif isinstance(input_field, list): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for item in input_field: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(item, str): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_user_message += item | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif isinstance(item, dict): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if item.get("type") == "text": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_user_message += item.get("text", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif item.get("role") == "user": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| content = item.get("content") or "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(content, str): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_user_message += content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif isinstance(content, list): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_user_message += "".join( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| b.get("text", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for b in content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(b, dict) and b.get("type") == "text" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1999
to
+2020
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: The distinction between input_text and text field names in the OpenAI API depends on which specific API endpoint and resource you are using. These are not interchangeable; using the incorrect type often results in an invalid_value error [1][2]. 1. Chat Completions API: For the standard Chat Completions endpoint (client.chat.completions.create), the content part type for text is strictly "text" [3]. Example structure: { "role": "user", "content": [ { "type": "text", "text": "Your message here" } ] } 2. Responses API: For the newer Responses API (client.responses.create), the content part type for text is "input_text" [4][5][6]. Example structure: { "role": "user", "content": [ { "type": "input_text", "text": "Your message here" } ] } Note that within the Responses API, if you are providing context from a previous assistant response, you may be required to use "output_text" instead of "input_text" [7][2]. In summary, always check the documentation for the specific endpoint you are targeting, as the API enforces these type schemas strictly to distinguish between input and output modalities across different services [1][7][2]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Locate file:"
git ls-files | rg '(^|/)router/main\.py$' || true
echo
echo "Relevant section:"
sed -n '1960,2045p' router/main.py
echo
echo "Search for last_user_message and chat_completions handling:"
rg -n "last_user_message|chat_completions|input_field|AUTO_MODELS|input_text|type.*text" router/main.py | head -200
echo
echo "Diff stat/name-only:"
git diff --stat
git diff -- router/main.py | sed -n '1,220p'Repository: sheepdestroyer/LLM-Routing Length of output: 5467 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Relevant chat_completions section:"
sed -n '2100,2245p' router/main.py
echo
echo "Programmatic behavior probe for current extraction loop:"
python3 - <<'PY'
def extract_current(input_field):
last_user_message = ""
if isinstance(input_field, str):
last_user_message = input_field
elif isinstance(input_field, list):
for item in input_field:
if isinstance(item, str):
last_user_message += item
elif isinstance(item, dict):
if item.get("type") == "text":
last_user_message += item.get("text", "")
elif item.get("role") == "user":
content = item.get("content") or ""
if isinstance(content, str):
last_user_message += content
elif isinstance(content, list):
last_user_message += "".join(
b.get("text", "")
for b in content
if isinstance(b, dict) and b.get("type") == "text"
)
return last_user_message
def extract_proposed(input_field):
last_user_message = ""
if isinstance(input_field, str):
last_user_message = input_field
elif isinstance(input_field, list):
for item in reversed(input_field):
if isinstance(item, dict) and item.get("role") == "user":
content = item.get("content") or ""
if isinstance(content, str):
last_user_message = content
elif isinstance(content, list):
last_user_message = "".join(
b.get("text", "")
for b in content
if isinstance(b, dict) and b.get("type") == "input_text"
)
break
return last_user_message
payloads = [
{"Responses structured input with input_text": [
{"role": "user", "content": [{"type": "input_text", "text": "Last user input"}]},
{"role": "assistant", "content": "assistant output"},
{"role": "user", "content": [{"type": "input_text", "text": "Oldest user input"}]},
]},
{"Responses with top-level text object": [
{"role": "user", "content": [{"type": "text", "text": "User text"}]},
]},
{"Responses plain strings": [
"Last",
"Oldest",
]},
]
for name, input_field in payloads.items():
current = extract_current(input_field)
proposed = extract_proposed(input_field)
print(f"{name}: current={current!r}, proposed={proposed!r}")
PYRepository: sheepdestroyer/LLM-Routing Length of output: 5610 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Programmatic behavior probe for current extraction loop:"
python3 - <<'PY'
def extract_current(input_field):
last_user_message = ""
if isinstance(input_field, str):
last_user_message = input_field
elif isinstance(input_field, list):
for item in input_field:
if isinstance(item, str):
last_user_message += item
elif isinstance(item, dict):
if item.get("type") == "text":
last_user_message += item.get("text", "")
elif item.get("role") == "user":
content = item.get("content") or ""
if isinstance(content, str):
last_user_message += content
elif isinstance(content, list):
last_user_message += "".join(
b.get("text", "")
for b in content
if isinstance(b, dict) and b.get("type") == "text"
)
return last_user_message
def extract_proposed(input_field):
last_user_message = ""
if isinstance(input_field, str):
last_user_message = input_field
elif isinstance(input_field, list):
for item in reversed(input_field):
if isinstance(item, dict) and item.get("role") == "user":
content = item.get("content") or ""
if isinstance(content, str):
last_user_message = content
elif isinstance(content, list):
last_user_message = "".join(
b.get("text", "")
for b in content
if isinstance(b, dict) and b.get("type") == "input_text"
)
break
return last_user_message
payloads = {
"Responses structured input with input_text": [
{"role": "user", "content": [{"type": "input_text", "text": "Last user input"}]},
{"role": "assistant", "content": "assistant output"},
{"role": "user", "content": [{"type": "input_text", "text": "Oldest user input"}]},
],
"Responses with top-level text object": [
{"role": "user", "content": [{"type": "text", "text": "User text"}]},
],
"Responses plain strings": [
"Last",
"Oldest",
],
}
for name, input_field in payloads.items():
current = extract_current(input_field)
proposed = extract_proposed(input_field)
print(f"{name}: current={current!r}, proposed={proposed!r}")
PYRepository: sheepdestroyer/LLM-Routing Length of output: 436 Extract the last user input before triage and support In 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not last_user_message: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instructions = body.get("instructions") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(instructions, str): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| last_user_message = instructions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1999
to
+2024
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Concatenating multiple input segments without separators may degrade triage classification quality. When
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_model = client_model | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if client_model in AUTO_MODELS or client_model == "llm-routing-ollama": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bypass_cache = request.headers.get("x-bypass-cache") == "true" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_model, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| triage_latency, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| was_cache_hit, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raw_classification, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) = await classify_request(last_user_message, bypass_cache=bypass_cache) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.info(f"Responses API Triage decision: Routing to -> '{target_model}'") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body_to_send = body.copy() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body_to_send["model"] = target_model | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| litellm_key = os.getenv("LITELLM_MASTER_KEY") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auth_header = request.headers.get("Authorization") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not auth_header or not auth_header.startswith("Bearer "): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auth_header = f"Bearer {litellm_key}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Authorization": auth_header, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": request.headers.get("content-type", "application/json"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is_streaming = body_to_send.get("stream", False) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client = get_http_client() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url = f"{LITELLM_URL}/v1/responses" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.info(f"Proxying Responses API request for model={target_model} to {url}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if is_streaming: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def response_streamer(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with client.stream( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "POST", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json=body_to_send, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers=headers, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeout=600.0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) as resp: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async for chunk in resp.aiter_bytes(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yield chunk | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as stream_err: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.error(f"Error during Responses API streaming proxy: {stream_err}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return StreamingResponse(response_streamer(), media_type="text/event-stream") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2050
to
+2072
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win Streaming path never checks the upstream status code — errors get silently returned as HTTP 200. Unlike 🐛 Proposed fix (mirrors the existing chat_completions pattern) if is_streaming:
- async def response_streamer():
- try:
- async with client.stream(
- "POST",
- url,
- json=body_to_send,
- headers=headers,
- timeout=600.0,
- ) as resp:
- async for chunk in resp.aiter_bytes():
- yield chunk
- except Exception as stream_err:
- logger.error(f"Error during Responses API streaming proxy: {stream_err}")
- raise
-
- return StreamingResponse(response_streamer(), media_type="text/event-stream")
+ req = client.build_request(
+ "POST", url, json=body_to_send, headers=headers, timeout=600.0
+ )
+ resp = await client.send(req, stream=True)
+ if resp.status_code != 200:
+ error_body = await resp.aread()
+ await resp.aclose()
+ logger.warning(
+ f"Responses API stream failed ({resp.status_code}): {error_body[:300]}"
+ )
+ raise HTTPException(status_code=resp.status_code, detail="Responses proxy failed")
+
+ async def response_streamer():
+ try:
+ async for chunk in resp.aiter_bytes():
+ yield chunk
+ except Exception as stream_err:
+ logger.error(f"Error during Responses API streaming proxy: {stream_err}")
+ raise
+ finally:
+ await resp.aclose()
+
+ return StreamingResponse(response_streamer(), media_type="text/event-stream")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r = await client.post( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json=body_to_send, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers=headers, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeout=600.0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response_headers = dict(r.headers) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for h in [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content-encoding", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content-length", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "transfer-encoding", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "connection", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response_headers.pop(h, None) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Response( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| content=r.content, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status_code=r.status_code, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers=response_headers, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.error(f"Failed to proxy Responses API request: {e}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=502, detail="Responses proxy failed") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @app.post("/v1/chat/completions") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def chat_completions(request: Request): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Handle incoming OpenAI-compatible chat completions requests. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2025,6 +2150,10 @@ async def chat_completions(request: Request): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "agent-reasoning-core", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "agent-advanced-core", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "llm-routing-agy", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "local-qwen-3.6", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "local-qwen-3.6-hass", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "gpt-4o-mini", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "gpt-4o", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AUTO_MODELS = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does LiteLLM's model_info supports_reasoning flag change request/response handling when true vs false for OpenAI-compatible backends?💡 Result:
In LiteLLM, the supports_reasoning flag (found in model_info and accessed via litellm.supports_reasoning(model)) is primarily a capability indicator used by LiteLLM’s internal logic to determine if a model supports reasoning-related parameters (such as thinking blocks or reasoning effort) [1][2]. It does not automatically change request/response handling solely based on its true/false value; rather, it is a metadata lookup that informs whether LiteLLM permits or attempts to translate specific reasoning parameters for that model [1][3][4]. When using OpenAI-compatible backends, the presence and state of this flag influence behavior in the following ways: 1. Parameter Validation: LiteLLM uses this flag to validate requests [5][4]. If you attempt to pass reasoning-specific parameters (like include_reasoning or reasoning_effort) to a model where supports_reasoning is false, LiteLLM may raise an error (e.g., UnsupportedParamsError) or fail to process the request correctly because the model is not registered as supporting those features [3][5]. 2. Parameter Transformation: When set to true, LiteLLM’s internal logic is more likely to permit and correctly transform or pass through reasoning-related fields (like thinking blocks) to the backend provider [1][4]. If the flag is false, LiteLLM may strip these parameters or fail to map them, depending on the current configuration and the use of parameters like drop_params [1][5]. 3. Automated Bridging: For some providers, when a model with supports_reasoning=true is used in conjunction with specific tools or parameters, LiteLLM may automatically route the request through internal bridges (such as the Responses API bridge) to ensure compatibility [1][6]. In summary, the flag acts as a gatekeeper for reasoning features within the LiteLLM library [1][2]. Setting it to true tells LiteLLM that it is valid to attempt reasoning-based interactions with that specific model, whereas setting it to false restricts those capabilities to prevent invalid parameter errors when communicating with backends that do not support them [3][5].
Citations:
litellm.supports_reasoninganddrop_paramsworking with reasoning BerriAI/litellm#8765🏁 Script executed:
Repository: sheepdestroyer/LLM-Routing
Length of output: 5429
🏁 Script executed:
Repository: sheepdestroyer/LLM-Routing
Length of output: 10311
Set
supports_reasoningtofalsefor the hass-only aliases.local-qwen-3.6-hass,gpt-4o-mini, andgpt-4ousechat_template_kwargs.enable_thinking: false, but theirmodel_infostill declares reasoning support. Sync these aliases with thelocal-qwen-3.6behavior so model capability and configuration match.🤖 Prompt for AI Agents