@@ -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,10 @@ 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+ if self ._async_tool_calls :
222+ self ._enqueue_tool_call_task (event )
223+ else :
224+ await self ._handle_tool_call (event )
220225 elif event .type == "audio" :
221226 await self ._put_event (
222227 RealtimeAudio (
@@ -752,10 +757,47 @@ def _cleanup_guardrail_tasks(self) -> None:
752757 task .cancel ()
753758 self ._guardrail_tasks .clear ()
754759
760+ def _enqueue_tool_call_task (self , event : RealtimeModelToolCallEvent ) -> None :
761+ """Run tool calls in the background to avoid blocking realtime transport."""
762+ task = asyncio .create_task (self ._handle_tool_call (event ))
763+ self ._tool_call_tasks .add (task )
764+ task .add_done_callback (self ._on_tool_call_task_done )
765+
766+ def _on_tool_call_task_done (self , task : asyncio .Task [Any ]) -> None :
767+ self ._tool_call_tasks .discard (task )
768+
769+ if task .cancelled ():
770+ return
771+
772+ exception = task .exception ()
773+ if exception is None :
774+ return
775+
776+ logger .exception ("Realtime tool call task failed" , exc_info = exception )
777+
778+ if self ._stored_exception is None :
779+ self ._stored_exception = exception
780+
781+ asyncio .create_task (
782+ self ._put_event (
783+ RealtimeError (
784+ info = self ._event_info ,
785+ error = {"message" : f"Tool call task failed: { exception } " },
786+ )
787+ )
788+ )
789+
790+ def _cleanup_tool_call_tasks (self ) -> None :
791+ for task in self ._tool_call_tasks :
792+ if not task .done ():
793+ task .cancel ()
794+ self ._tool_call_tasks .clear ()
795+
755796 async def _cleanup (self ) -> None :
756797 """Clean up all resources and mark session as closed."""
757798 # Cancel and cleanup guardrail tasks
758799 self ._cleanup_guardrail_tasks ()
800+ self ._cleanup_tool_call_tasks ()
759801
760802 # Remove ourselves as a listener
761803 self ._model .remove_listener (self )
0 commit comments