Skip to content

fix: convert Pydantic models from TOOL_CALLS_CACHE to dict - #20808

Closed
themavik wants to merge 1 commit into
BerriAI:mainfrom
themavik:fix/tool-cache-type-mismatch-20699
Closed

fix: convert Pydantic models from TOOL_CALLS_CACHE to dict#20808
themavik wants to merge 1 commit into
BerriAI:mainfrom
themavik:fix/tool-cache-type-mismatch-20699

Conversation

@themavik

Copy link
Copy Markdown
Contributor

Summary

Fixes #20699

Root cause: TOOL_CALLS_CACHE stores ChatCompletionMessageToolCall Pydantic objects, but _ensure_tool_results_have_corresponding_tool_calls checks isinstance(_, dict) which fails for Pydantic models. The code replaces the cached value with {}, creating malformed tool calls with empty function names and arguments.

Changes

  • litellm/responses/litellm_completion_transformation/transformation.py: Instead of replacing non-dict cached values with {}, use model_dump() (Pydantic v2) or dict() (Pydantic v1) to properly convert the Pydantic model to a dict while preserving all tool call data.

Risk Assessment

Low - The fix only changes the fallback behavior when a cached value isn't already a dict. It preserves data that was previously being discarded.

TOOL_CALLS_CACHE stores ChatCompletionMessageToolCall objects
(Pydantic models), but _ensure_tool_results_have_corresponding_
tool_calls expects plain dicts. The isinstance(_, dict) check
fails for Pydantic objects, replacing them with empty dicts.
This causes malformed tool calls with empty function names
and arguments, breaking multi-turn conversations.

Now uses model_dump()/dict() to properly convert Pydantic
models to dicts, preserving all tool call data.

Fixes #20699
@vercel

vercel Bot commented Feb 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Feb 10, 2026 4:01am

Request Review

@CLAassistant

CLAassistant commented Feb 10, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@greptile-apps

greptile-apps Bot commented Feb 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Overview

Greptile Summary

This PR adjusts tool-call caching normalization in litellm/responses/litellm_completion_transformation/transformation.py to avoid discarding cached ChatCompletionMessageToolCall Pydantic objects. Instead of replacing non-dict cached values with {}, it converts Pydantic models to plain dicts via model_dump() (Pydantic v2) or dict() (Pydantic v1), preserving the tool call id, type, and function payload so _ensure_tool_results_have_corresponding_tool_calls can correctly reconcile tool results with tool calls.

Confidence Score: 3/5

  • This PR is likely safe to merge but has a correctness edge case in how dumped Pydantic tool-call objects are normalized before reuse.
  • The change is localized and addresses a real data-loss bug when cache entries are Pydantic models. However, passing raw model_dump()/dict() output directly into _create_tool_call_chunk assumes the dumped structure matches the expected {id,type,function:{name,arguments}} schema; if it doesn’t, the original malformed-tool-call issue can persist in a different form (empty name/invalid arguments).
  • litellm/responses/litellm_completion_transformation/transformation.py

Important Files Changed

Filename Overview
litellm/responses/litellm_completion_transformation/transformation.py Changes tool-call cache normalization to convert cached Pydantic tool call models to dicts (via model_dump()/dict()) instead of replacing with {}; main concern is the dumped shape may not match the downstream {id,type,function:{name,arguments}} expectations without additional normalization.

Sequence Diagram

sequenceDiagram
    participant Caller as Responses session handler
    participant Xform as LiteLLMCompletionResponsesConfig
    participant Cache as TOOL_CALLS_CACHE

    Caller->>Xform: _ensure_tool_results_have_corresponding_tool_calls(messages, tools)
    loop each message
        Xform->>Xform: if role != "tool" skip
        Xform->>Xform: find previous assistant message
        Xform->>Xform: if missing tool_call_id try recover from assistant.tool_calls
        alt tool_call_id empty and removable
            Xform->>Xform: mark tool message for removal
        else tool_call_id present
            Xform->>Xform: check assistant.tool_calls contains tool_call_id
            alt missing tool_call
                Xform->>Cache: get_cache(tool_call_id)
                alt cache miss and tools provided
                    Xform->>Xform: reconstruct minimal tool_call from tools
                end
                alt cached tool_call is Pydantic model
                    Xform->>Xform: model_dump()/dict() to convert to dict
                end
                Xform->>Xform: _create_tool_call_chunk(dict, tool_call_id, idx)
                Xform->>Xform: _add_tool_call_to_assistant(prev_assistant, tool_call_chunk)
            end
        end
    end
    Xform-->>Caller: fixed_messages (tool_calls added / some tool msgs removed)
Loading

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 744 to +753
if _tool_use_definition:
if not isinstance(_tool_use_definition, dict):
_tool_use_definition = {}
# Convert Pydantic models (e.g. ChatCompletionMessageToolCall
# from TOOL_CALLS_CACHE) to dict to preserve tool call data
if hasattr(_tool_use_definition, 'model_dump'):
_tool_use_definition = _tool_use_definition.model_dump()
elif hasattr(_tool_use_definition, 'dict'):
_tool_use_definition = _tool_use_definition.dict()
else:
_tool_use_definition = {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pydantic dump may break expected shape

_create_tool_call_chunk expects a tool_use_definition dict with top-level keys like id, type, and a nested function dict containing name/arguments (see _create_tool_call_chunk at transformation.py:606-628). Converting cached ChatCompletionMessageToolCall via model_dump()/dict() may produce a different shape depending on model config (e.g., function may be a Pydantic object that becomes a dict with extra fields, or arguments may not be a string). This will still lead to empty name or malformed arguments in the reconstructed tool call.

Consider normalizing the dumped object into the exact {id,type,function:{name,arguments}} structure (and coercing arguments to a JSON string) before calling _create_tool_call_chunk, rather than passing the raw dump through.

@ghost

ghost commented Feb 10, 2026

Copy link
Copy Markdown

Closing in favor of #20700

@ghost ghost closed this Feb 10, 2026
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Responses API Tool Cache Type Mismatch Corrupts Multi-Turn Tool Calling

2 participants