BerriAI/litellm · error · Exception

Missing corresponding tool call for tool response message. R

Error message

Missing corresponding tool call for tool response message. Received - message={message}, last_message_with_tool_calls={last_message_with_tool_calls}

What it means

Raised when converting a role='tool' message for Gemini: LiteLLM looks up the function name by matching message.tool_call_id against the tool_calls in the last assistant message with tool calls, and no match with a non-empty name was found. Gemini needs the function name on every functionResponse, so an unmatched tool result cannot be forwarded.

Source

Thrown at litellm/litellm_core_utils/prompt_templates/factory.py:1497

    if last_message_with_tool_calls:
        tools: Final = last_message_with_tool_calls.get("tool_calls", [])
        msg_tool_call_id: Final = message.get("tool_call_id", None)
        for tool in tools:
            prev_tool_call_id = tool.get("id", None)
            if msg_tool_call_id and prev_tool_call_id and msg_tool_call_id == prev_tool_call_id:
                name = tool.get("function", {}).get("name", "")

    # Echo the OpenAI tool_call_id on functionResponse (strip thought-signature suffix).
    gemini_call_id: str | None = None
    if forward_function_call_id:
        raw_tool_call_id: Final = message.get("tool_call_id")
        if raw_tool_call_id and isinstance(raw_tool_call_id, str):
            stripped_id: Final = raw_tool_call_id.split(THOUGHT_SIGNATURE_SEPARATOR, 1)[0]
            if stripped_id:
                gemini_call_id = stripped_id

    if not name:
        raise Exception(
            f"Missing corresponding tool call for tool response message. Received - message={message}, last_message_with_tool_calls={last_message_with_tool_calls}"
        )

    # Parse response data - support both JSON string and plain string
    # For Computer Use, the response should contain structured data like {"url": "..."}
    response_data: dict
    try:
        if content_str.strip().startswith("{") or content_str.strip().startswith("["):
            # Try to parse as JSON (for Computer Use structured responses)
            parsed: Final = json.loads(content_str)
            if isinstance(parsed, dict):
                response_data = parsed  # Use the parsed JSON directly
            else:
                response_data = {"content": content_str}
        else:
            response_data = {"content": content_str}
    except (json.JSONDecodeError, ValueError):
        # Not valid JSON, wrap in content field

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Ensure the assistant message containing the matching tool_calls (with ids) is present immediately before the tool result in messages
  2. Echo back the exact tool_call_id from the assistant message on the tool response
  3. When trimming history, never cut between an assistant tool_calls turn and its tool responses
  4. For parallel calls, keep every tool response, one per tool_call_id, before the next user turn

Example fix

# before
messages = [
    {"role": "user", "content": "Weather?"},
    {"role": "tool", "tool_call_id": "call_1", "content": "72F"},  # assistant tool_calls turn was trimmed
]

# after
messages = [
    {"role": "user", "content": "Weather?"},
    {"role": "assistant", "tool_calls": [{"id": "call_1", "type": "function",
        "function": {"name": "get_weather", "arguments": "{\"city\": \"SF\"}"}}]},
    {"role": "tool", "tool_call_id": "call_1", "content": "72F"},
]
Defensive patterns

Strategy: validation

Validate before calling

def tool_results_match(messages) -> bool:
    last_ids = None
    for m in messages:
        if m.get("role") == "assistant" and m.get("tool_calls"):
            last_ids = {tc.get("id") for tc in m["tool_calls"]}
        elif m.get("role") == "tool":
            if not last_ids or m.get("tool_call_id") not in last_ids:
                return False
    return True

assert tool_results_match(messages), "tool result without matching assistant tool_call"

Prevention

When it happens

Trigger: Sending a tool message whose tool_call_id doesn't match any id in the preceding assistant tool_calls (typos, regenerated ids, stripped thought-signature suffix mismatch); sending the tool response without the assistant tool-call message in between; empty tool_calls in the referenced assistant message.

Common situations: Multi-turn agent loops where history is trimmed and the assistant tool-call turn falls out of the window; parallel tool calls where results are matched by index instead of id; providers that mutate tool_call ids; replaying stored conversations after id sanitization.

Related errors


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