@@ -112,7 +112,7 @@ def __init__(
112112 }
113113 self ._event_queue : asyncio .Queue [RealtimeSessionEvent ] = asyncio .Queue ()
114114 self ._closed = False
115- self ._stored_exception : Exception | None = None
115+ self ._stored_exception : BaseException | None = None
116116
117117 # Guardrails state tracking
118118 self ._interrupted_response_ids : set [str ] = set ()
@@ -123,6 +123,8 @@ def __init__(
123123 )
124124
125125 self ._guardrail_tasks : set [asyncio .Task [Any ]] = set ()
126+ self ._tool_call_tasks : set [asyncio .Task [Any ]] = set ()
127+ self ._async_tool_calls : bool = bool (self ._run_config .get ("async_tool_calls" , True ))
126128
127129 @property
128130 def model (self ) -> RealtimeModel :
@@ -216,7 +218,11 @@ async def on_event(self, event: RealtimeModelEvent) -> None:
216218 if event .type == "error" :
217219 await self ._put_event (RealtimeError (info = self ._event_info , error = event .error ))
218220 elif event .type == "function_call" :
219- await self ._handle_tool_call (event )
221+ agent_snapshot = self ._current_agent
222+ if self ._async_tool_calls :
223+ self ._enqueue_tool_call_task (event , agent_snapshot )
224+ else :
225+ await self ._handle_tool_call (event , agent_snapshot = agent_snapshot )
220226 elif event .type == "audio" :
221227 await self ._put_event (
222228 RealtimeAudio (
@@ -384,11 +390,17 @@ async def _put_event(self, event: RealtimeSessionEvent) -> None:
384390 """Put an event into the queue."""
385391 await self ._event_queue .put (event )
386392
387- async def _handle_tool_call (self , event : RealtimeModelToolCallEvent ) -> None :
393+ async def _handle_tool_call (
394+ self ,
395+ event : RealtimeModelToolCallEvent ,
396+ * ,
397+ agent_snapshot : RealtimeAgent | None = None ,
398+ ) -> None :
388399 """Handle a tool call event."""
400+ agent = agent_snapshot or self ._current_agent
389401 tools , handoffs = await asyncio .gather (
390- self . _current_agent .get_all_tools (self ._context_wrapper ),
391- self ._get_handoffs (self . _current_agent , self ._context_wrapper ),
402+ agent .get_all_tools (self ._context_wrapper ),
403+ self ._get_handoffs (agent , self ._context_wrapper ),
392404 )
393405 function_map = {tool .name : tool for tool in tools if isinstance (tool , FunctionTool )}
394406 handoff_map = {handoff .tool_name : handoff for handoff in handoffs }
@@ -398,7 +410,7 @@ async def _handle_tool_call(self, event: RealtimeModelToolCallEvent) -> None:
398410 RealtimeToolStart (
399411 info = self ._event_info ,
400412 tool = function_map [event .name ],
401- agent = self . _current_agent ,
413+ agent = agent ,
402414 )
403415 )
404416
@@ -423,7 +435,7 @@ async def _handle_tool_call(self, event: RealtimeModelToolCallEvent) -> None:
423435 info = self ._event_info ,
424436 tool = func_tool ,
425437 output = result ,
426- agent = self . _current_agent ,
438+ agent = agent ,
427439 )
428440 )
429441 elif event .name in handoff_map :
@@ -444,7 +456,7 @@ async def _handle_tool_call(self, event: RealtimeModelToolCallEvent) -> None:
444456 )
445457
446458 # Store previous agent for event
447- previous_agent = self . _current_agent
459+ previous_agent = agent
448460
449461 # Update current agent
450462 self ._current_agent = result
@@ -752,10 +764,49 @@ def _cleanup_guardrail_tasks(self) -> None:
752764 task .cancel ()
753765 self ._guardrail_tasks .clear ()
754766
767+ def _enqueue_tool_call_task (
768+ self , event : RealtimeModelToolCallEvent , agent_snapshot : RealtimeAgent
769+ ) -> None :
770+ """Run tool calls in the background to avoid blocking realtime transport."""
771+ task = asyncio .create_task (self ._handle_tool_call (event , agent_snapshot = agent_snapshot ))
772+ self ._tool_call_tasks .add (task )
773+ task .add_done_callback (self ._on_tool_call_task_done )
774+
775+ def _on_tool_call_task_done (self , task : asyncio .Task [Any ]) -> None :
776+ self ._tool_call_tasks .discard (task )
777+
778+ if task .cancelled ():
779+ return
780+
781+ exception = task .exception ()
782+ if exception is None :
783+ return
784+
785+ logger .exception ("Realtime tool call task failed" , exc_info = exception )
786+
787+ if self ._stored_exception is None :
788+ self ._stored_exception = exception
789+
790+ asyncio .create_task (
791+ self ._put_event (
792+ RealtimeError (
793+ info = self ._event_info ,
794+ error = {"message" : f"Tool call task failed: { exception } " },
795+ )
796+ )
797+ )
798+
799+ def _cleanup_tool_call_tasks (self ) -> None :
800+ for task in self ._tool_call_tasks :
801+ if not task .done ():
802+ task .cancel ()
803+ self ._tool_call_tasks .clear ()
804+
755805 async def _cleanup (self ) -> None :
756806 """Clean up all resources and mark session as closed."""
757807 # Cancel and cleanup guardrail tasks
758808 self ._cleanup_guardrail_tasks ()
809+ self ._cleanup_tool_call_tasks ()
759810
760811 # Remove ourselves as a listener
761812 self ._model .remove_listener (self )
0 commit comments