@@ -477,33 +477,33 @@ async def _execute_anthropic_native_tool_loop(
477477 log_msg += f" | Reasoning: { total_tokens ['reasoning_tokens' ]:,} "
478478 logger .info (log_msg )
479479 logger .info (f"| Turns: { turn_count } " )
480-
480+
481481 # Convert messages to SDK format
482- # sdk_format_messages = self._convert_to_sdk_format(messages)
483-
482+ sdk_format_messages = self ._convert_to_sdk_format (messages )
483+
484484 if hit_turn_limit :
485485 return {
486486 "success" : False ,
487- "output" : messages ,
487+ "output" : sdk_format_messages ,
488488 "token_usage" : total_tokens ,
489489 "turn_count" : turn_count ,
490490 "error" : f"Max turns ({ max_turns } ) exceeded" ,
491491 "litellm_run_model_name" : self .litellm_run_model_name ,
492492 }
493-
493+
494494 if error_msg :
495495 return {
496496 "success" : False ,
497- "output" : messages ,
497+ "output" : sdk_format_messages ,
498498 "token_usage" : total_tokens ,
499499 "turn_count" : turn_count ,
500500 "error" : error_msg ,
501501 "litellm_run_model_name" : self .litellm_run_model_name ,
502502 }
503-
503+
504504 return {
505505 "success" : True ,
506- "output" : messages ,
506+ "output" : sdk_format_messages ,
507507 "token_usage" : total_tokens ,
508508 "turn_count" : turn_count ,
509509 "error" : None ,
@@ -632,45 +632,54 @@ async def _execute_litellm_tool_loop(
632632 else :
633633 await asyncio .sleep (2 ** consecutive_failures )
634634 continue
635-
635+
636636 # Extract actual model name from response (first turn only)
637637 if turn_count == 0 and hasattr (response , 'model' ) and response .model :
638638 self .litellm_run_model_name = response .model .split ("/" )[- 1 ]
639-
639+
640640 # Update token usage including reasoning tokens
641641 if hasattr (response , 'usage' ) and response .usage :
642642 input_tokens = response .usage .prompt_tokens or 0
643643 total_tokens_count = response .usage .total_tokens or 0
644644 # Calculate output tokens as total - input for consistency
645645 output_tokens = total_tokens_count - input_tokens if total_tokens_count > 0 else (response .usage .completion_tokens or 0 )
646-
646+
647647 total_tokens ["input_tokens" ] += input_tokens
648648 total_tokens ["output_tokens" ] += output_tokens
649649 total_tokens ["total_tokens" ] += total_tokens_count
650-
650+
651651 # Extract reasoning tokens if available
652652 if hasattr (response .usage , 'completion_tokens_details' ):
653653 details = response .usage .completion_tokens_details
654654 if hasattr (details , 'reasoning_tokens' ):
655655 total_tokens ["reasoning_tokens" ] += details .reasoning_tokens or 0
656-
656+
657657 # Get response message
658658 choices = response .choices
659659 if len (choices ):
660660 message = choices [0 ].message
661+ # deeply dump the message to ensure we capture all fields
661662 message_dict = message .model_dump () if hasattr (message , 'model_dump' ) else dict (message )
662-
663+
664+ # Explicitly preserve function_call if present (even if tool_calls exists),
665+ # as it may contain provider-specific metadata (e.g. Gemini thought_signature)
666+ if hasattr (message , 'function_call' ) and message .function_call :
667+ # Ensure it's in the dict if model_dump missed it or it was excluded
668+ if 'function_call' not in message_dict or not message_dict ['function_call' ]:
669+ fc = message .function_call
670+ message_dict ['function_call' ] = fc .model_dump () if hasattr (fc , 'model_dump' ) else fc
671+
663672 # Log assistant's text content if present
664673 if hasattr (message , 'content' ) and message .content :
665674 # Display the content with line prefix
666675 for line in message .content .splitlines ():
667676 logger .info (f"| { line } " )
668-
677+
669678 # Also log to file if specified
670679 if tool_call_log_file :
671680 with open (tool_call_log_file , 'a' , encoding = 'utf-8' ) as f :
672681 f .write (f"{ message .content } \n " )
673-
682+
674683 # Check for tool calls (newer format)
675684 if hasattr (message , 'tool_calls' ) and message .tool_calls :
676685 messages .append (message_dict )
0 commit comments