@@ -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