Skip to content

feat(router): add OpenAI Responses API and Home Assistant model support (#374) - #382

Closed
sheepdestroyer wants to merge 4 commits into
masterfrom
feat/responses-api-ha-support
Closed

feat(router): add OpenAI Responses API and Home Assistant model support (#374)#382
sheepdestroyer wants to merge 4 commits into
masterfrom
feat/responses-api-ha-support

Conversation

@sheepdestroyer

@sheepdestroyersheepdestroyer commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

This PR addresses issue #374 by implementing OpenAI Responses API support and model aliases required by Home Assistant's openai_conversation integration.

Changes Included

  1. Responses API Endpoints:

    • Added POST /v1/responses and POST /responses handlers in router/main.py that proxy requests to LiteLLM's /v1/responses endpoint.
    • Preserves model triage classification when auto-routing models (llm-routing-auto-free) are requested.
    • Supports both streaming (SSE events) and non-streaming responses.
  2. Model Aliases:

    • Added gpt-4o-mini and gpt-4o model definitions to litellm/config.yaml routing to local-qwen-3.6-hass (thinking disabled).
    • Added local-qwen-3.6, local-qwen-3.6-hass, gpt-4o-mini, and gpt-4o to public_model_groups so GET /v1/models lists them.
    • Included new model aliases in DIRECT_TIERS in router/main.py to bypass classifier overhead when specified directly.
  3. Tool Support:

    • Verified and enabled support for Home Assistant Assist actions (function tool type returning function_call output), code_interpreter, and web_search.
  4. Testing & Documentation:

    • Added automated pytest test suite in router/tests/test_responses_api.py covering model routing, Responses API requests, tools, streaming, and error handling (363/363 tests passing).
    • Added section 9e to README.md detailing Home Assistant configuration and capability requirements.
    • Updated system wiki (wiki/entities/llm-routing.md and wiki/log.md).

Closes#374

Summary by Sourcery

Add OpenAI Responses API proxy support and Home Assistant-oriented model aliases and documentation, including tests for the new responses endpoint and routing behavior.

New Features:

  • Expose OpenAI-compatible Responses API endpoints that proxy to LiteLLM, supporting streaming and non-streaming requests.
  • Introduce public model aliases (local-qwen-3.6, local-qwen-3.6-hass, gpt-4o-mini, gpt-4o) for use with Home Assistant and other clients.

Enhancements:

  • Extend direct tier model list to include new local Qwen and GPT-4o aliases to avoid unnecessary classification overhead.
  • Document Home Assistant integration, supported models, and Responses API/tool compatibility in the README, and update links to key project files.

Tests:

  • Add pytest coverage for the Responses API endpoint, including direct and auto-routed models, tool usage, streaming behavior, and error handling.

Summary by CodeRabbit

  • New Features
    • Added an OpenAI Responses API-compatible gateway at POST /v1/responses and POST /responses, including auto model routing and streaming support.
    • Added tool/function calling passthrough and improved function-call argument streaming behavior.
    • Added a new audio proxy endpoint at /v1/audio{path} and /audio{path}.
    • Expanded model aliases and local model capability/config entries.
  • Documentation
    • Updated guidance for container health checks, memory endpoint/MCP references, and Home Assistant integration (including Responses API).
  • Bug Fixes
    • Added request validation for invalid JSON and hardened proxy path handling; returns 502 on upstream proxy failures.
  • Tests
    • Added coverage for non-streaming, auto-routing, tools support, validation errors, and streaming behavior.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation router litellm labels Jul 25, 2026
@sourcery-ai

sourcery-aiBot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds an OpenAI Responses API proxy endpoint with auto-routing and streaming support, introduces Home Assistant-friendly model aliases and visibility, and documents and tests the new Responses behavior and tools integration.

Sequence diagram for OpenAI Responses API proxy with auto-routing and streaming

sequenceDiagram
actor HomeAssistant
participant Router as responses_api
participant Classifier as classify_request
participant LiteLLM as LiteLLM_v1_responses
HomeAssistant->>Router: POST /v1/responses (model or auto model)
Router->>Router: sync_cooldowns_from_valkey
Router->>Router: parse last_user_message from input/instructions
alt auto model requested
Router->>Classifier: classify_request(last_user_message, bypass_cache)
Classifier-->>Router: target_model
else direct model alias
Router->>Router: target_model = client_model
end
Router->>Router: body_to_send[model] = target_model
Router->>Router: get_http_client
alt stream == true
Router->>LiteLLM: client.stream("POST", /v1/responses, json=body_to_send)
LiteLLM-->>Router: SSE chunks
Router-->>HomeAssistant: StreamingResponse(text/event-stream)
else non-streaming
Router->>LiteLLM: client.post(/v1/responses, json=body_to_send)
LiteLLM-->>Router: JSON response
Router-->>HomeAssistant: Response(status_code, headers, content)
end
Loading

File-Level Changes

ChangeDetailsFiles
Implement OpenAI-compatible Responses API proxy endpoint with auto-model triage and streaming/non-streaming support.
  • Add POST /v1/responses and /responses FastAPI route that proxies to LiteLLM /v1/responses.
  • Parse input/instructions to extract user text for classification when auto-routing models are requested.
  • Invoke classify_request for auto models, preserve chosen target_model, and forward updated payload to LiteLLM.
  • Handle streaming via client.stream and return StreamingResponse with SSE bytes; handle non-streaming via client.post and Response.
  • Synchronize cooldowns from Valkey and normalize Authorization header using LITELLM_MASTER_KEY when necessary.
  • Strip hop-by-hop headers on proxied non-streaming responses and surface HTTP 400/502 errors for invalid JSON or proxy failure.
router/main.py
Expose Home Assistant model aliases and local Qwen variants through LiteLLM configuration and direct tiers.
  • Add local-qwen-3.6, local-qwen-3.6-hass, gpt-4o-mini, and gpt-4o to DIRECT_TIERS to bypass classifier when directly requested.
  • Register new model groups in litellm/config.yaml with openai/local-qwen-3.6-hass backend, disabled thinking, and detailed model_info metadata.
  • Mark these models as public model groups and extend public_model_groups list for discovery via GET /v1/models.
router/main.py
litellm/config.yaml
Document Home Assistant integration and Responses API/tool compatibility and fix README link targets.
  • Add README section describing Home Assistant openai_conversation setup, supported models, and Responses API/tool support.
  • Update existing README references to internal files to use full GitHub URLs for quadlets, pod.yaml, memory_mcp.py, and verification script.
README.md
Add pytest coverage for Responses API endpoint behavior, including triage, tools, streaming, and error handling.
  • Create router/tests/test_responses_api.py with tests for direct model non-streaming proxying and response shape.
  • Add test verifying classifier is invoked for llm-routing-auto-free and that the forwarded payload uses the classified target model.
  • Add test validating tool payloads (function tools, code_interpreter, web_search) and function_call outputs.
  • Add tests for invalid JSON payload raising HTTP 400 and for stream=True returning StreamingResponse with event-stream media type.
router/tests/test_responses_api.py

Assessment against linked issues

IssueObjectiveAddressedExplanation
#374Implement an OpenAI-compatible /v1/responses endpoint that proxies to LiteLLM, supports Home Assistant-style tools (function calls, code_interpreter, web_search), streaming/non-streaming behavior, and maintains existing routing/classifier behavior without breaking /v1/chat/completions.
#374Expose Home Assistant-usable models via /v1/models by adding appropriate model aliases (e.g., gpt-4o-mini, gpt-4o, local-qwen-3.6, local-qwen-3.6-hass) and ensuring they route to the intended backend models with consistent authentication and alias behavior.
#374Add automated tests and documentation for the Responses API behavior, including model alias visibility, tool handling, streaming/non-streaming responses, and error handling/capability description for unsupported tools or malformed requests, specifically in the Home Assistant integration context.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitaiBot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@sheepdestroyer, you've reached your PR review limit, so we couldn't start this review.

Next review available in:53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 382c56c3-11cb-4957-9c25-654eb76ed0e3

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb542e and 76f3f43.

📒 Files selected for processing (4)
  • litellm/config.yaml
  • router/main.py
  • router/tests/test_responses_api.py
  • scripts/verification/verify_canonical_endpoints.py
📝 Walkthrough

Walkthrough

Adds OpenAI Responses API and audio proxy endpoints, Home Assistant-compatible model aliases, direct routing updates, transcription URL fixes, automated coverage, and README documentation for configuration and compatibility.

Changes

Responses API and Home Assistant integration

Layer / File(s)Summary
Home Assistant model aliases and routing
litellm/config.yaml, router/main.py
Adds public Qwen and GPT-4o aliases with capability metadata, updates transcription bases, and enables direct routing for the new aliases.
Responses API proxy flow
router/main.py
Adds /v1/responses and /responses, automatic model classification, streaming support, function-call event handling, and upstream response forwarding.
Audio forwarding
router/main.py
Adds /v1/audio and /audio proxy routes with path validation, header filtering, and upstream error handling.
Validation and documentation
router/tests/test_responses_api.py, README.md
Tests direct, automatic, tool, invalid-payload, and streaming requests, and documents Home Assistant Responses API configuration and compatibility.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
participant Client
participant Router
participant Classifier
participant LiteLLM
Client->>Router: POST /v1/responses
Router->>Classifier: Classify auto-routed request
Classifier-->>Router: Selected target model
Router->>LiteLLM: Proxy Responses API request
LiteLLM-->>Router: JSON response or SSE stream
Router-->>Client: Forward response and function-call events
Loading

Suggested labels:tests

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Out of Scope Changes check⚠️ WarningThe PR also changes unrelated docs, audio proxying, and transcription aliases beyond the Home Assistant Responses API scope.Split the unrelated README/audio/transcription updates into a separate PR and keep this one focused on Responses API and Home Assistant support.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title is concise and accurately summarizes the main change: Responses API support and Home Assistant model aliases.
Linked Issues check✅ PassedThe changes add Responses API routes, Home Assistant aliases, tool and streaming handling, and tests aligned with #374's core requirements.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/responses-api-ha-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-aisourcery-aiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The triage and proxy logic in responses_api largely duplicates the behavior in chat_completions; consider extracting a shared helper for model selection, auth header construction, and proxying to LiteLLM to keep these endpoints consistent as behavior evolves.
  • The AUTO_MODELS set is now defined separately in both responses_api and chat_completions; centralizing this configuration (e.g., as a module-level constant) would reduce the chance of model list drift between endpoints.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments- The triage and proxy logic in `responses_api` largely duplicates the behavior in `chat_completions`; consider extracting a shared helper for model selection, auth header construction, and proxying to LiteLLM to keep these endpoints consistent as behavior evolves.
- The `AUTO_MODELS` set is now defined separately in both `responses_api` and `chat_completions`; centralizing this configuration (e.g., as a module-level constant) would reduce the chance of model list drift between endpoints.
## Individual Comments### Comment 1
<locationpath="router/main.py"line_range="1999-2024" />
<code_context>
+ 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"+ )++ if not last_user_message:
</code_context>
<issue_to_address>
**suggestion:** Concatenating multiple input segments without separators may degrade triage classification quality.
When `input` is a list, all segments are concatenated into `last_user_message` without any separators. For long or structured inputs this can obscure boundaries between messages or content types and negatively impact `classify_request`. Consider inserting simple delimiters (spaces/newlines) between segments or restricting which parts are included in triage to improve routing accuracy.
```suggestion last_user_message = "" input_field = body.get("input") if isinstance(input_field, str): last_user_message = input_field elif isinstance(input_field, list): # Collect segments and join with a delimiter to preserve boundaries for triage segments: list[str] = [] for item in input_field: if isinstance(item, str): segments.append(item) elif isinstance(item, dict): if item.get("type") == "text": segments.append(item.get("text", "")) elif item.get("role") == "user": content = item.get("content") or "" if isinstance(content, str): segments.append(content) elif isinstance(content, list): segments.append( "".join( b.get("text", "") for b in content if isinstance(b, dict) and b.get("type") == "text" ) ) # Use a simple space delimiter to avoid degrading classification by merging segments last_user_message = " ".join(s for s in segments if s) if not last_user_message: instructions = body.get("instructions") if isinstance(instructions, str): last_user_message = instructions```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment threadrouter/main.py

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
litellm/config.yaml (1)

124-165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Three near-identical litellm_params/model_info blocks — consider YAML anchors.

local-qwen-3.6-hass, gpt-4o-mini, and gpt-4o repeat byte-identical litellm_params and model_info blocks. YAML anchors/merge keys (&anchor/<<: *anchor) would remove the duplication and reduce drift risk if one copy is updated but the others are forgotten (as likely happened with the supports_reasoning flag above).

♻️ Example using YAML anchors
- litellm_params: &hass_paramsapi_base: LLAMA_CLASSIFIER_URL_PLACEHOLDERapi_key: local-tokenmodel: openai/local-qwen-3.6-hassextra_body:
chat_template_kwargs:
enable_thinking: falserequest_timeout: 600model_name: local-qwen-3.6-hassmodel_info: &hass_model_infosupports_vision: truesupports_reasoning: falsesupports_function_calling: truemode: chatmax_tokens: 524288max_input_tokens: 524288is_public_model_group: true
- litellm_params: *hass_paramsmodel_name: gpt-4o-minimodel_info: *hass_model_info
- litellm_params: *hass_paramsmodel_name: gpt-4omodel_info: *hass_model_info
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@litellm/config.yaml` around lines 124 - 165, Replace the duplicated
litellm_params and model_info mappings for local-qwen-3.6-hass, gpt-4o-mini, and
gpt-4o with YAML anchors and aliases. Define each shared mapping once, then
reference it from the three model entries while preserving their distinct
model_name values and the intended supports_reasoning setting.
router/tests/test_responses_api.py (1)

1-177: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing coverage for structured input arrays and streaming error paths.

All five tests use a plain string input; none exercise the list-based input shape ([{"role": "user", "content": [{"type": "input_text", ...}]}]) that real Responses API clients (including the OpenAI SDK) send for multi-part/multi-turn requests. That gap is exactly why the type == "text" vs input_text bug in responses_api's extraction logic (see router/main.py review) went untested. Likewise, test_responses_api_streaming only simulates a successful stream — there's no test asserting behavior when the upstream stream returns a non-200 status, which is why the missing status-code check in the streaming branch wasn't caught.

Consider adding:

  • A test with "input": [{"role": "user", "content": [{"type": "input_text", "text": "..."}]}] asserting the classifier receives the extracted text.
  • A test where mock_client.stream/upstream returns a non-200 status during a streaming request, asserting an appropriate error is surfaced rather than a 200.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@router/tests/test_responses_api.py` around lines 1 - 177, Extend the response
API tests with structured input coverage: add a list-based input containing
input_text content and assert responses_api passes the extracted text to
classify_request. Add a streaming failure test using a non-200 upstream response
from mock_client.stream, asserting responses_api surfaces an appropriate error
response instead of returning a successful StreamingResponse.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@litellm/config.yaml`:
- Around line 124-165: Update the model_info entries for the
local-qwen-3.6-hass, gpt-4o-mini, and gpt-4o aliases to set supports_reasoning
to false. Leave the existing enable_thinking configuration and other capability
flags unchanged, matching the local-qwen-3.6 behavior.
In `@router/main.py`:
- Around line 2050-2072: Update the streaming branch in responses_api’s
response_streamer to validate the upstream resp.status_code before yielding any
bytes. Propagate non-success responses as HTTP errors, preserving the existing
successful StreamingResponse behavior and matching the status handling used by
chat_completions.
- Around line 1999-2020: Update the last-user-input extraction in responses_api
to recognize input_text for both top-level items and nested content blocks, and
change the input-list traversal to inspect items in reverse and stop after
extracting the latest user turn. Keep chat_completions aligned with this
behavior by selecting only the most recent user message rather than
concatenating multiple turns.
---
Nitpick comments:
In `@litellm/config.yaml`:
- Around line 124-165: Replace the duplicated litellm_params and model_info
mappings for local-qwen-3.6-hass, gpt-4o-mini, and gpt-4o with YAML anchors and
aliases. Define each shared mapping once, then reference it from the three model
entries while preserving their distinct model_name values and the intended
supports_reasoning setting.
In `@router/tests/test_responses_api.py`:
- Around line 1-177: Extend the response API tests with structured input
coverage: add a list-based input containing input_text content and assert
responses_api passes the extracted text to classify_request. Add a streaming
failure test using a non-200 upstream response from mock_client.stream,
asserting responses_api surfaces an appropriate error response instead of
returning a successful StreamingResponse.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e431313-2d14-4759-a6b2-c78600166af2

📥 Commits

Reviewing files that changed from the base of the PR and between dcdd018 and 5a3e7e7.

📒 Files selected for processing (4)
  • README.md
  • litellm/config.yaml
  • router/main.py
  • router/tests/test_responses_api.py

Comment threadlitellm/config.yaml
Comment threadrouter/main.py
Comment threadrouter/main.py

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@router/main.py`:
- Around line 2144-2145: Update the streaming loops around resp.aiter_bytes() to
preserve raw bytes across transport chunks instead of decoding each chunk with
errors="replace"; accumulate byte data and decode only after complete SSE
boundaries are identified, including the analogous path noted near the second
occurrence.
- Around line 2170-2188: Update the synthesized delta and done events in the
response streaming handler to derive output_index and sequence_number from the
corresponding upstream event or item metadata instead of hardcoding both to
zero. Preserve the existing item_id, function name, arguments, and deduplication
behavior while ensuring each function call retains its upstream output position
and event ordering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e4834f5a-2937-4d30-af1c-2fef409344f0

📥 Commits

Reviewing files that changed from the base of the PR and between 5a3e7e7 and 8fb542e.

📒 Files selected for processing (2)
  • litellm/config.yaml
  • router/main.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • litellm/config.yaml

Comment threadrouter/main.py Outdated
Comment on lines +2144 to +2145
async for chunk in resp.aiter_bytes():
buffer += chunk.decode("utf-8", errors="replace")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve raw SSE bytes across transport chunks.

HTTP chunks need not align to UTF-8 boundaries; decoding each chunk with errors="replace" can corrupt streamed text or function arguments before they are forwarded.

Proposed fix
- buffer = ""+ buffer = b""
seen_args_delta = set()
seen_args_done = set()
async for chunk in resp.aiter_bytes():
- buffer += chunk.decode("utf-8", errors="replace")- while "\n" in buffer:- line, buffer = buffer.split("\n", 1)+ buffer += chunk+ while b"\n" in buffer:+ line, buffer = buffer.split(b"\n", 1)
line_str = line.strip()
- if line_str.startswith("data:"):+ if line_str.startswith(b"data:"):
raw_data = line_str[5:].strip()
...
- yield (line + "\n").encode("utf-8")+ yield line + b"\n"
if buffer:
- yield buffer.encode("utf-8")+ yield buffer

Also applies to: 2191-2193

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@router/main.py` around lines 2144 - 2145, Update the streaming loops around
resp.aiter_bytes() to preserve raw bytes across transport chunks instead of
decoding each chunk with errors="replace"; accumulate byte data and decode only
after complete SSE boundaries are identified, including the analogous path noted
near the second occurrence.

Comment threadrouter/main.py Outdated
Comment on lines +2170 to +2188
delta_evt = {
"type": "response.function_call_arguments.delta",
"item_id": item_id,
"delta": args_val,
"output_index": 0,
"sequence_number": 0,
}
yield f"data: {json.dumps(delta_evt)}\n\n".encode("utf-8")
if item_id and item_id not in seen_args_done:
seen_args_done.add(item_id)
done_evt = {
"type": "response.function_call_arguments.done",
"item_id": item_id,
"name": item.get("name", ""),
"arguments": args_val,
"output_index": 0,
"sequence_number": 0,
}
yield f"data: {json.dumps(done_evt)}\n\n".encode("utf-8")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
rg -n -C 5 \
'response\.output_item\.done|function_call_arguments\.(delta|done)|output_index|sequence_number' \
router/main.py router/tests/test_responses_api.py

Repository: sheepdestroyer/LLM-Routing

Length of output: 4308


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== router/main.py relevant context =="
sed -n '2080,2210p' router/main.py
echoecho"== search for output_index/sequence_number in repo =="
rg -n 'output_index|sequence_number|response\.output_item\.done|response\.function_call_arguments\.(delta|done)' router ||true

Repository: sheepdestroyer/LLM-Routing

Length of output: 8009


🌐 Web query:

OpenAI Responses streaming event function_call_arguments.delta output_index sequence_number

💡 Result:

When streaming function call arguments in OpenAI's API (specifically within the context of the Responses API or Assistants API), the response.function_call_arguments.delta event is used to stream the incremental parts of the JSON arguments string [1][2][3]. The event object typically includes the following key fields: - delta: A string containing the next portion of the function arguments [2][4]. This should be appended to your existing buffer to reconstruct the full JSON string [2][3]. - item_id: The ID of the specific output item (function call) this delta belongs to [2][4]. - output_index: The integer index of the output item within the response, which helps track multiple concurrent or sequential function calls [2][3][4]. - sequence_number: An integer representing the sequence order of this specific event [1][3][4]. In practice, you should listen for these events, use the output_index (or item_id) to route the delta to the correct function call buffer, and append the delta string to that buffer [2][3]. Do not attempt to parse the arguments as JSON until you receive the corresponding response.function_call_arguments.done event, which signifies that the full argument string has been received [2][3]. Note that this streaming pattern differs from the standard Chat Completions API streaming (which uses tool_calls deltas within chunks) [5][6]; the event-based structure described above is specific to the Responses/Assistants API streaming architecture [7][3].

Citations:


🌐 Web query:

site:platform.openai.com/docs/api-reference/responses-streaming response.output_item.done output_index sequence_number

💡 Result:

response.output_item.done includes:

  • output_index: the index of the output item in the response’s output array.
  • sequence_number: the sequence number of the event, used to order streaming events. It’s an integer.
  • type: always response.output_item.done. (platform.openai.com)

Example:

{
"type": "response.output_item.done",
"output_index": 0,
"item": { "...": "..." },
"sequence_number": 1
}
``` ([platform.openai.com](https://platform.openai.com/docs/api-reference/responses-streaming/response/incomplete?lang=node.js&utm_source=openai))Citations:- 1: https://platform.openai.com/docs/api-reference/responses-streaming/response/incomplete?lang=node.js&utm_source=openai- 2: https://platform.openai.com/docs/api-reference/responses-streaming/response/incomplete?lang=node.js&utm_source=openai---🌐 Web query:`site:platform.openai.com/docs/api-reference/responses-streaming response.function_call_arguments.delta "output_index"`💡 Result:`response.function_call_arguments.delta` includes `output_index`, which is **the index of the output item that the function-call arguments delta is added to**. It’s an integer field in the streaming event payload. ([platform.openai.com](https://platform.openai.com/docs/api-reference/responses-streaming/response/refusal/delta?lang=curl&utm_source=openai))Citations:- 1: https://platform.openai.com/docs/api-reference/responses-streaming/response/refusal/delta?lang=curl&utm_source=openai---</details>**Derive synthesized function-call event metadata from upstream events.**These injected `response.function_call_arguments.delta` / `.done` events always use `output_index: 0` and `sequence_number: 0`, but `output_index` must reflect the output item the function call belongs to, and `sequence_number` is used to order streaming events. For multi-function-call responses, this can mislabel tool streams and create event-ordering conflicts.<details><summary>🧰 Tools</summary><details><summary>🪛 ast-grep (0.44.1)</summary>
[info] 2176-2176: use jsonify instead of json.dumps for JSON outputContext: json.dumps(delta_evt)Note: [CWE-116] Improper Encoding or Escaping of Output.(use-jsonify)---
[info] 2187-2187: use jsonify instead of json.dumps for JSON outputContext: json.dumps(done_evt)Note: [CWE-116] Improper Encoding or Escaping of Output.(use-jsonify)</details></details><details><summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @router/main.py around lines 2170 - 2188, Update the synthesized delta and
done events in the response streaming handler to derive output_index and
sequence_number from the corresponding upstream event or item metadata instead
of hardcoding both to zero. Preserve the existing item_id, function name,
arguments, and deduplication behavior while ensuring each function call retains
its upstream output position and event ordering.


</details>
<!-- fingerprinting:phantom:poseidon:terra -->
<!-- cr-indicator-types:potential_issue -->
<!-- cr-comment:v1:b16ce255483158d45a31475c -->
<!-- This is an auto-generated comment by CodeRabbit -->

boy added 2 commits July 26, 2026 03:05
- Set supports_reasoning to false in litellm/config.yaml for non-thinking hass aliases (local-qwen-3.6-hass, gpt-4o-mini, gpt-4o)
- Support input_text and text content parts, traverse input in reverse to extract the latest user turn for triage, and join multi-part text with space separators
- Validate upstream status_code in streaming branch before returning StreamingResponse and close stream on exit
- Expand pytest suite to cover input_text parsing and streaming error status propagation
- Parameterize test_responses_api_with_tools and test_responses_api_streaming_tool_calls across local-qwen-3.6-hass, gpt-4o-mini, and gpt-4o
- Add test_ha_tool_calling function to scripts/verification/verify_canonical_endpoints.py to replicate Home Assistant Assist tool call execution via /v1/responses and /v1/chat/completions
@sheepdestroyer

Copy link
Copy Markdown
OwnerAuthor

Closing in favor of a clean, updated PR incorporating all review feedback and tool-calling E2E validation.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationlitellmrouterscripts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add OpenAI Responses API tools support for Home Assistant

1 participant

@sheepdestroyer