fix: normalize ToolCall arguments to a map when serializing tool calls#581
Merged
Conversation
`ToolCall.arguments` is only parsed from a JSON string into a map by
`complete/1`, which runs when a tool call reaches `status: :complete`. A
tool call serialized without going through that path (a persisted/replayed
`:incomplete` call, or one accumulated from a truncated stream) can still
hold a raw string. Anthropic's `for_api/1` guarded this with
`call.arguments || %{}`, but `"" || %{}` is `""` and a non-empty JSON
string is truthy, so the string was sent verbatim and the API rejected the
request with "messages.N.content.M.tool_use.input: Input should be an object".
Add a shared `ToolCall.arguments_as_map/1` helper that normalizes any shape
(map, JSON-object string, nil, empty/invalid/non-object string) into a map,
and use it in the three providers that pass arguments straight through:
ChatAnthropic (`input`), ChatGoogleAI and ChatVertexAI (`args`). Google and
Vertex previously sent `args: nil` for zero-argument calls; they now send an
empty object, which is the correct representation.
Closes #580
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Closes #580
Problem
When an assistant
Messagereaches a chat model with aToolCallwhoseargumentsis still a string (rather than a parsed map), the serialized request sends the tool input as a string and the provider rejects the whole request. On Anthropic:ToolCall.argumentsis only parsed from string → map byvalidate_and_parse_arguments/1, which runs only whenstatus: :complete(viaToolCall.complete/1). Any flow that puts aToolCallinto an outgoing message without going through that completion path still holds a string:status: :incomplete(agent frameworks that serialize state and replay history)tool_useJSON was truncated beforecontent_block_stop""rather than"{}"Anthropic's
for_api/1guarded this withcall.arguments || %{}, but that only substitutes fornil/false."" || %{}is"", and a non-empty JSON string is truthy, so the string was sent verbatim.Root cause is broader than Anthropic
ChatGoogleAIandChatVertexAIhave the same passthrough ("args" => call.arguments) and the same latent bug. (The OpenAI-family providers useJason.encode!/1, a different code path, and are left unchanged.)Fix
Add a shared, documented
ToolCall.arguments_as_map/1helper that normalizes any shape into a map:nil, empty string, and undecodable / non-object strings become%{}and use it in the three providers that pass arguments straight through:
ChatAnthropic—tool_use.inputChatGoogleAI/ChatVertexAI—functionCall.args(both the plain andthoughtSignatureclauses)Google and Vertex previously sent
args: nilfor zero-argument calls; they now send an empty object, which is the correct representation and consistent with the Anthropic behavior.Backward compatible: maps pass through unchanged, so existing serialization is unaffected.
Tests
ToolCall.arguments_as_map/1unit tests + doctests covering map / JSON string / nil / empty / invalid / non-object.ChatAnthropic.for_api/1regression tests for a string-arguments call (→ object) and an empty-string call (→ empty object).35 doctests, 2017 tests, 0 failures.🤖 Generated with Claude Code