BerriAI/litellm · error · ValueError
WebSearchInterception: missing follow-up messages
Error message
WebSearchInterception: missing follow-up messages
What it means
In the legacy (non-streaming) Anthropic web-search interception path, the handler builds a request patch (search results + follow-up messages) and then requires patch.messages to be non-None before re-calling the model. If _build_anthropic_request_patch produced no follow-up message list — e.g. the tool call had no usable search query or the patch builder hit a shape it could not extend — it raises this ValueError.
Source
Thrown at litellm/integrations/websearch_interception/handler.py:1188
tool_calls: list[dict],
thinking_blocks: list[dict],
anthropic_messages_optional_request_params: dict,
logging_obj: "LiteLLMLoggingObj | None",
stream: bool,
kwargs: dict,
) -> "AnthropicMessagesResponse | AsyncIterator[object]":
"""Legacy path: execute search + build patch + run follow-up call."""
request_patch, structured_results = await self._build_anthropic_request_patch(
model=model,
messages=messages,
tool_calls=tool_calls,
thinking_blocks=thinking_blocks,
anthropic_messages_optional_request_params=anthropic_messages_optional_request_params,
logging_obj=logging_obj,
kwargs=kwargs,
)
if request_patch.messages is None:
raise ValueError("WebSearchInterception: missing follow-up messages")
optional_params: Final = dict(anthropic_messages_optional_request_params)
optional_params.update(request_patch.optional_params)
max_tokens = request_patch.max_tokens
if max_tokens is None:
max_tokens = cast(int | None, optional_params.pop("max_tokens", None))
else:
optional_params.pop("max_tokens", None)
if max_tokens is None:
max_tokens = cast(int, kwargs.get("max_tokens", 1024))
response: AnthropicMessagesResponse | AsyncIterator[object] = await anthropic_messages.acreate(
max_tokens=max_tokens,
messages=request_patch.messages,
model=request_patch.model or model,
**optional_params,
**request_patch.kwargs,
)View on GitHub (pinned to 6c2dcb801b)
Solutions
- Inspect the tool_calls/thinking_blocks passed in — the web_search tool call must carry a valid query input
- Upgrade litellm; the interception patch builder is actively fixed for edge-case message shapes
- If you control the caller, validate that each web_search tool_use has non-empty input before enabling interception
Example fix
# before — assistant tool call with empty input
messages = [
{"role": "assistant", "content": [
{"type": "tool_use", "id": "tu_1", "name": "web_search", "input": {}}
]},
]
# -> ValueError: WebSearchInterception: missing follow-up messages
# after
messages = [
{"role": "assistant", "content": [
{"type": "tool_use", "id": "tu_1", "name": "web_search",
"input": {"query": "latest litellm release notes"}}
]},
] Defensive patterns
Strategy: try-catch
Validate before calling
def has_valid_web_search_tool_use(messages: list[dict]) -> bool:
for msg in messages:
for block in (msg.get("content") or [] if isinstance(msg.get("content"), list) else []):
if isinstance(block, dict) and block.get("type") == "tool_use" and block.get("name") == "web_search":
if not block.get("input", {}).get("query"):
return False
return True
assert has_valid_web_search_tool_use(messages), "web_search tool_use blocks need a non-empty query" Type guard
from typing import Any, TypeGuard
def is_web_search_tool_use(block: Any) -> TypeGuard[dict]:
return (
isinstance(block, dict)
and block.get("type") == "tool_use"
and block.get("name") == "web_search"
and isinstance(block.get("input"), dict)
and bool(block["input"].get("query"))
) Try / catch
try:
result = await handler._legacy_search_and_follow_up(
model, messages, tool_calls, thinking_blocks, optional_params, logging_obj, kwargs
)
except ValueError as e:
if "missing follow-up messages" in str(e):
# fall back to a direct anthropic call without interception
result = await anthropic_messages.acreate(**original_kwargs)
else:
raise Prevention
- Validate replayed/hand-built Anthropic message arrays for well-formed tool_use blocks before enabling interception
- Prefer the streaming interception path over the legacy path when available — it is more tolerant of odd shapes
- Pin your litellm version against a regression suite that includes web-search conversations
When it happens
Trigger: An assistant message contains a web_search tool_use block whose input is empty/unparsable, so no search results and no follow-up user message are generated; a client supplies a hand-crafted message list with malformed server_tool_use/web_search_tool_result blocks; version drift between the handler and the anthropic SDK message shapes.
Common situations: Replaying captured Anthropic conversations through the interception handler; prompts where the model emitted a tool call missing the 'query' field; partial writes/truncation of the messages array in middleware.
Related errors
- Invalid first message. Should always start with 'role'='user
- Unable to parse anthropic tool result for message: {message}
- Unable to parse anthropic file message: {message}
- Either file_data or file_id must be present in the file mess
- Unsupported type {type(value)} for key tool_calls in message
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/2a73d80ac6a98457.
Report an issue: GitHub.