diff --git a/src/pipecat/adapters/base_llm_adapter.py b/src/pipecat/adapters/base_llm_adapter.py index 95082a5d6..0c8876553 100644 --- a/src/pipecat/adapters/base_llm_adapter.py +++ b/src/pipecat/adapters/base_llm_adapter.py @@ -129,4 +129,42 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]): # Fallback to return the same tools in case they are not in a standard format return tools + def _warn_about_orphaned_tool_messages(self, context: LLMContext) -> None: + """Warn if context contains messages referencing tools that aren't currently available. + + This can happen when tools are removed/deactivated but the conversation history + still contains function calls or tool responses for those tools. Such orphaned + messages may cause API errors from the LLM provider. + + Args: + context: The LLM context to check. + """ + # Get the set of currently available tool names + available_tool_names: set[str] = set() + if isinstance(context.tools, ToolsSchema): + available_tool_names = {tool.name for tool in context.tools.standard_tools} + # Note: We don't check custom tools as they may have varying formats + + # Track orphaned function names found in messages + orphaned_tools: set[str] = set() + + for message in self.get_messages(context): + if isinstance(message, LLMSpecificMessage): + # Skip LLM-specific messages for now + continue + + # Check for tool_calls in assistant messages + if message.get("tool_calls"): + for tc in message["tool_calls"]: + func_name = tc.get("function", {}).get("name") + if func_name and available_tool_names and func_name not in available_tool_names: + orphaned_tools.add(func_name) + + # Log warning for orphaned messages + if orphaned_tools: + logger.warning( + f"Context contains references to tools that are no longer available: " + f"{sorted(orphaned_tools)}. This may cause unexpected behavior or API errors." + ) + # TODO: we can move the logic to also handle the Messages here diff --git a/src/pipecat/adapters/services/anthropic_adapter.py b/src/pipecat/adapters/services/anthropic_adapter.py index ecc87154c..2a1762cf6 100644 --- a/src/pipecat/adapters/services/anthropic_adapter.py +++ b/src/pipecat/adapters/services/anthropic_adapter.py @@ -59,6 +59,9 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]): Returns: Dictionary of parameters for invoking Anthropic's LLM API. """ + # Warn about orphaned tool-related messages + self._warn_about_orphaned_tool_messages(context) + messages = self._from_universal_context_messages(self.get_messages(context)) return { "system": messages.system, diff --git a/src/pipecat/adapters/services/aws_nova_sonic_adapter.py b/src/pipecat/adapters/services/aws_nova_sonic_adapter.py index 1b62f5edc..b061956d1 100644 --- a/src/pipecat/adapters/services/aws_nova_sonic_adapter.py +++ b/src/pipecat/adapters/services/aws_nova_sonic_adapter.py @@ -83,6 +83,9 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]): Returns: Dictionary of parameters for invoking AWS Nova Sonic's LLM API. """ + # Warn about orphaned tool-related messages + self._warn_about_orphaned_tool_messages(context) + messages = self._from_universal_context_messages(self.get_messages(context)) return { "system_instruction": messages.system_instruction, diff --git a/src/pipecat/adapters/services/bedrock_adapter.py b/src/pipecat/adapters/services/bedrock_adapter.py index ccbbe5e2e..02d1cfc56 100644 --- a/src/pipecat/adapters/services/bedrock_adapter.py +++ b/src/pipecat/adapters/services/bedrock_adapter.py @@ -56,6 +56,9 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]): Returns: Dictionary of parameters for invoking AWS Bedrock's LLM API. """ + # Warn about orphaned tool-related messages + self._warn_about_orphaned_tool_messages(context) + messages = self._from_universal_context_messages(self.get_messages(context)) return { "system": messages.system, diff --git a/src/pipecat/adapters/services/gemini_adapter.py b/src/pipecat/adapters/services/gemini_adapter.py index 4968c2719..d4e1f4c22 100644 --- a/src/pipecat/adapters/services/gemini_adapter.py +++ b/src/pipecat/adapters/services/gemini_adapter.py @@ -62,6 +62,9 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]): Returns: Dictionary of parameters for Gemini's API. """ + # Warn about orphaned tool-related messages + self._warn_about_orphaned_tool_messages(context) + messages = self._from_universal_context_messages(self.get_messages(context)) return { "system_instruction": messages.system_instruction, diff --git a/src/pipecat/adapters/services/grok_realtime_adapter.py b/src/pipecat/adapters/services/grok_realtime_adapter.py index 03a3ed475..07b9f7157 100644 --- a/src/pipecat/adapters/services/grok_realtime_adapter.py +++ b/src/pipecat/adapters/services/grok_realtime_adapter.py @@ -59,6 +59,9 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter): Returns: Dictionary of parameters for invoking Grok's Voice Agent API. """ + # Warn about orphaned tool-related messages + self._warn_about_orphaned_tool_messages(context) + messages = self._from_universal_context_messages(self.get_messages(context)) return { "system_instruction": messages.system_instruction, diff --git a/src/pipecat/adapters/services/open_ai_adapter.py b/src/pipecat/adapters/services/open_ai_adapter.py index f4b534f2c..ce2f398d5 100644 --- a/src/pipecat/adapters/services/open_ai_adapter.py +++ b/src/pipecat/adapters/services/open_ai_adapter.py @@ -60,6 +60,9 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]): Returns: Dictionary of parameters for OpenAI's ChatCompletion API. """ + # Warn about orphaned tool-related messages + self._warn_about_orphaned_tool_messages(context) + return { "messages": self._from_universal_context_messages(self.get_messages(context)), # NOTE; LLMContext's tools are guaranteed to be a ToolsSchema (or NOT_GIVEN) diff --git a/src/pipecat/adapters/services/open_ai_realtime_adapter.py b/src/pipecat/adapters/services/open_ai_realtime_adapter.py index 136dc1a2f..0ecec76aa 100644 --- a/src/pipecat/adapters/services/open_ai_realtime_adapter.py +++ b/src/pipecat/adapters/services/open_ai_realtime_adapter.py @@ -54,6 +54,9 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter): Returns: Dictionary of parameters for invoking OpenAI Realtime's API. """ + # Warn about orphaned tool-related messages + self._warn_about_orphaned_tool_messages(context) + messages = self._from_universal_context_messages(self.get_messages(context)) return { "system_instruction": messages.system_instruction,