pipecat-ai / pipecat

Open Source framework for voice and multimodal conversational AI
BSD 2-Clause "Simplified" License
3.04k stars 245 forks source link

KeyError: 'content' in OpenAILLMContext.from_messages when UserIdleProcessor and function calling used together #344

Open bigahega opened 1 month ago

bigahega commented 1 month ago

I have the following pipeline definition:

context = OpenAILLMContext(messages, tools)
tma_in = LLMUserContextAggregator(context)
tma_out = LLMAssistantContextAggregator(context)
user_idle = UserIdleProcessor(callback=..., timeout=10.0)

pipeline = Pipeline([
    transport.input(),
    user_idle,
    tma_in,
    llm,
    tts,
    transport.output(),
    tma_out
])

Before the GPT invokes the registered function for the first time, user idle processor works without any problem. However, once the function is called, whenever user idle processor wants to add an llm message it causes an exception.

2024-08-06  23:07:58.738 | 2024-08-06 20:07:58.736 | ERROR    | pipecat.processors.frame_processor:push_frame:166 - Uncaught exception in LLMUserContextAggregator#0: 'content'

2024-08-06  23:07:48.739 | KeyError: 'content'

2024-08-06  23:07:48.739 | 

2024-08-06  23:07:48.739 |                └ {'role': 'assistant', 'tool_calls': [{'id': 'call_aU7K1w1B1oucg1dYMTze9Emt', 'function': {'arguments': '{"subject": "Coatley"...

2024-08-06  23:07:48.739 |     "content": message["content"],

2024-08-06  23:07:48.739 |   File "/usr/local/lib/python3.12/site-packages/pipecat/processors/aggregators/openai_llm_context.py", line 55, in from_messages

2024-08-06  23:07:48.739 |               └ <class 'pipecat.processors.aggregators.openai_llm_context.OpenAILLMContext'>

2024-08-06  23:07:48.739 |               │                └ <staticmethod(<function OpenAILLMContext.from_messages at 0x7fd38eb96020>)>

2024-08-06  23:07:48.739 |               │                │             └ LLMMessagesFrame(id=41138, name='LLMMessagesFrame#3', messages=[{'role': 'system', 'content': "\n                    Your out...

2024-08-06  23:07:48.739 |               │                │             │     └ [{'role': 'system', 'content': "\n                    Your output will be converted to audio so don't include special charact...

2024-08-06  23:07:48.739 |     context = OpenAILLMContext.from_messages(frame.messages)

2024-08-06  23:07:48.739 |   File "/usr/local/lib/python3.12/site-packages/pipecat/services/openai.py", line 228, in process_frame

2024-08-06  23:07:48.739 |           └ <pipecat.processors.aggregators.llm_response.LLMUserContextAggregator object at 0x7fd37f21e000>

2024-08-06  23:07:48.739 |           │    └ <pipecat.services.openai.OpenAILLMService object at 0x7fd37e0486b0>

2024-08-06  23:07:48.739 |           │    │     └ <function BaseOpenAILLMService.process_frame at 0x7fd390f13920>

2024-08-06  23:07:48.739 |           │    │     │             └ LLMMessagesFrame(id=41138, name='LLMMessagesFrame#3', messages=[{'role': 'system', 'content': "\n                    Your out...

2024-08-06  23:07:48.739 |           │    │     │             │      └ <FrameDirection.DOWNSTREAM: 1>

2024-08-06  23:07:48.739 |     await self._next.process_frame(frame, direction)

2024-08-06  23:07:48.739 | > File "/usr/local/lib/python3.12/site-packages/pipecat/processors/frame_processor.py", line 161, in push_frame

2024-08-06  23:07:48.739 |           └ <pipecat.processors.aggregators.llm_response.LLMUserContextAggregator object at 0x7fd37f21e000>

2024-08-06  23:07:48.739 |           │    └ <function FrameProcessor.push_frame at 0x7fd38ecc6020>

2024-08-06  23:07:48.739 |           │    │          └ LLMMessagesFrame(id=41138, name='LLMMessagesFrame#3', messages=[{'role': 'system', 'content': "\n                    Your out...

2024-08-06  23:07:48.739 |           │    │          │      └ <FrameDirection.DOWNSTREAM: 1>

2024-08-06  23:07:48.739 |     await self.push_frame(frame, direction)

2024-08-06  23:07:48.739 |   File "/usr/local/lib/python3.12/site-packages/pipecat/processors/aggregators/llm_response.py", line 139, in process_frame

2024-08-06  23:07:48.739 |           └ <pipecat.processors.user_idle_processor.UserIdleProcessor object at 0x7fd37f1e9880>

2024-08-06  23:07:48.738 |           │    └ <pipecat.processors.aggregators.llm_response.LLMUserContextAggregator object at 0x7fd37f21e000>

2024-08-06  23:07:48.738 |           │    │     └ <function LLMResponseAggregator.process_frame at 0x7fd38eceda80>

2024-08-06  23:07:48.738 |           │    │     │             └ LLMMessagesFrame(id=41138, name='LLMMessagesFrame#3', messages=[{'role': 'system', 'content': "\n                    Your out...

2024-08-06  23:07:48.738 |           │    │     │             │      └ <FrameDirection.DOWNSTREAM: 1>

2024-08-06  23:07:48.738 |     await self._next.process_frame(frame, direction)

2024-08-06  23:07:48.738 |   File "/usr/local/lib/python3.12/site-packages/pipecat/processors/frame_processor.py", line 161, in push_frame

2024-08-06  23:07:48.738 |           └ <pipecat.processors.user_idle_processor.UserIdleProcessor object at 0x7fd37f1e9880>

2024-08-06  23:07:48.738 |           │    └ <function FrameProcessor.push_frame at 0x7fd38ecc6020>

2024-08-06  23:07:48.738 |           │    │          └ LLMMessagesFrame(id=41138, name='LLMMessagesFrame#3', messages=[{'role': 'system', 'content': "\n                    Your out...

2024-08-06  23:07:48.738 |           │    │          │      └ <FrameDirection.DOWNSTREAM: 1>

2024-08-06  23:07:48.738 |     await self.push_frame(frame, direction)

2024-08-06  23:07:48.738 |   File "/usr/local/lib/python3.12/site-packages/pipecat/processors/async_frame_processor.py", line 60, in _push_frame_task_handler

2024-08-06  23:07:48.738 |     └ <Handle Task.task_wakeup(<Future finished result=None>)>

2024-08-06  23:07:48.738 |     │    └ <member '_context' of 'Handle' objects>

2024-08-06  23:07:48.738 |     │    │            └ <Handle Task.task_wakeup(<Future finished result=None>)>

2024-08-06  23:07:48.738 |     │    │            │    └ <member '_callback' of 'Handle' objects>

2024-08-06  23:07:48.738 |     │    │            │    │           └ <Handle Task.task_wakeup(<Future finished result=None>)>

2024-08-06  23:07:48.738 |     │    │            │    │           │    └ <member '_args' of 'Handle' objects>

2024-08-06  23:07:48.738 |     self._context.run(self._callback, *self._args)

2024-08-06  23:07:48.738 |   File "/usr/local/lib/python3.12/asyncio/events.py", line 88, in _run

2024-08-06  23:07:48.738 |     └ <Handle Task.task_wakeup(<Future finished result=None>)>

2024-08-06  23:07:48.738 |     │      └ <function Handle._run at 0x7fd3b577a840>

2024-08-06  23:07:48.738 |     handle._run()

2024-08-06  23:07:48.738 |   File "/usr/local/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once

2024-08-06  23:07:48.738 |     └ <_UnixSelectorEventLoop running=True closed=False debug=False>

2024-08-06  23:07:48.738 |     │    └ <function BaseEventLoop._run_once at 0x7fd3b56244a0>

2024-08-06  23:07:48.738 |     self._run_once()
bigahega commented 1 month ago

If anyone else comes across until its fixed for good, here is an ugly workaround:

            if "tool_calls" in message or message["role"] == "tool":
                context.add_message(message)
            elif "content" in message:
                context.add_message({
                    "content": message["content"],
                    "role": message["role"],
                    "name": message["name"] if "name" in message else message["role"]
                })

Monkey patched the OpenAILLMContext.from_messages function by replacing inside of the for loop with the above hack.