Summary
A malformed tool-call argument string can be persisted as a normal UI tool part with input as a string. When that conversation is later replayed to an Anthropic model, the provider rejects the historical tool use with:
messages.N.content.M.tool_use.input: Input should be a valid dictionary
This appears to happen after the AI SDK marks a tool call as invalid because its JSON arguments could not be parsed. VoltAgent then converts that invalid model tool call into a UI tool part and persists the raw string under input.
Environment
Observed in an app using:
@voltagent/core: 2.7.4
@voltagent/postgres: 2.1.2
ai: 6.0.190
@ai-sdk/provider-utils: 4.0.27
Anthropic via Google Vertex
Investigation notes
1. Production error in logs
We first found a stream error in production logs:
AI_APICallError: messages.1.content.1.tool_use.input: Input should be a valid dictionary
The failing request was a continuation of a conversation that had a previous assistant tool call.
2. The stored conversation message had a string input
In the Postgres memory table (voltagent_memory_messages), the assistant message contained a tool UI part like this:
{
"type": "tool-createImageFromText",
"toolCallId": "toolu_...",
"state": "output-available",
"input": "{\"prompts\": [\"Full body fashion editorial portrait of a stunning 5'7\" woman ...\"], \"imageSize\": \"1024x1536\"}",
"output": {
"type": "error-text",
"value": "Invalid input for tool createImageFromText: JSON parsing failed: ... Expected ',' or ']' after array element ..."
},
"providerExecuted": false
}
The important part is that input is a JSON string, not an object. In this real case the string was also malformed because the prompt included an unescaped quote: 5'7" woman.
3. Normal successful messages store input as an object
Later successful tool calls in the same conversation were stored like this:
{
"type": "tool-createImageFromText",
"toolCallId": "toolu_...",
"state": "output-available",
"input": {
"prompts": ["..."],
"imageSize": "1024x1536"
},
"output": {
"type": "text",
"value": "[\"https://...\"]"
},
"providerExecuted": false
}
So the issue is not that Postgres JSONB always stringifies tool inputs. It only happened for the invalid/malformed tool call.
4. AI SDK appears to preserve raw malformed input on invalid tool calls
Looking at the installed ai package, parseToolCall catches parse failures and returns an invalid tool call with the raw string as input:
const parsedInput = await safeParseJSON({ text: toolCall.input });
const input = parsedInput.success ? parsedInput.value : toolCall.input;
return {
type: "tool-call",
toolCallId: toolCall.toolCallId,
toolName: toolCall.toolName,
input,
dynamic: true,
invalid: true,
error,
providerExecuted: toolCall.providerExecuted,
providerMetadata: toolCall.providerMetadata,
};
This explains why the raw malformed string exists after parsing failed.
5. VoltAgent converts that model message to a UI message and stores the raw string input
In @voltagent/core, convertModelMessagesToUIMessages converts model tool calls like this:
case "tool-call": {
const toolPart = {
type: `tool-${contentPart.toolName}`,
toolCallId: contentPart.toolCallId,
state: "input-available",
input: contentPart.input || {},
...contentPart.providerOptions ? { callProviderMetadata: contentPart.providerOptions } : {},
...contentPart.providerExecuted != null ? { providerExecuted: contentPart.providerExecuted } : {}
};
ui.parts.push(toolPart);
break;
}
Because contentPart.input is the raw string from the invalid AI SDK tool call, the UI message gets input as a string.
The persisted message did not appear to preserve the invalid: true / error information from the parsed model tool call, so on later replay this stored part looks like a normal tool call except that input is a string.
6. Replay to Anthropic fails
When that persisted conversation is used as history in a later model call, the tool part is converted/replayed as an Anthropic tool_use. Anthropic requires tool_use.input to be a dictionary/object, not a string, so it returns:
Input should be a valid dictionary
Impact
A single malformed tool-call argument string can poison the persisted conversation history. Future turns may fail at the model API layer even if the user simply sends a follow-up message such as "continue".
The original tool parsing error was understandable, but the confusing part is that the invalid raw input was persisted/replayed in a form that later triggers a provider-level schema error.
Summary
A malformed tool-call argument string can be persisted as a normal UI tool part with
inputas a string. When that conversation is later replayed to an Anthropic model, the provider rejects the historical tool use with:This appears to happen after the AI SDK marks a tool call as invalid because its JSON arguments could not be parsed. VoltAgent then converts that invalid model tool call into a UI tool part and persists the raw string under
input.Environment
Observed in an app using:
Investigation notes
1. Production error in logs
We first found a stream error in production logs:
The failing request was a continuation of a conversation that had a previous assistant tool call.
2. The stored conversation message had a string
inputIn the Postgres memory table (
voltagent_memory_messages), the assistant message contained a tool UI part like this:{ "type": "tool-createImageFromText", "toolCallId": "toolu_...", "state": "output-available", "input": "{\"prompts\": [\"Full body fashion editorial portrait of a stunning 5'7\" woman ...\"], \"imageSize\": \"1024x1536\"}", "output": { "type": "error-text", "value": "Invalid input for tool createImageFromText: JSON parsing failed: ... Expected ',' or ']' after array element ..." }, "providerExecuted": false }The important part is that
inputis a JSON string, not an object. In this real case the string was also malformed because the prompt included an unescaped quote:5'7" woman.3. Normal successful messages store
inputas an objectLater successful tool calls in the same conversation were stored like this:
{ "type": "tool-createImageFromText", "toolCallId": "toolu_...", "state": "output-available", "input": { "prompts": ["..."], "imageSize": "1024x1536" }, "output": { "type": "text", "value": "[\"https://...\"]" }, "providerExecuted": false }So the issue is not that Postgres JSONB always stringifies tool inputs. It only happened for the invalid/malformed tool call.
4. AI SDK appears to preserve raw malformed input on invalid tool calls
Looking at the installed
aipackage,parseToolCallcatches parse failures and returns an invalid tool call with the raw string asinput:This explains why the raw malformed string exists after parsing failed.
5. VoltAgent converts that model message to a UI message and stores the raw string input
In
@voltagent/core,convertModelMessagesToUIMessagesconverts model tool calls like this:Because
contentPart.inputis the raw string from the invalid AI SDK tool call, the UI message getsinputas a string.The persisted message did not appear to preserve the
invalid: true/errorinformation from the parsed model tool call, so on later replay this stored part looks like a normal tool call except thatinputis a string.6. Replay to Anthropic fails
When that persisted conversation is used as history in a later model call, the tool part is converted/replayed as an Anthropic
tool_use. Anthropic requirestool_use.inputto be a dictionary/object, not a string, so it returns:Impact
A single malformed tool-call argument string can poison the persisted conversation history. Future turns may fail at the model API layer even if the user simply sends a follow-up message such as "continue".
The original tool parsing error was understandable, but the confusing part is that the invalid raw input was persisted/replayed in a form that later triggers a provider-level schema error.