Summary
@langchain/aws's ChatBedrockConverse throws Unsupported content block type: tool_call when an assistant message's content array contains a tool_call block. Its convertAIMessageToConverseMessage iterates content blocks and throws on any unrecognized type — but it already serializes tool calls correctly from the separate msg.tool_calls field a few lines later. So a tool_call content block is a duplicate that should be skipped, not thrown on: the converter rejects a message shape the same class produces.
Version
@langchain/aws@1.4.4
Where
dist/utils/message_inputs.js → convertAIMessageToConverseMessage:
concatenatedBlocks.forEach((block) => {
if (block.type === "text" && block.text !== "") { /* ... */ }
else if (block.type === "reasoning" || block.type === "reasoning_content") { /* ... */ }
else if (isDefaultCachePoint(block)) contentBlocks.push(convertCachePointBlock(block));
else {
const blockValues = Object.fromEntries(Object.entries(block).filter(([key]) => key !== "type"));
throw new Error(`Unsupported content block type: ${block.type} with content of ${JSON.stringify(blockValues, null, 2)}`);
}
});
// ...a few lines down, tool calls are handled correctly:
if (msg.tool_calls && msg.tool_calls.length) assistantMsg.content = [
...assistantMsg.content,
...msg.tool_calls.map((tc) => ({ toolUse: { toolUseId: tc.id, name: tc.name, input: tc.args } })),
];
When a prior assistant turn's message carries a tool_call (or tool_use) content block and is replayed from history, the forEach hits the else and throws.
Repro
Run an agent (e.g. a LangGraph/deepagents react loop) against ChatBedrockConverse where an assistant message ends up with a tool_call block in content (not only in .tool_calls). On the next turn, converting history throws:
Unsupported content block type: tool_call with content of { "id": "...", "name": "write_file", "args": { ... } }
Observed deterministically on the finalize turn of every run in our setup (openwiki@0.2.5 on Bedrock us.anthropic.claude-sonnet-5).
Suggested fix
Skip tool_call / tool_use content blocks in the content loop (they are already emitted from msg.tool_calls), rather than throwing:
else if (block.type === "tool_call" || block.type === "tool_use") { /* handled via msg.tool_calls below */ }
else { /* existing throw */ }
Applying exactly this one-branch skip (to both message_inputs.js and message_inputs.cjs) resolved it with no duplicate/missing toolUse blocks.
Summary
@langchain/aws'sChatBedrockConversethrowsUnsupported content block type: tool_callwhen an assistant message'scontentarray contains atool_callblock. ItsconvertAIMessageToConverseMessageiterates content blocks and throws on any unrecognizedtype— but it already serializes tool calls correctly from the separatemsg.tool_callsfield a few lines later. So atool_callcontent block is a duplicate that should be skipped, not thrown on: the converter rejects a message shape the same class produces.Version
@langchain/aws@1.4.4Where
dist/utils/message_inputs.js→convertAIMessageToConverseMessage:When a prior assistant turn's message carries a
tool_call(ortool_use) content block and is replayed from history, theforEachhits theelseand throws.Repro
Run an agent (e.g. a LangGraph/deepagents react loop) against
ChatBedrockConversewhere an assistant message ends up with atool_callblock incontent(not only in.tool_calls). On the next turn, converting history throws:Observed deterministically on the finalize turn of every run in our setup (
openwiki@0.2.5on Bedrockus.anthropic.claude-sonnet-5).Suggested fix
Skip
tool_call/tool_usecontent blocks in thecontentloop (they are already emitted frommsg.tool_calls), rather than throwing:Applying exactly this one-branch skip (to both
message_inputs.jsandmessage_inputs.cjs) resolved it with no duplicate/missingtoolUseblocks.