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"]
|
||||
logger.debug(f"loading conversation from {filename}")
|
||||
try:
|
||||
with open(filename, "r") as file:
|
||||
with open(filename) as file:
|
||||
params.context.set_messages(json.load(file))
|
||||
logger.debug(
|
||||
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"]
|
||||
logger.debug(f"loading conversation from {filename}")
|
||||
try:
|
||||
with open(filename, "r") as file:
|
||||
with open(filename) as file:
|
||||
messages = json.load(file)
|
||||
# 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(),
|
||||
|
||||
@@ -110,7 +110,7 @@ async def load_conversation(params: FunctionCallParams):
|
||||
filename = params.arguments["filename"]
|
||||
logger.debug(f"loading conversation from {filename}")
|
||||
try:
|
||||
with open(filename, "r") as file:
|
||||
with open(filename) as file:
|
||||
params.context.set_messages(json.load(file))
|
||||
await params.result_callback(
|
||||
{
|
||||
|
||||
@@ -94,7 +94,7 @@ async def load_conversation(params: FunctionCallParams):
|
||||
filename = params.arguments["filename"]
|
||||
logger.debug(f"loading conversation from {filename}")
|
||||
try:
|
||||
with open(filename, "r") as file:
|
||||
with open(filename) as file:
|
||||
params.context.set_messages(json.load(file))
|
||||
await params.llm.reset_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"]
|
||||
logger.debug(f"loading conversation from {filename}")
|
||||
try:
|
||||
with open(filename, "r") as file:
|
||||
with open(filename) as file:
|
||||
params.context.set_messages(json.load(file))
|
||||
await params.llm.reset_conversation()
|
||||
# NOTE: we manually create a response here rather than relying
|
||||
|
||||
@@ -85,7 +85,7 @@ async def load_conversation(params: FunctionCallParams):
|
||||
filename = params.arguments["filename"]
|
||||
logger.debug(f"loading conversation from {filename}")
|
||||
try:
|
||||
with open(filename, "r") as file:
|
||||
with open(filename) as file:
|
||||
params.context.set_messages(json.load(file))
|
||||
logger.debug(
|
||||
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"]
|
||||
logger.debug(f"loading conversation from {filename}")
|
||||
try:
|
||||
with open(filename, "r") as file:
|
||||
with open(filename) as file:
|
||||
params.context.set_messages(json.load(file))
|
||||
logger.debug(
|
||||
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"]
|
||||
logger.debug(f"loading conversation from {filename}")
|
||||
try:
|
||||
with open(filename, "r") as file:
|
||||
with open(filename) as file:
|
||||
params.context.set_messages(json.load(file))
|
||||
logger.debug(
|
||||
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."""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import argparse
|
||||
import asyncio
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Dict
|
||||
|
||||
import uvicorn
|
||||
from dotenv import load_dotenv
|
||||
@@ -39,7 +38,7 @@ load_dotenv(override=True)
|
||||
app = FastAPI()
|
||||
|
||||
# Store connections by pc_id
|
||||
pcs_map: Dict[str, SmallWebRTCConnection] = {}
|
||||
pcs_map: dict[str, SmallWebRTCConnection] = {}
|
||||
|
||||
ice_servers = [
|
||||
IceServer(
|
||||
|
||||
@@ -45,13 +45,13 @@ class TranscriptHandler:
|
||||
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.
|
||||
|
||||
Args:
|
||||
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(
|
||||
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 datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Optional, Tuple
|
||||
from typing import Any
|
||||
|
||||
import aiofiles
|
||||
from loguru import logger
|
||||
@@ -60,7 +60,7 @@ PIPELINE_IDLE_TIMEOUT_SECS = 60
|
||||
EVAL_TIMEOUT_SECS = 120
|
||||
EVAL_RESULT_TIMEOUT_SECS = 10
|
||||
|
||||
EvalPrompt = str | Tuple[str, ImageFile]
|
||||
EvalPrompt = str | tuple[str, ImageFile]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -68,7 +68,7 @@ class EvalConfig:
|
||||
prompt: EvalPrompt
|
||||
eval: str
|
||||
eval_speaks_first: bool = False
|
||||
runner_args_body: Optional[Any] = None
|
||||
runner_args_body: Any | None = None
|
||||
|
||||
|
||||
class EvalRunner:
|
||||
@@ -78,7 +78,7 @@ class EvalRunner:
|
||||
examples_dir: Path,
|
||||
pattern: str = "",
|
||||
record_audio: bool = False,
|
||||
name: Optional[str] = None,
|
||||
name: str | None = None,
|
||||
log_level: str = "DEBUG",
|
||||
):
|
||||
self._examples_dir = examples_dir
|
||||
@@ -86,8 +86,8 @@ class EvalRunner:
|
||||
self._record_audio = record_audio
|
||||
self._log_level = log_level
|
||||
self._total_success = 0
|
||||
self._tests: List[EvalResult] = []
|
||||
self._result_future: Optional[asyncio.Future[bool]] = None
|
||||
self._tests: list[EvalResult] = []
|
||||
self._result_future: asyncio.Future[bool] | None = None
|
||||
|
||||
# We to save runner files.
|
||||
name = name or f"{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
@@ -150,7 +150,7 @@ class EvalRunner:
|
||||
try:
|
||||
# Wait for the future to resolve.
|
||||
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.")
|
||||
result = False
|
||||
|
||||
@@ -282,7 +282,7 @@ async def run_eval_pipeline(
|
||||
|
||||
# Load example prompt depending on image.
|
||||
example_prompt = ""
|
||||
example_image: Optional[ImageFile] = None
|
||||
example_image: ImageFile | None = None
|
||||
if isinstance(eval_config.prompt, str):
|
||||
example_prompt = eval_config.prompt
|
||||
elif isinstance(eval_config.prompt, tuple):
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
@@ -41,7 +41,7 @@ EVAL_WEATHER_AND_RESTAURANT = EvalConfig(
|
||||
|
||||
EVAL_ONLINE_SEARCH = EvalConfig(
|
||||
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(
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
GREEN = "\033[92m"
|
||||
RED = "\033[91m"
|
||||
|
||||
@@ -5,13 +5,12 @@ handling format detection and conversion to int16 PCM format.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Tuple
|
||||
|
||||
import numpy as np
|
||||
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.
|
||||
|
||||
This function:
|
||||
|
||||
@@ -12,7 +12,7 @@ adapters that handle tool format conversion and standardization.
|
||||
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, Generic, List, Optional, TypeVar
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -50,10 +50,10 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
||||
def __init__(self):
|
||||
"""Initialize the adapter."""
|
||||
self._warned_system_instruction = False
|
||||
self._builtin_tools: Dict[str, FunctionSchema] = {}
|
||||
self._builtin_tools: dict[str, FunctionSchema] = {}
|
||||
|
||||
@property
|
||||
def builtin_tools(self) -> Dict[str, FunctionSchema]:
|
||||
def builtin_tools(self) -> dict[str, FunctionSchema]:
|
||||
"""Built-in tools automatically merged into every inference request.
|
||||
|
||||
Keyed by tool name for O(1) lookup, insertion, and removal. The
|
||||
@@ -90,7 +90,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
||||
pass
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
@@ -102,7 +102,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
||||
pass
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
@@ -127,7 +127,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
||||
|
||||
def get_messages(
|
||||
self, context: LLMContext, *, truncate_large_values: bool = False
|
||||
) -> List[LLMContextMessage]:
|
||||
) -> list[LLMContextMessage]:
|
||||
"""Get messages from the LLM context, including standard and LLM-specific messages.
|
||||
|
||||
Args:
|
||||
@@ -142,7 +142,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
||||
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.
|
||||
|
||||
Built-in tools are automatically merged into the schema before conversion so that every
|
||||
@@ -188,8 +188,8 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
||||
self,
|
||||
messages: list,
|
||||
*,
|
||||
system_instruction: Optional[str] = None,
|
||||
) -> Optional[str]:
|
||||
system_instruction: str | None = None,
|
||||
) -> str | None:
|
||||
"""Extract an initial ``"system"`` message for use as a system instruction.
|
||||
|
||||
Only useful for services that expect the system instruction as a
|
||||
@@ -247,11 +247,11 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
|
||||
|
||||
def _resolve_system_instruction(
|
||||
self,
|
||||
system_from_context: Optional[str],
|
||||
system_instruction: Optional[str],
|
||||
system_from_context: str | None,
|
||||
system_instruction: str | None,
|
||||
*,
|
||||
discard_context_system: bool,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""Resolve conflict between ``system_instruction`` and an extracted context system message.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -15,16 +15,11 @@ formats).
|
||||
|
||||
import inspect
|
||||
import types
|
||||
from collections.abc import Callable, Mapping
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Mapping,
|
||||
Protocol,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
get_args,
|
||||
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
|
||||
def _get_parameters_as_jsonschema(
|
||||
self, func: Callable, docstring_params: List[docstring_parser.DocstringParam]
|
||||
) -> Tuple[Dict[str, Any], List[str]]:
|
||||
self, func: Callable, docstring_params: list[docstring_parser.DocstringParam]
|
||||
) -> tuple[dict[str, Any], list[str]]:
|
||||
"""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.
|
||||
@@ -193,7 +188,7 @@ class BaseDirectFunctionWrapper:
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -216,9 +211,9 @@ class BaseDirectFunctionWrapper:
|
||||
return {"type": "number"}
|
||||
elif type_hint is bool:
|
||||
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"}
|
||||
elif type_hint is list or type_hint is List:
|
||||
elif type_hint is list or type_hint is list:
|
||||
return {"type": "array"}
|
||||
|
||||
# Get origin and arguments for complex types
|
||||
@@ -230,11 +225,11 @@ class BaseDirectFunctionWrapper:
|
||||
return {"anyOf": [self._typehint_to_jsonschema(arg) for arg in args]}
|
||||
|
||||
# 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])}
|
||||
|
||||
# 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
|
||||
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.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any
|
||||
|
||||
|
||||
class FunctionSchema:
|
||||
@@ -23,7 +23,7 @@ class FunctionSchema:
|
||||
"""
|
||||
|
||||
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:
|
||||
"""Initialize the function schema.
|
||||
|
||||
@@ -38,7 +38,7 @@ class FunctionSchema:
|
||||
self._properties = properties
|
||||
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.
|
||||
|
||||
Returns:
|
||||
@@ -73,7 +73,7 @@ class FunctionSchema:
|
||||
return self._description
|
||||
|
||||
@property
|
||||
def properties(self) -> Dict[str, Any]:
|
||||
def properties(self) -> dict[str, Any]:
|
||||
"""Get the function properties.
|
||||
|
||||
Returns:
|
||||
@@ -82,7 +82,7 @@ class FunctionSchema:
|
||||
return self._properties
|
||||
|
||||
@property
|
||||
def required(self) -> List[str]:
|
||||
def required(self) -> list[str]:
|
||||
"""Get the required parameters.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -11,7 +11,7 @@ and custom adapter-specific tools in the Pipecat framework.
|
||||
"""
|
||||
|
||||
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.function_schema import FunctionSchema
|
||||
@@ -39,8 +39,8 @@ class ToolsSchema:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
standard_tools: List[FunctionSchema | DirectFunction],
|
||||
custom_tools: Optional[Dict[AdapterType, List[Dict[str, Any]]]] = None,
|
||||
standard_tools: list[FunctionSchema | DirectFunction],
|
||||
custom_tools: dict[AdapterType, list[dict[str, Any]]] | None = None,
|
||||
) -> None:
|
||||
"""Initialize the tools schema.
|
||||
|
||||
@@ -66,7 +66,7 @@ class ToolsSchema:
|
||||
self._custom_tools = custom_tools
|
||||
|
||||
@property
|
||||
def standard_tools(self) -> List[FunctionSchema]:
|
||||
def standard_tools(self) -> list[FunctionSchema]:
|
||||
"""Get the list of standard function schema tools.
|
||||
|
||||
Returns:
|
||||
@@ -75,7 +75,7 @@ class ToolsSchema:
|
||||
return self._standard_tools
|
||||
|
||||
@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.
|
||||
|
||||
Returns:
|
||||
@@ -84,7 +84,7 @@ class ToolsSchema:
|
||||
return self._custom_tools
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import copy
|
||||
import json
|
||||
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.types.message_param import MessageParam
|
||||
@@ -31,8 +31,8 @@ class AnthropicLLMInvocationParams(TypedDict):
|
||||
"""Context-based parameters for invoking Anthropic's LLM API."""
|
||||
|
||||
system: str | NotGiven
|
||||
messages: List[MessageParam]
|
||||
tools: List[ToolUnionParam]
|
||||
messages: list[MessageParam]
|
||||
tools: list[ToolUnionParam]
|
||||
|
||||
|
||||
class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
||||
@@ -51,7 +51,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
||||
self,
|
||||
context: LLMContext,
|
||||
enable_prompt_caching: bool,
|
||||
system_instruction: Optional[str] = None,
|
||||
system_instruction: str | None = None,
|
||||
) -> AnthropicLLMInvocationParams:
|
||||
"""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 [],
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Removes or truncates sensitive data like image content for safe logging.
|
||||
@@ -115,14 +115,14 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
||||
class ConvertedMessages:
|
||||
"""Container for Anthropic-formatted messages converted from universal context."""
|
||||
|
||||
messages: List[MessageParam]
|
||||
messages: list[MessageParam]
|
||||
system: str | NotGiven
|
||||
|
||||
def _from_universal_context_messages(
|
||||
self,
|
||||
universal_context_messages: List[LLMContextMessage],
|
||||
universal_context_messages: list[LLMContextMessage],
|
||||
*,
|
||||
system_instruction: Optional[str] = None,
|
||||
system_instruction: str | None = None,
|
||||
) -> ConvertedMessages:
|
||||
system = NOT_GIVEN
|
||||
|
||||
@@ -333,7 +333,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -381,7 +381,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
||||
return messages_with_markers
|
||||
|
||||
@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.
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -10,7 +10,7 @@ import copy
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
system_instruction: Optional[str]
|
||||
messages: List[AWSNovaSonicConversationHistoryMessage]
|
||||
tools: List[Dict[str, Any]]
|
||||
system_instruction: str | None
|
||||
messages: list[AWSNovaSonicConversationHistoryMessage]
|
||||
tools: list[dict[str, Any]]
|
||||
|
||||
|
||||
class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
||||
@@ -73,7 +73,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
||||
return "aws-nova-sonic"
|
||||
|
||||
def get_llm_invocation_params(
|
||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
||||
self, context: LLMContext, *, system_instruction: str | None = None
|
||||
) -> AWSNovaSonicLLMInvocationParams:
|
||||
"""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 [],
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Removes or truncates sensitive data like image content for safe logging.
|
||||
@@ -116,11 +116,11 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
||||
class ConvertedMessages:
|
||||
"""Container for Google-formatted messages converted from universal context."""
|
||||
|
||||
messages: List[AWSNovaSonicConversationHistoryMessage]
|
||||
system_instruction: Optional[str] = None
|
||||
messages: list[AWSNovaSonicConversationHistoryMessage]
|
||||
system_instruction: str | None = None
|
||||
|
||||
def _from_universal_context_messages(
|
||||
self, universal_context_messages: List[LLMContextMessage]
|
||||
self, universal_context_messages: list[LLMContextMessage]
|
||||
) -> ConvertedMessages:
|
||||
system_instruction = None
|
||||
messages = []
|
||||
@@ -187,7 +187,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
||||
# Sonic conversation history
|
||||
|
||||
@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.
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -10,7 +10,7 @@ import base64
|
||||
import copy
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -29,9 +29,9 @@ from pipecat.processors.aggregators.llm_context import (
|
||||
class AWSBedrockLLMInvocationParams(TypedDict):
|
||||
"""Context-based parameters for invoking AWS Bedrock's LLM API."""
|
||||
|
||||
system: Optional[List[dict[str, Any]]] # [{"text": "system message"}]
|
||||
messages: List[dict[str, Any]]
|
||||
tools: List[dict[str, Any]]
|
||||
system: list[dict[str, Any]] | None # [{"text": "system message"}]
|
||||
messages: list[dict[str, Any]]
|
||||
tools: list[dict[str, Any]]
|
||||
tool_choice: LLMContextToolChoice
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
||||
return "aws"
|
||||
|
||||
def get_llm_invocation_params(
|
||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
||||
self, context: LLMContext, *, system_instruction: str | None = None
|
||||
) -> AWSBedrockLLMInvocationParams:
|
||||
"""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,
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Removes or truncates sensitive data like image content for safe logging.
|
||||
@@ -109,14 +109,14 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
||||
class ConvertedMessages:
|
||||
"""Container for Bedrock-formatted messages converted from universal context."""
|
||||
|
||||
messages: List[dict[str, Any]]
|
||||
system: Optional[str]
|
||||
messages: list[dict[str, Any]]
|
||||
system: str | None
|
||||
|
||||
def _from_universal_context_messages(
|
||||
self,
|
||||
universal_context_messages: List[LLMContextMessage],
|
||||
universal_context_messages: list[LLMContextMessage],
|
||||
*,
|
||||
system_instruction: Optional[str] = None,
|
||||
system_instruction: str | None = None,
|
||||
) -> ConvertedMessages:
|
||||
system = None
|
||||
|
||||
@@ -305,7 +305,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
||||
return message
|
||||
|
||||
@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.
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional, TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from loguru import logger
|
||||
from openai import NotGiven
|
||||
@@ -34,9 +34,9 @@ except ModuleNotFoundError as e:
|
||||
class GeminiLLMInvocationParams(TypedDict):
|
||||
"""Context-based parameters for invoking Gemini LLM."""
|
||||
|
||||
system_instruction: Optional[str]
|
||||
messages: List[Content]
|
||||
tools: List[Any] | NotGiven
|
||||
system_instruction: str | None
|
||||
messages: list[Content]
|
||||
tools: list[Any] | NotGiven
|
||||
|
||||
|
||||
class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
||||
@@ -54,7 +54,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
||||
return "google"
|
||||
|
||||
def get_llm_invocation_params(
|
||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
||||
self, context: LLMContext, *, system_instruction: str | None = None
|
||||
) -> GeminiLLMInvocationParams:
|
||||
"""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),
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -92,7 +92,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -139,7 +139,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
||||
|
||||
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.
|
||||
|
||||
Removes or truncates sensitive data like image content for safe logging.
|
||||
@@ -173,8 +173,8 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
||||
class ConvertedMessages:
|
||||
"""Container for Google-formatted messages converted from universal context."""
|
||||
|
||||
messages: List[Content]
|
||||
system_instruction: Optional[str] = None
|
||||
messages: list[Content]
|
||||
system_instruction: str | None = None
|
||||
|
||||
@dataclass
|
||||
class MessageConversionResult:
|
||||
@@ -184,20 +184,20 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
||||
for any tool calls discovered in the message.
|
||||
"""
|
||||
|
||||
content: Optional[Content] = None
|
||||
tool_call_id_to_name_mapping: Dict[str, str] = field(default_factory=dict)
|
||||
content: Content | None = None
|
||||
tool_call_id_to_name_mapping: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
@dataclass
|
||||
class MessageConversionParams:
|
||||
"""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(
|
||||
self,
|
||||
universal_context_messages: List[LLMContextMessage],
|
||||
universal_context_messages: list[LLMContextMessage],
|
||||
*,
|
||||
system_instruction: Optional[str] = None,
|
||||
system_instruction: str | None = None,
|
||||
) -> ConvertedMessages:
|
||||
"""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(
|
||||
self, thought_signature_dicts: List[dict], messages: List[Content]
|
||||
) -> List[Content]:
|
||||
self, thought_signature_dicts: list[dict], messages: list[Content]
|
||||
) -> list[Content]:
|
||||
"""Merge parallel tool calls into single Content objects when thinking is enabled.
|
||||
|
||||
Gemini expects parallel tool calls (multiple function calls made
|
||||
@@ -540,7 +540,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
||||
return merged_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:
|
||||
"""Apply thought signatures to corresponding assistant messages.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Grok's Voice Agent API.
|
||||
import copy
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -33,9 +33,9 @@ class GrokRealtimeLLMInvocationParams(TypedDict):
|
||||
tools: List of tool definitions (function, web_search, x_search, file_search).
|
||||
"""
|
||||
|
||||
system_instruction: Optional[str]
|
||||
messages: List[events.ConversationItem]
|
||||
tools: List[Dict[str, Any]]
|
||||
system_instruction: str | None
|
||||
messages: list[events.ConversationItem]
|
||||
tools: list[dict[str, Any]]
|
||||
|
||||
|
||||
class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
@@ -51,7 +51,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
return "grok-realtime"
|
||||
|
||||
def get_llm_invocation_params(
|
||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
||||
self, context: LLMContext, *, system_instruction: str | None = None
|
||||
) -> GrokRealtimeLLMInvocationParams:
|
||||
"""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 [],
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Binary data (images, audio) is replaced with short placeholders.
|
||||
@@ -91,11 +91,11 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
class ConvertedMessages:
|
||||
"""Container for Grok-formatted messages converted from universal context."""
|
||||
|
||||
messages: List[events.ConversationItem]
|
||||
system_instruction: Optional[str] = None
|
||||
messages: list[events.ConversationItem]
|
||||
system_instruction: str | None = None
|
||||
|
||||
def _from_universal_context_messages(
|
||||
self, universal_context_messages: List[LLMContextMessage]
|
||||
self, universal_context_messages: list[LLMContextMessage]
|
||||
) -> ConvertedMessages:
|
||||
"""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}")
|
||||
|
||||
@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.
|
||||
|
||||
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.
|
||||
|
||||
Supports both standard function tools and Grok-specific tools
|
||||
|
||||
@@ -13,7 +13,7 @@ Inworld's Realtime API.
|
||||
import copy
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -33,9 +33,9 @@ class InworldRealtimeLLMInvocationParams(TypedDict):
|
||||
tools: List of tool definitions.
|
||||
"""
|
||||
|
||||
system_instruction: Optional[str]
|
||||
messages: List[events.ConversationItem]
|
||||
tools: List[Dict[str, Any]]
|
||||
system_instruction: str | None
|
||||
messages: list[events.ConversationItem]
|
||||
tools: list[dict[str, Any]]
|
||||
|
||||
|
||||
class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
@@ -51,7 +51,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
return "inworld-realtime"
|
||||
|
||||
def get_llm_invocation_params(
|
||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
||||
self, context: LLMContext, *, system_instruction: str | None = None
|
||||
) -> InworldRealtimeLLMInvocationParams:
|
||||
"""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 [],
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Binary data (images, audio) is replaced with short placeholders.
|
||||
@@ -91,11 +91,11 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
class ConvertedMessages:
|
||||
"""Container for Inworld-formatted messages converted from universal context."""
|
||||
|
||||
messages: List[events.ConversationItem]
|
||||
system_instruction: Optional[str] = None
|
||||
messages: list[events.ConversationItem]
|
||||
system_instruction: str | None = None
|
||||
|
||||
def _from_universal_context_messages(
|
||||
self, universal_context_messages: List[LLMContextMessage]
|
||||
self, universal_context_messages: list[LLMContextMessage]
|
||||
) -> ConvertedMessages:
|
||||
"""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}")
|
||||
|
||||
@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.
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""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.chat import (
|
||||
@@ -29,8 +29,8 @@ from pipecat.processors.aggregators.llm_context import (
|
||||
class OpenAILLMInvocationParams(TypedDict):
|
||||
"""Context-based parameters for invoking OpenAI ChatCompletion API."""
|
||||
|
||||
messages: List[ChatCompletionMessageParam]
|
||||
tools: List[ChatCompletionToolParam] | OpenAINotGiven
|
||||
messages: list[ChatCompletionMessageParam]
|
||||
tools: list[ChatCompletionToolParam] | OpenAINotGiven
|
||||
tool_choice: ChatCompletionToolChoiceOptionParam | OpenAINotGiven
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
||||
self,
|
||||
context: LLMContext,
|
||||
*,
|
||||
system_instruction: Optional[str] = None,
|
||||
system_instruction: str | None = None,
|
||||
convert_developer_to_user: bool,
|
||||
) -> OpenAILLMInvocationParams:
|
||||
"""Get OpenAI-specific LLM invocation parameters from a universal LLM context.
|
||||
@@ -95,7 +95,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
||||
"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.
|
||||
|
||||
Args:
|
||||
@@ -115,7 +115,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
||||
custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, [])
|
||||
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.
|
||||
|
||||
Binary data (images, audio) is replaced with short placeholders.
|
||||
@@ -130,10 +130,10 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
||||
|
||||
def _from_universal_context_messages(
|
||||
self,
|
||||
messages: List[LLMContextMessage],
|
||||
messages: list[LLMContextMessage],
|
||||
*,
|
||||
convert_developer_to_user: bool,
|
||||
) -> List[ChatCompletionMessageParam]:
|
||||
) -> list[ChatCompletionMessageParam]:
|
||||
result = []
|
||||
for message in messages:
|
||||
if isinstance(message, LLMSpecificMessage):
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import copy
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
system_instruction: Optional[str]
|
||||
messages: List[events.ConversationItem]
|
||||
tools: List[Dict[str, Any]]
|
||||
system_instruction: str | None
|
||||
messages: list[events.ConversationItem]
|
||||
tools: list[dict[str, Any]]
|
||||
|
||||
|
||||
class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
@@ -44,7 +44,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
return "openai-realtime"
|
||||
|
||||
def get_llm_invocation_params(
|
||||
self, context: LLMContext, *, system_instruction: Optional[str] = None
|
||||
self, context: LLMContext, *, system_instruction: str | None = None
|
||||
) -> OpenAIRealtimeLLMInvocationParams:
|
||||
"""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 [],
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Binary data (images, audio) is replaced with short placeholders.
|
||||
@@ -87,11 +87,11 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
class ConvertedMessages:
|
||||
"""Container for OpenAI-formatted messages converted from universal context."""
|
||||
|
||||
messages: List[events.ConversationItem]
|
||||
system_instruction: Optional[str] = None
|
||||
messages: list[events.ConversationItem]
|
||||
system_instruction: str | None = None
|
||||
|
||||
def _from_universal_context_messages(
|
||||
self, universal_context_messages: List[LLMContextMessage]
|
||||
self, universal_context_messages: list[LLMContextMessage]
|
||||
) -> ConvertedMessages:
|
||||
# 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
|
||||
@@ -188,7 +188,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
||||
logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
|
||||
|
||||
@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.
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""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.responses import FunctionToolParam, ResponseInputItemParam, ToolParam
|
||||
@@ -23,8 +23,8 @@ from pipecat.processors.aggregators.llm_context import (
|
||||
class OpenAIResponsesLLMInvocationParams(TypedDict, total=False):
|
||||
"""Context-based parameters for invoking OpenAI Responses API."""
|
||||
|
||||
input: List[ResponseInputItemParam]
|
||||
tools: List[ToolParam] | OpenAINotGiven
|
||||
input: list[ResponseInputItemParam]
|
||||
tools: list[ToolParam] | OpenAINotGiven
|
||||
instructions: str
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
|
||||
self,
|
||||
context: LLMContext,
|
||||
*,
|
||||
system_instruction: Optional[str] = None,
|
||||
system_instruction: str | None = None,
|
||||
) -> OpenAIResponsesLLMInvocationParams:
|
||||
"""Get Responses API invocation parameters from a universal LLM context.
|
||||
|
||||
@@ -105,7 +105,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -132,7 +132,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
|
||||
custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, [])
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
def _convert_messages_to_input(
|
||||
self, messages: List[LLMContextMessage]
|
||||
) -> List[ResponseInputItemParam]:
|
||||
self, messages: list[LLMContextMessage]
|
||||
) -> list[ResponseInputItemParam]:
|
||||
"""Convert LLMContext messages to Responses API input items.
|
||||
|
||||
Args:
|
||||
@@ -156,7 +156,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
|
||||
Returns:
|
||||
List of Responses API input items.
|
||||
"""
|
||||
result: List[ResponseInputItemParam] = []
|
||||
result: list[ResponseInputItemParam] = []
|
||||
|
||||
for message in messages:
|
||||
if isinstance(message, LLMSpecificMessage):
|
||||
|
||||
@@ -28,7 +28,6 @@ the messages are sent to Perplexity's API.
|
||||
"""
|
||||
|
||||
import copy
|
||||
from typing import List, Optional
|
||||
|
||||
from openai.types.chat import ChatCompletionMessageParam
|
||||
|
||||
@@ -53,7 +52,7 @@ class PerplexityLLMAdapter(OpenAILLMAdapter):
|
||||
self,
|
||||
context: LLMContext,
|
||||
*,
|
||||
system_instruction: Optional[str] = None,
|
||||
system_instruction: str | None = None,
|
||||
convert_developer_to_user: bool,
|
||||
) -> OpenAILLMInvocationParams:
|
||||
"""Get OpenAI-compatible invocation parameters with Perplexity message fixes applied.
|
||||
@@ -78,8 +77,8 @@ class PerplexityLLMAdapter(OpenAILLMAdapter):
|
||||
return params
|
||||
|
||||
def _transform_messages(
|
||||
self, messages: List[ChatCompletionMessageParam]
|
||||
) -> List[ChatCompletionMessageParam]:
|
||||
self, messages: list[ChatCompletionMessageParam]
|
||||
) -> list[ChatCompletionMessageParam]:
|
||||
"""Transform messages to satisfy Perplexity's API constraints.
|
||||
|
||||
Applies three transformation steps in order:
|
||||
|
||||
@@ -11,10 +11,10 @@ key on the telephone keypad, facilitating the handling of input in
|
||||
telecommunication applications.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class KeypadEntry(str, Enum):
|
||||
class KeypadEntry(StrEnum):
|
||||
"""DTMF keypad entries for phone system integration.
|
||||
|
||||
Parameters:
|
||||
|
||||
@@ -15,7 +15,6 @@ import asyncio
|
||||
import io
|
||||
import wave
|
||||
from importlib.resources import files
|
||||
from typing import Dict, Optional
|
||||
|
||||
import aiofiles
|
||||
|
||||
@@ -24,8 +23,8 @@ from pipecat.audio.resamplers.base_audio_resampler import BaseAudioResampler
|
||||
from pipecat.audio.utils import create_file_resampler
|
||||
|
||||
__DTMF_LOCK__ = asyncio.Lock()
|
||||
__DTMF_AUDIO__: Dict[KeypadEntry, bytes] = {}
|
||||
__DTMF_RESAMPLER__: Optional[BaseAudioResampler] = None
|
||||
__DTMF_AUDIO__: dict[KeypadEntry, bytes] = {}
|
||||
__DTMF_RESAMPLER__: BaseAudioResampler | None = None
|
||||
|
||||
__DTMF_FILE_NAME = {
|
||||
KeypadEntry.POUND: "dtmf-pound.wav",
|
||||
|
||||
@@ -18,7 +18,6 @@ Classes:
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from threading import Lock
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from aic_sdk import (
|
||||
@@ -44,14 +43,14 @@ class AICModelManager:
|
||||
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()
|
||||
_loading: dict[
|
||||
str, asyncio.Task[Model]
|
||||
] = {} # key -> load task (deduplicates concurrent loads)
|
||||
|
||||
@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."""
|
||||
cached_model, ref_count = entry
|
||||
cls._cache[cache_key] = (cached_model, ref_count + 1)
|
||||
@@ -59,7 +58,7 @@ class AICModelManager:
|
||||
return cached_model, cache_key
|
||||
|
||||
@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."""
|
||||
cls._cache[cache_key] = (model, 1)
|
||||
logger.debug(f"AIC model cached key={cache_key!r} ref_count=1")
|
||||
@@ -70,9 +69,9 @@ class AICModelManager:
|
||||
cls,
|
||||
cache_key: str,
|
||||
*,
|
||||
model_path: Optional[Path] = None,
|
||||
model_id: Optional[str] = None,
|
||||
model_download_dir: Optional[Path] = None,
|
||||
model_path: Path | None = None,
|
||||
model_id: str | None = None,
|
||||
model_download_dir: Path | None = None,
|
||||
) -> Model:
|
||||
"""Run the actual load (file or download). Separate to allow create_task and deduplication."""
|
||||
if model_path is not None:
|
||||
@@ -94,9 +93,9 @@ class AICModelManager:
|
||||
@staticmethod
|
||||
def _get_cache_key(
|
||||
*,
|
||||
model_path: Optional[Path] = None,
|
||||
model_id: Optional[str] = None,
|
||||
model_download_dir: Optional[Path] = None,
|
||||
model_path: Path | None = None,
|
||||
model_id: str | None = None,
|
||||
model_download_dir: Path | None = None,
|
||||
) -> str:
|
||||
"""Build a stable cache key for the model.
|
||||
|
||||
@@ -120,10 +119,10 @@ class AICModelManager:
|
||||
async def acquire(
|
||||
cls,
|
||||
*,
|
||||
model_path: Optional[Path] = None,
|
||||
model_id: Optional[str] = None,
|
||||
model_download_dir: Optional[Path] = None,
|
||||
) -> Tuple[Model, str]:
|
||||
model_path: Path | None = None,
|
||||
model_id: str | None = None,
|
||||
model_download_dir: Path | None = None,
|
||||
) -> tuple[Model, str]:
|
||||
"""Get or load a Model and increment its reference count.
|
||||
|
||||
Call this when starting a filter. Store the returned key and pass it
|
||||
@@ -218,10 +217,10 @@ class AICFilter(BaseAudioFilter):
|
||||
self,
|
||||
*,
|
||||
license_key: str,
|
||||
model_id: Optional[str] = None,
|
||||
model_path: Optional[Path] = None,
|
||||
model_download_dir: Optional[Path] = None,
|
||||
enhancement_level: Optional[float] = None,
|
||||
model_id: str | None = None,
|
||||
model_path: Path | None = None,
|
||||
model_download_dir: Path | None = None,
|
||||
enhancement_level: float | None = None,
|
||||
) -> None:
|
||||
"""Initialize the AIC filter.
|
||||
|
||||
@@ -274,7 +273,7 @@ class AICFilter(BaseAudioFilter):
|
||||
)
|
||||
|
||||
# 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._processor = None
|
||||
self._processor_ctx = None
|
||||
@@ -298,9 +297,9 @@ class AICFilter(BaseAudioFilter):
|
||||
def create_vad_analyzer(
|
||||
self,
|
||||
*,
|
||||
speech_hold_duration: Optional[float] = None,
|
||||
minimum_speech_duration: Optional[float] = None,
|
||||
sensitivity: Optional[float] = None,
|
||||
speech_hold_duration: float | None = None,
|
||||
minimum_speech_duration: float | None = None,
|
||||
sensitivity: float | None = None,
|
||||
):
|
||||
"""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])
|
||||
self._audio_buffer = self._audio_buffer[total_size:]
|
||||
|
||||
filtered_chunks: List[bytes] = []
|
||||
filtered_chunks: list[bytes] = []
|
||||
|
||||
for i in range(num_blocks):
|
||||
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.
|
||||
"""
|
||||
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
|
||||
@@ -12,7 +12,8 @@ runtime configuration changes.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Any, Dict, Mapping
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
@@ -70,7 +71,7 @@ class SoundfileMixer(BaseAudioMixer):
|
||||
self._sample_rate = 0
|
||||
|
||||
self._sound_pos = 0
|
||||
self._sounds: Dict[str, Any] = {}
|
||||
self._sounds: dict[str, Any] = {}
|
||||
self._current_sound = default_sound
|
||||
self._mixing = mixing
|
||||
self._loop = loop
|
||||
|
||||
@@ -12,7 +12,6 @@ when a user has finished speaking in a conversation.
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -44,7 +43,7 @@ class BaseTurnAnalyzer(ABC):
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -108,7 +107,7 @@ class BaseTurnAnalyzer(ABC):
|
||||
pass
|
||||
|
||||
@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.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -16,7 +16,6 @@ passed directly to the constructor.
|
||||
|
||||
import os
|
||||
import time
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
@@ -61,9 +60,9 @@ class KrispVivaTurn(BaseTurnAnalyzer):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
model_path: Optional[str] = None,
|
||||
sample_rate: Optional[int] = None,
|
||||
params: Optional[KrispTurnParams] = None,
|
||||
model_path: str | None = None,
|
||||
sample_rate: int | None = None,
|
||||
params: KrispTurnParams | None = None,
|
||||
api_key: str = "",
|
||||
) -> None:
|
||||
"""Initialize the Krisp turn analyzer.
|
||||
@@ -119,9 +118,9 @@ class KrispVivaTurn(BaseTurnAnalyzer):
|
||||
self._last_probability = None
|
||||
self._frame_probabilities = []
|
||||
self._last_state = EndOfTurnState.INCOMPLETE
|
||||
self._speech_stopped_time: Optional[float] = None
|
||||
self._e2e_processing_time_ms: Optional[float] = None
|
||||
self._last_metrics: Optional[TurnMetricsData] = None
|
||||
self._speech_stopped_time: float | None = None
|
||||
self._e2e_processing_time_ms: float | None = None
|
||||
self._last_metrics: TurnMetricsData | None = None
|
||||
|
||||
# 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
|
||||
@@ -214,7 +213,7 @@ class KrispVivaTurn(BaseTurnAnalyzer):
|
||||
return self._frame_probabilities
|
||||
|
||||
@property
|
||||
def last_probability(self) -> Optional[float]:
|
||||
def last_probability(self) -> float | None:
|
||||
"""Get the last turn probability value computed.
|
||||
|
||||
Returns:
|
||||
@@ -348,7 +347,7 @@ class KrispVivaTurn(BaseTurnAnalyzer):
|
||||
self._last_state = 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.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -15,7 +15,7 @@ import asyncio
|
||||
import time
|
||||
from abc import abstractmethod
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
@@ -57,9 +57,7 @@ class BaseSmartTurn(BaseTurnAnalyzer):
|
||||
implement the specific model prediction logic.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, *, sample_rate: Optional[int] = None, params: Optional[SmartTurnParams] = None
|
||||
):
|
||||
def __init__(self, *, sample_rate: int | None = None, params: SmartTurnParams | None = None):
|
||||
"""Initialize the smart turn analyzer.
|
||||
|
||||
Args:
|
||||
@@ -146,7 +144,7 @@ class BaseSmartTurn(BaseTurnAnalyzer):
|
||||
|
||||
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.
|
||||
|
||||
Returns:
|
||||
@@ -178,7 +176,7 @@ class BaseSmartTurn(BaseTurnAnalyzer):
|
||||
self._speech_start_time = 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."""
|
||||
state = EndOfTurnState.INCOMPLETE
|
||||
|
||||
@@ -248,6 +246,6 @@ class BaseSmartTurn(BaseTurnAnalyzer):
|
||||
return state, result_data
|
||||
|
||||
@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."""
|
||||
pass
|
||||
|
||||
@@ -12,7 +12,7 @@ HTTP endpoints for ML-based end-of-turn detection.
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
import numpy as np
|
||||
@@ -33,7 +33,7 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
|
||||
*,
|
||||
url: str,
|
||||
aiohttp_session: aiohttp.ClientSession,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the HTTP smart turn analyzer.
|
||||
@@ -58,7 +58,7 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
|
||||
logger.trace(f"Serialized size: {len(serialized_bytes)} 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."""
|
||||
headers = {"Content-Type": "application/octet-stream"}
|
||||
headers.update(self._headers)
|
||||
@@ -97,14 +97,14 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
|
||||
logger.trace(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")
|
||||
raise SmartTurnTimeoutException(f"Request exceeded {self._params.stop_secs} seconds.")
|
||||
except aiohttp.ClientError as 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.")
|
||||
|
||||
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."""
|
||||
try:
|
||||
serialized_array = self._serialize_array(audio_array)
|
||||
|
||||
@@ -11,7 +11,7 @@ local end-of-turn detection without requiring network connectivity.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from typing import Any, Dict
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
@@ -76,7 +76,7 @@ class LocalCoreMLSmartTurnAnalyzer(BaseSmartTurn):
|
||||
self._turn_model = ct.models.MLModel(core_ml_model_path)
|
||||
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."""
|
||||
inputs = self._turn_processor(
|
||||
audio_array,
|
||||
|
||||
@@ -11,7 +11,7 @@ local end-of-turn detection without requiring network connectivity.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from typing import Any, Dict
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
@@ -87,7 +87,7 @@ class LocalSmartTurnAnalyzerV2(BaseSmartTurn):
|
||||
self._turn_model.eval()
|
||||
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."""
|
||||
inputs = self._turn_processor(
|
||||
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.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
@@ -32,9 +32,7 @@ class LocalSmartTurnAnalyzerV3(BaseSmartTurn):
|
||||
enabling offline operation without network dependencies.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, *, smart_turn_model_path: Optional[str] = None, cpu_count: int = 1, **kwargs
|
||||
):
|
||||
def __init__(self, *, smart_turn_model_path: str | None = None, cpu_count: int = 1, **kwargs):
|
||||
"""Initialize the local ONNX smart-turn-v3 analyzer.
|
||||
|
||||
Args:
|
||||
@@ -138,7 +136,7 @@ class LocalSmartTurnAnalyzerV3(BaseSmartTurn):
|
||||
|
||||
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."""
|
||||
|
||||
def truncate_audio_to_last_n_seconds(
|
||||
|
||||
@@ -7,7 +7,8 @@ Classes:
|
||||
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 loguru import logger
|
||||
@@ -46,10 +47,10 @@ class AICVADAnalyzer(VADAnalyzer):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
vad_context_factory: Optional[Callable[[], Any]] = None,
|
||||
speech_hold_duration: Optional[float] = None,
|
||||
minimum_speech_duration: Optional[float] = None,
|
||||
sensitivity: Optional[float] = None,
|
||||
vad_context_factory: Callable[[], Any] | None = None,
|
||||
speech_hold_duration: float | None = None,
|
||||
minimum_speech_duration: float | None = None,
|
||||
sensitivity: float | None = None,
|
||||
):
|
||||
"""Create an AIC VAD analyzer.
|
||||
|
||||
@@ -77,10 +78,10 @@ class AICVADAnalyzer(VADAnalyzer):
|
||||
super().__init__(sample_rate=None, params=fixed_params)
|
||||
|
||||
self._vad_context_factory = vad_context_factory
|
||||
self._vad_ctx: Optional[Any] = None
|
||||
self._pending_speech_hold_duration: Optional[float] = speech_hold_duration
|
||||
self._pending_minimum_speech_duration: Optional[float] = minimum_speech_duration
|
||||
self._pending_sensitivity: Optional[float] = sensitivity
|
||||
self._vad_ctx: Any | None = None
|
||||
self._pending_speech_hold_duration: float | None = speech_hold_duration
|
||||
self._pending_minimum_speech_duration: float | None = minimum_speech_duration
|
||||
self._pending_sensitivity: float | None = sensitivity
|
||||
|
||||
def bind_vad_context_factory(self, vad_context_factory: Callable[[], Any]):
|
||||
"""Attach or replace the factory post-construction."""
|
||||
|
||||
@@ -12,7 +12,6 @@ Supports 8kHz, 16kHz, 32kHz, 44.1kHz and 48kHz sample rates.
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
@@ -38,10 +37,10 @@ class KrispVivaVadAnalyzer(VADAnalyzer):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
model_path: Optional[str] = None,
|
||||
model_path: str | None = None,
|
||||
frame_duration: int = 10,
|
||||
sample_rate: Optional[int] = None,
|
||||
params: Optional[VADParams] = None,
|
||||
sample_rate: int | None = None,
|
||||
params: VADParams | None = None,
|
||||
):
|
||||
"""Initialize the Krisp VIVA VAD analyzer.
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ Supports 8kHz and 16kHz sample rates.
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
@@ -135,7 +134,7 @@ class SileroVADAnalyzer(VADAnalyzer):
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -15,7 +15,6 @@ import asyncio
|
||||
from abc import ABC, abstractmethod
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -68,7 +67,7 @@ class VADAnalyzer(ABC):
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -12,7 +12,6 @@ and emit events when speech starts, stops, or is actively detected.
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Optional, Type
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -90,7 +89,7 @@ class VADController(BaseObject):
|
||||
self._vad_analyzer = vad_analyzer
|
||||
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.
|
||||
self._speech_activity_time = 0
|
||||
@@ -102,7 +101,7 @@ class VADController(BaseObject):
|
||||
# while in SPEAKING state (e.g. user mutes mic mid-speech).
|
||||
self._last_audio_time: float = 0.0
|
||||
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_stopped", sync=True)
|
||||
@@ -234,7 +233,7 @@ class VADController(BaseObject):
|
||||
"""
|
||||
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.
|
||||
|
||||
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 typing import List, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -72,7 +71,7 @@ class IVRProcessor(FrameProcessor):
|
||||
*,
|
||||
classifier_prompt: str,
|
||||
ivr_prompt: str,
|
||||
ivr_vad_params: Optional[VADParams] = None,
|
||||
ivr_vad_params: VADParams | None = None,
|
||||
):
|
||||
"""Initialize the IVR processor.
|
||||
|
||||
@@ -88,7 +87,7 @@ class IVRProcessor(FrameProcessor):
|
||||
self._classifier_prompt = classifier_prompt
|
||||
|
||||
# Store saved context messages
|
||||
self._saved_messages: List[dict] = []
|
||||
self._saved_messages: list[dict] = []
|
||||
|
||||
# XML pattern aggregation
|
||||
self._aggregator = PatternPairAggregator()
|
||||
@@ -98,7 +97,7 @@ class IVRProcessor(FrameProcessor):
|
||||
self._register_event_handler("on_conversation_detected")
|
||||
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.
|
||||
|
||||
Sets the messages that are saved when switching between
|
||||
@@ -109,7 +108,7 @@ class IVRProcessor(FrameProcessor):
|
||||
"""
|
||||
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.
|
||||
|
||||
Returns:
|
||||
@@ -409,7 +408,7 @@ Remember: Respond with `<dtmf>NUMBER</dtmf>` (single or multiple for sequences),
|
||||
*,
|
||||
llm: LLMService,
|
||||
ivr_prompt: str,
|
||||
ivr_vad_params: Optional[VADParams] = None,
|
||||
ivr_vad_params: VADParams | None = None,
|
||||
):
|
||||
"""Initialize the IVR navigator.
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ Note:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import List, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -71,7 +70,7 @@ class NotifierGate(FrameProcessor):
|
||||
self._notifier = notifier
|
||||
self._task_name = task_name
|
||||
self._gate_opened = True
|
||||
self._gate_task: Optional[asyncio.Task] = None
|
||||
self._gate_task: asyncio.Task | None = None
|
||||
|
||||
async def setup(self, setup: FrameProcessorSetup):
|
||||
"""Set up the processor with required components.
|
||||
@@ -143,7 +142,7 @@ class ClassifierGate(NotifierGate):
|
||||
super().__init__(gate_notifier, task_name="classifier_gate")
|
||||
self._conversation_notifier = conversation_notifier
|
||||
self._conversation_detected = False
|
||||
self._conversation_task: Optional[asyncio.Task] = None
|
||||
self._conversation_task: asyncio.Task | None = None
|
||||
|
||||
async def setup(self, setup: FrameProcessorSetup):
|
||||
"""Set up the processor with required components.
|
||||
@@ -267,7 +266,7 @@ class ClassificationProcessor(FrameProcessor):
|
||||
|
||||
# Voicemail timing state
|
||||
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.set()
|
||||
|
||||
@@ -390,7 +389,7 @@ class ClassificationProcessor(FrameProcessor):
|
||||
self._voicemail_event.wait(), timeout=self._voicemail_response_delay
|
||||
)
|
||||
await asyncio.sleep(0.1)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
await self._call_event_handler("on_voicemail_detected")
|
||||
break
|
||||
|
||||
@@ -423,10 +422,10 @@ class TTSGate(FrameProcessor):
|
||||
super().__init__()
|
||||
self._conversation_notifier = conversation_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._conversation_task: Optional[asyncio.Task] = None
|
||||
self._voicemail_task: Optional[asyncio.Task] = None
|
||||
self._conversation_task: asyncio.Task | None = None
|
||||
self._voicemail_task: asyncio.Task | None = None
|
||||
|
||||
async def setup(self, setup: FrameProcessorSetup):
|
||||
"""Set up the processor with required components.
|
||||
@@ -591,7 +590,7 @@ VOICEMAIL SYSTEM (respond "VOICEMAIL"):
|
||||
*,
|
||||
llm: LLMService,
|
||||
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.
|
||||
|
||||
|
||||
@@ -11,20 +11,16 @@ including data frames, system frames, and control frames for audio, video, text,
|
||||
and LLM processing.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Literal,
|
||||
Mapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
from pipecat.adapters.schemas.tools_schema import ToolsSchema
|
||||
@@ -45,7 +41,7 @@ if TYPE_CHECKING:
|
||||
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.
|
||||
|
||||
Converts a PTS value in nanoseconds to a string representation.
|
||||
@@ -77,20 +73,20 @@ class Frame:
|
||||
|
||||
id: int = field(init=False)
|
||||
name: str = field(init=False)
|
||||
pts: Optional[int] = field(init=False)
|
||||
broadcast_sibling_id: Optional[int] = field(init=False)
|
||||
metadata: Dict[str, Any] = field(init=False)
|
||||
transport_source: Optional[str] = field(init=False)
|
||||
transport_destination: Optional[str] = field(init=False)
|
||||
pts: int | None = field(init=False)
|
||||
broadcast_sibling_id: int | None = field(init=False)
|
||||
metadata: dict[str, Any] = field(init=False)
|
||||
transport_source: str | None = field(init=False)
|
||||
transport_destination: str | None = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
self.id: int = obj_id()
|
||||
self.name: str = f"{self.__class__.__name__}#{obj_count(self)}"
|
||||
self.pts: Optional[int] = None
|
||||
self.broadcast_sibling_id: Optional[int] = None
|
||||
self.metadata: Dict[str, Any] = {}
|
||||
self.transport_source: Optional[str] = None
|
||||
self.transport_destination: Optional[str] = None
|
||||
self.pts: int | None = None
|
||||
self.broadcast_sibling_id: int | None = None
|
||||
self.metadata: dict[str, Any] = {}
|
||||
self.transport_source: str | None = None
|
||||
self.transport_destination: str | None = None
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -183,8 +179,8 @@ class ImageRawFrame:
|
||||
"""
|
||||
|
||||
image: bytes
|
||||
size: Tuple[int, int]
|
||||
format: Optional[str]
|
||||
size: tuple[int, int]
|
||||
format: str | None
|
||||
|
||||
|
||||
#
|
||||
@@ -242,7 +238,7 @@ class TTSAudioRawFrame(OutputAudioRawFrame):
|
||||
context_id: Unique identifier for the TTS context that generated this audio.
|
||||
"""
|
||||
|
||||
context_id: Optional[str] = None
|
||||
context_id: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -268,7 +264,7 @@ class URLImageRawFrame(OutputImageRawFrame):
|
||||
url: URL where the image can be downloaded from.
|
||||
"""
|
||||
|
||||
url: Optional[str] = None
|
||||
url: str | None = None
|
||||
|
||||
def __str__(self):
|
||||
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[OutputImageRawFrame]
|
||||
images: list[OutputImageRawFrame]
|
||||
|
||||
def __str__(self):
|
||||
pts = format_pts(self.pts)
|
||||
@@ -312,7 +308,7 @@ class TextFrame(DataFrame):
|
||||
"""
|
||||
|
||||
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
|
||||
# included in the text.
|
||||
# NOTE: Ideally this would be available at init time with a default value,
|
||||
@@ -357,7 +353,7 @@ class AggregatedTextFrame(TextFrame):
|
||||
"""
|
||||
|
||||
aggregated_by: AggregationType | str
|
||||
context_id: Optional[str] = None
|
||||
context_id: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -375,7 +371,7 @@ class TTSTextFrame(AggregatedTextFrame):
|
||||
context_id: Unique identifier for the TTS context that generated this text.
|
||||
"""
|
||||
|
||||
context_id: Optional[str] = None
|
||||
context_id: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -396,8 +392,8 @@ class TranscriptionFrame(TextFrame):
|
||||
|
||||
user_id: str
|
||||
timestamp: str
|
||||
language: Optional[Language] = None
|
||||
result: Optional[Any] = None
|
||||
language: Language | None = None
|
||||
result: Any | None = None
|
||||
finalized: bool = False
|
||||
|
||||
def __str__(self):
|
||||
@@ -422,8 +418,8 @@ class InterimTranscriptionFrame(TextFrame):
|
||||
text: str
|
||||
user_id: str
|
||||
timestamp: str
|
||||
language: Optional[Language] = None
|
||||
result: Optional[Any] = None
|
||||
language: Language | None = None
|
||||
result: Any | None = None
|
||||
|
||||
def __str__(self):
|
||||
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
|
||||
timestamp: str
|
||||
language: Optional[Language] = None
|
||||
language: Language | None = None
|
||||
|
||||
def __str__(self):
|
||||
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: "LLMContext"
|
||||
context: LLMContext
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -489,7 +485,7 @@ class LLMThoughtStartFrame(ControlFrame):
|
||||
"""
|
||||
|
||||
append_to_context: bool = False
|
||||
llm: Optional[str] = None
|
||||
llm: str | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
@@ -567,8 +563,8 @@ class LLMMessagesAppendFrame(DataFrame):
|
||||
run_llm: Whether the context update should be sent to the LLM.
|
||||
"""
|
||||
|
||||
messages: List[dict]
|
||||
run_llm: Optional[bool] = None
|
||||
messages: list[dict]
|
||||
run_llm: bool | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -583,8 +579,8 @@ class LLMMessagesUpdateFrame(DataFrame):
|
||||
run_llm: Whether the context update should be sent to the LLM.
|
||||
"""
|
||||
|
||||
messages: List[dict]
|
||||
run_llm: Optional[bool] = None
|
||||
messages: list[dict]
|
||||
run_llm: bool | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -600,8 +596,8 @@ class LLMMessagesTransformFrame(DataFrame):
|
||||
run_llm: Whether the context update should be sent to the LLM.
|
||||
"""
|
||||
|
||||
transform: Callable[[List["LLMContextMessage"]], List["LLMContextMessage"]]
|
||||
run_llm: Optional[bool] = None
|
||||
transform: Callable[[list[LLMContextMessage]], list[LLMContextMessage]]
|
||||
run_llm: bool | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -616,7 +612,7 @@ class LLMSetToolsFrame(DataFrame):
|
||||
tools: List of tool/function definitions for the LLM.
|
||||
"""
|
||||
|
||||
tools: List[dict] | ToolsSchema | "NotGiven"
|
||||
tools: list[dict] | ToolsSchema | NotGiven
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -668,8 +664,8 @@ class FunctionCallResultProperties:
|
||||
Only meaningful for async function calls (``cancel_on_interruption=False``).
|
||||
"""
|
||||
|
||||
run_llm: Optional[bool] = None
|
||||
on_context_updated: Optional[Callable[[], Awaitable[None]]] = None
|
||||
run_llm: bool | None = None
|
||||
on_context_updated: Callable[[], Awaitable[None]] | None = None
|
||||
is_final: bool = True
|
||||
|
||||
|
||||
@@ -694,8 +690,8 @@ class FunctionCallResultFrame(DataFrame, UninterruptibleFrame):
|
||||
tool_call_id: str
|
||||
arguments: Any
|
||||
result: Any
|
||||
run_llm: Optional[bool] = None
|
||||
properties: Optional[FunctionCallResultProperties] = None
|
||||
run_llm: bool | None = None
|
||||
properties: FunctionCallResultProperties | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -711,7 +707,7 @@ class TTSSpeakFrame(DataFrame):
|
||||
"""
|
||||
|
||||
text: str
|
||||
append_to_context: Optional[bool] = None
|
||||
append_to_context: bool | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -752,8 +748,8 @@ class OutputDTMFFrame(DTMFFrame, DataFrame):
|
||||
:meth:`from_string` to build this from a string like ``"123#"``.
|
||||
"""
|
||||
|
||||
button: Optional[KeypadEntry] = None
|
||||
buttons: Optional[List[KeypadEntry]] = None
|
||||
button: KeypadEntry | None = None
|
||||
buttons: list[KeypadEntry] | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
@@ -766,7 +762,7 @@ class OutputDTMFFrame(DTMFFrame, DataFrame):
|
||||
return f"{self.name}(buttons: {self.to_string()})"
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
@@ -820,7 +816,7 @@ class StartFrame(SystemFrame):
|
||||
enable_tracing: bool = False
|
||||
enable_usage_metrics: bool = False
|
||||
report_only_initial_ttfb: bool = False
|
||||
tracing_context: Optional["TracingContext"] = None
|
||||
tracing_context: TracingContext | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -834,7 +830,7 @@ class CancelFrame(SystemFrame):
|
||||
reason: Optional reason for pushing a cancel frame.
|
||||
"""
|
||||
|
||||
reason: Optional[Any] = None
|
||||
reason: Any | None = None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}(reason: {self.reason})"
|
||||
@@ -857,8 +853,8 @@ class ErrorFrame(SystemFrame):
|
||||
|
||||
error: str
|
||||
fatal: bool = False
|
||||
processor: Optional["FrameProcessor"] = None
|
||||
exception: Optional[Exception] = None
|
||||
processor: FrameProcessor | None = None
|
||||
exception: Exception | None = None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}(error: {self.error}, fatal: {self.fatal})"
|
||||
@@ -891,7 +887,7 @@ class FrameProcessorPauseUrgentFrame(SystemFrame):
|
||||
processor: The frame processor to pause.
|
||||
"""
|
||||
|
||||
processor: "FrameProcessor"
|
||||
processor: FrameProcessor
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -906,7 +902,7 @@ class FrameProcessorResumeUrgentFrame(SystemFrame):
|
||||
processor: The frame processor to resume.
|
||||
"""
|
||||
|
||||
processor: "FrameProcessor"
|
||||
processor: FrameProcessor
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1050,7 +1046,7 @@ class MetricsFrame(SystemFrame):
|
||||
data: List of metrics data collected by the processor.
|
||||
"""
|
||||
|
||||
data: List[MetricsData]
|
||||
data: list[MetricsData]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1156,12 +1152,12 @@ class UserImageRequestFrame(SystemFrame):
|
||||
"""
|
||||
|
||||
user_id: str
|
||||
text: Optional[str] = None
|
||||
append_to_context: Optional[bool] = None
|
||||
video_source: Optional[str] = None
|
||||
function_name: Optional[str] = None
|
||||
tool_call_id: Optional[str] = None
|
||||
result_callback: Optional[Any] = None
|
||||
text: str | None = None
|
||||
append_to_context: bool | None = None
|
||||
video_source: str | None = None
|
||||
function_name: str | None = None
|
||||
tool_call_id: str | None = None
|
||||
result_callback: Any | None = None
|
||||
|
||||
def __str__(self):
|
||||
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 = ""
|
||||
text: Optional[str] = None
|
||||
append_to_context: Optional[bool] = None
|
||||
request: Optional[UserImageRequestFrame] = None
|
||||
text: str | None = None
|
||||
append_to_context: bool | None = None
|
||||
request: UserImageRequestFrame | None = None
|
||||
|
||||
def __str__(self):
|
||||
pts = format_pts(self.pts)
|
||||
@@ -1266,8 +1262,8 @@ class AssistantImageRawFrame(OutputImageRawFrame):
|
||||
original_mime_type: The MIME type of the original image data.
|
||||
"""
|
||||
|
||||
original_data: Optional[bytes] = None
|
||||
original_mime_type: Optional[str] = None
|
||||
original_data: bytes | None = None
|
||||
original_mime_type: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1296,8 +1292,8 @@ class OutputDTMFUrgentFrame(DTMFFrame, SystemFrame):
|
||||
:meth:`from_string` to build this from a string like ``"123#"``.
|
||||
"""
|
||||
|
||||
button: Optional[KeypadEntry] = None
|
||||
buttons: Optional[List[KeypadEntry]] = None
|
||||
button: KeypadEntry | None = None
|
||||
buttons: list[KeypadEntry] | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
@@ -1310,7 +1306,7 @@ class OutputDTMFUrgentFrame(DTMFFrame, SystemFrame):
|
||||
return f"{self.name}(buttons: {self.to_string()})"
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
@@ -1349,8 +1345,8 @@ class SpeechControlParamsFrame(SystemFrame):
|
||||
turn_params: Current turn-taking analysis parameters.
|
||||
"""
|
||||
|
||||
vad_params: Optional[VADParams] = None
|
||||
turn_params: Optional[BaseTurnParams] = None
|
||||
vad_params: VADParams | None = None
|
||||
turn_params: BaseTurnParams | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1396,7 +1392,7 @@ class ServiceSwitcherRequestMetadataFrame(ControlFrame):
|
||||
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[Any] = None
|
||||
reason: Any | None = None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}(reason: {self.reason})"
|
||||
@@ -1475,7 +1471,7 @@ class CancelTaskFrame(TaskSystemFrame):
|
||||
reason: Optional reason for pushing a cancel frame.
|
||||
"""
|
||||
|
||||
reason: Optional[Any] = None
|
||||
reason: Any | None = None
|
||||
|
||||
def __str__(self):
|
||||
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[Any] = None
|
||||
reason: Any | None = None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}(reason: {self.reason})"
|
||||
@@ -1598,7 +1594,7 @@ class FrameProcessorPauseFrame(ControlFrame):
|
||||
processor: The frame processor to pause.
|
||||
"""
|
||||
|
||||
processor: "FrameProcessor"
|
||||
processor: FrameProcessor
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1613,7 +1609,7 @@ class FrameProcessorResumeFrame(ControlFrame):
|
||||
processor: The frame processor to resume.
|
||||
"""
|
||||
|
||||
processor: "FrameProcessor"
|
||||
processor: FrameProcessor
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1624,7 +1620,7 @@ class LLMFullResponseStartFrame(ControlFrame):
|
||||
more TextFrames and a final LLMFullResponseEndFrame.
|
||||
"""
|
||||
|
||||
skip_tts: Optional[bool] = field(init=False)
|
||||
skip_tts: bool | None = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
@@ -1635,7 +1631,7 @@ class LLMFullResponseStartFrame(ControlFrame):
|
||||
class LLMFullResponseEndFrame(ControlFrame):
|
||||
"""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):
|
||||
super().__post_init__()
|
||||
@@ -1665,7 +1661,7 @@ class LLMSummarizeContextFrame(ControlFrame):
|
||||
is used.
|
||||
"""
|
||||
|
||||
config: Optional["LLMContextSummaryConfig"] = None
|
||||
config: LLMContextSummaryConfig | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1692,11 +1688,11 @@ class LLMContextSummaryRequestFrame(ControlFrame):
|
||||
"""
|
||||
|
||||
request_id: str
|
||||
context: "LLMContext"
|
||||
context: LLMContext
|
||||
min_messages_to_keep: int
|
||||
target_context_tokens: int
|
||||
summarization_prompt: str
|
||||
summarization_timeout: Optional[float] = None
|
||||
summarization_timeout: float | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1718,7 +1714,7 @@ class LLMContextSummaryResultFrame(ControlFrame, UninterruptibleFrame):
|
||||
request_id: str
|
||||
summary: str
|
||||
last_summarized_index: int
|
||||
error: Optional[str] = None
|
||||
error: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1745,7 +1741,7 @@ class FunctionCallInProgressFrame(ControlFrame, UninterruptibleFrame):
|
||||
tool_call_id: str
|
||||
arguments: Any
|
||||
cancel_on_interruption: bool = False
|
||||
group_id: Optional[str] = None
|
||||
group_id: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1781,7 +1777,7 @@ class TTSStartedFrame(ControlFrame):
|
||||
context_id: Unique identifier for this TTS context.
|
||||
"""
|
||||
|
||||
context_id: Optional[str] = None
|
||||
context_id: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1792,7 +1788,7 @@ class TTSStoppedFrame(ControlFrame):
|
||||
context_id: Unique identifier for this TTS context.
|
||||
"""
|
||||
|
||||
context_id: Optional[str] = None
|
||||
context_id: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1817,8 +1813,8 @@ class ServiceUpdateSettingsFrame(ControlFrame, UninterruptibleFrame):
|
||||
"""
|
||||
|
||||
settings: Mapping[str, Any] = field(default_factory=dict)
|
||||
delta: Optional["ServiceSettings"] = None
|
||||
service: Optional["FrameProcessor"] = None
|
||||
delta: ServiceSettings | None = None
|
||||
service: FrameProcessor | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1942,4 +1938,4 @@ class ManuallySwitchServiceFrame(ServiceSwitcherFrame):
|
||||
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.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -25,7 +23,7 @@ class MetricsData(BaseModel):
|
||||
"""
|
||||
|
||||
processor: str
|
||||
model: Optional[str] = None
|
||||
model: str | None = None
|
||||
|
||||
|
||||
class TTFBMetricsData(MetricsData):
|
||||
@@ -62,9 +60,9 @@ class LLMTokenUsage(BaseModel):
|
||||
prompt_tokens: int
|
||||
completion_tokens: int
|
||||
total_tokens: int
|
||||
cache_read_input_tokens: Optional[int] = None
|
||||
cache_creation_input_tokens: Optional[int] = None
|
||||
reasoning_tokens: Optional[int] = None
|
||||
cache_read_input_tokens: int | None = None
|
||||
cache_creation_input_tokens: int | None = None
|
||||
reasoning_tokens: int | None = None
|
||||
|
||||
|
||||
class LLMUsageMetricsData(MetricsData):
|
||||
|
||||
@@ -12,8 +12,7 @@ for logging, debugging, analytics, and monitoring pipeline behavior.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing_extensions import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pipecat.frames.frames import Frame
|
||||
from pipecat.utils.base_object import BaseObject
|
||||
|
||||
@@ -13,7 +13,6 @@ understanding frame flow between processors.
|
||||
|
||||
from dataclasses import fields, is_dataclass
|
||||
from enum import Enum, auto
|
||||
from typing import Dict, Optional, Set, Tuple, Type, Union
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -75,10 +74,10 @@ class DebugLogObserver(BaseObserver):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
frame_types: Optional[
|
||||
Union[Tuple[Type[Frame], ...], Dict[Type[Frame], Optional[Tuple[Type, FrameEndpoint]]]]
|
||||
] = None,
|
||||
exclude_fields: Optional[Set[str]] = None,
|
||||
frame_types: tuple[type[Frame], ...]
|
||||
| dict[type[Frame], tuple[type, FrameEndpoint] | None]
|
||||
| None = None,
|
||||
exclude_fields: set[str] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the debug log observer.
|
||||
|
||||
@@ -11,8 +11,6 @@ allowing developers to monitor performance metrics, token usage, and other
|
||||
statistics in real-time.
|
||||
"""
|
||||
|
||||
from typing import Optional, Set, Type
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.frames.frames import MetricsFrame
|
||||
@@ -60,7 +58,7 @@ class MetricsLogObserver(BaseObserver):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
include_metrics: Optional[Set[Type[MetricsData]]] = None,
|
||||
include_metrics: set[type[MetricsData]] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the metrics log observer.
|
||||
|
||||
@@ -36,7 +36,6 @@ Example::
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional, Tuple, Type
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -84,7 +83,7 @@ class StartupTimingReport(BaseModel):
|
||||
|
||||
start_time: float
|
||||
total_duration_secs: float
|
||||
processor_timings: List[ProcessorStartupTiming] = Field(default_factory=list)
|
||||
processor_timings: list[ProcessorStartupTiming] = Field(default_factory=list)
|
||||
|
||||
|
||||
class TransportTimingReport(BaseModel):
|
||||
@@ -98,8 +97,8 @@ class TransportTimingReport(BaseModel):
|
||||
"""
|
||||
|
||||
start_time: float
|
||||
bot_connected_secs: Optional[float] = None
|
||||
client_connected_secs: Optional[float] = None
|
||||
bot_connected_secs: float | None = None
|
||||
client_connected_secs: float | None = None
|
||||
|
||||
|
||||
class StartupTimingObserver(BaseObserver):
|
||||
@@ -157,7 +156,7 @@ class StartupTimingObserver(BaseObserver):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
processor_types: Optional[Tuple[Type[FrameProcessor], ...]] = None,
|
||||
processor_types: tuple[type[FrameProcessor], ...] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the startup timing observer.
|
||||
@@ -171,13 +170,13 @@ class StartupTimingObserver(BaseObserver):
|
||||
self._processor_types = processor_types
|
||||
|
||||
# Map processor ID -> arrival info.
|
||||
self._arrivals: Dict[int, _ArrivalInfo] = {}
|
||||
self._arrivals: dict[int, _ArrivalInfo] = {}
|
||||
|
||||
# Collected timings in pipeline order.
|
||||
self._timings: List[ProcessorStartupTiming] = []
|
||||
self._timings: list[ProcessorStartupTiming] = []
|
||||
|
||||
# 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.
|
||||
self._startup_timing_reported = False
|
||||
@@ -186,13 +185,13 @@ class StartupTimingObserver(BaseObserver):
|
||||
self._transport_timing_reported = False
|
||||
|
||||
# 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).
|
||||
self._bot_connected_secs: Optional[float] = None
|
||||
self._bot_connected_secs: float | None = None
|
||||
|
||||
# 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_transport_timing_report")
|
||||
|
||||
@@ -14,7 +14,6 @@ is measured. Optionally collects per-service latency breakdown metrics
|
||||
|
||||
import time
|
||||
from collections import deque
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -48,7 +47,7 @@ class TTFBBreakdownMetrics(BaseModel):
|
||||
"""
|
||||
|
||||
processor: str
|
||||
model: Optional[str] = None
|
||||
model: str | None = None
|
||||
start_time: float
|
||||
duration_secs: float
|
||||
|
||||
@@ -105,13 +104,13 @@ class LatencyBreakdown(BaseModel):
|
||||
this cycle. Empty if no function calls occurred.
|
||||
"""
|
||||
|
||||
ttfb: List[TTFBBreakdownMetrics] = Field(default_factory=list)
|
||||
text_aggregation: Optional[TextAggregationBreakdownMetrics] = None
|
||||
user_turn_start_time: Optional[float] = None
|
||||
user_turn_secs: Optional[float] = None
|
||||
function_calls: List[FunctionCallMetrics] = Field(default_factory=list)
|
||||
ttfb: list[TTFBBreakdownMetrics] = Field(default_factory=list)
|
||||
text_aggregation: TextAggregationBreakdownMetrics | None = None
|
||||
user_turn_start_time: float | None = None
|
||||
user_turn_secs: float | None = None
|
||||
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.
|
||||
|
||||
Collects all sub-metrics into a flat list, sorts by ``start_time``,
|
||||
@@ -120,7 +119,7 @@ class LatencyBreakdown(BaseModel):
|
||||
Returns:
|
||||
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:
|
||||
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.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self._user_stopped_time: Optional[float] = None
|
||||
self._user_turn_start_time: Optional[float] = None
|
||||
self._user_turn: Optional[float] = None
|
||||
self._user_stopped_time: float | None = None
|
||||
self._user_turn_start_time: float | None = None
|
||||
self._user_turn: float | None = None
|
||||
|
||||
# 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
|
||||
|
||||
# Frame deduplication (bounded deque + set pattern)
|
||||
@@ -194,10 +193,10 @@ class UserBotLatencyObserver(BaseObserver):
|
||||
self._frame_history: deque = deque(maxlen=max_frames)
|
||||
|
||||
# Per-cycle metric accumulators
|
||||
self._ttfb: List[TTFBBreakdownMetrics] = []
|
||||
self._text_aggregation: Optional[TextAggregationBreakdownMetrics] = None
|
||||
self._function_call_starts: Dict[str, tuple[str, float]] = {}
|
||||
self._function_call_metrics: List[FunctionCallMetrics] = []
|
||||
self._ttfb: list[TTFBBreakdownMetrics] = []
|
||||
self._text_aggregation: TextAggregationBreakdownMetrics | None = None
|
||||
self._function_call_starts: dict[str, tuple[str, float]] = {}
|
||||
self._function_call_metrics: list[FunctionCallMetrics] = []
|
||||
|
||||
self._register_event_handler("on_latency_measured")
|
||||
self._register_event_handler("on_latency_breakdown")
|
||||
|
||||
@@ -12,8 +12,8 @@ tasks that manage the lifecycle and execution of frame processing pipelines.
|
||||
|
||||
import asyncio
|
||||
from abc import abstractmethod
|
||||
from collections.abc import AsyncIterable, Iterable
|
||||
from dataclasses import dataclass
|
||||
from typing import AsyncIterable, Iterable
|
||||
|
||||
from pipecat.frames.frames import Frame
|
||||
from pipecat.utils.base_object import BaseObject
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""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.pipeline.service_switcher import (
|
||||
@@ -28,8 +28,8 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llms: List[LLMService],
|
||||
strategy_type: Type[StrategyType] = ServiceSwitcherStrategyManual,
|
||||
llms: list[LLMService],
|
||||
strategy_type: type[StrategyType] = ServiceSwitcherStrategyManual,
|
||||
):
|
||||
"""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)
|
||||
|
||||
@property
|
||||
def llms(self) -> List[LLMService]:
|
||||
def llms(self) -> list[LLMService]:
|
||||
"""Get the list of LLMs managed by this switcher.
|
||||
|
||||
Returns:
|
||||
@@ -58,7 +58,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -75,11 +75,11 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
|
||||
|
||||
def register_function(
|
||||
self,
|
||||
function_name: Optional[str],
|
||||
function_name: str | None,
|
||||
handler: Any,
|
||||
*,
|
||||
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.
|
||||
|
||||
@@ -105,7 +105,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
|
||||
handler: DirectFunction,
|
||||
*,
|
||||
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.
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ handling of pipeline lifecycle events.
|
||||
"""
|
||||
|
||||
from itertools import chain
|
||||
from typing import Dict, List
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -51,7 +50,7 @@ class ParallelPipeline(BasePipeline):
|
||||
self._pipelines = []
|
||||
|
||||
self._seen_ids = set()
|
||||
self._frame_counter: Dict[int, int] = {}
|
||||
self._frame_counter: dict[int, int] = {}
|
||||
self._synchronizing: bool = False
|
||||
self._buffered_frames: list[tuple[Frame, FrameDirection]] = []
|
||||
|
||||
@@ -93,7 +92,7 @@ class ParallelPipeline(BasePipeline):
|
||||
return self._pipelines
|
||||
|
||||
@property
|
||||
def entry_processors(self) -> List["FrameProcessor"]:
|
||||
def entry_processors(self) -> list["FrameProcessor"]:
|
||||
"""Return the list of entry processors for this processor.
|
||||
|
||||
Entry processors are the first processors in a compound processor
|
||||
@@ -106,7 +105,7 @@ class ParallelPipeline(BasePipeline):
|
||||
"""
|
||||
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.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -11,7 +11,7 @@ in sequence and manages frame flow between them, along with helper classes
|
||||
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.pipeline.base_pipeline import BasePipeline
|
||||
@@ -98,10 +98,10 @@ class Pipeline(BasePipeline):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
processors: List[FrameProcessor],
|
||||
processors: list[FrameProcessor],
|
||||
*,
|
||||
source: Optional[FrameProcessor] = None,
|
||||
sink: Optional[FrameProcessor] = None,
|
||||
source: FrameProcessor | None = None,
|
||||
sink: FrameProcessor | None = None,
|
||||
):
|
||||
"""Initialize the pipeline with a list of processors.
|
||||
|
||||
@@ -116,7 +116,7 @@ class Pipeline(BasePipeline):
|
||||
# downstream outside of the pipeline.
|
||||
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._processors: List[FrameProcessor] = [self._source] + processors + [self._sink]
|
||||
self._processors: list[FrameProcessor] = [self._source] + processors + [self._sink]
|
||||
|
||||
self._link_processors()
|
||||
|
||||
@@ -137,7 +137,7 @@ class Pipeline(BasePipeline):
|
||||
return self._processors
|
||||
|
||||
@property
|
||||
def entry_processors(self) -> List["FrameProcessor"]:
|
||||
def entry_processors(self) -> list["FrameProcessor"]:
|
||||
"""Return the list of entry processors for this processor.
|
||||
|
||||
Entry processors are the first processors in a compound processor
|
||||
|
||||
@@ -14,7 +14,6 @@ management.
|
||||
import asyncio
|
||||
import gc
|
||||
import signal
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -34,11 +33,11 @@ class PipelineRunner(BaseObject):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
name: str | None = None,
|
||||
handle_sigint: bool = True,
|
||||
handle_sigterm: bool = False,
|
||||
force_gc: bool = False,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||
loop: asyncio.AbstractEventLoop | None = None,
|
||||
):
|
||||
"""Initialize the pipeline runner.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Note:
|
||||
@@ -62,7 +62,7 @@ class ServiceSwitcherStrategy(BaseObject):
|
||||
self._register_event_handler("on_service_switched")
|
||||
|
||||
@property
|
||||
def services(self) -> List[FrameProcessor]:
|
||||
def services(self) -> list[FrameProcessor]:
|
||||
"""Return the list of available services."""
|
||||
return self._services
|
||||
|
||||
@@ -73,7 +73,7 @@ class ServiceSwitcherStrategy(BaseObject):
|
||||
|
||||
async def handle_frame(
|
||||
self, frame: ServiceSwitcherFrame, direction: FrameDirection
|
||||
) -> Optional[FrameProcessor]:
|
||||
) -> FrameProcessor | None:
|
||||
"""Handle a frame that controls service switching.
|
||||
|
||||
The base implementation returns ``None`` for all frames. Subclasses
|
||||
@@ -88,7 +88,7 @@ class ServiceSwitcherStrategy(BaseObject):
|
||||
"""
|
||||
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.
|
||||
|
||||
Called by ``ServiceSwitcher`` when a non-fatal ``ErrorFrame`` is pushed
|
||||
@@ -103,7 +103,7 @@ class ServiceSwitcherStrategy(BaseObject):
|
||||
"""
|
||||
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.
|
||||
|
||||
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(
|
||||
self, frame: ServiceSwitcherFrame, direction: FrameDirection
|
||||
) -> Optional[FrameProcessor]:
|
||||
) -> FrameProcessor | None:
|
||||
"""Handle a frame that controls service switching.
|
||||
|
||||
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.
|
||||
|
||||
Switches to the next service in the list. The failed service remains
|
||||
@@ -223,8 +223,8 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
services: List[FrameProcessor],
|
||||
strategy_type: Type[StrategyType] = ServiceSwitcherStrategyManual,
|
||||
services: list[FrameProcessor],
|
||||
strategy_type: type[StrategyType] = ServiceSwitcherStrategyManual,
|
||||
):
|
||||
"""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
|
||||
|
||||
@property
|
||||
def services(self) -> List[FrameProcessor]:
|
||||
def services(self) -> list[FrameProcessor]:
|
||||
"""Return the list of available services."""
|
||||
return self._services
|
||||
|
||||
@staticmethod
|
||||
def _make_pipeline_definitions(
|
||||
services: List[FrameProcessor], strategy: ServiceSwitcherStrategy
|
||||
) -> List[Any]:
|
||||
services: list[FrameProcessor], strategy: ServiceSwitcherStrategy
|
||||
) -> list[Any]:
|
||||
pipelines = []
|
||||
for service in services:
|
||||
pipelines.append(ServiceSwitcher._make_pipeline_definition(service, strategy))
|
||||
|
||||
@@ -20,7 +20,6 @@ import asyncio
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from itertools import chain
|
||||
from typing import List
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -215,7 +214,7 @@ class SyncParallelPipeline(BasePipeline):
|
||||
return self._pipelines
|
||||
|
||||
@property
|
||||
def entry_processors(self) -> List["FrameProcessor"]:
|
||||
def entry_processors(self) -> list["FrameProcessor"]:
|
||||
"""Return the list of entry processors for this 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]
|
||||
|
||||
def processors_with_metrics(self) -> List[FrameProcessor]:
|
||||
def processors_with_metrics(self) -> list[FrameProcessor]:
|
||||
"""Collect processors that can generate metrics from all parallel pipelines.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -14,8 +14,9 @@ including heartbeats, idle detection, and observer integration.
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import os
|
||||
from collections.abc import AsyncIterable, Iterable
|
||||
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 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.
|
||||
|
||||
Args:
|
||||
@@ -134,7 +135,7 @@ class PipelineParams(BaseModel):
|
||||
heartbeats_monitor_secs: float = HEARTBEAT_MONITOR_SECS
|
||||
report_only_initial_ttfb: bool = False
|
||||
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):
|
||||
@@ -190,22 +191,22 @@ class PipelineTask(BasePipelineTask):
|
||||
self,
|
||||
pipeline: BasePipeline,
|
||||
*,
|
||||
params: Optional[PipelineParams] = None,
|
||||
additional_span_attributes: Optional[dict] = None,
|
||||
params: PipelineParams | None = None,
|
||||
additional_span_attributes: dict | None = None,
|
||||
cancel_on_idle_timeout: bool = True,
|
||||
cancel_timeout_secs: float = CANCEL_TIMEOUT_SECS,
|
||||
check_dangling_tasks: bool = True,
|
||||
clock: Optional[BaseClock] = None,
|
||||
conversation_id: Optional[str] = None,
|
||||
clock: BaseClock | None = None,
|
||||
conversation_id: str | None = None,
|
||||
enable_tracing: bool = False,
|
||||
enable_turn_tracking: bool = True,
|
||||
enable_rtvi: bool = True,
|
||||
idle_timeout_frames: Tuple[Type[Frame], ...] = (BotSpeakingFrame, UserSpeakingFrame),
|
||||
idle_timeout_secs: Optional[float] = IDLE_TIMEOUT_SECS,
|
||||
observers: Optional[List[BaseObserver]] = None,
|
||||
rtvi_processor: Optional[RTVIProcessor] = None,
|
||||
rtvi_observer_params: Optional[RTVIObserverParams] = None,
|
||||
task_manager: Optional[BaseTaskManager] = None,
|
||||
idle_timeout_frames: tuple[type[Frame], ...] = (BotSpeakingFrame, UserSpeakingFrame),
|
||||
idle_timeout_secs: float | None = IDLE_TIMEOUT_SECS,
|
||||
observers: list[BaseObserver] | None = None,
|
||||
rtvi_processor: RTVIProcessor | None = None,
|
||||
rtvi_observer_params: RTVIObserverParams | None = None,
|
||||
task_manager: BaseTaskManager | None = None,
|
||||
):
|
||||
"""Initialize the PipelineTask.
|
||||
|
||||
@@ -246,10 +247,10 @@ class PipelineTask(BasePipelineTask):
|
||||
self._enable_turn_tracking = enable_turn_tracking
|
||||
self._idle_timeout_secs = idle_timeout_secs
|
||||
observers = observers or []
|
||||
self._turn_tracking_observer: Optional[TurnTrackingObserver] = None
|
||||
self._user_bot_latency_observer: Optional[UserBotLatencyObserver] = None
|
||||
self._turn_trace_observer: Optional[TurnTraceObserver] = None
|
||||
self._tracing_context: Optional[TracingContext] = None
|
||||
self._turn_tracking_observer: TurnTrackingObserver | None = None
|
||||
self._user_bot_latency_observer: UserBotLatencyObserver | None = None
|
||||
self._turn_trace_observer: TurnTraceObserver | None = None
|
||||
self._tracing_context: TracingContext | None = None
|
||||
if self._enable_turn_tracking:
|
||||
self._turn_tracking_observer = TurnTrackingObserver()
|
||||
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.
|
||||
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
|
||||
# down queue we add it to the heartbeat queue for processing.
|
||||
self._heartbeat_queue = asyncio.Queue()
|
||||
self._heartbeat_push_task: Optional[asyncio.Task] = None
|
||||
self._heartbeat_monitor_task: Optional[asyncio.Task] = None
|
||||
self._heartbeat_push_task: asyncio.Task | None = None
|
||||
self._heartbeat_monitor_task: asyncio.Task | None = None
|
||||
|
||||
# RTVI support
|
||||
self._rtvi = None
|
||||
@@ -323,7 +324,7 @@ class PipelineTask(BasePipelineTask):
|
||||
# processor we consider the pipeline is not idle. We use an observer
|
||||
# which will be listening any part of the pipeline.
|
||||
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:
|
||||
idle_frame_observer = IdleFrameObserver(
|
||||
idle_event=self._idle_event,
|
||||
@@ -365,8 +366,8 @@ class PipelineTask(BasePipelineTask):
|
||||
# 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
|
||||
# types.
|
||||
self._reached_upstream_types: Set[Type[Frame]] = set()
|
||||
self._reached_downstream_types: Set[Type[Frame]] = set()
|
||||
self._reached_upstream_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_downstream")
|
||||
self._register_event_handler("on_idle_timeout")
|
||||
@@ -395,7 +396,7 @@ class PipelineTask(BasePipelineTask):
|
||||
return self._pipeline
|
||||
|
||||
@property
|
||||
def turn_tracking_observer(self) -> Optional[TurnTrackingObserver]:
|
||||
def turn_tracking_observer(self) -> TurnTrackingObserver | None:
|
||||
"""Get the turn tracking observer if enabled.
|
||||
|
||||
Returns:
|
||||
@@ -404,7 +405,7 @@ class PipelineTask(BasePipelineTask):
|
||||
return self._turn_tracking_observer
|
||||
|
||||
@property
|
||||
def turn_trace_observer(self) -> Optional[TurnTraceObserver]:
|
||||
def turn_trace_observer(self) -> TurnTraceObserver | None:
|
||||
"""Get the turn trace observer if enabled.
|
||||
|
||||
Returns:
|
||||
@@ -424,7 +425,7 @@ class PipelineTask(BasePipelineTask):
|
||||
return self._rtvi
|
||||
|
||||
@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.
|
||||
|
||||
Returns:
|
||||
@@ -433,7 +434,7 @@ class PipelineTask(BasePipelineTask):
|
||||
return tuple(self._reached_upstream_types)
|
||||
|
||||
@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.
|
||||
|
||||
Returns:
|
||||
@@ -457,7 +458,7 @@ class PipelineTask(BasePipelineTask):
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -465,7 +466,7 @@ class PipelineTask(BasePipelineTask):
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -473,7 +474,7 @@ class PipelineTask(BasePipelineTask):
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -481,7 +482,7 @@ class PipelineTask(BasePipelineTask):
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -509,7 +510,7 @@ class PipelineTask(BasePipelineTask):
|
||||
logger.debug(f"Task {self} scheduled to stop when done")
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -597,7 +598,7 @@ class PipelineTask(BasePipelineTask):
|
||||
for frame in frames:
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -685,7 +686,7 @@ class PipelineTask(BasePipelineTask):
|
||||
self._pipeline_end_event.wait(), timeout=self._cancel_timeout_secs
|
||||
)
|
||||
logger.debug(f"{self}: {frame} reached the end of the pipeline.")
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.warning(
|
||||
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
|
||||
logger.trace(f"{self}: heartbeat frame processed in {process_time} seconds")
|
||||
self._heartbeat_queue.task_done()
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.warning(
|
||||
f"{self}: heartbeat frame not received for more than {wait_time} seconds"
|
||||
)
|
||||
@@ -913,7 +914,7 @@ class PipelineTask(BasePipelineTask):
|
||||
try:
|
||||
await asyncio.wait_for(self._idle_event.wait(), timeout=self._idle_timeout_secs)
|
||||
self._idle_event.clear()
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
running = await self._idle_timeout_detected()
|
||||
|
||||
async def _idle_timeout_detected(self) -> bool:
|
||||
@@ -972,7 +973,7 @@ class PipelineTask(BasePipelineTask):
|
||||
if 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."""
|
||||
start_metadata = {}
|
||||
|
||||
@@ -981,7 +982,7 @@ class PipelineTask(BasePipelineTask):
|
||||
|
||||
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."""
|
||||
if isinstance(processor, processor_type):
|
||||
return processor
|
||||
|
||||
@@ -12,7 +12,7 @@ the main pipeline execution.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any
|
||||
|
||||
from attr import dataclass
|
||||
|
||||
@@ -61,7 +61,7 @@ class TaskObserver(BaseObserver):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
observers: Optional[List[BaseObserver]] = None,
|
||||
observers: list[BaseObserver] | None = None,
|
||||
task_manager: BaseTaskManager,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -75,7 +75,7 @@ class TaskObserver(BaseObserver):
|
||||
super().__init__(**kwargs)
|
||||
self._observers = observers or []
|
||||
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
|
||||
)
|
||||
|
||||
@@ -164,7 +164,7 @@ class TaskObserver(BaseObserver):
|
||||
proxy = Proxy(queue=queue, task=task, observer=observer)
|
||||
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."""
|
||||
proxies = {}
|
||||
for observer in observers:
|
||||
|
||||
@@ -12,7 +12,6 @@ for downstream processing by LLM context aggregators.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
from pipecat.audio.dtmf.types import KeypadEntry
|
||||
from pipecat.frames.frames import (
|
||||
@@ -62,7 +61,7 @@ class DTMFAggregator(FrameProcessor):
|
||||
self._prefix = prefix
|
||||
|
||||
self._digit_event = asyncio.Event()
|
||||
self._aggregation_task: Optional[asyncio.Task] = None
|
||||
self._aggregation_task: asyncio.Task | None = None
|
||||
|
||||
async def cleanup(self) -> None:
|
||||
"""Clean up resources."""
|
||||
@@ -130,7 +129,7 @@ class DTMFAggregator(FrameProcessor):
|
||||
try:
|
||||
await asyncio.wait_for(self._digit_event.wait(), timeout=self._idle_timeout)
|
||||
self._digit_event.clear()
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
if self._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.
|
||||
"""
|
||||
|
||||
from typing import List, Tuple
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.frames.frames import Frame, SystemFrame
|
||||
@@ -48,7 +46,7 @@ class GatedAggregator(FrameProcessor):
|
||||
self._gate_close_fn = gate_close_fn
|
||||
self._gate_open = start_open
|
||||
self._direction = direction
|
||||
self._accumulator: List[Tuple[Frame, FrameDirection]] = []
|
||||
self._accumulator: list[tuple[Frame, FrameDirection]] = []
|
||||
|
||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||
"""Process incoming frames with gated accumulation logic.
|
||||
|
||||
@@ -19,8 +19,9 @@ import base64
|
||||
import copy
|
||||
import io
|
||||
import wave
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, List, Optional, TypeAlias, Union
|
||||
from typing import Any, TypeAlias
|
||||
|
||||
from loguru import logger
|
||||
from openai._types import NOT_GIVEN as OPEN_AI_NOT_GIVEN
|
||||
@@ -57,7 +58,7 @@ class LLMSpecificMessage:
|
||||
message: Any
|
||||
|
||||
|
||||
LLMContextMessage: TypeAlias = Union[LLMStandardMessage, LLMSpecificMessage]
|
||||
LLMContextMessage: TypeAlias = LLMStandardMessage | LLMSpecificMessage
|
||||
|
||||
|
||||
class LLMContext:
|
||||
@@ -70,7 +71,7 @@ class LLMContext:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
messages: Optional[List[LLMContextMessage]] = None,
|
||||
messages: list[LLMContextMessage] | None = None,
|
||||
tools: ToolsSchema | NotGiven = NOT_GIVEN,
|
||||
tool_choice: LLMContextToolChoice | NotGiven = NOT_GIVEN,
|
||||
):
|
||||
@@ -81,7 +82,7 @@ class LLMContext:
|
||||
tools: Available tools for the LLM to use.
|
||||
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._tool_choice: LLMContextToolChoice | NotGiven = tool_choice
|
||||
|
||||
@@ -90,7 +91,7 @@ class LLMContext:
|
||||
*,
|
||||
role: str = "user",
|
||||
url: str,
|
||||
text: Optional[str] = None,
|
||||
text: str | None = None,
|
||||
) -> LLMContextMessage:
|
||||
"""Create a context message containing an image URL.
|
||||
|
||||
@@ -114,7 +115,7 @@ class LLMContext:
|
||||
format: str,
|
||||
size: tuple[int, int],
|
||||
image: bytes,
|
||||
text: Optional[str] = None,
|
||||
text: str | None = None,
|
||||
) -> LLMContextMessage:
|
||||
"""Create a context message containing an image.
|
||||
|
||||
@@ -187,7 +188,7 @@ class LLMContext:
|
||||
return {"role": role, "content": content}
|
||||
|
||||
@property
|
||||
def messages(self) -> List[LLMContextMessage]:
|
||||
def messages(self) -> list[LLMContextMessage]:
|
||||
"""Get the current messages list.
|
||||
|
||||
NOTE: This is equivalent to calling `get_messages()` with no filter. If
|
||||
@@ -201,10 +202,10 @@ class LLMContext:
|
||||
|
||||
def get_messages(
|
||||
self,
|
||||
llm_specific_filter: Optional[str] = None,
|
||||
llm_specific_filter: str | None = None,
|
||||
*,
|
||||
truncate_large_values: bool = False,
|
||||
) -> List[LLMContextMessage]:
|
||||
) -> list[LLMContextMessage]:
|
||||
"""Get the current messages list.
|
||||
|
||||
Args:
|
||||
@@ -242,8 +243,8 @@ class LLMContext:
|
||||
|
||||
@staticmethod
|
||||
def _truncate_large_values_from_messages(
|
||||
messages: List[LLMContextMessage],
|
||||
) -> List[LLMContextMessage]:
|
||||
messages: list[LLMContextMessage],
|
||||
) -> list[LLMContextMessage]:
|
||||
"""Return deep copies of messages with large values replaced by placeholders.
|
||||
|
||||
For standard (universal-format) messages, the following known binary
|
||||
@@ -344,7 +345,7 @@ class LLMContext:
|
||||
"""
|
||||
self._messages.append(message)
|
||||
|
||||
def add_messages(self, messages: List[LLMContextMessage]):
|
||||
def add_messages(self, messages: list[LLMContextMessage]):
|
||||
"""Add multiple messages to the context.
|
||||
|
||||
Args:
|
||||
@@ -352,7 +353,7 @@ class LLMContext:
|
||||
"""
|
||||
self._messages.extend(messages)
|
||||
|
||||
def set_messages(self, messages: List[LLMContextMessage]):
|
||||
def set_messages(self, messages: list[LLMContextMessage]):
|
||||
"""Replace all messages in the context.
|
||||
|
||||
Args:
|
||||
@@ -361,7 +362,7 @@ class LLMContext:
|
||||
self._messages[:] = 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.
|
||||
|
||||
@@ -393,7 +394,7 @@ class LLMContext:
|
||||
format: str,
|
||||
size: tuple[int, int],
|
||||
image: bytes,
|
||||
text: Optional[str] = None,
|
||||
text: str | None = None,
|
||||
role: str = "user",
|
||||
):
|
||||
"""Add a message containing an image frame.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -101,7 +101,7 @@ class LLMContextSummarizer(BaseObject):
|
||||
self,
|
||||
*,
|
||||
context: LLMContext,
|
||||
config: Optional[LLMAutoContextSummarizationConfig] = None,
|
||||
config: LLMAutoContextSummarizationConfig | None = None,
|
||||
auto_trigger: bool = True,
|
||||
):
|
||||
"""Initialize the context summarizer.
|
||||
@@ -122,10 +122,10 @@ class LLMContextSummarizer(BaseObject):
|
||||
self._auto_config = config or LLMAutoContextSummarizationConfig()
|
||||
self._auto_trigger = auto_trigger
|
||||
|
||||
self._task_manager: Optional[BaseTaskManager] = None
|
||||
self._task_manager: BaseTaskManager | None = None
|
||||
|
||||
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_summary_applied")
|
||||
@@ -269,9 +269,7 @@ class LLMContextSummarizer(BaseObject):
|
||||
logger.debug(f"{self}: ✓ Summarization needed - {', '.join(reason)}")
|
||||
return True
|
||||
|
||||
async def _request_summarization(
|
||||
self, config_override: Optional[LLMContextSummaryConfig] = None
|
||||
):
|
||||
async def _request_summarization(self, config_override: LLMContextSummaryConfig | None = None):
|
||||
"""Request context summarization from LLM service.
|
||||
|
||||
Creates a summarization request frame and either handles it directly
|
||||
@@ -338,7 +336,7 @@ class LLMContextSummarizer(BaseObject):
|
||||
summary=summary,
|
||||
last_summarized_index=last_index,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
error = f"Context summarization timed out after {timeout}s"
|
||||
logger.error(f"{self}: {error}")
|
||||
result_frame = LLMContextSummaryResultFrame(
|
||||
|
||||
@@ -15,8 +15,9 @@ import asyncio
|
||||
import json
|
||||
import warnings
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Callable
|
||||
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
|
||||
|
||||
@@ -119,14 +120,14 @@ class LLMUserAggregatorParams:
|
||||
filter_incomplete_user_turns is True.
|
||||
"""
|
||||
|
||||
user_turn_strategies: Optional[UserTurnStrategies] = None
|
||||
user_mute_strategies: List[BaseUserMuteStrategy] = field(default_factory=list)
|
||||
user_turn_strategies: UserTurnStrategies | None = None
|
||||
user_mute_strategies: list[BaseUserMuteStrategy] = field(default_factory=list)
|
||||
user_turn_stop_timeout: float = 5.0
|
||||
user_idle_timeout: float = 0
|
||||
vad_analyzer: Optional[VADAnalyzer] = None
|
||||
vad_analyzer: VADAnalyzer | None = None
|
||||
audio_idle_timeout: float = 1.0
|
||||
filter_incomplete_user_turns: bool = False
|
||||
user_turn_completion_config: Optional[UserTurnCompletionConfig] = None
|
||||
user_turn_completion_config: UserTurnCompletionConfig | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -145,14 +146,14 @@ class LLMAssistantAggregatorParams:
|
||||
"""
|
||||
|
||||
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.
|
||||
# Use enable_auto_context_summarization and auto_context_summarization_config instead.
|
||||
# ---------------------------------------------------------------------------
|
||||
enable_context_summarization: Optional[bool] = None
|
||||
context_summarization_config: Optional[LLMContextSummarizationConfig] = None
|
||||
enable_context_summarization: bool | None = None
|
||||
context_summarization_config: LLMContextSummarizationConfig | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.enable_context_summarization is not None:
|
||||
@@ -198,7 +199,7 @@ class UserTurnStoppedMessage:
|
||||
|
||||
content: str
|
||||
timestamp: str
|
||||
user_id: Optional[str] = None
|
||||
user_id: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -259,10 +260,10 @@ class LLMContextAggregator(FrameProcessor):
|
||||
self._context = context
|
||||
self._role = role
|
||||
|
||||
self._aggregation: List[TextPartForConcatenation] = []
|
||||
self._aggregation: list[TextPartForConcatenation] = []
|
||||
|
||||
@property
|
||||
def messages(self) -> List[LLMContextMessage]:
|
||||
def messages(self) -> list[LLMContextMessage]:
|
||||
"""Get messages from the LLM context.
|
||||
|
||||
Returns:
|
||||
@@ -322,7 +323,7 @@ class LLMContextAggregator(FrameProcessor):
|
||||
self._context.set_messages(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.
|
||||
|
||||
@@ -423,7 +424,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
self,
|
||||
context: LLMContext,
|
||||
*,
|
||||
params: Optional[LLMUserAggregatorParams] = None,
|
||||
params: LLMUserAggregatorParams | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""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)
|
||||
|
||||
# VAD controller
|
||||
self._vad_controller: Optional[VADController] = None
|
||||
self._vad_controller: VADController | None = None
|
||||
if self._params.vad_analyzer:
|
||||
self._vad_controller = VADController(
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
async def _on_vad_speech_started(self, controller):
|
||||
@@ -768,7 +769,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
|
||||
async def _maybe_emit_user_turn_stopped(
|
||||
self,
|
||||
strategy: Optional[BaseUserTurnStopStrategy] = None,
|
||||
strategy: BaseUserTurnStopStrategy | None = None,
|
||||
on_session_end: bool = False,
|
||||
):
|
||||
"""Maybe emit user turn stopped event.
|
||||
@@ -832,7 +833,7 @@ class LLMAssistantAggregator(LLMContextAggregator):
|
||||
self,
|
||||
context: LLMContext,
|
||||
*,
|
||||
params: Optional[LLMAssistantAggregatorParams] = None,
|
||||
params: LLMAssistantAggregatorParams | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the assistant context aggregator.
|
||||
@@ -845,9 +846,9 @@ class LLMAssistantAggregator(LLMContextAggregator):
|
||||
super().__init__(context=context, role="assistant", **kwargs)
|
||||
self._params = params or LLMAssistantAggregatorParams()
|
||||
|
||||
self._function_calls_in_progress: Dict[str, Optional[FunctionCallInProgressFrame]] = {}
|
||||
self._function_calls_image_results: Dict[str, UserImageRawFrame] = {}
|
||||
self._context_updated_tasks: Set[asyncio.Task] = set()
|
||||
self._function_calls_in_progress: dict[str, FunctionCallInProgressFrame | None] = {}
|
||||
self._function_calls_image_results: dict[str, UserImageRawFrame] = {}
|
||||
self._context_updated_tasks: set[asyncio.Task] = set()
|
||||
|
||||
self._user_speaking: bool = False
|
||||
self._bot_speaking: bool = False
|
||||
@@ -862,14 +863,14 @@ class LLMAssistantAggregator(LLMContextAggregator):
|
||||
|
||||
self._thought_append_to_context = False
|
||||
self._thought_llm: str = ""
|
||||
self._thought_aggregation: List[TextPartForConcatenation] = []
|
||||
self._thought_aggregation: list[TextPartForConcatenation] = []
|
||||
self._thought_start_time: str = ""
|
||||
|
||||
# Context summarization — always create the summarizer so that manually
|
||||
# pushed LLMSummarizeContextFrame frames are always handled.
|
||||
# Auto-triggering based on thresholds is only enabled when
|
||||
# enable_auto_context_summarization is True.
|
||||
self._summarizer: Optional[LLMContextSummarizer] = LLMContextSummarizer(
|
||||
self._summarizer: LLMContextSummarizer | None = LLMContextSummarizer(
|
||||
context=self._context,
|
||||
config=self._params.auto_context_summarization_config,
|
||||
auto_trigger=self._params.enable_auto_context_summarization,
|
||||
@@ -1475,8 +1476,8 @@ class LLMContextAggregatorPair:
|
||||
self,
|
||||
context: LLMContext,
|
||||
*,
|
||||
user_params: Optional[LLMUserAggregatorParams] = None,
|
||||
assistant_params: Optional[LLMAssistantAggregatorParams] = None,
|
||||
user_params: LLMUserAggregatorParams | None = None,
|
||||
assistant_params: LLMAssistantAggregatorParams | None = None,
|
||||
):
|
||||
"""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.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
AggregatedTextFrame,
|
||||
EndFrame,
|
||||
@@ -38,7 +36,7 @@ class LLMTextProcessor(FrameProcessor):
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -91,7 +89,7 @@ class LLMTextProcessor(FrameProcessor):
|
||||
out_frame.skip_tts = in_frame.skip_tts
|
||||
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
|
||||
remaining = await self._text_aggregator.flush()
|
||||
if remaining:
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"""Async generator processor for frame serialization and streaming."""
|
||||
|
||||
import asyncio
|
||||
from typing import Any, AsyncGenerator
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
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.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pipecat.audio.utils import create_stream_resampler, interleave_stereo_audio, mix_audio
|
||||
from pipecat.frames.frames import (
|
||||
BotStartedSpeakingFrame,
|
||||
@@ -55,7 +53,7 @@ class AudioBufferProcessor(FrameProcessor):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
sample_rate: Optional[int] = None,
|
||||
sample_rate: int | None = None,
|
||||
num_channels: int = 1,
|
||||
buffer_size: int = 0,
|
||||
enable_turn_audio: bool = False,
|
||||
@@ -263,7 +261,7 @@ class AudioBufferProcessor(FrameProcessor):
|
||||
silence_needed = target_position - current_len
|
||||
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."""
|
||||
# Speaking state (_user_speaking / _bot_speaking) is maintained by
|
||||
# _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.
|
||||
"""
|
||||
|
||||
from typing import Type
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.audio.vad.vad_analyzer import VADAnalyzer
|
||||
@@ -94,7 +92,7 @@ class VADProcessor(FrameProcessor):
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
@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)
|
||||
|
||||
async def cleanup(self):
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"""Consumer processor for consuming frames from ProducerProcessor queues."""
|
||||
|
||||
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.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
@@ -42,7 +42,7 @@ class ConsumerProcessor(FrameProcessor):
|
||||
self._transformer = transformer
|
||||
self._direction = direction
|
||||
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):
|
||||
"""Process incoming frames and handle lifecycle events.
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
"""Frame filtering processor for the Pipecat framework."""
|
||||
|
||||
from typing import Tuple, Type
|
||||
|
||||
from pipecat.frames.frames import EndFrame, Frame, SystemFrame
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
|
||||
@@ -20,7 +18,7 @@ class FrameFilter(FrameProcessor):
|
||||
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.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from typing import Awaitable, Callable, Optional
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame, SystemFrame
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
@@ -29,7 +29,7 @@ class FunctionFilter(FrameProcessor):
|
||||
def __init__(
|
||||
self,
|
||||
filter: FilterType,
|
||||
direction: Optional[FrameDirection] = FrameDirection.DOWNSTREAM,
|
||||
direction: FrameDirection | None = FrameDirection.DOWNSTREAM,
|
||||
filter_system_frames: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
@@ -18,7 +18,6 @@ import re
|
||||
import time
|
||||
import warnings
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -71,7 +70,7 @@ class WakeCheckFilter(FrameProcessor):
|
||||
self.wake_timer = 0.0
|
||||
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.
|
||||
|
||||
.. deprecated:: 0.0.106
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""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.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
@@ -25,7 +25,7 @@ class WakeNotifierFilter(FrameProcessor):
|
||||
self,
|
||||
notifier: BaseNotifier,
|
||||
*,
|
||||
types: Tuple[Type[Frame], ...],
|
||||
types: tuple[type[Frame], ...],
|
||||
filter: Callable[[Frame], Awaitable[bool]],
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
@@ -11,21 +11,17 @@ audio/video processing pipelines. It includes frame processors, pipeline
|
||||
management, and frame flow control mechanisms.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import traceback
|
||||
from collections.abc import Awaitable, Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Coroutine,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
|
||||
from loguru import logger
|
||||
@@ -79,7 +75,7 @@ class FrameProcessorSetup:
|
||||
|
||||
clock: BaseClock
|
||||
task_manager: BaseTaskManager
|
||||
observer: Optional[BaseObserver] = None
|
||||
observer: BaseObserver | None = None
|
||||
|
||||
|
||||
class FrameProcessorQueue(asyncio.PriorityQueue):
|
||||
@@ -100,7 +96,7 @@ class FrameProcessorQueue(asyncio.PriorityQueue):
|
||||
self.__high_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.
|
||||
|
||||
System frames (`SystemFrame`) have higher priority than any other
|
||||
@@ -160,9 +156,9 @@ class FrameProcessor(BaseObject):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
name: str | None = None,
|
||||
enable_direct_mode: bool = False,
|
||||
metrics: Optional[FrameProcessorMetrics] = None,
|
||||
metrics: FrameProcessorMetrics | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the frame processor.
|
||||
@@ -174,20 +170,20 @@ class FrameProcessor(BaseObject):
|
||||
**kwargs: Additional arguments passed to parent class.
|
||||
"""
|
||||
super().__init__(name=name, **kwargs)
|
||||
self._prev: Optional["FrameProcessor"] = None
|
||||
self._next: Optional["FrameProcessor"] = None
|
||||
self._prev: FrameProcessor | None = None
|
||||
self._next: FrameProcessor | None = None
|
||||
|
||||
# Enable direct mode to skip queues and process frames right away.
|
||||
self._enable_direct_mode = enable_direct_mode
|
||||
|
||||
# Clock
|
||||
self._clock: Optional[BaseClock] = None
|
||||
self._clock: BaseClock | None = None
|
||||
|
||||
# Task Manager
|
||||
self._task_manager: Optional[BaseTaskManager] = None
|
||||
self._task_manager: BaseTaskManager | None = None
|
||||
|
||||
# Observer
|
||||
self._observer: Optional[BaseObserver] = None
|
||||
self._observer: BaseObserver | None = None
|
||||
|
||||
# Other properties
|
||||
self._enable_metrics = False
|
||||
@@ -221,8 +217,8 @@ class FrameProcessor(BaseObject):
|
||||
# frames right away and queues non-system frames for later processing.
|
||||
self.__should_block_system_frames = False
|
||||
self.__input_queue = FrameProcessorQueue()
|
||||
self.__input_event: Optional[asyncio.Event] = None
|
||||
self.__input_frame_task: Optional[asyncio.Task] = None
|
||||
self.__input_event: asyncio.Event | None = None
|
||||
self.__input_frame_task: asyncio.Task | None = None
|
||||
|
||||
# The process task processes non-system frames. Non-system frames will
|
||||
# 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.
|
||||
self.__should_block_frames = False
|
||||
self.__process_queue = FrameQueue(frame_getter=lambda item: item[0])
|
||||
self.__process_event: Optional[asyncio.Event] = None
|
||||
self.__process_frame_task: Optional[asyncio.Task] = None
|
||||
self.__process_current_frame: Optional[Frame] = None
|
||||
self.__process_event: asyncio.Event | None = None
|
||||
self.__process_frame_task: asyncio.Task | None = None
|
||||
self.__process_current_frame: Frame | None = None
|
||||
|
||||
# Frame processor events.
|
||||
self._register_event_handler("on_before_process_frame", sync=True)
|
||||
@@ -261,7 +257,7 @@ class FrameProcessor(BaseObject):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def processors(self) -> List["FrameProcessor"]:
|
||||
def processors(self) -> list[FrameProcessor]:
|
||||
"""Return the list of sub-processors contained within this processor.
|
||||
|
||||
Only compound processors (e.g. pipelines and parallel pipelines) have
|
||||
@@ -273,7 +269,7 @@ class FrameProcessor(BaseObject):
|
||||
return []
|
||||
|
||||
@property
|
||||
def entry_processors(self) -> List["FrameProcessor"]:
|
||||
def entry_processors(self) -> list[FrameProcessor]:
|
||||
"""Return the list of entry processors for this processor.
|
||||
|
||||
Entry processors are the first processors in a compound processor
|
||||
@@ -287,7 +283,7 @@ class FrameProcessor(BaseObject):
|
||||
return []
|
||||
|
||||
@property
|
||||
def next(self) -> Optional["FrameProcessor"]:
|
||||
def next(self) -> FrameProcessor | None:
|
||||
"""Get the next processor.
|
||||
|
||||
Returns:
|
||||
@@ -296,7 +292,7 @@ class FrameProcessor(BaseObject):
|
||||
return self._next
|
||||
|
||||
@property
|
||||
def previous(self) -> Optional["FrameProcessor"]:
|
||||
def previous(self) -> FrameProcessor | None:
|
||||
"""Get the previous processor.
|
||||
|
||||
Returns:
|
||||
@@ -372,7 +368,7 @@ class FrameProcessor(BaseObject):
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -384,7 +380,7 @@ class FrameProcessor(BaseObject):
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -396,7 +392,7 @@ class FrameProcessor(BaseObject):
|
||||
if 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.
|
||||
|
||||
Args:
|
||||
@@ -406,7 +402,7 @@ class FrameProcessor(BaseObject):
|
||||
if self.can_generate_metrics() and self.metrics_enabled:
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -458,7 +454,7 @@ class FrameProcessor(BaseObject):
|
||||
await self.stop_processing_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.
|
||||
|
||||
Args:
|
||||
@@ -474,7 +470,7 @@ class FrameProcessor(BaseObject):
|
||||
name = f"{self}::{coroutine.cr_code.co_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.
|
||||
|
||||
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:
|
||||
await self._metrics.cleanup()
|
||||
|
||||
def link(self, processor: "FrameProcessor"):
|
||||
def link(self, processor: FrameProcessor):
|
||||
"""Link this processor to the next processor in the pipeline.
|
||||
|
||||
Args:
|
||||
@@ -546,7 +542,7 @@ class FrameProcessor(BaseObject):
|
||||
self,
|
||||
frame: Frame,
|
||||
direction: FrameDirection = FrameDirection.DOWNSTREAM,
|
||||
callback: Optional[FrameCallback] = None,
|
||||
callback: FrameCallback | None = None,
|
||||
):
|
||||
"""Queue a frame for processing.
|
||||
|
||||
@@ -622,7 +618,7 @@ class FrameProcessor(BaseObject):
|
||||
async def push_error(
|
||||
self,
|
||||
error_msg: str,
|
||||
exception: Optional[Exception] = None,
|
||||
exception: Exception | None = None,
|
||||
fatal: bool = False,
|
||||
):
|
||||
"""Creates and pushes an ErrorFrame upstream.
|
||||
@@ -720,7 +716,7 @@ class FrameProcessor(BaseObject):
|
||||
|
||||
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.
|
||||
|
||||
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."""
|
||||
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.
|
||||
|
||||
Delegates to :meth:`FrameQueue.has_frame` so the check is O(distinct
|
||||
@@ -951,7 +947,7 @@ class FrameProcessor(BaseObject):
|
||||
self.__process_frame_task = None
|
||||
|
||||
async def __process_frame(
|
||||
self, frame: Frame, direction: FrameDirection, callback: Optional[FrameCallback]
|
||||
self, frame: Frame, direction: FrameDirection, callback: FrameCallback | None
|
||||
):
|
||||
try:
|
||||
await self._call_event_handler("on_before_process_frame", frame)
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
"""Langchain integration processor for Pipecat."""
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
@@ -45,7 +43,7 @@ class LangchainProcessor(FrameProcessor):
|
||||
super().__init__()
|
||||
self._chain = chain
|
||||
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):
|
||||
"""Set the participant ID for session tracking.
|
||||
@@ -76,7 +74,7 @@ class LangchainProcessor(FrameProcessor):
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"""RTVI pipeline frame definitions."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from pipecat.frames.frames import SystemFrame
|
||||
|
||||
@@ -37,7 +37,7 @@ class RTVIClientMessageFrame(SystemFrame):
|
||||
|
||||
msg_id: str
|
||||
type: str
|
||||
data: Optional[Any] = None
|
||||
data: Any | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -53,5 +53,5 @@ class RTVIServerResponseFrame(SystemFrame):
|
||||
"""
|
||||
|
||||
client_msg: RTVIClientMessageFrame
|
||||
data: Optional[Any] = None
|
||||
error: Optional[str] = None
|
||||
data: Any | None = 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))
|
||||
"""
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
Literal,
|
||||
Mapping,
|
||||
Optional,
|
||||
)
|
||||
|
||||
from pydantic import BaseModel
|
||||
@@ -46,7 +44,7 @@ class Message(BaseModel):
|
||||
label: MessageLiteral = MESSAGE_LABEL
|
||||
type: str
|
||||
id: str
|
||||
data: Optional[Dict[str, Any]] = None
|
||||
data: dict[str, Any] | None = None
|
||||
|
||||
|
||||
# -- Client -> Pipecat messages.
|
||||
@@ -56,7 +54,7 @@ class RawClientMessageData(BaseModel):
|
||||
"""Data structure expected from client messages sent to the RTVI server."""
|
||||
|
||||
t: str
|
||||
d: Optional[Any] = None
|
||||
d: Any | None = None
|
||||
|
||||
|
||||
class ClientMessage(BaseModel):
|
||||
@@ -64,14 +62,14 @@ class ClientMessage(BaseModel):
|
||||
|
||||
msg_id: str
|
||||
type: str
|
||||
data: Optional[Any] = None
|
||||
data: Any | None = None
|
||||
|
||||
|
||||
class RawServerResponseData(BaseModel):
|
||||
"""Data structure for server responses to client messages."""
|
||||
|
||||
t: str
|
||||
d: Optional[Any] = None
|
||||
d: Any | None = None
|
||||
|
||||
|
||||
class ServerResponse(BaseModel):
|
||||
@@ -94,10 +92,10 @@ class AboutClientData(BaseModel):
|
||||
"""
|
||||
|
||||
library: str
|
||||
library_version: Optional[str] = None
|
||||
platform: Optional[str] = None
|
||||
platform_version: Optional[str] = None
|
||||
platform_details: Optional[Any] = None
|
||||
library_version: str | None = None
|
||||
platform: str | None = None
|
||||
platform_version: str | None = None
|
||||
platform_details: Any | None = None
|
||||
|
||||
|
||||
class ClientReadyData(BaseModel):
|
||||
@@ -165,7 +163,7 @@ class BotReadyData(BaseModel):
|
||||
"""
|
||||
|
||||
version: str
|
||||
about: Optional[Mapping[str, Any]] = None
|
||||
about: Mapping[str, Any] | None = None
|
||||
|
||||
|
||||
class BotReady(BaseModel):
|
||||
@@ -226,7 +224,7 @@ class SendTextData(BaseModel):
|
||||
"""
|
||||
|
||||
content: str
|
||||
options: Optional[SendTextOptions] = None
|
||||
options: SendTextOptions | None = None
|
||||
|
||||
|
||||
class LLMFunctionCallStartMessageData(BaseModel):
|
||||
@@ -236,7 +234,7 @@ class LLMFunctionCallStartMessageData(BaseModel):
|
||||
the configured function_call_report_level for security.
|
||||
"""
|
||||
|
||||
function_name: Optional[str] = None
|
||||
function_name: str | None = None
|
||||
|
||||
|
||||
class LLMFunctionCallStartMessage(BaseModel):
|
||||
@@ -270,8 +268,8 @@ class LLMFunctionCallInProgressMessageData(BaseModel):
|
||||
"""
|
||||
|
||||
tool_call_id: str
|
||||
function_name: Optional[str] = None
|
||||
arguments: Optional[Mapping[str, Any]] = None
|
||||
function_name: str | None = None
|
||||
arguments: Mapping[str, Any] | None = None
|
||||
|
||||
|
||||
class LLMFunctionCallInProgressMessage(BaseModel):
|
||||
@@ -295,8 +293,8 @@ class LLMFunctionCallStoppedMessageData(BaseModel):
|
||||
|
||||
tool_call_id: str
|
||||
cancelled: bool
|
||||
function_name: Optional[str] = None
|
||||
result: Optional[Any] = None
|
||||
function_name: str | None = None
|
||||
result: Any | None = None
|
||||
|
||||
|
||||
class LLMFunctionCallStoppedMessage(BaseModel):
|
||||
|
||||
@@ -7,17 +7,12 @@
|
||||
"""RTVI observer for converting pipeline frames to outgoing RTVI messages."""
|
||||
|
||||
import time
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from enum import Enum, StrEnum
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
from loguru import logger
|
||||
@@ -71,7 +66,7 @@ if TYPE_CHECKING:
|
||||
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.
|
||||
|
||||
Controls what information is exposed in function call events for security.
|
||||
@@ -148,18 +143,14 @@ class RTVIObserverParams:
|
||||
user_audio_level_enabled: bool = False
|
||||
metrics_enabled: bool = True
|
||||
system_logs_enabled: bool = False
|
||||
ignored_sources: List[FrameProcessor] = field(default_factory=list)
|
||||
skip_aggregator_types: Optional[List[AggregationType | str]] = None
|
||||
bot_output_transforms: Optional[
|
||||
List[
|
||||
Tuple[
|
||||
AggregationType | str,
|
||||
Callable[[str, AggregationType | str], Awaitable[str]],
|
||||
]
|
||||
]
|
||||
] = None
|
||||
ignored_sources: list[FrameProcessor] = field(default_factory=list)
|
||||
skip_aggregator_types: list[AggregationType | str] | None = None
|
||||
bot_output_transforms: (
|
||||
list[tuple[AggregationType | str, Callable[[str, AggregationType | str], Awaitable[str]]]]
|
||||
| None
|
||||
) = None
|
||||
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}
|
||||
)
|
||||
|
||||
@@ -180,7 +171,7 @@ class RTVIObserver(BaseObserver):
|
||||
self,
|
||||
rtvi: Optional["RTVIProcessor"] = None,
|
||||
*,
|
||||
params: Optional[RTVIObserverParams] = None,
|
||||
params: RTVIObserverParams | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the RTVI observer.
|
||||
@@ -194,7 +185,7 @@ class RTVIObserver(BaseObserver):
|
||||
self._rtvi = rtvi
|
||||
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._bot_transcription = ""
|
||||
@@ -203,13 +194,13 @@ class RTVIObserver(BaseObserver):
|
||||
|
||||
# Track bot speaking state for queuing aggregated text frames
|
||||
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:
|
||||
self._system_logger_id = logger.add(self._logger_sink)
|
||||
|
||||
self._aggregation_transforms: List[
|
||||
Tuple[AggregationType | str, Callable[[str, AggregationType | str], Awaitable[str]]]
|
||||
self._aggregation_transforms: list[
|
||||
tuple[AggregationType | str, Callable[[str, AggregationType | str], Awaitable[str]]]
|
||||
] = self._params.bot_output_transforms or []
|
||||
|
||||
def add_bot_output_transformer(
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
from typing import Any, Mapping, Optional
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel, ValidationError
|
||||
@@ -51,7 +52,7 @@ class RTVIProcessor(FrameProcessor):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
transport: Optional[BaseTransport] = None,
|
||||
transport: BaseTransport | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""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.
|
||||
|
||||
# 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_client_ready")
|
||||
@@ -84,7 +85,7 @@ class RTVIProcessor(FrameProcessor):
|
||||
self._input_transport = input_transport
|
||||
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.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
@@ -38,9 +36,9 @@ class StrandsAgentsProcessor(FrameProcessor):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
agent: Optional[Agent] = None,
|
||||
graph: Optional[Graph] = None,
|
||||
graph_exit_node: Optional[str] = None,
|
||||
agent: Agent | None = None,
|
||||
graph: Graph | None = None,
|
||||
graph_exit_node: str | None = None,
|
||||
):
|
||||
"""Initialize the Strands Agents processor.
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"""GStreamer pipeline source integration for Pipecat."""
|
||||
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -58,11 +57,11 @@ class GStreamerPipelineSource(FrameProcessor):
|
||||
|
||||
video_width: int = 1280
|
||||
video_height: int = 720
|
||||
audio_sample_rate: Optional[int] = None
|
||||
audio_sample_rate: int | None = None
|
||||
audio_channels: int = 1
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"""Idle frame processor for timeout-based callback execution."""
|
||||
|
||||
import asyncio
|
||||
from typing import Awaitable, Callable, List, Optional
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
from pipecat.frames.frames import Frame, StartFrame
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
@@ -26,7 +26,7 @@ class IdleFrameProcessor(FrameProcessor):
|
||||
*,
|
||||
callback: Callable[["IdleFrameProcessor"], Awaitable[None]],
|
||||
timeout: float,
|
||||
types: Optional[List[type]] = None,
|
||||
types: list[type] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the idle frame processor.
|
||||
@@ -86,5 +86,5 @@ class IdleFrameProcessor(FrameProcessor):
|
||||
try:
|
||||
await asyncio.wait_for(self._idle_event.wait(), timeout=self._timeout)
|
||||
self._idle_event.clear()
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
await self._callback(self)
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
"""Frame logging utilities for debugging and monitoring frame flow in Pipecat pipelines."""
|
||||
|
||||
from typing import Optional, Tuple, Type
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.frames.frames import (
|
||||
@@ -33,8 +31,8 @@ class FrameLogger(FrameProcessor):
|
||||
def __init__(
|
||||
self,
|
||||
prefix="Frame",
|
||||
color: Optional[str] = None,
|
||||
ignored_frame_types: Tuple[Type[Frame], ...] = (
|
||||
color: str | None = None,
|
||||
ignored_frame_types: tuple[type[Frame], ...] = (
|
||||
BotSpeakingFrame,
|
||||
UserSpeakingFrame,
|
||||
InputAudioRawFrame,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"""Frame processor metrics collection and reporting."""
|
||||
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -70,7 +69,7 @@ class FrameProcessorMetrics(BaseObject):
|
||||
return self._task_manager
|
||||
|
||||
@property
|
||||
def ttfb(self) -> Optional[float]:
|
||||
def ttfb(self) -> float | None:
|
||||
"""Get the current TTFB value in seconds.
|
||||
|
||||
Returns:
|
||||
@@ -110,7 +109,7 @@ class FrameProcessorMetrics(BaseObject):
|
||||
self._core_metrics_data = MetricsData(processor=name)
|
||||
|
||||
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).
|
||||
|
||||
@@ -124,7 +123,7 @@ class FrameProcessorMetrics(BaseObject):
|
||||
self._last_ttfb_time = 0
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -147,7 +146,7 @@ class FrameProcessorMetrics(BaseObject):
|
||||
self._start_ttfb_time = 0
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -156,7 +155,7 @@ class FrameProcessorMetrics(BaseObject):
|
||||
"""
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"""Sentry integration for frame processor metrics."""
|
||||
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -72,7 +71,7 @@ class SentryMetrics(FrameProcessorMetrics):
|
||||
sentry_sdk.flush(timeout=5.0)
|
||||
|
||||
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.
|
||||
|
||||
@@ -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})"
|
||||
)
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -105,7 +104,7 @@ class SentryMetrics(FrameProcessorMetrics):
|
||||
await self._sentry_queue.put(self._ttfb_metrics_tx)
|
||||
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.
|
||||
|
||||
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})"
|
||||
)
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"""Producer processor for frame filtering and distribution."""
|
||||
|
||||
import asyncio
|
||||
from typing import Awaitable, Callable, List
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
from pipecat.frames.frames import Frame
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
@@ -55,7 +55,7 @@ class ProducerProcessor(FrameProcessor):
|
||||
self._filter = filter
|
||||
self._transformer = transformer
|
||||
self._passthrough = passthrough
|
||||
self._consumers: List[asyncio.Queue] = []
|
||||
self._consumers: list[asyncio.Queue] = []
|
||||
|
||||
def add_consumer(self):
|
||||
"""Add a new consumer and return its associated queue.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
"""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.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
@@ -21,7 +21,7 @@ class StatelessTextTransformer(FrameProcessor):
|
||||
"""
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ Example::
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
from loguru import logger
|
||||
@@ -64,7 +63,7 @@ class DailyRoomConfig(BaseModel):
|
||||
|
||||
room_url: str
|
||||
token: str
|
||||
sip_endpoint: Optional[str] = None
|
||||
sip_endpoint: str | None = None
|
||||
|
||||
def __iter__(self):
|
||||
"""Enable tuple unpacking for backward compatibility.
|
||||
@@ -78,18 +77,18 @@ class DailyRoomConfig(BaseModel):
|
||||
async def configure(
|
||||
aiohttp_session: aiohttp.ClientSession,
|
||||
*,
|
||||
api_key: Optional[str] = None,
|
||||
api_key: str | None = None,
|
||||
room_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_num_endpoints: int = 1,
|
||||
enable_dialout: bool = False,
|
||||
sip_codecs: Optional[Dict[str, List[str]]] = None,
|
||||
sip_provider: Optional[str] = None,
|
||||
room_geo: Optional[str] = None,
|
||||
room_properties: Optional[DailyRoomProperties] = None,
|
||||
token_properties: Optional[DailyMeetingTokenProperties] = None,
|
||||
sip_codecs: dict[str, list[str]] | None = None,
|
||||
sip_provider: str | None = None,
|
||||
room_geo: str | None = None,
|
||||
room_properties: DailyRoomProperties | None = None,
|
||||
token_properties: DailyMeetingTokenProperties | None = None,
|
||||
) -> DailyRoomConfig:
|
||||
"""Configure Daily room URL and token with optional SIP capabilities.
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ Example::
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from livekit import api
|
||||
from loguru import logger
|
||||
@@ -98,7 +97,7 @@ async def configure():
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -74,7 +74,7 @@ import uuid
|
||||
from contextlib import asynccontextmanager
|
||||
from http import HTTPMethod
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, TypedDict, Union
|
||||
from typing import Any, TypedDict
|
||||
|
||||
import aiohttp
|
||||
from fastapi.responses import FileResponse, Response
|
||||
@@ -106,7 +106,7 @@ os.environ["ENV"] = "local"
|
||||
|
||||
TELEPHONY_TRANSPORTS = ["twilio", "telnyx", "plivo", "exotel"]
|
||||
|
||||
RUNNER_DOWNLOADS_FOLDER: Optional[str] = None
|
||||
RUNNER_DOWNLOADS_FOLDER: str | None = None
|
||||
RUNNER_HOST: str = "localhost"
|
||||
RUNNER_PORT: int = 7860
|
||||
|
||||
@@ -220,17 +220,17 @@ def _setup_webrtc_routes(app: FastAPI, args: argparse.Namespace):
|
||||
return
|
||||
|
||||
class IceServer(TypedDict, total=False):
|
||||
urls: Union[str, List[str]]
|
||||
urls: str | list[str]
|
||||
|
||||
class IceConfig(TypedDict):
|
||||
iceServers: List[IceServer]
|
||||
iceServers: list[IceServer]
|
||||
|
||||
class StartBotResult(TypedDict, total=False):
|
||||
sessionId: str
|
||||
iceConfig: Optional[IceConfig]
|
||||
iceConfig: IceConfig | None
|
||||
|
||||
# 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
|
||||
app.mount("/client", SmallWebRTCPrebuiltUI)
|
||||
@@ -418,7 +418,7 @@ def _setup_whatsapp_routes(app: FastAPI, args: argparse.Namespace):
|
||||
return
|
||||
|
||||
# Global WhatsApp client instance
|
||||
whatsapp_client: Optional[WhatsAppClient] = None
|
||||
whatsapp_client: WhatsAppClient | None = None
|
||||
|
||||
@app.get(
|
||||
"/whatsapp",
|
||||
@@ -857,7 +857,7 @@ def _validate_and_clean_proxy(proxy: str) -> str:
|
||||
return proxy
|
||||
|
||||
|
||||
def runner_downloads_folder() -> Optional[str]:
|
||||
def runner_downloads_folder() -> str | None:
|
||||
"""Returns the folder where files are stored for later download."""
|
||||
return RUNNER_DOWNLOADS_FOLDER
|
||||
|
||||
@@ -872,7 +872,7 @@ def runner_port() -> int:
|
||||
return RUNNER_PORT
|
||||
|
||||
|
||||
def main(parser: Optional[argparse.ArgumentParser] = None):
|
||||
def main(parser: argparse.ArgumentParser | None = None):
|
||||
"""Start the Pipecat development runner.
|
||||
|
||||
Parses command-line arguments and starts a FastAPI server configured
|
||||
|
||||
@@ -12,7 +12,7 @@ information to bot functions.
|
||||
|
||||
import argparse
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from fastapi import WebSocket
|
||||
from pydantic import BaseModel
|
||||
@@ -34,9 +34,9 @@ class DialinSettings(BaseModel):
|
||||
|
||||
call_id: str
|
||||
call_domain: str
|
||||
To: Optional[str] = None
|
||||
From: Optional[str] = None
|
||||
sip_headers: Optional[Dict[str, str]] = None
|
||||
To: str | None = None
|
||||
From: str | None = None
|
||||
sip_headers: dict[str, str] | None = None
|
||||
|
||||
|
||||
class DailyDialinRequest(BaseModel):
|
||||
@@ -64,8 +64,8 @@ class RunnerArguments:
|
||||
handle_sigint: 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)
|
||||
body: Optional[Any] = field(default_factory=dict, kw_only=True)
|
||||
cli_args: Optional[argparse.Namespace] = field(default=None, init=False, kw_only=True)
|
||||
body: Any | None = field(default_factory=dict, kw_only=True)
|
||||
cli_args: argparse.Namespace | None = field(default=None, init=False, kw_only=True)
|
||||
|
||||
def __post_init__(self):
|
||||
self.handle_sigint = False
|
||||
@@ -84,7 +84,7 @@ class DailyRunnerArguments(RunnerArguments):
|
||||
"""
|
||||
|
||||
room_url: str
|
||||
token: Optional[str] = None
|
||||
token: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -122,4 +122,4 @@ class LiveKitRunnerArguments(RunnerArguments):
|
||||
|
||||
room_name: str
|
||||
url: str
|
||||
token: Optional[str] = None
|
||||
token: str | None = None
|
||||
|
||||
@@ -32,7 +32,8 @@ Example::
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from fastapi import WebSocket
|
||||
from loguru import logger
|
||||
@@ -373,7 +374,7 @@ def _smallwebrtc_sdp_cleanup_fingerprints(text: str) -> str:
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -389,7 +390,7 @@ def smallwebrtc_sdp_munging(sdp: str, host: Optional[str]) -> str:
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -415,7 +416,7 @@ def _get_transport_params(transport_key: str, transport_params: Dict[str, Callab
|
||||
|
||||
async def _create_telephony_transport(
|
||||
websocket: WebSocket,
|
||||
params: Optional[Any] = None,
|
||||
params: Any | None = None,
|
||||
transport_type: str = None,
|
||||
call_data: dict = None,
|
||||
) -> BaseTransport:
|
||||
@@ -488,7 +489,7 @@ async def _create_telephony_transport(
|
||||
|
||||
|
||||
async def create_transport(
|
||||
runner_args: Any, transport_params: Dict[str, Callable]
|
||||
runner_args: Any, transport_params: dict[str, Callable]
|
||||
) -> BaseTransport:
|
||||
"""Create a transport from runner arguments using factory functions.
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"""Frame serialization interfaces for Pipecat."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -39,7 +38,7 @@ class FrameSerializer(BaseObject):
|
||||
|
||||
ignore_rtvi_messages: bool = True
|
||||
|
||||
def __init__(self, params: Optional[InputParams] = None, **kwargs):
|
||||
def __init__(self, params: InputParams | None = None, **kwargs):
|
||||
"""Initialize the FrameSerializer.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import base64
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -48,10 +47,10 @@ class ExotelFrameSerializer(FrameSerializer):
|
||||
"""
|
||||
|
||||
exotel_sample_rate: int = 8000
|
||||
sample_rate: Optional[int] = None
|
||||
sample_rate: int | None = None
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ Audio Format:
|
||||
import json
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -46,7 +46,7 @@ from pipecat.frames.frames import (
|
||||
from pipecat.serializers.base_serializer import FrameSerializer
|
||||
|
||||
|
||||
class AudioHookMessageType(str, Enum):
|
||||
class AudioHookMessageType(StrEnum):
|
||||
"""AudioHook protocol message types."""
|
||||
|
||||
OPEN = "open"
|
||||
@@ -63,7 +63,7 @@ class AudioHookMessageType(str, Enum):
|
||||
DISCONNECT = "disconnect"
|
||||
|
||||
|
||||
class AudioHookChannel(str, Enum):
|
||||
class AudioHookChannel(StrEnum):
|
||||
"""AudioHook audio channel configuration."""
|
||||
|
||||
EXTERNAL = "external" # Customer audio only (mono)
|
||||
@@ -71,7 +71,7 @@ class AudioHookChannel(str, Enum):
|
||||
BOTH = "both" # Stereo: external=left, internal=right
|
||||
|
||||
|
||||
class AudioHookMediaFormat(str, Enum):
|
||||
class AudioHookMediaFormat(StrEnum):
|
||||
"""Supported audio formats."""
|
||||
|
||||
PCMU = "PCMU" # μ-law, 8kHz
|
||||
@@ -146,18 +146,18 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
"""
|
||||
|
||||
genesys_sample_rate: int = 8000
|
||||
sample_rate: Optional[int] = None
|
||||
sample_rate: int | None = None
|
||||
channel: AudioHookChannel = AudioHookChannel.EXTERNAL
|
||||
media_format: AudioHookMediaFormat = AudioHookMediaFormat.PCMU
|
||||
process_external: bool = True
|
||||
process_internal: bool = False
|
||||
supported_languages: Optional[List[str]] = None
|
||||
selected_language: Optional[str] = None
|
||||
supported_languages: list[str] | None = None
|
||||
selected_language: str | None = None
|
||||
start_paused: bool = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
params: Optional[InputParams] = None,
|
||||
params: InputParams | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the GenesysAudioHookSerializer.
|
||||
@@ -185,12 +185,12 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
self._position = timedelta(0)
|
||||
|
||||
# Session metadata
|
||||
self._conversation_id: Optional[str] = None
|
||||
self._participant: Optional[Dict[str, Any]] = None
|
||||
self._custom_config: Optional[Dict[str, Any]] = None
|
||||
self._media_info: Optional[List[Dict[str, Any]]] = None
|
||||
self._input_variables: Optional[Dict[str, Any]] = None # Custom input from Genesys
|
||||
self._output_variables: Optional[Dict[str, Any]] = None # Custom output to Genesys
|
||||
self._conversation_id: str | None = None
|
||||
self._participant: dict[str, Any] | None = None
|
||||
self._custom_config: dict[str, Any] | None = None
|
||||
self._media_info: list[dict[str, Any]] | None = None
|
||||
self._input_variables: dict[str, Any] | None = None # Custom input from Genesys
|
||||
self._output_variables: dict[str, Any] | None = None # Custom output to Genesys
|
||||
|
||||
# Event handlers
|
||||
self._register_event_handler("on_open")
|
||||
@@ -207,7 +207,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
return self._session_id
|
||||
|
||||
@property
|
||||
def conversation_id(self) -> Optional[str]:
|
||||
def conversation_id(self) -> str | None:
|
||||
"""Get the Genesys conversation ID."""
|
||||
return self._conversation_id
|
||||
|
||||
@@ -222,21 +222,21 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
return self._is_paused
|
||||
|
||||
@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."""
|
||||
return self._participant
|
||||
|
||||
@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."""
|
||||
return self._input_variables
|
||||
|
||||
@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."""
|
||||
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.
|
||||
|
||||
These variables will be included in the 'closed' response when Genesys
|
||||
@@ -305,9 +305,9 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
def _create_message(
|
||||
self,
|
||||
msg_type: AudioHookMessageType,
|
||||
parameters: Optional[Dict[str, Any]] = None,
|
||||
parameters: dict[str, Any] | None = None,
|
||||
include_position: bool = True,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""Create a protocol message with common fields.
|
||||
|
||||
Based on the Genesys AudioHook protocol, responses include:
|
||||
@@ -341,9 +341,9 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
def create_opened_response(
|
||||
self,
|
||||
start_paused: bool = False,
|
||||
supported_languages: Optional[List[str]] = None,
|
||||
selected_language: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
supported_languages: list[str] | None = None,
|
||||
selected_language: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Create an 'opened' response message for the client.
|
||||
|
||||
This should be sent in response to an 'open' message from Genesys.
|
||||
@@ -397,8 +397,8 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
|
||||
def create_closed_response(
|
||||
self,
|
||||
output_variables: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
output_variables: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Create a 'closed' response message.
|
||||
|
||||
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:
|
||||
parameters = {"outputVariables": output_variables}
|
||||
@@ -437,7 +437,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
|
||||
return msg
|
||||
|
||||
def create_pong_response(self) -> Dict[str, Any]:
|
||||
def create_pong_response(self) -> dict[str, Any]:
|
||||
"""Create a 'pong' response message.
|
||||
|
||||
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)
|
||||
return msg
|
||||
|
||||
def create_resumed_response(self) -> Dict[str, Any]:
|
||||
def create_resumed_response(self) -> dict[str, Any]:
|
||||
"""Create a 'resumed' response message.
|
||||
|
||||
This should be sent in response to a 'pause' message when ready to resume.
|
||||
@@ -463,7 +463,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
|
||||
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.
|
||||
|
||||
This notifies Genesys Cloud that the user has interrupted the bot's
|
||||
@@ -485,9 +485,9 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
self,
|
||||
reason: str = "completed",
|
||||
action: str = "transfer",
|
||||
output_variables: Optional[Dict[str, Any]] = None,
|
||||
info: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
output_variables: dict[str, Any] | None = None,
|
||||
info: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Create a 'disconnect' message to initiate session termination.
|
||||
|
||||
Args:
|
||||
@@ -499,7 +499,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
Returns:
|
||||
Dictionary of the disconnect message.
|
||||
"""
|
||||
parameters: Dict[str, Any] = {"reason": reason}
|
||||
parameters: dict[str, Any] = {"reason": reason}
|
||||
|
||||
# Build outputVariables
|
||||
out_vars = {"action": action}
|
||||
@@ -523,7 +523,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
code: int,
|
||||
message: str,
|
||||
retryable: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""Create an 'error' message.
|
||||
|
||||
Args:
|
||||
@@ -700,7 +700,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -748,7 +748,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
logger.warning(f"Unknown AudioHook message type: {msg_type}")
|
||||
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.
|
||||
|
||||
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_list = self._media_info
|
||||
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", [])
|
||||
logger.debug(
|
||||
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.
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
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.
|
||||
|
||||
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 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.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
Updates may include changes to participants or configuration.
|
||||
@@ -910,7 +910,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -929,7 +929,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
|
||||
|
||||
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.
|
||||
|
||||
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