Modernize Python typing across the codebase
Automated via ruff UP006, UP007, UP035, UP045 rules (target: py311): - Replace `typing.List`, `Dict`, `Tuple`, `Set`, `FrozenSet`, `Type` with their built-in equivalents (`list`, `dict`, `tuple`, etc.) - Replace `typing.Optional[X]` with `X | None` - Replace `typing.Union[X, Y]` with `X | Y` - Move `Mapping`, `Sequence`, `Callable`, `Awaitable`, `MutableMapping`, `MutableSequence`, `Iterator`, `AsyncIterator`, `AsyncGenerator` imports from `typing` to `collections.abc` - Remove now-unused `typing` imports - Add `from __future__ import annotations` to 5 files that use forward-reference strings in `X | "Y"` annotations
This commit is contained in:
@@ -84,7 +84,7 @@ async def load_conversation(params: FunctionCallParams):
|
|||||||
filename = params.arguments["filename"]
|
filename = params.arguments["filename"]
|
||||||
logger.debug(f"loading conversation from {filename}")
|
logger.debug(f"loading conversation from {filename}")
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename) as file:
|
||||||
params.context.set_messages(json.load(file))
|
params.context.set_messages(json.load(file))
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"
|
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ async def load_conversation(params: FunctionCallParams):
|
|||||||
filename = params.arguments["filename"]
|
filename = params.arguments["filename"]
|
||||||
logger.debug(f"loading conversation from {filename}")
|
logger.debug(f"loading conversation from {filename}")
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename) as file:
|
||||||
messages = json.load(file)
|
messages = json.load(file)
|
||||||
# HACK: if using the older Nova Sonic (pre-2) model, you need a special way of
|
# HACK: if using the older Nova Sonic (pre-2) model, you need a special way of
|
||||||
# triggering the first assistant response. The call to trigger_assistant_response(),
|
# triggering the first assistant response. The call to trigger_assistant_response(),
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ async def load_conversation(params: FunctionCallParams):
|
|||||||
filename = params.arguments["filename"]
|
filename = params.arguments["filename"]
|
||||||
logger.debug(f"loading conversation from {filename}")
|
logger.debug(f"loading conversation from {filename}")
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename) as file:
|
||||||
params.context.set_messages(json.load(file))
|
params.context.set_messages(json.load(file))
|
||||||
await params.result_callback(
|
await params.result_callback(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ async def load_conversation(params: FunctionCallParams):
|
|||||||
filename = params.arguments["filename"]
|
filename = params.arguments["filename"]
|
||||||
logger.debug(f"loading conversation from {filename}")
|
logger.debug(f"loading conversation from {filename}")
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename) as file:
|
||||||
params.context.set_messages(json.load(file))
|
params.context.set_messages(json.load(file))
|
||||||
await params.llm.reset_conversation()
|
await params.llm.reset_conversation()
|
||||||
# Manually create a response since we've reset the conversation
|
# Manually create a response since we've reset the conversation
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ async def load_conversation(params: FunctionCallParams):
|
|||||||
filename = params.arguments["filename"]
|
filename = params.arguments["filename"]
|
||||||
logger.debug(f"loading conversation from {filename}")
|
logger.debug(f"loading conversation from {filename}")
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename) as file:
|
||||||
params.context.set_messages(json.load(file))
|
params.context.set_messages(json.load(file))
|
||||||
await params.llm.reset_conversation()
|
await params.llm.reset_conversation()
|
||||||
# NOTE: we manually create a response here rather than relying
|
# NOTE: we manually create a response here rather than relying
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ async def load_conversation(params: FunctionCallParams):
|
|||||||
filename = params.arguments["filename"]
|
filename = params.arguments["filename"]
|
||||||
logger.debug(f"loading conversation from {filename}")
|
logger.debug(f"loading conversation from {filename}")
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename) as file:
|
||||||
params.context.set_messages(json.load(file))
|
params.context.set_messages(json.load(file))
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"
|
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ async def load_conversation(params: FunctionCallParams):
|
|||||||
filename = params.arguments["filename"]
|
filename = params.arguments["filename"]
|
||||||
logger.debug(f"loading conversation from {filename}")
|
logger.debug(f"loading conversation from {filename}")
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename) as file:
|
||||||
params.context.set_messages(json.load(file))
|
params.context.set_messages(json.load(file))
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"
|
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ async def load_conversation(params: FunctionCallParams):
|
|||||||
filename = params.arguments["filename"]
|
filename = params.arguments["filename"]
|
||||||
logger.debug(f"loading conversation from {filename}")
|
logger.debug(f"loading conversation from {filename}")
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename) as file:
|
||||||
params.context.set_messages(json.load(file))
|
params.context.set_messages(json.load(file))
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"
|
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ def get_rag_content():
|
|||||||
"""Get the RAG content from the file."""
|
"""Get the RAG content from the file."""
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
rag_content_path = os.path.join(script_dir, "assets", "rag-content.txt")
|
rag_content_path = os.path.join(script_dir, "assets", "rag-content.txt")
|
||||||
with open(rag_content_path, "r") as f:
|
with open(rag_content_path) as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import argparse
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
@@ -39,7 +38,7 @@ load_dotenv(override=True)
|
|||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# Store connections by pc_id
|
# Store connections by pc_id
|
||||||
pcs_map: Dict[str, SmallWebRTCConnection] = {}
|
pcs_map: dict[str, SmallWebRTCConnection] = {}
|
||||||
|
|
||||||
ice_servers = [
|
ice_servers = [
|
||||||
IceServer(
|
IceServer(
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ class TranscriptHandler:
|
|||||||
output_file: Optional path to file where transcript is saved. If None, outputs to log only.
|
output_file: Optional path to file where transcript is saved. If None, outputs to log only.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, output_file: Optional[str] = None):
|
def __init__(self, output_file: str | None = None):
|
||||||
"""Initialize handler with optional file output.
|
"""Initialize handler with optional file output.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
output_file: Path to output file. If None, outputs to log only.
|
output_file: Path to output file. If None, outputs to log only.
|
||||||
"""
|
"""
|
||||||
self.output_file: Optional[str] = output_file
|
self.output_file: str | None = output_file
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"TranscriptHandler initialized {'with output_file=' + output_file if output_file else 'with log output only'}"
|
f"TranscriptHandler initialized {'with output_file=' + output_file if output_file else 'with log output only'}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import wave
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List, Optional, Tuple
|
from typing import Any
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -60,7 +60,7 @@ PIPELINE_IDLE_TIMEOUT_SECS = 60
|
|||||||
EVAL_TIMEOUT_SECS = 120
|
EVAL_TIMEOUT_SECS = 120
|
||||||
EVAL_RESULT_TIMEOUT_SECS = 10
|
EVAL_RESULT_TIMEOUT_SECS = 10
|
||||||
|
|
||||||
EvalPrompt = str | Tuple[str, ImageFile]
|
EvalPrompt = str | tuple[str, ImageFile]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -68,7 +68,7 @@ class EvalConfig:
|
|||||||
prompt: EvalPrompt
|
prompt: EvalPrompt
|
||||||
eval: str
|
eval: str
|
||||||
eval_speaks_first: bool = False
|
eval_speaks_first: bool = False
|
||||||
runner_args_body: Optional[Any] = None
|
runner_args_body: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
class EvalRunner:
|
class EvalRunner:
|
||||||
@@ -78,7 +78,7 @@ class EvalRunner:
|
|||||||
examples_dir: Path,
|
examples_dir: Path,
|
||||||
pattern: str = "",
|
pattern: str = "",
|
||||||
record_audio: bool = False,
|
record_audio: bool = False,
|
||||||
name: Optional[str] = None,
|
name: str | None = None,
|
||||||
log_level: str = "DEBUG",
|
log_level: str = "DEBUG",
|
||||||
):
|
):
|
||||||
self._examples_dir = examples_dir
|
self._examples_dir = examples_dir
|
||||||
@@ -86,8 +86,8 @@ class EvalRunner:
|
|||||||
self._record_audio = record_audio
|
self._record_audio = record_audio
|
||||||
self._log_level = log_level
|
self._log_level = log_level
|
||||||
self._total_success = 0
|
self._total_success = 0
|
||||||
self._tests: List[EvalResult] = []
|
self._tests: list[EvalResult] = []
|
||||||
self._result_future: Optional[asyncio.Future[bool]] = None
|
self._result_future: asyncio.Future[bool] | None = None
|
||||||
|
|
||||||
# We to save runner files.
|
# We to save runner files.
|
||||||
name = name or f"{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
name = name or f"{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||||
@@ -150,7 +150,7 @@ class EvalRunner:
|
|||||||
try:
|
try:
|
||||||
# Wait for the future to resolve.
|
# Wait for the future to resolve.
|
||||||
result = await asyncio.wait_for(self._result_future, timeout=EVAL_RESULT_TIMEOUT_SECS)
|
result = await asyncio.wait_for(self._result_future, timeout=EVAL_RESULT_TIMEOUT_SECS)
|
||||||
except asyncio.TimeoutError:
|
except TimeoutError:
|
||||||
logger.error(f"ERROR: Timeout waiting for eval result.")
|
logger.error(f"ERROR: Timeout waiting for eval result.")
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@ async def run_eval_pipeline(
|
|||||||
|
|
||||||
# Load example prompt depending on image.
|
# Load example prompt depending on image.
|
||||||
example_prompt = ""
|
example_prompt = ""
|
||||||
example_image: Optional[ImageFile] = None
|
example_image: ImageFile | None = None
|
||||||
if isinstance(eval_config.prompt, str):
|
if isinstance(eval_config.prompt, str):
|
||||||
example_prompt = eval_config.prompt
|
example_prompt = eval_config.prompt
|
||||||
elif isinstance(eval_config.prompt, tuple):
|
elif isinstance(eval_config.prompt, tuple):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
@@ -41,7 +41,7 @@ EVAL_WEATHER_AND_RESTAURANT = EvalConfig(
|
|||||||
|
|
||||||
EVAL_ONLINE_SEARCH = EvalConfig(
|
EVAL_ONLINE_SEARCH = EvalConfig(
|
||||||
prompt="What's the current date in UTC?",
|
prompt="What's the current date in UTC?",
|
||||||
eval=f"Current date in UTC is {datetime.now(timezone.utc).strftime('%A, %B %d, %Y')}.",
|
eval=f"Current date in UTC is {datetime.now(UTC).strftime('%A, %B %d, %Y')}.",
|
||||||
)
|
)
|
||||||
|
|
||||||
EVAL_SWITCH_LANGUAGE = EvalConfig(
|
EVAL_SWITCH_LANGUAGE = EvalConfig(
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
|
from collections.abc import Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Sequence
|
|
||||||
|
|
||||||
GREEN = "\033[92m"
|
GREEN = "\033[92m"
|
||||||
RED = "\033[91m"
|
RED = "\033[91m"
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ handling format detection and conversion to int16 PCM format.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import soundfile as sf
|
import soundfile as sf
|
||||||
|
|
||||||
|
|
||||||
def read_audio_file(input_path: str, verbose: bool = False) -> Tuple[np.ndarray, int]:
|
def read_audio_file(input_path: str, verbose: bool = False) -> tuple[np.ndarray, int]:
|
||||||
"""Read an audio file and convert to int16 mono format.
|
"""Read an audio file and convert to int16 mono format.
|
||||||
|
|
||||||
This function:
|
This function:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ adapters that handle tool format conversion and standardization.
|
|||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, Dict, Generic, List, Optional, TypeVar
|
from typing import Any, Generic, TypeVar
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -50,10 +50,10 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the adapter."""
|
"""Initialize the adapter."""
|
||||||
self._warned_system_instruction = False
|
self._warned_system_instruction = False
|
||||||
self._builtin_tools: Dict[str, FunctionSchema] = {}
|
self._builtin_tools: dict[str, FunctionSchema] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def builtin_tools(self) -> Dict[str, FunctionSchema]:
|
def builtin_tools(self) -> dict[str, FunctionSchema]:
|
||||||
"""Built-in tools automatically merged into every inference request.
|
"""Built-in tools automatically merged into every inference request.
|
||||||
|
|
||||||
Keyed by tool name for O(1) lookup, insertion, and removal. The
|
Keyed by tool name for O(1) lookup, insertion, and removal. The
|
||||||
@@ -90,7 +90,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Any]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[Any]:
|
||||||
"""Convert tools schema to the provider's specific format.
|
"""Convert tools schema to the provider's specific format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -102,7 +102,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
|
||||||
"""Get messages from a universal LLM context in a format ready for logging about this provider.
|
"""Get messages from a universal LLM context in a format ready for logging about this provider.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -127,7 +127,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
|||||||
|
|
||||||
def get_messages(
|
def get_messages(
|
||||||
self, context: LLMContext, *, truncate_large_values: bool = False
|
self, context: LLMContext, *, truncate_large_values: bool = False
|
||||||
) -> List[LLMContextMessage]:
|
) -> list[LLMContextMessage]:
|
||||||
"""Get messages from the LLM context, including standard and LLM-specific messages.
|
"""Get messages from the LLM context, including standard and LLM-specific messages.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -142,7 +142,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
|||||||
self.id_for_llm_specific_messages, truncate_large_values=truncate_large_values
|
self.id_for_llm_specific_messages, truncate_large_values=truncate_large_values
|
||||||
)
|
)
|
||||||
|
|
||||||
def from_standard_tools(self, tools: Any) -> List[Any] | NotGiven:
|
def from_standard_tools(self, tools: Any) -> list[Any] | NotGiven:
|
||||||
"""Convert tools from standard format to provider format.
|
"""Convert tools from standard format to provider format.
|
||||||
|
|
||||||
Built-in tools are automatically merged into the schema before conversion so that every
|
Built-in tools are automatically merged into the schema before conversion so that every
|
||||||
@@ -188,8 +188,8 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
|||||||
self,
|
self,
|
||||||
messages: list,
|
messages: list,
|
||||||
*,
|
*,
|
||||||
system_instruction: Optional[str] = None,
|
system_instruction: str | None = None,
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
"""Extract an initial ``"system"`` message for use as a system instruction.
|
"""Extract an initial ``"system"`` message for use as a system instruction.
|
||||||
|
|
||||||
Only useful for services that expect the system instruction as a
|
Only useful for services that expect the system instruction as a
|
||||||
@@ -247,11 +247,11 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
|||||||
|
|
||||||
def _resolve_system_instruction(
|
def _resolve_system_instruction(
|
||||||
self,
|
self,
|
||||||
system_from_context: Optional[str],
|
system_from_context: str | None,
|
||||||
system_instruction: Optional[str],
|
system_instruction: str | None,
|
||||||
*,
|
*,
|
||||||
discard_context_system: bool,
|
discard_context_system: bool,
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
"""Resolve conflict between ``system_instruction`` and an extracted context system message.
|
"""Resolve conflict between ``system_instruction`` and an extracted context system message.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -15,16 +15,11 @@ formats).
|
|||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import types
|
import types
|
||||||
|
from collections.abc import Callable, Mapping
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
List,
|
|
||||||
Mapping,
|
|
||||||
Protocol,
|
Protocol,
|
||||||
Set,
|
|
||||||
Tuple,
|
|
||||||
Union,
|
Union,
|
||||||
get_args,
|
get_args,
|
||||||
get_origin,
|
get_origin,
|
||||||
@@ -144,8 +139,8 @@ class BaseDirectFunctionWrapper:
|
|||||||
|
|
||||||
# TODO: maybe to better support things like enums, check if each type is a pydantic type and use its convert-to-jsonschema function
|
# TODO: maybe to better support things like enums, check if each type is a pydantic type and use its convert-to-jsonschema function
|
||||||
def _get_parameters_as_jsonschema(
|
def _get_parameters_as_jsonschema(
|
||||||
self, func: Callable, docstring_params: List[docstring_parser.DocstringParam]
|
self, func: Callable, docstring_params: list[docstring_parser.DocstringParam]
|
||||||
) -> Tuple[Dict[str, Any], List[str]]:
|
) -> tuple[dict[str, Any], list[str]]:
|
||||||
"""Get function parameters as a dictionary of JSON schemas and a list of required parameters.
|
"""Get function parameters as a dictionary of JSON schemas and a list of required parameters.
|
||||||
|
|
||||||
Ignore the first parameter, as it's expected to be the "special" one.
|
Ignore the first parameter, as it's expected to be the "special" one.
|
||||||
@@ -193,7 +188,7 @@ class BaseDirectFunctionWrapper:
|
|||||||
|
|
||||||
return properties, required
|
return properties, required
|
||||||
|
|
||||||
def _typehint_to_jsonschema(self, type_hint: Any) -> Dict[str, Any]:
|
def _typehint_to_jsonschema(self, type_hint: Any) -> dict[str, Any]:
|
||||||
"""Convert a Python type hint to a JSON Schema.
|
"""Convert a Python type hint to a JSON Schema.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -216,9 +211,9 @@ class BaseDirectFunctionWrapper:
|
|||||||
return {"type": "number"}
|
return {"type": "number"}
|
||||||
elif type_hint is bool:
|
elif type_hint is bool:
|
||||||
return {"type": "boolean"}
|
return {"type": "boolean"}
|
||||||
elif type_hint is dict or type_hint is Dict:
|
elif type_hint is dict or type_hint is dict:
|
||||||
return {"type": "object"}
|
return {"type": "object"}
|
||||||
elif type_hint is list or type_hint is List:
|
elif type_hint is list or type_hint is list:
|
||||||
return {"type": "array"}
|
return {"type": "array"}
|
||||||
|
|
||||||
# Get origin and arguments for complex types
|
# Get origin and arguments for complex types
|
||||||
@@ -230,11 +225,11 @@ class BaseDirectFunctionWrapper:
|
|||||||
return {"anyOf": [self._typehint_to_jsonschema(arg) for arg in args]}
|
return {"anyOf": [self._typehint_to_jsonschema(arg) for arg in args]}
|
||||||
|
|
||||||
# Handle List, Tuple, Set with specific item types
|
# Handle List, Tuple, Set with specific item types
|
||||||
if origin in (list, List, tuple, Tuple, set, Set) and args:
|
if origin in (list, list, tuple, tuple, set, set) and args:
|
||||||
return {"type": "array", "items": self._typehint_to_jsonschema(args[0])}
|
return {"type": "array", "items": self._typehint_to_jsonschema(args[0])}
|
||||||
|
|
||||||
# Handle Dict with specific key/value types
|
# Handle Dict with specific key/value types
|
||||||
if origin in (dict, Dict) and len(args) == 2:
|
if origin in (dict, dict) and len(args) == 2:
|
||||||
# For JSON Schema, keys must be strings
|
# For JSON Schema, keys must be strings
|
||||||
return {"type": "object", "additionalProperties": self._typehint_to_jsonschema(args[1])}
|
return {"type": "object", "additionalProperties": self._typehint_to_jsonschema(args[1])}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ tools and functions used with AI models, ensuring consistent formatting
|
|||||||
across different AI service providers.
|
across different AI service providers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class FunctionSchema:
|
class FunctionSchema:
|
||||||
@@ -23,7 +23,7 @@ class FunctionSchema:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name: str, description: str, properties: Dict[str, Any], required: List[str]
|
self, name: str, description: str, properties: dict[str, Any], required: list[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the function schema.
|
"""Initialize the function schema.
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class FunctionSchema:
|
|||||||
self._properties = properties
|
self._properties = properties
|
||||||
self._required = required
|
self._required = required
|
||||||
|
|
||||||
def to_default_dict(self) -> Dict[str, Any]:
|
def to_default_dict(self) -> dict[str, Any]:
|
||||||
"""Converts the function schema to a dictionary.
|
"""Converts the function schema to a dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -73,7 +73,7 @@ class FunctionSchema:
|
|||||||
return self._description
|
return self._description
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def properties(self) -> Dict[str, Any]:
|
def properties(self) -> dict[str, Any]:
|
||||||
"""Get the function properties.
|
"""Get the function properties.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -82,7 +82,7 @@ class FunctionSchema:
|
|||||||
return self._properties
|
return self._properties
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def required(self) -> List[str]:
|
def required(self) -> list[str]:
|
||||||
"""Get the required parameters.
|
"""Get the required parameters.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ and custom adapter-specific tools in the Pipecat framework.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
from pipecat.adapters.schemas.direct_function import DirectFunction, DirectFunctionWrapper
|
from pipecat.adapters.schemas.direct_function import DirectFunction, DirectFunctionWrapper
|
||||||
from pipecat.adapters.schemas.function_schema import FunctionSchema
|
from pipecat.adapters.schemas.function_schema import FunctionSchema
|
||||||
@@ -39,8 +39,8 @@ class ToolsSchema:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
standard_tools: List[FunctionSchema | DirectFunction],
|
standard_tools: list[FunctionSchema | DirectFunction],
|
||||||
custom_tools: Optional[Dict[AdapterType, List[Dict[str, Any]]]] = None,
|
custom_tools: dict[AdapterType, list[dict[str, Any]]] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the tools schema.
|
"""Initialize the tools schema.
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ class ToolsSchema:
|
|||||||
self._custom_tools = custom_tools
|
self._custom_tools = custom_tools
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def standard_tools(self) -> List[FunctionSchema]:
|
def standard_tools(self) -> list[FunctionSchema]:
|
||||||
"""Get the list of standard function schema tools.
|
"""Get the list of standard function schema tools.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -75,7 +75,7 @@ class ToolsSchema:
|
|||||||
return self._standard_tools
|
return self._standard_tools
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def custom_tools(self) -> Dict[AdapterType, List[Dict[str, Any]]]:
|
def custom_tools(self) -> dict[AdapterType, list[dict[str, Any]]]:
|
||||||
"""Get the custom tools dictionary.
|
"""Get the custom tools dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -84,7 +84,7 @@ class ToolsSchema:
|
|||||||
return self._custom_tools
|
return self._custom_tools
|
||||||
|
|
||||||
@custom_tools.setter
|
@custom_tools.setter
|
||||||
def custom_tools(self, value: Dict[AdapterType, List[Dict[str, Any]]]) -> None:
|
def custom_tools(self, value: dict[AdapterType, list[dict[str, Any]]]) -> None:
|
||||||
"""Set the custom tools dictionary.
|
"""Set the custom tools dictionary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from anthropic import NOT_GIVEN, NotGiven
|
from anthropic import NOT_GIVEN, NotGiven
|
||||||
from anthropic.types.message_param import MessageParam
|
from anthropic.types.message_param import MessageParam
|
||||||
@@ -31,8 +31,8 @@ class AnthropicLLMInvocationParams(TypedDict):
|
|||||||
"""Context-based parameters for invoking Anthropic's LLM API."""
|
"""Context-based parameters for invoking Anthropic's LLM API."""
|
||||||
|
|
||||||
system: str | NotGiven
|
system: str | NotGiven
|
||||||
messages: List[MessageParam]
|
messages: list[MessageParam]
|
||||||
tools: List[ToolUnionParam]
|
tools: list[ToolUnionParam]
|
||||||
|
|
||||||
|
|
||||||
class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
||||||
@@ -51,7 +51,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|||||||
self,
|
self,
|
||||||
context: LLMContext,
|
context: LLMContext,
|
||||||
enable_prompt_caching: bool,
|
enable_prompt_caching: bool,
|
||||||
system_instruction: Optional[str] = None,
|
system_instruction: str | None = None,
|
||||||
) -> AnthropicLLMInvocationParams:
|
) -> AnthropicLLMInvocationParams:
|
||||||
"""Get Anthropic-specific LLM invocation parameters from a universal LLM context.
|
"""Get Anthropic-specific LLM invocation parameters from a universal LLM context.
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|||||||
"tools": self.from_standard_tools(context.tools) or [],
|
"tools": self.from_standard_tools(context.tools) or [],
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
|
||||||
"""Get messages from a universal LLM context in a format ready for logging about Anthropic.
|
"""Get messages from a universal LLM context in a format ready for logging about Anthropic.
|
||||||
|
|
||||||
Removes or truncates sensitive data like image content for safe logging.
|
Removes or truncates sensitive data like image content for safe logging.
|
||||||
@@ -115,14 +115,14 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|||||||
class ConvertedMessages:
|
class ConvertedMessages:
|
||||||
"""Container for Anthropic-formatted messages converted from universal context."""
|
"""Container for Anthropic-formatted messages converted from universal context."""
|
||||||
|
|
||||||
messages: List[MessageParam]
|
messages: list[MessageParam]
|
||||||
system: str | NotGiven
|
system: str | NotGiven
|
||||||
|
|
||||||
def _from_universal_context_messages(
|
def _from_universal_context_messages(
|
||||||
self,
|
self,
|
||||||
universal_context_messages: List[LLMContextMessage],
|
universal_context_messages: list[LLMContextMessage],
|
||||||
*,
|
*,
|
||||||
system_instruction: Optional[str] = None,
|
system_instruction: str | None = None,
|
||||||
) -> ConvertedMessages:
|
) -> ConvertedMessages:
|
||||||
system = NOT_GIVEN
|
system = NOT_GIVEN
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def _with_cache_control_markers(self, messages: List[MessageParam]) -> List[MessageParam]:
|
def _with_cache_control_markers(self, messages: list[MessageParam]) -> list[MessageParam]:
|
||||||
"""Add cache control markers to messages for prompt caching.
|
"""Add cache control markers to messages for prompt caching.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -381,7 +381,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|||||||
return messages_with_markers
|
return messages_with_markers
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_anthropic_function_format(function: FunctionSchema) -> Dict[str, Any]:
|
def _to_anthropic_function_format(function: FunctionSchema) -> dict[str, Any]:
|
||||||
"""Convert a single function schema to Anthropic's format.
|
"""Convert a single function schema to Anthropic's format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -400,7 +400,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
|
||||||
"""Convert function schemas to Anthropic's function-calling format.
|
"""Convert function schemas to Anthropic's function-calling format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import copy
|
|||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -55,9 +55,9 @@ class AWSNovaSonicLLMInvocationParams(TypedDict):
|
|||||||
This is a placeholder until support for universal LLMContext machinery is added for AWS Nova Sonic.
|
This is a placeholder until support for universal LLMContext machinery is added for AWS Nova Sonic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
system_instruction: Optional[str]
|
system_instruction: str | None
|
||||||
messages: List[AWSNovaSonicConversationHistoryMessage]
|
messages: list[AWSNovaSonicConversationHistoryMessage]
|
||||||
tools: List[Dict[str, Any]]
|
tools: list[dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
||||||
@@ -73,7 +73,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
|||||||
return "aws-nova-sonic"
|
return "aws-nova-sonic"
|
||||||
|
|
||||||
def get_llm_invocation_params(
|
def get_llm_invocation_params(
|
||||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
self, context: LLMContext, *, system_instruction: str | None = None
|
||||||
) -> AWSNovaSonicLLMInvocationParams:
|
) -> AWSNovaSonicLLMInvocationParams:
|
||||||
"""Get AWS Nova Sonic-specific LLM invocation parameters from a universal LLM context.
|
"""Get AWS Nova Sonic-specific LLM invocation parameters from a universal LLM context.
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
|||||||
"tools": self.from_standard_tools(context.tools) or [],
|
"tools": self.from_standard_tools(context.tools) or [],
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
|
||||||
"""Get messages from a universal LLM context in a format ready for logging about AWS Nova Sonic.
|
"""Get messages from a universal LLM context in a format ready for logging about AWS Nova Sonic.
|
||||||
|
|
||||||
Removes or truncates sensitive data like image content for safe logging.
|
Removes or truncates sensitive data like image content for safe logging.
|
||||||
@@ -116,11 +116,11 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
|||||||
class ConvertedMessages:
|
class ConvertedMessages:
|
||||||
"""Container for Google-formatted messages converted from universal context."""
|
"""Container for Google-formatted messages converted from universal context."""
|
||||||
|
|
||||||
messages: List[AWSNovaSonicConversationHistoryMessage]
|
messages: list[AWSNovaSonicConversationHistoryMessage]
|
||||||
system_instruction: Optional[str] = None
|
system_instruction: str | None = None
|
||||||
|
|
||||||
def _from_universal_context_messages(
|
def _from_universal_context_messages(
|
||||||
self, universal_context_messages: List[LLMContextMessage]
|
self, universal_context_messages: list[LLMContextMessage]
|
||||||
) -> ConvertedMessages:
|
) -> ConvertedMessages:
|
||||||
system_instruction = None
|
system_instruction = None
|
||||||
messages = []
|
messages = []
|
||||||
@@ -187,7 +187,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
|||||||
# Sonic conversation history
|
# Sonic conversation history
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_aws_nova_sonic_function_format(function: FunctionSchema) -> Dict[str, Any]:
|
def _to_aws_nova_sonic_function_format(function: FunctionSchema) -> dict[str, Any]:
|
||||||
"""Convert a function schema to AWS Nova Sonic format.
|
"""Convert a function schema to AWS Nova Sonic format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -212,7 +212,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
|
||||||
"""Convert tools schema to AWS Nova Sonic function-calling format.
|
"""Convert tools schema to AWS Nova Sonic function-calling format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import base64
|
|||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -29,9 +29,9 @@ from pipecat.processors.aggregators.llm_context import (
|
|||||||
class AWSBedrockLLMInvocationParams(TypedDict):
|
class AWSBedrockLLMInvocationParams(TypedDict):
|
||||||
"""Context-based parameters for invoking AWS Bedrock's LLM API."""
|
"""Context-based parameters for invoking AWS Bedrock's LLM API."""
|
||||||
|
|
||||||
system: Optional[List[dict[str, Any]]] # [{"text": "system message"}]
|
system: list[dict[str, Any]] | None # [{"text": "system message"}]
|
||||||
messages: List[dict[str, Any]]
|
messages: list[dict[str, Any]]
|
||||||
tools: List[dict[str, Any]]
|
tools: list[dict[str, Any]]
|
||||||
tool_choice: LLMContextToolChoice
|
tool_choice: LLMContextToolChoice
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
|||||||
return "aws"
|
return "aws"
|
||||||
|
|
||||||
def get_llm_invocation_params(
|
def get_llm_invocation_params(
|
||||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
self, context: LLMContext, *, system_instruction: str | None = None
|
||||||
) -> AWSBedrockLLMInvocationParams:
|
) -> AWSBedrockLLMInvocationParams:
|
||||||
"""Get AWS Bedrock-specific LLM invocation parameters from a universal LLM context.
|
"""Get AWS Bedrock-specific LLM invocation parameters from a universal LLM context.
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
|||||||
"tool_choice": context.tool_choice,
|
"tool_choice": context.tool_choice,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
|
||||||
"""Get messages from a universal LLM context in a format ready for logging about AWS Bedrock.
|
"""Get messages from a universal LLM context in a format ready for logging about AWS Bedrock.
|
||||||
|
|
||||||
Removes or truncates sensitive data like image content for safe logging.
|
Removes or truncates sensitive data like image content for safe logging.
|
||||||
@@ -109,14 +109,14 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
|||||||
class ConvertedMessages:
|
class ConvertedMessages:
|
||||||
"""Container for Bedrock-formatted messages converted from universal context."""
|
"""Container for Bedrock-formatted messages converted from universal context."""
|
||||||
|
|
||||||
messages: List[dict[str, Any]]
|
messages: list[dict[str, Any]]
|
||||||
system: Optional[str]
|
system: str | None
|
||||||
|
|
||||||
def _from_universal_context_messages(
|
def _from_universal_context_messages(
|
||||||
self,
|
self,
|
||||||
universal_context_messages: List[LLMContextMessage],
|
universal_context_messages: list[LLMContextMessage],
|
||||||
*,
|
*,
|
||||||
system_instruction: Optional[str] = None,
|
system_instruction: str | None = None,
|
||||||
) -> ConvertedMessages:
|
) -> ConvertedMessages:
|
||||||
system = None
|
system = None
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
|||||||
return message
|
return message
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_bedrock_function_format(function: FunctionSchema) -> Dict[str, Any]:
|
def _to_bedrock_function_format(function: FunctionSchema) -> dict[str, Any]:
|
||||||
"""Convert a function schema to Bedrock's tool format.
|
"""Convert a function schema to Bedrock's tool format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -328,7 +328,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
|
||||||
"""Convert function schemas to Bedrock's function-calling format.
|
"""Convert function schemas to Bedrock's function-calling format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from openai import NotGiven
|
from openai import NotGiven
|
||||||
@@ -34,9 +34,9 @@ except ModuleNotFoundError as e:
|
|||||||
class GeminiLLMInvocationParams(TypedDict):
|
class GeminiLLMInvocationParams(TypedDict):
|
||||||
"""Context-based parameters for invoking Gemini LLM."""
|
"""Context-based parameters for invoking Gemini LLM."""
|
||||||
|
|
||||||
system_instruction: Optional[str]
|
system_instruction: str | None
|
||||||
messages: List[Content]
|
messages: list[Content]
|
||||||
tools: List[Any] | NotGiven
|
tools: list[Any] | NotGiven
|
||||||
|
|
||||||
|
|
||||||
class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
||||||
@@ -54,7 +54,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
return "google"
|
return "google"
|
||||||
|
|
||||||
def get_llm_invocation_params(
|
def get_llm_invocation_params(
|
||||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
self, context: LLMContext, *, system_instruction: str | None = None
|
||||||
) -> GeminiLLMInvocationParams:
|
) -> GeminiLLMInvocationParams:
|
||||||
"""Get Gemini-specific LLM invocation parameters from a universal LLM context.
|
"""Get Gemini-specific LLM invocation parameters from a universal LLM context.
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
"tools": self.from_standard_tools(context.tools),
|
"tools": self.from_standard_tools(context.tools),
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
|
||||||
"""Convert tool schemas to Gemini's function-calling format.
|
"""Convert tool schemas to Gemini's function-calling format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -92,7 +92,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
Includes both converted standard tools and any custom Gemini-specific tools.
|
Includes both converted standard tools and any custom Gemini-specific tools.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _strip_additional_properties(schema: Dict[str, Any]) -> Dict[str, Any]:
|
def _strip_additional_properties(schema: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Recursively remove "additionalProperties" fields from JSON schema, as they're not supported by Gemini.
|
"""Recursively remove "additionalProperties" fields from JSON schema, as they're not supported by Gemini.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -139,7 +139,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
|
|
||||||
return formatted_standard_tools + custom_gemini_tools
|
return formatted_standard_tools + custom_gemini_tools
|
||||||
|
|
||||||
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
|
||||||
"""Get messages from a universal LLM context in a format ready for logging about Gemini.
|
"""Get messages from a universal LLM context in a format ready for logging about Gemini.
|
||||||
|
|
||||||
Removes or truncates sensitive data like image content for safe logging.
|
Removes or truncates sensitive data like image content for safe logging.
|
||||||
@@ -173,8 +173,8 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
class ConvertedMessages:
|
class ConvertedMessages:
|
||||||
"""Container for Google-formatted messages converted from universal context."""
|
"""Container for Google-formatted messages converted from universal context."""
|
||||||
|
|
||||||
messages: List[Content]
|
messages: list[Content]
|
||||||
system_instruction: Optional[str] = None
|
system_instruction: str | None = None
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MessageConversionResult:
|
class MessageConversionResult:
|
||||||
@@ -184,20 +184,20 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
for any tool calls discovered in the message.
|
for any tool calls discovered in the message.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
content: Optional[Content] = None
|
content: Content | None = None
|
||||||
tool_call_id_to_name_mapping: Dict[str, str] = field(default_factory=dict)
|
tool_call_id_to_name_mapping: dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MessageConversionParams:
|
class MessageConversionParams:
|
||||||
"""Parameters for converting a single universal context message to Google format."""
|
"""Parameters for converting a single universal context message to Google format."""
|
||||||
|
|
||||||
tool_call_id_to_name_mapping: Dict[str, str]
|
tool_call_id_to_name_mapping: dict[str, str]
|
||||||
|
|
||||||
def _from_universal_context_messages(
|
def _from_universal_context_messages(
|
||||||
self,
|
self,
|
||||||
universal_context_messages: List[LLMContextMessage],
|
universal_context_messages: list[LLMContextMessage],
|
||||||
*,
|
*,
|
||||||
system_instruction: Optional[str] = None,
|
system_instruction: str | None = None,
|
||||||
) -> ConvertedMessages:
|
) -> ConvertedMessages:
|
||||||
"""Restructures messages to ensure proper Google format and message ordering.
|
"""Restructures messages to ensure proper Google format and message ordering.
|
||||||
|
|
||||||
@@ -443,8 +443,8 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _merge_parallel_tool_calls_for_thinking(
|
def _merge_parallel_tool_calls_for_thinking(
|
||||||
self, thought_signature_dicts: List[dict], messages: List[Content]
|
self, thought_signature_dicts: list[dict], messages: list[Content]
|
||||||
) -> List[Content]:
|
) -> list[Content]:
|
||||||
"""Merge parallel tool calls into single Content objects when thinking is enabled.
|
"""Merge parallel tool calls into single Content objects when thinking is enabled.
|
||||||
|
|
||||||
Gemini expects parallel tool calls (multiple function calls made
|
Gemini expects parallel tool calls (multiple function calls made
|
||||||
@@ -540,7 +540,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|||||||
return merged_messages
|
return merged_messages
|
||||||
|
|
||||||
def _apply_thought_signatures_to_messages(
|
def _apply_thought_signatures_to_messages(
|
||||||
self, thought_signature_dicts: List[dict], messages: List[Content]
|
self, thought_signature_dicts: list[dict], messages: list[Content]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Apply thought signatures to corresponding assistant messages.
|
"""Apply thought signatures to corresponding assistant messages.
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Grok's Voice Agent API.
|
|||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -33,9 +33,9 @@ class GrokRealtimeLLMInvocationParams(TypedDict):
|
|||||||
tools: List of tool definitions (function, web_search, x_search, file_search).
|
tools: List of tool definitions (function, web_search, x_search, file_search).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
system_instruction: Optional[str]
|
system_instruction: str | None
|
||||||
messages: List[events.ConversationItem]
|
messages: list[events.ConversationItem]
|
||||||
tools: List[Dict[str, Any]]
|
tools: list[dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
||||||
@@ -51,7 +51,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
return "grok-realtime"
|
return "grok-realtime"
|
||||||
|
|
||||||
def get_llm_invocation_params(
|
def get_llm_invocation_params(
|
||||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
self, context: LLMContext, *, system_instruction: str | None = None
|
||||||
) -> GrokRealtimeLLMInvocationParams:
|
) -> GrokRealtimeLLMInvocationParams:
|
||||||
"""Get Grok Realtime-specific LLM invocation parameters from a universal LLM context.
|
"""Get Grok Realtime-specific LLM invocation parameters from a universal LLM context.
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
"tools": self.from_standard_tools(context.tools) or [],
|
"tools": self.from_standard_tools(context.tools) or [],
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
|
||||||
"""Get messages from context in a format safe for logging.
|
"""Get messages from context in a format safe for logging.
|
||||||
|
|
||||||
Binary data (images, audio) is replaced with short placeholders.
|
Binary data (images, audio) is replaced with short placeholders.
|
||||||
@@ -91,11 +91,11 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
class ConvertedMessages:
|
class ConvertedMessages:
|
||||||
"""Container for Grok-formatted messages converted from universal context."""
|
"""Container for Grok-formatted messages converted from universal context."""
|
||||||
|
|
||||||
messages: List[events.ConversationItem]
|
messages: list[events.ConversationItem]
|
||||||
system_instruction: Optional[str] = None
|
system_instruction: str | None = None
|
||||||
|
|
||||||
def _from_universal_context_messages(
|
def _from_universal_context_messages(
|
||||||
self, universal_context_messages: List[LLMContextMessage]
|
self, universal_context_messages: list[LLMContextMessage]
|
||||||
) -> ConvertedMessages:
|
) -> ConvertedMessages:
|
||||||
"""Convert universal context messages to Grok Realtime format.
|
"""Convert universal context messages to Grok Realtime format.
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
|
logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_grok_function_format(function: FunctionSchema) -> Dict[str, Any]:
|
def _to_grok_function_format(function: FunctionSchema) -> dict[str, Any]:
|
||||||
"""Convert a function schema to Grok Realtime function format.
|
"""Convert a function schema to Grok Realtime function format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -231,7 +231,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
|
||||||
"""Convert tool schemas to Grok Realtime format.
|
"""Convert tool schemas to Grok Realtime format.
|
||||||
|
|
||||||
Supports both standard function tools and Grok-specific tools
|
Supports both standard function tools and Grok-specific tools
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Inworld's Realtime API.
|
|||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -33,9 +33,9 @@ class InworldRealtimeLLMInvocationParams(TypedDict):
|
|||||||
tools: List of tool definitions.
|
tools: List of tool definitions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
system_instruction: Optional[str]
|
system_instruction: str | None
|
||||||
messages: List[events.ConversationItem]
|
messages: list[events.ConversationItem]
|
||||||
tools: List[Dict[str, Any]]
|
tools: list[dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
||||||
@@ -51,7 +51,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
return "inworld-realtime"
|
return "inworld-realtime"
|
||||||
|
|
||||||
def get_llm_invocation_params(
|
def get_llm_invocation_params(
|
||||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
self, context: LLMContext, *, system_instruction: str | None = None
|
||||||
) -> InworldRealtimeLLMInvocationParams:
|
) -> InworldRealtimeLLMInvocationParams:
|
||||||
"""Get Inworld Realtime-specific LLM invocation parameters from a universal LLM context.
|
"""Get Inworld Realtime-specific LLM invocation parameters from a universal LLM context.
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
"tools": self.from_standard_tools(context.tools) or [],
|
"tools": self.from_standard_tools(context.tools) or [],
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
|
||||||
"""Get messages from context in a format safe for logging.
|
"""Get messages from context in a format safe for logging.
|
||||||
|
|
||||||
Binary data (images, audio) is replaced with short placeholders.
|
Binary data (images, audio) is replaced with short placeholders.
|
||||||
@@ -91,11 +91,11 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
class ConvertedMessages:
|
class ConvertedMessages:
|
||||||
"""Container for Inworld-formatted messages converted from universal context."""
|
"""Container for Inworld-formatted messages converted from universal context."""
|
||||||
|
|
||||||
messages: List[events.ConversationItem]
|
messages: list[events.ConversationItem]
|
||||||
system_instruction: Optional[str] = None
|
system_instruction: str | None = None
|
||||||
|
|
||||||
def _from_universal_context_messages(
|
def _from_universal_context_messages(
|
||||||
self, universal_context_messages: List[LLMContextMessage]
|
self, universal_context_messages: list[LLMContextMessage]
|
||||||
) -> ConvertedMessages:
|
) -> ConvertedMessages:
|
||||||
"""Convert universal context messages to Inworld Realtime format.
|
"""Convert universal context messages to Inworld Realtime format.
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
|
logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_inworld_function_format(function: FunctionSchema) -> Dict[str, Any]:
|
def _to_inworld_function_format(function: FunctionSchema) -> dict[str, Any]:
|
||||||
"""Convert a function schema to Inworld Realtime function format.
|
"""Convert a function schema to Inworld Realtime function format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -231,7 +231,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
|
||||||
"""Convert tool schemas to Inworld Realtime format.
|
"""Convert tool schemas to Inworld Realtime format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
"""OpenAI LLM adapter for Pipecat."""
|
"""OpenAI LLM adapter for Pipecat."""
|
||||||
|
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from openai._types import NotGiven as OpenAINotGiven
|
from openai._types import NotGiven as OpenAINotGiven
|
||||||
from openai.types.chat import (
|
from openai.types.chat import (
|
||||||
@@ -29,8 +29,8 @@ from pipecat.processors.aggregators.llm_context import (
|
|||||||
class OpenAILLMInvocationParams(TypedDict):
|
class OpenAILLMInvocationParams(TypedDict):
|
||||||
"""Context-based parameters for invoking OpenAI ChatCompletion API."""
|
"""Context-based parameters for invoking OpenAI ChatCompletion API."""
|
||||||
|
|
||||||
messages: List[ChatCompletionMessageParam]
|
messages: list[ChatCompletionMessageParam]
|
||||||
tools: List[ChatCompletionToolParam] | OpenAINotGiven
|
tools: list[ChatCompletionToolParam] | OpenAINotGiven
|
||||||
tool_choice: ChatCompletionToolChoiceOptionParam | OpenAINotGiven
|
tool_choice: ChatCompletionToolChoiceOptionParam | OpenAINotGiven
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
|||||||
self,
|
self,
|
||||||
context: LLMContext,
|
context: LLMContext,
|
||||||
*,
|
*,
|
||||||
system_instruction: Optional[str] = None,
|
system_instruction: str | None = None,
|
||||||
convert_developer_to_user: bool,
|
convert_developer_to_user: bool,
|
||||||
) -> OpenAILLMInvocationParams:
|
) -> OpenAILLMInvocationParams:
|
||||||
"""Get OpenAI-specific LLM invocation parameters from a universal LLM context.
|
"""Get OpenAI-specific LLM invocation parameters from a universal LLM context.
|
||||||
@@ -95,7 +95,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
|||||||
"tool_choice": context.tool_choice,
|
"tool_choice": context.tool_choice,
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[ChatCompletionToolParam]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[ChatCompletionToolParam]:
|
||||||
"""Convert function schemas to OpenAI's function-calling format.
|
"""Convert function schemas to OpenAI's function-calling format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -115,7 +115,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
|||||||
custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, [])
|
custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, [])
|
||||||
return formatted_standard_tools + custom_openai_tools
|
return formatted_standard_tools + custom_openai_tools
|
||||||
|
|
||||||
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
|
||||||
"""Get messages from a universal LLM context in a format ready for logging about OpenAI.
|
"""Get messages from a universal LLM context in a format ready for logging about OpenAI.
|
||||||
|
|
||||||
Binary data (images, audio) is replaced with short placeholders.
|
Binary data (images, audio) is replaced with short placeholders.
|
||||||
@@ -130,10 +130,10 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
|||||||
|
|
||||||
def _from_universal_context_messages(
|
def _from_universal_context_messages(
|
||||||
self,
|
self,
|
||||||
messages: List[LLMContextMessage],
|
messages: list[LLMContextMessage],
|
||||||
*,
|
*,
|
||||||
convert_developer_to_user: bool,
|
convert_developer_to_user: bool,
|
||||||
) -> List[ChatCompletionMessageParam]:
|
) -> list[ChatCompletionMessageParam]:
|
||||||
result = []
|
result = []
|
||||||
for message in messages:
|
for message in messages:
|
||||||
if isinstance(message, LLMSpecificMessage):
|
if isinstance(message, LLMSpecificMessage):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -26,9 +26,9 @@ class OpenAIRealtimeLLMInvocationParams(TypedDict):
|
|||||||
This is a placeholder until support for universal LLMContext machinery is added for OpenAI Realtime.
|
This is a placeholder until support for universal LLMContext machinery is added for OpenAI Realtime.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
system_instruction: Optional[str]
|
system_instruction: str | None
|
||||||
messages: List[events.ConversationItem]
|
messages: list[events.ConversationItem]
|
||||||
tools: List[Dict[str, Any]]
|
tools: list[dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
||||||
@@ -44,7 +44,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
return "openai-realtime"
|
return "openai-realtime"
|
||||||
|
|
||||||
def get_llm_invocation_params(
|
def get_llm_invocation_params(
|
||||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
self, context: LLMContext, *, system_instruction: str | None = None
|
||||||
) -> OpenAIRealtimeLLMInvocationParams:
|
) -> OpenAIRealtimeLLMInvocationParams:
|
||||||
"""Get OpenAI Realtime-specific LLM invocation parameters from a universal LLM context.
|
"""Get OpenAI Realtime-specific LLM invocation parameters from a universal LLM context.
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
"tools": self.from_standard_tools(context.tools) or [],
|
"tools": self.from_standard_tools(context.tools) or [],
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
|
||||||
"""Get messages from a universal LLM context in a format ready for logging about OpenAI Realtime.
|
"""Get messages from a universal LLM context in a format ready for logging about OpenAI Realtime.
|
||||||
|
|
||||||
Binary data (images, audio) is replaced with short placeholders.
|
Binary data (images, audio) is replaced with short placeholders.
|
||||||
@@ -87,11 +87,11 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
class ConvertedMessages:
|
class ConvertedMessages:
|
||||||
"""Container for OpenAI-formatted messages converted from universal context."""
|
"""Container for OpenAI-formatted messages converted from universal context."""
|
||||||
|
|
||||||
messages: List[events.ConversationItem]
|
messages: list[events.ConversationItem]
|
||||||
system_instruction: Optional[str] = None
|
system_instruction: str | None = None
|
||||||
|
|
||||||
def _from_universal_context_messages(
|
def _from_universal_context_messages(
|
||||||
self, universal_context_messages: List[LLMContextMessage]
|
self, universal_context_messages: list[LLMContextMessage]
|
||||||
) -> ConvertedMessages:
|
) -> ConvertedMessages:
|
||||||
# We can't load a long conversation history into the openai realtime api yet. (The API/model
|
# 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
|
# forgets that it can do audio, if you do a series of `conversation.item.create` calls.) So
|
||||||
@@ -188,7 +188,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
|
logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_openai_realtime_function_format(function: FunctionSchema) -> Dict[str, Any]:
|
def _to_openai_realtime_function_format(function: FunctionSchema) -> dict[str, Any]:
|
||||||
"""Convert a function schema to OpenAI Realtime format.
|
"""Convert a function schema to OpenAI Realtime format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -208,7 +208,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
|
||||||
"""Convert tool schemas to OpenAI Realtime function-calling format.
|
"""Convert tool schemas to OpenAI Realtime function-calling format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
"""OpenAI Responses API adapter for Pipecat."""
|
"""OpenAI Responses API adapter for Pipecat."""
|
||||||
|
|
||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
from openai._types import NotGiven as OpenAINotGiven
|
from openai._types import NotGiven as OpenAINotGiven
|
||||||
from openai.types.responses import FunctionToolParam, ResponseInputItemParam, ToolParam
|
from openai.types.responses import FunctionToolParam, ResponseInputItemParam, ToolParam
|
||||||
@@ -23,8 +23,8 @@ from pipecat.processors.aggregators.llm_context import (
|
|||||||
class OpenAIResponsesLLMInvocationParams(TypedDict, total=False):
|
class OpenAIResponsesLLMInvocationParams(TypedDict, total=False):
|
||||||
"""Context-based parameters for invoking OpenAI Responses API."""
|
"""Context-based parameters for invoking OpenAI Responses API."""
|
||||||
|
|
||||||
input: List[ResponseInputItemParam]
|
input: list[ResponseInputItemParam]
|
||||||
tools: List[ToolParam] | OpenAINotGiven
|
tools: list[ToolParam] | OpenAINotGiven
|
||||||
instructions: str
|
instructions: str
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
|
|||||||
self,
|
self,
|
||||||
context: LLMContext,
|
context: LLMContext,
|
||||||
*,
|
*,
|
||||||
system_instruction: Optional[str] = None,
|
system_instruction: str | None = None,
|
||||||
) -> OpenAIResponsesLLMInvocationParams:
|
) -> OpenAIResponsesLLMInvocationParams:
|
||||||
"""Get Responses API invocation parameters from a universal LLM context.
|
"""Get Responses API invocation parameters from a universal LLM context.
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
|
|||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[ToolParam]:
|
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[ToolParam]:
|
||||||
"""Convert function schemas to Responses API function tool format.
|
"""Convert function schemas to Responses API function tool format.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -132,7 +132,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
|
|||||||
custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, [])
|
custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, [])
|
||||||
return result + custom_openai_tools
|
return result + custom_openai_tools
|
||||||
|
|
||||||
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]:
|
def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
|
||||||
"""Get messages from context in a format ready for logging.
|
"""Get messages from context in a format ready for logging.
|
||||||
|
|
||||||
Binary data (images, audio) is replaced with short placeholders.
|
Binary data (images, audio) is replaced with short placeholders.
|
||||||
@@ -146,8 +146,8 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
|
|||||||
return self.get_messages(context, truncate_large_values=True)
|
return self.get_messages(context, truncate_large_values=True)
|
||||||
|
|
||||||
def _convert_messages_to_input(
|
def _convert_messages_to_input(
|
||||||
self, messages: List[LLMContextMessage]
|
self, messages: list[LLMContextMessage]
|
||||||
) -> List[ResponseInputItemParam]:
|
) -> list[ResponseInputItemParam]:
|
||||||
"""Convert LLMContext messages to Responses API input items.
|
"""Convert LLMContext messages to Responses API input items.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -156,7 +156,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
|
|||||||
Returns:
|
Returns:
|
||||||
List of Responses API input items.
|
List of Responses API input items.
|
||||||
"""
|
"""
|
||||||
result: List[ResponseInputItemParam] = []
|
result: list[ResponseInputItemParam] = []
|
||||||
|
|
||||||
for message in messages:
|
for message in messages:
|
||||||
if isinstance(message, LLMSpecificMessage):
|
if isinstance(message, LLMSpecificMessage):
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ the messages are sent to Perplexity's API.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from openai.types.chat import ChatCompletionMessageParam
|
from openai.types.chat import ChatCompletionMessageParam
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ class PerplexityLLMAdapter(OpenAILLMAdapter):
|
|||||||
self,
|
self,
|
||||||
context: LLMContext,
|
context: LLMContext,
|
||||||
*,
|
*,
|
||||||
system_instruction: Optional[str] = None,
|
system_instruction: str | None = None,
|
||||||
convert_developer_to_user: bool,
|
convert_developer_to_user: bool,
|
||||||
) -> OpenAILLMInvocationParams:
|
) -> OpenAILLMInvocationParams:
|
||||||
"""Get OpenAI-compatible invocation parameters with Perplexity message fixes applied.
|
"""Get OpenAI-compatible invocation parameters with Perplexity message fixes applied.
|
||||||
@@ -78,8 +77,8 @@ class PerplexityLLMAdapter(OpenAILLMAdapter):
|
|||||||
return params
|
return params
|
||||||
|
|
||||||
def _transform_messages(
|
def _transform_messages(
|
||||||
self, messages: List[ChatCompletionMessageParam]
|
self, messages: list[ChatCompletionMessageParam]
|
||||||
) -> List[ChatCompletionMessageParam]:
|
) -> list[ChatCompletionMessageParam]:
|
||||||
"""Transform messages to satisfy Perplexity's API constraints.
|
"""Transform messages to satisfy Perplexity's API constraints.
|
||||||
|
|
||||||
Applies three transformation steps in order:
|
Applies three transformation steps in order:
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ key on the telephone keypad, facilitating the handling of input in
|
|||||||
telecommunication applications.
|
telecommunication applications.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from enum import Enum
|
from enum import StrEnum
|
||||||
|
|
||||||
|
|
||||||
class KeypadEntry(str, Enum):
|
class KeypadEntry(StrEnum):
|
||||||
"""DTMF keypad entries for phone system integration.
|
"""DTMF keypad entries for phone system integration.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import asyncio
|
|||||||
import io
|
import io
|
||||||
import wave
|
import wave
|
||||||
from importlib.resources import files
|
from importlib.resources import files
|
||||||
from typing import Dict, Optional
|
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
|
|
||||||
@@ -24,8 +23,8 @@ from pipecat.audio.resamplers.base_audio_resampler import BaseAudioResampler
|
|||||||
from pipecat.audio.utils import create_file_resampler
|
from pipecat.audio.utils import create_file_resampler
|
||||||
|
|
||||||
__DTMF_LOCK__ = asyncio.Lock()
|
__DTMF_LOCK__ = asyncio.Lock()
|
||||||
__DTMF_AUDIO__: Dict[KeypadEntry, bytes] = {}
|
__DTMF_AUDIO__: dict[KeypadEntry, bytes] = {}
|
||||||
__DTMF_RESAMPLER__: Optional[BaseAudioResampler] = None
|
__DTMF_RESAMPLER__: BaseAudioResampler | None = None
|
||||||
|
|
||||||
__DTMF_FILE_NAME = {
|
__DTMF_FILE_NAME = {
|
||||||
KeypadEntry.POUND: "dtmf-pound.wav",
|
KeypadEntry.POUND: "dtmf-pound.wav",
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ Classes:
|
|||||||
import asyncio
|
import asyncio
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from aic_sdk import (
|
from aic_sdk import (
|
||||||
@@ -44,14 +43,14 @@ class AICModelManager:
|
|||||||
acquires on first use and releases when the last reference is dropped.
|
acquires on first use and releases when the last reference is dropped.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_cache: dict[str, Tuple[Model, int]] = {} # key -> (model, ref_count)
|
_cache: dict[str, tuple[Model, int]] = {} # key -> (model, ref_count)
|
||||||
_lock = Lock()
|
_lock = Lock()
|
||||||
_loading: dict[
|
_loading: dict[
|
||||||
str, asyncio.Task[Model]
|
str, asyncio.Task[Model]
|
||||||
] = {} # key -> load task (deduplicates concurrent loads)
|
] = {} # key -> load task (deduplicates concurrent loads)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _increment_reference(cls, cache_key: str, entry: Tuple[Model, int]) -> Tuple[Model, str]:
|
def _increment_reference(cls, cache_key: str, entry: tuple[Model, int]) -> tuple[Model, str]:
|
||||||
"""Increment reference count for cached entry. Caller must hold _lock."""
|
"""Increment reference count for cached entry. Caller must hold _lock."""
|
||||||
cached_model, ref_count = entry
|
cached_model, ref_count = entry
|
||||||
cls._cache[cache_key] = (cached_model, ref_count + 1)
|
cls._cache[cache_key] = (cached_model, ref_count + 1)
|
||||||
@@ -59,7 +58,7 @@ class AICModelManager:
|
|||||||
return cached_model, cache_key
|
return cached_model, cache_key
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _store_new_reference(cls, cache_key: str, model: Model) -> Tuple[Model, str]:
|
def _store_new_reference(cls, cache_key: str, model: Model) -> tuple[Model, str]:
|
||||||
"""Store new model in cache with ref count 1. Caller must hold _lock."""
|
"""Store new model in cache with ref count 1. Caller must hold _lock."""
|
||||||
cls._cache[cache_key] = (model, 1)
|
cls._cache[cache_key] = (model, 1)
|
||||||
logger.debug(f"AIC model cached key={cache_key!r} ref_count=1")
|
logger.debug(f"AIC model cached key={cache_key!r} ref_count=1")
|
||||||
@@ -70,9 +69,9 @@ class AICModelManager:
|
|||||||
cls,
|
cls,
|
||||||
cache_key: str,
|
cache_key: str,
|
||||||
*,
|
*,
|
||||||
model_path: Optional[Path] = None,
|
model_path: Path | None = None,
|
||||||
model_id: Optional[str] = None,
|
model_id: str | None = None,
|
||||||
model_download_dir: Optional[Path] = None,
|
model_download_dir: Path | None = None,
|
||||||
) -> Model:
|
) -> Model:
|
||||||
"""Run the actual load (file or download). Separate to allow create_task and deduplication."""
|
"""Run the actual load (file or download). Separate to allow create_task and deduplication."""
|
||||||
if model_path is not None:
|
if model_path is not None:
|
||||||
@@ -94,9 +93,9 @@ class AICModelManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_cache_key(
|
def _get_cache_key(
|
||||||
*,
|
*,
|
||||||
model_path: Optional[Path] = None,
|
model_path: Path | None = None,
|
||||||
model_id: Optional[str] = None,
|
model_id: str | None = None,
|
||||||
model_download_dir: Optional[Path] = None,
|
model_download_dir: Path | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Build a stable cache key for the model.
|
"""Build a stable cache key for the model.
|
||||||
|
|
||||||
@@ -120,10 +119,10 @@ class AICModelManager:
|
|||||||
async def acquire(
|
async def acquire(
|
||||||
cls,
|
cls,
|
||||||
*,
|
*,
|
||||||
model_path: Optional[Path] = None,
|
model_path: Path | None = None,
|
||||||
model_id: Optional[str] = None,
|
model_id: str | None = None,
|
||||||
model_download_dir: Optional[Path] = None,
|
model_download_dir: Path | None = None,
|
||||||
) -> Tuple[Model, str]:
|
) -> tuple[Model, str]:
|
||||||
"""Get or load a Model and increment its reference count.
|
"""Get or load a Model and increment its reference count.
|
||||||
|
|
||||||
Call this when starting a filter. Store the returned key and pass it
|
Call this when starting a filter. Store the returned key and pass it
|
||||||
@@ -218,10 +217,10 @@ class AICFilter(BaseAudioFilter):
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
license_key: str,
|
license_key: str,
|
||||||
model_id: Optional[str] = None,
|
model_id: str | None = None,
|
||||||
model_path: Optional[Path] = None,
|
model_path: Path | None = None,
|
||||||
model_download_dir: Optional[Path] = None,
|
model_download_dir: Path | None = None,
|
||||||
enhancement_level: Optional[float] = None,
|
enhancement_level: float | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the AIC filter.
|
"""Initialize the AIC filter.
|
||||||
|
|
||||||
@@ -274,7 +273,7 @@ class AICFilter(BaseAudioFilter):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# AIC SDK objects; model is shared via AICModelManager
|
# AIC SDK objects; model is shared via AICModelManager
|
||||||
self._model_cache_key: Optional[str] = None
|
self._model_cache_key: str | None = None
|
||||||
self._model = None
|
self._model = None
|
||||||
self._processor = None
|
self._processor = None
|
||||||
self._processor_ctx = None
|
self._processor_ctx = None
|
||||||
@@ -298,9 +297,9 @@ class AICFilter(BaseAudioFilter):
|
|||||||
def create_vad_analyzer(
|
def create_vad_analyzer(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
speech_hold_duration: Optional[float] = None,
|
speech_hold_duration: float | None = None,
|
||||||
minimum_speech_duration: Optional[float] = None,
|
minimum_speech_duration: float | None = None,
|
||||||
sensitivity: Optional[float] = None,
|
sensitivity: float | None = None,
|
||||||
):
|
):
|
||||||
"""Return an analyzer that will lazily instantiate the AIC VAD when ready.
|
"""Return an analyzer that will lazily instantiate the AIC VAD when ready.
|
||||||
|
|
||||||
@@ -491,7 +490,7 @@ class AICFilter(BaseAudioFilter):
|
|||||||
blocks_data = bytes(self._audio_buffer[:total_size])
|
blocks_data = bytes(self._audio_buffer[:total_size])
|
||||||
self._audio_buffer = self._audio_buffer[total_size:]
|
self._audio_buffer = self._audio_buffer[total_size:]
|
||||||
|
|
||||||
filtered_chunks: List[bytes] = []
|
filtered_chunks: list[bytes] = []
|
||||||
|
|
||||||
for i in range(num_blocks):
|
for i in range(num_blocks):
|
||||||
start = i * block_size
|
start = i * block_size
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ This module provides an audio filter implementation using PicoVoice's Koala
|
|||||||
Noise Suppression engine to reduce background noise in audio streams.
|
Noise Suppression engine to reduce background noise in audio streams.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ runtime configuration changes.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any, Dict, Mapping
|
from collections.abc import Mapping
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -70,7 +71,7 @@ class SoundfileMixer(BaseAudioMixer):
|
|||||||
self._sample_rate = 0
|
self._sample_rate = 0
|
||||||
|
|
||||||
self._sound_pos = 0
|
self._sound_pos = 0
|
||||||
self._sounds: Dict[str, Any] = {}
|
self._sounds: dict[str, Any] = {}
|
||||||
self._current_sound = default_sound
|
self._current_sound = default_sound
|
||||||
self._mixing = mixing
|
self._mixing = mixing
|
||||||
self._loop = loop
|
self._loop = loop
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ when a user has finished speaking in a conversation.
|
|||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional, Tuple
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ class BaseTurnAnalyzer(ABC):
|
|||||||
while still defining an abstract interface through abstract methods.
|
while still defining an abstract interface through abstract methods.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, sample_rate: Optional[int] = None):
|
def __init__(self, *, sample_rate: int | None = None):
|
||||||
"""Initialize the turn analyzer.
|
"""Initialize the turn analyzer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -108,7 +107,7 @@ class BaseTurnAnalyzer(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def analyze_end_of_turn(self) -> Tuple[EndOfTurnState, Optional[MetricsData]]:
|
async def analyze_end_of_turn(self) -> tuple[EndOfTurnState, MetricsData | None]:
|
||||||
"""Analyzes if an end of turn has occurred based on the audio input.
|
"""Analyzes if an end of turn has occurred based on the audio input.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ passed directly to the constructor.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Tuple
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -61,9 +60,9 @@ class KrispVivaTurn(BaseTurnAnalyzer):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
model_path: Optional[str] = None,
|
model_path: str | None = None,
|
||||||
sample_rate: Optional[int] = None,
|
sample_rate: int | None = None,
|
||||||
params: Optional[KrispTurnParams] = None,
|
params: KrispTurnParams | None = None,
|
||||||
api_key: str = "",
|
api_key: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the Krisp turn analyzer.
|
"""Initialize the Krisp turn analyzer.
|
||||||
@@ -119,9 +118,9 @@ class KrispVivaTurn(BaseTurnAnalyzer):
|
|||||||
self._last_probability = None
|
self._last_probability = None
|
||||||
self._frame_probabilities = []
|
self._frame_probabilities = []
|
||||||
self._last_state = EndOfTurnState.INCOMPLETE
|
self._last_state = EndOfTurnState.INCOMPLETE
|
||||||
self._speech_stopped_time: Optional[float] = None
|
self._speech_stopped_time: float | None = None
|
||||||
self._e2e_processing_time_ms: Optional[float] = None
|
self._e2e_processing_time_ms: float | None = None
|
||||||
self._last_metrics: Optional[TurnMetricsData] = None
|
self._last_metrics: TurnMetricsData | None = None
|
||||||
|
|
||||||
# Create session with provided sample rate or default to 16000 Hz
|
# Create session with provided sample rate or default to 16000 Hz
|
||||||
# This preloads the model to improve latency when set_sample_rate is called later
|
# This preloads the model to improve latency when set_sample_rate is called later
|
||||||
@@ -214,7 +213,7 @@ class KrispVivaTurn(BaseTurnAnalyzer):
|
|||||||
return self._frame_probabilities
|
return self._frame_probabilities
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_probability(self) -> Optional[float]:
|
def last_probability(self) -> float | None:
|
||||||
"""Get the last turn probability value computed.
|
"""Get the last turn probability value computed.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -348,7 +347,7 @@ class KrispVivaTurn(BaseTurnAnalyzer):
|
|||||||
self._last_state = error_state
|
self._last_state = error_state
|
||||||
return error_state
|
return error_state
|
||||||
|
|
||||||
async def analyze_end_of_turn(self) -> Tuple[EndOfTurnState, Optional[MetricsData]]:
|
async def analyze_end_of_turn(self) -> tuple[EndOfTurnState, MetricsData | None]:
|
||||||
"""Analyze the current audio state to determine if turn has ended.
|
"""Analyze the current audio state to determine if turn has ended.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import asyncio
|
|||||||
import time
|
import time
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from typing import Any, Dict, Optional, Tuple
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -57,9 +57,7 @@ class BaseSmartTurn(BaseTurnAnalyzer):
|
|||||||
implement the specific model prediction logic.
|
implement the specific model prediction logic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, *, sample_rate: int | None = None, params: SmartTurnParams | None = None):
|
||||||
self, *, sample_rate: Optional[int] = None, params: Optional[SmartTurnParams] = None
|
|
||||||
):
|
|
||||||
"""Initialize the smart turn analyzer.
|
"""Initialize the smart turn analyzer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -146,7 +144,7 @@ class BaseSmartTurn(BaseTurnAnalyzer):
|
|||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
async def analyze_end_of_turn(self) -> Tuple[EndOfTurnState, Optional[MetricsData]]:
|
async def analyze_end_of_turn(self) -> tuple[EndOfTurnState, MetricsData | None]:
|
||||||
"""Analyze the current audio state to determine if turn has ended.
|
"""Analyze the current audio state to determine if turn has ended.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -178,7 +176,7 @@ class BaseSmartTurn(BaseTurnAnalyzer):
|
|||||||
self._speech_start_time = 0
|
self._speech_start_time = 0
|
||||||
self._silence_ms = 0
|
self._silence_ms = 0
|
||||||
|
|
||||||
def _process_speech_segment(self, audio_buffer) -> Tuple[EndOfTurnState, Optional[MetricsData]]:
|
def _process_speech_segment(self, audio_buffer) -> tuple[EndOfTurnState, MetricsData | None]:
|
||||||
"""Process accumulated audio segment using ML model."""
|
"""Process accumulated audio segment using ML model."""
|
||||||
state = EndOfTurnState.INCOMPLETE
|
state = EndOfTurnState.INCOMPLETE
|
||||||
|
|
||||||
@@ -248,6 +246,6 @@ class BaseSmartTurn(BaseTurnAnalyzer):
|
|||||||
return state, result_data
|
return state, result_data
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]:
|
def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
|
||||||
"""Predict end-of-turn using ML model from audio data."""
|
"""Predict end-of-turn using ML model from audio data."""
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ HTTP endpoints for ML-based end-of-turn detection.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import io
|
import io
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -33,7 +33,7 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
|
|||||||
*,
|
*,
|
||||||
url: str,
|
url: str,
|
||||||
aiohttp_session: aiohttp.ClientSession,
|
aiohttp_session: aiohttp.ClientSession,
|
||||||
headers: Optional[Dict[str, str]] = None,
|
headers: dict[str, str] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the HTTP smart turn analyzer.
|
"""Initialize the HTTP smart turn analyzer.
|
||||||
@@ -58,7 +58,7 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
|
|||||||
logger.trace(f"Serialized size: {len(serialized_bytes)} bytes")
|
logger.trace(f"Serialized size: {len(serialized_bytes)} bytes")
|
||||||
return serialized_bytes
|
return serialized_bytes
|
||||||
|
|
||||||
async def _send_raw_request(self, data_bytes: bytes) -> Dict[str, Any]:
|
async def _send_raw_request(self, data_bytes: bytes) -> dict[str, Any]:
|
||||||
"""Send raw audio data to the HTTP endpoint for prediction."""
|
"""Send raw audio data to the HTTP endpoint for prediction."""
|
||||||
headers = {"Content-Type": "application/octet-stream"}
|
headers = {"Content-Type": "application/octet-stream"}
|
||||||
headers.update(self._headers)
|
headers.update(self._headers)
|
||||||
@@ -97,14 +97,14 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
|
|||||||
logger.trace(text)
|
logger.trace(text)
|
||||||
raise Exception(f"Non-JSON response: {text}")
|
raise Exception(f"Non-JSON response: {text}")
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except TimeoutError:
|
||||||
logger.error(f"Request timed out after {self._params.stop_secs} seconds")
|
logger.error(f"Request timed out after {self._params.stop_secs} seconds")
|
||||||
raise SmartTurnTimeoutException(f"Request exceeded {self._params.stop_secs} seconds.")
|
raise SmartTurnTimeoutException(f"Request exceeded {self._params.stop_secs} seconds.")
|
||||||
except aiohttp.ClientError as e:
|
except aiohttp.ClientError as e:
|
||||||
logger.error(f"Failed to send raw request to Daily Smart Turn: {e}")
|
logger.error(f"Failed to send raw request to Daily Smart Turn: {e}")
|
||||||
raise Exception("Failed to send raw request to Daily Smart Turn.")
|
raise Exception("Failed to send raw request to Daily Smart Turn.")
|
||||||
|
|
||||||
def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]:
|
def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
|
||||||
"""Predict end-of-turn using remote HTTP ML service."""
|
"""Predict end-of-turn using remote HTTP ML service."""
|
||||||
try:
|
try:
|
||||||
serialized_array = self._serialize_array(audio_array)
|
serialized_array = self._serialize_array(audio_array)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ local end-of-turn detection without requiring network connectivity.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -76,7 +76,7 @@ class LocalCoreMLSmartTurnAnalyzer(BaseSmartTurn):
|
|||||||
self._turn_model = ct.models.MLModel(core_ml_model_path)
|
self._turn_model = ct.models.MLModel(core_ml_model_path)
|
||||||
logger.debug("Loaded Local Smart Turn")
|
logger.debug("Loaded Local Smart Turn")
|
||||||
|
|
||||||
async def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]:
|
async def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
|
||||||
"""Predict end-of-turn using local CoreML model."""
|
"""Predict end-of-turn using local CoreML model."""
|
||||||
inputs = self._turn_processor(
|
inputs = self._turn_processor(
|
||||||
audio_array,
|
audio_array,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ local end-of-turn detection without requiring network connectivity.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -87,7 +87,7 @@ class LocalSmartTurnAnalyzerV2(BaseSmartTurn):
|
|||||||
self._turn_model.eval()
|
self._turn_model.eval()
|
||||||
logger.debug("Loaded Local Smart Turn v2")
|
logger.debug("Loaded Local Smart Turn v2")
|
||||||
|
|
||||||
def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]:
|
def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
|
||||||
"""Predict end-of-turn using local PyTorch model."""
|
"""Predict end-of-turn using local PyTorch model."""
|
||||||
inputs = self._turn_processor(
|
inputs = self._turn_processor(
|
||||||
audio_array,
|
audio_array,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ This module provides a smart turn analyzer that uses an ONNX model for
|
|||||||
local end-of-turn detection without requiring network connectivity.
|
local end-of-turn detection without requiring network connectivity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import onnxruntime as ort
|
import onnxruntime as ort
|
||||||
@@ -32,9 +32,7 @@ class LocalSmartTurnAnalyzerV3(BaseSmartTurn):
|
|||||||
enabling offline operation without network dependencies.
|
enabling offline operation without network dependencies.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, *, smart_turn_model_path: str | None = None, cpu_count: int = 1, **kwargs):
|
||||||
self, *, smart_turn_model_path: Optional[str] = None, cpu_count: int = 1, **kwargs
|
|
||||||
):
|
|
||||||
"""Initialize the local ONNX smart-turn-v3 analyzer.
|
"""Initialize the local ONNX smart-turn-v3 analyzer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -138,7 +136,7 @@ class LocalSmartTurnAnalyzerV3(BaseSmartTurn):
|
|||||||
|
|
||||||
return soxr.resample(audio_array, actual_rate, _MODEL_SAMPLE_RATE, quality="VHQ")
|
return soxr.resample(audio_array, actual_rate, _MODEL_SAMPLE_RATE, quality="VHQ")
|
||||||
|
|
||||||
def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]:
|
def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
|
||||||
"""Predict end-of-turn using local ONNX model."""
|
"""Predict end-of-turn using local ONNX model."""
|
||||||
|
|
||||||
def truncate_audio_to_last_n_seconds(
|
def truncate_audio_to_last_n_seconds(
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ Classes:
|
|||||||
AICVADAnalyzer: For aic-sdk (uses 'aic_sdk' module)
|
AICVADAnalyzer: For aic-sdk (uses 'aic_sdk' module)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Callable, Optional
|
from collections.abc import Callable
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from aic_sdk import VadParameter
|
from aic_sdk import VadParameter
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -46,10 +47,10 @@ class AICVADAnalyzer(VADAnalyzer):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
vad_context_factory: Optional[Callable[[], Any]] = None,
|
vad_context_factory: Callable[[], Any] | None = None,
|
||||||
speech_hold_duration: Optional[float] = None,
|
speech_hold_duration: float | None = None,
|
||||||
minimum_speech_duration: Optional[float] = None,
|
minimum_speech_duration: float | None = None,
|
||||||
sensitivity: Optional[float] = None,
|
sensitivity: float | None = None,
|
||||||
):
|
):
|
||||||
"""Create an AIC VAD analyzer.
|
"""Create an AIC VAD analyzer.
|
||||||
|
|
||||||
@@ -77,10 +78,10 @@ class AICVADAnalyzer(VADAnalyzer):
|
|||||||
super().__init__(sample_rate=None, params=fixed_params)
|
super().__init__(sample_rate=None, params=fixed_params)
|
||||||
|
|
||||||
self._vad_context_factory = vad_context_factory
|
self._vad_context_factory = vad_context_factory
|
||||||
self._vad_ctx: Optional[Any] = None
|
self._vad_ctx: Any | None = None
|
||||||
self._pending_speech_hold_duration: Optional[float] = speech_hold_duration
|
self._pending_speech_hold_duration: float | None = speech_hold_duration
|
||||||
self._pending_minimum_speech_duration: Optional[float] = minimum_speech_duration
|
self._pending_minimum_speech_duration: float | None = minimum_speech_duration
|
||||||
self._pending_sensitivity: Optional[float] = sensitivity
|
self._pending_sensitivity: float | None = sensitivity
|
||||||
|
|
||||||
def bind_vad_context_factory(self, vad_context_factory: Callable[[], Any]):
|
def bind_vad_context_factory(self, vad_context_factory: Callable[[], Any]):
|
||||||
"""Attach or replace the factory post-construction."""
|
"""Attach or replace the factory post-construction."""
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ Supports 8kHz, 16kHz, 32kHz, 44.1kHz and 48kHz sample rates.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -38,10 +37,10 @@ class KrispVivaVadAnalyzer(VADAnalyzer):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
model_path: Optional[str] = None,
|
model_path: str | None = None,
|
||||||
frame_duration: int = 10,
|
frame_duration: int = 10,
|
||||||
sample_rate: Optional[int] = None,
|
sample_rate: int | None = None,
|
||||||
params: Optional[VADParams] = None,
|
params: VADParams | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the Krisp VIVA VAD analyzer.
|
"""Initialize the Krisp VIVA VAD analyzer.
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ Supports 8kHz and 16kHz sample rates.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -135,7 +134,7 @@ class SileroVADAnalyzer(VADAnalyzer):
|
|||||||
with automatic model state management and periodic resets.
|
with automatic model state management and periodic resets.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, sample_rate: Optional[int] = None, params: Optional[VADParams] = None):
|
def __init__(self, *, sample_rate: int | None = None, params: VADParams | None = None):
|
||||||
"""Initialize the Silero VAD analyzer.
|
"""Initialize the Silero VAD analyzer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import asyncio
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -68,7 +67,7 @@ class VADAnalyzer(ABC):
|
|||||||
Subclasses must implement the core voice confidence calculation.
|
Subclasses must implement the core voice confidence calculation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, sample_rate: Optional[int] = None, params: Optional[VADParams] = None):
|
def __init__(self, *, sample_rate: int | None = None, params: VADParams | None = None):
|
||||||
"""Initialize the VAD analyzer.
|
"""Initialize the VAD analyzer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ and emit events when speech starts, stops, or is actively detected.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Type
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -90,7 +89,7 @@ class VADController(BaseObject):
|
|||||||
self._vad_analyzer = vad_analyzer
|
self._vad_analyzer = vad_analyzer
|
||||||
self._vad_state: VADState = VADState.QUIET
|
self._vad_state: VADState = VADState.QUIET
|
||||||
|
|
||||||
self._task_manager: Optional[BaseTaskManager] = None
|
self._task_manager: BaseTaskManager | None = None
|
||||||
|
|
||||||
# Last time a on_speech_activity was triggered.
|
# Last time a on_speech_activity was triggered.
|
||||||
self._speech_activity_time = 0
|
self._speech_activity_time = 0
|
||||||
@@ -102,7 +101,7 @@ class VADController(BaseObject):
|
|||||||
# while in SPEAKING state (e.g. user mutes mic mid-speech).
|
# while in SPEAKING state (e.g. user mutes mic mid-speech).
|
||||||
self._last_audio_time: float = 0.0
|
self._last_audio_time: float = 0.0
|
||||||
self._audio_idle_timeout = audio_idle_timeout
|
self._audio_idle_timeout = audio_idle_timeout
|
||||||
self._audio_idle_task: Optional[asyncio.Task] = None
|
self._audio_idle_task: asyncio.Task | None = None
|
||||||
|
|
||||||
self._register_event_handler("on_speech_started", sync=True)
|
self._register_event_handler("on_speech_started", sync=True)
|
||||||
self._register_event_handler("on_speech_stopped", sync=True)
|
self._register_event_handler("on_speech_stopped", sync=True)
|
||||||
@@ -234,7 +233,7 @@ class VADController(BaseObject):
|
|||||||
"""
|
"""
|
||||||
await self._call_event_handler("on_push_frame", frame, direction)
|
await self._call_event_handler("on_push_frame", frame, direction)
|
||||||
|
|
||||||
async def broadcast_frame(self, frame_cls: Type[Frame], **kwargs):
|
async def broadcast_frame(self, frame_cls: type[Frame], **kwargs):
|
||||||
"""Request a frame to be broadcast upstream and downstream.
|
"""Request a frame to be broadcast upstream and downstream.
|
||||||
|
|
||||||
This emits an on_broadcast_frame event that must be handled by a processor
|
This emits an on_broadcast_frame event that must be handled by a processor
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using LLM-based decision making and DTMF tone generation.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -72,7 +71,7 @@ class IVRProcessor(FrameProcessor):
|
|||||||
*,
|
*,
|
||||||
classifier_prompt: str,
|
classifier_prompt: str,
|
||||||
ivr_prompt: str,
|
ivr_prompt: str,
|
||||||
ivr_vad_params: Optional[VADParams] = None,
|
ivr_vad_params: VADParams | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the IVR processor.
|
"""Initialize the IVR processor.
|
||||||
|
|
||||||
@@ -88,7 +87,7 @@ class IVRProcessor(FrameProcessor):
|
|||||||
self._classifier_prompt = classifier_prompt
|
self._classifier_prompt = classifier_prompt
|
||||||
|
|
||||||
# Store saved context messages
|
# Store saved context messages
|
||||||
self._saved_messages: List[dict] = []
|
self._saved_messages: list[dict] = []
|
||||||
|
|
||||||
# XML pattern aggregation
|
# XML pattern aggregation
|
||||||
self._aggregator = PatternPairAggregator()
|
self._aggregator = PatternPairAggregator()
|
||||||
@@ -98,7 +97,7 @@ class IVRProcessor(FrameProcessor):
|
|||||||
self._register_event_handler("on_conversation_detected")
|
self._register_event_handler("on_conversation_detected")
|
||||||
self._register_event_handler("on_ivr_status_changed")
|
self._register_event_handler("on_ivr_status_changed")
|
||||||
|
|
||||||
def update_saved_messages(self, messages: List[dict]) -> None:
|
def update_saved_messages(self, messages: list[dict]) -> None:
|
||||||
"""Update the saved context messages.
|
"""Update the saved context messages.
|
||||||
|
|
||||||
Sets the messages that are saved when switching between
|
Sets the messages that are saved when switching between
|
||||||
@@ -109,7 +108,7 @@ class IVRProcessor(FrameProcessor):
|
|||||||
"""
|
"""
|
||||||
self._saved_messages = messages
|
self._saved_messages = messages
|
||||||
|
|
||||||
def _get_conversation_history(self) -> List[dict]:
|
def _get_conversation_history(self) -> list[dict]:
|
||||||
"""Get saved context messages without the system message.
|
"""Get saved context messages without the system message.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -409,7 +408,7 @@ Remember: Respond with `<dtmf>NUMBER</dtmf>` (single or multiple for sequences),
|
|||||||
*,
|
*,
|
||||||
llm: LLMService,
|
llm: LLMService,
|
||||||
ivr_prompt: str,
|
ivr_prompt: str,
|
||||||
ivr_vad_params: Optional[VADParams] = None,
|
ivr_vad_params: VADParams | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the IVR navigator.
|
"""Initialize the IVR navigator.
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ Note:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -71,7 +70,7 @@ class NotifierGate(FrameProcessor):
|
|||||||
self._notifier = notifier
|
self._notifier = notifier
|
||||||
self._task_name = task_name
|
self._task_name = task_name
|
||||||
self._gate_opened = True
|
self._gate_opened = True
|
||||||
self._gate_task: Optional[asyncio.Task] = None
|
self._gate_task: asyncio.Task | None = None
|
||||||
|
|
||||||
async def setup(self, setup: FrameProcessorSetup):
|
async def setup(self, setup: FrameProcessorSetup):
|
||||||
"""Set up the processor with required components.
|
"""Set up the processor with required components.
|
||||||
@@ -143,7 +142,7 @@ class ClassifierGate(NotifierGate):
|
|||||||
super().__init__(gate_notifier, task_name="classifier_gate")
|
super().__init__(gate_notifier, task_name="classifier_gate")
|
||||||
self._conversation_notifier = conversation_notifier
|
self._conversation_notifier = conversation_notifier
|
||||||
self._conversation_detected = False
|
self._conversation_detected = False
|
||||||
self._conversation_task: Optional[asyncio.Task] = None
|
self._conversation_task: asyncio.Task | None = None
|
||||||
|
|
||||||
async def setup(self, setup: FrameProcessorSetup):
|
async def setup(self, setup: FrameProcessorSetup):
|
||||||
"""Set up the processor with required components.
|
"""Set up the processor with required components.
|
||||||
@@ -267,7 +266,7 @@ class ClassificationProcessor(FrameProcessor):
|
|||||||
|
|
||||||
# Voicemail timing state
|
# Voicemail timing state
|
||||||
self._voicemail_detected = False
|
self._voicemail_detected = False
|
||||||
self._voicemail_task: Optional[asyncio.Task] = None
|
self._voicemail_task: asyncio.Task | None = None
|
||||||
self._voicemail_event = asyncio.Event()
|
self._voicemail_event = asyncio.Event()
|
||||||
self._voicemail_event.set()
|
self._voicemail_event.set()
|
||||||
|
|
||||||
@@ -390,7 +389,7 @@ class ClassificationProcessor(FrameProcessor):
|
|||||||
self._voicemail_event.wait(), timeout=self._voicemail_response_delay
|
self._voicemail_event.wait(), timeout=self._voicemail_response_delay
|
||||||
)
|
)
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
except asyncio.TimeoutError:
|
except TimeoutError:
|
||||||
await self._call_event_handler("on_voicemail_detected")
|
await self._call_event_handler("on_voicemail_detected")
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -423,10 +422,10 @@ class TTSGate(FrameProcessor):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self._conversation_notifier = conversation_notifier
|
self._conversation_notifier = conversation_notifier
|
||||||
self._voicemail_notifier = voicemail_notifier
|
self._voicemail_notifier = voicemail_notifier
|
||||||
self._frame_buffer: List[tuple[Frame, FrameDirection]] = []
|
self._frame_buffer: list[tuple[Frame, FrameDirection]] = []
|
||||||
self._gating_active = True
|
self._gating_active = True
|
||||||
self._conversation_task: Optional[asyncio.Task] = None
|
self._conversation_task: asyncio.Task | None = None
|
||||||
self._voicemail_task: Optional[asyncio.Task] = None
|
self._voicemail_task: asyncio.Task | None = None
|
||||||
|
|
||||||
async def setup(self, setup: FrameProcessorSetup):
|
async def setup(self, setup: FrameProcessorSetup):
|
||||||
"""Set up the processor with required components.
|
"""Set up the processor with required components.
|
||||||
@@ -591,7 +590,7 @@ VOICEMAIL SYSTEM (respond "VOICEMAIL"):
|
|||||||
*,
|
*,
|
||||||
llm: LLMService,
|
llm: LLMService,
|
||||||
voicemail_response_delay: float = 2.0,
|
voicemail_response_delay: float = 2.0,
|
||||||
custom_system_prompt: Optional[str] = None,
|
custom_system_prompt: str | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the voicemail detector with classification and buffering components.
|
"""Initialize the voicemail detector with classification and buffering components.
|
||||||
|
|
||||||
|
|||||||
@@ -11,20 +11,16 @@ including data frames, system frames, and control frames for audio, video, text,
|
|||||||
and LLM processing.
|
and LLM processing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
Awaitable,
|
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
List,
|
|
||||||
Literal,
|
Literal,
|
||||||
Mapping,
|
|
||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
|
||||||
Tuple,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from pipecat.adapters.schemas.tools_schema import ToolsSchema
|
from pipecat.adapters.schemas.tools_schema import ToolsSchema
|
||||||
@@ -45,7 +41,7 @@ if TYPE_CHECKING:
|
|||||||
from pipecat.utils.tracing.tracing_context import TracingContext
|
from pipecat.utils.tracing.tracing_context import TracingContext
|
||||||
|
|
||||||
|
|
||||||
def format_pts(pts: Optional[int]):
|
def format_pts(pts: int | None):
|
||||||
"""Format presentation timestamp (PTS) in nanoseconds to a human-readable string.
|
"""Format presentation timestamp (PTS) in nanoseconds to a human-readable string.
|
||||||
|
|
||||||
Converts a PTS value in nanoseconds to a string representation.
|
Converts a PTS value in nanoseconds to a string representation.
|
||||||
@@ -77,20 +73,20 @@ class Frame:
|
|||||||
|
|
||||||
id: int = field(init=False)
|
id: int = field(init=False)
|
||||||
name: str = field(init=False)
|
name: str = field(init=False)
|
||||||
pts: Optional[int] = field(init=False)
|
pts: int | None = field(init=False)
|
||||||
broadcast_sibling_id: Optional[int] = field(init=False)
|
broadcast_sibling_id: int | None = field(init=False)
|
||||||
metadata: Dict[str, Any] = field(init=False)
|
metadata: dict[str, Any] = field(init=False)
|
||||||
transport_source: Optional[str] = field(init=False)
|
transport_source: str | None = field(init=False)
|
||||||
transport_destination: Optional[str] = field(init=False)
|
transport_destination: str | None = field(init=False)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.id: int = obj_id()
|
self.id: int = obj_id()
|
||||||
self.name: str = f"{self.__class__.__name__}#{obj_count(self)}"
|
self.name: str = f"{self.__class__.__name__}#{obj_count(self)}"
|
||||||
self.pts: Optional[int] = None
|
self.pts: int | None = None
|
||||||
self.broadcast_sibling_id: Optional[int] = None
|
self.broadcast_sibling_id: int | None = None
|
||||||
self.metadata: Dict[str, Any] = {}
|
self.metadata: dict[str, Any] = {}
|
||||||
self.transport_source: Optional[str] = None
|
self.transport_source: str | None = None
|
||||||
self.transport_destination: Optional[str] = None
|
self.transport_destination: str | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@@ -183,8 +179,8 @@ class ImageRawFrame:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
image: bytes
|
image: bytes
|
||||||
size: Tuple[int, int]
|
size: tuple[int, int]
|
||||||
format: Optional[str]
|
format: str | None
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -242,7 +238,7 @@ class TTSAudioRawFrame(OutputAudioRawFrame):
|
|||||||
context_id: Unique identifier for the TTS context that generated this audio.
|
context_id: Unique identifier for the TTS context that generated this audio.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
context_id: Optional[str] = None
|
context_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -268,7 +264,7 @@ class URLImageRawFrame(OutputImageRawFrame):
|
|||||||
url: URL where the image can be downloaded from.
|
url: URL where the image can be downloaded from.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url: Optional[str] = None
|
url: str | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
pts = format_pts(self.pts)
|
pts = format_pts(self.pts)
|
||||||
@@ -287,7 +283,7 @@ class SpriteFrame(DataFrame):
|
|||||||
images: List of image frames that make up the sprite animation.
|
images: List of image frames that make up the sprite animation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
images: List[OutputImageRawFrame]
|
images: list[OutputImageRawFrame]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
pts = format_pts(self.pts)
|
pts = format_pts(self.pts)
|
||||||
@@ -312,7 +308,7 @@ class TextFrame(DataFrame):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
text: str
|
text: str
|
||||||
skip_tts: Optional[bool] = field(init=False)
|
skip_tts: bool | None = field(init=False)
|
||||||
# Whether any necessary inter-frame (leading/trailing) spaces are already
|
# Whether any necessary inter-frame (leading/trailing) spaces are already
|
||||||
# included in the text.
|
# included in the text.
|
||||||
# NOTE: Ideally this would be available at init time with a default value,
|
# NOTE: Ideally this would be available at init time with a default value,
|
||||||
@@ -357,7 +353,7 @@ class AggregatedTextFrame(TextFrame):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
aggregated_by: AggregationType | str
|
aggregated_by: AggregationType | str
|
||||||
context_id: Optional[str] = None
|
context_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -375,7 +371,7 @@ class TTSTextFrame(AggregatedTextFrame):
|
|||||||
context_id: Unique identifier for the TTS context that generated this text.
|
context_id: Unique identifier for the TTS context that generated this text.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
context_id: Optional[str] = None
|
context_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -396,8 +392,8 @@ class TranscriptionFrame(TextFrame):
|
|||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
timestamp: str
|
timestamp: str
|
||||||
language: Optional[Language] = None
|
language: Language | None = None
|
||||||
result: Optional[Any] = None
|
result: Any | None = None
|
||||||
finalized: bool = False
|
finalized: bool = False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -422,8 +418,8 @@ class InterimTranscriptionFrame(TextFrame):
|
|||||||
text: str
|
text: str
|
||||||
user_id: str
|
user_id: str
|
||||||
timestamp: str
|
timestamp: str
|
||||||
language: Optional[Language] = None
|
language: Language | None = None
|
||||||
result: Optional[Any] = None
|
result: Any | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}(user: {self.user_id}, text: [{self.text}], language: {self.language}, timestamp: {self.timestamp})"
|
return f"{self.name}(user: {self.user_id}, text: [{self.text}], language: {self.language}, timestamp: {self.timestamp})"
|
||||||
@@ -444,7 +440,7 @@ class TranslationFrame(TextFrame):
|
|||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
timestamp: str
|
timestamp: str
|
||||||
language: Optional[Language] = None
|
language: Language | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}(user: {self.user_id}, text: [{self.text}], language: {self.language}, timestamp: {self.timestamp})"
|
return f"{self.name}(user: {self.user_id}, text: [{self.text}], language: {self.language}, timestamp: {self.timestamp})"
|
||||||
@@ -472,7 +468,7 @@ class LLMContextFrame(Frame):
|
|||||||
context: The LLM context containing messages, tools, and configuration.
|
context: The LLM context containing messages, tools, and configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
context: "LLMContext"
|
context: LLMContext
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -489,7 +485,7 @@ class LLMThoughtStartFrame(ControlFrame):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
append_to_context: bool = False
|
append_to_context: bool = False
|
||||||
llm: Optional[str] = None
|
llm: str | None = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
@@ -567,8 +563,8 @@ class LLMMessagesAppendFrame(DataFrame):
|
|||||||
run_llm: Whether the context update should be sent to the LLM.
|
run_llm: Whether the context update should be sent to the LLM.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages: List[dict]
|
messages: list[dict]
|
||||||
run_llm: Optional[bool] = None
|
run_llm: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -583,8 +579,8 @@ class LLMMessagesUpdateFrame(DataFrame):
|
|||||||
run_llm: Whether the context update should be sent to the LLM.
|
run_llm: Whether the context update should be sent to the LLM.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages: List[dict]
|
messages: list[dict]
|
||||||
run_llm: Optional[bool] = None
|
run_llm: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -600,8 +596,8 @@ class LLMMessagesTransformFrame(DataFrame):
|
|||||||
run_llm: Whether the context update should be sent to the LLM.
|
run_llm: Whether the context update should be sent to the LLM.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
transform: Callable[[List["LLMContextMessage"]], List["LLMContextMessage"]]
|
transform: Callable[[list[LLMContextMessage]], list[LLMContextMessage]]
|
||||||
run_llm: Optional[bool] = None
|
run_llm: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -616,7 +612,7 @@ class LLMSetToolsFrame(DataFrame):
|
|||||||
tools: List of tool/function definitions for the LLM.
|
tools: List of tool/function definitions for the LLM.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tools: List[dict] | ToolsSchema | "NotGiven"
|
tools: list[dict] | ToolsSchema | NotGiven
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -668,8 +664,8 @@ class FunctionCallResultProperties:
|
|||||||
Only meaningful for async function calls (``cancel_on_interruption=False``).
|
Only meaningful for async function calls (``cancel_on_interruption=False``).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
run_llm: Optional[bool] = None
|
run_llm: bool | None = None
|
||||||
on_context_updated: Optional[Callable[[], Awaitable[None]]] = None
|
on_context_updated: Callable[[], Awaitable[None]] | None = None
|
||||||
is_final: bool = True
|
is_final: bool = True
|
||||||
|
|
||||||
|
|
||||||
@@ -694,8 +690,8 @@ class FunctionCallResultFrame(DataFrame, UninterruptibleFrame):
|
|||||||
tool_call_id: str
|
tool_call_id: str
|
||||||
arguments: Any
|
arguments: Any
|
||||||
result: Any
|
result: Any
|
||||||
run_llm: Optional[bool] = None
|
run_llm: bool | None = None
|
||||||
properties: Optional[FunctionCallResultProperties] = None
|
properties: FunctionCallResultProperties | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -711,7 +707,7 @@ class TTSSpeakFrame(DataFrame):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
text: str
|
text: str
|
||||||
append_to_context: Optional[bool] = None
|
append_to_context: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -752,8 +748,8 @@ class OutputDTMFFrame(DTMFFrame, DataFrame):
|
|||||||
:meth:`from_string` to build this from a string like ``"123#"``.
|
:meth:`from_string` to build this from a string like ``"123#"``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
button: Optional[KeypadEntry] = None
|
button: KeypadEntry | None = None
|
||||||
buttons: Optional[List[KeypadEntry]] = None
|
buttons: list[KeypadEntry] | None = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
@@ -766,7 +762,7 @@ class OutputDTMFFrame(DTMFFrame, DataFrame):
|
|||||||
return f"{self.name}(buttons: {self.to_string()})"
|
return f"{self.name}(buttons: {self.to_string()})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_string(cls, buttons: str, **kwargs) -> "OutputDTMFFrame":
|
def from_string(cls, buttons: str, **kwargs) -> OutputDTMFFrame:
|
||||||
"""Build an ``OutputDTMFFrame`` from a string of DTMF characters.
|
"""Build an ``OutputDTMFFrame`` from a string of DTMF characters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -820,7 +816,7 @@ class StartFrame(SystemFrame):
|
|||||||
enable_tracing: bool = False
|
enable_tracing: bool = False
|
||||||
enable_usage_metrics: bool = False
|
enable_usage_metrics: bool = False
|
||||||
report_only_initial_ttfb: bool = False
|
report_only_initial_ttfb: bool = False
|
||||||
tracing_context: Optional["TracingContext"] = None
|
tracing_context: TracingContext | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -834,7 +830,7 @@ class CancelFrame(SystemFrame):
|
|||||||
reason: Optional reason for pushing a cancel frame.
|
reason: Optional reason for pushing a cancel frame.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
reason: Optional[Any] = None
|
reason: Any | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}(reason: {self.reason})"
|
return f"{self.name}(reason: {self.reason})"
|
||||||
@@ -857,8 +853,8 @@ class ErrorFrame(SystemFrame):
|
|||||||
|
|
||||||
error: str
|
error: str
|
||||||
fatal: bool = False
|
fatal: bool = False
|
||||||
processor: Optional["FrameProcessor"] = None
|
processor: FrameProcessor | None = None
|
||||||
exception: Optional[Exception] = None
|
exception: Exception | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}(error: {self.error}, fatal: {self.fatal})"
|
return f"{self.name}(error: {self.error}, fatal: {self.fatal})"
|
||||||
@@ -891,7 +887,7 @@ class FrameProcessorPauseUrgentFrame(SystemFrame):
|
|||||||
processor: The frame processor to pause.
|
processor: The frame processor to pause.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
processor: "FrameProcessor"
|
processor: FrameProcessor
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -906,7 +902,7 @@ class FrameProcessorResumeUrgentFrame(SystemFrame):
|
|||||||
processor: The frame processor to resume.
|
processor: The frame processor to resume.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
processor: "FrameProcessor"
|
processor: FrameProcessor
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1050,7 +1046,7 @@ class MetricsFrame(SystemFrame):
|
|||||||
data: List of metrics data collected by the processor.
|
data: List of metrics data collected by the processor.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data: List[MetricsData]
|
data: list[MetricsData]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1156,12 +1152,12 @@ class UserImageRequestFrame(SystemFrame):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
text: Optional[str] = None
|
text: str | None = None
|
||||||
append_to_context: Optional[bool] = None
|
append_to_context: bool | None = None
|
||||||
video_source: Optional[str] = None
|
video_source: str | None = None
|
||||||
function_name: Optional[str] = None
|
function_name: str | None = None
|
||||||
tool_call_id: Optional[str] = None
|
tool_call_id: str | None = None
|
||||||
result_callback: Optional[Any] = None
|
result_callback: Any | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}(user: {self.user_id}, text: {self.text}, append_to_context: {self.append_to_context}, {self.video_source})"
|
return f"{self.name}(user: {self.user_id}, text: {self.text}, append_to_context: {self.append_to_context}, {self.video_source})"
|
||||||
@@ -1244,9 +1240,9 @@ class UserImageRawFrame(InputImageRawFrame):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
user_id: str = ""
|
user_id: str = ""
|
||||||
text: Optional[str] = None
|
text: str | None = None
|
||||||
append_to_context: Optional[bool] = None
|
append_to_context: bool | None = None
|
||||||
request: Optional[UserImageRequestFrame] = None
|
request: UserImageRequestFrame | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
pts = format_pts(self.pts)
|
pts = format_pts(self.pts)
|
||||||
@@ -1266,8 +1262,8 @@ class AssistantImageRawFrame(OutputImageRawFrame):
|
|||||||
original_mime_type: The MIME type of the original image data.
|
original_mime_type: The MIME type of the original image data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
original_data: Optional[bytes] = None
|
original_data: bytes | None = None
|
||||||
original_mime_type: Optional[str] = None
|
original_mime_type: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1296,8 +1292,8 @@ class OutputDTMFUrgentFrame(DTMFFrame, SystemFrame):
|
|||||||
:meth:`from_string` to build this from a string like ``"123#"``.
|
:meth:`from_string` to build this from a string like ``"123#"``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
button: Optional[KeypadEntry] = None
|
button: KeypadEntry | None = None
|
||||||
buttons: Optional[List[KeypadEntry]] = None
|
buttons: list[KeypadEntry] | None = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
@@ -1310,7 +1306,7 @@ class OutputDTMFUrgentFrame(DTMFFrame, SystemFrame):
|
|||||||
return f"{self.name}(buttons: {self.to_string()})"
|
return f"{self.name}(buttons: {self.to_string()})"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_string(cls, buttons: str, **kwargs) -> "OutputDTMFUrgentFrame":
|
def from_string(cls, buttons: str, **kwargs) -> OutputDTMFUrgentFrame:
|
||||||
"""Build an ``OutputDTMFUrgentFrame`` from a string of DTMF characters.
|
"""Build an ``OutputDTMFUrgentFrame`` from a string of DTMF characters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -1349,8 +1345,8 @@ class SpeechControlParamsFrame(SystemFrame):
|
|||||||
turn_params: Current turn-taking analysis parameters.
|
turn_params: Current turn-taking analysis parameters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
vad_params: Optional[VADParams] = None
|
vad_params: VADParams | None = None
|
||||||
turn_params: Optional[BaseTurnParams] = None
|
turn_params: BaseTurnParams | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1396,7 +1392,7 @@ class ServiceSwitcherRequestMetadataFrame(ControlFrame):
|
|||||||
service: The target service that should re-emit its metadata.
|
service: The target service that should re-emit its metadata.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
service: "FrameProcessor"
|
service: FrameProcessor
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -1444,7 +1440,7 @@ class EndTaskFrame(TaskFrame, UninterruptibleFrame):
|
|||||||
reason: Optional reason for pushing an end frame.
|
reason: Optional reason for pushing an end frame.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
reason: Optional[Any] = None
|
reason: Any | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}(reason: {self.reason})"
|
return f"{self.name}(reason: {self.reason})"
|
||||||
@@ -1475,7 +1471,7 @@ class CancelTaskFrame(TaskSystemFrame):
|
|||||||
reason: Optional reason for pushing a cancel frame.
|
reason: Optional reason for pushing a cancel frame.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
reason: Optional[Any] = None
|
reason: Any | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}(reason: {self.reason})"
|
return f"{self.name}(reason: {self.reason})"
|
||||||
@@ -1516,7 +1512,7 @@ class EndFrame(ControlFrame, UninterruptibleFrame):
|
|||||||
reason: Optional reason for pushing an end frame.
|
reason: Optional reason for pushing an end frame.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
reason: Optional[Any] = None
|
reason: Any | None = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}(reason: {self.reason})"
|
return f"{self.name}(reason: {self.reason})"
|
||||||
@@ -1598,7 +1594,7 @@ class FrameProcessorPauseFrame(ControlFrame):
|
|||||||
processor: The frame processor to pause.
|
processor: The frame processor to pause.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
processor: "FrameProcessor"
|
processor: FrameProcessor
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1613,7 +1609,7 @@ class FrameProcessorResumeFrame(ControlFrame):
|
|||||||
processor: The frame processor to resume.
|
processor: The frame processor to resume.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
processor: "FrameProcessor"
|
processor: FrameProcessor
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1624,7 +1620,7 @@ class LLMFullResponseStartFrame(ControlFrame):
|
|||||||
more TextFrames and a final LLMFullResponseEndFrame.
|
more TextFrames and a final LLMFullResponseEndFrame.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
skip_tts: Optional[bool] = field(init=False)
|
skip_tts: bool | None = field(init=False)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
@@ -1635,7 +1631,7 @@ class LLMFullResponseStartFrame(ControlFrame):
|
|||||||
class LLMFullResponseEndFrame(ControlFrame):
|
class LLMFullResponseEndFrame(ControlFrame):
|
||||||
"""Frame indicating the end of an LLM response."""
|
"""Frame indicating the end of an LLM response."""
|
||||||
|
|
||||||
skip_tts: Optional[bool] = field(init=False)
|
skip_tts: bool | None = field(init=False)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
@@ -1665,7 +1661,7 @@ class LLMSummarizeContextFrame(ControlFrame):
|
|||||||
is used.
|
is used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config: Optional["LLMContextSummaryConfig"] = None
|
config: LLMContextSummaryConfig | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1692,11 +1688,11 @@ class LLMContextSummaryRequestFrame(ControlFrame):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
request_id: str
|
request_id: str
|
||||||
context: "LLMContext"
|
context: LLMContext
|
||||||
min_messages_to_keep: int
|
min_messages_to_keep: int
|
||||||
target_context_tokens: int
|
target_context_tokens: int
|
||||||
summarization_prompt: str
|
summarization_prompt: str
|
||||||
summarization_timeout: Optional[float] = None
|
summarization_timeout: float | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1718,7 +1714,7 @@ class LLMContextSummaryResultFrame(ControlFrame, UninterruptibleFrame):
|
|||||||
request_id: str
|
request_id: str
|
||||||
summary: str
|
summary: str
|
||||||
last_summarized_index: int
|
last_summarized_index: int
|
||||||
error: Optional[str] = None
|
error: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1745,7 +1741,7 @@ class FunctionCallInProgressFrame(ControlFrame, UninterruptibleFrame):
|
|||||||
tool_call_id: str
|
tool_call_id: str
|
||||||
arguments: Any
|
arguments: Any
|
||||||
cancel_on_interruption: bool = False
|
cancel_on_interruption: bool = False
|
||||||
group_id: Optional[str] = None
|
group_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1781,7 +1777,7 @@ class TTSStartedFrame(ControlFrame):
|
|||||||
context_id: Unique identifier for this TTS context.
|
context_id: Unique identifier for this TTS context.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
context_id: Optional[str] = None
|
context_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1792,7 +1788,7 @@ class TTSStoppedFrame(ControlFrame):
|
|||||||
context_id: Unique identifier for this TTS context.
|
context_id: Unique identifier for this TTS context.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
context_id: Optional[str] = None
|
context_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1817,8 +1813,8 @@ class ServiceUpdateSettingsFrame(ControlFrame, UninterruptibleFrame):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
settings: Mapping[str, Any] = field(default_factory=dict)
|
settings: Mapping[str, Any] = field(default_factory=dict)
|
||||||
delta: Optional["ServiceSettings"] = None
|
delta: ServiceSettings | None = None
|
||||||
service: Optional["FrameProcessor"] = None
|
service: FrameProcessor | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1942,4 +1938,4 @@ class ManuallySwitchServiceFrame(ServiceSwitcherFrame):
|
|||||||
Handled by ServiceSwitcherStrategyManual to switch the active service.
|
Handled by ServiceSwitcherStrategyManual to switch the active service.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
service: "FrameProcessor"
|
service: FrameProcessor
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ collected throughout the pipeline, including timing, token usage, and
|
|||||||
processing statistics.
|
processing statistics.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +23,7 @@ class MetricsData(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
processor: str
|
processor: str
|
||||||
model: Optional[str] = None
|
model: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class TTFBMetricsData(MetricsData):
|
class TTFBMetricsData(MetricsData):
|
||||||
@@ -62,9 +60,9 @@ class LLMTokenUsage(BaseModel):
|
|||||||
prompt_tokens: int
|
prompt_tokens: int
|
||||||
completion_tokens: int
|
completion_tokens: int
|
||||||
total_tokens: int
|
total_tokens: int
|
||||||
cache_read_input_tokens: Optional[int] = None
|
cache_read_input_tokens: int | None = None
|
||||||
cache_creation_input_tokens: Optional[int] = None
|
cache_creation_input_tokens: int | None = None
|
||||||
reasoning_tokens: Optional[int] = None
|
reasoning_tokens: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class LLMUsageMetricsData(MetricsData):
|
class LLMUsageMetricsData(MetricsData):
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ for logging, debugging, analytics, and monitoring pipeline behavior.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from typing_extensions import TYPE_CHECKING
|
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame
|
from pipecat.frames.frames import Frame
|
||||||
from pipecat.utils.base_object import BaseObject
|
from pipecat.utils.base_object import BaseObject
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ understanding frame flow between processors.
|
|||||||
|
|
||||||
from dataclasses import fields, is_dataclass
|
from dataclasses import fields, is_dataclass
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from typing import Dict, Optional, Set, Tuple, Type, Union
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -75,10 +74,10 @@ class DebugLogObserver(BaseObserver):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
frame_types: Optional[
|
frame_types: tuple[type[Frame], ...]
|
||||||
Union[Tuple[Type[Frame], ...], Dict[Type[Frame], Optional[Tuple[Type, FrameEndpoint]]]]
|
| dict[type[Frame], tuple[type, FrameEndpoint] | None]
|
||||||
] = None,
|
| None = None,
|
||||||
exclude_fields: Optional[Set[str]] = None,
|
exclude_fields: set[str] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the debug log observer.
|
"""Initialize the debug log observer.
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ allowing developers to monitor performance metrics, token usage, and other
|
|||||||
statistics in real-time.
|
statistics in real-time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional, Set, Type
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.frames.frames import MetricsFrame
|
from pipecat.frames.frames import MetricsFrame
|
||||||
@@ -60,7 +58,7 @@ class MetricsLogObserver(BaseObserver):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
include_metrics: Optional[Set[Type[MetricsData]]] = None,
|
include_metrics: set[type[MetricsData]] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the metrics log observer.
|
"""Initialize the metrics log observer.
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ Example::
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict, List, Optional, Tuple, Type
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
@@ -84,7 +83,7 @@ class StartupTimingReport(BaseModel):
|
|||||||
|
|
||||||
start_time: float
|
start_time: float
|
||||||
total_duration_secs: float
|
total_duration_secs: float
|
||||||
processor_timings: List[ProcessorStartupTiming] = Field(default_factory=list)
|
processor_timings: list[ProcessorStartupTiming] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class TransportTimingReport(BaseModel):
|
class TransportTimingReport(BaseModel):
|
||||||
@@ -98,8 +97,8 @@ class TransportTimingReport(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
start_time: float
|
start_time: float
|
||||||
bot_connected_secs: Optional[float] = None
|
bot_connected_secs: float | None = None
|
||||||
client_connected_secs: Optional[float] = None
|
client_connected_secs: float | None = None
|
||||||
|
|
||||||
|
|
||||||
class StartupTimingObserver(BaseObserver):
|
class StartupTimingObserver(BaseObserver):
|
||||||
@@ -157,7 +156,7 @@ class StartupTimingObserver(BaseObserver):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
processor_types: Optional[Tuple[Type[FrameProcessor], ...]] = None,
|
processor_types: tuple[type[FrameProcessor], ...] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the startup timing observer.
|
"""Initialize the startup timing observer.
|
||||||
@@ -171,13 +170,13 @@ class StartupTimingObserver(BaseObserver):
|
|||||||
self._processor_types = processor_types
|
self._processor_types = processor_types
|
||||||
|
|
||||||
# Map processor ID -> arrival info.
|
# Map processor ID -> arrival info.
|
||||||
self._arrivals: Dict[int, _ArrivalInfo] = {}
|
self._arrivals: dict[int, _ArrivalInfo] = {}
|
||||||
|
|
||||||
# Collected timings in pipeline order.
|
# Collected timings in pipeline order.
|
||||||
self._timings: List[ProcessorStartupTiming] = []
|
self._timings: list[ProcessorStartupTiming] = []
|
||||||
|
|
||||||
# Lock onto the first StartFrame we see (by frame ID).
|
# Lock onto the first StartFrame we see (by frame ID).
|
||||||
self._start_frame_id: Optional[str] = None
|
self._start_frame_id: str | None = None
|
||||||
|
|
||||||
# Whether we've already emitted the startup timing report.
|
# Whether we've already emitted the startup timing report.
|
||||||
self._startup_timing_reported = False
|
self._startup_timing_reported = False
|
||||||
@@ -186,13 +185,13 @@ class StartupTimingObserver(BaseObserver):
|
|||||||
self._transport_timing_reported = False
|
self._transport_timing_reported = False
|
||||||
|
|
||||||
# Timestamp (ns) when we first see a StartFrame arrive at a processor.
|
# Timestamp (ns) when we first see a StartFrame arrive at a processor.
|
||||||
self._start_frame_arrival_ns: Optional[int] = None
|
self._start_frame_arrival_ns: int | None = None
|
||||||
|
|
||||||
# Bot connected timing (stored for inclusion in the transport report).
|
# Bot connected timing (stored for inclusion in the transport report).
|
||||||
self._bot_connected_secs: Optional[float] = None
|
self._bot_connected_secs: float | None = None
|
||||||
|
|
||||||
# Wall clock time when the StartFrame was first seen.
|
# Wall clock time when the StartFrame was first seen.
|
||||||
self._start_wall_clock: Optional[float] = None
|
self._start_wall_clock: float | None = None
|
||||||
|
|
||||||
self._register_event_handler("on_startup_timing_report")
|
self._register_event_handler("on_startup_timing_report")
|
||||||
self._register_event_handler("on_transport_timing_report")
|
self._register_event_handler("on_transport_timing_report")
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ is measured. Optionally collects per-service latency breakdown metrics
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ class TTFBBreakdownMetrics(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
processor: str
|
processor: str
|
||||||
model: Optional[str] = None
|
model: str | None = None
|
||||||
start_time: float
|
start_time: float
|
||||||
duration_secs: float
|
duration_secs: float
|
||||||
|
|
||||||
@@ -105,13 +104,13 @@ class LatencyBreakdown(BaseModel):
|
|||||||
this cycle. Empty if no function calls occurred.
|
this cycle. Empty if no function calls occurred.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ttfb: List[TTFBBreakdownMetrics] = Field(default_factory=list)
|
ttfb: list[TTFBBreakdownMetrics] = Field(default_factory=list)
|
||||||
text_aggregation: Optional[TextAggregationBreakdownMetrics] = None
|
text_aggregation: TextAggregationBreakdownMetrics | None = None
|
||||||
user_turn_start_time: Optional[float] = None
|
user_turn_start_time: float | None = None
|
||||||
user_turn_secs: Optional[float] = None
|
user_turn_secs: float | None = None
|
||||||
function_calls: List[FunctionCallMetrics] = Field(default_factory=list)
|
function_calls: list[FunctionCallMetrics] = Field(default_factory=list)
|
||||||
|
|
||||||
def chronological_events(self) -> List[str]:
|
def chronological_events(self) -> list[str]:
|
||||||
"""Return human-readable event labels sorted by start time.
|
"""Return human-readable event labels sorted by start time.
|
||||||
|
|
||||||
Collects all sub-metrics into a flat list, sorts by ``start_time``,
|
Collects all sub-metrics into a flat list, sorts by ``start_time``,
|
||||||
@@ -120,7 +119,7 @@ class LatencyBreakdown(BaseModel):
|
|||||||
Returns:
|
Returns:
|
||||||
List of formatted strings, one per event, in chronological order.
|
List of formatted strings, one per event, in chronological order.
|
||||||
"""
|
"""
|
||||||
events: List[tuple] = []
|
events: list[tuple] = []
|
||||||
|
|
||||||
if self.user_turn_start_time is not None and self.user_turn_secs is not None:
|
if self.user_turn_start_time is not None and self.user_turn_secs is not None:
|
||||||
events.append((self.user_turn_start_time, f"User turn: {self.user_turn_secs:.3f}s"))
|
events.append((self.user_turn_start_time, f"User turn: {self.user_turn_secs:.3f}s"))
|
||||||
@@ -181,12 +180,12 @@ class UserBotLatencyObserver(BaseObserver):
|
|||||||
**kwargs: Additional arguments passed to parent class.
|
**kwargs: Additional arguments passed to parent class.
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._user_stopped_time: Optional[float] = None
|
self._user_stopped_time: float | None = None
|
||||||
self._user_turn_start_time: Optional[float] = None
|
self._user_turn_start_time: float | None = None
|
||||||
self._user_turn: Optional[float] = None
|
self._user_turn: float | None = None
|
||||||
|
|
||||||
# First bot speech tracking
|
# First bot speech tracking
|
||||||
self._client_connected_time: Optional[float] = None
|
self._client_connected_time: float | None = None
|
||||||
self._first_bot_speech_measured: bool = False
|
self._first_bot_speech_measured: bool = False
|
||||||
|
|
||||||
# Frame deduplication (bounded deque + set pattern)
|
# Frame deduplication (bounded deque + set pattern)
|
||||||
@@ -194,10 +193,10 @@ class UserBotLatencyObserver(BaseObserver):
|
|||||||
self._frame_history: deque = deque(maxlen=max_frames)
|
self._frame_history: deque = deque(maxlen=max_frames)
|
||||||
|
|
||||||
# Per-cycle metric accumulators
|
# Per-cycle metric accumulators
|
||||||
self._ttfb: List[TTFBBreakdownMetrics] = []
|
self._ttfb: list[TTFBBreakdownMetrics] = []
|
||||||
self._text_aggregation: Optional[TextAggregationBreakdownMetrics] = None
|
self._text_aggregation: TextAggregationBreakdownMetrics | None = None
|
||||||
self._function_call_starts: Dict[str, tuple[str, float]] = {}
|
self._function_call_starts: dict[str, tuple[str, float]] = {}
|
||||||
self._function_call_metrics: List[FunctionCallMetrics] = []
|
self._function_call_metrics: list[FunctionCallMetrics] = []
|
||||||
|
|
||||||
self._register_event_handler("on_latency_measured")
|
self._register_event_handler("on_latency_measured")
|
||||||
self._register_event_handler("on_latency_breakdown")
|
self._register_event_handler("on_latency_breakdown")
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ tasks that manage the lifecycle and execution of frame processing pipelines.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from collections.abc import AsyncIterable, Iterable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import AsyncIterable, Iterable
|
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame
|
from pipecat.frames.frames import Frame
|
||||||
from pipecat.utils.base_object import BaseObject
|
from pipecat.utils.base_object import BaseObject
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
"""LLM switcher for switching between different LLMs at runtime, with different switching strategies."""
|
"""LLM switcher for switching between different LLMs at runtime, with different switching strategies."""
|
||||||
|
|
||||||
from typing import Any, List, Optional, Type
|
from typing import Any
|
||||||
|
|
||||||
from pipecat.adapters.schemas.direct_function import DirectFunction
|
from pipecat.adapters.schemas.direct_function import DirectFunction
|
||||||
from pipecat.pipeline.service_switcher import (
|
from pipecat.pipeline.service_switcher import (
|
||||||
@@ -28,8 +28,8 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
llms: List[LLMService],
|
llms: list[LLMService],
|
||||||
strategy_type: Type[StrategyType] = ServiceSwitcherStrategyManual,
|
strategy_type: type[StrategyType] = ServiceSwitcherStrategyManual,
|
||||||
):
|
):
|
||||||
"""Initialize the service switcher with a list of LLMs and a switching strategy.
|
"""Initialize the service switcher with a list of LLMs and a switching strategy.
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
|
|||||||
super().__init__(llms, strategy_type)
|
super().__init__(llms, strategy_type)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def llms(self) -> List[LLMService]:
|
def llms(self) -> list[LLMService]:
|
||||||
"""Get the list of LLMs managed by this switcher.
|
"""Get the list of LLMs managed by this switcher.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -58,7 +58,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
|
|||||||
"""
|
"""
|
||||||
return self.strategy.active_service
|
return self.strategy.active_service
|
||||||
|
|
||||||
async def run_inference(self, context: LLMContext, **kwargs) -> Optional[str]:
|
async def run_inference(self, context: LLMContext, **kwargs) -> str | None:
|
||||||
"""Run a one-shot, out-of-band (i.e. out-of-pipeline) inference with the given LLM context, using the currently active LLM.
|
"""Run a one-shot, out-of-band (i.e. out-of-pipeline) inference with the given LLM context, using the currently active LLM.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -75,11 +75,11 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
|
|||||||
|
|
||||||
def register_function(
|
def register_function(
|
||||||
self,
|
self,
|
||||||
function_name: Optional[str],
|
function_name: str | None,
|
||||||
handler: Any,
|
handler: Any,
|
||||||
*,
|
*,
|
||||||
cancel_on_interruption: bool = True,
|
cancel_on_interruption: bool = True,
|
||||||
timeout_secs: Optional[float] = None,
|
timeout_secs: float | None = None,
|
||||||
):
|
):
|
||||||
"""Register a function handler for LLM function calls, on all LLMs, active or not.
|
"""Register a function handler for LLM function calls, on all LLMs, active or not.
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
|
|||||||
handler: DirectFunction,
|
handler: DirectFunction,
|
||||||
*,
|
*,
|
||||||
cancel_on_interruption: bool = True,
|
cancel_on_interruption: bool = True,
|
||||||
timeout_secs: Optional[float] = None,
|
timeout_secs: float | None = None,
|
||||||
):
|
):
|
||||||
"""Register a direct function handler for LLM function calls, on all LLMs, active or not.
|
"""Register a direct function handler for LLM function calls, on all LLMs, active or not.
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ handling of pipeline lifecycle events.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ class ParallelPipeline(BasePipeline):
|
|||||||
self._pipelines = []
|
self._pipelines = []
|
||||||
|
|
||||||
self._seen_ids = set()
|
self._seen_ids = set()
|
||||||
self._frame_counter: Dict[int, int] = {}
|
self._frame_counter: dict[int, int] = {}
|
||||||
self._synchronizing: bool = False
|
self._synchronizing: bool = False
|
||||||
self._buffered_frames: list[tuple[Frame, FrameDirection]] = []
|
self._buffered_frames: list[tuple[Frame, FrameDirection]] = []
|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ class ParallelPipeline(BasePipeline):
|
|||||||
return self._pipelines
|
return self._pipelines
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry_processors(self) -> List["FrameProcessor"]:
|
def entry_processors(self) -> list["FrameProcessor"]:
|
||||||
"""Return the list of entry processors for this processor.
|
"""Return the list of entry processors for this processor.
|
||||||
|
|
||||||
Entry processors are the first processors in a compound processor
|
Entry processors are the first processors in a compound processor
|
||||||
@@ -106,7 +105,7 @@ class ParallelPipeline(BasePipeline):
|
|||||||
"""
|
"""
|
||||||
return self._pipelines
|
return self._pipelines
|
||||||
|
|
||||||
def processors_with_metrics(self) -> List[FrameProcessor]:
|
def processors_with_metrics(self) -> list[FrameProcessor]:
|
||||||
"""Collect processors that can generate metrics from all parallel branches.
|
"""Collect processors that can generate metrics from all parallel branches.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ in sequence and manages frame flow between them, along with helper classes
|
|||||||
for pipeline source and sink operations.
|
for pipeline source and sink operations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Callable, Coroutine, List, Optional
|
from collections.abc import Callable, Coroutine
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame
|
from pipecat.frames.frames import Frame
|
||||||
from pipecat.pipeline.base_pipeline import BasePipeline
|
from pipecat.pipeline.base_pipeline import BasePipeline
|
||||||
@@ -98,10 +98,10 @@ class Pipeline(BasePipeline):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
processors: List[FrameProcessor],
|
processors: list[FrameProcessor],
|
||||||
*,
|
*,
|
||||||
source: Optional[FrameProcessor] = None,
|
source: FrameProcessor | None = None,
|
||||||
sink: Optional[FrameProcessor] = None,
|
sink: FrameProcessor | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the pipeline with a list of processors.
|
"""Initialize the pipeline with a list of processors.
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ class Pipeline(BasePipeline):
|
|||||||
# downstream outside of the pipeline.
|
# downstream outside of the pipeline.
|
||||||
self._source = source or PipelineSource(self.push_frame, name=f"{self}::Source")
|
self._source = source or PipelineSource(self.push_frame, name=f"{self}::Source")
|
||||||
self._sink = sink or PipelineSink(self.push_frame, name=f"{self}::Sink")
|
self._sink = sink or PipelineSink(self.push_frame, name=f"{self}::Sink")
|
||||||
self._processors: List[FrameProcessor] = [self._source] + processors + [self._sink]
|
self._processors: list[FrameProcessor] = [self._source] + processors + [self._sink]
|
||||||
|
|
||||||
self._link_processors()
|
self._link_processors()
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ class Pipeline(BasePipeline):
|
|||||||
return self._processors
|
return self._processors
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry_processors(self) -> List["FrameProcessor"]:
|
def entry_processors(self) -> list["FrameProcessor"]:
|
||||||
"""Return the list of entry processors for this processor.
|
"""Return the list of entry processors for this processor.
|
||||||
|
|
||||||
Entry processors are the first processors in a compound processor
|
Entry processors are the first processors in a compound processor
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ management.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import gc
|
import gc
|
||||||
import signal
|
import signal
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -34,11 +33,11 @@ class PipelineRunner(BaseObject):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: Optional[str] = None,
|
name: str | None = None,
|
||||||
handle_sigint: bool = True,
|
handle_sigint: bool = True,
|
||||||
handle_sigterm: bool = False,
|
handle_sigterm: bool = False,
|
||||||
force_gc: bool = False,
|
force_gc: bool = False,
|
||||||
loop: Optional[asyncio.AbstractEventLoop] = None,
|
loop: asyncio.AbstractEventLoop | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the pipeline runner.
|
"""Initialize the pipeline runner.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
"""Service switcher for switching between different services at runtime, with different switching strategies."""
|
"""Service switcher for switching between different services at runtime, with different switching strategies."""
|
||||||
|
|
||||||
from typing import Any, Generic, List, Optional, Type, TypeVar
|
from typing import Any, Generic, TypeVar
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class ServiceSwitcherStrategy(BaseObject):
|
|||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, services: List[FrameProcessor]):
|
def __init__(self, services: list[FrameProcessor]):
|
||||||
"""Initialize the service switcher strategy with a list of services.
|
"""Initialize the service switcher strategy with a list of services.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@@ -62,7 +62,7 @@ class ServiceSwitcherStrategy(BaseObject):
|
|||||||
self._register_event_handler("on_service_switched")
|
self._register_event_handler("on_service_switched")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def services(self) -> List[FrameProcessor]:
|
def services(self) -> list[FrameProcessor]:
|
||||||
"""Return the list of available services."""
|
"""Return the list of available services."""
|
||||||
return self._services
|
return self._services
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class ServiceSwitcherStrategy(BaseObject):
|
|||||||
|
|
||||||
async def handle_frame(
|
async def handle_frame(
|
||||||
self, frame: ServiceSwitcherFrame, direction: FrameDirection
|
self, frame: ServiceSwitcherFrame, direction: FrameDirection
|
||||||
) -> Optional[FrameProcessor]:
|
) -> FrameProcessor | None:
|
||||||
"""Handle a frame that controls service switching.
|
"""Handle a frame that controls service switching.
|
||||||
|
|
||||||
The base implementation returns ``None`` for all frames. Subclasses
|
The base implementation returns ``None`` for all frames. Subclasses
|
||||||
@@ -88,7 +88,7 @@ class ServiceSwitcherStrategy(BaseObject):
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def handle_error(self, error: ErrorFrame) -> Optional[FrameProcessor]:
|
async def handle_error(self, error: ErrorFrame) -> FrameProcessor | None:
|
||||||
"""Handle an error from the active service.
|
"""Handle an error from the active service.
|
||||||
|
|
||||||
Called by ``ServiceSwitcher`` when a non-fatal ``ErrorFrame`` is pushed
|
Called by ``ServiceSwitcher`` when a non-fatal ``ErrorFrame`` is pushed
|
||||||
@@ -103,7 +103,7 @@ class ServiceSwitcherStrategy(BaseObject):
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _set_active_if_available(self, service: FrameProcessor) -> Optional[FrameProcessor]:
|
async def _set_active_if_available(self, service: FrameProcessor) -> FrameProcessor | None:
|
||||||
"""Set the active service to the given one, if it is in the list of available services.
|
"""Set the active service to the given one, if it is in the list of available services.
|
||||||
|
|
||||||
If it's not in the list, the request is ignored, as it may have been
|
If it's not in the list, the request is ignored, as it may have been
|
||||||
@@ -139,7 +139,7 @@ class ServiceSwitcherStrategyManual(ServiceSwitcherStrategy):
|
|||||||
|
|
||||||
async def handle_frame(
|
async def handle_frame(
|
||||||
self, frame: ServiceSwitcherFrame, direction: FrameDirection
|
self, frame: ServiceSwitcherFrame, direction: FrameDirection
|
||||||
) -> Optional[FrameProcessor]:
|
) -> FrameProcessor | None:
|
||||||
"""Handle a frame that controls service switching.
|
"""Handle a frame that controls service switching.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -179,7 +179,7 @@ class ServiceSwitcherStrategyFailover(ServiceSwitcherStrategyManual):
|
|||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def handle_error(self, error: ErrorFrame) -> Optional[FrameProcessor]:
|
async def handle_error(self, error: ErrorFrame) -> FrameProcessor | None:
|
||||||
"""Handle an error from the active service by failing over.
|
"""Handle an error from the active service by failing over.
|
||||||
|
|
||||||
Switches to the next service in the list. The failed service remains
|
Switches to the next service in the list. The failed service remains
|
||||||
@@ -223,8 +223,8 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
services: List[FrameProcessor],
|
services: list[FrameProcessor],
|
||||||
strategy_type: Type[StrategyType] = ServiceSwitcherStrategyManual,
|
strategy_type: type[StrategyType] = ServiceSwitcherStrategyManual,
|
||||||
):
|
):
|
||||||
"""Initialize the service switcher with a list of services and a switching strategy.
|
"""Initialize the service switcher with a list of services and a switching strategy.
|
||||||
|
|
||||||
@@ -244,14 +244,14 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
|
|||||||
return self._strategy
|
return self._strategy
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def services(self) -> List[FrameProcessor]:
|
def services(self) -> list[FrameProcessor]:
|
||||||
"""Return the list of available services."""
|
"""Return the list of available services."""
|
||||||
return self._services
|
return self._services
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _make_pipeline_definitions(
|
def _make_pipeline_definitions(
|
||||||
services: List[FrameProcessor], strategy: ServiceSwitcherStrategy
|
services: list[FrameProcessor], strategy: ServiceSwitcherStrategy
|
||||||
) -> List[Any]:
|
) -> list[Any]:
|
||||||
pipelines = []
|
pipelines = []
|
||||||
for service in services:
|
for service in services:
|
||||||
pipelines.append(ServiceSwitcher._make_pipeline_definition(service, strategy))
|
pipelines.append(ServiceSwitcher._make_pipeline_definition(service, strategy))
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import asyncio
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -215,7 +214,7 @@ class SyncParallelPipeline(BasePipeline):
|
|||||||
return self._pipelines
|
return self._pipelines
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry_processors(self) -> List["FrameProcessor"]:
|
def entry_processors(self) -> list["FrameProcessor"]:
|
||||||
"""Return the list of entry processors for this processor.
|
"""Return the list of entry processors for this processor.
|
||||||
|
|
||||||
Entry processors are the first processors in a compound processor
|
Entry processors are the first processors in a compound processor
|
||||||
@@ -228,7 +227,7 @@ class SyncParallelPipeline(BasePipeline):
|
|||||||
"""
|
"""
|
||||||
return [s["processor"] for s in self._sources]
|
return [s["processor"] for s in self._sources]
|
||||||
|
|
||||||
def processors_with_metrics(self) -> List[FrameProcessor]:
|
def processors_with_metrics(self) -> list[FrameProcessor]:
|
||||||
"""Collect processors that can generate metrics from all parallel pipelines.
|
"""Collect processors that can generate metrics from all parallel pipelines.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ including heartbeats, idle detection, and observer integration.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
|
from collections.abc import AsyncIterable, Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, AsyncIterable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
@@ -74,7 +75,7 @@ class IdleFrameObserver(BaseObserver):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, idle_event: asyncio.Event, idle_timeout_frames: Tuple[Type[Frame], ...]):
|
def __init__(self, *, idle_event: asyncio.Event, idle_timeout_frames: tuple[type[Frame], ...]):
|
||||||
"""Initialize the observer.
|
"""Initialize the observer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -134,7 +135,7 @@ class PipelineParams(BaseModel):
|
|||||||
heartbeats_monitor_secs: float = HEARTBEAT_MONITOR_SECS
|
heartbeats_monitor_secs: float = HEARTBEAT_MONITOR_SECS
|
||||||
report_only_initial_ttfb: bool = False
|
report_only_initial_ttfb: bool = False
|
||||||
send_initial_empty_metrics: bool = True
|
send_initial_empty_metrics: bool = True
|
||||||
start_metadata: Dict[str, Any] = Field(default_factory=dict)
|
start_metadata: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
class PipelineTask(BasePipelineTask):
|
class PipelineTask(BasePipelineTask):
|
||||||
@@ -190,22 +191,22 @@ class PipelineTask(BasePipelineTask):
|
|||||||
self,
|
self,
|
||||||
pipeline: BasePipeline,
|
pipeline: BasePipeline,
|
||||||
*,
|
*,
|
||||||
params: Optional[PipelineParams] = None,
|
params: PipelineParams | None = None,
|
||||||
additional_span_attributes: Optional[dict] = None,
|
additional_span_attributes: dict | None = None,
|
||||||
cancel_on_idle_timeout: bool = True,
|
cancel_on_idle_timeout: bool = True,
|
||||||
cancel_timeout_secs: float = CANCEL_TIMEOUT_SECS,
|
cancel_timeout_secs: float = CANCEL_TIMEOUT_SECS,
|
||||||
check_dangling_tasks: bool = True,
|
check_dangling_tasks: bool = True,
|
||||||
clock: Optional[BaseClock] = None,
|
clock: BaseClock | None = None,
|
||||||
conversation_id: Optional[str] = None,
|
conversation_id: str | None = None,
|
||||||
enable_tracing: bool = False,
|
enable_tracing: bool = False,
|
||||||
enable_turn_tracking: bool = True,
|
enable_turn_tracking: bool = True,
|
||||||
enable_rtvi: bool = True,
|
enable_rtvi: bool = True,
|
||||||
idle_timeout_frames: Tuple[Type[Frame], ...] = (BotSpeakingFrame, UserSpeakingFrame),
|
idle_timeout_frames: tuple[type[Frame], ...] = (BotSpeakingFrame, UserSpeakingFrame),
|
||||||
idle_timeout_secs: Optional[float] = IDLE_TIMEOUT_SECS,
|
idle_timeout_secs: float | None = IDLE_TIMEOUT_SECS,
|
||||||
observers: Optional[List[BaseObserver]] = None,
|
observers: list[BaseObserver] | None = None,
|
||||||
rtvi_processor: Optional[RTVIProcessor] = None,
|
rtvi_processor: RTVIProcessor | None = None,
|
||||||
rtvi_observer_params: Optional[RTVIObserverParams] = None,
|
rtvi_observer_params: RTVIObserverParams | None = None,
|
||||||
task_manager: Optional[BaseTaskManager] = None,
|
task_manager: BaseTaskManager | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the PipelineTask.
|
"""Initialize the PipelineTask.
|
||||||
|
|
||||||
@@ -246,10 +247,10 @@ class PipelineTask(BasePipelineTask):
|
|||||||
self._enable_turn_tracking = enable_turn_tracking
|
self._enable_turn_tracking = enable_turn_tracking
|
||||||
self._idle_timeout_secs = idle_timeout_secs
|
self._idle_timeout_secs = idle_timeout_secs
|
||||||
observers = observers or []
|
observers = observers or []
|
||||||
self._turn_tracking_observer: Optional[TurnTrackingObserver] = None
|
self._turn_tracking_observer: TurnTrackingObserver | None = None
|
||||||
self._user_bot_latency_observer: Optional[UserBotLatencyObserver] = None
|
self._user_bot_latency_observer: UserBotLatencyObserver | None = None
|
||||||
self._turn_trace_observer: Optional[TurnTraceObserver] = None
|
self._turn_trace_observer: TurnTraceObserver | None = None
|
||||||
self._tracing_context: Optional[TracingContext] = None
|
self._tracing_context: TracingContext | None = None
|
||||||
if self._enable_turn_tracking:
|
if self._enable_turn_tracking:
|
||||||
self._turn_tracking_observer = TurnTrackingObserver()
|
self._turn_tracking_observer = TurnTrackingObserver()
|
||||||
observers.append(self._turn_tracking_observer)
|
observers.append(self._turn_tracking_observer)
|
||||||
@@ -278,13 +279,13 @@ class PipelineTask(BasePipelineTask):
|
|||||||
|
|
||||||
# This queue is the queue used to push frames to the pipeline.
|
# This queue is the queue used to push frames to the pipeline.
|
||||||
self._push_queue = asyncio.Queue()
|
self._push_queue = asyncio.Queue()
|
||||||
self._process_push_task: Optional[asyncio.Task] = None
|
self._process_push_task: asyncio.Task | None = None
|
||||||
|
|
||||||
# This is the heartbeat queue. When a heartbeat frame is received in the
|
# This is the heartbeat queue. When a heartbeat frame is received in the
|
||||||
# down queue we add it to the heartbeat queue for processing.
|
# down queue we add it to the heartbeat queue for processing.
|
||||||
self._heartbeat_queue = asyncio.Queue()
|
self._heartbeat_queue = asyncio.Queue()
|
||||||
self._heartbeat_push_task: Optional[asyncio.Task] = None
|
self._heartbeat_push_task: asyncio.Task | None = None
|
||||||
self._heartbeat_monitor_task: Optional[asyncio.Task] = None
|
self._heartbeat_monitor_task: asyncio.Task | None = None
|
||||||
|
|
||||||
# RTVI support
|
# RTVI support
|
||||||
self._rtvi = None
|
self._rtvi = None
|
||||||
@@ -323,7 +324,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
# processor we consider the pipeline is not idle. We use an observer
|
# processor we consider the pipeline is not idle. We use an observer
|
||||||
# which will be listening any part of the pipeline.
|
# which will be listening any part of the pipeline.
|
||||||
self._idle_event = asyncio.Event()
|
self._idle_event = asyncio.Event()
|
||||||
self._idle_monitor_task: Optional[asyncio.Task] = None
|
self._idle_monitor_task: asyncio.Task | None = None
|
||||||
if self._idle_timeout_secs:
|
if self._idle_timeout_secs:
|
||||||
idle_frame_observer = IdleFrameObserver(
|
idle_frame_observer = IdleFrameObserver(
|
||||||
idle_event=self._idle_event,
|
idle_event=self._idle_event,
|
||||||
@@ -365,8 +366,8 @@ class PipelineTask(BasePipelineTask):
|
|||||||
# in. This is mainly for efficiency reason because each event handler
|
# in. This is mainly for efficiency reason because each event handler
|
||||||
# creates a task and most likely you only care about one or two frame
|
# creates a task and most likely you only care about one or two frame
|
||||||
# types.
|
# types.
|
||||||
self._reached_upstream_types: Set[Type[Frame]] = set()
|
self._reached_upstream_types: set[type[Frame]] = set()
|
||||||
self._reached_downstream_types: Set[Type[Frame]] = set()
|
self._reached_downstream_types: set[type[Frame]] = set()
|
||||||
self._register_event_handler("on_frame_reached_upstream")
|
self._register_event_handler("on_frame_reached_upstream")
|
||||||
self._register_event_handler("on_frame_reached_downstream")
|
self._register_event_handler("on_frame_reached_downstream")
|
||||||
self._register_event_handler("on_idle_timeout")
|
self._register_event_handler("on_idle_timeout")
|
||||||
@@ -395,7 +396,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
return self._pipeline
|
return self._pipeline
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def turn_tracking_observer(self) -> Optional[TurnTrackingObserver]:
|
def turn_tracking_observer(self) -> TurnTrackingObserver | None:
|
||||||
"""Get the turn tracking observer if enabled.
|
"""Get the turn tracking observer if enabled.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -404,7 +405,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
return self._turn_tracking_observer
|
return self._turn_tracking_observer
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def turn_trace_observer(self) -> Optional[TurnTraceObserver]:
|
def turn_trace_observer(self) -> TurnTraceObserver | None:
|
||||||
"""Get the turn trace observer if enabled.
|
"""Get the turn trace observer if enabled.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -424,7 +425,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
return self._rtvi
|
return self._rtvi
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reached_upstream_types(self) -> Tuple[Type[Frame], ...]:
|
def reached_upstream_types(self) -> tuple[type[Frame], ...]:
|
||||||
"""Get the currently configured upstream frame type filters.
|
"""Get the currently configured upstream frame type filters.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -433,7 +434,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
return tuple(self._reached_upstream_types)
|
return tuple(self._reached_upstream_types)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reached_downstream_types(self) -> Tuple[Type[Frame], ...]:
|
def reached_downstream_types(self) -> tuple[type[Frame], ...]:
|
||||||
"""Get the currently configured downstream frame type filters.
|
"""Get the currently configured downstream frame type filters.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -457,7 +458,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
"""
|
"""
|
||||||
await self._observer.remove_observer(observer)
|
await self._observer.remove_observer(observer)
|
||||||
|
|
||||||
def set_reached_upstream_filter(self, types: Tuple[Type[Frame], ...]):
|
def set_reached_upstream_filter(self, types: tuple[type[Frame], ...]):
|
||||||
"""Set which frame types trigger the on_frame_reached_upstream event.
|
"""Set which frame types trigger the on_frame_reached_upstream event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -465,7 +466,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
"""
|
"""
|
||||||
self._reached_upstream_types = set(types)
|
self._reached_upstream_types = set(types)
|
||||||
|
|
||||||
def set_reached_downstream_filter(self, types: Tuple[Type[Frame], ...]):
|
def set_reached_downstream_filter(self, types: tuple[type[Frame], ...]):
|
||||||
"""Set which frame types trigger the on_frame_reached_downstream event.
|
"""Set which frame types trigger the on_frame_reached_downstream event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -473,7 +474,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
"""
|
"""
|
||||||
self._reached_downstream_types = set(types)
|
self._reached_downstream_types = set(types)
|
||||||
|
|
||||||
def add_reached_upstream_filter(self, types: Tuple[Type[Frame], ...]):
|
def add_reached_upstream_filter(self, types: tuple[type[Frame], ...]):
|
||||||
"""Add frame types to trigger the on_frame_reached_upstream event.
|
"""Add frame types to trigger the on_frame_reached_upstream event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -481,7 +482,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
"""
|
"""
|
||||||
self._reached_upstream_types.update(types)
|
self._reached_upstream_types.update(types)
|
||||||
|
|
||||||
def add_reached_downstream_filter(self, types: Tuple[Type[Frame], ...]):
|
def add_reached_downstream_filter(self, types: tuple[type[Frame], ...]):
|
||||||
"""Add frame types to trigger the on_frame_reached_downstream event.
|
"""Add frame types to trigger the on_frame_reached_downstream event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -509,7 +510,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
logger.debug(f"Task {self} scheduled to stop when done")
|
logger.debug(f"Task {self} scheduled to stop when done")
|
||||||
await self.queue_frame(EndFrame())
|
await self.queue_frame(EndFrame())
|
||||||
|
|
||||||
async def cancel(self, *, reason: Optional[str] = None):
|
async def cancel(self, *, reason: str | None = None):
|
||||||
"""Request the running pipeline to cancel.
|
"""Request the running pipeline to cancel.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -597,7 +598,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
for frame in frames:
|
for frame in frames:
|
||||||
await self.queue_frame(frame, direction)
|
await self.queue_frame(frame, direction)
|
||||||
|
|
||||||
async def _cancel(self, *, reason: Optional[str] = None):
|
async def _cancel(self, *, reason: str | None = None):
|
||||||
"""Internal cancellation logic for the pipeline task.
|
"""Internal cancellation logic for the pipeline task.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -685,7 +686,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
self._pipeline_end_event.wait(), timeout=self._cancel_timeout_secs
|
self._pipeline_end_event.wait(), timeout=self._cancel_timeout_secs
|
||||||
)
|
)
|
||||||
logger.debug(f"{self}: {frame} reached the end of the pipeline.")
|
logger.debug(f"{self}: {frame} reached the end of the pipeline.")
|
||||||
except asyncio.TimeoutError:
|
except TimeoutError:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{self}: timeout waiting for {frame} to reach the end of the pipeline (being blocked somewhere?)."
|
f"{self}: timeout waiting for {frame} to reach the end of the pipeline (being blocked somewhere?)."
|
||||||
)
|
)
|
||||||
@@ -895,7 +896,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
process_time = (self._clock.get_time() - frame.timestamp) / 1_000_000_000
|
process_time = (self._clock.get_time() - frame.timestamp) / 1_000_000_000
|
||||||
logger.trace(f"{self}: heartbeat frame processed in {process_time} seconds")
|
logger.trace(f"{self}: heartbeat frame processed in {process_time} seconds")
|
||||||
self._heartbeat_queue.task_done()
|
self._heartbeat_queue.task_done()
|
||||||
except asyncio.TimeoutError:
|
except TimeoutError:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{self}: heartbeat frame not received for more than {wait_time} seconds"
|
f"{self}: heartbeat frame not received for more than {wait_time} seconds"
|
||||||
)
|
)
|
||||||
@@ -913,7 +914,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self._idle_event.wait(), timeout=self._idle_timeout_secs)
|
await asyncio.wait_for(self._idle_event.wait(), timeout=self._idle_timeout_secs)
|
||||||
self._idle_event.clear()
|
self._idle_event.clear()
|
||||||
except asyncio.TimeoutError:
|
except TimeoutError:
|
||||||
running = await self._idle_timeout_detected()
|
running = await self._idle_timeout_detected()
|
||||||
|
|
||||||
async def _idle_timeout_detected(self) -> bool:
|
async def _idle_timeout_detected(self) -> bool:
|
||||||
@@ -972,7 +973,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
if tasks:
|
if tasks:
|
||||||
logger.warning(f"{self} dangling tasks detected: {tasks}")
|
logger.warning(f"{self} dangling tasks detected: {tasks}")
|
||||||
|
|
||||||
def _create_start_metadata(self) -> Dict[str, Any]:
|
def _create_start_metadata(self) -> dict[str, Any]:
|
||||||
"""Build and return start metadata including user-provided values."""
|
"""Build and return start metadata including user-provided values."""
|
||||||
start_metadata = {}
|
start_metadata = {}
|
||||||
|
|
||||||
@@ -981,7 +982,7 @@ class PipelineTask(BasePipelineTask):
|
|||||||
|
|
||||||
return start_metadata
|
return start_metadata
|
||||||
|
|
||||||
def _find_processor(self, processor: FrameProcessor, processor_type: Type[T]) -> Optional[T]:
|
def _find_processor(self, processor: FrameProcessor, processor_type: type[T]) -> T | None:
|
||||||
"""Recursively find a processor of the given type in the pipeline."""
|
"""Recursively find a processor of the given type in the pipeline."""
|
||||||
if isinstance(processor, processor_type):
|
if isinstance(processor, processor_type):
|
||||||
return processor
|
return processor
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ the main pipeline execution.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
from attr import dataclass
|
from attr import dataclass
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ class TaskObserver(BaseObserver):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
observers: Optional[List[BaseObserver]] = None,
|
observers: list[BaseObserver] | None = None,
|
||||||
task_manager: BaseTaskManager,
|
task_manager: BaseTaskManager,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
@@ -75,7 +75,7 @@ class TaskObserver(BaseObserver):
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._observers = observers or []
|
self._observers = observers or []
|
||||||
self._task_manager = task_manager
|
self._task_manager = task_manager
|
||||||
self._proxies: Optional[Dict[BaseObserver, Proxy]] = (
|
self._proxies: dict[BaseObserver, Proxy] | None = (
|
||||||
None # Becomes a dict after start() is called
|
None # Becomes a dict after start() is called
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ class TaskObserver(BaseObserver):
|
|||||||
proxy = Proxy(queue=queue, task=task, observer=observer)
|
proxy = Proxy(queue=queue, task=task, observer=observer)
|
||||||
return proxy
|
return proxy
|
||||||
|
|
||||||
def _create_proxies(self, observers: List[BaseObserver]) -> Dict[BaseObserver, Proxy]:
|
def _create_proxies(self, observers: list[BaseObserver]) -> dict[BaseObserver, Proxy]:
|
||||||
"""Create proxies for all observers."""
|
"""Create proxies for all observers."""
|
||||||
proxies = {}
|
proxies = {}
|
||||||
for observer in observers:
|
for observer in observers:
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ for downstream processing by LLM context aggregators.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pipecat.audio.dtmf.types import KeypadEntry
|
from pipecat.audio.dtmf.types import KeypadEntry
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
@@ -62,7 +61,7 @@ class DTMFAggregator(FrameProcessor):
|
|||||||
self._prefix = prefix
|
self._prefix = prefix
|
||||||
|
|
||||||
self._digit_event = asyncio.Event()
|
self._digit_event = asyncio.Event()
|
||||||
self._aggregation_task: Optional[asyncio.Task] = None
|
self._aggregation_task: asyncio.Task | None = None
|
||||||
|
|
||||||
async def cleanup(self) -> None:
|
async def cleanup(self) -> None:
|
||||||
"""Clean up resources."""
|
"""Clean up resources."""
|
||||||
@@ -130,7 +129,7 @@ class DTMFAggregator(FrameProcessor):
|
|||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self._digit_event.wait(), timeout=self._idle_timeout)
|
await asyncio.wait_for(self._digit_event.wait(), timeout=self._idle_timeout)
|
||||||
self._digit_event.clear()
|
self._digit_event.clear()
|
||||||
except asyncio.TimeoutError:
|
except TimeoutError:
|
||||||
if self._aggregation:
|
if self._aggregation:
|
||||||
await self._flush_aggregation()
|
await self._flush_aggregation()
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ custom gate open/close functions, allowing for conditional frame buffering
|
|||||||
and release in frame processing pipelines.
|
and release in frame processing pipelines.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Tuple
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame, SystemFrame
|
from pipecat.frames.frames import Frame, SystemFrame
|
||||||
@@ -48,7 +46,7 @@ class GatedAggregator(FrameProcessor):
|
|||||||
self._gate_close_fn = gate_close_fn
|
self._gate_close_fn = gate_close_fn
|
||||||
self._gate_open = start_open
|
self._gate_open = start_open
|
||||||
self._direction = direction
|
self._direction = direction
|
||||||
self._accumulator: List[Tuple[Frame, FrameDirection]] = []
|
self._accumulator: list[tuple[Frame, FrameDirection]] = []
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
"""Process incoming frames with gated accumulation logic.
|
"""Process incoming frames with gated accumulation logic.
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ import base64
|
|||||||
import copy
|
import copy
|
||||||
import io
|
import io
|
||||||
import wave
|
import wave
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Callable, List, Optional, TypeAlias, Union
|
from typing import Any, TypeAlias
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from openai._types import NOT_GIVEN as OPEN_AI_NOT_GIVEN
|
from openai._types import NOT_GIVEN as OPEN_AI_NOT_GIVEN
|
||||||
@@ -57,7 +58,7 @@ class LLMSpecificMessage:
|
|||||||
message: Any
|
message: Any
|
||||||
|
|
||||||
|
|
||||||
LLMContextMessage: TypeAlias = Union[LLMStandardMessage, LLMSpecificMessage]
|
LLMContextMessage: TypeAlias = LLMStandardMessage | LLMSpecificMessage
|
||||||
|
|
||||||
|
|
||||||
class LLMContext:
|
class LLMContext:
|
||||||
@@ -70,7 +71,7 @@ class LLMContext:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
messages: Optional[List[LLMContextMessage]] = None,
|
messages: list[LLMContextMessage] | None = None,
|
||||||
tools: ToolsSchema | NotGiven = NOT_GIVEN,
|
tools: ToolsSchema | NotGiven = NOT_GIVEN,
|
||||||
tool_choice: LLMContextToolChoice | NotGiven = NOT_GIVEN,
|
tool_choice: LLMContextToolChoice | NotGiven = NOT_GIVEN,
|
||||||
):
|
):
|
||||||
@@ -81,7 +82,7 @@ class LLMContext:
|
|||||||
tools: Available tools for the LLM to use.
|
tools: Available tools for the LLM to use.
|
||||||
tool_choice: Tool selection strategy for the LLM.
|
tool_choice: Tool selection strategy for the LLM.
|
||||||
"""
|
"""
|
||||||
self._messages: List[LLMContextMessage] = messages if messages else []
|
self._messages: list[LLMContextMessage] = messages if messages else []
|
||||||
self._tools: ToolsSchema | NotGiven = LLMContext._normalize_and_validate_tools(tools)
|
self._tools: ToolsSchema | NotGiven = LLMContext._normalize_and_validate_tools(tools)
|
||||||
self._tool_choice: LLMContextToolChoice | NotGiven = tool_choice
|
self._tool_choice: LLMContextToolChoice | NotGiven = tool_choice
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ class LLMContext:
|
|||||||
*,
|
*,
|
||||||
role: str = "user",
|
role: str = "user",
|
||||||
url: str,
|
url: str,
|
||||||
text: Optional[str] = None,
|
text: str | None = None,
|
||||||
) -> LLMContextMessage:
|
) -> LLMContextMessage:
|
||||||
"""Create a context message containing an image URL.
|
"""Create a context message containing an image URL.
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ class LLMContext:
|
|||||||
format: str,
|
format: str,
|
||||||
size: tuple[int, int],
|
size: tuple[int, int],
|
||||||
image: bytes,
|
image: bytes,
|
||||||
text: Optional[str] = None,
|
text: str | None = None,
|
||||||
) -> LLMContextMessage:
|
) -> LLMContextMessage:
|
||||||
"""Create a context message containing an image.
|
"""Create a context message containing an image.
|
||||||
|
|
||||||
@@ -187,7 +188,7 @@ class LLMContext:
|
|||||||
return {"role": role, "content": content}
|
return {"role": role, "content": content}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def messages(self) -> List[LLMContextMessage]:
|
def messages(self) -> list[LLMContextMessage]:
|
||||||
"""Get the current messages list.
|
"""Get the current messages list.
|
||||||
|
|
||||||
NOTE: This is equivalent to calling `get_messages()` with no filter. If
|
NOTE: This is equivalent to calling `get_messages()` with no filter. If
|
||||||
@@ -201,10 +202,10 @@ class LLMContext:
|
|||||||
|
|
||||||
def get_messages(
|
def get_messages(
|
||||||
self,
|
self,
|
||||||
llm_specific_filter: Optional[str] = None,
|
llm_specific_filter: str | None = None,
|
||||||
*,
|
*,
|
||||||
truncate_large_values: bool = False,
|
truncate_large_values: bool = False,
|
||||||
) -> List[LLMContextMessage]:
|
) -> list[LLMContextMessage]:
|
||||||
"""Get the current messages list.
|
"""Get the current messages list.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -242,8 +243,8 @@ class LLMContext:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _truncate_large_values_from_messages(
|
def _truncate_large_values_from_messages(
|
||||||
messages: List[LLMContextMessage],
|
messages: list[LLMContextMessage],
|
||||||
) -> List[LLMContextMessage]:
|
) -> list[LLMContextMessage]:
|
||||||
"""Return deep copies of messages with large values replaced by placeholders.
|
"""Return deep copies of messages with large values replaced by placeholders.
|
||||||
|
|
||||||
For standard (universal-format) messages, the following known binary
|
For standard (universal-format) messages, the following known binary
|
||||||
@@ -344,7 +345,7 @@ class LLMContext:
|
|||||||
"""
|
"""
|
||||||
self._messages.append(message)
|
self._messages.append(message)
|
||||||
|
|
||||||
def add_messages(self, messages: List[LLMContextMessage]):
|
def add_messages(self, messages: list[LLMContextMessage]):
|
||||||
"""Add multiple messages to the context.
|
"""Add multiple messages to the context.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -352,7 +353,7 @@ class LLMContext:
|
|||||||
"""
|
"""
|
||||||
self._messages.extend(messages)
|
self._messages.extend(messages)
|
||||||
|
|
||||||
def set_messages(self, messages: List[LLMContextMessage]):
|
def set_messages(self, messages: list[LLMContextMessage]):
|
||||||
"""Replace all messages in the context.
|
"""Replace all messages in the context.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -361,7 +362,7 @@ class LLMContext:
|
|||||||
self._messages[:] = messages
|
self._messages[:] = messages
|
||||||
|
|
||||||
def transform_messages(
|
def transform_messages(
|
||||||
self, transform: Callable[[List[LLMContextMessage]], List[LLMContextMessage]]
|
self, transform: Callable[[list[LLMContextMessage]], list[LLMContextMessage]]
|
||||||
):
|
):
|
||||||
"""Transform the current messages using the provided function.
|
"""Transform the current messages using the provided function.
|
||||||
|
|
||||||
@@ -393,7 +394,7 @@ class LLMContext:
|
|||||||
format: str,
|
format: str,
|
||||||
size: tuple[int, int],
|
size: tuple[int, int],
|
||||||
image: bytes,
|
image: bytes,
|
||||||
text: Optional[str] = None,
|
text: str | None = None,
|
||||||
role: str = "user",
|
role: str = "user",
|
||||||
):
|
):
|
||||||
"""Add a message containing an image frame.
|
"""Add a message containing an image frame.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import uuid
|
import uuid
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ class LLMContextSummarizer(BaseObject):
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
context: LLMContext,
|
context: LLMContext,
|
||||||
config: Optional[LLMAutoContextSummarizationConfig] = None,
|
config: LLMAutoContextSummarizationConfig | None = None,
|
||||||
auto_trigger: bool = True,
|
auto_trigger: bool = True,
|
||||||
):
|
):
|
||||||
"""Initialize the context summarizer.
|
"""Initialize the context summarizer.
|
||||||
@@ -122,10 +122,10 @@ class LLMContextSummarizer(BaseObject):
|
|||||||
self._auto_config = config or LLMAutoContextSummarizationConfig()
|
self._auto_config = config or LLMAutoContextSummarizationConfig()
|
||||||
self._auto_trigger = auto_trigger
|
self._auto_trigger = auto_trigger
|
||||||
|
|
||||||
self._task_manager: Optional[BaseTaskManager] = None
|
self._task_manager: BaseTaskManager | None = None
|
||||||
|
|
||||||
self._summarization_in_progress = False
|
self._summarization_in_progress = False
|
||||||
self._pending_summary_request_id: Optional[str] = None
|
self._pending_summary_request_id: str | None = None
|
||||||
|
|
||||||
self._register_event_handler("on_request_summarization", sync=True)
|
self._register_event_handler("on_request_summarization", sync=True)
|
||||||
self._register_event_handler("on_summary_applied")
|
self._register_event_handler("on_summary_applied")
|
||||||
@@ -269,9 +269,7 @@ class LLMContextSummarizer(BaseObject):
|
|||||||
logger.debug(f"{self}: ✓ Summarization needed - {', '.join(reason)}")
|
logger.debug(f"{self}: ✓ Summarization needed - {', '.join(reason)}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _request_summarization(
|
async def _request_summarization(self, config_override: LLMContextSummaryConfig | None = None):
|
||||||
self, config_override: Optional[LLMContextSummaryConfig] = None
|
|
||||||
):
|
|
||||||
"""Request context summarization from LLM service.
|
"""Request context summarization from LLM service.
|
||||||
|
|
||||||
Creates a summarization request frame and either handles it directly
|
Creates a summarization request frame and either handles it directly
|
||||||
@@ -338,7 +336,7 @@ class LLMContextSummarizer(BaseObject):
|
|||||||
summary=summary,
|
summary=summary,
|
||||||
last_summarized_index=last_index,
|
last_summarized_index=last_index,
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except TimeoutError:
|
||||||
error = f"Context summarization timed out after {timeout}s"
|
error = f"Context summarization timed out after {timeout}s"
|
||||||
logger.error(f"{self}: {error}")
|
logger.error(f"{self}: {error}")
|
||||||
result_frame = LLMContextSummaryResultFrame(
|
result_frame = LLMContextSummaryResultFrame(
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import warnings
|
import warnings
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Callable, Dict, List, Literal, Optional, Set, Type
|
from typing import Any, Literal
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -119,14 +120,14 @@ class LLMUserAggregatorParams:
|
|||||||
filter_incomplete_user_turns is True.
|
filter_incomplete_user_turns is True.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user_turn_strategies: Optional[UserTurnStrategies] = None
|
user_turn_strategies: UserTurnStrategies | None = None
|
||||||
user_mute_strategies: List[BaseUserMuteStrategy] = field(default_factory=list)
|
user_mute_strategies: list[BaseUserMuteStrategy] = field(default_factory=list)
|
||||||
user_turn_stop_timeout: float = 5.0
|
user_turn_stop_timeout: float = 5.0
|
||||||
user_idle_timeout: float = 0
|
user_idle_timeout: float = 0
|
||||||
vad_analyzer: Optional[VADAnalyzer] = None
|
vad_analyzer: VADAnalyzer | None = None
|
||||||
audio_idle_timeout: float = 1.0
|
audio_idle_timeout: float = 1.0
|
||||||
filter_incomplete_user_turns: bool = False
|
filter_incomplete_user_turns: bool = False
|
||||||
user_turn_completion_config: Optional[UserTurnCompletionConfig] = None
|
user_turn_completion_config: UserTurnCompletionConfig | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -145,14 +146,14 @@ class LLMAssistantAggregatorParams:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
enable_auto_context_summarization: bool = False
|
enable_auto_context_summarization: bool = False
|
||||||
auto_context_summarization_config: Optional[LLMAutoContextSummarizationConfig] = None
|
auto_context_summarization_config: LLMAutoContextSummarizationConfig | None = None
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Deprecated field names — kept for backward compatibility.
|
# Deprecated field names — kept for backward compatibility.
|
||||||
# Use enable_auto_context_summarization and auto_context_summarization_config instead.
|
# Use enable_auto_context_summarization and auto_context_summarization_config instead.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
enable_context_summarization: Optional[bool] = None
|
enable_context_summarization: bool | None = None
|
||||||
context_summarization_config: Optional[LLMContextSummarizationConfig] = None
|
context_summarization_config: LLMContextSummarizationConfig | None = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if self.enable_context_summarization is not None:
|
if self.enable_context_summarization is not None:
|
||||||
@@ -198,7 +199,7 @@ class UserTurnStoppedMessage:
|
|||||||
|
|
||||||
content: str
|
content: str
|
||||||
timestamp: str
|
timestamp: str
|
||||||
user_id: Optional[str] = None
|
user_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -259,10 +260,10 @@ class LLMContextAggregator(FrameProcessor):
|
|||||||
self._context = context
|
self._context = context
|
||||||
self._role = role
|
self._role = role
|
||||||
|
|
||||||
self._aggregation: List[TextPartForConcatenation] = []
|
self._aggregation: list[TextPartForConcatenation] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def messages(self) -> List[LLMContextMessage]:
|
def messages(self) -> list[LLMContextMessage]:
|
||||||
"""Get messages from the LLM context.
|
"""Get messages from the LLM context.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -322,7 +323,7 @@ class LLMContextAggregator(FrameProcessor):
|
|||||||
self._context.set_messages(messages)
|
self._context.set_messages(messages)
|
||||||
|
|
||||||
def transform_messages(
|
def transform_messages(
|
||||||
self, transform: Callable[[List[LLMContextMessage]], List[LLMContextMessage]]
|
self, transform: Callable[[list[LLMContextMessage]], list[LLMContextMessage]]
|
||||||
):
|
):
|
||||||
"""Transform the context messages using a provided function.
|
"""Transform the context messages using a provided function.
|
||||||
|
|
||||||
@@ -423,7 +424,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
self,
|
self,
|
||||||
context: LLMContext,
|
context: LLMContext,
|
||||||
*,
|
*,
|
||||||
params: Optional[LLMUserAggregatorParams] = None,
|
params: LLMUserAggregatorParams | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the user context aggregator.
|
"""Initialize the user context aggregator.
|
||||||
@@ -473,7 +474,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
self._user_idle_controller.add_event_handler("on_user_turn_idle", self._on_user_turn_idle)
|
self._user_idle_controller.add_event_handler("on_user_turn_idle", self._on_user_turn_idle)
|
||||||
|
|
||||||
# VAD controller
|
# VAD controller
|
||||||
self._vad_controller: Optional[VADController] = None
|
self._vad_controller: VADController | None = None
|
||||||
if self._params.vad_analyzer:
|
if self._params.vad_analyzer:
|
||||||
self._vad_controller = VADController(
|
self._vad_controller = VADController(
|
||||||
self._params.vad_analyzer,
|
self._params.vad_analyzer,
|
||||||
@@ -681,7 +682,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _queued_broadcast_frame(self, frame_cls: Type[Frame], **kwargs):
|
async def _queued_broadcast_frame(self, frame_cls: type[Frame], **kwargs):
|
||||||
"""Broadcasts a frame upstream and queues it for internal processing.
|
"""Broadcasts a frame upstream and queues it for internal processing.
|
||||||
|
|
||||||
Queues the frame so it flows through `process_frame` and is handled
|
Queues the frame so it flows through `process_frame` and is handled
|
||||||
@@ -701,7 +702,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
):
|
):
|
||||||
await self.queue_frame(frame, direction)
|
await self.queue_frame(frame, direction)
|
||||||
|
|
||||||
async def _on_broadcast_frame(self, controller, frame_cls: Type[Frame], **kwargs):
|
async def _on_broadcast_frame(self, controller, frame_cls: type[Frame], **kwargs):
|
||||||
await self._queued_broadcast_frame(frame_cls, **kwargs)
|
await self._queued_broadcast_frame(frame_cls, **kwargs)
|
||||||
|
|
||||||
async def _on_vad_speech_started(self, controller):
|
async def _on_vad_speech_started(self, controller):
|
||||||
@@ -768,7 +769,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
|
|
||||||
async def _maybe_emit_user_turn_stopped(
|
async def _maybe_emit_user_turn_stopped(
|
||||||
self,
|
self,
|
||||||
strategy: Optional[BaseUserTurnStopStrategy] = None,
|
strategy: BaseUserTurnStopStrategy | None = None,
|
||||||
on_session_end: bool = False,
|
on_session_end: bool = False,
|
||||||
):
|
):
|
||||||
"""Maybe emit user turn stopped event.
|
"""Maybe emit user turn stopped event.
|
||||||
@@ -832,7 +833,7 @@ class LLMAssistantAggregator(LLMContextAggregator):
|
|||||||
self,
|
self,
|
||||||
context: LLMContext,
|
context: LLMContext,
|
||||||
*,
|
*,
|
||||||
params: Optional[LLMAssistantAggregatorParams] = None,
|
params: LLMAssistantAggregatorParams | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the assistant context aggregator.
|
"""Initialize the assistant context aggregator.
|
||||||
@@ -845,9 +846,9 @@ class LLMAssistantAggregator(LLMContextAggregator):
|
|||||||
super().__init__(context=context, role="assistant", **kwargs)
|
super().__init__(context=context, role="assistant", **kwargs)
|
||||||
self._params = params or LLMAssistantAggregatorParams()
|
self._params = params or LLMAssistantAggregatorParams()
|
||||||
|
|
||||||
self._function_calls_in_progress: Dict[str, Optional[FunctionCallInProgressFrame]] = {}
|
self._function_calls_in_progress: dict[str, FunctionCallInProgressFrame | None] = {}
|
||||||
self._function_calls_image_results: Dict[str, UserImageRawFrame] = {}
|
self._function_calls_image_results: dict[str, UserImageRawFrame] = {}
|
||||||
self._context_updated_tasks: Set[asyncio.Task] = set()
|
self._context_updated_tasks: set[asyncio.Task] = set()
|
||||||
|
|
||||||
self._user_speaking: bool = False
|
self._user_speaking: bool = False
|
||||||
self._bot_speaking: bool = False
|
self._bot_speaking: bool = False
|
||||||
@@ -862,14 +863,14 @@ class LLMAssistantAggregator(LLMContextAggregator):
|
|||||||
|
|
||||||
self._thought_append_to_context = False
|
self._thought_append_to_context = False
|
||||||
self._thought_llm: str = ""
|
self._thought_llm: str = ""
|
||||||
self._thought_aggregation: List[TextPartForConcatenation] = []
|
self._thought_aggregation: list[TextPartForConcatenation] = []
|
||||||
self._thought_start_time: str = ""
|
self._thought_start_time: str = ""
|
||||||
|
|
||||||
# Context summarization — always create the summarizer so that manually
|
# Context summarization — always create the summarizer so that manually
|
||||||
# pushed LLMSummarizeContextFrame frames are always handled.
|
# pushed LLMSummarizeContextFrame frames are always handled.
|
||||||
# Auto-triggering based on thresholds is only enabled when
|
# Auto-triggering based on thresholds is only enabled when
|
||||||
# enable_auto_context_summarization is True.
|
# enable_auto_context_summarization is True.
|
||||||
self._summarizer: Optional[LLMContextSummarizer] = LLMContextSummarizer(
|
self._summarizer: LLMContextSummarizer | None = LLMContextSummarizer(
|
||||||
context=self._context,
|
context=self._context,
|
||||||
config=self._params.auto_context_summarization_config,
|
config=self._params.auto_context_summarization_config,
|
||||||
auto_trigger=self._params.enable_auto_context_summarization,
|
auto_trigger=self._params.enable_auto_context_summarization,
|
||||||
@@ -1475,8 +1476,8 @@ class LLMContextAggregatorPair:
|
|||||||
self,
|
self,
|
||||||
context: LLMContext,
|
context: LLMContext,
|
||||||
*,
|
*,
|
||||||
user_params: Optional[LLMUserAggregatorParams] = None,
|
user_params: LLMUserAggregatorParams | None = None,
|
||||||
assistant_params: Optional[LLMAssistantAggregatorParams] = None,
|
assistant_params: LLMAssistantAggregatorParams | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the LLM context aggregator pair.
|
"""Initialize the LLM context aggregator pair.
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ components such as TTS services or context aggregators. It can be used to pre-ag
|
|||||||
and categorize, modify, or filter direct output tokens from the LLM.
|
and categorize, modify, or filter direct output tokens from the LLM.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
AggregatedTextFrame,
|
AggregatedTextFrame,
|
||||||
EndFrame,
|
EndFrame,
|
||||||
@@ -38,7 +36,7 @@ class LLMTextProcessor(FrameProcessor):
|
|||||||
output tokens from the LLM.
|
output tokens from the LLM.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, text_aggregator: Optional[BaseTextAggregator] = None, **kwargs):
|
def __init__(self, *, text_aggregator: BaseTextAggregator | None = None, **kwargs):
|
||||||
"""Initialize the LLM text processor.
|
"""Initialize the LLM text processor.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -91,7 +89,7 @@ class LLMTextProcessor(FrameProcessor):
|
|||||||
out_frame.skip_tts = in_frame.skip_tts
|
out_frame.skip_tts = in_frame.skip_tts
|
||||||
await self.push_frame(out_frame)
|
await self.push_frame(out_frame)
|
||||||
|
|
||||||
async def _handle_llm_end(self, skip_tts: Optional[bool] = None):
|
async def _handle_llm_end(self, skip_tts: bool | None = None):
|
||||||
# Flush any remaining text
|
# Flush any remaining text
|
||||||
remaining = await self._text_aggregator.flush()
|
remaining = await self._text_aggregator.flush()
|
||||||
if remaining:
|
if remaining:
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"""Async generator processor for frame serialization and streaming."""
|
"""Async generator processor for frame serialization and streaming."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any, AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ of audio from both user input and bot output sources, with support for various a
|
|||||||
configurations and event-driven processing.
|
configurations and event-driven processing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pipecat.audio.utils import create_stream_resampler, interleave_stereo_audio, mix_audio
|
from pipecat.audio.utils import create_stream_resampler, interleave_stereo_audio, mix_audio
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
BotStartedSpeakingFrame,
|
BotStartedSpeakingFrame,
|
||||||
@@ -55,7 +53,7 @@ class AudioBufferProcessor(FrameProcessor):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
sample_rate: Optional[int] = None,
|
sample_rate: int | None = None,
|
||||||
num_channels: int = 1,
|
num_channels: int = 1,
|
||||||
buffer_size: int = 0,
|
buffer_size: int = 0,
|
||||||
enable_turn_audio: bool = False,
|
enable_turn_audio: bool = False,
|
||||||
@@ -263,7 +261,7 @@ class AudioBufferProcessor(FrameProcessor):
|
|||||||
silence_needed = target_position - current_len
|
silence_needed = target_position - current_len
|
||||||
buffer.extend(b"\x00" * silence_needed)
|
buffer.extend(b"\x00" * silence_needed)
|
||||||
|
|
||||||
async def _process_turn_recording(self, frame: Frame, resampled_audio: Optional[bytes] = None):
|
async def _process_turn_recording(self, frame: Frame, resampled_audio: bytes | None = None):
|
||||||
"""Process frames for turn-based audio recording."""
|
"""Process frames for turn-based audio recording."""
|
||||||
# Speaking state (_user_speaking / _bot_speaking) is maintained by
|
# Speaking state (_user_speaking / _bot_speaking) is maintained by
|
||||||
# _process_recording so it is always up-to-date here.
|
# _process_recording so it is always up-to-date here.
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ This module provides a VADProcessor that wraps a VADController to process
|
|||||||
audio frames and push VAD-related frames into the pipeline.
|
audio frames and push VAD-related frames into the pipeline.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.audio.vad.vad_analyzer import VADAnalyzer
|
from pipecat.audio.vad.vad_analyzer import VADAnalyzer
|
||||||
@@ -94,7 +92,7 @@ class VADProcessor(FrameProcessor):
|
|||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
@self._vad_controller.event_handler("on_broadcast_frame")
|
@self._vad_controller.event_handler("on_broadcast_frame")
|
||||||
async def on_broadcast_frame(_controller, frame_cls: Type[Frame], **kwargs):
|
async def on_broadcast_frame(_controller, frame_cls: type[Frame], **kwargs):
|
||||||
await self.broadcast_frame(frame_cls, **kwargs)
|
await self.broadcast_frame(frame_cls, **kwargs)
|
||||||
|
|
||||||
async def cleanup(self):
|
async def cleanup(self):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"""Consumer processor for consuming frames from ProducerProcessor queues."""
|
"""Consumer processor for consuming frames from ProducerProcessor queues."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Awaitable, Callable, Optional
|
from collections.abc import Awaitable, Callable
|
||||||
|
|
||||||
from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame
|
from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
@@ -42,7 +42,7 @@ class ConsumerProcessor(FrameProcessor):
|
|||||||
self._transformer = transformer
|
self._transformer = transformer
|
||||||
self._direction = direction
|
self._direction = direction
|
||||||
self._producer = producer
|
self._producer = producer
|
||||||
self._consumer_task: Optional[asyncio.Task] = None
|
self._consumer_task: asyncio.Task | None = None
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
"""Process incoming frames and handle lifecycle events.
|
"""Process incoming frames and handle lifecycle events.
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
"""Frame filtering processor for the Pipecat framework."""
|
"""Frame filtering processor for the Pipecat framework."""
|
||||||
|
|
||||||
from typing import Tuple, Type
|
|
||||||
|
|
||||||
from pipecat.frames.frames import EndFrame, Frame, SystemFrame
|
from pipecat.frames.frames import EndFrame, Frame, SystemFrame
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
|
||||||
@@ -20,7 +18,7 @@ class FrameFilter(FrameProcessor):
|
|||||||
automatically allowed to pass through to maintain pipeline integrity.
|
automatically allowed to pass through to maintain pipeline integrity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, types: Tuple[Type[Frame], ...]):
|
def __init__(self, types: tuple[type[Frame], ...]):
|
||||||
"""Initialize the frame filter.
|
"""Initialize the frame filter.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ This module provides a processor that filters frames based on a custom function,
|
|||||||
allowing for flexible frame filtering logic in processing pipelines.
|
allowing for flexible frame filtering logic in processing pipelines.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Awaitable, Callable, Optional
|
from collections.abc import Awaitable, Callable
|
||||||
|
|
||||||
from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame, SystemFrame
|
from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame, SystemFrame
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
@@ -29,7 +29,7 @@ class FunctionFilter(FrameProcessor):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
filter: FilterType,
|
filter: FilterType,
|
||||||
direction: Optional[FrameDirection] = FrameDirection.DOWNSTREAM,
|
direction: FrameDirection | None = FrameDirection.DOWNSTREAM,
|
||||||
filter_system_frames: bool = False,
|
filter_system_frames: bool = False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import re
|
|||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -71,7 +70,7 @@ class WakeCheckFilter(FrameProcessor):
|
|||||||
self.wake_timer = 0.0
|
self.wake_timer = 0.0
|
||||||
self.accumulator = ""
|
self.accumulator = ""
|
||||||
|
|
||||||
def __init__(self, wake_phrases: List[str], keepalive_timeout: float = 3):
|
def __init__(self, wake_phrases: list[str], keepalive_timeout: float = 3):
|
||||||
"""Initialize the wake phrase filter.
|
"""Initialize the wake phrase filter.
|
||||||
|
|
||||||
.. deprecated:: 0.0.106
|
.. deprecated:: 0.0.106
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
"""Wake notifier filter for conditional frame-based notifications."""
|
"""Wake notifier filter for conditional frame-based notifications."""
|
||||||
|
|
||||||
from typing import Awaitable, Callable, Tuple, Type
|
from collections.abc import Awaitable, Callable
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame
|
from pipecat.frames.frames import Frame
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
@@ -25,7 +25,7 @@ class WakeNotifierFilter(FrameProcessor):
|
|||||||
self,
|
self,
|
||||||
notifier: BaseNotifier,
|
notifier: BaseNotifier,
|
||||||
*,
|
*,
|
||||||
types: Tuple[Type[Frame], ...],
|
types: tuple[type[Frame], ...],
|
||||||
filter: Callable[[Frame], Awaitable[bool]],
|
filter: Callable[[Frame], Awaitable[bool]],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -11,21 +11,17 @@ audio/video processing pipelines. It includes frame processors, pipeline
|
|||||||
management, and frame flow control mechanisms.
|
management, and frame flow control mechanisms.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import traceback
|
import traceback
|
||||||
|
from collections.abc import Awaitable, Callable, Coroutine
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Awaitable,
|
|
||||||
Callable,
|
|
||||||
Coroutine,
|
|
||||||
List,
|
|
||||||
Optional,
|
Optional,
|
||||||
Tuple,
|
|
||||||
Type,
|
|
||||||
Union,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -79,7 +75,7 @@ class FrameProcessorSetup:
|
|||||||
|
|
||||||
clock: BaseClock
|
clock: BaseClock
|
||||||
task_manager: BaseTaskManager
|
task_manager: BaseTaskManager
|
||||||
observer: Optional[BaseObserver] = None
|
observer: BaseObserver | None = None
|
||||||
|
|
||||||
|
|
||||||
class FrameProcessorQueue(asyncio.PriorityQueue):
|
class FrameProcessorQueue(asyncio.PriorityQueue):
|
||||||
@@ -100,7 +96,7 @@ class FrameProcessorQueue(asyncio.PriorityQueue):
|
|||||||
self.__high_counter = 0
|
self.__high_counter = 0
|
||||||
self.__low_counter = 0
|
self.__low_counter = 0
|
||||||
|
|
||||||
async def put(self, item: Tuple[Frame, FrameDirection, FrameCallback]):
|
async def put(self, item: tuple[Frame, FrameDirection, FrameCallback]):
|
||||||
"""Put an item into the priority queue.
|
"""Put an item into the priority queue.
|
||||||
|
|
||||||
System frames (`SystemFrame`) have higher priority than any other
|
System frames (`SystemFrame`) have higher priority than any other
|
||||||
@@ -160,9 +156,9 @@ class FrameProcessor(BaseObject):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: Optional[str] = None,
|
name: str | None = None,
|
||||||
enable_direct_mode: bool = False,
|
enable_direct_mode: bool = False,
|
||||||
metrics: Optional[FrameProcessorMetrics] = None,
|
metrics: FrameProcessorMetrics | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the frame processor.
|
"""Initialize the frame processor.
|
||||||
@@ -174,20 +170,20 @@ class FrameProcessor(BaseObject):
|
|||||||
**kwargs: Additional arguments passed to parent class.
|
**kwargs: Additional arguments passed to parent class.
|
||||||
"""
|
"""
|
||||||
super().__init__(name=name, **kwargs)
|
super().__init__(name=name, **kwargs)
|
||||||
self._prev: Optional["FrameProcessor"] = None
|
self._prev: FrameProcessor | None = None
|
||||||
self._next: Optional["FrameProcessor"] = None
|
self._next: FrameProcessor | None = None
|
||||||
|
|
||||||
# Enable direct mode to skip queues and process frames right away.
|
# Enable direct mode to skip queues and process frames right away.
|
||||||
self._enable_direct_mode = enable_direct_mode
|
self._enable_direct_mode = enable_direct_mode
|
||||||
|
|
||||||
# Clock
|
# Clock
|
||||||
self._clock: Optional[BaseClock] = None
|
self._clock: BaseClock | None = None
|
||||||
|
|
||||||
# Task Manager
|
# Task Manager
|
||||||
self._task_manager: Optional[BaseTaskManager] = None
|
self._task_manager: BaseTaskManager | None = None
|
||||||
|
|
||||||
# Observer
|
# Observer
|
||||||
self._observer: Optional[BaseObserver] = None
|
self._observer: BaseObserver | None = None
|
||||||
|
|
||||||
# Other properties
|
# Other properties
|
||||||
self._enable_metrics = False
|
self._enable_metrics = False
|
||||||
@@ -221,8 +217,8 @@ class FrameProcessor(BaseObject):
|
|||||||
# frames right away and queues non-system frames for later processing.
|
# frames right away and queues non-system frames for later processing.
|
||||||
self.__should_block_system_frames = False
|
self.__should_block_system_frames = False
|
||||||
self.__input_queue = FrameProcessorQueue()
|
self.__input_queue = FrameProcessorQueue()
|
||||||
self.__input_event: Optional[asyncio.Event] = None
|
self.__input_event: asyncio.Event | None = None
|
||||||
self.__input_frame_task: Optional[asyncio.Task] = None
|
self.__input_frame_task: asyncio.Task | None = None
|
||||||
|
|
||||||
# The process task processes non-system frames. Non-system frames will
|
# The process task processes non-system frames. Non-system frames will
|
||||||
# be processed as soon as they are received by the processing task
|
# be processed as soon as they are received by the processing task
|
||||||
@@ -231,9 +227,9 @@ class FrameProcessor(BaseObject):
|
|||||||
# `resume_processing_frames()` which will wake up the event.
|
# `resume_processing_frames()` which will wake up the event.
|
||||||
self.__should_block_frames = False
|
self.__should_block_frames = False
|
||||||
self.__process_queue = FrameQueue(frame_getter=lambda item: item[0])
|
self.__process_queue = FrameQueue(frame_getter=lambda item: item[0])
|
||||||
self.__process_event: Optional[asyncio.Event] = None
|
self.__process_event: asyncio.Event | None = None
|
||||||
self.__process_frame_task: Optional[asyncio.Task] = None
|
self.__process_frame_task: asyncio.Task | None = None
|
||||||
self.__process_current_frame: Optional[Frame] = None
|
self.__process_current_frame: Frame | None = None
|
||||||
|
|
||||||
# Frame processor events.
|
# Frame processor events.
|
||||||
self._register_event_handler("on_before_process_frame", sync=True)
|
self._register_event_handler("on_before_process_frame", sync=True)
|
||||||
@@ -261,7 +257,7 @@ class FrameProcessor(BaseObject):
|
|||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def processors(self) -> List["FrameProcessor"]:
|
def processors(self) -> list[FrameProcessor]:
|
||||||
"""Return the list of sub-processors contained within this processor.
|
"""Return the list of sub-processors contained within this processor.
|
||||||
|
|
||||||
Only compound processors (e.g. pipelines and parallel pipelines) have
|
Only compound processors (e.g. pipelines and parallel pipelines) have
|
||||||
@@ -273,7 +269,7 @@ class FrameProcessor(BaseObject):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entry_processors(self) -> List["FrameProcessor"]:
|
def entry_processors(self) -> list[FrameProcessor]:
|
||||||
"""Return the list of entry processors for this processor.
|
"""Return the list of entry processors for this processor.
|
||||||
|
|
||||||
Entry processors are the first processors in a compound processor
|
Entry processors are the first processors in a compound processor
|
||||||
@@ -287,7 +283,7 @@ class FrameProcessor(BaseObject):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def next(self) -> Optional["FrameProcessor"]:
|
def next(self) -> FrameProcessor | None:
|
||||||
"""Get the next processor.
|
"""Get the next processor.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -296,7 +292,7 @@ class FrameProcessor(BaseObject):
|
|||||||
return self._next
|
return self._next
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def previous(self) -> Optional["FrameProcessor"]:
|
def previous(self) -> FrameProcessor | None:
|
||||||
"""Get the previous processor.
|
"""Get the previous processor.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -372,7 +368,7 @@ class FrameProcessor(BaseObject):
|
|||||||
"""
|
"""
|
||||||
self._metrics.set_core_metrics_data(data)
|
self._metrics.set_core_metrics_data(data)
|
||||||
|
|
||||||
async def start_ttfb_metrics(self, *, start_time: Optional[float] = None):
|
async def start_ttfb_metrics(self, *, start_time: float | None = None):
|
||||||
"""Start time-to-first-byte metrics collection.
|
"""Start time-to-first-byte metrics collection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -384,7 +380,7 @@ class FrameProcessor(BaseObject):
|
|||||||
start_time=start_time, report_only_initial_ttfb=self._report_only_initial_ttfb
|
start_time=start_time, report_only_initial_ttfb=self._report_only_initial_ttfb
|
||||||
)
|
)
|
||||||
|
|
||||||
async def stop_ttfb_metrics(self, *, end_time: Optional[float] = None):
|
async def stop_ttfb_metrics(self, *, end_time: float | None = None):
|
||||||
"""Stop time-to-first-byte metrics collection and push results.
|
"""Stop time-to-first-byte metrics collection and push results.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -396,7 +392,7 @@ class FrameProcessor(BaseObject):
|
|||||||
if frame:
|
if frame:
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
|
|
||||||
async def start_processing_metrics(self, *, start_time: Optional[float] = None):
|
async def start_processing_metrics(self, *, start_time: float | None = None):
|
||||||
"""Start processing metrics collection.
|
"""Start processing metrics collection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -406,7 +402,7 @@ class FrameProcessor(BaseObject):
|
|||||||
if self.can_generate_metrics() and self.metrics_enabled:
|
if self.can_generate_metrics() and self.metrics_enabled:
|
||||||
await self._metrics.start_processing_metrics(start_time=start_time)
|
await self._metrics.start_processing_metrics(start_time=start_time)
|
||||||
|
|
||||||
async def stop_processing_metrics(self, *, end_time: Optional[float] = None):
|
async def stop_processing_metrics(self, *, end_time: float | None = None):
|
||||||
"""Stop processing metrics collection and push results.
|
"""Stop processing metrics collection and push results.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -458,7 +454,7 @@ class FrameProcessor(BaseObject):
|
|||||||
await self.stop_processing_metrics()
|
await self.stop_processing_metrics()
|
||||||
await self.stop_text_aggregation_metrics()
|
await self.stop_text_aggregation_metrics()
|
||||||
|
|
||||||
def create_task(self, coroutine: Coroutine, name: Optional[str] = None) -> asyncio.Task:
|
def create_task(self, coroutine: Coroutine, name: str | None = None) -> asyncio.Task:
|
||||||
"""Create a new task managed by this processor.
|
"""Create a new task managed by this processor.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -474,7 +470,7 @@ class FrameProcessor(BaseObject):
|
|||||||
name = f"{self}::{coroutine.cr_code.co_name}"
|
name = f"{self}::{coroutine.cr_code.co_name}"
|
||||||
return self.task_manager.create_task(coroutine, name)
|
return self.task_manager.create_task(coroutine, name)
|
||||||
|
|
||||||
async def cancel_task(self, task: asyncio.Task, timeout: Optional[float] = 1.0):
|
async def cancel_task(self, task: asyncio.Task, timeout: float | None = 1.0):
|
||||||
"""Cancel a task managed by this processor.
|
"""Cancel a task managed by this processor.
|
||||||
|
|
||||||
A default timeout if 1 second is used in order to avoid potential
|
A default timeout if 1 second is used in order to avoid potential
|
||||||
@@ -511,7 +507,7 @@ class FrameProcessor(BaseObject):
|
|||||||
if self._metrics is not None:
|
if self._metrics is not None:
|
||||||
await self._metrics.cleanup()
|
await self._metrics.cleanup()
|
||||||
|
|
||||||
def link(self, processor: "FrameProcessor"):
|
def link(self, processor: FrameProcessor):
|
||||||
"""Link this processor to the next processor in the pipeline.
|
"""Link this processor to the next processor in the pipeline.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -546,7 +542,7 @@ class FrameProcessor(BaseObject):
|
|||||||
self,
|
self,
|
||||||
frame: Frame,
|
frame: Frame,
|
||||||
direction: FrameDirection = FrameDirection.DOWNSTREAM,
|
direction: FrameDirection = FrameDirection.DOWNSTREAM,
|
||||||
callback: Optional[FrameCallback] = None,
|
callback: FrameCallback | None = None,
|
||||||
):
|
):
|
||||||
"""Queue a frame for processing.
|
"""Queue a frame for processing.
|
||||||
|
|
||||||
@@ -622,7 +618,7 @@ class FrameProcessor(BaseObject):
|
|||||||
async def push_error(
|
async def push_error(
|
||||||
self,
|
self,
|
||||||
error_msg: str,
|
error_msg: str,
|
||||||
exception: Optional[Exception] = None,
|
exception: Exception | None = None,
|
||||||
fatal: bool = False,
|
fatal: bool = False,
|
||||||
):
|
):
|
||||||
"""Creates and pushes an ErrorFrame upstream.
|
"""Creates and pushes an ErrorFrame upstream.
|
||||||
@@ -720,7 +716,7 @@ class FrameProcessor(BaseObject):
|
|||||||
|
|
||||||
await self.broadcast_interruption()
|
await self.broadcast_interruption()
|
||||||
|
|
||||||
async def broadcast_frame(self, frame_cls: Type[Frame], **kwargs):
|
async def broadcast_frame(self, frame_cls: type[Frame], **kwargs):
|
||||||
"""Broadcasts a frame of the specified class upstream and downstream.
|
"""Broadcasts a frame of the specified class upstream and downstream.
|
||||||
|
|
||||||
This method creates two instances of the given frame class using the
|
This method creates two instances of the given frame class using the
|
||||||
@@ -929,7 +925,7 @@ class FrameProcessor(BaseObject):
|
|||||||
"""Reset non-system frame processing queue."""
|
"""Reset non-system frame processing queue."""
|
||||||
self.__process_queue.reset()
|
self.__process_queue.reset()
|
||||||
|
|
||||||
def has_queued_frame(self, frame_type: Union[Type[Frame], Type[UninterruptibleFrame]]) -> bool:
|
def has_queued_frame(self, frame_type: type[Frame] | type[UninterruptibleFrame]) -> bool:
|
||||||
"""Return True if a frame of the given type is waiting in the processing queue.
|
"""Return True if a frame of the given type is waiting in the processing queue.
|
||||||
|
|
||||||
Delegates to :meth:`FrameQueue.has_frame` so the check is O(distinct
|
Delegates to :meth:`FrameQueue.has_frame` so the check is O(distinct
|
||||||
@@ -951,7 +947,7 @@ class FrameProcessor(BaseObject):
|
|||||||
self.__process_frame_task = None
|
self.__process_frame_task = None
|
||||||
|
|
||||||
async def __process_frame(
|
async def __process_frame(
|
||||||
self, frame: Frame, direction: FrameDirection, callback: Optional[FrameCallback]
|
self, frame: Frame, direction: FrameDirection, callback: FrameCallback | None
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
await self._call_event_handler("on_before_process_frame", frame)
|
await self._call_event_handler("on_before_process_frame", frame)
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
"""Langchain integration processor for Pipecat."""
|
"""Langchain integration processor for Pipecat."""
|
||||||
|
|
||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
@@ -45,7 +43,7 @@ class LangchainProcessor(FrameProcessor):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self._chain = chain
|
self._chain = chain
|
||||||
self._transcript_key = transcript_key
|
self._transcript_key = transcript_key
|
||||||
self._participant_id: Optional[str] = None
|
self._participant_id: str | None = None
|
||||||
|
|
||||||
def set_participant_id(self, participant_id: str):
|
def set_participant_id(self, participant_id: str):
|
||||||
"""Set the participant ID for session tracking.
|
"""Set the participant ID for session tracking.
|
||||||
@@ -76,7 +74,7 @@ class LangchainProcessor(FrameProcessor):
|
|||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_token_value(text: Union[str, AIMessageChunk]) -> str:
|
def __get_token_value(text: str | AIMessageChunk) -> str:
|
||||||
"""Extract token value from various text types.
|
"""Extract token value from various text types.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"""RTVI pipeline frame definitions."""
|
"""RTVI pipeline frame definitions."""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
from pipecat.frames.frames import SystemFrame
|
from pipecat.frames.frames import SystemFrame
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class RTVIClientMessageFrame(SystemFrame):
|
|||||||
|
|
||||||
msg_id: str
|
msg_id: str
|
||||||
type: str
|
type: str
|
||||||
data: Optional[Any] = None
|
data: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -53,5 +53,5 @@ class RTVIServerResponseFrame(SystemFrame):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
client_msg: RTVIClientMessageFrame
|
client_msg: RTVIClientMessageFrame
|
||||||
data: Optional[Any] = None
|
data: Any | None = None
|
||||||
error: Optional[str] = None
|
error: str | None = None
|
||||||
|
|||||||
@@ -14,12 +14,10 @@ Import this module under the ``RTVI`` alias to use as a namespace::
|
|||||||
msg = RTVI.BotReady(id="1", data=RTVI.BotReadyData(version=RTVI.PROTOCOL_VERSION))
|
msg = RTVI.BotReady(id="1", data=RTVI.BotReadyData(version=RTVI.PROTOCOL_VERSION))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Dict,
|
|
||||||
Literal,
|
Literal,
|
||||||
Mapping,
|
|
||||||
Optional,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -46,7 +44,7 @@ class Message(BaseModel):
|
|||||||
label: MessageLiteral = MESSAGE_LABEL
|
label: MessageLiteral = MESSAGE_LABEL
|
||||||
type: str
|
type: str
|
||||||
id: str
|
id: str
|
||||||
data: Optional[Dict[str, Any]] = None
|
data: dict[str, Any] | None = None
|
||||||
|
|
||||||
|
|
||||||
# -- Client -> Pipecat messages.
|
# -- Client -> Pipecat messages.
|
||||||
@@ -56,7 +54,7 @@ class RawClientMessageData(BaseModel):
|
|||||||
"""Data structure expected from client messages sent to the RTVI server."""
|
"""Data structure expected from client messages sent to the RTVI server."""
|
||||||
|
|
||||||
t: str
|
t: str
|
||||||
d: Optional[Any] = None
|
d: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
class ClientMessage(BaseModel):
|
class ClientMessage(BaseModel):
|
||||||
@@ -64,14 +62,14 @@ class ClientMessage(BaseModel):
|
|||||||
|
|
||||||
msg_id: str
|
msg_id: str
|
||||||
type: str
|
type: str
|
||||||
data: Optional[Any] = None
|
data: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
class RawServerResponseData(BaseModel):
|
class RawServerResponseData(BaseModel):
|
||||||
"""Data structure for server responses to client messages."""
|
"""Data structure for server responses to client messages."""
|
||||||
|
|
||||||
t: str
|
t: str
|
||||||
d: Optional[Any] = None
|
d: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
class ServerResponse(BaseModel):
|
class ServerResponse(BaseModel):
|
||||||
@@ -94,10 +92,10 @@ class AboutClientData(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
library: str
|
library: str
|
||||||
library_version: Optional[str] = None
|
library_version: str | None = None
|
||||||
platform: Optional[str] = None
|
platform: str | None = None
|
||||||
platform_version: Optional[str] = None
|
platform_version: str | None = None
|
||||||
platform_details: Optional[Any] = None
|
platform_details: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
class ClientReadyData(BaseModel):
|
class ClientReadyData(BaseModel):
|
||||||
@@ -165,7 +163,7 @@ class BotReadyData(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
version: str
|
version: str
|
||||||
about: Optional[Mapping[str, Any]] = None
|
about: Mapping[str, Any] | None = None
|
||||||
|
|
||||||
|
|
||||||
class BotReady(BaseModel):
|
class BotReady(BaseModel):
|
||||||
@@ -226,7 +224,7 @@ class SendTextData(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
content: str
|
content: str
|
||||||
options: Optional[SendTextOptions] = None
|
options: SendTextOptions | None = None
|
||||||
|
|
||||||
|
|
||||||
class LLMFunctionCallStartMessageData(BaseModel):
|
class LLMFunctionCallStartMessageData(BaseModel):
|
||||||
@@ -236,7 +234,7 @@ class LLMFunctionCallStartMessageData(BaseModel):
|
|||||||
the configured function_call_report_level for security.
|
the configured function_call_report_level for security.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
function_name: Optional[str] = None
|
function_name: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class LLMFunctionCallStartMessage(BaseModel):
|
class LLMFunctionCallStartMessage(BaseModel):
|
||||||
@@ -270,8 +268,8 @@ class LLMFunctionCallInProgressMessageData(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
tool_call_id: str
|
tool_call_id: str
|
||||||
function_name: Optional[str] = None
|
function_name: str | None = None
|
||||||
arguments: Optional[Mapping[str, Any]] = None
|
arguments: Mapping[str, Any] | None = None
|
||||||
|
|
||||||
|
|
||||||
class LLMFunctionCallInProgressMessage(BaseModel):
|
class LLMFunctionCallInProgressMessage(BaseModel):
|
||||||
@@ -295,8 +293,8 @@ class LLMFunctionCallStoppedMessageData(BaseModel):
|
|||||||
|
|
||||||
tool_call_id: str
|
tool_call_id: str
|
||||||
cancelled: bool
|
cancelled: bool
|
||||||
function_name: Optional[str] = None
|
function_name: str | None = None
|
||||||
result: Optional[Any] = None
|
result: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
class LLMFunctionCallStoppedMessage(BaseModel):
|
class LLMFunctionCallStoppedMessage(BaseModel):
|
||||||
|
|||||||
@@ -7,17 +7,12 @@
|
|||||||
"""RTVI observer for converting pipeline frames to outgoing RTVI messages."""
|
"""RTVI observer for converting pipeline frames to outgoing RTVI messages."""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Awaitable, Callable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum, StrEnum
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Awaitable,
|
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
List,
|
|
||||||
Optional,
|
Optional,
|
||||||
Set,
|
|
||||||
Tuple,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -71,7 +66,7 @@ if TYPE_CHECKING:
|
|||||||
from pipecat.processors.frameworks.rtvi.processor import RTVIProcessor
|
from pipecat.processors.frameworks.rtvi.processor import RTVIProcessor
|
||||||
|
|
||||||
|
|
||||||
class RTVIFunctionCallReportLevel(str, Enum):
|
class RTVIFunctionCallReportLevel(StrEnum):
|
||||||
"""Level of detail to include in function call RTVI events.
|
"""Level of detail to include in function call RTVI events.
|
||||||
|
|
||||||
Controls what information is exposed in function call events for security.
|
Controls what information is exposed in function call events for security.
|
||||||
@@ -148,18 +143,14 @@ class RTVIObserverParams:
|
|||||||
user_audio_level_enabled: bool = False
|
user_audio_level_enabled: bool = False
|
||||||
metrics_enabled: bool = True
|
metrics_enabled: bool = True
|
||||||
system_logs_enabled: bool = False
|
system_logs_enabled: bool = False
|
||||||
ignored_sources: List[FrameProcessor] = field(default_factory=list)
|
ignored_sources: list[FrameProcessor] = field(default_factory=list)
|
||||||
skip_aggregator_types: Optional[List[AggregationType | str]] = None
|
skip_aggregator_types: list[AggregationType | str] | None = None
|
||||||
bot_output_transforms: Optional[
|
bot_output_transforms: (
|
||||||
List[
|
list[tuple[AggregationType | str, Callable[[str, AggregationType | str], Awaitable[str]]]]
|
||||||
Tuple[
|
| None
|
||||||
AggregationType | str,
|
) = None
|
||||||
Callable[[str, AggregationType | str], Awaitable[str]],
|
|
||||||
]
|
|
||||||
]
|
|
||||||
] = None
|
|
||||||
audio_level_period_secs: float = 0.15
|
audio_level_period_secs: float = 0.15
|
||||||
function_call_report_level: Dict[str, RTVIFunctionCallReportLevel] = field(
|
function_call_report_level: dict[str, RTVIFunctionCallReportLevel] = field(
|
||||||
default_factory=lambda: {"*": RTVIFunctionCallReportLevel.NONE}
|
default_factory=lambda: {"*": RTVIFunctionCallReportLevel.NONE}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -180,7 +171,7 @@ class RTVIObserver(BaseObserver):
|
|||||||
self,
|
self,
|
||||||
rtvi: Optional["RTVIProcessor"] = None,
|
rtvi: Optional["RTVIProcessor"] = None,
|
||||||
*,
|
*,
|
||||||
params: Optional[RTVIObserverParams] = None,
|
params: RTVIObserverParams | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the RTVI observer.
|
"""Initialize the RTVI observer.
|
||||||
@@ -194,7 +185,7 @@ class RTVIObserver(BaseObserver):
|
|||||||
self._rtvi = rtvi
|
self._rtvi = rtvi
|
||||||
self._params = params or RTVIObserverParams()
|
self._params = params or RTVIObserverParams()
|
||||||
|
|
||||||
self._ignored_sources: Set[FrameProcessor] = set(self._params.ignored_sources)
|
self._ignored_sources: set[FrameProcessor] = set(self._params.ignored_sources)
|
||||||
self._frames_seen = set()
|
self._frames_seen = set()
|
||||||
|
|
||||||
self._bot_transcription = ""
|
self._bot_transcription = ""
|
||||||
@@ -203,13 +194,13 @@ class RTVIObserver(BaseObserver):
|
|||||||
|
|
||||||
# Track bot speaking state for queuing aggregated text frames
|
# Track bot speaking state for queuing aggregated text frames
|
||||||
self._bot_is_speaking = False
|
self._bot_is_speaking = False
|
||||||
self._queued_aggregated_text_frames: List[AggregatedTextFrame] = []
|
self._queued_aggregated_text_frames: list[AggregatedTextFrame] = []
|
||||||
|
|
||||||
if self._params.system_logs_enabled:
|
if self._params.system_logs_enabled:
|
||||||
self._system_logger_id = logger.add(self._logger_sink)
|
self._system_logger_id = logger.add(self._logger_sink)
|
||||||
|
|
||||||
self._aggregation_transforms: List[
|
self._aggregation_transforms: list[
|
||||||
Tuple[AggregationType | str, Callable[[str, AggregationType | str], Awaitable[str]]]
|
tuple[AggregationType | str, Callable[[str, AggregationType | str], Awaitable[str]]]
|
||||||
] = self._params.bot_output_transforms or []
|
] = self._params.bot_output_transforms or []
|
||||||
|
|
||||||
def add_bot_output_transformer(
|
def add_bot_output_transformer(
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
from typing import Any, Mapping, Optional
|
from collections.abc import Mapping
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel, ValidationError
|
from pydantic import BaseModel, ValidationError
|
||||||
@@ -51,7 +52,7 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
transport: Optional[BaseTransport] = None,
|
transport: BaseTransport | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the RTVI processor.
|
"""Initialize the RTVI processor.
|
||||||
@@ -70,7 +71,7 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
self._llm_skip_tts: bool = False # Keep in sync with llm_service.py's configuration.
|
self._llm_skip_tts: bool = False # Keep in sync with llm_service.py's configuration.
|
||||||
|
|
||||||
# A task to process incoming transport messages.
|
# A task to process incoming transport messages.
|
||||||
self._message_task: Optional[asyncio.Task] = None
|
self._message_task: asyncio.Task | None = None
|
||||||
|
|
||||||
self._register_event_handler("on_bot_started")
|
self._register_event_handler("on_bot_started")
|
||||||
self._register_event_handler("on_client_ready")
|
self._register_event_handler("on_client_ready")
|
||||||
@@ -84,7 +85,7 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
self._input_transport = input_transport
|
self._input_transport = input_transport
|
||||||
self._input_transport.enable_audio_in_stream_on_start(False)
|
self._input_transport.enable_audio_in_stream_on_start(False)
|
||||||
|
|
||||||
def create_rtvi_observer(self, *, params: Optional[RTVIObserverParams] = None, **kwargs):
|
def create_rtvi_observer(self, *, params: RTVIObserverParams | None = None, **kwargs):
|
||||||
"""Creates a new RTVI Observer.
|
"""Creates a new RTVI Observer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ This module provides integration with Strands Agents for handling conversational
|
|||||||
interactions. It supports both single agent and multi-agent graphs.
|
interactions. It supports both single agent and multi-agent graphs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
@@ -38,9 +36,9 @@ class StrandsAgentsProcessor(FrameProcessor):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
agent: Optional[Agent] = None,
|
agent: Agent | None = None,
|
||||||
graph: Optional[Graph] = None,
|
graph: Graph | None = None,
|
||||||
graph_exit_node: Optional[str] = None,
|
graph_exit_node: str | None = None,
|
||||||
):
|
):
|
||||||
"""Initialize the Strands Agents processor.
|
"""Initialize the Strands Agents processor.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"""GStreamer pipeline source integration for Pipecat."""
|
"""GStreamer pipeline source integration for Pipecat."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -58,11 +57,11 @@ class GStreamerPipelineSource(FrameProcessor):
|
|||||||
|
|
||||||
video_width: int = 1280
|
video_width: int = 1280
|
||||||
video_height: int = 720
|
video_height: int = 720
|
||||||
audio_sample_rate: Optional[int] = None
|
audio_sample_rate: int | None = None
|
||||||
audio_channels: int = 1
|
audio_channels: int = 1
|
||||||
clock_sync: bool = True
|
clock_sync: bool = True
|
||||||
|
|
||||||
def __init__(self, *, pipeline: str, out_params: Optional[OutputParams] = None, **kwargs):
|
def __init__(self, *, pipeline: str, out_params: OutputParams | None = None, **kwargs):
|
||||||
"""Initialize the GStreamer pipeline source.
|
"""Initialize the GStreamer pipeline source.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"""Idle frame processor for timeout-based callback execution."""
|
"""Idle frame processor for timeout-based callback execution."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Awaitable, Callable, List, Optional
|
from collections.abc import Awaitable, Callable
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame, StartFrame
|
from pipecat.frames.frames import Frame, StartFrame
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
@@ -26,7 +26,7 @@ class IdleFrameProcessor(FrameProcessor):
|
|||||||
*,
|
*,
|
||||||
callback: Callable[["IdleFrameProcessor"], Awaitable[None]],
|
callback: Callable[["IdleFrameProcessor"], Awaitable[None]],
|
||||||
timeout: float,
|
timeout: float,
|
||||||
types: Optional[List[type]] = None,
|
types: list[type] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the idle frame processor.
|
"""Initialize the idle frame processor.
|
||||||
@@ -86,5 +86,5 @@ class IdleFrameProcessor(FrameProcessor):
|
|||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self._idle_event.wait(), timeout=self._timeout)
|
await asyncio.wait_for(self._idle_event.wait(), timeout=self._timeout)
|
||||||
self._idle_event.clear()
|
self._idle_event.clear()
|
||||||
except asyncio.TimeoutError:
|
except TimeoutError:
|
||||||
await self._callback(self)
|
await self._callback(self)
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
"""Frame logging utilities for debugging and monitoring frame flow in Pipecat pipelines."""
|
"""Frame logging utilities for debugging and monitoring frame flow in Pipecat pipelines."""
|
||||||
|
|
||||||
from typing import Optional, Tuple, Type
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
@@ -33,8 +31,8 @@ class FrameLogger(FrameProcessor):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
prefix="Frame",
|
prefix="Frame",
|
||||||
color: Optional[str] = None,
|
color: str | None = None,
|
||||||
ignored_frame_types: Tuple[Type[Frame], ...] = (
|
ignored_frame_types: tuple[type[Frame], ...] = (
|
||||||
BotSpeakingFrame,
|
BotSpeakingFrame,
|
||||||
UserSpeakingFrame,
|
UserSpeakingFrame,
|
||||||
InputAudioRawFrame,
|
InputAudioRawFrame,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"""Frame processor metrics collection and reporting."""
|
"""Frame processor metrics collection and reporting."""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -70,7 +69,7 @@ class FrameProcessorMetrics(BaseObject):
|
|||||||
return self._task_manager
|
return self._task_manager
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ttfb(self) -> Optional[float]:
|
def ttfb(self) -> float | None:
|
||||||
"""Get the current TTFB value in seconds.
|
"""Get the current TTFB value in seconds.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -110,7 +109,7 @@ class FrameProcessorMetrics(BaseObject):
|
|||||||
self._core_metrics_data = MetricsData(processor=name)
|
self._core_metrics_data = MetricsData(processor=name)
|
||||||
|
|
||||||
async def start_ttfb_metrics(
|
async def start_ttfb_metrics(
|
||||||
self, *, start_time: Optional[float] = None, report_only_initial_ttfb: bool
|
self, *, start_time: float | None = None, report_only_initial_ttfb: bool
|
||||||
):
|
):
|
||||||
"""Start measuring time-to-first-byte (TTFB).
|
"""Start measuring time-to-first-byte (TTFB).
|
||||||
|
|
||||||
@@ -124,7 +123,7 @@ class FrameProcessorMetrics(BaseObject):
|
|||||||
self._last_ttfb_time = 0
|
self._last_ttfb_time = 0
|
||||||
self._should_report_ttfb = not report_only_initial_ttfb
|
self._should_report_ttfb = not report_only_initial_ttfb
|
||||||
|
|
||||||
async def stop_ttfb_metrics(self, *, end_time: Optional[float] = None):
|
async def stop_ttfb_metrics(self, *, end_time: float | None = None):
|
||||||
"""Stop TTFB measurement and generate metrics frame.
|
"""Stop TTFB measurement and generate metrics frame.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -147,7 +146,7 @@ class FrameProcessorMetrics(BaseObject):
|
|||||||
self._start_ttfb_time = 0
|
self._start_ttfb_time = 0
|
||||||
return MetricsFrame(data=[ttfb])
|
return MetricsFrame(data=[ttfb])
|
||||||
|
|
||||||
async def start_processing_metrics(self, *, start_time: Optional[float] = None):
|
async def start_processing_metrics(self, *, start_time: float | None = None):
|
||||||
"""Start measuring processing time.
|
"""Start measuring processing time.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -156,7 +155,7 @@ class FrameProcessorMetrics(BaseObject):
|
|||||||
"""
|
"""
|
||||||
self._start_processing_time = start_time or time.time()
|
self._start_processing_time = start_time or time.time()
|
||||||
|
|
||||||
async def stop_processing_metrics(self, *, end_time: Optional[float] = None):
|
async def stop_processing_metrics(self, *, end_time: float | None = None):
|
||||||
"""Stop processing time measurement and generate metrics frame.
|
"""Stop processing time measurement and generate metrics frame.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"""Sentry integration for frame processor metrics."""
|
"""Sentry integration for frame processor metrics."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -72,7 +71,7 @@ class SentryMetrics(FrameProcessorMetrics):
|
|||||||
sentry_sdk.flush(timeout=5.0)
|
sentry_sdk.flush(timeout=5.0)
|
||||||
|
|
||||||
async def start_ttfb_metrics(
|
async def start_ttfb_metrics(
|
||||||
self, *, start_time: Optional[float] = None, report_only_initial_ttfb: bool
|
self, *, start_time: float | None = None, report_only_initial_ttfb: bool
|
||||||
):
|
):
|
||||||
"""Start tracking time-to-first-byte metrics.
|
"""Start tracking time-to-first-byte metrics.
|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ class SentryMetrics(FrameProcessorMetrics):
|
|||||||
f"{self} Sentry transaction started (ID: {self._ttfb_metrics_tx.span_id} Name: {self._ttfb_metrics_tx.name})"
|
f"{self} Sentry transaction started (ID: {self._ttfb_metrics_tx.span_id} Name: {self._ttfb_metrics_tx.name})"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def stop_ttfb_metrics(self, *, end_time: Optional[float] = None):
|
async def stop_ttfb_metrics(self, *, end_time: float | None = None):
|
||||||
"""Stop tracking time-to-first-byte metrics.
|
"""Stop tracking time-to-first-byte metrics.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -105,7 +104,7 @@ class SentryMetrics(FrameProcessorMetrics):
|
|||||||
await self._sentry_queue.put(self._ttfb_metrics_tx)
|
await self._sentry_queue.put(self._ttfb_metrics_tx)
|
||||||
self._ttfb_metrics_tx = None
|
self._ttfb_metrics_tx = None
|
||||||
|
|
||||||
async def start_processing_metrics(self, *, start_time: Optional[float] = None):
|
async def start_processing_metrics(self, *, start_time: float | None = None):
|
||||||
"""Start tracking frame processing metrics.
|
"""Start tracking frame processing metrics.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -122,7 +121,7 @@ class SentryMetrics(FrameProcessorMetrics):
|
|||||||
f"{self} Sentry transaction started (ID: {self._processing_metrics_tx.span_id} Name: {self._processing_metrics_tx.name})"
|
f"{self} Sentry transaction started (ID: {self._processing_metrics_tx.span_id} Name: {self._processing_metrics_tx.name})"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def stop_processing_metrics(self, *, end_time: Optional[float] = None):
|
async def stop_processing_metrics(self, *, end_time: float | None = None):
|
||||||
"""Stop tracking frame processing metrics.
|
"""Stop tracking frame processing metrics.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"""Producer processor for frame filtering and distribution."""
|
"""Producer processor for frame filtering and distribution."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Awaitable, Callable, List
|
from collections.abc import Awaitable, Callable
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame
|
from pipecat.frames.frames import Frame
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
@@ -55,7 +55,7 @@ class ProducerProcessor(FrameProcessor):
|
|||||||
self._filter = filter
|
self._filter = filter
|
||||||
self._transformer = transformer
|
self._transformer = transformer
|
||||||
self._passthrough = passthrough
|
self._passthrough = passthrough
|
||||||
self._consumers: List[asyncio.Queue] = []
|
self._consumers: list[asyncio.Queue] = []
|
||||||
|
|
||||||
def add_consumer(self):
|
def add_consumer(self):
|
||||||
"""Add a new consumer and return its associated queue.
|
"""Add a new consumer and return its associated queue.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
"""Stateless text transformation processor for Pipecat."""
|
"""Stateless text transformation processor for Pipecat."""
|
||||||
|
|
||||||
from typing import Callable, Coroutine, Union
|
from collections.abc import Callable, Coroutine
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame, TextFrame
|
from pipecat.frames.frames import Frame, TextFrame
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
@@ -21,7 +21,7 @@ class StatelessTextTransformer(FrameProcessor):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, transform_fn: Union[Callable[[str], str], Callable[[str], Coroutine[None, None, str]]]
|
self, transform_fn: Callable[[str], str] | Callable[[str], Coroutine[None, None, str]]
|
||||||
):
|
):
|
||||||
"""Initialize the text transformer.
|
"""Initialize the text transformer.
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ Example::
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -64,7 +63,7 @@ class DailyRoomConfig(BaseModel):
|
|||||||
|
|
||||||
room_url: str
|
room_url: str
|
||||||
token: str
|
token: str
|
||||||
sip_endpoint: Optional[str] = None
|
sip_endpoint: str | None = None
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"""Enable tuple unpacking for backward compatibility.
|
"""Enable tuple unpacking for backward compatibility.
|
||||||
@@ -78,18 +77,18 @@ class DailyRoomConfig(BaseModel):
|
|||||||
async def configure(
|
async def configure(
|
||||||
aiohttp_session: aiohttp.ClientSession,
|
aiohttp_session: aiohttp.ClientSession,
|
||||||
*,
|
*,
|
||||||
api_key: Optional[str] = None,
|
api_key: str | None = None,
|
||||||
room_exp_duration: float = 2.0,
|
room_exp_duration: float = 2.0,
|
||||||
token_exp_duration: float = 2.0,
|
token_exp_duration: float = 2.0,
|
||||||
sip_caller_phone: Optional[str] = None,
|
sip_caller_phone: str | None = None,
|
||||||
sip_enable_video: bool = False,
|
sip_enable_video: bool = False,
|
||||||
sip_num_endpoints: int = 1,
|
sip_num_endpoints: int = 1,
|
||||||
enable_dialout: bool = False,
|
enable_dialout: bool = False,
|
||||||
sip_codecs: Optional[Dict[str, List[str]]] = None,
|
sip_codecs: dict[str, list[str]] | None = None,
|
||||||
sip_provider: Optional[str] = None,
|
sip_provider: str | None = None,
|
||||||
room_geo: Optional[str] = None,
|
room_geo: str | None = None,
|
||||||
room_properties: Optional[DailyRoomProperties] = None,
|
room_properties: DailyRoomProperties | None = None,
|
||||||
token_properties: Optional[DailyMeetingTokenProperties] = None,
|
token_properties: DailyMeetingTokenProperties | None = None,
|
||||||
) -> DailyRoomConfig:
|
) -> DailyRoomConfig:
|
||||||
"""Configure Daily room URL and token with optional SIP capabilities.
|
"""Configure Daily room URL and token with optional SIP capabilities.
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ Example::
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from livekit import api
|
from livekit import api
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -98,7 +97,7 @@ async def configure():
|
|||||||
return (url, token, room_name)
|
return (url, token, room_name)
|
||||||
|
|
||||||
|
|
||||||
async def configure_with_args(parser: Optional[argparse.ArgumentParser] = None):
|
async def configure_with_args(parser: argparse.ArgumentParser | None = None):
|
||||||
"""Configure LiveKit room with command-line argument parsing.
|
"""Configure LiveKit room with command-line argument parsing.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ import uuid
|
|||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from http import HTTPMethod
|
from http import HTTPMethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, TypedDict, Union
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from fastapi.responses import FileResponse, Response
|
from fastapi.responses import FileResponse, Response
|
||||||
@@ -106,7 +106,7 @@ os.environ["ENV"] = "local"
|
|||||||
|
|
||||||
TELEPHONY_TRANSPORTS = ["twilio", "telnyx", "plivo", "exotel"]
|
TELEPHONY_TRANSPORTS = ["twilio", "telnyx", "plivo", "exotel"]
|
||||||
|
|
||||||
RUNNER_DOWNLOADS_FOLDER: Optional[str] = None
|
RUNNER_DOWNLOADS_FOLDER: str | None = None
|
||||||
RUNNER_HOST: str = "localhost"
|
RUNNER_HOST: str = "localhost"
|
||||||
RUNNER_PORT: int = 7860
|
RUNNER_PORT: int = 7860
|
||||||
|
|
||||||
@@ -220,17 +220,17 @@ def _setup_webrtc_routes(app: FastAPI, args: argparse.Namespace):
|
|||||||
return
|
return
|
||||||
|
|
||||||
class IceServer(TypedDict, total=False):
|
class IceServer(TypedDict, total=False):
|
||||||
urls: Union[str, List[str]]
|
urls: str | list[str]
|
||||||
|
|
||||||
class IceConfig(TypedDict):
|
class IceConfig(TypedDict):
|
||||||
iceServers: List[IceServer]
|
iceServers: list[IceServer]
|
||||||
|
|
||||||
class StartBotResult(TypedDict, total=False):
|
class StartBotResult(TypedDict, total=False):
|
||||||
sessionId: str
|
sessionId: str
|
||||||
iceConfig: Optional[IceConfig]
|
iceConfig: IceConfig | None
|
||||||
|
|
||||||
# In-memory store of active sessions: session_id -> session info
|
# In-memory store of active sessions: session_id -> session info
|
||||||
active_sessions: Dict[str, Dict[str, Any]] = {}
|
active_sessions: dict[str, dict[str, Any]] = {}
|
||||||
|
|
||||||
# Mount the frontend
|
# Mount the frontend
|
||||||
app.mount("/client", SmallWebRTCPrebuiltUI)
|
app.mount("/client", SmallWebRTCPrebuiltUI)
|
||||||
@@ -418,7 +418,7 @@ def _setup_whatsapp_routes(app: FastAPI, args: argparse.Namespace):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Global WhatsApp client instance
|
# Global WhatsApp client instance
|
||||||
whatsapp_client: Optional[WhatsAppClient] = None
|
whatsapp_client: WhatsAppClient | None = None
|
||||||
|
|
||||||
@app.get(
|
@app.get(
|
||||||
"/whatsapp",
|
"/whatsapp",
|
||||||
@@ -857,7 +857,7 @@ def _validate_and_clean_proxy(proxy: str) -> str:
|
|||||||
return proxy
|
return proxy
|
||||||
|
|
||||||
|
|
||||||
def runner_downloads_folder() -> Optional[str]:
|
def runner_downloads_folder() -> str | None:
|
||||||
"""Returns the folder where files are stored for later download."""
|
"""Returns the folder where files are stored for later download."""
|
||||||
return RUNNER_DOWNLOADS_FOLDER
|
return RUNNER_DOWNLOADS_FOLDER
|
||||||
|
|
||||||
@@ -872,7 +872,7 @@ def runner_port() -> int:
|
|||||||
return RUNNER_PORT
|
return RUNNER_PORT
|
||||||
|
|
||||||
|
|
||||||
def main(parser: Optional[argparse.ArgumentParser] = None):
|
def main(parser: argparse.ArgumentParser | None = None):
|
||||||
"""Start the Pipecat development runner.
|
"""Start the Pipecat development runner.
|
||||||
|
|
||||||
Parses command-line arguments and starts a FastAPI server configured
|
Parses command-line arguments and starts a FastAPI server configured
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ information to bot functions.
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import WebSocket
|
from fastapi import WebSocket
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -34,9 +34,9 @@ class DialinSettings(BaseModel):
|
|||||||
|
|
||||||
call_id: str
|
call_id: str
|
||||||
call_domain: str
|
call_domain: str
|
||||||
To: Optional[str] = None
|
To: str | None = None
|
||||||
From: Optional[str] = None
|
From: str | None = None
|
||||||
sip_headers: Optional[Dict[str, str]] = None
|
sip_headers: dict[str, str] | None = None
|
||||||
|
|
||||||
|
|
||||||
class DailyDialinRequest(BaseModel):
|
class DailyDialinRequest(BaseModel):
|
||||||
@@ -64,8 +64,8 @@ class RunnerArguments:
|
|||||||
handle_sigint: bool = field(init=False, kw_only=True)
|
handle_sigint: bool = field(init=False, kw_only=True)
|
||||||
handle_sigterm: bool = field(init=False, kw_only=True)
|
handle_sigterm: bool = field(init=False, kw_only=True)
|
||||||
pipeline_idle_timeout_secs: int = field(init=False, kw_only=True)
|
pipeline_idle_timeout_secs: int = field(init=False, kw_only=True)
|
||||||
body: Optional[Any] = field(default_factory=dict, kw_only=True)
|
body: Any | None = field(default_factory=dict, kw_only=True)
|
||||||
cli_args: Optional[argparse.Namespace] = field(default=None, init=False, kw_only=True)
|
cli_args: argparse.Namespace | None = field(default=None, init=False, kw_only=True)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.handle_sigint = False
|
self.handle_sigint = False
|
||||||
@@ -84,7 +84,7 @@ class DailyRunnerArguments(RunnerArguments):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
room_url: str
|
room_url: str
|
||||||
token: Optional[str] = None
|
token: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -122,4 +122,4 @@ class LiveKitRunnerArguments(RunnerArguments):
|
|||||||
|
|
||||||
room_name: str
|
room_name: str
|
||||||
url: str
|
url: str
|
||||||
token: Optional[str] = None
|
token: str | None = None
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ Example::
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from typing import Any, Callable, Dict, Optional
|
from collections.abc import Callable
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import WebSocket
|
from fastapi import WebSocket
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -373,7 +374,7 @@ def _smallwebrtc_sdp_cleanup_fingerprints(text: str) -> str:
|
|||||||
return "\r\n".join(result) + "\r\n"
|
return "\r\n".join(result) + "\r\n"
|
||||||
|
|
||||||
|
|
||||||
def smallwebrtc_sdp_munging(sdp: str, host: Optional[str]) -> str:
|
def smallwebrtc_sdp_munging(sdp: str, host: str | None) -> str:
|
||||||
"""Apply SDP modifications for SmallWebRTC compatibility.
|
"""Apply SDP modifications for SmallWebRTC compatibility.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -389,7 +390,7 @@ def smallwebrtc_sdp_munging(sdp: str, host: Optional[str]) -> str:
|
|||||||
return sdp
|
return sdp
|
||||||
|
|
||||||
|
|
||||||
def _get_transport_params(transport_key: str, transport_params: Dict[str, Callable]) -> Any:
|
def _get_transport_params(transport_key: str, transport_params: dict[str, Callable]) -> Any:
|
||||||
"""Get transport parameters from factory function.
|
"""Get transport parameters from factory function.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -415,7 +416,7 @@ def _get_transport_params(transport_key: str, transport_params: Dict[str, Callab
|
|||||||
|
|
||||||
async def _create_telephony_transport(
|
async def _create_telephony_transport(
|
||||||
websocket: WebSocket,
|
websocket: WebSocket,
|
||||||
params: Optional[Any] = None,
|
params: Any | None = None,
|
||||||
transport_type: str = None,
|
transport_type: str = None,
|
||||||
call_data: dict = None,
|
call_data: dict = None,
|
||||||
) -> BaseTransport:
|
) -> BaseTransport:
|
||||||
@@ -488,7 +489,7 @@ async def _create_telephony_transport(
|
|||||||
|
|
||||||
|
|
||||||
async def create_transport(
|
async def create_transport(
|
||||||
runner_args: Any, transport_params: Dict[str, Callable]
|
runner_args: Any, transport_params: dict[str, Callable]
|
||||||
) -> BaseTransport:
|
) -> BaseTransport:
|
||||||
"""Create a transport from runner arguments using factory functions.
|
"""Create a transport from runner arguments using factory functions.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"""Frame serialization interfaces for Pipecat."""
|
"""Frame serialization interfaces for Pipecat."""
|
||||||
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ class FrameSerializer(BaseObject):
|
|||||||
|
|
||||||
ignore_rtvi_messages: bool = True
|
ignore_rtvi_messages: bool = True
|
||||||
|
|
||||||
def __init__(self, params: Optional[InputParams] = None, **kwargs):
|
def __init__(self, params: InputParams | None = None, **kwargs):
|
||||||
"""Initialize the FrameSerializer.
|
"""Initialize the FrameSerializer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -48,10 +47,10 @@ class ExotelFrameSerializer(FrameSerializer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
exotel_sample_rate: int = 8000
|
exotel_sample_rate: int = 8000
|
||||||
sample_rate: Optional[int] = None
|
sample_rate: int | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, stream_sid: str, call_sid: Optional[str] = None, params: Optional[InputParams] = None
|
self, stream_sid: str, call_sid: str | None = None, params: InputParams | None = None
|
||||||
):
|
):
|
||||||
"""Initialize the ExotelFrameSerializer.
|
"""Initialize the ExotelFrameSerializer.
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ Audio Format:
|
|||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from enum import Enum
|
from enum import StrEnum
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ from pipecat.frames.frames import (
|
|||||||
from pipecat.serializers.base_serializer import FrameSerializer
|
from pipecat.serializers.base_serializer import FrameSerializer
|
||||||
|
|
||||||
|
|
||||||
class AudioHookMessageType(str, Enum):
|
class AudioHookMessageType(StrEnum):
|
||||||
"""AudioHook protocol message types."""
|
"""AudioHook protocol message types."""
|
||||||
|
|
||||||
OPEN = "open"
|
OPEN = "open"
|
||||||
@@ -63,7 +63,7 @@ class AudioHookMessageType(str, Enum):
|
|||||||
DISCONNECT = "disconnect"
|
DISCONNECT = "disconnect"
|
||||||
|
|
||||||
|
|
||||||
class AudioHookChannel(str, Enum):
|
class AudioHookChannel(StrEnum):
|
||||||
"""AudioHook audio channel configuration."""
|
"""AudioHook audio channel configuration."""
|
||||||
|
|
||||||
EXTERNAL = "external" # Customer audio only (mono)
|
EXTERNAL = "external" # Customer audio only (mono)
|
||||||
@@ -71,7 +71,7 @@ class AudioHookChannel(str, Enum):
|
|||||||
BOTH = "both" # Stereo: external=left, internal=right
|
BOTH = "both" # Stereo: external=left, internal=right
|
||||||
|
|
||||||
|
|
||||||
class AudioHookMediaFormat(str, Enum):
|
class AudioHookMediaFormat(StrEnum):
|
||||||
"""Supported audio formats."""
|
"""Supported audio formats."""
|
||||||
|
|
||||||
PCMU = "PCMU" # μ-law, 8kHz
|
PCMU = "PCMU" # μ-law, 8kHz
|
||||||
@@ -146,18 +146,18 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
genesys_sample_rate: int = 8000
|
genesys_sample_rate: int = 8000
|
||||||
sample_rate: Optional[int] = None
|
sample_rate: int | None = None
|
||||||
channel: AudioHookChannel = AudioHookChannel.EXTERNAL
|
channel: AudioHookChannel = AudioHookChannel.EXTERNAL
|
||||||
media_format: AudioHookMediaFormat = AudioHookMediaFormat.PCMU
|
media_format: AudioHookMediaFormat = AudioHookMediaFormat.PCMU
|
||||||
process_external: bool = True
|
process_external: bool = True
|
||||||
process_internal: bool = False
|
process_internal: bool = False
|
||||||
supported_languages: Optional[List[str]] = None
|
supported_languages: list[str] | None = None
|
||||||
selected_language: Optional[str] = None
|
selected_language: str | None = None
|
||||||
start_paused: bool = False
|
start_paused: bool = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
params: Optional[InputParams] = None,
|
params: InputParams | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Initialize the GenesysAudioHookSerializer.
|
"""Initialize the GenesysAudioHookSerializer.
|
||||||
@@ -185,12 +185,12 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
self._position = timedelta(0)
|
self._position = timedelta(0)
|
||||||
|
|
||||||
# Session metadata
|
# Session metadata
|
||||||
self._conversation_id: Optional[str] = None
|
self._conversation_id: str | None = None
|
||||||
self._participant: Optional[Dict[str, Any]] = None
|
self._participant: dict[str, Any] | None = None
|
||||||
self._custom_config: Optional[Dict[str, Any]] = None
|
self._custom_config: dict[str, Any] | None = None
|
||||||
self._media_info: Optional[List[Dict[str, Any]]] = None
|
self._media_info: list[dict[str, Any]] | None = None
|
||||||
self._input_variables: Optional[Dict[str, Any]] = None # Custom input from Genesys
|
self._input_variables: dict[str, Any] | None = None # Custom input from Genesys
|
||||||
self._output_variables: Optional[Dict[str, Any]] = None # Custom output to Genesys
|
self._output_variables: dict[str, Any] | None = None # Custom output to Genesys
|
||||||
|
|
||||||
# Event handlers
|
# Event handlers
|
||||||
self._register_event_handler("on_open")
|
self._register_event_handler("on_open")
|
||||||
@@ -207,7 +207,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
return self._session_id
|
return self._session_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def conversation_id(self) -> Optional[str]:
|
def conversation_id(self) -> str | None:
|
||||||
"""Get the Genesys conversation ID."""
|
"""Get the Genesys conversation ID."""
|
||||||
return self._conversation_id
|
return self._conversation_id
|
||||||
|
|
||||||
@@ -222,21 +222,21 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
return self._is_paused
|
return self._is_paused
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def participant(self) -> Optional[Dict[str, Any]]:
|
def participant(self) -> dict[str, Any] | None:
|
||||||
"""Get participant info (ani, dnis, etc.) from the open message."""
|
"""Get participant info (ani, dnis, etc.) from the open message."""
|
||||||
return self._participant
|
return self._participant
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def input_variables(self) -> Optional[Dict[str, Any]]:
|
def input_variables(self) -> dict[str, Any] | None:
|
||||||
"""Get custom input variables from the open message."""
|
"""Get custom input variables from the open message."""
|
||||||
return self._input_variables
|
return self._input_variables
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def output_variables(self) -> Optional[Dict[str, Any]]:
|
def output_variables(self) -> dict[str, Any] | None:
|
||||||
"""Get custom output variables to send back to Genesys."""
|
"""Get custom output variables to send back to Genesys."""
|
||||||
return self._output_variables
|
return self._output_variables
|
||||||
|
|
||||||
def set_output_variables(self, variables: Dict[str, Any]) -> None:
|
def set_output_variables(self, variables: dict[str, Any]) -> None:
|
||||||
"""Set custom output variables to send back to Genesys on close.
|
"""Set custom output variables to send back to Genesys on close.
|
||||||
|
|
||||||
These variables will be included in the 'closed' response when Genesys
|
These variables will be included in the 'closed' response when Genesys
|
||||||
@@ -305,9 +305,9 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
def _create_message(
|
def _create_message(
|
||||||
self,
|
self,
|
||||||
msg_type: AudioHookMessageType,
|
msg_type: AudioHookMessageType,
|
||||||
parameters: Optional[Dict[str, Any]] = None,
|
parameters: dict[str, Any] | None = None,
|
||||||
include_position: bool = True,
|
include_position: bool = True,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Create a protocol message with common fields.
|
"""Create a protocol message with common fields.
|
||||||
|
|
||||||
Based on the Genesys AudioHook protocol, responses include:
|
Based on the Genesys AudioHook protocol, responses include:
|
||||||
@@ -341,9 +341,9 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
def create_opened_response(
|
def create_opened_response(
|
||||||
self,
|
self,
|
||||||
start_paused: bool = False,
|
start_paused: bool = False,
|
||||||
supported_languages: Optional[List[str]] = None,
|
supported_languages: list[str] | None = None,
|
||||||
selected_language: Optional[str] = None,
|
selected_language: str | None = None,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Create an 'opened' response message for the client.
|
"""Create an 'opened' response message for the client.
|
||||||
|
|
||||||
This should be sent in response to an 'open' message from Genesys.
|
This should be sent in response to an 'open' message from Genesys.
|
||||||
@@ -397,8 +397,8 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
|
|
||||||
def create_closed_response(
|
def create_closed_response(
|
||||||
self,
|
self,
|
||||||
output_variables: Optional[Dict[str, Any]] = None,
|
output_variables: dict[str, Any] | None = None,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Create a 'closed' response message.
|
"""Create a 'closed' response message.
|
||||||
|
|
||||||
This should be sent in response to a 'close' message from Genesys.
|
This should be sent in response to a 'close' message from Genesys.
|
||||||
@@ -422,7 +422,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
parameters: Optional[Dict[str, Any]] = None
|
parameters: dict[str, Any] | None = None
|
||||||
|
|
||||||
if output_variables:
|
if output_variables:
|
||||||
parameters = {"outputVariables": output_variables}
|
parameters = {"outputVariables": output_variables}
|
||||||
@@ -437,7 +437,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def create_pong_response(self) -> Dict[str, Any]:
|
def create_pong_response(self) -> dict[str, Any]:
|
||||||
"""Create a 'pong' response message.
|
"""Create a 'pong' response message.
|
||||||
|
|
||||||
This should be sent in response to a 'ping' message from Genesys.
|
This should be sent in response to a 'ping' message from Genesys.
|
||||||
@@ -448,7 +448,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
msg = self._create_message(AudioHookMessageType.PONG)
|
msg = self._create_message(AudioHookMessageType.PONG)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def create_resumed_response(self) -> Dict[str, Any]:
|
def create_resumed_response(self) -> dict[str, Any]:
|
||||||
"""Create a 'resumed' response message.
|
"""Create a 'resumed' response message.
|
||||||
|
|
||||||
This should be sent in response to a 'pause' message when ready to resume.
|
This should be sent in response to a 'pause' message when ready to resume.
|
||||||
@@ -463,7 +463,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def create_barge_in_event(self) -> Dict[str, Any]:
|
def create_barge_in_event(self) -> dict[str, Any]:
|
||||||
"""Create a barge-in event message.
|
"""Create a barge-in event message.
|
||||||
|
|
||||||
This notifies Genesys Cloud that the user has interrupted the bot's
|
This notifies Genesys Cloud that the user has interrupted the bot's
|
||||||
@@ -485,9 +485,9 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
self,
|
self,
|
||||||
reason: str = "completed",
|
reason: str = "completed",
|
||||||
action: str = "transfer",
|
action: str = "transfer",
|
||||||
output_variables: Optional[Dict[str, Any]] = None,
|
output_variables: dict[str, Any] | None = None,
|
||||||
info: Optional[str] = None,
|
info: str | None = None,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Create a 'disconnect' message to initiate session termination.
|
"""Create a 'disconnect' message to initiate session termination.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -499,7 +499,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
Returns:
|
Returns:
|
||||||
Dictionary of the disconnect message.
|
Dictionary of the disconnect message.
|
||||||
"""
|
"""
|
||||||
parameters: Dict[str, Any] = {"reason": reason}
|
parameters: dict[str, Any] = {"reason": reason}
|
||||||
|
|
||||||
# Build outputVariables
|
# Build outputVariables
|
||||||
out_vars = {"action": action}
|
out_vars = {"action": action}
|
||||||
@@ -523,7 +523,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
code: int,
|
code: int,
|
||||||
message: str,
|
message: str,
|
||||||
retryable: bool = False,
|
retryable: bool = False,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Create an 'error' message.
|
"""Create an 'error' message.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -700,7 +700,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
|
|
||||||
return audio_frame
|
return audio_frame
|
||||||
|
|
||||||
async def _handle_control_message(self, message: Dict[str, Any]) -> Frame | None:
|
async def _handle_control_message(self, message: dict[str, Any]) -> Frame | None:
|
||||||
"""Handle a JSON control message from Genesys.
|
"""Handle a JSON control message from Genesys.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -748,7 +748,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
logger.warning(f"Unknown AudioHook message type: {msg_type}")
|
logger.warning(f"Unknown AudioHook message type: {msg_type}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _handle_open(self, message: Dict[str, Any]) -> Frame | None:
|
async def _handle_open(self, message: dict[str, Any]) -> Frame | None:
|
||||||
"""Handle an 'open' message from Genesys.
|
"""Handle an 'open' message from Genesys.
|
||||||
|
|
||||||
This initializes the session with metadata from Genesys Cloud and
|
This initializes the session with metadata from Genesys Cloud and
|
||||||
@@ -781,7 +781,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
# media is a list like: [{"type": "audio", "format": "PCMU", "channels": ["external"], "rate": 8000}]
|
# media is a list like: [{"type": "audio", "format": "PCMU", "channels": ["external"], "rate": 8000}]
|
||||||
media_list = self._media_info
|
media_list = self._media_info
|
||||||
if media_list and isinstance(media_list, list) and len(media_list) > 0:
|
if media_list and isinstance(media_list, list) and len(media_list) > 0:
|
||||||
audio_media: Dict[str, Any] = media_list[0] # Get first media entry
|
audio_media: dict[str, Any] = media_list[0] # Get first media entry
|
||||||
channels = audio_media.get("channels", [])
|
channels = audio_media.get("channels", [])
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"📡 Genesys audio config: format={audio_media.get('format')}, channels={channels}, rate={audio_media.get('rate')}"
|
f"📡 Genesys audio config: format={audio_media.get('format')}, channels={channels}, rate={audio_media.get('rate')}"
|
||||||
@@ -815,7 +815,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _handle_close(self, message: Dict[str, Any]) -> Frame | None:
|
async def _handle_close(self, message: dict[str, Any]) -> Frame | None:
|
||||||
"""Handle a 'close' message from Genesys.
|
"""Handle a 'close' message from Genesys.
|
||||||
|
|
||||||
Automatically responds with a 'closed' message. If output_variables
|
Automatically responds with a 'closed' message. If output_variables
|
||||||
@@ -846,7 +846,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
message=self.create_closed_response(output_variables=self._output_variables)
|
message=self.create_closed_response(output_variables=self._output_variables)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _handle_ping(self, message: Dict[str, Any]) -> Frame | None:
|
async def _handle_ping(self, message: dict[str, Any]) -> Frame | None:
|
||||||
"""Handle a 'ping' message from Genesys.
|
"""Handle a 'ping' message from Genesys.
|
||||||
|
|
||||||
Automatically responds with a 'pong' message to maintain the connection.
|
Automatically responds with a 'pong' message to maintain the connection.
|
||||||
@@ -864,7 +864,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
# Return as urgent frame to be sent through pipeline immediately
|
# Return as urgent frame to be sent through pipeline immediately
|
||||||
return OutputTransportMessageUrgentFrame(message=self.create_pong_response())
|
return OutputTransportMessageUrgentFrame(message=self.create_pong_response())
|
||||||
|
|
||||||
async def _handle_pause(self, message: Dict[str, Any]) -> Frame | None:
|
async def _handle_pause(self, message: dict[str, Any]) -> Frame | None:
|
||||||
"""Handle a 'pause' message from Genesys.
|
"""Handle a 'pause' message from Genesys.
|
||||||
|
|
||||||
This is used when audio streaming is temporarily suspended
|
This is used when audio streaming is temporarily suspended
|
||||||
@@ -888,7 +888,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
# Note: Application should call create_resumed_response() when ready
|
# Note: Application should call create_resumed_response() when ready
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _handle_update(self, message: Dict[str, Any]) -> Frame | None:
|
async def _handle_update(self, message: dict[str, Any]) -> Frame | None:
|
||||||
"""Handle an 'update' message from Genesys.
|
"""Handle an 'update' message from Genesys.
|
||||||
|
|
||||||
Updates may include changes to participants or configuration.
|
Updates may include changes to participants or configuration.
|
||||||
@@ -910,7 +910,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _handle_error(self, message: Dict[str, Any]) -> Frame | None:
|
async def _handle_error(self, message: dict[str, Any]) -> Frame | None:
|
||||||
"""Handle an 'error' message from Genesys.
|
"""Handle an 'error' message from Genesys.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -929,7 +929,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _handle_dtmf(self, message: Dict[str, Any]) -> Frame | None:
|
async def _handle_dtmf(self, message: dict[str, Any]) -> Frame | None:
|
||||||
"""Handle a 'dtmf' message from Genesys.
|
"""Handle a 'dtmf' message from Genesys.
|
||||||
|
|
||||||
DTMF (Dual-Tone Multi-Frequency) events are sent when the user
|
DTMF (Dual-Tone Multi-Frequency) events are sent when the user
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user