Merge pull request #2380 from pipecat-ai/pk/deprecate-llm-messages-frame

Deprecate `LLMMessagesFrame`, `LLMUserResponseAggregator`, and `LLMAssistantResponseAggregator`
This commit is contained in:
kompfner
2025-08-07 15:13:01 -04:00
committed by GitHub
16 changed files with 143 additions and 84 deletions

View File

@@ -5,6 +5,20 @@ All notable changes to **Pipecat** will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Deprecated
- `LLMMessagesFrame` is deprecated, in favor of either:
- `LLMMessagesUpdateFrame` with `run_llm=True`
- `OpenAILLMContextFrame` with desired messages in a new context
- `LLMUserResponseAggregator` and `LLMAssistantResponseAggregator` are
deprecated, as they depended on the now-deprecated `LLMMessagesFrame`. Use
`LLMUserContextAggregator` and `LLMAssistantResponseAggregator` (or
LLM-specific subclasses thereof) instead.
## [0.0.78] - 2025-08-07 ## [0.0.78] - 2025-08-07
### Added ### Added

View File

@@ -29,6 +29,9 @@ CARTESIA_API_KEY=...
DAILY_API_KEY=... DAILY_API_KEY=...
DAILY_SAMPLE_ROOM_URL=https://... DAILY_SAMPLE_ROOM_URL=https://...
# Deepgram
DEEPGRAM_API_KEY=...
# ElevenLabs # ElevenLabs
ELEVENLABS_API_KEY=... ELEVENLABS_API_KEY=...
ELEVENLABS_VOICE_ID=... ELEVENLABS_VOICE_ID=...

View File

@@ -9,10 +9,14 @@ import os
from dotenv import load_dotenv from dotenv import load_dotenv
from loguru import logger from loguru import logger
from pipecat.frames.frames import EndFrame, LLMMessagesFrame from pipecat.frames.frames import EndFrame
from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineTask from pipecat.pipeline.task import PipelineTask
from pipecat.processors.aggregators.openai_llm_context import (
OpenAILLMContext,
OpenAILLMContextFrame,
)
from pipecat.runner.types import RunnerArguments from pipecat.runner.types import RunnerArguments
from pipecat.runner.utils import create_transport from pipecat.runner.utils import create_transport
from pipecat.services.cartesia.tts import CartesiaTTSService from pipecat.services.cartesia.tts import CartesiaTTSService
@@ -59,7 +63,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
# Register an event handler so we can play the audio when the client joins # Register an event handler so we can play the audio when the client joins
@transport.event_handler("on_client_connected") @transport.event_handler("on_client_connected")
async def on_client_connected(transport, client): async def on_client_connected(transport, client):
await task.queue_frames([LLMMessagesFrame(messages), EndFrame()]) await task.queue_frames([OpenAILLMContextFrame(OpenAILLMContext(messages)), EndFrame()])
runner = PipelineRunner(handle_sigint=runner_args.handle_sigint) runner = PipelineRunner(handle_sigint=runner_args.handle_sigint)

View File

@@ -15,13 +15,16 @@ from pipecat.frames.frames import (
DataFrame, DataFrame,
Frame, Frame,
LLMFullResponseStartFrame, LLMFullResponseStartFrame,
LLMMessagesFrame,
TextFrame, TextFrame,
) )
from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.sync_parallel_pipeline import SyncParallelPipeline from pipecat.pipeline.sync_parallel_pipeline import SyncParallelPipeline
from pipecat.pipeline.task import PipelineTask from pipecat.pipeline.task import PipelineTask
from pipecat.processors.aggregators.openai_llm_context import (
OpenAILLMContext,
OpenAILLMContextFrame,
)
from pipecat.processors.aggregators.sentence import SentenceAggregator from pipecat.processors.aggregators.sentence import SentenceAggregator
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from pipecat.runner.types import RunnerArguments from pipecat.runner.types import RunnerArguments
@@ -153,7 +156,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
} }
] ]
frames.append(MonthFrame(month=month)) frames.append(MonthFrame(month=month))
frames.append(LLMMessagesFrame(messages)) frames.append(OpenAILLMContextFrame(OpenAILLMContext(messages)))
task = PipelineTask( task = PipelineTask(
pipeline, pipeline,

View File

@@ -15,7 +15,6 @@ from loguru import logger
from pipecat.frames.frames import ( from pipecat.frames.frames import (
Frame, Frame,
LLMMessagesFrame,
OutputAudioRawFrame, OutputAudioRawFrame,
TextFrame, TextFrame,
TTSAudioRawFrame, TTSAudioRawFrame,
@@ -25,6 +24,10 @@ from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.sync_parallel_pipeline import SyncParallelPipeline from pipecat.pipeline.sync_parallel_pipeline import SyncParallelPipeline
from pipecat.pipeline.task import PipelineTask from pipecat.pipeline.task import PipelineTask
from pipecat.processors.aggregators.openai_llm_context import (
OpenAILLMContext,
OpenAILLMContextFrame,
)
from pipecat.processors.aggregators.sentence import SentenceAggregator from pipecat.processors.aggregators.sentence import SentenceAggregator
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from pipecat.services.cartesia.tts import CartesiaHttpTTSService from pipecat.services.cartesia.tts import CartesiaHttpTTSService
@@ -137,7 +140,7 @@ async def main():
) )
task = PipelineTask(pipeline) task = PipelineTask(pipeline)
await task.queue_frame(LLMMessagesFrame(messages)) await task.queue_frame(OpenAILLMContextFrame(OpenAILLMContext(messages)))
await task.stop_when_done() await task.stop_when_done()
await runner.run(task) await runner.run(task)

View File

@@ -16,13 +16,16 @@ from langchain_openai import ChatOpenAI
from loguru import logger from loguru import logger
from pipecat.audio.vad.silero import SileroVADAnalyzer from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.frames.frames import LLMMessagesFrame from pipecat.frames.frames import LLMMessagesUpdateFrame
from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat.processors.aggregators.llm_response import ( from pipecat.processors.aggregators.llm_response import (
LLMAssistantResponseAggregator, LLMAssistantContextAggregator,
LLMUserResponseAggregator, LLMUserContextAggregator,
)
from pipecat.processors.aggregators.openai_llm_context import (
OpenAILLMContext,
) )
from pipecat.processors.frameworks.langchain import LangchainProcessor from pipecat.processors.frameworks.langchain import LangchainProcessor
from pipecat.runner.types import RunnerArguments from pipecat.runner.types import RunnerArguments
@@ -97,8 +100,9 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
) )
lc = LangchainProcessor(history_chain) lc = LangchainProcessor(history_chain)
tma_in = LLMUserResponseAggregator() context = OpenAILLMContext()
tma_out = LLMAssistantResponseAggregator() tma_in = LLMUserContextAggregator(context=context)
tma_out = LLMAssistantContextAggregator(context=context)
pipeline = Pipeline( pipeline = Pipeline(
[ [
@@ -125,11 +129,11 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def on_client_connected(transport, client): async def on_client_connected(transport, client):
logger.info(f"Client connected") logger.info(f"Client connected")
# Kick off the conversation. # Kick off the conversation.
# the `LLMMessagesFrame` will be picked up by the LangchainProcessor using # An `OpenAILLMContextFrame` will be picked up by the LangchainProcessor using
# only the content of the last message to inject it in the prompt defined # only the content of the last message to inject it in the prompt defined
# above. So no role is required here. # above. So no role is required here.
messages = [({"content": "Please briefly introduce yourself to the user."})] messages = [({"content": "Please briefly introduce yourself to the user."})]
await task.queue_frames([LLMMessagesFrame(messages)]) await task.queue_frames([LLMMessagesUpdateFrame(messages, run_llm=True)])
@transport.event_handler("on_client_disconnected") @transport.event_handler("on_client_disconnected")
async def on_client_disconnected(transport, client): async def on_client_disconnected(transport, client):

View File

@@ -6,9 +6,13 @@ from typing import Tuple
import aiohttp import aiohttp
from dotenv import load_dotenv from dotenv import load_dotenv
from pipecat.frames.frames import AudioFrame, EndFrame, ImageFrame, LLMMessagesFrame, TextFrame from pipecat.frames.frames import AudioFrame, EndFrame, ImageFrame, TextFrame
from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.pipeline import Pipeline
from pipecat.processors.aggregators import SentenceAggregator from pipecat.processors.aggregators import SentenceAggregator
from pipecat.processors.aggregators.openai_llm_context import (
OpenAILLMContext,
OpenAILLMContextFrame,
)
from pipecat.runner.daily import configure from pipecat.runner.daily import configure
from pipecat.services.azure import AzureLLMService, AzureTTSService from pipecat.services.azure import AzureLLMService, AzureTTSService
from pipecat.services.elevenlabs import ElevenLabsTTSService from pipecat.services.elevenlabs import ElevenLabsTTSService
@@ -79,7 +83,7 @@ async def main():
sentence_aggregator = SentenceAggregator() sentence_aggregator = SentenceAggregator()
pipeline = Pipeline([llm, sentence_aggregator, tts1], source_queue, sink_queue) pipeline = Pipeline([llm, sentence_aggregator, tts1], source_queue, sink_queue)
await source_queue.put(LLMMessagesFrame(messages)) await source_queue.put(OpenAILLMContextFrame(OpenAILLMContext(messages)))
await source_queue.put(EndFrame()) await source_queue.put(EndFrame())
await pipeline.run_pipeline() await pipeline.run_pipeline()

View File

@@ -11,7 +11,7 @@ from dotenv import load_dotenv
from loguru import logger from loguru import logger
from pipecat.audio.vad.silero import SileroVADAnalyzer from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.frames.frames import EndFrame, LLMMessagesFrame, TTSSpeakFrame from pipecat.frames.frames import EndFrame, LLMMessagesAppendFrame, TTSSpeakFrame
from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask from pipecat.pipeline.task import PipelineParams, PipelineTask
@@ -75,23 +75,19 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def handle_user_idle(user_idle: UserIdleProcessor, retry_count: int) -> bool: async def handle_user_idle(user_idle: UserIdleProcessor, retry_count: int) -> bool:
if retry_count == 1: if retry_count == 1:
# First attempt: Add a gentle prompt to the conversation # First attempt: Add a gentle prompt to the conversation
messages.append( message = {
{ "role": "system",
"role": "system", "content": "The user has been quiet. Politely and briefly ask if they're still there.",
"content": "The user has been quiet. Politely and briefly ask if they're still there.", }
} await user_idle.push_frame(LLMMessagesAppendFrame([message], run_llm=True))
)
await user_idle.push_frame(LLMMessagesFrame(messages))
return True return True
elif retry_count == 2: elif retry_count == 2:
# Second attempt: More direct prompt # Second attempt: More direct prompt
messages.append( message = {
{ "role": "system",
"role": "system", "content": "The user is still inactive. Ask if they'd like to continue our conversation.",
"content": "The user is still inactive. Ask if they'd like to continue our conversation.", }
} await user_idle.push_frame(LLMMessagesAppendFrame([message], run_llm=True))
)
await user_idle.push_frame(LLMMessagesFrame(messages))
return True return True
else: else:
# Third attempt: End the conversation # Third attempt: End the conversation

View File

@@ -19,7 +19,6 @@ from pipecat.frames.frames import (
Frame, Frame,
FunctionCallInProgressFrame, FunctionCallInProgressFrame,
FunctionCallResultFrame, FunctionCallResultFrame,
LLMMessagesFrame,
StartFrame, StartFrame,
StartInterruptionFrame, StartInterruptionFrame,
StopInterruptionFrame, StopInterruptionFrame,
@@ -60,10 +59,6 @@ classifier_statement = "Determine if the user's statement ends with a complete t
class StatementJudgeContextFilter(FrameProcessor): class StatementJudgeContextFilter(FrameProcessor):
def __init__(self, notifier: BaseNotifier, **kwargs):
super().__init__(**kwargs)
self._notifier = notifier
async def process_frame(self, frame: Frame, direction: FrameDirection): async def process_frame(self, frame: Frame, direction: FrameDirection):
await super().process_frame(frame, direction) await super().process_frame(frame, direction)
# We must not block system frames. # We must not block system frames.
@@ -71,13 +66,8 @@ class StatementJudgeContextFilter(FrameProcessor):
await self.push_frame(frame, direction) await self.push_frame(frame, direction)
return return
# Just treat an LLMMessagesFrame as complete, no matter what. # We only want to handle OpenAILLMContextFrames, and only want to push through a simplified
if isinstance(frame, LLMMessagesFrame): # context frame that contains a system prompt and the most recent user messages,
await self._notifier.notify()
return
# Otherwise, we only want to handle OpenAILLMContextFrames, and only want to push a simple
# messages frame that contains a system prompt and the most recent user messages,
# concatenated. # concatenated.
if isinstance(frame, OpenAILLMContextFrame): if isinstance(frame, OpenAILLMContextFrame):
logger.debug(f"Context Frame: {frame}") logger.debug(f"Context Frame: {frame}")
@@ -96,7 +86,7 @@ class StatementJudgeContextFilter(FrameProcessor):
for content in message["content"]: for content in message["content"]:
if content["type"] == "text": if content["type"] == "text":
user_text_messages.insert(0, content["text"]) user_text_messages.insert(0, content["text"])
# If we have any user text content, push an LLMMessagesFrame # If we have any user text content, push a context frame with the simplified context.
if user_text_messages: if user_text_messages:
logger.debug(f"User text messages: {user_text_messages}") logger.debug(f"User text messages: {user_text_messages}")
user_message = " ".join(reversed(user_text_messages)) user_message = " ".join(reversed(user_text_messages))
@@ -110,7 +100,7 @@ class StatementJudgeContextFilter(FrameProcessor):
if last_assistant_message: if last_assistant_message:
messages.append(last_assistant_message) messages.append(last_assistant_message)
messages.append({"role": "user", "content": user_message}) messages.append({"role": "user", "content": user_message})
await self.push_frame(LLMMessagesFrame(messages)) await self.push_frame(OpenAILLMContextFrame(OpenAILLMContext(messages)))
class CompletenessCheck(FrameProcessor): class CompletenessCheck(FrameProcessor):
@@ -296,7 +286,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
# This turns the LLM context into an inference request to classify the user's speech # This turns the LLM context into an inference request to classify the user's speech
# as complete or incomplete. # as complete or incomplete.
statement_judge_context_filter = StatementJudgeContextFilter(notifier=notifier) statement_judge_context_filter = StatementJudgeContextFilter()
# This sends a UserStoppedSpeakingFrame and triggers the notifier event # This sends a UserStoppedSpeakingFrame and triggers the notifier event
completeness_check = CompletenessCheck(notifier=notifier) completeness_check = CompletenessCheck(notifier=notifier)
@@ -316,7 +306,6 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def pass_only_llm_trigger_frames(frame): async def pass_only_llm_trigger_frames(frame):
return ( return (
isinstance(frame, OpenAILLMContextFrame) isinstance(frame, OpenAILLMContextFrame)
or isinstance(frame, LLMMessagesFrame)
or isinstance(frame, StartInterruptionFrame) or isinstance(frame, StartInterruptionFrame)
or isinstance(frame, StopInterruptionFrame) or isinstance(frame, StopInterruptionFrame)
or isinstance(frame, FunctionCallInProgressFrame) or isinstance(frame, FunctionCallInProgressFrame)
@@ -331,14 +320,14 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
ParallelPipeline( ParallelPipeline(
[ [
# Ignore everything except an OpenAILLMContextFrame. Pass a specially constructed # Ignore everything except an OpenAILLMContextFrame. Pass a specially constructed
# LLMMessagesFrame to the statement classifier LLM. The only frame this # simplified context frame to the statement classifier LLM. The only frame this
# sub-pipeline will output is a UserStoppedSpeakingFrame. # sub-pipeline will output is a UserStoppedSpeakingFrame.
statement_judge_context_filter, statement_judge_context_filter,
statement_llm, statement_llm,
completeness_check, completeness_check,
], ],
[ [
# Block everything except OpenAILLMContextFrame and LLMMessagesFrame # Block everything except frames that trigger LLM inference.
FunctionFilter(filter=pass_only_llm_trigger_frames), FunctionFilter(filter=pass_only_llm_trigger_frames),
llm, llm,
bot_output_gate, # Buffer all llm/tts output until notified. bot_output_gate, # Buffer all llm/tts output until notified.

View File

@@ -19,7 +19,6 @@ from pipecat.frames.frames import (
Frame, Frame,
FunctionCallInProgressFrame, FunctionCallInProgressFrame,
FunctionCallResultFrame, FunctionCallResultFrame,
LLMMessagesFrame,
StartFrame, StartFrame,
StartInterruptionFrame, StartInterruptionFrame,
StopInterruptionFrame, StopInterruptionFrame,
@@ -266,10 +265,6 @@ Please be very concise in your responses. Unless you are explicitly asked to do
class StatementJudgeContextFilter(FrameProcessor): class StatementJudgeContextFilter(FrameProcessor):
def __init__(self, notifier: BaseNotifier, **kwargs):
super().__init__(**kwargs)
self._notifier = notifier
async def process_frame(self, frame: Frame, direction: FrameDirection): async def process_frame(self, frame: Frame, direction: FrameDirection):
await super().process_frame(frame, direction) await super().process_frame(frame, direction)
# We must not block system frames. # We must not block system frames.
@@ -277,14 +272,8 @@ class StatementJudgeContextFilter(FrameProcessor):
await self.push_frame(frame, direction) await self.push_frame(frame, direction)
return return
# Just treat an LLMMessagesFrame as complete, no matter what. # We only want to handle OpenAILLMContextFrames, and only want to push through a simplified
if isinstance(frame, LLMMessagesFrame): # context frame that contains a system prompt and the most recent user messages,
await self._notifier.notify()
return
# Otherwise, we only want to handle OpenAILLMContextFrames, and only want to push a simple
# messages frame that contains a system prompt and the most recent user messages,
# concatenated.
if isinstance(frame, OpenAILLMContextFrame): if isinstance(frame, OpenAILLMContextFrame):
# Take text content from the most recent user messages. # Take text content from the most recent user messages.
messages = frame.context.messages messages = frame.context.messages
@@ -301,7 +290,7 @@ class StatementJudgeContextFilter(FrameProcessor):
for content in message["content"]: for content in message["content"]:
if content["type"] == "text": if content["type"] == "text":
user_text_messages.insert(0, content["text"]) user_text_messages.insert(0, content["text"])
# If we have any user text content, push an LLMMessagesFrame # If we have any user text content, push a context frame with the simplified context.
if user_text_messages: if user_text_messages:
user_message = " ".join(reversed(user_text_messages)) user_message = " ".join(reversed(user_text_messages))
logger.debug(f"!!! {user_message}") logger.debug(f"!!! {user_message}")
@@ -314,7 +303,7 @@ class StatementJudgeContextFilter(FrameProcessor):
if last_assistant_message: if last_assistant_message:
messages.append(last_assistant_message) messages.append(last_assistant_message)
messages.append({"role": "user", "content": user_message}) messages.append({"role": "user", "content": user_message})
await self.push_frame(LLMMessagesFrame(messages)) await self.push_frame(OpenAILLMContextFrame(OpenAILLMContext(messages)))
class CompletenessCheck(FrameProcessor): class CompletenessCheck(FrameProcessor):
@@ -499,7 +488,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
# This turns the LLM context into an inference request to classify the user's speech # This turns the LLM context into an inference request to classify the user's speech
# as complete or incomplete. # as complete or incomplete.
statement_judge_context_filter = StatementJudgeContextFilter(notifier=notifier) statement_judge_context_filter = StatementJudgeContextFilter()
# This sends a UserStoppedSpeakingFrame and triggers the notifier event # This sends a UserStoppedSpeakingFrame and triggers the notifier event
completeness_check = CompletenessCheck(notifier=notifier) completeness_check = CompletenessCheck(notifier=notifier)
@@ -522,7 +511,6 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
async def pass_only_llm_trigger_frames(frame): async def pass_only_llm_trigger_frames(frame):
return ( return (
isinstance(frame, OpenAILLMContextFrame) isinstance(frame, OpenAILLMContextFrame)
or isinstance(frame, LLMMessagesFrame)
or isinstance(frame, StartInterruptionFrame) or isinstance(frame, StartInterruptionFrame)
or isinstance(frame, StopInterruptionFrame) or isinstance(frame, StopInterruptionFrame)
or isinstance(frame, FunctionCallInProgressFrame) or isinstance(frame, FunctionCallInProgressFrame)
@@ -542,14 +530,14 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
], ],
[ [
# Ignore everything except an OpenAILLMContextFrame. Pass a specially constructed # Ignore everything except an OpenAILLMContextFrame. Pass a specially constructed
# LLMMessagesFrame to the statement classifier LLM. The only frame this # simplified context frame to the statement classifier LLM. The only frame this
# sub-pipeline will output is a UserStoppedSpeakingFrame. # sub-pipeline will output is a UserStoppedSpeakingFrame.
statement_judge_context_filter, statement_judge_context_filter,
statement_llm, statement_llm,
completeness_check, completeness_check,
], ],
[ [
# Block everything except OpenAILLMContextFrame and LLMMessagesFrame # Block everything except frames that trigger LLM inference.
FunctionFilter(filter=pass_only_llm_trigger_frames), FunctionFilter(filter=pass_only_llm_trigger_frames),
llm, llm,
bot_output_gate, # Buffer all llm/tts output until notified. bot_output_gate, # Buffer all llm/tts output until notified.

View File

@@ -478,6 +478,12 @@ class TranscriptionUpdateFrame(DataFrame):
class LLMMessagesFrame(DataFrame): class LLMMessagesFrame(DataFrame):
"""Frame containing LLM messages for chat completion. """Frame containing LLM messages for chat completion.
.. deprecated:: 0.0.79
This class is deprecated and will be removed in a future version.
Instead, use either:
- `LLMMessagesUpdateFrame` with `run_llm=True`
- `OpenAILLMContextFrame` with desired messages in a new context
A frame containing a list of LLM messages. Used to signal that an LLM A frame containing a list of LLM messages. Used to signal that an LLM
service should run a chat completion and emit an LLMFullResponseStartFrame, service should run a chat completion and emit an LLMFullResponseStartFrame,
TextFrames and an LLMFullResponseEndFrame. Note that the `messages` TextFrames and an LLMFullResponseEndFrame. Note that the `messages`
@@ -490,6 +496,20 @@ class LLMMessagesFrame(DataFrame):
messages: List[dict] messages: List[dict]
def __post_init__(self):
super().__post_init__()
import warnings
warnings.simplefilter("always")
warnings.warn(
"LLMMessagesFrame is deprecated and will be removed in a future version. "
"Instead, use either "
"`LLMMessagesUpdateFrame` with `run_llm=True`, or "
"`OpenAILLMContextFrame` with desired messages in a new context",
DeprecationWarning,
stacklevel=2,
)
@dataclass @dataclass
class LLMMessagesAppendFrame(DataFrame): class LLMMessagesAppendFrame(DataFrame):

View File

@@ -12,6 +12,7 @@ LLM processing, and text-to-speech components in conversational AI pipelines.
""" """
import asyncio import asyncio
import warnings
from abc import abstractmethod from abc import abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Literal, Optional, Set from typing import Dict, List, Literal, Optional, Set
@@ -994,6 +995,10 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
class LLMUserResponseAggregator(LLMUserContextAggregator): class LLMUserResponseAggregator(LLMUserContextAggregator):
"""User response aggregator that outputs LLMMessagesFrame instead of context frames. """User response aggregator that outputs LLMMessagesFrame instead of context frames.
.. deprecated:: 0.0.79
This class is deprecated and will be removed in a future version.
Use `LLMUserContextAggregator` or another LLM-specific subclass instead.
This aggregator extends LLMUserContextAggregator but pushes LLMMessagesFrame This aggregator extends LLMUserContextAggregator but pushes LLMMessagesFrame
objects downstream instead of OpenAILLMContextFrame objects. This is useful objects downstream instead of OpenAILLMContextFrame objects. This is useful
when you need message-based output rather than context-based output. when you need message-based output rather than context-based output.
@@ -1013,6 +1018,12 @@ class LLMUserResponseAggregator(LLMUserContextAggregator):
params: Configuration parameters for aggregation behavior. params: Configuration parameters for aggregation behavior.
**kwargs: Additional arguments passed to parent class. **kwargs: Additional arguments passed to parent class.
""" """
warnings.warn(
"LLMUserResponseAggregator is deprecated and will be removed in a future version. "
"Use LLMUserContextAggregator or another LLM-specific subclass instead.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(context=OpenAILLMContext(messages), params=params, **kwargs) super().__init__(context=OpenAILLMContext(messages), params=params, **kwargs)
async def _process_aggregation(self): async def _process_aggregation(self):
@@ -1027,6 +1038,10 @@ class LLMUserResponseAggregator(LLMUserContextAggregator):
class LLMAssistantResponseAggregator(LLMAssistantContextAggregator): class LLMAssistantResponseAggregator(LLMAssistantContextAggregator):
"""Assistant response aggregator that outputs LLMMessagesFrame instead of context frames. """Assistant response aggregator that outputs LLMMessagesFrame instead of context frames.
.. deprecated:: 0.0.79
This class is deprecated and will be removed in a future version.
Use `LLMAssistantContextAggregator` or another LLM-specific subclass instead.
This aggregator extends LLMAssistantContextAggregator but pushes LLMMessagesFrame This aggregator extends LLMAssistantContextAggregator but pushes LLMMessagesFrame
objects downstream instead of OpenAILLMContextFrame objects. This is useful objects downstream instead of OpenAILLMContextFrame objects. This is useful
when you need message-based output rather than context-based output. when you need message-based output rather than context-based output.
@@ -1046,6 +1061,12 @@ class LLMAssistantResponseAggregator(LLMAssistantContextAggregator):
params: Configuration parameters for aggregation behavior. params: Configuration parameters for aggregation behavior.
**kwargs: Additional arguments passed to parent class. **kwargs: Additional arguments passed to parent class.
""" """
warnings.warn(
"LLMAssistantResponseAggregator is deprecated and will be removed in a future version. "
"Use LLMAssistantContextAggregator or another LLM-specific subclass instead.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(context=OpenAILLMContext(messages), params=params, **kwargs) super().__init__(context=OpenAILLMContext(messages), params=params, **kwargs)
async def push_aggregation(self): async def push_aggregation(self):

View File

@@ -12,13 +12,14 @@ in conversational pipelines.
""" """
from pipecat.frames.frames import TextFrame from pipecat.frames.frames import TextFrame
from pipecat.processors.aggregators.llm_response import LLMUserResponseAggregator from pipecat.processors.aggregators.llm_response import LLMUserContextAggregator
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
class UserResponseAggregator(LLMUserResponseAggregator): class UserResponseAggregator(LLMUserContextAggregator):
"""Aggregates user responses into TextFrame objects. """Aggregates user responses into TextFrame objects.
This aggregator extends LLMUserResponseAggregator to specifically handle This aggregator extends LLMUserContextAggregator to specifically handle
user input by collecting text responses and outputting them as TextFrame user input by collecting text responses and outputting them as TextFrame
objects when the aggregation is complete. objects when the aggregation is complete.
""" """
@@ -27,9 +28,9 @@ class UserResponseAggregator(LLMUserResponseAggregator):
"""Initialize the user response aggregator. """Initialize the user response aggregator.
Args: Args:
**kwargs: Additional arguments passed to parent LLMUserResponseAggregator. **kwargs: Additional arguments passed to parent LLMUserContextAggregator.
""" """
super().__init__(**kwargs) super().__init__(context=OpenAILLMContext(), **kwargs)
async def push_aggregation(self): async def push_aggregation(self):
"""Push the aggregated user response as a TextFrame. """Push the aggregated user response as a TextFrame.

View File

@@ -14,9 +14,9 @@ from pipecat.frames.frames import (
Frame, Frame,
LLMFullResponseEndFrame, LLMFullResponseEndFrame,
LLMFullResponseStartFrame, LLMFullResponseStartFrame,
LLMMessagesFrame,
TextFrame, TextFrame,
) )
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContextFrame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
try: try:
@@ -64,11 +64,11 @@ class LangchainProcessor(FrameProcessor):
""" """
await super().process_frame(frame, direction) await super().process_frame(frame, direction)
if isinstance(frame, LLMMessagesFrame): if isinstance(frame, OpenAILLMContextFrame):
# Messages are accumulated on the context as a list of messages. # Messages are accumulated on the context as a list of messages.
# The last one by the human is the one we want to send to the LLM. # The last one by the human is the one we want to send to the LLM.
logger.debug(f"Got transcription frame {frame}") logger.debug(f"Got transcription frame {frame}")
text: str = frame.messages[-1]["content"] text: str = frame.context.messages[-1]["content"]
await self._ainvoke(text.strip()) await self._ainvoke(text.strip())
else: else:

View File

@@ -448,14 +448,16 @@ class AnthropicLLMContext(OpenAILLMContext):
system: System message content. system: System message content.
""" """
super().__init__(messages=messages, tools=tools, tool_choice=tool_choice) super().__init__(messages=messages, tools=tools, tool_choice=tool_choice)
self.__setup_local()
self.system = system
def __setup_local(self):
# For beta prompt caching. This is a counter that tracks the number of turns # For beta prompt caching. This is a counter that tracks the number of turns
# we've seen above the cache threshold. We reset this when we reset the # we've seen above the cache threshold. We reset this when we reset the
# messages list. We only care about this number being 0, 1, or 2. But # messages list. We only care about this number being 0, 1, or 2. But
# it's easiest just to treat it as a counter. # it's easiest just to treat it as a counter.
self.turns_above_cache_threshold = 0 self.turns_above_cache_threshold = 0
return
self.system = system
@staticmethod @staticmethod
def upgrade_to_anthropic(obj: OpenAILLMContext) -> "AnthropicLLMContext": def upgrade_to_anthropic(obj: OpenAILLMContext) -> "AnthropicLLMContext":
@@ -472,6 +474,7 @@ class AnthropicLLMContext(OpenAILLMContext):
logger.debug(f"Upgrading to Anthropic: {obj}") logger.debug(f"Upgrading to Anthropic: {obj}")
if isinstance(obj, OpenAILLMContext) and not isinstance(obj, AnthropicLLMContext): if isinstance(obj, OpenAILLMContext) and not isinstance(obj, AnthropicLLMContext):
obj.__class__ = AnthropicLLMContext obj.__class__ = AnthropicLLMContext
obj.__setup_local()
obj._restructure_from_openai_messages() obj._restructure_from_openai_messages()
return obj return obj

View File

@@ -12,7 +12,7 @@ from langchain_core.language_models import FakeStreamingListLLM
from pipecat.frames.frames import ( from pipecat.frames.frames import (
LLMFullResponseEndFrame, LLMFullResponseEndFrame,
LLMFullResponseStartFrame, LLMFullResponseStartFrame,
LLMMessagesFrame, OpenAILLMContextAssistantTimestampFrame,
TextFrame, TextFrame,
TranscriptionFrame, TranscriptionFrame,
UserStartedSpeakingFrame, UserStartedSpeakingFrame,
@@ -21,8 +21,12 @@ from pipecat.frames.frames import (
from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.pipeline import Pipeline
from pipecat.processors.aggregators.llm_response import ( from pipecat.processors.aggregators.llm_response import (
LLMAssistantAggregatorParams, LLMAssistantAggregatorParams,
LLMAssistantResponseAggregator, LLMAssistantContextAggregator,
LLMUserResponseAggregator, LLMUserContextAggregator,
)
from pipecat.processors.aggregators.openai_llm_context import (
OpenAILLMContext,
OpenAILLMContextFrame,
) )
from pipecat.processors.frame_processor import FrameProcessor from pipecat.processors.frame_processor import FrameProcessor
from pipecat.processors.frameworks.langchain import LangchainProcessor from pipecat.processors.frameworks.langchain import LangchainProcessor
@@ -63,9 +67,10 @@ class TestLangchain(unittest.IsolatedAsyncioTestCase):
proc = LangchainProcessor(chain=chain) proc = LangchainProcessor(chain=chain)
self.mock_proc = self.MockProcessor("token_collector") self.mock_proc = self.MockProcessor("token_collector")
tma_in = LLMUserResponseAggregator(messages) context = OpenAILLMContext()
tma_out = LLMAssistantResponseAggregator( tma_in = LLMUserContextAggregator(context)
messages, params=LLMAssistantAggregatorParams(expect_stripped_words=False) tma_out = LLMAssistantContextAggregator(
context, params=LLMAssistantAggregatorParams(expect_stripped_words=False)
) )
pipeline = Pipeline([tma_in, proc, self.mock_proc, tma_out]) pipeline = Pipeline([tma_in, proc, self.mock_proc, tma_out])
@@ -79,7 +84,8 @@ class TestLangchain(unittest.IsolatedAsyncioTestCase):
expected_down_frames = [ expected_down_frames = [
UserStartedSpeakingFrame, UserStartedSpeakingFrame,
UserStoppedSpeakingFrame, UserStoppedSpeakingFrame,
LLMMessagesFrame, OpenAILLMContextFrame,
OpenAILLMContextAssistantTimestampFrame,
] ]
await run_test( await run_test(
pipeline, pipeline,