{"record":{"id":"d6feb50d34f73219","repo":"langchain-ai/langchain","slug":"cannot-concatenate-toolmessagechunks-with-differen","errorCode":null,"errorMessage":"Cannot concatenate ToolMessageChunks with different names.","messagePattern":"Cannot concatenate ToolMessageChunks with different names\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/messages/tool.py","lineNumber":187,"sourceCode":"            )\n        else:\n            super().__init__(content=content, **kwargs)\n\n\nclass ToolMessageChunk(ToolMessage, BaseMessageChunk):\n    \"\"\"Tool Message chunk.\"\"\"\n\n    # Ignoring mypy re-assignment here since we're overriding the value\n    # to make sure that the chunk variant can be discriminated from the\n    # non-chunk variant.\n    type: Literal[\"ToolMessageChunk\"] = \"ToolMessageChunk\"  # type: ignore[assignment]\n\n    @override\n    def __add__(self, other: Any) -> BaseMessageChunk:  # type: ignore[override]\n        if isinstance(other, ToolMessageChunk):\n            if self.tool_call_id != other.tool_call_id:\n                msg = \"Cannot concatenate ToolMessageChunks with different names.\"\n                raise ValueError(msg)\n\n            return self.__class__(\n                tool_call_id=self.tool_call_id,\n                content=merge_content(self.content, other.content),\n                artifact=merge_obj(self.artifact, other.artifact),\n                additional_kwargs=merge_dicts(\n                    self.additional_kwargs, other.additional_kwargs\n                ),\n                response_metadata=merge_dicts(\n                    self.response_metadata, other.response_metadata\n                ),\n                id=self.id,\n                status=_merge_status(self.status, other.status),\n            )\n\n        return super().__add__(other)\n\n","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/messages/tool.py#L169-L205","documentation":"Raised when two `ToolMessageChunk` objects are added together (`+`) but have different `tool_call_id` values. Chunk concatenation is how langchain-core aggregates streamed tool-message fragments into one message, and it is only valid between fragments of the same tool call. Note the message text says 'names' but the check is actually on `tool_call_id`.","triggerScenarios":"Calling `chunk1 + chunk2` (or `sum(chunks)`, or letting a streaming aggregator merge chunks) where the two `ToolMessageChunk` instances were created with different `tool_call_id` values, e.g. mixing fragments of two parallel tool calls into one accumulation buffer.","commonSituations":"Streaming agents that issue multiple tool calls per turn and accumulate all incoming chunks into a single variable without grouping by `tool_call_id`; manually re-indexing chunks after an id remapping step; writing a custom streaming callback that concatenates chunks out of order.","solutions":["Group streamed chunks by `tool_call_id` before concatenating (dict keyed by tool_call_id) instead of summing everything into one accumulator","Verify you are not merging chunks from two different tool calls returned in the same streaming response","If writing a custom aggregator, check `chunk.tool_call_id == other.tool_call_id` before `+` and raise/store separately otherwise","If you build `ToolMessageChunk` manually, pass the exact `tool_call_id` from the originating AI message's `tool_calls` entry"],"exampleFix":"# before (mixes parallel tool calls)\ntotal = None\nfor chunk in stream:\n    total = chunk if total is None else total + chunk\n\n# after\ntotals: dict[str, ToolMessageChunk] = {}\nfor chunk in stream:\n    prev = totals.get(chunk.tool_call_id)\n    totals[chunk.tool_call_id] = chunk if prev is None else prev + chunk","handlingStrategy":"validation","validationCode":"def can_concat(a: ToolMessageChunk, b: ToolMessageChunk) -> bool:\n    return a.tool_call_id == b.tool_call_id\n\n# before accumulating streamed chunks:\nif total is not None and not can_concat(total, chunk):\n    raise ValueError(f'chunk belongs to tool_call_id={chunk.tool_call_id}, buffer holds {total.tool_call_id}')\ntotal = total + chunk if total is not None else chunk","typeGuard":"from langchain_core.messages import ToolMessageChunk\n\ndef same_tool_call(a: ToolMessageChunk, b: ToolMessageChunk) -> bool:\n    return isinstance(a, ToolMessageChunk) and isinstance(b, ToolMessageChunk) and a.tool_call_id == b.tool_call_id","tryCatchPattern":"try:\n    merged = a + b\nexcept ValueError as e:\n    if 'different names' in str(e):\n        # keep chunks separate per tool_call_id instead of crashing\n        buffers[a.tool_call_id] = a\n        buffers[b.tool_call_id] = b\n    else:\n        raise","preventionTips":["Key streaming accumulation buffers by tool_call_id rather than using one running total","Never assume parallel tool calls share a tool_call_id","Log tool_call_id when streaming starts so mismatches are diagnosable"],"tags":["streaming","tool-calls","message-chunks"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}