Summary
An Anthropic server-side web_search turn is persisted without its result (tool_call.output is never set), so on the next request the assistant message is replayed as plain text with no server_tool_use / web_search_tool_result blocks. The model sees a prior answer citing "search results" that, from its point of view, never happened. Newer Claude models that are candid about what they did and didn't do then tell the user the earlier answer was fabricated and that they "have no search tool", even when web_search is still declared in tools.
Observed on @librechat/agents 3.8.6 with @langchain/anthropic 1.5.2.
Reproduction
- Anthropic provider, built-in web search enabled (
web_search server tool in tools).
- Turn A: "Search the web: what is the latest stable release of Node.js? Cite the source." → the model runs
web_search, results stream, the host receives the artifact through TOOL_END, and the answer cites sources.
- Turn B (same conversation, web search still on or off — both reproduce): "Looking back at this conversation, is there a record of a web search having been run?"
Observed model reply (turn B, web_search still declared):
No, the history shows no web search call or results, only my Node.js answer describing "results" that weren't there, and I was also wrong to say I have no search tool, since web_search is available and I just didn't run it.
Captured request bodies for turn B (block types per message; str = string content):
u:text
a:str <- turn A, should be server_tool_use + web_search_tool_result + text
u:text
What is persisted after turn A
The tool_call content part for the server tool has no output and empty args:
{
"id": "srvtoolu_01…",
"name": "web_search",
"args": "",
"type": "tool_call",
"runStepStatus": "…",
"runStepDurationMs": …
}
The search results themselves only reach the host through the TOOL_END artifact (handleAnthropicSearchResults in src/tools/handlers.ts), which the host turns into attachments/sources. Nothing dispatches ON_RUN_STEP_COMPLETED for the server-tool step, so the content part never gets its output (or final args).
Why the turn is dropped on replay
formatAgentMessages (src/messages/format.ts) only replays a srvtoolu_ tool call when a matching server tool result is present in the same message (serverToolResultIds), otherwise it is skipped unless preserveUnpairedServerToolUses is set (only true for the last message). With no persisted result the server_tool_use is treated as unpaired and skipped, and the message collapses to its text.
Relevant code paths:
src/tools/handlers.ts → handleServerToolResult / handleAnthropicSearchResults: dispatches TOOL_END only.
src/events.ts → ToolEndHandler: "tool completion (ON_RUN_STEP_COMPLETED dispatch …) is handled in graph context by ToolNode directly" — server tools never go through ToolNode.
src/messages/format.ts ~L1880: !serverToolResultIds.has(id) && options.preserveUnpairedServerToolUses !== true → continue.
This looks like fallout from cac0c9e ("streamline tool completion handling in ToolNode and Graph"), which moved step completion into ToolNode; the server-tool path was not given an equivalent completion.
Expected
Either:
- complete the server-tool run step with the search result so the
tool_call part carries output (and the final args), and replay it as server_tool_use + web_search_tool_result when the provider is Anthropic; or
- when the paired result is unavailable, replay a textual trace of the call (query + result summary, or at least "used web_search with query …") rather than silently dropping the block, so the model keeps an accurate record of its own actions.
Dropping the record is worse than either option: the model's follow-up contradicts an answer the user already received.
Summary
An Anthropic server-side
web_searchturn is persisted without its result (tool_call.outputis never set), so on the next request the assistant message is replayed as plain text with noserver_tool_use/web_search_tool_resultblocks. The model sees a prior answer citing "search results" that, from its point of view, never happened. Newer Claude models that are candid about what they did and didn't do then tell the user the earlier answer was fabricated and that they "have no search tool", even whenweb_searchis still declared intools.Observed on
@librechat/agents3.8.6 with@langchain/anthropic1.5.2.Reproduction
web_searchserver tool intools).web_search, results stream, the host receives the artifact throughTOOL_END, and the answer cites sources.Observed model reply (turn B,
web_searchstill declared):Captured request bodies for turn B (block types per message;
str= string content):What is persisted after turn A
The
tool_callcontent part for the server tool has nooutputand emptyargs:{ "id": "srvtoolu_01…", "name": "web_search", "args": "", "type": "tool_call", "runStepStatus": "…", "runStepDurationMs": … }The search results themselves only reach the host through the
TOOL_ENDartifact (handleAnthropicSearchResultsinsrc/tools/handlers.ts), which the host turns into attachments/sources. Nothing dispatchesON_RUN_STEP_COMPLETEDfor the server-tool step, so the content part never gets itsoutput(or finalargs).Why the turn is dropped on replay
formatAgentMessages(src/messages/format.ts) only replays asrvtoolu_tool call when a matching server tool result is present in the same message (serverToolResultIds), otherwise it is skipped unlesspreserveUnpairedServerToolUsesis set (only true for the last message). With no persisted result theserver_tool_useis treated as unpaired and skipped, and the message collapses to its text.Relevant code paths:
src/tools/handlers.ts→handleServerToolResult/handleAnthropicSearchResults: dispatchesTOOL_ENDonly.src/events.ts→ToolEndHandler: "tool completion (ON_RUN_STEP_COMPLETED dispatch …) is handled in graph context by ToolNode directly" — server tools never go throughToolNode.src/messages/format.ts~L1880:!serverToolResultIds.has(id) && options.preserveUnpairedServerToolUses !== true → continue.This looks like fallout from cac0c9e ("streamline tool completion handling in ToolNode and Graph"), which moved step completion into
ToolNode; the server-tool path was not given an equivalent completion.Expected
Either:
tool_callpart carriesoutput(and the finalargs), and replay it asserver_tool_use+web_search_tool_resultwhen the provider is Anthropic; orDropping the record is worse than either option: the model's follow-up contradicts an answer the user already received.