Merge pull request #517 from pipecat-ai/mb/update-settings-frame
Consolidate update frames classes into a single UpdateSettingsFrame class
This commit is contained in:
@@ -93,6 +93,9 @@ async def on_connected(processor):
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated individual update settings frame classes into a single UpdateSettingsFrame
|
||||
class for STT, LLM, and TTS.
|
||||
|
||||
- We now distinguish between input and output audio and image frames. We
|
||||
introduce `InputAudioRawFrame`, `OutputAudioRawFrame`, `InputImageRawFrame`
|
||||
and `OutputImageRawFrame` (and other subclasses of those). The input frames
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
# SPDX-License-Identifier: BSD 2-Clause License
|
||||
#
|
||||
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
from pipecat.clocks.base_clock import BaseClock
|
||||
from pipecat.metrics.metrics import MetricsData
|
||||
@@ -528,113 +527,45 @@ class UserImageRequestFrame(ControlFrame):
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMModelUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new LLM model."""
|
||||
class LLMUpdateSettingsFrame(ControlFrame):
|
||||
"""A control frame containing a request to update LLM settings."""
|
||||
|
||||
model: str
|
||||
model: Optional[str] = None
|
||||
temperature: Optional[float] = None
|
||||
top_k: Optional[int] = None
|
||||
top_p: Optional[float] = None
|
||||
frequency_penalty: Optional[float] = None
|
||||
presence_penalty: Optional[float] = None
|
||||
max_tokens: Optional[int] = None
|
||||
seed: Optional[int] = None
|
||||
extra: dict = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMTemperatureUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new LLM temperature."""
|
||||
class TTSUpdateSettingsFrame(ControlFrame):
|
||||
"""A control frame containing a request to update TTS settings."""
|
||||
|
||||
temperature: float
|
||||
model: Optional[str] = None
|
||||
voice: Optional[str] = None
|
||||
language: Optional[Language] = None
|
||||
speed: Optional[Union[str, float]] = None
|
||||
emotion: Optional[List[str]] = None
|
||||
engine: Optional[str] = None
|
||||
pitch: Optional[str] = None
|
||||
rate: Optional[str] = None
|
||||
volume: Optional[str] = None
|
||||
emphasis: Optional[str] = None
|
||||
style: Optional[str] = None
|
||||
style_degree: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMTopKUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new LLM top_k."""
|
||||
class STTUpdateSettingsFrame(ControlFrame):
|
||||
"""A control frame containing a request to update STT settings."""
|
||||
|
||||
top_k: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMTopPUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new LLM top_p."""
|
||||
|
||||
top_p: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMFrequencyPenaltyUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new LLM frequency
|
||||
penalty.
|
||||
|
||||
"""
|
||||
|
||||
frequency_penalty: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMPresencePenaltyUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new LLM presence
|
||||
penalty.
|
||||
|
||||
"""
|
||||
|
||||
presence_penalty: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMMaxTokensUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new LLM max tokens."""
|
||||
|
||||
max_tokens: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMSeedUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new LLM seed."""
|
||||
|
||||
seed: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMExtraUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new LLM extra params."""
|
||||
|
||||
extra: dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class TTSModelUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update the TTS model."""
|
||||
|
||||
model: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class TTSVoiceUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new TTS voice."""
|
||||
|
||||
voice: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class TTSLanguageUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to a new TTS language and
|
||||
optional voice.
|
||||
|
||||
"""
|
||||
|
||||
language: Language
|
||||
|
||||
|
||||
@dataclass
|
||||
class STTModelUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update the STT model and optional
|
||||
language.
|
||||
|
||||
"""
|
||||
|
||||
model: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class STTLanguageUpdateFrame(ControlFrame):
|
||||
"""A control frame containing a request to update to STT language."""
|
||||
|
||||
language: Language
|
||||
model: Optional[str] = None
|
||||
language: Optional[Language] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
import asyncio
|
||||
import io
|
||||
import wave
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import AsyncGenerator, List, Optional, Tuple
|
||||
from typing import AsyncGenerator, List, Optional, Tuple, Union
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
AudioRawFrame,
|
||||
@@ -18,31 +19,26 @@ from pipecat.frames.frames import (
|
||||
ErrorFrame,
|
||||
Frame,
|
||||
LLMFullResponseEndFrame,
|
||||
STTLanguageUpdateFrame,
|
||||
STTModelUpdateFrame,
|
||||
StartFrame,
|
||||
StartInterruptionFrame,
|
||||
STTUpdateSettingsFrame,
|
||||
TextFrame,
|
||||
TTSAudioRawFrame,
|
||||
TTSLanguageUpdateFrame,
|
||||
TTSModelUpdateFrame,
|
||||
TTSSpeakFrame,
|
||||
TTSStartedFrame,
|
||||
TTSStoppedFrame,
|
||||
TTSVoiceUpdateFrame,
|
||||
TextFrame,
|
||||
TTSUpdateSettingsFrame,
|
||||
UserImageRequestFrame,
|
||||
VisionImageRawFrame,
|
||||
)
|
||||
from pipecat.metrics.metrics import MetricsData
|
||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
from pipecat.transcriptions.language import Language
|
||||
from pipecat.utils.audio import calculate_audio_volume
|
||||
from pipecat.utils.string import match_endofsentence
|
||||
from pipecat.utils.time import seconds_to_nanoseconds
|
||||
from pipecat.utils.utils import exp_smoothing
|
||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class AIService(FrameProcessor):
|
||||
@@ -174,6 +170,46 @@ class TTSService(AIService):
|
||||
async def set_language(self, language: Language):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_speed(self, speed: Union[str, float]):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_emotion(self, emotion: List[str]):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_engine(self, engine: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_pitch(self, pitch: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_rate(self, rate: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_volume(self, volume: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_emphasis(self, emphasis: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_style(self, style: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_style_degree(self, style_degree: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def set_role(self, role: str):
|
||||
pass
|
||||
|
||||
# Converts the text to audio.
|
||||
@abstractmethod
|
||||
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
|
||||
@@ -212,6 +248,34 @@ class TTSService(AIService):
|
||||
# interrupted, the text is not added to the assistant context.
|
||||
await self.push_frame(TextFrame(text))
|
||||
|
||||
async def _update_tts_settings(self, frame: TTSUpdateSettingsFrame):
|
||||
if frame.model is not None:
|
||||
await self.set_model(frame.model)
|
||||
if frame.voice is not None:
|
||||
await self.set_voice(frame.voice)
|
||||
if frame.language is not None:
|
||||
await self.set_language(frame.language)
|
||||
if frame.speed is not None:
|
||||
await self.set_speed(frame.speed)
|
||||
if frame.emotion is not None:
|
||||
await self.set_emotion(frame.emotion)
|
||||
if frame.engine is not None:
|
||||
await self.set_engine(frame.engine)
|
||||
if frame.pitch is not None:
|
||||
await self.set_pitch(frame.pitch)
|
||||
if frame.rate is not None:
|
||||
await self.set_rate(frame.rate)
|
||||
if frame.volume is not None:
|
||||
await self.set_volume(frame.volume)
|
||||
if frame.emphasis is not None:
|
||||
await self.set_emphasis(frame.emphasis)
|
||||
if frame.style is not None:
|
||||
await self.set_style(frame.style)
|
||||
if frame.style_degree is not None:
|
||||
await self.set_style_degree(frame.style_degree)
|
||||
if frame.role is not None:
|
||||
await self.set_role(frame.role)
|
||||
|
||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||
await super().process_frame(frame, direction)
|
||||
|
||||
@@ -230,12 +294,8 @@ class TTSService(AIService):
|
||||
await self.push_frame(frame, direction)
|
||||
elif isinstance(frame, TTSSpeakFrame):
|
||||
await self._push_tts_frames(frame.text)
|
||||
elif isinstance(frame, TTSModelUpdateFrame):
|
||||
await self.set_model(frame.model)
|
||||
elif isinstance(frame, TTSVoiceUpdateFrame):
|
||||
await self.set_voice(frame.voice)
|
||||
elif isinstance(frame, TTSLanguageUpdateFrame):
|
||||
await self.set_language(frame.language)
|
||||
elif isinstance(frame, TTSUpdateSettingsFrame):
|
||||
await self._update_tts_settings(frame)
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
@@ -397,6 +457,12 @@ class STTService(AIService):
|
||||
"""Returns transcript as a string"""
|
||||
pass
|
||||
|
||||
async def _update_stt_settings(self, frame: STTUpdateSettingsFrame):
|
||||
if frame.model is not None:
|
||||
await self.set_model(frame.model)
|
||||
if frame.language is not None:
|
||||
await self.set_language(frame.language)
|
||||
|
||||
async def process_audio_frame(self, frame: AudioRawFrame):
|
||||
await self.process_generator(self.run_stt(frame.audio))
|
||||
|
||||
@@ -408,10 +474,8 @@ class STTService(AIService):
|
||||
# In this service we accumulate audio internally and at the end we
|
||||
# push a TextFrame. We don't really want to push audio frames down.
|
||||
await self.process_audio_frame(frame)
|
||||
elif isinstance(frame, STTModelUpdateFrame):
|
||||
await self.set_model(frame.model)
|
||||
elif isinstance(frame, STTLanguageUpdateFrame):
|
||||
await self.set_language(frame.language)
|
||||
elif isinstance(frame, STTUpdateSettingsFrame):
|
||||
await self._update_stt_settings(frame)
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
|
||||
@@ -5,47 +5,47 @@
|
||||
#
|
||||
|
||||
import base64
|
||||
import json
|
||||
import io
|
||||
import copy
|
||||
from typing import Any, Dict, List, Optional
|
||||
from dataclasses import dataclass
|
||||
from PIL import Image
|
||||
from asyncio import CancelledError
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
from asyncio import CancelledError
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from loguru import logger
|
||||
from PIL import Image
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
Frame,
|
||||
LLMEnablePromptCachingFrame,
|
||||
LLMModelUpdateFrame,
|
||||
TextFrame,
|
||||
VisionImageRawFrame,
|
||||
UserImageRequestFrame,
|
||||
UserImageRawFrame,
|
||||
LLMMessagesFrame,
|
||||
LLMFullResponseStartFrame,
|
||||
LLMFullResponseEndFrame,
|
||||
FunctionCallResultFrame,
|
||||
FunctionCallInProgressFrame,
|
||||
FunctionCallResultFrame,
|
||||
LLMEnablePromptCachingFrame,
|
||||
LLMFullResponseEndFrame,
|
||||
LLMFullResponseStartFrame,
|
||||
LLMMessagesFrame,
|
||||
LLMUpdateSettingsFrame,
|
||||
StartInterruptionFrame,
|
||||
TextFrame,
|
||||
UserImageRawFrame,
|
||||
UserImageRequestFrame,
|
||||
VisionImageRawFrame,
|
||||
)
|
||||
from pipecat.metrics.metrics import LLMTokenUsage
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.ai_services import LLMService
|
||||
from pipecat.processors.aggregators.llm_response import (
|
||||
LLMAssistantContextAggregator,
|
||||
LLMUserContextAggregator,
|
||||
)
|
||||
from pipecat.processors.aggregators.openai_llm_context import (
|
||||
OpenAILLMContext,
|
||||
OpenAILLMContextFrame,
|
||||
)
|
||||
from pipecat.processors.aggregators.llm_response import (
|
||||
LLMUserContextAggregator,
|
||||
LLMAssistantContextAggregator,
|
||||
)
|
||||
|
||||
from loguru import logger
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.ai_services import LLMService
|
||||
|
||||
try:
|
||||
from anthropic import AsyncAnthropic, NOT_GIVEN, NotGiven
|
||||
from anthropic import NOT_GIVEN, AsyncAnthropic, NotGiven
|
||||
except ModuleNotFoundError as e:
|
||||
logger.error(f"Exception: {e}")
|
||||
logger.error(
|
||||
@@ -279,6 +279,21 @@ class AnthropicLLMService(LLMService):
|
||||
cache_read_input_tokens=cache_read_input_tokens,
|
||||
)
|
||||
|
||||
async def _update_settings(self, frame: LLMUpdateSettingsFrame):
|
||||
if frame.model is not None:
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self.set_model_name(frame.model)
|
||||
if frame.max_tokens is not None:
|
||||
await self.set_max_tokens(frame.max_tokens)
|
||||
if frame.temperature is not None:
|
||||
await self.set_temperature(frame.temperature)
|
||||
if frame.top_k is not None:
|
||||
await self.set_top_k(frame.top_k)
|
||||
if frame.top_p is not None:
|
||||
await self.set_top_p(frame.top_p)
|
||||
if frame.extra:
|
||||
await self.set_extra(frame.extra)
|
||||
|
||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||
await super().process_frame(frame, direction)
|
||||
|
||||
@@ -293,9 +308,8 @@ class AnthropicLLMService(LLMService):
|
||||
# UserImageRawFrames coming through the pipeline and add them
|
||||
# to the context.
|
||||
context = AnthropicLLMContext.from_image_frame(frame)
|
||||
elif isinstance(frame, LLMModelUpdateFrame):
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self.set_model_name(frame.model)
|
||||
elif isinstance(frame, LLMUpdateSettingsFrame):
|
||||
await self._update_settings(frame)
|
||||
elif isinstance(frame, LLMEnablePromptCachingFrame):
|
||||
logger.debug(f"Setting enable prompt caching to: [{frame.enable}]")
|
||||
self._enable_prompt_caching_beta = frame.enable
|
||||
|
||||
@@ -41,7 +41,10 @@ try:
|
||||
SpeechRecognizer,
|
||||
SpeechSynthesizer,
|
||||
)
|
||||
from azure.cognitiveservices.speech.audio import AudioStreamFormat, PushAudioInputStream
|
||||
from azure.cognitiveservices.speech.audio import (
|
||||
AudioStreamFormat,
|
||||
PushAudioInputStream,
|
||||
)
|
||||
from azure.cognitiveservices.speech.dialog import AudioConfig
|
||||
from openai import AsyncAzureOpenAI
|
||||
except ModuleNotFoundError as e:
|
||||
@@ -73,7 +76,7 @@ class AzureLLMService(BaseOpenAILLMService):
|
||||
class AzureTTSService(TTSService):
|
||||
class InputParams(BaseModel):
|
||||
emphasis: Optional[str] = None
|
||||
language_code: Optional[str] = "en-US"
|
||||
language: Optional[str] = "en-US"
|
||||
pitch: Optional[str] = None
|
||||
rate: Optional[str] = "1.05"
|
||||
role: Optional[str] = None
|
||||
@@ -105,7 +108,7 @@ class AzureTTSService(TTSService):
|
||||
|
||||
def _construct_ssml(self, text: str) -> str:
|
||||
ssml = (
|
||||
f"<speak version='1.0' xml:lang='{self._params.language_code}' "
|
||||
f"<speak version='1.0' xml:lang='{self._params.language}' "
|
||||
"xmlns='http://www.w3.org/2001/10/synthesis' "
|
||||
"xmlns:mstts='http://www.w3.org/2001/mstts'>"
|
||||
f"<voice name='{self._voice}'>"
|
||||
@@ -155,9 +158,9 @@ class AzureTTSService(TTSService):
|
||||
logger.debug(f"Setting TTS emphasis to: [{emphasis}]")
|
||||
self._params.emphasis = emphasis
|
||||
|
||||
async def set_language_code(self, language_code: str):
|
||||
logger.debug(f"Setting TTS language code to: [{language_code}]")
|
||||
self._params.language_code = language_code
|
||||
async def set_language(self, language: str):
|
||||
logger.debug(f"Setting TTS language code to: [{language}]")
|
||||
self._params.language = language
|
||||
|
||||
async def set_pitch(self, pitch: str):
|
||||
logger.debug(f"Setting TTS pitch to: [{pitch}]")
|
||||
@@ -187,7 +190,7 @@ class AzureTTSService(TTSService):
|
||||
valid_params = {
|
||||
"voice": self.set_voice,
|
||||
"emphasis": self.set_emphasis,
|
||||
"language_code": self.set_language_code,
|
||||
"language_code": self.set_language,
|
||||
"pitch": self.set_pitch,
|
||||
"rate": self.set_rate,
|
||||
"role": self.set_role,
|
||||
|
||||
@@ -72,7 +72,7 @@ def calculate_word_times(
|
||||
|
||||
class ElevenLabsTTSService(AsyncWordTTSService):
|
||||
class InputParams(BaseModel):
|
||||
language_code: Optional[str] = None
|
||||
language: Optional[str] = None
|
||||
output_format: Literal["pcm_16000", "pcm_22050", "pcm_24000", "pcm_44100"] = "pcm_16000"
|
||||
optimize_streaming_latency: Optional[str] = None
|
||||
stability: Optional[float] = None
|
||||
@@ -229,13 +229,13 @@ class ElevenLabsTTSService(AsyncWordTTSService):
|
||||
if self._params.optimize_streaming_latency:
|
||||
url += f"&optimize_streaming_latency={self._params.optimize_streaming_latency}"
|
||||
|
||||
# language_code can only be used with the 'eleven_turbo_v2_5' model
|
||||
if self._params.language_code:
|
||||
# language can only be used with the 'eleven_turbo_v2_5' model
|
||||
if self._params.language:
|
||||
if model == "eleven_turbo_v2_5":
|
||||
url += f"&language_code={self._params.language_code}"
|
||||
url += f"&language_code={self._params.language}"
|
||||
else:
|
||||
logger.debug(
|
||||
f"Language code [{self._params.language_code}] not applied. Language codes can only be used with the 'eleven_turbo_v2_5' model."
|
||||
f"Language code [{self._params.language}] not applied. Language codes can only be used with the 'eleven_turbo_v2_5' model."
|
||||
)
|
||||
|
||||
self._websocket = await websockets.connect(url)
|
||||
|
||||
@@ -17,7 +17,7 @@ from pipecat.frames.frames import (
|
||||
LLMFullResponseEndFrame,
|
||||
LLMFullResponseStartFrame,
|
||||
LLMMessagesFrame,
|
||||
LLMModelUpdateFrame,
|
||||
LLMUpdateSettingsFrame,
|
||||
TextFrame,
|
||||
TTSAudioRawFrame,
|
||||
TTSStartedFrame,
|
||||
@@ -136,9 +136,10 @@ class GoogleLLMService(LLMService):
|
||||
context = OpenAILLMContext.from_messages(frame.messages)
|
||||
elif isinstance(frame, VisionImageRawFrame):
|
||||
context = OpenAILLMContext.from_image_frame(frame)
|
||||
elif isinstance(frame, LLMModelUpdateFrame):
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self._create_client(frame.model)
|
||||
elif isinstance(frame, LLMUpdateSettingsFrame):
|
||||
if frame.model is not None:
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self.set_model_name(frame.model)
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
|
||||
@@ -4,38 +4,39 @@
|
||||
# SPDX-License-Identifier: BSD 2-Clause License
|
||||
#
|
||||
|
||||
import aiohttp
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import httpx
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing import Any, AsyncGenerator, Dict, List, Literal, Optional
|
||||
|
||||
import aiohttp
|
||||
import httpx
|
||||
from loguru import logger
|
||||
from PIL import Image
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
ErrorFrame,
|
||||
Frame,
|
||||
FunctionCallInProgressFrame,
|
||||
FunctionCallResultFrame,
|
||||
LLMFullResponseEndFrame,
|
||||
LLMFullResponseStartFrame,
|
||||
LLMMessagesFrame,
|
||||
LLMModelUpdateFrame,
|
||||
LLMUpdateSettingsFrame,
|
||||
StartInterruptionFrame,
|
||||
TextFrame,
|
||||
TTSAudioRawFrame,
|
||||
TTSStartedFrame,
|
||||
TTSStoppedFrame,
|
||||
TextFrame,
|
||||
URLImageRawFrame,
|
||||
VisionImageRawFrame,
|
||||
FunctionCallResultFrame,
|
||||
FunctionCallInProgressFrame,
|
||||
StartInterruptionFrame,
|
||||
)
|
||||
from pipecat.metrics.metrics import LLMTokenUsage
|
||||
from pipecat.processors.aggregators.llm_response import (
|
||||
LLMUserContextAggregator,
|
||||
LLMAssistantContextAggregator,
|
||||
LLMUserContextAggregator,
|
||||
)
|
||||
from pipecat.processors.aggregators.openai_llm_context import (
|
||||
OpenAILLMContext,
|
||||
@@ -44,12 +45,14 @@ from pipecat.processors.aggregators.openai_llm_context import (
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.ai_services import ImageGenService, LLMService, TTSService
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from loguru import logger
|
||||
|
||||
try:
|
||||
from openai import AsyncOpenAI, AsyncStream, DefaultAsyncHttpxClient, BadRequestError, NOT_GIVEN
|
||||
from openai import (
|
||||
NOT_GIVEN,
|
||||
AsyncOpenAI,
|
||||
AsyncStream,
|
||||
BadRequestError,
|
||||
DefaultAsyncHttpxClient,
|
||||
)
|
||||
from openai.types.chat import ChatCompletionChunk, ChatCompletionMessageParam
|
||||
except ModuleNotFoundError as e:
|
||||
logger.error(f"Exception: {e}")
|
||||
@@ -270,6 +273,23 @@ class BaseOpenAILLMService(LLMService):
|
||||
arguments=arguments,
|
||||
)
|
||||
|
||||
async def _update_settings(self, frame: LLMUpdateSettingsFrame):
|
||||
if frame.model is not None:
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self.set_model_name(frame.model)
|
||||
if frame.frequency_penalty is not None:
|
||||
await self.set_frequency_penalty(frame.frequency_penalty)
|
||||
if frame.presence_penalty is not None:
|
||||
await self.set_presence_penalty(frame.presence_penalty)
|
||||
if frame.seed is not None:
|
||||
await self.set_seed(frame.seed)
|
||||
if frame.temperature is not None:
|
||||
await self.set_temperature(frame.temperature)
|
||||
if frame.top_p is not None:
|
||||
await self.set_top_p(frame.top_p)
|
||||
if frame.extra:
|
||||
await self.set_extra(frame.extra)
|
||||
|
||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||
await super().process_frame(frame, direction)
|
||||
|
||||
@@ -280,9 +300,8 @@ class BaseOpenAILLMService(LLMService):
|
||||
context = OpenAILLMContext.from_messages(frame.messages)
|
||||
elif isinstance(frame, VisionImageRawFrame):
|
||||
context = OpenAILLMContext.from_image_frame(frame)
|
||||
elif isinstance(frame, LLMModelUpdateFrame):
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self.set_model_name(frame.model)
|
||||
elif isinstance(frame, LLMUpdateSettingsFrame):
|
||||
await self._update_settings(frame)
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
@@ -464,7 +483,7 @@ class OpenAIAssistantContextAggregator(LLMAssistantContextAggregator):
|
||||
await self._push_aggregation()
|
||||
else:
|
||||
logger.warning(
|
||||
f"FunctionCallResultFrame tool_call_id does not match FunctionCallInProgressFrame tool_call_id"
|
||||
"FunctionCallResultFrame tool_call_id does not match FunctionCallInProgressFrame tool_call_id"
|
||||
)
|
||||
self._function_call_in_progress = None
|
||||
self._function_call_result = None
|
||||
|
||||
@@ -7,37 +7,36 @@
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
from dataclasses import dataclass
|
||||
from asyncio import CancelledError
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
Frame,
|
||||
LLMModelUpdateFrame,
|
||||
FunctionCallInProgressFrame,
|
||||
FunctionCallResultFrame,
|
||||
LLMFullResponseEndFrame,
|
||||
LLMFullResponseStartFrame,
|
||||
LLMMessagesFrame,
|
||||
LLMUpdateSettingsFrame,
|
||||
StartInterruptionFrame,
|
||||
TextFrame,
|
||||
UserImageRequestFrame,
|
||||
LLMMessagesFrame,
|
||||
LLMFullResponseStartFrame,
|
||||
LLMFullResponseEndFrame,
|
||||
FunctionCallResultFrame,
|
||||
FunctionCallInProgressFrame,
|
||||
StartInterruptionFrame,
|
||||
)
|
||||
from pipecat.metrics.metrics import LLMTokenUsage
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.ai_services import LLMService
|
||||
from pipecat.processors.aggregators.llm_response import (
|
||||
LLMAssistantContextAggregator,
|
||||
LLMUserContextAggregator,
|
||||
)
|
||||
from pipecat.processors.aggregators.openai_llm_context import (
|
||||
OpenAILLMContext,
|
||||
OpenAILLMContextFrame,
|
||||
)
|
||||
from pipecat.processors.aggregators.llm_response import (
|
||||
LLMUserContextAggregator,
|
||||
LLMAssistantContextAggregator,
|
||||
)
|
||||
|
||||
from loguru import logger
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.ai_services import LLMService
|
||||
|
||||
try:
|
||||
from together import AsyncTogether
|
||||
@@ -129,6 +128,25 @@ class TogetherLLMService(LLMService):
|
||||
logger.debug(f"Switching LLM extra to: [{extra}]")
|
||||
self._extra = extra
|
||||
|
||||
async def _update_settings(self, frame: LLMUpdateSettingsFrame):
|
||||
if frame.model is not None:
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self.set_model_name(frame.model)
|
||||
if frame.frequency_penalty is not None:
|
||||
await self.set_frequency_penalty(frame.frequency_penalty)
|
||||
if frame.max_tokens is not None:
|
||||
await self.set_max_tokens(frame.max_tokens)
|
||||
if frame.presence_penalty is not None:
|
||||
await self.set_presence_penalty(frame.presence_penalty)
|
||||
if frame.temperature is not None:
|
||||
await self.set_temperature(frame.temperature)
|
||||
if frame.top_k is not None:
|
||||
await self.set_top_k(frame.top_k)
|
||||
if frame.top_p is not None:
|
||||
await self.set_top_p(frame.top_p)
|
||||
if frame.extra:
|
||||
await self.set_extra(frame.extra)
|
||||
|
||||
async def _process_context(self, context: OpenAILLMContext):
|
||||
try:
|
||||
await self.push_frame(LLMFullResponseStartFrame())
|
||||
@@ -188,7 +206,7 @@ class TogetherLLMService(LLMService):
|
||||
if chunk.choices[0].finish_reason == "eos" and accumulating_function_call:
|
||||
await self._extract_function_call(context, function_call_accumulator)
|
||||
|
||||
except CancelledError as e:
|
||||
except CancelledError:
|
||||
# todo: implement token counting estimates for use when the user interrupts a long generation
|
||||
# we do this in the anthropic.py service
|
||||
raise
|
||||
@@ -206,9 +224,8 @@ class TogetherLLMService(LLMService):
|
||||
context = frame.context
|
||||
elif isinstance(frame, LLMMessagesFrame):
|
||||
context = TogetherLLMContext.from_messages(frame.messages)
|
||||
elif isinstance(frame, LLMModelUpdateFrame):
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self.set_model_name(frame.model)
|
||||
elif isinstance(frame, LLMUpdateSettingsFrame):
|
||||
await self._update_settings(frame)
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
@@ -338,7 +355,7 @@ class TogetherAssistantContextAggregator(LLMAssistantContextAggregator):
|
||||
await self._push_aggregation()
|
||||
else:
|
||||
logger.warning(
|
||||
f"FunctionCallResultFrame tool_call_id does not match FunctionCallInProgressFrame tool_call_id"
|
||||
"FunctionCallResultFrame tool_call_id does not match FunctionCallInProgressFrame tool_call_id"
|
||||
)
|
||||
self._function_call_in_progress = None
|
||||
self._function_call_result = None
|
||||
|
||||
Reference in New Issue
Block a user