@@ -165,10 +165,10 @@ def _convert_ollama_response_to_chatmessage(ollama_response: ChatResponse) -> Ch
165165 tool_calls : list [ToolCall ] = []
166166
167167 if ollama_tool_calls := ollama_message .get ("tool_calls" ):
168- for idx , ollama_tc in enumerate ( ollama_tool_calls ) :
168+ for ollama_tc in ollama_tool_calls :
169169 tool_calls .append (
170170 ToolCall (
171- id = ollama_tc .get ("id" ) or f"call_ { idx } " ,
171+ id = ollama_tc .get ("id" ),
172172 tool_name = ollama_tc ["function" ]["name" ],
173173 arguments = ollama_tc ["function" ]["arguments" ],
174174 )
@@ -209,7 +209,7 @@ def _build_chunk(
209209 tool_calls_list .append (
210210 ToolCallDelta (
211211 index = tool_call_index ,
212- id = tool_call .get ("id" ) or f"call_ { tool_call_index } " ,
212+ id = tool_call .get ("id" ),
213213 tool_name = tool_call ["function" ]["name" ],
214214 arguments = json .dumps (tool_call ["function" ]["arguments" ])
215215 if tool_call ["function" ]["arguments" ]
@@ -372,10 +372,11 @@ def _handle_streaming_response(
372372 component_info = ComponentInfo .from_component (self )
373373 chunks : list [StreamingChunk ] = []
374374
375- # Accumulators
376- arg_by_id : dict [str , str ] = {}
377- name_by_id : dict [str , str ] = {}
378- id_order : list [str ] = []
375+ # Accumulators keyed by tool_call.index (always unique per call, even for repeated tool names)
376+ arg_by_index : dict [str , str ] = {}
377+ name_by_index : dict [str , str ] = {}
378+ id_by_index : dict [str , str | None ] = {}
379+ index_order : list [str ] = []
379380 tool_call_index : int = 0
380381
381382 # track reasoning and content blocks to correctly set start=True on the first chunk of each block
@@ -402,15 +403,14 @@ def _handle_streaming_response(
402403
403404 if chunk .tool_calls :
404405 for tool_call in chunk .tool_calls :
405- # id is always set by _build_chunk (either from the server or synthetic "call_N").
406- # Fall back to tool_name only as a last resort for callers that bypass _build_chunk.
407- tool_call_id = tool_call .id or tool_call .tool_name or ""
406+ key = str (tool_call .index )
408407 args = tool_call .arguments or ""
409408
410- if tool_call_id not in id_order :
411- id_order .append (tool_call_id )
412- name_by_id [tool_call_id ] = tool_call .tool_name or ""
413- arg_by_id [tool_call_id ] = args
409+ if key not in index_order :
410+ index_order .append (key )
411+ name_by_index [key ] = tool_call .tool_name or ""
412+ id_by_index [key ] = tool_call .id
413+ arg_by_index [key ] = args
414414
415415 if callback :
416416 callback (chunk )
@@ -423,10 +423,10 @@ def _handle_streaming_response(
423423 reasoning += c .reasoning .reasoning_text if c .reasoning else ""
424424
425425 tool_calls = []
426- for tool_call_id in id_order :
427- arguments : str = arg_by_id .get (tool_call_id , "" )
426+ for key in index_order :
427+ arguments : str = arg_by_index .get (key , "" )
428428 tool_calls .append (
429- ToolCall (id = tool_call_id , tool_name = name_by_id [ tool_call_id ], arguments = json .loads (arguments ))
429+ ToolCall (id = id_by_index [ key ] , tool_name = name_by_index [ key ], arguments = json .loads (arguments ))
430430 )
431431
432432 # We can't use _convert_streaming_chunks_to_chat_message because
@@ -453,10 +453,11 @@ async def _handle_streaming_response_async(
453453 component_info = ComponentInfo .from_component (self )
454454 chunks : list [StreamingChunk ] = []
455455
456- # Accumulators
457- arg_by_id : dict [str , str ] = {}
458- name_by_id : dict [str , str ] = {}
459- id_order : list [str ] = []
456+ # Accumulators keyed by tool_call.index (always unique per call, even for repeated tool names)
457+ arg_by_index : dict [str , str ] = {}
458+ name_by_index : dict [str , str ] = {}
459+ id_by_index : dict [str , str | None ] = {}
460+ index_order : list [str ] = []
460461 tool_call_index : int = 0
461462
462463 # track reasoning and content blocks to correctly set start=True on the first chunk of each block
@@ -484,13 +485,14 @@ async def _handle_streaming_response_async(
484485
485486 if chunk .tool_calls :
486487 for tool_call in chunk .tool_calls :
487- tool_call_id = tool_call .id or tool_call . tool_name or ""
488+ key = str ( tool_call .index )
488489 args = tool_call .arguments or ""
489490
490- if tool_call_id not in id_order :
491- id_order .append (tool_call_id )
492- name_by_id [tool_call_id ] = tool_call .tool_name or ""
493- arg_by_id [tool_call_id ] = args
491+ if key not in index_order :
492+ index_order .append (key )
493+ name_by_index [key ] = tool_call .tool_name or ""
494+ id_by_index [key ] = tool_call .id
495+ arg_by_index [key ] = args
494496
495497 if callback is not None :
496498 await callback (chunk )
@@ -505,10 +507,10 @@ async def _handle_streaming_response_async(
505507 reasoning += c .reasoning .reasoning_text if c .reasoning else ""
506508
507509 tool_calls = []
508- for tool_call_id in id_order :
509- arguments : str = arg_by_id .get (tool_call_id , "" )
510+ for key in index_order :
511+ arguments : str = arg_by_index .get (key , "" )
510512 tool_calls .append (
511- ToolCall (id = tool_call_id , tool_name = name_by_id [ tool_call_id ], arguments = json .loads (arguments ))
513+ ToolCall (id = id_by_index [ key ] , tool_name = name_by_index [ key ], arguments = json .loads (arguments ))
512514 )
513515
514516 # We can't use _convert_streaming_chunks_to_chat_message because
0 commit comments