{"record":{"id":"2a73d80ac6a98457","repo":"BerriAI/litellm","slug":"websearchinterception-missing-follow-up-messages","errorCode":null,"errorMessage":"WebSearchInterception: missing follow-up messages","messagePattern":"WebSearchInterception: missing follow-up messages","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/integrations/websearch_interception/handler.py","lineNumber":1188,"sourceCode":"        tool_calls: list[dict],\n        thinking_blocks: list[dict],\n        anthropic_messages_optional_request_params: dict,\n        logging_obj: \"LiteLLMLoggingObj | None\",\n        stream: bool,\n        kwargs: dict,\n    ) -> \"AnthropicMessagesResponse | AsyncIterator[object]\":\n        \"\"\"Legacy path: execute search + build patch + run follow-up call.\"\"\"\n        request_patch, structured_results = await self._build_anthropic_request_patch(\n            model=model,\n            messages=messages,\n            tool_calls=tool_calls,\n            thinking_blocks=thinking_blocks,\n            anthropic_messages_optional_request_params=anthropic_messages_optional_request_params,\n            logging_obj=logging_obj,\n            kwargs=kwargs,\n        )\n        if request_patch.messages is None:\n            raise ValueError(\"WebSearchInterception: missing follow-up messages\")\n\n        optional_params: Final = dict(anthropic_messages_optional_request_params)\n        optional_params.update(request_patch.optional_params)\n        max_tokens = request_patch.max_tokens\n        if max_tokens is None:\n            max_tokens = cast(int | None, optional_params.pop(\"max_tokens\", None))\n        else:\n            optional_params.pop(\"max_tokens\", None)\n        if max_tokens is None:\n            max_tokens = cast(int, kwargs.get(\"max_tokens\", 1024))\n\n        response: AnthropicMessagesResponse | AsyncIterator[object] = await anthropic_messages.acreate(\n            max_tokens=max_tokens,\n            messages=request_patch.messages,\n            model=request_patch.model or model,\n            **optional_params,\n            **request_patch.kwargs,\n        )","sourceCodeStart":1170,"sourceCodeEnd":1206,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/integrations/websearch_interception/handler.py#L1170-L1206","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before — assistant tool call with empty input\nmessages = [\n    {\"role\": \"assistant\", \"content\": [\n        {\"type\": \"tool_use\", \"id\": \"tu_1\", \"name\": \"web_search\", \"input\": {}}\n    ]},\n]\n# -> ValueError: WebSearchInterception: missing follow-up messages\n\n# after\nmessages = [\n    {\"role\": \"assistant\", \"content\": [\n        {\"type\": \"tool_use\", \"id\": \"tu_1\", \"name\": \"web_search\",\n         \"input\": {\"query\": \"latest litellm release notes\"}}\n    ]},\n]","handlingStrategy":"try-catch","validationCode":"def has_valid_web_search_tool_use(messages: list[dict]) -> bool:\n    for msg in messages:\n        for block in (msg.get(\"content\") or [] if isinstance(msg.get(\"content\"), list) else []):\n            if isinstance(block, dict) and block.get(\"type\") == \"tool_use\" and block.get(\"name\") == \"web_search\":\n                if not block.get(\"input\", {}).get(\"query\"):\n                    return False\n    return True\n\nassert has_valid_web_search_tool_use(messages), \"web_search tool_use blocks need a non-empty query\"","typeGuard":"from typing import Any, TypeGuard\n\ndef is_web_search_tool_use(block: Any) -> TypeGuard[dict]:\n    return (\n        isinstance(block, dict)\n        and block.get(\"type\") == \"tool_use\"\n        and block.get(\"name\") == \"web_search\"\n        and isinstance(block.get(\"input\"), dict)\n        and bool(block[\"input\"].get(\"query\"))\n    )","tryCatchPattern":"try:\n    result = await handler._legacy_search_and_follow_up(\n        model, messages, tool_calls, thinking_blocks, optional_params, logging_obj, kwargs\n    )\nexcept ValueError as e:\n    if \"missing follow-up messages\" in str(e):\n        # fall back to a direct anthropic call without interception\n        result = await anthropic_messages.acreate(**original_kwargs)\n    else:\n        raise","preventionTips":["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"],"tags":["anthropic","web-search","interception","tool-calls","validation"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}