Skip to content

fix(websearch): wrap agentic loop response in fake stream for streaming requests - #27449

Closed
vokako wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
vokako:fix/websearch-streaming-v2
Closed

fix(websearch): wrap agentic loop response in fake stream for streaming requests#27449
vokako wants to merge 1 commit into
BerriAI:litellm_internal_stagingfrom
vokako:fix/websearch-streaming-v2

Conversation

@vokako

@vokako vokako commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

When websearch_interception is enabled and a client sends a streaming request via /v1/messages, the handler converts stream=True to stream=False internally to execute the search. After the agentic loop completes, the non-streaming dict response was returned directly to the client expecting SSE events, resulting in empty streams.

Root Cause

In _call_agentic_completion_hooks(), the FakeAnthropicMessagesStreamIterator wrapping code only executes when no agentic loop ran. When the agentic loop does run, all return paths return the dict directly without wrapping.

Fix

Added _maybe_wrap_in_fake_stream() helper that checks the websearch_interception_converted_stream flag and wraps dict responses in FakeAnthropicMessagesStreamIterator. Applied to all return paths in _call_agentic_completion_hooks:

  • async_run_agentic_loop (legacy path)
  • _execute_anthropic_agentic_plan (plan-based path)
  • plan.response_override
  • plan.terminate

Testing

  • Unit tests added for _maybe_wrap_in_fake_stream()
  • Verified with anthropic SDK client.messages.stream() + websearch tool:
    • Before fix: stream completes with 0 text chars (no events received)
    • After fix: stream correctly delivers text_delta events and message_stop

Changes

  • litellm/llms/custom_httpx/llm_http_handler.py: Added _maybe_wrap_in_fake_stream() method, wrapped all agentic loop return paths
  • tests/test_litellm/integrations/websearch_interception/test_websearch_streaming_wrap.py: Unit tests

@codecov

codecov Bot commented May 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 55.55556% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/llms/custom_httpx/llm_http_handler.py 55.55% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where streaming requests routed through websearch_interception received an empty stream after an agentic loop completed, because every early-return path inside _call_agentic_completion_hooks returned the raw dict directly instead of wrapping it in FakeAnthropicMessagesStreamIterator.

  • Extracts a _maybe_wrap_in_fake_stream() helper that checks websearch_interception_converted_stream in logging_obj.model_call_details and wraps dict responses; non-dict values (already-streaming objects, None) are returned unchanged.
  • Applies the helper to all four agentic-loop return paths: async_run_agentic_loop, _execute_anthropic_agentic_plan, plan.response_override, and plan.terminate.
  • Adds focused unit tests covering the flag-true/false, non-dict, and None-logging-obj cases; no real network calls.

Confidence Score: 4/5

The change is safe to merge; it fixes a clearly broken streaming path without touching any other request flow.

All four agentic-loop exit paths are correctly guarded. The isinstance(response, dict) check prevents double-wrapping when a recursive agentic iteration already returned an iterator. The only gap is that the no-loop-ran fallback at the bottom of _call_agentic_completion_hooks still inlines the same detection logic instead of delegating to the new helper, leaving duplicate code that could drift.

The inline duplication in _call_agentic_completion_hooks around lines 4929-4966 should be cleaned up.

Important Files Changed

Filename Overview
litellm/llms/custom_httpx/llm_http_handler.py Adds _maybe_wrap_in_fake_stream() helper and applies it to all four agentic-loop return paths; the existing no-loop path at the bottom of the function still inlines the same logic rather than delegating to the helper.
tests/test_litellm/integrations/websearch_interception/test_websearch_streaming_wrap.py New unit tests for _maybe_wrap_in_fake_stream; all four branches covered, mock-only with no real network calls.

Comments Outside Diff (1)

  1. litellm/llms/custom_httpx/llm_http_handler.py, line 4929-4966 (link)

    P2 The "no agentic loop ran" fallback at the end of _call_agentic_completion_hooks duplicates the same flag-check and wrapping logic that was just extracted into _maybe_wrap_in_fake_stream. Using the helper here too would remove the duplication and make future maintenance easier — any change to the wrapping logic only needs to happen in one place.

Reviews (1): Last reviewed commit: "fix(websearch): wrap agentic loop respon..." | Re-trigger Greptile

@vokako
vokako force-pushed the fix/websearch-streaming-v2 branch 2 times, most recently from 14c0b6a to fe1ff75 Compare May 8, 2026 07:26
…ng requests

When websearch_interception converts stream=True to stream=False internally,
the agentic loop returns a plain dict. Previously this dict was returned
directly to the client expecting SSE events, resulting in empty streams.

Added _maybe_wrap_in_fake_stream() which checks the
websearch_interception_converted_stream flag and wraps dict responses in
FakeAnthropicMessagesStreamIterator. Applied to all return paths in
_call_agentic_completion_hooks:
- async_run_agentic_loop (legacy path)
- _execute_anthropic_agentic_plan (plan-based path)
- plan.response_override
- plan.terminate

Includes unit tests for _maybe_wrap_in_fake_stream().
@mateo-berri

mateo-berri commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

This has been merged. We've credited you for this change. Thank you so much for your contribution!

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.

2 participants