1515import time
1616import json
1717import xml .etree .ElementTree as ET
18+ # Gap 2: Tool call execution imports
19+ from ..tools .call_executor import ToolCall , create_tool_call_executor
1820# Display functions - lazy loaded to avoid importing rich at startup
1921# These are only needed when output=verbose
2022_display_module = None
@@ -1649,6 +1651,7 @@ def get_response(
16491651 task_description : Optional [str ] = None ,
16501652 task_id : Optional [str ] = None ,
16511653 execute_tool_fn : Optional [Callable ] = None ,
1654+ parallel_tool_calls : bool = False , # Gap 2: Enable parallel tool execution
16521655 stream : bool = True ,
16531656 stream_callback : Optional [Callable ] = None ,
16541657 emit_events : bool = False ,
@@ -1893,26 +1896,47 @@ def _prepare_return_value(text: str) -> Union[str, tuple]:
18931896 "tool_calls" : serializable_tool_calls ,
18941897 })
18951898
1896- tool_results = []
1899+ # Execute tool calls using ToolCallExecutor (Gap 2: parallel or sequential)
1900+ is_ollama = self ._is_ollama_provider ()
1901+ tool_calls_batch = []
1902+
1903+ # Prepare batch of ToolCall objects
18971904 for tool_call in tool_calls :
1898- function_name , arguments , tool_call_id = self ._extract_tool_call_info (tool_call )
1899-
1900- logging .debug (f"[RESPONSES_API] Executing tool { function_name } with args: { arguments } " )
1901- tool_result = execute_tool_fn (function_name , arguments , tool_call_id = tool_call_id )
1905+ function_name , arguments , tool_call_id = self ._extract_tool_call_info (tool_call , is_ollama = is_ollama )
1906+ tool_calls_batch .append (ToolCall (
1907+ function_name = function_name ,
1908+ arguments = arguments ,
1909+ tool_call_id = tool_call_id ,
1910+ is_ollama = is_ollama
1911+ ))
1912+
1913+ # Create appropriate executor based on parallel_tool_calls setting
1914+ executor = create_tool_call_executor (parallel = parallel_tool_calls )
1915+
1916+ # Execute batch
1917+ tool_results_batch = executor .execute_batch (tool_calls_batch , execute_tool_fn )
1918+
1919+ tool_results = []
1920+ for tool_call_obj , tool_result_obj in zip (tool_calls_batch , tool_results_batch ):
1921+ if tool_result_obj .error is not None :
1922+ raise tool_result_obj .error
1923+ tool_result = tool_result_obj .result
19021924 tool_results .append (tool_result )
19031925 accumulated_tool_results .append (tool_result )
19041926
1927+ logging .debug (f"[RESPONSES_API] Executed tool { tool_result_obj .function_name } with result: { tool_result } " )
1928+
19051929 if verbose :
1906- display_message = f"Agent { agent_name } called function '{ function_name } ' with arguments: { arguments } \n "
1930+ display_message = f"Agent { agent_name } called function '{ tool_call_obj . function_name } ' with arguments: { tool_call_obj . arguments } \n "
19071931 display_message += f"Function returned: { tool_result } " if tool_result else "Function returned no output"
19081932 _get_display_functions ()['display_tool_call' ](display_message , console = self .console )
19091933
19101934 result_str = json .dumps (tool_result ) if tool_result else "empty"
19111935 _get_display_functions ()['execute_sync_callback' ](
19121936 'tool_call' ,
1913- message = f"Calling function: { function_name } " ,
1914- tool_name = function_name ,
1915- tool_input = arguments ,
1937+ message = f"Calling function: { tool_call_obj . function_name } " ,
1938+ tool_name = tool_call_obj . function_name ,
1939+ tool_input = tool_call_obj . arguments ,
19161940 tool_output = result_str [:200 ] if result_str else None ,
19171941 )
19181942
@@ -1927,7 +1951,7 @@ def _prepare_return_value(text: str) -> Union[str, tuple]:
19271951 content = json .dumps (tool_result )
19281952 messages .append ({
19291953 "role" : "tool" ,
1930- "tool_call_id" : tool_call_id ,
1954+ "tool_call_id" : tool_result_obj . tool_call_id ,
19311955 "content" : content ,
19321956 })
19331957
@@ -3142,6 +3166,7 @@ def get_response_stream(
31423166 task_description : Optional [str ] = None ,
31433167 task_id : Optional [str ] = None ,
31443168 execute_tool_fn : Optional [Callable ] = None ,
3169+ parallel_tool_calls : bool = False , # Gap 2: Enable parallel tool execution
31453170 ** kwargs
31463171 ):
31473172 """Generator that yields real-time response chunks from the LLM.
@@ -3167,6 +3192,7 @@ def get_response_stream(
31673192 task_description: Optional task description for logging
31683193 task_id: Optional task ID for logging
31693194 execute_tool_fn: Optional function for executing tools
3195+ parallel_tool_calls: If True, execute batched LLM tool calls in parallel (default False)
31703196 **kwargs: Additional parameters
31713197
31723198 Yields:
@@ -3301,26 +3327,44 @@ def get_response_stream(
33013327 "tool_calls" : serializable_tool_calls
33023328 })
33033329
3304- # Execute tool calls and add results to conversation
3330+ # Execute tool calls using ToolCallExecutor (Gap 2: parallel or sequential)
3331+ is_ollama = self ._is_ollama_provider ()
3332+ tool_calls_batch = []
3333+
3334+ # Prepare batch of ToolCall objects
33053335 for tool_call in tool_calls :
3306- is_ollama = self ._is_ollama_provider ()
33073336 function_name , arguments , tool_call_id = self ._extract_tool_call_info (tool_call , is_ollama )
3308-
3309- try :
3310- # Execute the tool (pass tool_call_id for event correlation)
3311- tool_result = execute_tool_fn (function_name , arguments , tool_call_id = tool_call_id )
3312-
3313- # Add tool result to messages
3314- tool_message = self ._create_tool_message (function_name , tool_result , tool_call_id , is_ollama )
3315- messages .append (tool_message )
3316-
3317- except Exception as e :
3318- logging .error (f"Tool execution error for { function_name } : { e } " )
3319- # Add error message to conversation
3320- error_message = self ._create_tool_message (
3321- function_name , f"Error executing tool: { e } " , tool_call_id , is_ollama
3337+ tool_calls_batch .append (ToolCall (
3338+ function_name = function_name ,
3339+ arguments = arguments ,
3340+ tool_call_id = tool_call_id ,
3341+ is_ollama = is_ollama
3342+ ))
3343+
3344+ # Create appropriate executor based on parallel_tool_calls setting
3345+ executor = create_tool_call_executor (parallel = parallel_tool_calls )
3346+
3347+ # Execute batch and add results to conversation
3348+ tool_results = executor .execute_batch (tool_calls_batch , execute_tool_fn )
3349+
3350+ for tool_result in tool_results :
3351+ if tool_result .error is None :
3352+ # Successful execution
3353+ tool_message = self ._create_tool_message (
3354+ tool_result .function_name ,
3355+ tool_result .result ,
3356+ tool_result .tool_call_id ,
3357+ tool_result .is_ollama
3358+ )
3359+ else :
3360+ # Error during execution (already logged by executor)
3361+ tool_message = self ._create_tool_message (
3362+ tool_result .function_name ,
3363+ tool_result .result , # Contains error message
3364+ tool_result .tool_call_id ,
3365+ tool_result .is_ollama
33223366 )
3323- messages .append (error_message )
3367+ messages .append (tool_message )
33243368
33253369 # Continue conversation after tool execution - get follow-up response
33263370 try :
@@ -5462,4 +5506,4 @@ def _generate_tool_definition(self, function_or_name) -> Optional[Dict]:
54625506 }
54635507 }
54645508 logging .debug (f"Generated tool definition: { tool_def } " )
5465- return tool_def
5509+ return tool_def
0 commit comments