66import json
77import logging
88import mimetypes
9+ import secrets
910from collections .abc import AsyncGenerator
1011from typing import Any , TypedDict , TypeVar , cast
1112
@@ -86,6 +87,7 @@ def __init__(
8687
8788 self ._custom_client = client
8889 self .client_args = client_args or {}
90+ self ._tool_use_id_to_name : dict [str , str ] = {}
8991
9092 # Validate gemini_tools if provided
9193 if "gemini_tools" in self .config :
@@ -173,10 +175,13 @@ def _format_request_content_part(self, content: ContentBlock) -> genai.types.Par
173175 return genai .types .Part (text = content ["text" ])
174176
175177 if "toolResult" in content :
178+ tool_use_id = content ["toolResult" ]["toolUseId" ]
179+ function_name = self ._tool_use_id_to_name .get (tool_use_id , tool_use_id )
180+
176181 return genai .types .Part (
177182 function_response = genai .types .FunctionResponse (
178- id = content [ "toolResult" ][ "toolUseId" ] ,
179- name = content [ "toolResult" ][ "toolUseId" ] ,
183+ id = tool_use_id ,
184+ name = function_name ,
180185 response = {
181186 "output" : [
182187 tool_result_content
@@ -191,6 +196,12 @@ def _format_request_content_part(self, content: ContentBlock) -> genai.types.Par
191196 )
192197
193198 if "toolUse" in content :
199+ # Store the mapping from toolUseId to name for later use in toolResult formatting.
200+ # This mapping is built as we format the request, ensuring that when we encounter
201+ # toolResult blocks (which come after toolUse blocks in the message history),
202+ # we can look up the function name.
203+ self ._tool_use_id_to_name [content ["toolUse" ]["toolUseId" ]] = content ["toolUse" ]["name" ]
204+
194205 return genai .types .Part (
195206 function_call = genai .types .FunctionCall (
196207 args = content ["toolUse" ]["input" ],
@@ -317,16 +328,16 @@ def _format_chunk(self, event: dict[str, Any]) -> StreamEvent:
317328 case "content_start" :
318329 match event ["data_type" ]:
319330 case "tool" :
320- # Note: toolUseId is the only identifier available in a tool result. However, Gemini requires
321- # that name be set in the equivalent FunctionResponse type. Consequently, we assign
322- # function name to toolUseId in our tool use block. And another reason, function_call is
323- # not guaranteed to have id populated.
331+ function_call = event [ "data" ]. function_call
332+ # Use Gemini's provided ID or generate one if missing
333+ tool_use_id = function_call . id or f"tooluse_ { secrets . token_urlsafe ( 16 ) } "
334+
324335 return {
325336 "contentBlockStart" : {
326337 "start" : {
327338 "toolUse" : {
328- "name" : event [ "data" ]. function_call .name ,
329- "toolUseId" : event [ "data" ]. function_call . name ,
339+ "name" : function_call .name ,
340+ "toolUseId" : tool_use_id ,
330341 },
331342 },
332343 },
@@ -417,6 +428,7 @@ async def stream(
417428 ModelThrottledException: If the request is throttled by Gemini.
418429 """
419430 request = self ._format_request (messages , tool_specs , system_prompt , self .config .get ("params" ))
431+ self ._tool_use_id_to_name .clear ()
420432
421433 client = self ._get_client ().aio
422434
0 commit comments