Skip to content

Commit 1ad348d

Browse files
GWealecopybara-github
authored andcommitted
fix: preserve function call ids for litellm models
Close #2621 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 933306904
1 parent 573f043 commit 1ad348d

2 files changed

Lines changed: 86 additions & 6 deletions

File tree

src/google/adk/flows/llm_flows/contents.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,23 @@ async def run_async(
5252
):
5353
preserve_function_call_ids = True
5454
else:
55-
# Anthropic pairs tool_use/tool_result by id, so `adk-*` fallback
56-
# ids must survive replay.
55+
# Anthropic and LiteLLM-backed providers (e.g. OpenAI) pair tool
56+
# calls with their results by id, so `adk-*` fallback ids must
57+
# survive replay.
58+
id_pairing_model_types: list[type] = []
5759
try:
5860
from ...models.anthropic_llm import AnthropicLlm
61+
62+
id_pairing_model_types.append(AnthropicLlm)
5963
except (ImportError, OSError):
60-
AnthropicLlm = None
61-
if AnthropicLlm is not None and isinstance(
62-
canonical_model, AnthropicLlm
63-
):
64+
pass
65+
try:
66+
from ...models.lite_llm import LiteLlm
67+
68+
id_pairing_model_types.append(LiteLlm)
69+
except (ImportError, OSError):
70+
pass
71+
if isinstance(canonical_model, tuple(id_pairing_model_types)):
6472
preserve_function_call_ids = True
6573

6674
# Preserve all contents that were added by instruction processor

tests/unittests/flows/llm_flows/test_contents.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,78 @@ async def test_adk_function_call_ids_preserved_for_anthropic_model():
11451145
assert user_fr_part.function_response.id == function_call_id
11461146

11471147

1148+
@pytest.mark.asyncio
1149+
async def test_adk_function_call_ids_preserved_for_lite_llm_model():
1150+
"""LiteLLM-backed providers (e.g. OpenAI) pair tool calls with their
1151+
results by id, so `adk-*` fallback ids must survive replay.
1152+
"""
1153+
from google.adk.models.lite_llm import LiteLlm
1154+
1155+
agent = Agent(
1156+
model=LiteLlm(model="openai/gpt-4o-mini"),
1157+
name="test_agent",
1158+
)
1159+
llm_request = LlmRequest(model="openai/gpt-4o-mini")
1160+
invocation_context = await testing_utils.create_invocation_context(
1161+
agent=agent
1162+
)
1163+
1164+
function_call_id = "adk-test-call-id"
1165+
events = [
1166+
Event(
1167+
invocation_id="inv1",
1168+
author="user",
1169+
content=types.UserContent("Call the tool"),
1170+
),
1171+
Event(
1172+
invocation_id="inv2",
1173+
author="test_agent",
1174+
content=types.Content(
1175+
role="model",
1176+
parts=[
1177+
types.Part(
1178+
function_call=types.FunctionCall(
1179+
id=function_call_id,
1180+
name="test_tool",
1181+
args={"x": 1},
1182+
)
1183+
)
1184+
],
1185+
),
1186+
),
1187+
Event(
1188+
invocation_id="inv3",
1189+
author="test_agent",
1190+
content=types.Content(
1191+
role="user",
1192+
parts=[
1193+
types.Part(
1194+
function_response=types.FunctionResponse(
1195+
id=function_call_id,
1196+
name="test_tool",
1197+
response={"result": 2},
1198+
)
1199+
)
1200+
],
1201+
),
1202+
),
1203+
]
1204+
invocation_context.session.events = events
1205+
1206+
async for _ in contents.request_processor.run_async(
1207+
invocation_context, llm_request
1208+
):
1209+
pass
1210+
1211+
model_fc_part = llm_request.contents[1].parts[0]
1212+
assert model_fc_part.function_call is not None
1213+
assert model_fc_part.function_call.id == function_call_id
1214+
1215+
user_fr_part = llm_request.contents[2].parts[0]
1216+
assert user_fr_part.function_response is not None
1217+
assert user_fr_part.function_response.id == function_call_id
1218+
1219+
11481220
def test_is_other_agent_reply_live_session():
11491221
"""Test _is_other_agent_reply when live_session_id is present."""
11501222
event = Event(author="another_agent", live_session_id="session_123")

0 commit comments

Comments
 (0)