From f0cbdc4e6831a890914a4d0f3951e524582ae730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Fri, 30 May 2025 09:18:01 -0700 Subject: [PATCH] LLMService: add `on_function_calls_started` event --- CHANGELOG.md | 4 ++++ src/pipecat/services/llm_service.py | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 715f8c7c7..3304454da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added LLM services `on_function_calls_started` event. This event will be + triggered when the LLM service receives function calls from the model and is + going to start executing them. + - Function calls can now be executed sequentially (in the order received in the completion) by passing `run_in_parallel=False` when creating your LLM service. By default, if the LLM completion returns 2 or more function calls diff --git a/src/pipecat/services/llm_service.py b/src/pipecat/services/llm_service.py index 1ea55fb13..6b419ad70 100644 --- a/src/pipecat/services/llm_service.py +++ b/src/pipecat/services/llm_service.py @@ -124,7 +124,23 @@ class FunctionCallRunnerItem: class LLMService(AIService): - """This class is a no-op but serves as a base class for LLM services.""" + """This is the base class for all LLM services. It handles function calling + registration and execution. The class also provides event handlers. + + An event to know when an LLM service completion timeout occurs: + + @task.event_handler("on_completion_timeout") + async def on_completion_timeout(service): + ... + + And an event to know that function calls have been received from the LLM + service and that we are going to start executing them: + + @task.event_handler("on_function_calls_started") + async def on_function_calls_started(service, function_calls: Sequence[FunctionCallFromLLM]): + ... + + """ # OpenAILLMAdapter is used as the default adapter since it aligns with most LLM implementations. # However, subclasses should override this with a more specific adapter when necessary. @@ -139,6 +155,7 @@ class LLMService(AIService): self._function_call_tasks: Dict[asyncio.Task, FunctionCallRunnerItem] = {} self._sequential_runner_task: Optional[asyncio.Task] = None + self._register_event_handler("on_function_calls_started") self._register_event_handler("on_completion_timeout") def get_llm_adapter(self) -> BaseLLMAdapter: @@ -219,6 +236,8 @@ class LLMService(AIService): return function_name in self._functions.keys() async def run_function_calls(self, function_calls: Sequence[FunctionCallFromLLM]): + await self._call_event_handler("on_function_calls_started", function_calls) + total_function_calls = len(function_calls) for index, function_call in enumerate(function_calls): if function_call.function_name in self._functions.keys():