BerriAI/litellm · error · Exception
Unable to parse anthropic tool result for message: {message}
Error message
Unable to parse anthropic tool result for message: {message} What it means
In Anthropic tool-result conversion: after handling role='tool' and role='function' messages, anthropic_tool_result is still None, meaning the message did not fit any supported tool-result shape (unsupported role, missing content, or content blocks the converter cannot map to tool_result). LiteLLM includes the entire message so you can see what failed.
Source
Thrown at litellm/litellm_core_utils/prompt_templates/factory.py:1692
anthropic_tool_result = AnthropicMessagesToolResultParam(
type="tool_result",
tool_use_id=sanitized_tool_use_id,
content=anthropic_content,
)
if message["role"] == "function":
function_message: Final[ChatCompletionFunctionMessage] = message
tool_call_id = function_message.get("tool_call_id") or str(uuid.uuid4())
# Sanitize tool_use_id to match Anthropic's pattern requirement: ^[a-zA-Z0-9_-]+$
sanitized_tool_use_id = _sanitize_anthropic_tool_use_id(tool_call_id)
anthropic_tool_result = AnthropicMessagesToolResultParam(
type="tool_result",
tool_use_id=sanitized_tool_use_id,
content=anthropic_content,
)
if anthropic_tool_result is None:
raise Exception(f"Unable to parse anthropic tool result for message: {message}")
if cache_control is not None:
anthropic_tool_result["cache_control"] = cache_control
return anthropic_tool_result
def convert_function_to_anthropic_tool_invoke(
function_call: dict | ChatCompletionToolCallFunctionChunk,
) -> list[AnthropicMessagesToolUseParam]:
try:
_name: Final = get_attribute_or_key(function_call, "name") or ""
_arguments: Final = get_attribute_or_key(function_call, "arguments")
tool_input: Final = parse_tool_call_arguments(
_arguments, tool_name=_name, context="Anthropic function to tool invoke"
)
anthropic_tool_invoke: Final = [
AnthropicMessagesToolUseParam(View on GitHub (pinned to 6c2dcb801b)
Solutions
- Make each tool message a plain dict: {"role": "tool", "tool_call_id": <id>, "content": <str or simple blocks>}
- Coerce tool output to a non-empty string (str(result) or json.dumps(result)) before appending
- Verify the preceding assistant message has the matching tool_call id
Example fix
# before
messages.append({"role": "tool", "tool_call_id": call_id, "content": tool_output}) # tool_output may be None
# after
messages.append({"role": "tool", "tool_call_id": call_id,
"content": json.dumps(tool_output) if tool_output is not None else "(no output)"}) Defensive patterns
Strategy: validation
Validate before calling
def is_valid_tool_message(m: dict) -> bool:
return (
m.get("role") in ("tool", "function")
and bool(m.get("tool_call_id"))
and bool(m.get("content"))
) Type guard
from typing import Any
def is_anthropic_safe_tool_message(m: Any) -> bool:
return (
isinstance(m, dict)
and m.get("role") in ("tool", "function")
and isinstance(m.get("content"), (str, list))
and len(m.get("content") or "") > 0
) Prevention
- Coerce tool output to a non-empty string before appending (json.dumps or str)
- Always pair tool messages with their assistant tool_call by id
- Never append None tool results
When it happens
Trigger: A message with role='tool' but empty/unparseable content, unexpected content block types, or a role that reached this function without matching the tool/function branches; sending tool results to anthropic/claude models where the content list contains unsupported block types.
Common situations: Agent frameworks returning None or empty-string tool output; tool responses containing nested content lists instead of plain text/JSON; hand-assembled messages with role typos like 'Tool'.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Invalid first message. Should always start with 'role'='user
- messages is required
- WebSearchInterception: missing follow-up messages
- Unable to parse anthropic file message: {message}
- Either file_data or file_id must be present in the file mess
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/9e1f722144669323.
Report an issue: GitHub.