litellm - ✅(Solved) Fix [Bug]: GoogleGenAI adapter produces duplicate tool_call_ids for repeated functionCall parts [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
BerriAI/litellm#27078Fetched 2026-05-04 04:59:14
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
referenced ×4cross-referenced ×1labeled ×1

Fix Action

Fix

Replace f"call_{name}" with f"call_{uuid.uuid4().hex[:24]}" and maintain a per-name FIFO queue so that functionResponse parts are matched to the correct preceding functionCall.

PR fix notes

PR #27079: fix(gemini): generate unique tool_call_ids in GoogleGenAI adapter

Description (problem / solution / changelog)

Relevant issues

Fixes #27078

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Screenshots / Proof of Fix

tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py::TestToolCallIdUniqueness::test_single_function_call_gets_unique_id PASSED
tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py::TestToolCallIdUniqueness::test_duplicate_function_names_get_distinct_ids PASSED
tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py::TestToolCallIdUniqueness::test_different_functions_get_distinct_ids PASSED
tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py::TestFunctionResponseIdMatching::test_response_matches_call_id PASSED
tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py::TestFunctionResponseIdMatching::test_fifo_matching_for_duplicate_function_names PASSED
tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py::TestFunctionResponseIdMatching::test_mixed_functions_match_correctly PASSED
tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py::TestMultiTurnToolCalling::test_full_multi_turn_tool_conversation PASSED
tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py::TestMultiTurnToolCalling::test_orphan_function_response_gets_fresh_id PASSED
tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py::TestMultiTurnToolCalling::test_function_response_content_serialization PASSED

9 passed, 0 failed — existing adapter tests (test_google_genai_adapter_fixes.py) also pass with 0 regressions.

Type

🐛 Bug Fix

Changes

Problem

GoogleGenAIAdapter._transform_contents_to_messages() generates deterministic tool_call_id values using f"call_{func_name}". When a model turn contains multiple functionCall parts for the same function (e.g., get_weather for two cities), all tool calls receive the identical id call_get_weather. The corresponding functionResponse parts also get call_get_weather, making it impossible for the downstream provider to match responses to their correct calls.

Fix

File: litellm/google_genai/adapters/transformation.py (+35/-5 lines)

  • Replace deterministic f"call_{name}" with f"call_{uuid.uuid4().hex[:24]}" for globally unique IDs
  • Maintain a per-function-name FIFO queue (pending_tool_call_ids) so that each functionResponse is matched to the correct preceding functionCall by consuming IDs in order
  • Fallback: orphan functionResponse parts (no preceding functionCall) get a fresh uuid instead of crashing

Testing

File: tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py (9 new mocked tests)

  • TestToolCallIdUniqueness — single call, duplicate names, different functions all get distinct IDs
  • TestFunctionResponseIdMatching — FIFO matching for same-name calls, mixed function matching
  • TestMultiTurnToolCalling — full multi-turn conversation (add→multiply→answer), orphan responses, content serialization

Changed files

  • litellm/google_genai/adapters/transformation.py (modified, +124/-104)
  • tests/test_litellm/google_genai/test_google_genai_adapter.py (modified, +1/-1)
  • tests/test_litellm/google_genai/test_google_genai_adapter_tool_call_id.py (added, +505/-0)

Code Example

from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter

adapter = GoogleGenAIAdapter()
contents = [
    {"role": "user", "parts": [{"text": "Weather in London and Paris"}]},
    {
        "role": "model",
        "parts": [
            {"functionCall": {"name": "get_weather", "args": {"city": "London"}}},
            {"functionCall": {"name": "get_weather", "args": {"city": "Paris"}}},
        ],
    },
    {
        "role": "user",
        "parts": [
            {"functionResponse": {"name": "get_weather", "response": {"temp": "15C"}}},
            {"functionResponse": {"name": "get_weather", "response": {"temp": "18C"}}},
        ],
    },
]
messages = adapter._transform_contents_to_messages(contents)

# BUG: Both tool_calls have id="call_get_weather"
# BUG: Both tool messages have tool_call_id="call_get_weather"
# The backend cannot tell which response is for London vs Paris
RAW_BUFFERClick to expand / collapse

What happened?

The GoogleGenAIAdapter._transform_contents_to_messages() method generates deterministic tool_call_id values using f"call_{func_name}". When a model turn contains multiple functionCall parts for the same function (e.g., get_weather for London AND Paris), all tool calls get the identical id call_get_weather.

This causes:

  1. The OpenAI-format backend cannot disambiguate which functionResponse matches which functionCall
  2. Multi-turn tool-calling conversations silently corrupt the tool call matching
  3. Downstream providers that enforce tool_call_id uniqueness reject the request

Relevant Code

File: litellm/google_genai/adapters/transformation.py

The _transform_contents_to_messages method uses f"call_{func_call.get('name', 'unknown')}" for both functionCall (model role) and functionResponse (user role) parts. This means two calls to the same function produce identical IDs.

How to reproduce

from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter

adapter = GoogleGenAIAdapter()
contents = [
    {"role": "user", "parts": [{"text": "Weather in London and Paris"}]},
    {
        "role": "model",
        "parts": [
            {"functionCall": {"name": "get_weather", "args": {"city": "London"}}},
            {"functionCall": {"name": "get_weather", "args": {"city": "Paris"}}},
        ],
    },
    {
        "role": "user",
        "parts": [
            {"functionResponse": {"name": "get_weather", "response": {"temp": "15C"}}},
            {"functionResponse": {"name": "get_weather", "response": {"temp": "18C"}}},
        ],
    },
]
messages = adapter._transform_contents_to_messages(contents)

# BUG: Both tool_calls have id="call_get_weather"
# BUG: Both tool messages have tool_call_id="call_get_weather"
# The backend cannot tell which response is for London vs Paris

Expected behavior

Each functionCall part should receive a globally unique tool_call_id (e.g., uuid-based). The corresponding functionResponse should reference the correct id via FIFO matching by function name.

Fix

Replace f"call_{name}" with f"call_{uuid.uuid4().hex[:24]}" and maintain a per-name FIFO queue so that functionResponse parts are matched to the correct preceding functionCall.

Are you a LiteLLM maintainer?

No

Twitter / LinkedIn

No response

extent analysis

TL;DR

Replace the deterministic tool_call_id generation with a unique identifier, such as a UUID, to ensure each functionCall part receives a globally unique tool_call_id.

Guidance

  • Identify the _transform_contents_to_messages method in transformation.py and update the tool_call_id generation to use a unique identifier, such as uuid.uuid4().hex[:24].
  • Implement a per-name FIFO queue to match functionResponse parts to the correct preceding functionCall based on the function name.
  • Verify that the updated code correctly assigns unique tool_call_id values to each functionCall part and matches functionResponse parts correctly.
  • Test the updated code with the provided example to ensure it produces the expected behavior.

Example

import uuid

# ...

def _transform_contents_to_messages(self, contents):
    # ...
    for part in parts:
        if 'functionCall' in part:
            tool_call_id = f"call_{uuid.uuid4().hex[:24]}"
            # ...
        # ...

Notes

The provided fix suggests using a UUID-based approach, which should ensure globally unique tool_call_id values. However, the implementation details of the per-name FIFO queue are not specified and may require additional development.

Recommendation

Apply the workaround by replacing the deterministic tool_call_id generation with a unique identifier, such as a UUID, and implement a per-name FIFO queue to match functionResponse parts to the correct preceding functionCall. This should resolve the issue of duplicate tool_call_id values and ensure correct matching of functionResponse parts.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Each functionCall part should receive a globally unique tool_call_id (e.g., uuid-based). The corresponding functionResponse should reference the correct id via FIFO matching by function name.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING