Merge pull request #2827 from pipecat-ai/aleix/openai-realtime-move
move openai_realtime to openai.realtime
This commit is contained in:
@@ -45,6 +45,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
|
- `pipecat.service.openai_realtime` is now deprecated, use
|
||||||
|
`pipecat.services.openai.realtime` instead or
|
||||||
|
`pipecat.services.azure.realtime` for Azure Realtime.
|
||||||
|
|
||||||
- `pipecat.service.aws_nova_sonic` is now deprecated, use
|
- `pipecat.service.aws_nova_sonic` is now deprecated, use
|
||||||
`pipecat.services.aws.nova_sonic` instead.
|
`pipecat.services.aws.nova_sonic` instead.
|
||||||
|
|
||||||
|
|||||||
@@ -24,14 +24,15 @@ from pipecat.processors.transcript_processor import TranscriptProcessor
|
|||||||
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.llm_service import FunctionCallParams
|
from pipecat.services.llm_service import FunctionCallParams
|
||||||
from pipecat.services.openai_realtime import (
|
from pipecat.services.openai.realtime.events import (
|
||||||
|
AudioConfiguration,
|
||||||
|
AudioInput,
|
||||||
InputAudioNoiseReduction,
|
InputAudioNoiseReduction,
|
||||||
InputAudioTranscription,
|
InputAudioTranscription,
|
||||||
OpenAIRealtimeLLMService,
|
|
||||||
SemanticTurnDetection,
|
SemanticTurnDetection,
|
||||||
SessionProperties,
|
SessionProperties,
|
||||||
)
|
)
|
||||||
from pipecat.services.openai_realtime.events import AudioConfiguration, AudioInput
|
from pipecat.services.openai.realtime.llm import OpenAIRealtimeLLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||||
from pipecat.transports.daily.transport import DailyParams
|
from pipecat.transports.daily.transport import DailyParams
|
||||||
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ from pipecat.pipeline.task import PipelineParams, PipelineTask
|
|||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||||
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.azure.realtime.llm import AzureRealtimeLLMService
|
||||||
from pipecat.services.llm_service import FunctionCallParams
|
from pipecat.services.llm_service import FunctionCallParams
|
||||||
from pipecat.services.openai_realtime import (
|
from pipecat.services.openai.realtime.events import (
|
||||||
AzureRealtimeLLMService,
|
AudioConfiguration,
|
||||||
|
AudioInput,
|
||||||
InputAudioTranscription,
|
InputAudioTranscription,
|
||||||
SessionProperties,
|
SessionProperties,
|
||||||
)
|
)
|
||||||
from pipecat.services.openai_realtime.events import AudioConfiguration, AudioInput
|
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||||
from pipecat.transports.daily.transport import DailyParams
|
from pipecat.transports.daily.transport import DailyParams
|
||||||
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
||||||
|
|||||||
@@ -22,16 +22,17 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|||||||
from pipecat.processors.transcript_processor import TranscriptProcessor
|
from pipecat.processors.transcript_processor import TranscriptProcessor
|
||||||
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 import CartesiaTTSService
|
from pipecat.services.cartesia.tts import CartesiaTTSService
|
||||||
from pipecat.services.llm_service import FunctionCallParams
|
from pipecat.services.llm_service import FunctionCallParams
|
||||||
from pipecat.services.openai_realtime import (
|
from pipecat.services.openai.realtime.events import (
|
||||||
|
AudioConfiguration,
|
||||||
|
AudioInput,
|
||||||
InputAudioNoiseReduction,
|
InputAudioNoiseReduction,
|
||||||
InputAudioTranscription,
|
InputAudioTranscription,
|
||||||
OpenAIRealtimeLLMService,
|
|
||||||
SemanticTurnDetection,
|
SemanticTurnDetection,
|
||||||
SessionProperties,
|
SessionProperties,
|
||||||
)
|
)
|
||||||
from pipecat.services.openai_realtime.events import AudioConfiguration, AudioInput
|
from pipecat.services.openai.realtime.llm import OpenAIRealtimeLLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||||
from pipecat.transports.daily.transport import DailyParams
|
from pipecat.transports.daily.transport import DailyParams
|
||||||
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
||||||
|
|||||||
@@ -25,13 +25,14 @@ from pipecat.runner.types import RunnerArguments
|
|||||||
from pipecat.runner.utils import create_transport
|
from pipecat.runner.utils import create_transport
|
||||||
from pipecat.services.deepgram.stt import DeepgramSTTService
|
from pipecat.services.deepgram.stt import DeepgramSTTService
|
||||||
from pipecat.services.llm_service import FunctionCallParams
|
from pipecat.services.llm_service import FunctionCallParams
|
||||||
from pipecat.services.openai_realtime import (
|
from pipecat.services.openai.realtime.events import (
|
||||||
|
AudioConfiguration,
|
||||||
|
AudioInput,
|
||||||
InputAudioTranscription,
|
InputAudioTranscription,
|
||||||
OpenAIRealtimeLLMService,
|
|
||||||
SessionProperties,
|
SessionProperties,
|
||||||
TurnDetection,
|
TurnDetection,
|
||||||
)
|
)
|
||||||
from pipecat.services.openai_realtime.events import AudioConfiguration, AudioInput
|
from pipecat.services.openai.realtime.llm import OpenAIRealtimeLLMService
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||||
from pipecat.transports.daily.transport import DailyParams
|
from pipecat.transports.daily.transport import DailyParams
|
||||||
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
||||||
|
|||||||
@@ -97,9 +97,7 @@ class AIService(FrameProcessor):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _update_settings(self, settings: Mapping[str, Any]):
|
async def _update_settings(self, settings: Mapping[str, Any]):
|
||||||
from pipecat.services.openai_realtime_beta.events import (
|
from pipecat.services.openai.realtime.events import SessionProperties
|
||||||
SessionProperties,
|
|
||||||
)
|
|
||||||
|
|
||||||
for key, value in settings.items():
|
for key, value in settings.items():
|
||||||
logger.debug("Update request for:", key, value)
|
logger.debug("Update request for:", key, value)
|
||||||
@@ -111,9 +109,7 @@ class AIService(FrameProcessor):
|
|||||||
logger.debug("Attempting to update", key, value)
|
logger.debug("Attempting to update", key, value)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pipecat.services.openai_realtime_beta.events import (
|
from pipecat.services.openai.realtime.events import TurnDetection
|
||||||
TurnDetection,
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(self._session_properties, SessionProperties):
|
if isinstance(self._session_properties, SessionProperties):
|
||||||
current_properties = self._session_properties
|
current_properties = self._session_properties
|
||||||
|
|||||||
0
src/pipecat/services/azure/realtime/__init__.py
Normal file
0
src/pipecat/services/azure/realtime/__init__.py
Normal file
65
src/pipecat/services/azure/realtime/llm.py
Normal file
65
src/pipecat/services/azure/realtime/llm.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Azure OpenAI Realtime LLM service implementation."""
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from pipecat.services.openai.realtime.llm import OpenAIRealtimeLLMService
|
||||||
|
|
||||||
|
try:
|
||||||
|
from websockets.asyncio.client import connect as websocket_connect
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
logger.error(f"Exception: {e}")
|
||||||
|
logger.error("In order to use Azure Realtime, you need to `pip install pipecat-ai[openai]`.")
|
||||||
|
raise Exception(f"Missing module: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
class AzureRealtimeLLMService(OpenAIRealtimeLLMService):
|
||||||
|
"""Azure OpenAI Realtime LLM service with Azure-specific authentication.
|
||||||
|
|
||||||
|
Extends the OpenAI Realtime service to work with Azure OpenAI endpoints,
|
||||||
|
using Azure's authentication headers and endpoint format. Provides the same
|
||||||
|
real-time audio and text communication capabilities as the base OpenAI service.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
api_key: str,
|
||||||
|
base_url: str,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""Initialize Azure Realtime LLM service.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key: The API key for the Azure OpenAI service.
|
||||||
|
base_url: The full Azure WebSocket endpoint URL including api-version and deployment.
|
||||||
|
Example: "wss://my-project.openai.azure.com/openai/realtime?api-version=2024-10-01-preview&deployment=my-realtime-deployment"
|
||||||
|
**kwargs: Additional arguments passed to parent OpenAIRealtimeLLMService.
|
||||||
|
"""
|
||||||
|
super().__init__(base_url=base_url, api_key=api_key, **kwargs)
|
||||||
|
self.api_key = api_key
|
||||||
|
self.base_url = base_url
|
||||||
|
|
||||||
|
async def _connect(self):
|
||||||
|
try:
|
||||||
|
if self._websocket:
|
||||||
|
# Here we assume that if we have a websocket, we are connected. We
|
||||||
|
# handle disconnections in the send/recv code paths.
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(f"Connecting to {self.base_url}, api key: {self.api_key}")
|
||||||
|
self._websocket = await websocket_connect(
|
||||||
|
uri=self.base_url,
|
||||||
|
additional_headers={
|
||||||
|
"api-key": self.api_key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._receive_task = self.create_task(self._receive_task_handler())
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{self} initialization error: {e}")
|
||||||
|
self._websocket = None
|
||||||
@@ -10,6 +10,7 @@ from pipecat.services import DeprecatedModuleProxy
|
|||||||
|
|
||||||
from .image import *
|
from .image import *
|
||||||
from .llm import *
|
from .llm import *
|
||||||
|
from .realtime import *
|
||||||
from .stt import *
|
from .stt import *
|
||||||
from .tts import *
|
from .tts import *
|
||||||
|
|
||||||
|
|||||||
0
src/pipecat/services/openai/realtime/__init__.py
Normal file
0
src/pipecat/services/openai/realtime/__init__.py
Normal file
272
src/pipecat/services/openai/realtime/context.py
Normal file
272
src/pipecat/services/openai/realtime/context.py
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""OpenAI Realtime LLM context and aggregator implementations."""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from pipecat.frames.frames import (
|
||||||
|
Frame,
|
||||||
|
FunctionCallResultFrame,
|
||||||
|
InterimTranscriptionFrame,
|
||||||
|
LLMMessagesUpdateFrame,
|
||||||
|
LLMSetToolsFrame,
|
||||||
|
LLMTextFrame,
|
||||||
|
TranscriptionFrame,
|
||||||
|
)
|
||||||
|
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||||
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
|
from pipecat.services.openai.llm import (
|
||||||
|
OpenAIAssistantContextAggregator,
|
||||||
|
OpenAIUserContextAggregator,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import events
|
||||||
|
from .frames import RealtimeFunctionCallResultFrame, RealtimeMessagesUpdateFrame
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAIRealtimeLLMContext(OpenAILLMContext):
|
||||||
|
"""OpenAI Realtime LLM context with session management and message conversion.
|
||||||
|
|
||||||
|
Extends the standard OpenAI LLM context to support real-time session properties,
|
||||||
|
instruction management, and conversion between standard message formats and
|
||||||
|
realtime conversation items.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, messages=None, tools=None, **kwargs):
|
||||||
|
"""Initialize the OpenAIRealtimeLLMContext.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
messages: Initial conversation messages. Defaults to None.
|
||||||
|
tools: Available function tools. Defaults to None.
|
||||||
|
**kwargs: Additional arguments passed to parent OpenAILLMContext.
|
||||||
|
"""
|
||||||
|
super().__init__(messages=messages, tools=tools, **kwargs)
|
||||||
|
self.__setup_local()
|
||||||
|
|
||||||
|
def __setup_local(self):
|
||||||
|
self.llm_needs_settings_update = True
|
||||||
|
self.llm_needs_initial_messages = True
|
||||||
|
self._session_instructions = ""
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def upgrade_to_realtime(obj: OpenAILLMContext) -> "OpenAIRealtimeLLMContext":
|
||||||
|
"""Upgrade a standard OpenAI LLM context to a realtime context.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj: The OpenAILLMContext instance to upgrade.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The upgraded OpenAIRealtimeLLMContext instance.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, OpenAILLMContext) and not isinstance(obj, OpenAIRealtimeLLMContext):
|
||||||
|
obj.__class__ = OpenAIRealtimeLLMContext
|
||||||
|
obj.__setup_local()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# todo
|
||||||
|
# - finish implementing all frames
|
||||||
|
|
||||||
|
def from_standard_message(self, message):
|
||||||
|
"""Convert a standard message format to a realtime conversation item.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: The standard message dictionary to convert.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A ConversationItem instance for the realtime API.
|
||||||
|
"""
|
||||||
|
if message.get("role") == "user":
|
||||||
|
content = message.get("content")
|
||||||
|
if isinstance(message.get("content"), list):
|
||||||
|
content = ""
|
||||||
|
for c in message.get("content"):
|
||||||
|
if c.get("type") == "text":
|
||||||
|
content += " " + c.get("text")
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
f"Unhandled content type in context message: {c.get('type')} - {message}"
|
||||||
|
)
|
||||||
|
return events.ConversationItem(
|
||||||
|
role="user",
|
||||||
|
type="message",
|
||||||
|
content=[events.ItemContent(type="input_text", text=content)],
|
||||||
|
)
|
||||||
|
if message.get("role") == "assistant" and message.get("tool_calls"):
|
||||||
|
tc = message.get("tool_calls")[0]
|
||||||
|
return events.ConversationItem(
|
||||||
|
type="function_call",
|
||||||
|
call_id=tc["id"],
|
||||||
|
name=tc["function"]["name"],
|
||||||
|
arguments=tc["function"]["arguments"],
|
||||||
|
)
|
||||||
|
logger.error(f"Unhandled message type in from_standard_message: {message}")
|
||||||
|
|
||||||
|
def get_messages_for_initializing_history(self):
|
||||||
|
"""Get conversation items for initializing the realtime session history.
|
||||||
|
|
||||||
|
Converts the context's messages to a format suitable for the realtime API,
|
||||||
|
handling system instructions and conversation history packaging.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of conversation items for session initialization.
|
||||||
|
"""
|
||||||
|
# We can't load a long conversation history into the openai realtime api yet. (The API/model
|
||||||
|
# forgets that it can do audio, if you do a series of `conversation.item.create` calls.) So
|
||||||
|
# our general strategy until this is fixed is just to put everything into a first "user"
|
||||||
|
# message as a single input.
|
||||||
|
if not self.messages:
|
||||||
|
return []
|
||||||
|
|
||||||
|
messages = copy.deepcopy(self.messages)
|
||||||
|
|
||||||
|
# If we have a "system" message as our first message, let's pull that out into session
|
||||||
|
# "instructions"
|
||||||
|
if messages[0].get("role") == "system":
|
||||||
|
self.llm_needs_settings_update = True
|
||||||
|
system = messages.pop(0)
|
||||||
|
content = system.get("content")
|
||||||
|
if isinstance(content, str):
|
||||||
|
self._session_instructions = content
|
||||||
|
elif isinstance(content, list):
|
||||||
|
self._session_instructions = content[0].get("text")
|
||||||
|
if not messages:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# If we have just a single "user" item, we can just send it normally
|
||||||
|
if len(messages) == 1 and messages[0].get("role") == "user":
|
||||||
|
return [self.from_standard_message(messages[0])]
|
||||||
|
|
||||||
|
# Otherwise, let's pack everything into a single "user" message with a bit of
|
||||||
|
# explanation for the LLM
|
||||||
|
intro_text = """
|
||||||
|
This is a previously saved conversation. Please treat this conversation history as a
|
||||||
|
starting point for the current conversation."""
|
||||||
|
|
||||||
|
trailing_text = """
|
||||||
|
This is the end of the previously saved conversation. Please continue the conversation
|
||||||
|
from here. If the last message is a user instruction or question, act on that instruction
|
||||||
|
or answer the question. If the last message is an assistant response, simple say that you
|
||||||
|
are ready to continue the conversation."""
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"type": "message",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "input_text",
|
||||||
|
"text": "\n\n".join(
|
||||||
|
[intro_text, json.dumps(messages, indent=2), trailing_text]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def add_user_content_item_as_message(self, item):
|
||||||
|
"""Add a user content item as a standard message to the context.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item: The conversation item to add as a user message.
|
||||||
|
"""
|
||||||
|
message = {
|
||||||
|
"role": "user",
|
||||||
|
"content": [{"type": "text", "text": item.content[0].transcript}],
|
||||||
|
}
|
||||||
|
self.add_message(message)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAIRealtimeUserContextAggregator(OpenAIUserContextAggregator):
|
||||||
|
"""User context aggregator for OpenAI Realtime API.
|
||||||
|
|
||||||
|
Handles user input frames and generates appropriate context updates
|
||||||
|
for the realtime conversation, including message updates and tool settings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
context: The OpenAI realtime LLM context.
|
||||||
|
**kwargs: Additional arguments passed to parent aggregator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def process_frame(
|
||||||
|
self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM
|
||||||
|
):
|
||||||
|
"""Process incoming frames and handle realtime-specific frame types.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The frame to process.
|
||||||
|
direction: The direction of frame flow in the pipeline.
|
||||||
|
"""
|
||||||
|
await super().process_frame(frame, direction)
|
||||||
|
# Parent does not push LLMMessagesUpdateFrame. This ensures that in a typical pipeline,
|
||||||
|
# messages are only processed by the user context aggregator, which is generally what we want. But
|
||||||
|
# we also need to send new messages over the websocket, so the openai realtime API has them
|
||||||
|
# in its context.
|
||||||
|
if isinstance(frame, LLMMessagesUpdateFrame):
|
||||||
|
await self.push_frame(RealtimeMessagesUpdateFrame(context=self._context))
|
||||||
|
|
||||||
|
# Parent also doesn't push the LLMSetToolsFrame.
|
||||||
|
if isinstance(frame, LLMSetToolsFrame):
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
async def push_aggregation(self):
|
||||||
|
"""Push user input aggregation.
|
||||||
|
|
||||||
|
Currently ignores all user input coming into the pipeline as realtime
|
||||||
|
audio input is handled directly by the service.
|
||||||
|
"""
|
||||||
|
# for the moment, ignore all user input coming into the pipeline.
|
||||||
|
# todo: think about whether/how to fix this to allow for text input from
|
||||||
|
# upstream (transport/transcription, or other sources)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAIRealtimeAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
||||||
|
"""Assistant context aggregator for OpenAI Realtime API.
|
||||||
|
|
||||||
|
Handles assistant output frames from the realtime service, filtering
|
||||||
|
out duplicate text frames and managing function call results.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
context: The OpenAI realtime LLM context.
|
||||||
|
**kwargs: Additional arguments passed to parent aggregator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The LLMAssistantContextAggregator uses TextFrames to aggregate the LLM output,
|
||||||
|
# but the OpenAIRealtimeLLMService pushes LLMTextFrames and TTSTextFrames. We
|
||||||
|
# need to override this proces_frame for LLMTextFrame, so that only the TTSTextFrames
|
||||||
|
# are process. This ensures that the context gets only one set of messages.
|
||||||
|
# OpenAIRealtimeLLMService also pushes TranscriptionFrames and InterimTranscriptionFrames,
|
||||||
|
# so we need to ignore pushing those as well, as they're also TextFrames.
|
||||||
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
|
"""Process assistant frames, filtering out duplicate text content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The frame to process.
|
||||||
|
direction: The direction of frame flow in the pipeline.
|
||||||
|
"""
|
||||||
|
if not isinstance(frame, (LLMTextFrame, TranscriptionFrame, InterimTranscriptionFrame)):
|
||||||
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
|
async def handle_function_call_result(self, frame: FunctionCallResultFrame):
|
||||||
|
"""Handle function call result and notify the realtime service.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The function call result frame to handle.
|
||||||
|
"""
|
||||||
|
await super().handle_function_call_result(frame)
|
||||||
|
|
||||||
|
# The standard function callback code path pushes the FunctionCallResultFrame from the llm itself,
|
||||||
|
# so we didn't have a chance to add the result to the openai realtime api context. Let's push a
|
||||||
|
# special frame to do that.
|
||||||
|
await self.push_frame(
|
||||||
|
RealtimeFunctionCallResultFrame(result_frame=frame), FrameDirection.UPSTREAM
|
||||||
|
)
|
||||||
1106
src/pipecat/services/openai/realtime/events.py
Normal file
1106
src/pipecat/services/openai/realtime/events.py
Normal file
File diff suppressed because it is too large
Load Diff
37
src/pipecat/services/openai/realtime/frames.py
Normal file
37
src/pipecat/services/openai/realtime/frames.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Custom frame types for OpenAI Realtime API integration."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from pipecat.frames.frames import DataFrame, FunctionCallResultFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pipecat.services.openai.realtime.context import OpenAIRealtimeLLMContext
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RealtimeMessagesUpdateFrame(DataFrame):
|
||||||
|
"""Frame indicating that the realtime context messages have been updated.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
context: The updated OpenAI realtime LLM context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
context: "OpenAIRealtimeLLMContext"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RealtimeFunctionCallResultFrame(DataFrame):
|
||||||
|
"""Frame containing function call results for the realtime service.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
result_frame: The function call result frame to send to the realtime API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
result_frame: FunctionCallResultFrame
|
||||||
@@ -1,9 +1,27 @@
|
|||||||
from .azure import AzureRealtimeLLMService
|
#
|
||||||
from .events import (
|
# Copyright (c) 2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from pipecat.services.azure.realtime.llm import AzureRealtimeLLMService
|
||||||
|
from pipecat.services.openai.realtime.events import (
|
||||||
InputAudioNoiseReduction,
|
InputAudioNoiseReduction,
|
||||||
InputAudioTranscription,
|
InputAudioTranscription,
|
||||||
SemanticTurnDetection,
|
SemanticTurnDetection,
|
||||||
SessionProperties,
|
SessionProperties,
|
||||||
TurnDetection,
|
TurnDetection,
|
||||||
)
|
)
|
||||||
from .openai import OpenAIRealtimeLLMService
|
from pipecat.services.openai.realtime.llm import OpenAIRealtimeLLMService
|
||||||
|
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
warnings.warn(
|
||||||
|
"Types in pipecat.services.openai_realtime are deprecated. "
|
||||||
|
"Please use the equivalent types from "
|
||||||
|
"pipecat.services.openai.realtime instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,67 +1,21 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2024–2025, Daily
|
# Copyright (c) 2025, Daily
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
#
|
#
|
||||||
|
|
||||||
"""Azure OpenAI Realtime LLM service implementation."""
|
"""Azure OpenAI Realtime LLM service implementation."""
|
||||||
|
|
||||||
from loguru import logger
|
import warnings
|
||||||
|
|
||||||
from .openai import OpenAIRealtimeLLMService
|
from pipecat.services.azure.realtime.llm import *
|
||||||
|
|
||||||
try:
|
with warnings.catch_warnings():
|
||||||
from websockets.asyncio.client import connect as websocket_connect
|
warnings.simplefilter("always")
|
||||||
except ModuleNotFoundError as e:
|
warnings.warn(
|
||||||
logger.error(f"Exception: {e}")
|
"Types in pipecat.services.openai_realtime.azure are deprecated. "
|
||||||
logger.error(
|
"Please use the equivalent types from "
|
||||||
"In order to use OpenAI, you need to `pip install pipecat-ai[openai]`. Also, set `OPENAI_API_KEY` environment variable."
|
"pipecat.services.azure.realtime.llm instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
raise Exception(f"Missing module: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
class AzureRealtimeLLMService(OpenAIRealtimeLLMService):
|
|
||||||
"""Azure OpenAI Realtime LLM service with Azure-specific authentication.
|
|
||||||
|
|
||||||
Extends the OpenAI Realtime service to work with Azure OpenAI endpoints,
|
|
||||||
using Azure's authentication headers and endpoint format. Provides the same
|
|
||||||
real-time audio and text communication capabilities as the base OpenAI service.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
api_key: str,
|
|
||||||
base_url: str,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
"""Initialize Azure Realtime LLM service.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
api_key: The API key for the Azure OpenAI service.
|
|
||||||
base_url: The full Azure WebSocket endpoint URL including api-version and deployment.
|
|
||||||
Example: "wss://my-project.openai.azure.com/openai/realtime?api-version=2024-10-01-preview&deployment=my-realtime-deployment"
|
|
||||||
**kwargs: Additional arguments passed to parent OpenAIRealtimeLLMService.
|
|
||||||
"""
|
|
||||||
super().__init__(base_url=base_url, api_key=api_key, **kwargs)
|
|
||||||
self.api_key = api_key
|
|
||||||
self.base_url = base_url
|
|
||||||
|
|
||||||
async def _connect(self):
|
|
||||||
try:
|
|
||||||
if self._websocket:
|
|
||||||
# Here we assume that if we have a websocket, we are connected. We
|
|
||||||
# handle disconnections in the send/recv code paths.
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info(f"Connecting to {self.base_url}, api key: {self.api_key}")
|
|
||||||
self._websocket = await websocket_connect(
|
|
||||||
uri=self.base_url,
|
|
||||||
additional_headers={
|
|
||||||
"api-key": self.api_key,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self._receive_task = self.create_task(self._receive_task_handler())
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{self} initialization error: {e}")
|
|
||||||
self._websocket = None
|
|
||||||
|
|||||||
@@ -1,272 +1,21 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2024–2025, Daily
|
# Copyright (c) 2025, Daily
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
#
|
#
|
||||||
|
|
||||||
"""OpenAI Realtime LLM context and aggregator implementations."""
|
"""OpenAI Realtime LLM context and aggregator implementations."""
|
||||||
|
|
||||||
import copy
|
import warnings
|
||||||
import json
|
|
||||||
|
|
||||||
from loguru import logger
|
from pipecat.services.openai.realtime.context import *
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
with warnings.catch_warnings():
|
||||||
Frame,
|
warnings.simplefilter("always")
|
||||||
FunctionCallResultFrame,
|
warnings.warn(
|
||||||
InterimTranscriptionFrame,
|
"Types in pipecat.services.openai_realtime.context are deprecated. "
|
||||||
LLMMessagesUpdateFrame,
|
"Please use the equivalent types from "
|
||||||
LLMSetToolsFrame,
|
"pipecat.services.openai.realtime.context instead.",
|
||||||
LLMTextFrame,
|
DeprecationWarning,
|
||||||
TranscriptionFrame,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
|
||||||
from pipecat.services.openai.llm import (
|
|
||||||
OpenAIAssistantContextAggregator,
|
|
||||||
OpenAIUserContextAggregator,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import events
|
|
||||||
from .frames import RealtimeFunctionCallResultFrame, RealtimeMessagesUpdateFrame
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAIRealtimeLLMContext(OpenAILLMContext):
|
|
||||||
"""OpenAI Realtime LLM context with session management and message conversion.
|
|
||||||
|
|
||||||
Extends the standard OpenAI LLM context to support real-time session properties,
|
|
||||||
instruction management, and conversion between standard message formats and
|
|
||||||
realtime conversation items.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, messages=None, tools=None, **kwargs):
|
|
||||||
"""Initialize the OpenAIRealtimeLLMContext.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
messages: Initial conversation messages. Defaults to None.
|
|
||||||
tools: Available function tools. Defaults to None.
|
|
||||||
**kwargs: Additional arguments passed to parent OpenAILLMContext.
|
|
||||||
"""
|
|
||||||
super().__init__(messages=messages, tools=tools, **kwargs)
|
|
||||||
self.__setup_local()
|
|
||||||
|
|
||||||
def __setup_local(self):
|
|
||||||
self.llm_needs_settings_update = True
|
|
||||||
self.llm_needs_initial_messages = True
|
|
||||||
self._session_instructions = ""
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def upgrade_to_realtime(obj: OpenAILLMContext) -> "OpenAIRealtimeLLMContext":
|
|
||||||
"""Upgrade a standard OpenAI LLM context to a realtime context.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
obj: The OpenAILLMContext instance to upgrade.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The upgraded OpenAIRealtimeLLMContext instance.
|
|
||||||
"""
|
|
||||||
if isinstance(obj, OpenAILLMContext) and not isinstance(obj, OpenAIRealtimeLLMContext):
|
|
||||||
obj.__class__ = OpenAIRealtimeLLMContext
|
|
||||||
obj.__setup_local()
|
|
||||||
return obj
|
|
||||||
|
|
||||||
# todo
|
|
||||||
# - finish implementing all frames
|
|
||||||
|
|
||||||
def from_standard_message(self, message):
|
|
||||||
"""Convert a standard message format to a realtime conversation item.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
message: The standard message dictionary to convert.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A ConversationItem instance for the realtime API.
|
|
||||||
"""
|
|
||||||
if message.get("role") == "user":
|
|
||||||
content = message.get("content")
|
|
||||||
if isinstance(message.get("content"), list):
|
|
||||||
content = ""
|
|
||||||
for c in message.get("content"):
|
|
||||||
if c.get("type") == "text":
|
|
||||||
content += " " + c.get("text")
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
f"Unhandled content type in context message: {c.get('type')} - {message}"
|
|
||||||
)
|
|
||||||
return events.ConversationItem(
|
|
||||||
role="user",
|
|
||||||
type="message",
|
|
||||||
content=[events.ItemContent(type="input_text", text=content)],
|
|
||||||
)
|
|
||||||
if message.get("role") == "assistant" and message.get("tool_calls"):
|
|
||||||
tc = message.get("tool_calls")[0]
|
|
||||||
return events.ConversationItem(
|
|
||||||
type="function_call",
|
|
||||||
call_id=tc["id"],
|
|
||||||
name=tc["function"]["name"],
|
|
||||||
arguments=tc["function"]["arguments"],
|
|
||||||
)
|
|
||||||
logger.error(f"Unhandled message type in from_standard_message: {message}")
|
|
||||||
|
|
||||||
def get_messages_for_initializing_history(self):
|
|
||||||
"""Get conversation items for initializing the realtime session history.
|
|
||||||
|
|
||||||
Converts the context's messages to a format suitable for the realtime API,
|
|
||||||
handling system instructions and conversation history packaging.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of conversation items for session initialization.
|
|
||||||
"""
|
|
||||||
# We can't load a long conversation history into the openai realtime api yet. (The API/model
|
|
||||||
# forgets that it can do audio, if you do a series of `conversation.item.create` calls.) So
|
|
||||||
# our general strategy until this is fixed is just to put everything into a first "user"
|
|
||||||
# message as a single input.
|
|
||||||
if not self.messages:
|
|
||||||
return []
|
|
||||||
|
|
||||||
messages = copy.deepcopy(self.messages)
|
|
||||||
|
|
||||||
# If we have a "system" message as our first message, let's pull that out into session
|
|
||||||
# "instructions"
|
|
||||||
if messages[0].get("role") == "system":
|
|
||||||
self.llm_needs_settings_update = True
|
|
||||||
system = messages.pop(0)
|
|
||||||
content = system.get("content")
|
|
||||||
if isinstance(content, str):
|
|
||||||
self._session_instructions = content
|
|
||||||
elif isinstance(content, list):
|
|
||||||
self._session_instructions = content[0].get("text")
|
|
||||||
if not messages:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# If we have just a single "user" item, we can just send it normally
|
|
||||||
if len(messages) == 1 and messages[0].get("role") == "user":
|
|
||||||
return [self.from_standard_message(messages[0])]
|
|
||||||
|
|
||||||
# Otherwise, let's pack everything into a single "user" message with a bit of
|
|
||||||
# explanation for the LLM
|
|
||||||
intro_text = """
|
|
||||||
This is a previously saved conversation. Please treat this conversation history as a
|
|
||||||
starting point for the current conversation."""
|
|
||||||
|
|
||||||
trailing_text = """
|
|
||||||
This is the end of the previously saved conversation. Please continue the conversation
|
|
||||||
from here. If the last message is a user instruction or question, act on that instruction
|
|
||||||
or answer the question. If the last message is an assistant response, simple say that you
|
|
||||||
are ready to continue the conversation."""
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"type": "message",
|
|
||||||
"content": [
|
|
||||||
{
|
|
||||||
"type": "input_text",
|
|
||||||
"text": "\n\n".join(
|
|
||||||
[intro_text, json.dumps(messages, indent=2), trailing_text]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
def add_user_content_item_as_message(self, item):
|
|
||||||
"""Add a user content item as a standard message to the context.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
item: The conversation item to add as a user message.
|
|
||||||
"""
|
|
||||||
message = {
|
|
||||||
"role": "user",
|
|
||||||
"content": [{"type": "text", "text": item.content[0].transcript}],
|
|
||||||
}
|
|
||||||
self.add_message(message)
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAIRealtimeUserContextAggregator(OpenAIUserContextAggregator):
|
|
||||||
"""User context aggregator for OpenAI Realtime API.
|
|
||||||
|
|
||||||
Handles user input frames and generates appropriate context updates
|
|
||||||
for the realtime conversation, including message updates and tool settings.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
context: The OpenAI realtime LLM context.
|
|
||||||
**kwargs: Additional arguments passed to parent aggregator.
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def process_frame(
|
|
||||||
self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM
|
|
||||||
):
|
|
||||||
"""Process incoming frames and handle realtime-specific frame types.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
frame: The frame to process.
|
|
||||||
direction: The direction of frame flow in the pipeline.
|
|
||||||
"""
|
|
||||||
await super().process_frame(frame, direction)
|
|
||||||
# Parent does not push LLMMessagesUpdateFrame. This ensures that in a typical pipeline,
|
|
||||||
# messages are only processed by the user context aggregator, which is generally what we want. But
|
|
||||||
# we also need to send new messages over the websocket, so the openai realtime API has them
|
|
||||||
# in its context.
|
|
||||||
if isinstance(frame, LLMMessagesUpdateFrame):
|
|
||||||
await self.push_frame(RealtimeMessagesUpdateFrame(context=self._context))
|
|
||||||
|
|
||||||
# Parent also doesn't push the LLMSetToolsFrame.
|
|
||||||
if isinstance(frame, LLMSetToolsFrame):
|
|
||||||
await self.push_frame(frame, direction)
|
|
||||||
|
|
||||||
async def push_aggregation(self):
|
|
||||||
"""Push user input aggregation.
|
|
||||||
|
|
||||||
Currently ignores all user input coming into the pipeline as realtime
|
|
||||||
audio input is handled directly by the service.
|
|
||||||
"""
|
|
||||||
# for the moment, ignore all user input coming into the pipeline.
|
|
||||||
# todo: think about whether/how to fix this to allow for text input from
|
|
||||||
# upstream (transport/transcription, or other sources)
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAIRealtimeAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|
||||||
"""Assistant context aggregator for OpenAI Realtime API.
|
|
||||||
|
|
||||||
Handles assistant output frames from the realtime service, filtering
|
|
||||||
out duplicate text frames and managing function call results.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
context: The OpenAI realtime LLM context.
|
|
||||||
**kwargs: Additional arguments passed to parent aggregator.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The LLMAssistantContextAggregator uses TextFrames to aggregate the LLM output,
|
|
||||||
# but the OpenAIRealtimeLLMService pushes LLMTextFrames and TTSTextFrames. We
|
|
||||||
# need to override this proces_frame for LLMTextFrame, so that only the TTSTextFrames
|
|
||||||
# are process. This ensures that the context gets only one set of messages.
|
|
||||||
# OpenAIRealtimeLLMService also pushes TranscriptionFrames and InterimTranscriptionFrames,
|
|
||||||
# so we need to ignore pushing those as well, as they're also TextFrames.
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
||||||
"""Process assistant frames, filtering out duplicate text content.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
frame: The frame to process.
|
|
||||||
direction: The direction of frame flow in the pipeline.
|
|
||||||
"""
|
|
||||||
if not isinstance(frame, (LLMTextFrame, TranscriptionFrame, InterimTranscriptionFrame)):
|
|
||||||
await super().process_frame(frame, direction)
|
|
||||||
|
|
||||||
async def handle_function_call_result(self, frame: FunctionCallResultFrame):
|
|
||||||
"""Handle function call result and notify the realtime service.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
frame: The function call result frame to handle.
|
|
||||||
"""
|
|
||||||
await super().handle_function_call_result(frame)
|
|
||||||
|
|
||||||
# The standard function callback code path pushes the FunctionCallResultFrame from the llm itself,
|
|
||||||
# so we didn't have a chance to add the result to the openai realtime api context. Let's push a
|
|
||||||
# special frame to do that.
|
|
||||||
await self.push_frame(
|
|
||||||
RealtimeFunctionCallResultFrame(result_frame=frame), FrameDirection.UPSTREAM
|
|
||||||
)
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,37 +1,21 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2024–2025, Daily
|
# Copyright (c) 2025, Daily
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
#
|
#
|
||||||
|
|
||||||
"""Custom frame types for OpenAI Realtime API integration."""
|
"""Custom frame types for OpenAI Realtime API integration."""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
import warnings
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from pipecat.frames.frames import DataFrame, FunctionCallResultFrame
|
from pipecat.services.openai.realtime.frames import *
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
with warnings.catch_warnings():
|
||||||
from pipecat.services.openai_realtime.context import OpenAIRealtimeLLMContext
|
warnings.simplefilter("always")
|
||||||
|
warnings.warn(
|
||||||
|
"Types in pipecat.services.openai_realtime.frames are deprecated. "
|
||||||
@dataclass
|
"Please use the equivalent types from "
|
||||||
class RealtimeMessagesUpdateFrame(DataFrame):
|
"pipecat.services.openai.realtime.frames instead.",
|
||||||
"""Frame indicating that the realtime context messages have been updated.
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
Parameters:
|
)
|
||||||
context: The updated OpenAI realtime LLM context.
|
|
||||||
"""
|
|
||||||
|
|
||||||
context: "OpenAIRealtimeLLMContext"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RealtimeFunctionCallResultFrame(DataFrame):
|
|
||||||
"""Frame containing function call results for the realtime service.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
result_frame: The function call result frame to send to the realtime API.
|
|
||||||
"""
|
|
||||||
|
|
||||||
result_frame: FunctionCallResultFrame
|
|
||||||
|
|||||||
Reference in New Issue
Block a user