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

  1. Make each tool message a plain dict: {"role": "tool", "tool_call_id": <id>, "content": <str or simple blocks>}
  2. Coerce tool output to a non-empty string (str(result) or json.dumps(result)) before appending
  3. 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

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

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/9e1f722144669323. Report an issue: GitHub.