Skip to content

ChatBedrockConverse throws 'Unsupported content block type: tool_call' on tool_call content blocks it should skip #11476

Description

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.jsconvertAIMessageToConverseMessage:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions