{"record":{"id":"404f497043b7383e","repo":"deepset-ai/haystack","slug":"tool-message-must-contain-only-one-tool-call-resul","errorCode":null,"errorMessage":"Tool message must contain only one tool call result.","messagePattern":"Tool message must contain only one tool call result\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"haystack/utils/jinja2_chat_extension.py","lineNumber":397,"sourceCode":"            reasoning = [part for part in parts if isinstance(part, ReasoningContent)]\n            if len(texts) > 1:\n                raise ValueError(\"Assistant message must contain one text part at most.\")\n            if len(texts) == 0 and len(tool_calls) == 0:\n                raise ValueError(\"Assistant message must contain at least one text or tool call part.\")\n            if len(parts) > len(texts) + len(tool_calls) + len(reasoning):\n                raise ValueError(\"Assistant message must contain only text, tool call or reasoning parts.\")\n            return ChatMessage.from_assistant(\n                meta=meta,\n                name=name,\n                text=texts[0] if texts else None,\n                tool_calls=tool_calls or None,\n                reasoning=reasoning[0] if reasoning else None,\n            )\n\n        if role == \"tool\":\n            tool_call_results = [part for part in parts if isinstance(part, ToolCallResult)]\n            if len(tool_call_results) == 0 or len(tool_call_results) > 1 or len(parts) > len(tool_call_results):\n                raise ValueError(\"Tool message must contain only one tool call result.\")\n\n            tool_result = tool_call_results[0].result\n            origin = tool_call_results[0].origin\n            error = tool_call_results[0].error\n\n            return ChatMessage.from_tool(meta=meta, tool_result=tool_result, origin=origin, error=error)\n\n        raise ValueError(f\"Unsupported role: {role}\")\n\n\n@pass_environment\ndef templatize_part(environment: Any, value: ChatMessageContentT) -> \"_TemplatizedPart\":\n    \"\"\"\n    Jinja filter to convert a ChatMessageContentT object into a JSON string wrapped in sentinel content tags.\n\n    :param environment: The Jinja2 environment\n    :param value: The ChatMessageContentT object to convert\n    :return: A `_TemplatizedPart` holding a JSON string wrapped in special XML content tags","sourceCodeStart":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/utils/jinja2_chat_extension.py#L379-L415","documentation":"A tool-role message built from a template must contain exactly one ToolCallResult part and nothing else. This error fires when the tool message has zero tool call results, more than one, or contains additional parts.","triggerScenarios":"A tool message block with no {% insert %} of a ToolCallResult; inserting a list of multiple tool results into one tool message; mixing tool results with text parts in the same tool message.","commonSituations":"Batching several tool outputs into a single tool turn; forgetting to insert the ToolCallResult and only writing text; converting agent frameworks where one tool turn carries multiple results.","solutions":["Insert exactly one ToolCallResult message per tool message block via {% insert %}.","Split multiple tool results into separate tool messages, one per result.","Remove extra text or other parts from tool messages; put explanations in user/assistant messages.","Verify the variable inserted holds a single ChatMessage with role 'tool', not a list."],"exampleFix":"// before\n{% insert %}{{ tool_results }}{% endinsert %}  // list of 2 results\n// after\n{% for r in tool_results %}tool message with {% insert %}{{ r }}{% endinsert %}{% endfor %}  // one per message","handlingStrategy":"validation","validationCode":"from haystack.dataclasses import ChatMessage, ToolCallResult\n\ndef validate_tool_message(msg: ChatMessage):\n    results = [p for p in msg.content_parts if isinstance(p, ToolCallResult)]\n    if len(results) != 1 or len(msg.content_parts) != 1:\n        raise TypeError(\"Tool message must contain exactly one ToolCallResult part\")","typeGuard":"def is_single_tool_result(msg: ChatMessage) -> bool:\n    parts = msg.content_parts\n    return len(parts) == 1 and isinstance(parts[0], ToolCallResult)","tryCatchPattern":"try:\n    messages = renderer.run(template=tpl, variables=vars)[\"messages\"]\nexcept ValueError as e:\n    if \"Tool message must contain only one tool call result\" in str(e):\n        log.error(\"Tool block invalid; ensure one ToolCallResult per tool message\")\n    raise","preventionTips":["Emit one tool message per tool call result; never batch results.","Always insert a ToolCallResult-carrying ChatMessage into tool blocks.","Keep tool messages free of extra text or other parts.","Test agent templates with multi-tool-call scenarios."],"tags":["chat-template","message-validation","tool-message"],"backgroundTag":"invalid-message-content-part","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}