Skip to content

fix(responses_id_security): handle a non-streaming response in the streaming iterator hook - #42263

Open
AloysJehwin wants to merge 1 commit into
BerriAI:mainfrom
AloysJehwin:fix/responses-id-security-nonstreaming-response
Open

AloysJehwin wants to merge 1 commit into
BerriAI:mainfrom
AloysJehwin:fix/responses-id-security-nonstreaming-response

Conversation

@AloysJehwin

Copy link
Copy Markdown

The bug (#41411)

ResponsesIDSecurity.async_post_call_streaming_iterator_hook runs:

async for chunk in response:

unconditionally. But a streaming chat request can still produce a single, non-streaming ModelResponse — e.g. when websearch_interception runs an agentic loop that resolves the turn without streaming. ModelResponse has no __aiter__, so the hook raises:

TypeError: 'async for' requires an object with __aiter__ method, got ModelResponse

Reported on v1.100.0 and v1.101.0, with both an OpenAI-compatible model and anthropic.claude-haiku-4-5, using a SearXNG search provider.

The fix

Guard for the non-async-iterable case: if response has no __aiter__, encrypt it if the responses-id-security rules apply and yield it as the single item; otherwise iterate as before. The per-chunk encryption logic is unchanged — it's factored into a local _encrypt used by both paths, so streaming behaviour and the responses-API id encryption are identical to before.

Test

TestStreamingIteratorNonStreamingResponse::test_non_streaming_response_passed_through feeds a plain ModelResponse to the hook and asserts it comes back as a single item. Verified it fails on main with the exact TypeError: 'async for' requires an object with __aiter__ method, got ModelResponse, and passes with the change. The full tests/test_litellm/test_responses_id_security.py file passes (65 passed, 2 pre-existing skips). No new ruff errors introduced (the file's pre-existing B010/SIM103 findings are untouched and out of scope).

Fixes #41411

@AloysJehwin
AloysJehwin requested a review from a team September 21, 2026 17:49
@CLAassistant

CLAassistant commented Sep 21, 2026 •

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@codspeed

codspeed Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing AloysJehwin:fix/responses-id-security-nonstreaming-response (fbe9219) with main (569ccae)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (97a6c27) during the generation of this report, so 569ccae was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@greptile-apps

greptile-apps Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

The behavioral fix appears correct, but the repository’s explicit typing and source-comment requirements must be satisfied before merging

Findings

  1. P2 Avoid Any annotations ▶
  2. P2 Redundant test docstring ▶

Summary

This PR allows the streaming iterator hook to handle a single non-streaming model response without attempting asynchronous iteration

  • Factors the existing conditional response-ID encryption into a local helper shared by both response paths
  • Adds a focused regression test proving that a non-streaming chat response is yielded once without raising
  • Requires small repository-style fixes to the helper typing and test documentation

Reviews (1) · Last reviewed commit: "fix(responses_id_security): handle a non..."

request_encryption_cache: Final[dict[str, str]] = {}

async for chunk in response:
def _encrypt(chunk: Any) -> Any:

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.

P2 Avoid Any annotations

The helper adds Any parameter and return annotations, violating the repository's strong typing requirement. This requirement must be met before merging

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +1099 to +1104
class TestStreamingIteratorNonStreamingResponse:
"""A streaming chat request can still hand this hook a single, non-streaming
ModelResponse — e.g. when an agentic loop (websearch_interception) resolves the
turn without streaming. That object has no __aiter__, so `async for` used to
raise `TypeError: 'async for' requires ... got ModelResponse` (#41411). The hook
must pass such a response through as one item."""

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.

P2 Redundant test docstring

This docstring repeats implementation and failure context, violating the repository rule that source comments must explain complex business logic. Remove it before merging

Suggested change
class TestStreamingIteratorNonStreamingResponse:
"""A streaming chat request can still hand this hook a single, non-streaming
ModelResponse — e.g. when an agentic loop (websearch_interception) resolves the
turn without streaming. That object has no __aiter__, so `async for` used to
raise `TypeError: 'async for' requires ... got ModelResponse` (#41411). The hook
must pass such a response through as one item."""
class TestStreamingIteratorNonStreamingResponse:

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@codecov

codecov Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 25.00000% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/hooks/responses_id_security.py 25.00% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

…reaming iterator hook

async_post_call_streaming_iterator_hook did 'async for chunk in
response' unconditionally. A streaming chat request can still hand it a
single, non-streaming ModelResponse — e.g. when an agentic loop
(websearch_interception) resolves the turn without streaming. That
object has no __aiter__, so the hook raised TypeError. Wrap a single
non-streaming response as a one-item async stream (_single_chunk) and
iterate only when the response is actually async iterable.

Fixes BerriAI#41411

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: websearch_interception with stream=true crashes: ModelResponse is not async iterable

2 participants