From 53f809b7d5fbde6eb180463c34675db179b75bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Wed, 1 Apr 2026 13:58:09 -0700 Subject: [PATCH 1/2] Make function_call_timeout_secs optional and skip timeout task when unset Change the default from 10s to None so deferred function calls can run indefinitely when no timeout is configured. Only create the timeout task when a timeout is actually provided (per-call or service-level). --- src/pipecat/services/llm_service.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/pipecat/services/llm_service.py b/src/pipecat/services/llm_service.py index e4f96c388..37d7d094a 100644 --- a/src/pipecat/services/llm_service.py +++ b/src/pipecat/services/llm_service.py @@ -185,7 +185,7 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService): def __init__( self, run_in_parallel: bool = True, - function_call_timeout_secs: float = 10.0, + function_call_timeout_secs: Optional[float] = None, settings: Optional[LLMSettings] = None, **kwargs, ): @@ -194,8 +194,8 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService): Args: run_in_parallel: Whether to run function calls in parallel or sequentially. Defaults to True. - function_call_timeout_secs: Timeout in seconds for deferred function calls. - Defaults to 10.0 seconds. + function_call_timeout_secs: Optional timeout in seconds for deferred function + calls. settings: The runtime-updatable settings for the LLM service. **kwargs: Additional arguments passed to the parent AIService. @@ -753,11 +753,7 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService): # Start a timeout task for deferred function calls async def timeout_handler(): try: - effective_timeout = ( - item.timeout_secs - if item.timeout_secs is not None - else self._function_call_timeout_secs - ) + effective_timeout = item.timeout_secs or self._function_call_timeout_secs await asyncio.sleep(effective_timeout) logger.warning( f"{self} Function call [{runner_item.function_name}:{runner_item.tool_call_id}] timed out after {effective_timeout} seconds." @@ -768,13 +764,15 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService): except asyncio.CancelledError: raise - timeout_task = self.create_task(timeout_handler()) + if item.timeout_secs or self._function_call_timeout_secs: + timeout_task = self.create_task(timeout_handler()) + + # Yield to the event loop so the timeout task coroutine gets entered + # before it could be cancelled. Without this, cancelling the task before + # it starts would leave the coroutine in a "never awaited" state. + await asyncio.sleep(0) try: - # Yield to the event loop so the timeout task coroutine gets entered - # before it could be cancelled. Without this, cancelling the task before - # it starts would leave the coroutine in a "never awaited" state. - await asyncio.sleep(0) if isinstance(item.handler, DirectFunctionWrapper): # Handler is a DirectFunctionWrapper await item.handler.invoke( From 6ed4109da924b8827d79482f64d0c96f37530ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Wed, 1 Apr 2026 13:58:45 -0700 Subject: [PATCH 2/2] Add changelog for #4224 --- changelog/4224.changed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/4224.changed.md diff --git a/changelog/4224.changed.md b/changelog/4224.changed.md new file mode 100644 index 000000000..256204a24 --- /dev/null +++ b/changelog/4224.changed.md @@ -0,0 +1 @@ +- ⚠️ `LLMService.function_call_timeout_secs` now defaults to `None` instead of `10.0`. Deferred function calls will run indefinitely unless a timeout is explicitly set at the service level or per-call. If you relied on the previous 10-second default, pass `function_call_timeout_secs=10.0` explicitly.