fix(anthropic): preserve server tool content blocks during multi-step tool loop#952
Open
lorisleiva wants to merge 1 commit intoprism-php:mainfrom
Open
fix(anthropic): preserve server tool content blocks during multi-step tool loop#952lorisleiva wants to merge 1 commit intoprism-php:mainfrom
lorisleiva wants to merge 1 commit intoprism-php:mainfrom
Conversation
… tool loop ## Context When Anthropic's server-side tools (like `web_search`) are used alongside regular user-defined tools, the model can do both in a single response: perform a web search, write text with citations referencing the search results, and call a regular tool. Because a regular tool was called, Prism enters its multi-step tool loop. It executes the tool, then replays the entire conversation back to the API for the next turn. The problem is that when Prism builds the replayed assistant message, it includes the text with citations but drops the `server_tool_use` and `web_search_tool_result` content blocks that the citations reference. The API validates that every citation points to an existing search result, finds none, and rejects the request with: `invalid_request_error - Could not find search result for citation index.` This only triggers when the model performs a server-side tool call AND a regular tool call in the same response. If either happens alone, everything works fine. ## Changes Both the Text and Stream handlers had the same gap in their tool loop replay logic. **Text handler (`Text.php`):** Added `extractProviderToolContent()` that pulls `server_tool_use` and `*_tool_result` content blocks from the API response and stores them in `additionalContent` as `provider_tool_calls` and `provider_tool_results`, the same keys that `MessageMap::mapAssistantMessage()` already reads and serializes back to the API. This follows the existing pattern of `extractText()`, `extractCitations()`, and `extractThinking()`. **Stream handler (`Stream.php`):** The stream state already tracked provider tool calls, provider tool results, and citations during streaming, but `handleToolCalls()` only included `thinking` and `thinking_signature` in the replayed `AssistantMessage`'s `additionalContent`. Now it also includes `citations`, `provider_tool_calls`, and `provider_tool_results`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
When Anthropic's server-side tools (like
web_search) are used alongside regular user-defined tools, the model can do both in a single response: perform a web search, write text with citations referencing the search results, and call a regular tool. Because a regular tool was called, Prism enters its multi-step tool loop. It executes the tool, then replays the entire conversation back to the API for the next turn.The problem is that when Prism builds the replayed assistant message, it includes the text with citations but drops the
server_tool_useandweb_search_tool_resultcontent blocks that the citations reference. The API validates that every citation points to an existing search result, finds none, and rejects the request with:invalid_request_error - Could not find search result for citation index.This only triggers when the model performs a server-side tool call AND a regular tool call in the same response. If either happens alone, everything works fine.
Changes
Both the Text and Stream handlers had the same gap in their tool loop replay logic.
Text handler (
Text.php): AddedextractProviderToolContent()that pullsserver_tool_useand*_tool_resultcontent blocks from the API response and stores them inadditionalContentasprovider_tool_callsandprovider_tool_results, the same keys thatMessageMap::mapAssistantMessage()already reads and serializes back to the API. This follows the existing pattern ofextractText(),extractCitations(), andextractThinking().Stream handler (
Stream.php): The stream state already tracked provider tool calls, provider tool results, and citations during streaming, buthandleToolCalls()only includedthinkingandthinking_signaturein the replayedAssistantMessage'sadditionalContent. Now it also includescitations,provider_tool_calls, andprovider_tool_results.