fix(websearch): wrap agentic loop response in fake stream when original request was streaming

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.

This fix adds _maybe_wrap_in_fake_stream() which checks the
websearch_interception_converted_stream flag and wraps the dict response
in FakeAnthropicMessagesStreamIterator when needed.

Applied to both code paths in _call_agentic_completion_hooks:
- async_run_agentic_loop (legacy path)
- _execute_anthropic_agentic_plan (plan-based path)

Fixes: streaming websearch requests via /v1/messages returning empty
events when websearch_interception callback is enabled for bedrock.
This commit is contained in:
Clawd 2026-05-07 18:34:58 +08:00
parent 6ff668c7aa
commit 6ee175dd24

View file

@ -4740,6 +4740,42 @@ class BaseLLMHTTPHandler:
**kwargs_for_followup,
)
def _maybe_wrap_in_fake_stream(
self,
response: Any,
logging_obj: "LiteLLMLoggingObj",
) -> Any:
"""
If the original request was streaming but converted to non-streaming for
WebSearch interception, wrap the dict response in a FakeAnthropicMessagesStreamIterator.
"""
websearch_converted_stream = (
logging_obj.model_call_details.get(
"websearch_interception_converted_stream", False
)
if logging_obj is not None
else False
)
if websearch_converted_stream and isinstance(response, dict):
from typing import cast
from litellm._logging import verbose_logger
from litellm.llms.anthropic.experimental_pass_through.messages.fake_stream_iterator import (
FakeAnthropicMessagesStreamIterator,
)
from litellm.types.llms.anthropic_messages.anthropic_response import (
AnthropicMessagesResponse,
)
verbose_logger.debug(
"WebSearchInterception: Agentic loop completed, "
"converting non-streaming response to fake stream"
)
return FakeAnthropicMessagesStreamIterator(
response=cast(AnthropicMessagesResponse, response)
)
return response
async def _call_agentic_completion_hooks(
self,
response: Any,
@ -4821,7 +4857,7 @@ class BaseLLMHTTPHandler:
is not CustomLogger.async_build_agentic_loop_plan
)
if not build_plan_overridden:
return await callback.async_run_agentic_loop(
agentic_result = await callback.async_run_agentic_loop(
tools=tool_calls,
model=model,
messages=messages,
@ -4832,6 +4868,9 @@ class BaseLLMHTTPHandler:
stream=stream,
kwargs=kwargs_with_provider,
)
return self._maybe_wrap_in_fake_stream(
agentic_result, logging_obj
)
plan = await callback.async_build_agentic_loop_plan(
tools=tool_calls,
@ -4857,18 +4896,21 @@ class BaseLLMHTTPHandler:
if not plan.run_agentic_loop:
continue
return await self._execute_anthropic_agentic_plan(
plan=plan,
model=model,
messages=messages,
anthropic_messages_optional_request_params=anthropic_messages_optional_request_params,
logging_obj=logging_obj,
kwargs=kwargs_with_provider,
depth=depth,
max_loops=max_loops,
fingerprints=fingerprints,
fingerprint=fingerprint,
stream=stream,
return self._maybe_wrap_in_fake_stream(
await self._execute_anthropic_agentic_plan(
plan=plan,
model=model,
messages=messages,
anthropic_messages_optional_request_params=anthropic_messages_optional_request_params,
logging_obj=logging_obj,
kwargs=kwargs_with_provider,
depth=depth,
max_loops=max_loops,
fingerprints=fingerprints,
fingerprint=fingerprint,
stream=stream,
),
logging_obj,
)
except Exception as e:
_call_id = getattr(logging_obj, "litellm_call_id", "unknown")