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:
Aleix Conchillo Flaqué
2026-04-16 09:16:53 -07:00
parent 12b8af3d89
commit b3bb6fdaa5
283 changed files with 2902 additions and 3020 deletions

View File

@@ -84,7 +84,7 @@ async def load_conversation(params: FunctionCallParams):
filename = params.arguments["filename"] filename = params.arguments["filename"]
logger.debug(f"loading conversation from {filename}") logger.debug(f"loading conversation from {filename}")
try: try:
with open(filename, "r") as file: with open(filename) as file:
params.context.set_messages(json.load(file)) params.context.set_messages(json.load(file))
logger.debug( logger.debug(
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}" f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"

View File

@@ -105,7 +105,7 @@ async def load_conversation(params: FunctionCallParams):
filename = params.arguments["filename"] filename = params.arguments["filename"]
logger.debug(f"loading conversation from {filename}") logger.debug(f"loading conversation from {filename}")
try: try:
with open(filename, "r") as file: with open(filename) as file:
messages = json.load(file) messages = json.load(file)
# HACK: if using the older Nova Sonic (pre-2) model, you need a special way of # HACK: if using the older Nova Sonic (pre-2) model, you need a special way of
# triggering the first assistant response. The call to trigger_assistant_response(), # triggering the first assistant response. The call to trigger_assistant_response(),

View File

@@ -110,7 +110,7 @@ async def load_conversation(params: FunctionCallParams):
filename = params.arguments["filename"] filename = params.arguments["filename"]
logger.debug(f"loading conversation from {filename}") logger.debug(f"loading conversation from {filename}")
try: try:
with open(filename, "r") as file: with open(filename) as file:
params.context.set_messages(json.load(file)) params.context.set_messages(json.load(file))
await params.result_callback( await params.result_callback(
{ {

View File

@@ -94,7 +94,7 @@ async def load_conversation(params: FunctionCallParams):
filename = params.arguments["filename"] filename = params.arguments["filename"]
logger.debug(f"loading conversation from {filename}") logger.debug(f"loading conversation from {filename}")
try: try:
with open(filename, "r") as file: with open(filename) as file:
params.context.set_messages(json.load(file)) params.context.set_messages(json.load(file))
await params.llm.reset_conversation() await params.llm.reset_conversation()
# Manually create a response since we've reset the conversation # Manually create a response since we've reset the conversation

View File

@@ -91,7 +91,7 @@ async def load_conversation(params: FunctionCallParams):
filename = params.arguments["filename"] filename = params.arguments["filename"]
logger.debug(f"loading conversation from {filename}") logger.debug(f"loading conversation from {filename}")
try: try:
with open(filename, "r") as file: with open(filename) as file:
params.context.set_messages(json.load(file)) params.context.set_messages(json.load(file))
await params.llm.reset_conversation() await params.llm.reset_conversation()
# NOTE: we manually create a response here rather than relying # NOTE: we manually create a response here rather than relying

View File

@@ -85,7 +85,7 @@ async def load_conversation(params: FunctionCallParams):
filename = params.arguments["filename"] filename = params.arguments["filename"]
logger.debug(f"loading conversation from {filename}") logger.debug(f"loading conversation from {filename}")
try: try:
with open(filename, "r") as file: with open(filename) as file:
params.context.set_messages(json.load(file)) params.context.set_messages(json.load(file))
logger.debug( logger.debug(
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}" f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"

View File

@@ -85,7 +85,7 @@ async def load_conversation(params: FunctionCallParams):
filename = params.arguments["filename"] filename = params.arguments["filename"]
logger.debug(f"loading conversation from {filename}") logger.debug(f"loading conversation from {filename}")
try: try:
with open(filename, "r") as file: with open(filename) as file:
params.context.set_messages(json.load(file)) params.context.set_messages(json.load(file))
logger.debug( logger.debug(
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}" f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"

View File

@@ -85,7 +85,7 @@ async def load_conversation(params: FunctionCallParams):
filename = params.arguments["filename"] filename = params.arguments["filename"]
logger.debug(f"loading conversation from {filename}") logger.debug(f"loading conversation from {filename}")
try: try:
with open(filename, "r") as file: with open(filename) as file:
params.context.set_messages(json.load(file)) params.context.set_messages(json.load(file))
logger.debug( logger.debug(
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}" f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"

View File

@@ -87,7 +87,7 @@ def get_rag_content():
"""Get the RAG content from the file.""" """Get the RAG content from the file."""
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
rag_content_path = os.path.join(script_dir, "assets", "rag-content.txt") rag_content_path = os.path.join(script_dir, "assets", "rag-content.txt")
with open(rag_content_path, "r") as f: with open(rag_content_path) as f:
return f.read() return f.read()

View File

@@ -8,7 +8,6 @@ import argparse
import asyncio import asyncio
import os import os
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import Dict
import uvicorn import uvicorn
from dotenv import load_dotenv from dotenv import load_dotenv
@@ -39,7 +38,7 @@ load_dotenv(override=True)
app = FastAPI() app = FastAPI()
# Store connections by pc_id # Store connections by pc_id
pcs_map: Dict[str, SmallWebRTCConnection] = {} pcs_map: dict[str, SmallWebRTCConnection] = {}
ice_servers = [ ice_servers = [
IceServer( IceServer(

View File

@@ -45,13 +45,13 @@ class TranscriptHandler:
output_file: Optional path to file where transcript is saved. If None, outputs to log only. output_file: Optional path to file where transcript is saved. If None, outputs to log only.
""" """
def __init__(self, output_file: Optional[str] = None): def __init__(self, output_file: str | None = None):
"""Initialize handler with optional file output. """Initialize handler with optional file output.
Args: Args:
output_file: Path to output file. If None, outputs to log only. output_file: Path to output file. If None, outputs to log only.
""" """
self.output_file: Optional[str] = output_file self.output_file: str | None = output_file
logger.debug( logger.debug(
f"TranscriptHandler initialized {'with output_file=' + output_file if output_file else 'with log output only'}" f"TranscriptHandler initialized {'with output_file=' + output_file if output_file else 'with log output only'}"
) )

View File

@@ -13,7 +13,7 @@ import wave
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any, List, Optional, Tuple from typing import Any
import aiofiles import aiofiles
from loguru import logger from loguru import logger
@@ -60,7 +60,7 @@ PIPELINE_IDLE_TIMEOUT_SECS = 60
EVAL_TIMEOUT_SECS = 120 EVAL_TIMEOUT_SECS = 120
EVAL_RESULT_TIMEOUT_SECS = 10 EVAL_RESULT_TIMEOUT_SECS = 10
EvalPrompt = str | Tuple[str, ImageFile] EvalPrompt = str | tuple[str, ImageFile]
@dataclass @dataclass
@@ -68,7 +68,7 @@ class EvalConfig:
prompt: EvalPrompt prompt: EvalPrompt
eval: str eval: str
eval_speaks_first: bool = False eval_speaks_first: bool = False
runner_args_body: Optional[Any] = None runner_args_body: Any | None = None
class EvalRunner: class EvalRunner:
@@ -78,7 +78,7 @@ class EvalRunner:
examples_dir: Path, examples_dir: Path,
pattern: str = "", pattern: str = "",
record_audio: bool = False, record_audio: bool = False,
name: Optional[str] = None, name: str | None = None,
log_level: str = "DEBUG", log_level: str = "DEBUG",
): ):
self._examples_dir = examples_dir self._examples_dir = examples_dir
@@ -86,8 +86,8 @@ class EvalRunner:
self._record_audio = record_audio self._record_audio = record_audio
self._log_level = log_level self._log_level = log_level
self._total_success = 0 self._total_success = 0
self._tests: List[EvalResult] = [] self._tests: list[EvalResult] = []
self._result_future: Optional[asyncio.Future[bool]] = None self._result_future: asyncio.Future[bool] | None = None
# We to save runner files. # We to save runner files.
name = name or f"{datetime.now().strftime('%Y%m%d_%H%M%S')}" name = name or f"{datetime.now().strftime('%Y%m%d_%H%M%S')}"
@@ -150,7 +150,7 @@ class EvalRunner:
try: try:
# Wait for the future to resolve. # Wait for the future to resolve.
result = await asyncio.wait_for(self._result_future, timeout=EVAL_RESULT_TIMEOUT_SECS) result = await asyncio.wait_for(self._result_future, timeout=EVAL_RESULT_TIMEOUT_SECS)
except asyncio.TimeoutError: except TimeoutError:
logger.error(f"ERROR: Timeout waiting for eval result.") logger.error(f"ERROR: Timeout waiting for eval result.")
result = False result = False
@@ -282,7 +282,7 @@ async def run_eval_pipeline(
# Load example prompt depending on image. # Load example prompt depending on image.
example_prompt = "" example_prompt = ""
example_image: Optional[ImageFile] = None example_image: ImageFile | None = None
if isinstance(eval_config.prompt, str): if isinstance(eval_config.prompt, str):
example_prompt = eval_config.prompt example_prompt = eval_config.prompt
elif isinstance(eval_config.prompt, tuple): elif isinstance(eval_config.prompt, tuple):

View File

@@ -7,7 +7,7 @@
import argparse import argparse
import asyncio import asyncio
import sys import sys
from datetime import datetime, timezone from datetime import UTC, datetime, timezone
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
@@ -41,7 +41,7 @@ EVAL_WEATHER_AND_RESTAURANT = EvalConfig(
EVAL_ONLINE_SEARCH = EvalConfig( EVAL_ONLINE_SEARCH = EvalConfig(
prompt="What's the current date in UTC?", prompt="What's the current date in UTC?",
eval=f"Current date in UTC is {datetime.now(timezone.utc).strftime('%A, %B %d, %Y')}.", eval=f"Current date in UTC is {datetime.now(UTC).strftime('%A, %B %d, %Y')}.",
) )
EVAL_SWITCH_LANGUAGE = EvalConfig( EVAL_SWITCH_LANGUAGE = EvalConfig(

View File

@@ -6,9 +6,9 @@
import importlib.util import importlib.util
import os import os
from collections.abc import Sequence
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Sequence
GREEN = "\033[92m" GREEN = "\033[92m"
RED = "\033[91m" RED = "\033[91m"

View File

@@ -5,13 +5,12 @@ handling format detection and conversion to int16 PCM format.
""" """
import sys import sys
from typing import Tuple
import numpy as np import numpy as np
import soundfile as sf import soundfile as sf
def read_audio_file(input_path: str, verbose: bool = False) -> Tuple[np.ndarray, int]: def read_audio_file(input_path: str, verbose: bool = False) -> tuple[np.ndarray, int]:
"""Read an audio file and convert to int16 mono format. """Read an audio file and convert to int16 mono format.
This function: This function:

View File

@@ -12,7 +12,7 @@ adapters that handle tool format conversion and standardization.
import warnings import warnings
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Any, Dict, Generic, List, Optional, TypeVar from typing import Any, Generic, TypeVar
from loguru import logger from loguru import logger
@@ -50,10 +50,10 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
def __init__(self): def __init__(self):
"""Initialize the adapter.""" """Initialize the adapter."""
self._warned_system_instruction = False self._warned_system_instruction = False
self._builtin_tools: Dict[str, FunctionSchema] = {} self._builtin_tools: dict[str, FunctionSchema] = {}
@property @property
def builtin_tools(self) -> Dict[str, FunctionSchema]: def builtin_tools(self) -> dict[str, FunctionSchema]:
"""Built-in tools automatically merged into every inference request. """Built-in tools automatically merged into every inference request.
Keyed by tool name for O(1) lookup, insertion, and removal. The Keyed by tool name for O(1) lookup, insertion, and removal. The
@@ -90,7 +90,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
pass pass
@abstractmethod @abstractmethod
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Any]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[Any]:
"""Convert tools schema to the provider's specific format. """Convert tools schema to the provider's specific format.
Args: Args:
@@ -102,7 +102,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
pass pass
@abstractmethod @abstractmethod
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
"""Get messages from a universal LLM context in a format ready for logging about this provider. """Get messages from a universal LLM context in a format ready for logging about this provider.
Args: Args:
@@ -127,7 +127,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
def get_messages( def get_messages(
self, context: LLMContext, *, truncate_large_values: bool = False self, context: LLMContext, *, truncate_large_values: bool = False
) -> List[LLMContextMessage]: ) -> list[LLMContextMessage]:
"""Get messages from the LLM context, including standard and LLM-specific messages. """Get messages from the LLM context, including standard and LLM-specific messages.
Args: Args:
@@ -142,7 +142,7 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
self.id_for_llm_specific_messages, truncate_large_values=truncate_large_values self.id_for_llm_specific_messages, truncate_large_values=truncate_large_values
) )
def from_standard_tools(self, tools: Any) -> List[Any] | NotGiven: def from_standard_tools(self, tools: Any) -> list[Any] | NotGiven:
"""Convert tools from standard format to provider format. """Convert tools from standard format to provider format.
Built-in tools are automatically merged into the schema before conversion so that every Built-in tools are automatically merged into the schema before conversion so that every
@@ -188,8 +188,8 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
self, self,
messages: list, messages: list,
*, *,
system_instruction: Optional[str] = None, system_instruction: str | None = None,
) -> Optional[str]: ) -> str | None:
"""Extract an initial ``"system"`` message for use as a system instruction. """Extract an initial ``"system"`` message for use as a system instruction.
Only useful for services that expect the system instruction as a Only useful for services that expect the system instruction as a
@@ -247,11 +247,11 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]):
def _resolve_system_instruction( def _resolve_system_instruction(
self, self,
system_from_context: Optional[str], system_from_context: str | None,
system_instruction: Optional[str], system_instruction: str | None,
*, *,
discard_context_system: bool, discard_context_system: bool,
) -> Optional[str]: ) -> str | None:
"""Resolve conflict between ``system_instruction`` and an extracted context system message. """Resolve conflict between ``system_instruction`` and an extracted context system message.
Args: Args:

View File

@@ -15,16 +15,11 @@ formats).
import inspect import inspect
import types import types
from collections.abc import Callable, Mapping
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
Callable,
Dict,
List,
Mapping,
Protocol, Protocol,
Set,
Tuple,
Union, Union,
get_args, get_args,
get_origin, get_origin,
@@ -144,8 +139,8 @@ class BaseDirectFunctionWrapper:
# TODO: maybe to better support things like enums, check if each type is a pydantic type and use its convert-to-jsonschema function # TODO: maybe to better support things like enums, check if each type is a pydantic type and use its convert-to-jsonschema function
def _get_parameters_as_jsonschema( def _get_parameters_as_jsonschema(
self, func: Callable, docstring_params: List[docstring_parser.DocstringParam] self, func: Callable, docstring_params: list[docstring_parser.DocstringParam]
) -> Tuple[Dict[str, Any], List[str]]: ) -> tuple[dict[str, Any], list[str]]:
"""Get function parameters as a dictionary of JSON schemas and a list of required parameters. """Get function parameters as a dictionary of JSON schemas and a list of required parameters.
Ignore the first parameter, as it's expected to be the "special" one. Ignore the first parameter, as it's expected to be the "special" one.
@@ -193,7 +188,7 @@ class BaseDirectFunctionWrapper:
return properties, required return properties, required
def _typehint_to_jsonschema(self, type_hint: Any) -> Dict[str, Any]: def _typehint_to_jsonschema(self, type_hint: Any) -> dict[str, Any]:
"""Convert a Python type hint to a JSON Schema. """Convert a Python type hint to a JSON Schema.
Args: Args:
@@ -216,9 +211,9 @@ class BaseDirectFunctionWrapper:
return {"type": "number"} return {"type": "number"}
elif type_hint is bool: elif type_hint is bool:
return {"type": "boolean"} return {"type": "boolean"}
elif type_hint is dict or type_hint is Dict: elif type_hint is dict or type_hint is dict:
return {"type": "object"} return {"type": "object"}
elif type_hint is list or type_hint is List: elif type_hint is list or type_hint is list:
return {"type": "array"} return {"type": "array"}
# Get origin and arguments for complex types # Get origin and arguments for complex types
@@ -230,11 +225,11 @@ class BaseDirectFunctionWrapper:
return {"anyOf": [self._typehint_to_jsonschema(arg) for arg in args]} return {"anyOf": [self._typehint_to_jsonschema(arg) for arg in args]}
# Handle List, Tuple, Set with specific item types # Handle List, Tuple, Set with specific item types
if origin in (list, List, tuple, Tuple, set, Set) and args: if origin in (list, list, tuple, tuple, set, set) and args:
return {"type": "array", "items": self._typehint_to_jsonschema(args[0])} return {"type": "array", "items": self._typehint_to_jsonschema(args[0])}
# Handle Dict with specific key/value types # Handle Dict with specific key/value types
if origin in (dict, Dict) and len(args) == 2: if origin in (dict, dict) and len(args) == 2:
# For JSON Schema, keys must be strings # For JSON Schema, keys must be strings
return {"type": "object", "additionalProperties": self._typehint_to_jsonschema(args[1])} return {"type": "object", "additionalProperties": self._typehint_to_jsonschema(args[1])}

View File

@@ -11,7 +11,7 @@ tools and functions used with AI models, ensuring consistent formatting
across different AI service providers. across different AI service providers.
""" """
from typing import Any, Dict, List from typing import Any
class FunctionSchema: class FunctionSchema:
@@ -23,7 +23,7 @@ class FunctionSchema:
""" """
def __init__( def __init__(
self, name: str, description: str, properties: Dict[str, Any], required: List[str] self, name: str, description: str, properties: dict[str, Any], required: list[str]
) -> None: ) -> None:
"""Initialize the function schema. """Initialize the function schema.
@@ -38,7 +38,7 @@ class FunctionSchema:
self._properties = properties self._properties = properties
self._required = required self._required = required
def to_default_dict(self) -> Dict[str, Any]: def to_default_dict(self) -> dict[str, Any]:
"""Converts the function schema to a dictionary. """Converts the function schema to a dictionary.
Returns: Returns:
@@ -73,7 +73,7 @@ class FunctionSchema:
return self._description return self._description
@property @property
def properties(self) -> Dict[str, Any]: def properties(self) -> dict[str, Any]:
"""Get the function properties. """Get the function properties.
Returns: Returns:
@@ -82,7 +82,7 @@ class FunctionSchema:
return self._properties return self._properties
@property @property
def required(self) -> List[str]: def required(self) -> list[str]:
"""Get the required parameters. """Get the required parameters.
Returns: Returns:

View File

@@ -11,7 +11,7 @@ and custom adapter-specific tools in the Pipecat framework.
""" """
from enum import Enum from enum import Enum
from typing import Any, Dict, List, Optional from typing import Any
from pipecat.adapters.schemas.direct_function import DirectFunction, DirectFunctionWrapper from pipecat.adapters.schemas.direct_function import DirectFunction, DirectFunctionWrapper
from pipecat.adapters.schemas.function_schema import FunctionSchema from pipecat.adapters.schemas.function_schema import FunctionSchema
@@ -39,8 +39,8 @@ class ToolsSchema:
def __init__( def __init__(
self, self,
standard_tools: List[FunctionSchema | DirectFunction], standard_tools: list[FunctionSchema | DirectFunction],
custom_tools: Optional[Dict[AdapterType, List[Dict[str, Any]]]] = None, custom_tools: dict[AdapterType, list[dict[str, Any]]] | None = None,
) -> None: ) -> None:
"""Initialize the tools schema. """Initialize the tools schema.
@@ -66,7 +66,7 @@ class ToolsSchema:
self._custom_tools = custom_tools self._custom_tools = custom_tools
@property @property
def standard_tools(self) -> List[FunctionSchema]: def standard_tools(self) -> list[FunctionSchema]:
"""Get the list of standard function schema tools. """Get the list of standard function schema tools.
Returns: Returns:
@@ -75,7 +75,7 @@ class ToolsSchema:
return self._standard_tools return self._standard_tools
@property @property
def custom_tools(self) -> Dict[AdapterType, List[Dict[str, Any]]]: def custom_tools(self) -> dict[AdapterType, list[dict[str, Any]]]:
"""Get the custom tools dictionary. """Get the custom tools dictionary.
Returns: Returns:
@@ -84,7 +84,7 @@ class ToolsSchema:
return self._custom_tools return self._custom_tools
@custom_tools.setter @custom_tools.setter
def custom_tools(self, value: Dict[AdapterType, List[Dict[str, Any]]]) -> None: def custom_tools(self, value: dict[AdapterType, list[dict[str, Any]]]) -> None:
"""Set the custom tools dictionary. """Set the custom tools dictionary.
Args: Args:

View File

@@ -9,7 +9,7 @@
import copy import copy
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, TypedDict
from anthropic import NOT_GIVEN, NotGiven from anthropic import NOT_GIVEN, NotGiven
from anthropic.types.message_param import MessageParam from anthropic.types.message_param import MessageParam
@@ -31,8 +31,8 @@ class AnthropicLLMInvocationParams(TypedDict):
"""Context-based parameters for invoking Anthropic's LLM API.""" """Context-based parameters for invoking Anthropic's LLM API."""
system: str | NotGiven system: str | NotGiven
messages: List[MessageParam] messages: list[MessageParam]
tools: List[ToolUnionParam] tools: list[ToolUnionParam]
class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]): class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
@@ -51,7 +51,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
self, self,
context: LLMContext, context: LLMContext,
enable_prompt_caching: bool, enable_prompt_caching: bool,
system_instruction: Optional[str] = None, system_instruction: str | None = None,
) -> AnthropicLLMInvocationParams: ) -> AnthropicLLMInvocationParams:
"""Get Anthropic-specific LLM invocation parameters from a universal LLM context. """Get Anthropic-specific LLM invocation parameters from a universal LLM context.
@@ -83,7 +83,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
"tools": self.from_standard_tools(context.tools) or [], "tools": self.from_standard_tools(context.tools) or [],
} }
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
"""Get messages from a universal LLM context in a format ready for logging about Anthropic. """Get messages from a universal LLM context in a format ready for logging about Anthropic.
Removes or truncates sensitive data like image content for safe logging. Removes or truncates sensitive data like image content for safe logging.
@@ -115,14 +115,14 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
class ConvertedMessages: class ConvertedMessages:
"""Container for Anthropic-formatted messages converted from universal context.""" """Container for Anthropic-formatted messages converted from universal context."""
messages: List[MessageParam] messages: list[MessageParam]
system: str | NotGiven system: str | NotGiven
def _from_universal_context_messages( def _from_universal_context_messages(
self, self,
universal_context_messages: List[LLMContextMessage], universal_context_messages: list[LLMContextMessage],
*, *,
system_instruction: Optional[str] = None, system_instruction: str | None = None,
) -> ConvertedMessages: ) -> ConvertedMessages:
system = NOT_GIVEN system = NOT_GIVEN
@@ -333,7 +333,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
return message return message
def _with_cache_control_markers(self, messages: List[MessageParam]) -> List[MessageParam]: def _with_cache_control_markers(self, messages: list[MessageParam]) -> list[MessageParam]:
"""Add cache control markers to messages for prompt caching. """Add cache control markers to messages for prompt caching.
Args: Args:
@@ -381,7 +381,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
return messages_with_markers return messages_with_markers
@staticmethod @staticmethod
def _to_anthropic_function_format(function: FunctionSchema) -> Dict[str, Any]: def _to_anthropic_function_format(function: FunctionSchema) -> dict[str, Any]:
"""Convert a single function schema to Anthropic's format. """Convert a single function schema to Anthropic's format.
Args: Args:
@@ -400,7 +400,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
}, },
} }
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
"""Convert function schemas to Anthropic's function-calling format. """Convert function schemas to Anthropic's function-calling format.
Args: Args:

View File

@@ -10,7 +10,7 @@ import copy
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, TypedDict
from loguru import logger from loguru import logger
@@ -55,9 +55,9 @@ class AWSNovaSonicLLMInvocationParams(TypedDict):
This is a placeholder until support for universal LLMContext machinery is added for AWS Nova Sonic. This is a placeholder until support for universal LLMContext machinery is added for AWS Nova Sonic.
""" """
system_instruction: Optional[str] system_instruction: str | None
messages: List[AWSNovaSonicConversationHistoryMessage] messages: list[AWSNovaSonicConversationHistoryMessage]
tools: List[Dict[str, Any]] tools: list[dict[str, Any]]
class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]): class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
@@ -73,7 +73,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
return "aws-nova-sonic" return "aws-nova-sonic"
def get_llm_invocation_params( def get_llm_invocation_params(
self, context: LLMContext, *, system_instruction: Optional[str] = None self, context: LLMContext, *, system_instruction: str | None = None
) -> AWSNovaSonicLLMInvocationParams: ) -> AWSNovaSonicLLMInvocationParams:
"""Get AWS Nova Sonic-specific LLM invocation parameters from a universal LLM context. """Get AWS Nova Sonic-specific LLM invocation parameters from a universal LLM context.
@@ -97,7 +97,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
"tools": self.from_standard_tools(context.tools) or [], "tools": self.from_standard_tools(context.tools) or [],
} }
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
"""Get messages from a universal LLM context in a format ready for logging about AWS Nova Sonic. """Get messages from a universal LLM context in a format ready for logging about AWS Nova Sonic.
Removes or truncates sensitive data like image content for safe logging. Removes or truncates sensitive data like image content for safe logging.
@@ -116,11 +116,11 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
class ConvertedMessages: class ConvertedMessages:
"""Container for Google-formatted messages converted from universal context.""" """Container for Google-formatted messages converted from universal context."""
messages: List[AWSNovaSonicConversationHistoryMessage] messages: list[AWSNovaSonicConversationHistoryMessage]
system_instruction: Optional[str] = None system_instruction: str | None = None
def _from_universal_context_messages( def _from_universal_context_messages(
self, universal_context_messages: List[LLMContextMessage] self, universal_context_messages: list[LLMContextMessage]
) -> ConvertedMessages: ) -> ConvertedMessages:
system_instruction = None system_instruction = None
messages = [] messages = []
@@ -187,7 +187,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
# Sonic conversation history # Sonic conversation history
@staticmethod @staticmethod
def _to_aws_nova_sonic_function_format(function: FunctionSchema) -> Dict[str, Any]: def _to_aws_nova_sonic_function_format(function: FunctionSchema) -> dict[str, Any]:
"""Convert a function schema to AWS Nova Sonic format. """Convert a function schema to AWS Nova Sonic format.
Args: Args:
@@ -212,7 +212,7 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
} }
} }
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
"""Convert tools schema to AWS Nova Sonic function-calling format. """Convert tools schema to AWS Nova Sonic function-calling format.
Args: Args:

View File

@@ -10,7 +10,7 @@ import base64
import copy import copy
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, TypedDict
from loguru import logger from loguru import logger
@@ -29,9 +29,9 @@ from pipecat.processors.aggregators.llm_context import (
class AWSBedrockLLMInvocationParams(TypedDict): class AWSBedrockLLMInvocationParams(TypedDict):
"""Context-based parameters for invoking AWS Bedrock's LLM API.""" """Context-based parameters for invoking AWS Bedrock's LLM API."""
system: Optional[List[dict[str, Any]]] # [{"text": "system message"}] system: list[dict[str, Any]] | None # [{"text": "system message"}]
messages: List[dict[str, Any]] messages: list[dict[str, Any]]
tools: List[dict[str, Any]] tools: list[dict[str, Any]]
tool_choice: LLMContextToolChoice tool_choice: LLMContextToolChoice
@@ -48,7 +48,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
return "aws" return "aws"
def get_llm_invocation_params( def get_llm_invocation_params(
self, context: LLMContext, *, system_instruction: Optional[str] = None self, context: LLMContext, *, system_instruction: str | None = None
) -> AWSBedrockLLMInvocationParams: ) -> AWSBedrockLLMInvocationParams:
"""Get AWS Bedrock-specific LLM invocation parameters from a universal LLM context. """Get AWS Bedrock-specific LLM invocation parameters from a universal LLM context.
@@ -79,7 +79,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
"tool_choice": context.tool_choice, "tool_choice": context.tool_choice,
} }
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
"""Get messages from a universal LLM context in a format ready for logging about AWS Bedrock. """Get messages from a universal LLM context in a format ready for logging about AWS Bedrock.
Removes or truncates sensitive data like image content for safe logging. Removes or truncates sensitive data like image content for safe logging.
@@ -109,14 +109,14 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
class ConvertedMessages: class ConvertedMessages:
"""Container for Bedrock-formatted messages converted from universal context.""" """Container for Bedrock-formatted messages converted from universal context."""
messages: List[dict[str, Any]] messages: list[dict[str, Any]]
system: Optional[str] system: str | None
def _from_universal_context_messages( def _from_universal_context_messages(
self, self,
universal_context_messages: List[LLMContextMessage], universal_context_messages: list[LLMContextMessage],
*, *,
system_instruction: Optional[str] = None, system_instruction: str | None = None,
) -> ConvertedMessages: ) -> ConvertedMessages:
system = None system = None
@@ -305,7 +305,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
return message return message
@staticmethod @staticmethod
def _to_bedrock_function_format(function: FunctionSchema) -> Dict[str, Any]: def _to_bedrock_function_format(function: FunctionSchema) -> dict[str, Any]:
"""Convert a function schema to Bedrock's tool format. """Convert a function schema to Bedrock's tool format.
Args: Args:
@@ -328,7 +328,7 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
} }
} }
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
"""Convert function schemas to Bedrock's function-calling format. """Convert function schemas to Bedrock's function-calling format.
Args: Args:

View File

@@ -9,7 +9,7 @@
import base64 import base64
import json import json
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, TypedDict
from loguru import logger from loguru import logger
from openai import NotGiven from openai import NotGiven
@@ -34,9 +34,9 @@ except ModuleNotFoundError as e:
class GeminiLLMInvocationParams(TypedDict): class GeminiLLMInvocationParams(TypedDict):
"""Context-based parameters for invoking Gemini LLM.""" """Context-based parameters for invoking Gemini LLM."""
system_instruction: Optional[str] system_instruction: str | None
messages: List[Content] messages: list[Content]
tools: List[Any] | NotGiven tools: list[Any] | NotGiven
class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]): class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
@@ -54,7 +54,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
return "google" return "google"
def get_llm_invocation_params( def get_llm_invocation_params(
self, context: LLMContext, *, system_instruction: Optional[str] = None self, context: LLMContext, *, system_instruction: str | None = None
) -> GeminiLLMInvocationParams: ) -> GeminiLLMInvocationParams:
"""Get Gemini-specific LLM invocation parameters from a universal LLM context. """Get Gemini-specific LLM invocation parameters from a universal LLM context.
@@ -81,7 +81,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
"tools": self.from_standard_tools(context.tools), "tools": self.from_standard_tools(context.tools),
} }
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
"""Convert tool schemas to Gemini's function-calling format. """Convert tool schemas to Gemini's function-calling format.
Args: Args:
@@ -92,7 +92,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
Includes both converted standard tools and any custom Gemini-specific tools. Includes both converted standard tools and any custom Gemini-specific tools.
""" """
def _strip_additional_properties(schema: Dict[str, Any]) -> Dict[str, Any]: def _strip_additional_properties(schema: dict[str, Any]) -> dict[str, Any]:
"""Recursively remove "additionalProperties" fields from JSON schema, as they're not supported by Gemini. """Recursively remove "additionalProperties" fields from JSON schema, as they're not supported by Gemini.
Args: Args:
@@ -139,7 +139,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
return formatted_standard_tools + custom_gemini_tools return formatted_standard_tools + custom_gemini_tools
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
"""Get messages from a universal LLM context in a format ready for logging about Gemini. """Get messages from a universal LLM context in a format ready for logging about Gemini.
Removes or truncates sensitive data like image content for safe logging. Removes or truncates sensitive data like image content for safe logging.
@@ -173,8 +173,8 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
class ConvertedMessages: class ConvertedMessages:
"""Container for Google-formatted messages converted from universal context.""" """Container for Google-formatted messages converted from universal context."""
messages: List[Content] messages: list[Content]
system_instruction: Optional[str] = None system_instruction: str | None = None
@dataclass @dataclass
class MessageConversionResult: class MessageConversionResult:
@@ -184,20 +184,20 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
for any tool calls discovered in the message. for any tool calls discovered in the message.
""" """
content: Optional[Content] = None content: Content | None = None
tool_call_id_to_name_mapping: Dict[str, str] = field(default_factory=dict) tool_call_id_to_name_mapping: dict[str, str] = field(default_factory=dict)
@dataclass @dataclass
class MessageConversionParams: class MessageConversionParams:
"""Parameters for converting a single universal context message to Google format.""" """Parameters for converting a single universal context message to Google format."""
tool_call_id_to_name_mapping: Dict[str, str] tool_call_id_to_name_mapping: dict[str, str]
def _from_universal_context_messages( def _from_universal_context_messages(
self, self,
universal_context_messages: List[LLMContextMessage], universal_context_messages: list[LLMContextMessage],
*, *,
system_instruction: Optional[str] = None, system_instruction: str | None = None,
) -> ConvertedMessages: ) -> ConvertedMessages:
"""Restructures messages to ensure proper Google format and message ordering. """Restructures messages to ensure proper Google format and message ordering.
@@ -443,8 +443,8 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
) )
def _merge_parallel_tool_calls_for_thinking( def _merge_parallel_tool_calls_for_thinking(
self, thought_signature_dicts: List[dict], messages: List[Content] self, thought_signature_dicts: list[dict], messages: list[Content]
) -> List[Content]: ) -> list[Content]:
"""Merge parallel tool calls into single Content objects when thinking is enabled. """Merge parallel tool calls into single Content objects when thinking is enabled.
Gemini expects parallel tool calls (multiple function calls made Gemini expects parallel tool calls (multiple function calls made
@@ -540,7 +540,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
return merged_messages return merged_messages
def _apply_thought_signatures_to_messages( def _apply_thought_signatures_to_messages(
self, thought_signature_dicts: List[dict], messages: List[Content] self, thought_signature_dicts: list[dict], messages: list[Content]
) -> None: ) -> None:
"""Apply thought signatures to corresponding assistant messages. """Apply thought signatures to corresponding assistant messages.

View File

@@ -13,7 +13,7 @@ Grok's Voice Agent API.
import copy import copy
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, TypedDict
from loguru import logger from loguru import logger
@@ -33,9 +33,9 @@ class GrokRealtimeLLMInvocationParams(TypedDict):
tools: List of tool definitions (function, web_search, x_search, file_search). tools: List of tool definitions (function, web_search, x_search, file_search).
""" """
system_instruction: Optional[str] system_instruction: str | None
messages: List[events.ConversationItem] messages: list[events.ConversationItem]
tools: List[Dict[str, Any]] tools: list[dict[str, Any]]
class GrokRealtimeLLMAdapter(BaseLLMAdapter): class GrokRealtimeLLMAdapter(BaseLLMAdapter):
@@ -51,7 +51,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
return "grok-realtime" return "grok-realtime"
def get_llm_invocation_params( def get_llm_invocation_params(
self, context: LLMContext, *, system_instruction: Optional[str] = None self, context: LLMContext, *, system_instruction: str | None = None
) -> GrokRealtimeLLMInvocationParams: ) -> GrokRealtimeLLMInvocationParams:
"""Get Grok Realtime-specific LLM invocation parameters from a universal LLM context. """Get Grok Realtime-specific LLM invocation parameters from a universal LLM context.
@@ -74,7 +74,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
"tools": self.from_standard_tools(context.tools) or [], "tools": self.from_standard_tools(context.tools) or [],
} }
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
"""Get messages from context in a format safe for logging. """Get messages from context in a format safe for logging.
Binary data (images, audio) is replaced with short placeholders. Binary data (images, audio) is replaced with short placeholders.
@@ -91,11 +91,11 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
class ConvertedMessages: class ConvertedMessages:
"""Container for Grok-formatted messages converted from universal context.""" """Container for Grok-formatted messages converted from universal context."""
messages: List[events.ConversationItem] messages: list[events.ConversationItem]
system_instruction: Optional[str] = None system_instruction: str | None = None
def _from_universal_context_messages( def _from_universal_context_messages(
self, universal_context_messages: List[LLMContextMessage] self, universal_context_messages: list[LLMContextMessage]
) -> ConvertedMessages: ) -> ConvertedMessages:
"""Convert universal context messages to Grok Realtime format. """Convert universal context messages to Grok Realtime format.
@@ -211,7 +211,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
logger.error(f"Unhandled message type in _from_universal_context_message: {message}") logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
@staticmethod @staticmethod
def _to_grok_function_format(function: FunctionSchema) -> Dict[str, Any]: def _to_grok_function_format(function: FunctionSchema) -> dict[str, Any]:
"""Convert a function schema to Grok Realtime function format. """Convert a function schema to Grok Realtime function format.
Args: Args:
@@ -231,7 +231,7 @@ class GrokRealtimeLLMAdapter(BaseLLMAdapter):
}, },
} }
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
"""Convert tool schemas to Grok Realtime format. """Convert tool schemas to Grok Realtime format.
Supports both standard function tools and Grok-specific tools Supports both standard function tools and Grok-specific tools

View File

@@ -13,7 +13,7 @@ Inworld's Realtime API.
import copy import copy
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, TypedDict
from loguru import logger from loguru import logger
@@ -33,9 +33,9 @@ class InworldRealtimeLLMInvocationParams(TypedDict):
tools: List of tool definitions. tools: List of tool definitions.
""" """
system_instruction: Optional[str] system_instruction: str | None
messages: List[events.ConversationItem] messages: list[events.ConversationItem]
tools: List[Dict[str, Any]] tools: list[dict[str, Any]]
class InworldRealtimeLLMAdapter(BaseLLMAdapter): class InworldRealtimeLLMAdapter(BaseLLMAdapter):
@@ -51,7 +51,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
return "inworld-realtime" return "inworld-realtime"
def get_llm_invocation_params( def get_llm_invocation_params(
self, context: LLMContext, *, system_instruction: Optional[str] = None self, context: LLMContext, *, system_instruction: str | None = None
) -> InworldRealtimeLLMInvocationParams: ) -> InworldRealtimeLLMInvocationParams:
"""Get Inworld Realtime-specific LLM invocation parameters from a universal LLM context. """Get Inworld Realtime-specific LLM invocation parameters from a universal LLM context.
@@ -74,7 +74,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
"tools": self.from_standard_tools(context.tools) or [], "tools": self.from_standard_tools(context.tools) or [],
} }
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
"""Get messages from context in a format safe for logging. """Get messages from context in a format safe for logging.
Binary data (images, audio) is replaced with short placeholders. Binary data (images, audio) is replaced with short placeholders.
@@ -91,11 +91,11 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
class ConvertedMessages: class ConvertedMessages:
"""Container for Inworld-formatted messages converted from universal context.""" """Container for Inworld-formatted messages converted from universal context."""
messages: List[events.ConversationItem] messages: list[events.ConversationItem]
system_instruction: Optional[str] = None system_instruction: str | None = None
def _from_universal_context_messages( def _from_universal_context_messages(
self, universal_context_messages: List[LLMContextMessage] self, universal_context_messages: list[LLMContextMessage]
) -> ConvertedMessages: ) -> ConvertedMessages:
"""Convert universal context messages to Inworld Realtime format. """Convert universal context messages to Inworld Realtime format.
@@ -211,7 +211,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
logger.error(f"Unhandled message type in _from_universal_context_message: {message}") logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
@staticmethod @staticmethod
def _to_inworld_function_format(function: FunctionSchema) -> Dict[str, Any]: def _to_inworld_function_format(function: FunctionSchema) -> dict[str, Any]:
"""Convert a function schema to Inworld Realtime function format. """Convert a function schema to Inworld Realtime function format.
Args: Args:
@@ -231,7 +231,7 @@ class InworldRealtimeLLMAdapter(BaseLLMAdapter):
}, },
} }
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
"""Convert tool schemas to Inworld Realtime format. """Convert tool schemas to Inworld Realtime format.
Args: Args:

View File

@@ -6,7 +6,7 @@
"""OpenAI LLM adapter for Pipecat.""" """OpenAI LLM adapter for Pipecat."""
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, TypedDict
from openai._types import NotGiven as OpenAINotGiven from openai._types import NotGiven as OpenAINotGiven
from openai.types.chat import ( from openai.types.chat import (
@@ -29,8 +29,8 @@ from pipecat.processors.aggregators.llm_context import (
class OpenAILLMInvocationParams(TypedDict): class OpenAILLMInvocationParams(TypedDict):
"""Context-based parameters for invoking OpenAI ChatCompletion API.""" """Context-based parameters for invoking OpenAI ChatCompletion API."""
messages: List[ChatCompletionMessageParam] messages: list[ChatCompletionMessageParam]
tools: List[ChatCompletionToolParam] | OpenAINotGiven tools: list[ChatCompletionToolParam] | OpenAINotGiven
tool_choice: ChatCompletionToolChoiceOptionParam | OpenAINotGiven tool_choice: ChatCompletionToolChoiceOptionParam | OpenAINotGiven
@@ -54,7 +54,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
self, self,
context: LLMContext, context: LLMContext,
*, *,
system_instruction: Optional[str] = None, system_instruction: str | None = None,
convert_developer_to_user: bool, convert_developer_to_user: bool,
) -> OpenAILLMInvocationParams: ) -> OpenAILLMInvocationParams:
"""Get OpenAI-specific LLM invocation parameters from a universal LLM context. """Get OpenAI-specific LLM invocation parameters from a universal LLM context.
@@ -95,7 +95,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
"tool_choice": context.tool_choice, "tool_choice": context.tool_choice,
} }
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[ChatCompletionToolParam]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[ChatCompletionToolParam]:
"""Convert function schemas to OpenAI's function-calling format. """Convert function schemas to OpenAI's function-calling format.
Args: Args:
@@ -115,7 +115,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, []) custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, [])
return formatted_standard_tools + custom_openai_tools return formatted_standard_tools + custom_openai_tools
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
"""Get messages from a universal LLM context in a format ready for logging about OpenAI. """Get messages from a universal LLM context in a format ready for logging about OpenAI.
Binary data (images, audio) is replaced with short placeholders. Binary data (images, audio) is replaced with short placeholders.
@@ -130,10 +130,10 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
def _from_universal_context_messages( def _from_universal_context_messages(
self, self,
messages: List[LLMContextMessage], messages: list[LLMContextMessage],
*, *,
convert_developer_to_user: bool, convert_developer_to_user: bool,
) -> List[ChatCompletionMessageParam]: ) -> list[ChatCompletionMessageParam]:
result = [] result = []
for message in messages: for message in messages:
if isinstance(message, LLMSpecificMessage): if isinstance(message, LLMSpecificMessage):

View File

@@ -9,7 +9,7 @@
import copy import copy
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, TypedDict
from loguru import logger from loguru import logger
@@ -26,9 +26,9 @@ class OpenAIRealtimeLLMInvocationParams(TypedDict):
This is a placeholder until support for universal LLMContext machinery is added for OpenAI Realtime. This is a placeholder until support for universal LLMContext machinery is added for OpenAI Realtime.
""" """
system_instruction: Optional[str] system_instruction: str | None
messages: List[events.ConversationItem] messages: list[events.ConversationItem]
tools: List[Dict[str, Any]] tools: list[dict[str, Any]]
class OpenAIRealtimeLLMAdapter(BaseLLMAdapter): class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
@@ -44,7 +44,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
return "openai-realtime" return "openai-realtime"
def get_llm_invocation_params( def get_llm_invocation_params(
self, context: LLMContext, *, system_instruction: Optional[str] = None self, context: LLMContext, *, system_instruction: str | None = None
) -> OpenAIRealtimeLLMInvocationParams: ) -> OpenAIRealtimeLLMInvocationParams:
"""Get OpenAI Realtime-specific LLM invocation parameters from a universal LLM context. """Get OpenAI Realtime-specific LLM invocation parameters from a universal LLM context.
@@ -68,7 +68,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
"tools": self.from_standard_tools(context.tools) or [], "tools": self.from_standard_tools(context.tools) or [],
} }
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context) -> list[dict[str, Any]]:
"""Get messages from a universal LLM context in a format ready for logging about OpenAI Realtime. """Get messages from a universal LLM context in a format ready for logging about OpenAI Realtime.
Binary data (images, audio) is replaced with short placeholders. Binary data (images, audio) is replaced with short placeholders.
@@ -87,11 +87,11 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
class ConvertedMessages: class ConvertedMessages:
"""Container for OpenAI-formatted messages converted from universal context.""" """Container for OpenAI-formatted messages converted from universal context."""
messages: List[events.ConversationItem] messages: list[events.ConversationItem]
system_instruction: Optional[str] = None system_instruction: str | None = None
def _from_universal_context_messages( def _from_universal_context_messages(
self, universal_context_messages: List[LLMContextMessage] self, universal_context_messages: list[LLMContextMessage]
) -> ConvertedMessages: ) -> ConvertedMessages:
# We can't load a long conversation history into the openai realtime api yet. (The API/model # We can't load a long conversation history into the openai realtime api yet. (The API/model
# forgets that it can do audio, if you do a series of `conversation.item.create` calls.) So # forgets that it can do audio, if you do a series of `conversation.item.create` calls.) So
@@ -188,7 +188,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
logger.error(f"Unhandled message type in _from_universal_context_message: {message}") logger.error(f"Unhandled message type in _from_universal_context_message: {message}")
@staticmethod @staticmethod
def _to_openai_realtime_function_format(function: FunctionSchema) -> Dict[str, Any]: def _to_openai_realtime_function_format(function: FunctionSchema) -> dict[str, Any]:
"""Convert a function schema to OpenAI Realtime format. """Convert a function schema to OpenAI Realtime format.
Args: Args:
@@ -208,7 +208,7 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
}, },
} }
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[Dict[str, Any]]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[dict[str, Any]]:
"""Convert tool schemas to OpenAI Realtime function-calling format. """Convert tool schemas to OpenAI Realtime function-calling format.
Args: Args:

View File

@@ -6,7 +6,7 @@
"""OpenAI Responses API adapter for Pipecat.""" """OpenAI Responses API adapter for Pipecat."""
from typing import Any, Dict, List, Optional, TypedDict from typing import Any, TypedDict
from openai._types import NotGiven as OpenAINotGiven from openai._types import NotGiven as OpenAINotGiven
from openai.types.responses import FunctionToolParam, ResponseInputItemParam, ToolParam from openai.types.responses import FunctionToolParam, ResponseInputItemParam, ToolParam
@@ -23,8 +23,8 @@ from pipecat.processors.aggregators.llm_context import (
class OpenAIResponsesLLMInvocationParams(TypedDict, total=False): class OpenAIResponsesLLMInvocationParams(TypedDict, total=False):
"""Context-based parameters for invoking OpenAI Responses API.""" """Context-based parameters for invoking OpenAI Responses API."""
input: List[ResponseInputItemParam] input: list[ResponseInputItemParam]
tools: List[ToolParam] | OpenAINotGiven tools: list[ToolParam] | OpenAINotGiven
instructions: str instructions: str
@@ -47,7 +47,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
self, self,
context: LLMContext, context: LLMContext,
*, *,
system_instruction: Optional[str] = None, system_instruction: str | None = None,
) -> OpenAIResponsesLLMInvocationParams: ) -> OpenAIResponsesLLMInvocationParams:
"""Get Responses API invocation parameters from a universal LLM context. """Get Responses API invocation parameters from a universal LLM context.
@@ -105,7 +105,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
return params return params
def to_provider_tools_format(self, tools_schema: ToolsSchema) -> List[ToolParam]: def to_provider_tools_format(self, tools_schema: ToolsSchema) -> list[ToolParam]:
"""Convert function schemas to Responses API function tool format. """Convert function schemas to Responses API function tool format.
Args: Args:
@@ -132,7 +132,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, []) custom_openai_tools = tools_schema.custom_tools.get(AdapterType.OPENAI, [])
return result + custom_openai_tools return result + custom_openai_tools
def get_messages_for_logging(self, context: LLMContext) -> List[Dict[str, Any]]: def get_messages_for_logging(self, context: LLMContext) -> list[dict[str, Any]]:
"""Get messages from context in a format ready for logging. """Get messages from context in a format ready for logging.
Binary data (images, audio) is replaced with short placeholders. Binary data (images, audio) is replaced with short placeholders.
@@ -146,8 +146,8 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
return self.get_messages(context, truncate_large_values=True) return self.get_messages(context, truncate_large_values=True)
def _convert_messages_to_input( def _convert_messages_to_input(
self, messages: List[LLMContextMessage] self, messages: list[LLMContextMessage]
) -> List[ResponseInputItemParam]: ) -> list[ResponseInputItemParam]:
"""Convert LLMContext messages to Responses API input items. """Convert LLMContext messages to Responses API input items.
Args: Args:
@@ -156,7 +156,7 @@ class OpenAIResponsesLLMAdapter(BaseLLMAdapter[OpenAIResponsesLLMInvocationParam
Returns: Returns:
List of Responses API input items. List of Responses API input items.
""" """
result: List[ResponseInputItemParam] = [] result: list[ResponseInputItemParam] = []
for message in messages: for message in messages:
if isinstance(message, LLMSpecificMessage): if isinstance(message, LLMSpecificMessage):

View File

@@ -28,7 +28,6 @@ the messages are sent to Perplexity's API.
""" """
import copy import copy
from typing import List, Optional
from openai.types.chat import ChatCompletionMessageParam from openai.types.chat import ChatCompletionMessageParam
@@ -53,7 +52,7 @@ class PerplexityLLMAdapter(OpenAILLMAdapter):
self, self,
context: LLMContext, context: LLMContext,
*, *,
system_instruction: Optional[str] = None, system_instruction: str | None = None,
convert_developer_to_user: bool, convert_developer_to_user: bool,
) -> OpenAILLMInvocationParams: ) -> OpenAILLMInvocationParams:
"""Get OpenAI-compatible invocation parameters with Perplexity message fixes applied. """Get OpenAI-compatible invocation parameters with Perplexity message fixes applied.
@@ -78,8 +77,8 @@ class PerplexityLLMAdapter(OpenAILLMAdapter):
return params return params
def _transform_messages( def _transform_messages(
self, messages: List[ChatCompletionMessageParam] self, messages: list[ChatCompletionMessageParam]
) -> List[ChatCompletionMessageParam]: ) -> list[ChatCompletionMessageParam]:
"""Transform messages to satisfy Perplexity's API constraints. """Transform messages to satisfy Perplexity's API constraints.
Applies three transformation steps in order: Applies three transformation steps in order:

View File

@@ -11,10 +11,10 @@ key on the telephone keypad, facilitating the handling of input in
telecommunication applications. telecommunication applications.
""" """
from enum import Enum from enum import StrEnum
class KeypadEntry(str, Enum): class KeypadEntry(StrEnum):
"""DTMF keypad entries for phone system integration. """DTMF keypad entries for phone system integration.
Parameters: Parameters:

View File

@@ -15,7 +15,6 @@ import asyncio
import io import io
import wave import wave
from importlib.resources import files from importlib.resources import files
from typing import Dict, Optional
import aiofiles import aiofiles
@@ -24,8 +23,8 @@ from pipecat.audio.resamplers.base_audio_resampler import BaseAudioResampler
from pipecat.audio.utils import create_file_resampler from pipecat.audio.utils import create_file_resampler
__DTMF_LOCK__ = asyncio.Lock() __DTMF_LOCK__ = asyncio.Lock()
__DTMF_AUDIO__: Dict[KeypadEntry, bytes] = {} __DTMF_AUDIO__: dict[KeypadEntry, bytes] = {}
__DTMF_RESAMPLER__: Optional[BaseAudioResampler] = None __DTMF_RESAMPLER__: BaseAudioResampler | None = None
__DTMF_FILE_NAME = { __DTMF_FILE_NAME = {
KeypadEntry.POUND: "dtmf-pound.wav", KeypadEntry.POUND: "dtmf-pound.wav",

View File

@@ -18,7 +18,6 @@ Classes:
import asyncio import asyncio
from pathlib import Path from pathlib import Path
from threading import Lock from threading import Lock
from typing import List, Optional, Tuple
import numpy as np import numpy as np
from aic_sdk import ( from aic_sdk import (
@@ -44,14 +43,14 @@ class AICModelManager:
acquires on first use and releases when the last reference is dropped. acquires on first use and releases when the last reference is dropped.
""" """
_cache: dict[str, Tuple[Model, int]] = {} # key -> (model, ref_count) _cache: dict[str, tuple[Model, int]] = {} # key -> (model, ref_count)
_lock = Lock() _lock = Lock()
_loading: dict[ _loading: dict[
str, asyncio.Task[Model] str, asyncio.Task[Model]
] = {} # key -> load task (deduplicates concurrent loads) ] = {} # key -> load task (deduplicates concurrent loads)
@classmethod @classmethod
def _increment_reference(cls, cache_key: str, entry: Tuple[Model, int]) -> Tuple[Model, str]: def _increment_reference(cls, cache_key: str, entry: tuple[Model, int]) -> tuple[Model, str]:
"""Increment reference count for cached entry. Caller must hold _lock.""" """Increment reference count for cached entry. Caller must hold _lock."""
cached_model, ref_count = entry cached_model, ref_count = entry
cls._cache[cache_key] = (cached_model, ref_count + 1) cls._cache[cache_key] = (cached_model, ref_count + 1)
@@ -59,7 +58,7 @@ class AICModelManager:
return cached_model, cache_key return cached_model, cache_key
@classmethod @classmethod
def _store_new_reference(cls, cache_key: str, model: Model) -> Tuple[Model, str]: def _store_new_reference(cls, cache_key: str, model: Model) -> tuple[Model, str]:
"""Store new model in cache with ref count 1. Caller must hold _lock.""" """Store new model in cache with ref count 1. Caller must hold _lock."""
cls._cache[cache_key] = (model, 1) cls._cache[cache_key] = (model, 1)
logger.debug(f"AIC model cached key={cache_key!r} ref_count=1") logger.debug(f"AIC model cached key={cache_key!r} ref_count=1")
@@ -70,9 +69,9 @@ class AICModelManager:
cls, cls,
cache_key: str, cache_key: str,
*, *,
model_path: Optional[Path] = None, model_path: Path | None = None,
model_id: Optional[str] = None, model_id: str | None = None,
model_download_dir: Optional[Path] = None, model_download_dir: Path | None = None,
) -> Model: ) -> Model:
"""Run the actual load (file or download). Separate to allow create_task and deduplication.""" """Run the actual load (file or download). Separate to allow create_task and deduplication."""
if model_path is not None: if model_path is not None:
@@ -94,9 +93,9 @@ class AICModelManager:
@staticmethod @staticmethod
def _get_cache_key( def _get_cache_key(
*, *,
model_path: Optional[Path] = None, model_path: Path | None = None,
model_id: Optional[str] = None, model_id: str | None = None,
model_download_dir: Optional[Path] = None, model_download_dir: Path | None = None,
) -> str: ) -> str:
"""Build a stable cache key for the model. """Build a stable cache key for the model.
@@ -120,10 +119,10 @@ class AICModelManager:
async def acquire( async def acquire(
cls, cls,
*, *,
model_path: Optional[Path] = None, model_path: Path | None = None,
model_id: Optional[str] = None, model_id: str | None = None,
model_download_dir: Optional[Path] = None, model_download_dir: Path | None = None,
) -> Tuple[Model, str]: ) -> tuple[Model, str]:
"""Get or load a Model and increment its reference count. """Get or load a Model and increment its reference count.
Call this when starting a filter. Store the returned key and pass it Call this when starting a filter. Store the returned key and pass it
@@ -218,10 +217,10 @@ class AICFilter(BaseAudioFilter):
self, self,
*, *,
license_key: str, license_key: str,
model_id: Optional[str] = None, model_id: str | None = None,
model_path: Optional[Path] = None, model_path: Path | None = None,
model_download_dir: Optional[Path] = None, model_download_dir: Path | None = None,
enhancement_level: Optional[float] = None, enhancement_level: float | None = None,
) -> None: ) -> None:
"""Initialize the AIC filter. """Initialize the AIC filter.
@@ -274,7 +273,7 @@ class AICFilter(BaseAudioFilter):
) )
# AIC SDK objects; model is shared via AICModelManager # AIC SDK objects; model is shared via AICModelManager
self._model_cache_key: Optional[str] = None self._model_cache_key: str | None = None
self._model = None self._model = None
self._processor = None self._processor = None
self._processor_ctx = None self._processor_ctx = None
@@ -298,9 +297,9 @@ class AICFilter(BaseAudioFilter):
def create_vad_analyzer( def create_vad_analyzer(
self, self,
*, *,
speech_hold_duration: Optional[float] = None, speech_hold_duration: float | None = None,
minimum_speech_duration: Optional[float] = None, minimum_speech_duration: float | None = None,
sensitivity: Optional[float] = None, sensitivity: float | None = None,
): ):
"""Return an analyzer that will lazily instantiate the AIC VAD when ready. """Return an analyzer that will lazily instantiate the AIC VAD when ready.
@@ -491,7 +490,7 @@ class AICFilter(BaseAudioFilter):
blocks_data = bytes(self._audio_buffer[:total_size]) blocks_data = bytes(self._audio_buffer[:total_size])
self._audio_buffer = self._audio_buffer[total_size:] self._audio_buffer = self._audio_buffer[total_size:]
filtered_chunks: List[bytes] = [] filtered_chunks: list[bytes] = []
for i in range(num_blocks): for i in range(num_blocks):
start = i * block_size start = i * block_size

View File

@@ -10,7 +10,7 @@ This module provides an audio filter implementation using PicoVoice's Koala
Noise Suppression engine to reduce background noise in audio streams. Noise Suppression engine to reduce background noise in audio streams.
""" """
from typing import Sequence from collections.abc import Sequence
import numpy as np import numpy as np
from loguru import logger from loguru import logger

View File

@@ -12,7 +12,8 @@ runtime configuration changes.
""" """
import asyncio import asyncio
from typing import Any, Dict, Mapping from collections.abc import Mapping
from typing import Any
import numpy as np import numpy as np
from loguru import logger from loguru import logger
@@ -70,7 +71,7 @@ class SoundfileMixer(BaseAudioMixer):
self._sample_rate = 0 self._sample_rate = 0
self._sound_pos = 0 self._sound_pos = 0
self._sounds: Dict[str, Any] = {} self._sounds: dict[str, Any] = {}
self._current_sound = default_sound self._current_sound = default_sound
self._mixing = mixing self._mixing = mixing
self._loop = loop self._loop = loop

View File

@@ -12,7 +12,6 @@ when a user has finished speaking in a conversation.
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from enum import Enum from enum import Enum
from typing import Optional, Tuple
from pydantic import BaseModel from pydantic import BaseModel
@@ -44,7 +43,7 @@ class BaseTurnAnalyzer(ABC):
while still defining an abstract interface through abstract methods. while still defining an abstract interface through abstract methods.
""" """
def __init__(self, *, sample_rate: Optional[int] = None): def __init__(self, *, sample_rate: int | None = None):
"""Initialize the turn analyzer. """Initialize the turn analyzer.
Args: Args:
@@ -108,7 +107,7 @@ class BaseTurnAnalyzer(ABC):
pass pass
@abstractmethod @abstractmethod
async def analyze_end_of_turn(self) -> Tuple[EndOfTurnState, Optional[MetricsData]]: async def analyze_end_of_turn(self) -> tuple[EndOfTurnState, MetricsData | None]:
"""Analyzes if an end of turn has occurred based on the audio input. """Analyzes if an end of turn has occurred based on the audio input.
Returns: Returns:

View File

@@ -16,7 +16,6 @@ passed directly to the constructor.
import os import os
import time import time
from typing import Optional, Tuple
import numpy as np import numpy as np
from loguru import logger from loguru import logger
@@ -61,9 +60,9 @@ class KrispVivaTurn(BaseTurnAnalyzer):
def __init__( def __init__(
self, self,
*, *,
model_path: Optional[str] = None, model_path: str | None = None,
sample_rate: Optional[int] = None, sample_rate: int | None = None,
params: Optional[KrispTurnParams] = None, params: KrispTurnParams | None = None,
api_key: str = "", api_key: str = "",
) -> None: ) -> None:
"""Initialize the Krisp turn analyzer. """Initialize the Krisp turn analyzer.
@@ -119,9 +118,9 @@ class KrispVivaTurn(BaseTurnAnalyzer):
self._last_probability = None self._last_probability = None
self._frame_probabilities = [] self._frame_probabilities = []
self._last_state = EndOfTurnState.INCOMPLETE self._last_state = EndOfTurnState.INCOMPLETE
self._speech_stopped_time: Optional[float] = None self._speech_stopped_time: float | None = None
self._e2e_processing_time_ms: Optional[float] = None self._e2e_processing_time_ms: float | None = None
self._last_metrics: Optional[TurnMetricsData] = None self._last_metrics: TurnMetricsData | None = None
# Create session with provided sample rate or default to 16000 Hz # Create session with provided sample rate or default to 16000 Hz
# This preloads the model to improve latency when set_sample_rate is called later # This preloads the model to improve latency when set_sample_rate is called later
@@ -214,7 +213,7 @@ class KrispVivaTurn(BaseTurnAnalyzer):
return self._frame_probabilities return self._frame_probabilities
@property @property
def last_probability(self) -> Optional[float]: def last_probability(self) -> float | None:
"""Get the last turn probability value computed. """Get the last turn probability value computed.
Returns: Returns:
@@ -348,7 +347,7 @@ class KrispVivaTurn(BaseTurnAnalyzer):
self._last_state = error_state self._last_state = error_state
return error_state return error_state
async def analyze_end_of_turn(self) -> Tuple[EndOfTurnState, Optional[MetricsData]]: async def analyze_end_of_turn(self) -> tuple[EndOfTurnState, MetricsData | None]:
"""Analyze the current audio state to determine if turn has ended. """Analyze the current audio state to determine if turn has ended.
Returns: Returns:

View File

@@ -15,7 +15,7 @@ import asyncio
import time import time
from abc import abstractmethod from abc import abstractmethod
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from typing import Any, Dict, Optional, Tuple from typing import Any
import numpy as np import numpy as np
from loguru import logger from loguru import logger
@@ -57,9 +57,7 @@ class BaseSmartTurn(BaseTurnAnalyzer):
implement the specific model prediction logic. implement the specific model prediction logic.
""" """
def __init__( def __init__(self, *, sample_rate: int | None = None, params: SmartTurnParams | None = None):
self, *, sample_rate: Optional[int] = None, params: Optional[SmartTurnParams] = None
):
"""Initialize the smart turn analyzer. """Initialize the smart turn analyzer.
Args: Args:
@@ -146,7 +144,7 @@ class BaseSmartTurn(BaseTurnAnalyzer):
return state return state
async def analyze_end_of_turn(self) -> Tuple[EndOfTurnState, Optional[MetricsData]]: async def analyze_end_of_turn(self) -> tuple[EndOfTurnState, MetricsData | None]:
"""Analyze the current audio state to determine if turn has ended. """Analyze the current audio state to determine if turn has ended.
Returns: Returns:
@@ -178,7 +176,7 @@ class BaseSmartTurn(BaseTurnAnalyzer):
self._speech_start_time = 0 self._speech_start_time = 0
self._silence_ms = 0 self._silence_ms = 0
def _process_speech_segment(self, audio_buffer) -> Tuple[EndOfTurnState, Optional[MetricsData]]: def _process_speech_segment(self, audio_buffer) -> tuple[EndOfTurnState, MetricsData | None]:
"""Process accumulated audio segment using ML model.""" """Process accumulated audio segment using ML model."""
state = EndOfTurnState.INCOMPLETE state = EndOfTurnState.INCOMPLETE
@@ -248,6 +246,6 @@ class BaseSmartTurn(BaseTurnAnalyzer):
return state, result_data return state, result_data
@abstractmethod @abstractmethod
def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]: def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
"""Predict end-of-turn using ML model from audio data.""" """Predict end-of-turn using ML model from audio data."""
pass pass

View File

@@ -12,7 +12,7 @@ HTTP endpoints for ML-based end-of-turn detection.
import asyncio import asyncio
import io import io
from typing import Any, Dict, Optional from typing import Any
import aiohttp import aiohttp
import numpy as np import numpy as np
@@ -33,7 +33,7 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
*, *,
url: str, url: str,
aiohttp_session: aiohttp.ClientSession, aiohttp_session: aiohttp.ClientSession,
headers: Optional[Dict[str, str]] = None, headers: dict[str, str] | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the HTTP smart turn analyzer. """Initialize the HTTP smart turn analyzer.
@@ -58,7 +58,7 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
logger.trace(f"Serialized size: {len(serialized_bytes)} bytes") logger.trace(f"Serialized size: {len(serialized_bytes)} bytes")
return serialized_bytes return serialized_bytes
async def _send_raw_request(self, data_bytes: bytes) -> Dict[str, Any]: async def _send_raw_request(self, data_bytes: bytes) -> dict[str, Any]:
"""Send raw audio data to the HTTP endpoint for prediction.""" """Send raw audio data to the HTTP endpoint for prediction."""
headers = {"Content-Type": "application/octet-stream"} headers = {"Content-Type": "application/octet-stream"}
headers.update(self._headers) headers.update(self._headers)
@@ -97,14 +97,14 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
logger.trace(text) logger.trace(text)
raise Exception(f"Non-JSON response: {text}") raise Exception(f"Non-JSON response: {text}")
except asyncio.TimeoutError: except TimeoutError:
logger.error(f"Request timed out after {self._params.stop_secs} seconds") logger.error(f"Request timed out after {self._params.stop_secs} seconds")
raise SmartTurnTimeoutException(f"Request exceeded {self._params.stop_secs} seconds.") raise SmartTurnTimeoutException(f"Request exceeded {self._params.stop_secs} seconds.")
except aiohttp.ClientError as e: except aiohttp.ClientError as e:
logger.error(f"Failed to send raw request to Daily Smart Turn: {e}") logger.error(f"Failed to send raw request to Daily Smart Turn: {e}")
raise Exception("Failed to send raw request to Daily Smart Turn.") raise Exception("Failed to send raw request to Daily Smart Turn.")
def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]: def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
"""Predict end-of-turn using remote HTTP ML service.""" """Predict end-of-turn using remote HTTP ML service."""
try: try:
serialized_array = self._serialize_array(audio_array) serialized_array = self._serialize_array(audio_array)

View File

@@ -11,7 +11,7 @@ local end-of-turn detection without requiring network connectivity.
""" """
import warnings import warnings
from typing import Any, Dict from typing import Any
import numpy as np import numpy as np
from loguru import logger from loguru import logger
@@ -76,7 +76,7 @@ class LocalCoreMLSmartTurnAnalyzer(BaseSmartTurn):
self._turn_model = ct.models.MLModel(core_ml_model_path) self._turn_model = ct.models.MLModel(core_ml_model_path)
logger.debug("Loaded Local Smart Turn") logger.debug("Loaded Local Smart Turn")
async def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]: async def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
"""Predict end-of-turn using local CoreML model.""" """Predict end-of-turn using local CoreML model."""
inputs = self._turn_processor( inputs = self._turn_processor(
audio_array, audio_array,

View File

@@ -11,7 +11,7 @@ local end-of-turn detection without requiring network connectivity.
""" """
import warnings import warnings
from typing import Any, Dict from typing import Any
import numpy as np import numpy as np
from loguru import logger from loguru import logger
@@ -87,7 +87,7 @@ class LocalSmartTurnAnalyzerV2(BaseSmartTurn):
self._turn_model.eval() self._turn_model.eval()
logger.debug("Loaded Local Smart Turn v2") logger.debug("Loaded Local Smart Turn v2")
def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]: def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
"""Predict end-of-turn using local PyTorch model.""" """Predict end-of-turn using local PyTorch model."""
inputs = self._turn_processor( inputs = self._turn_processor(
audio_array, audio_array,

View File

@@ -10,7 +10,7 @@ This module provides a smart turn analyzer that uses an ONNX model for
local end-of-turn detection without requiring network connectivity. local end-of-turn detection without requiring network connectivity.
""" """
from typing import Any, Dict, Optional from typing import Any
import numpy as np import numpy as np
import onnxruntime as ort import onnxruntime as ort
@@ -32,9 +32,7 @@ class LocalSmartTurnAnalyzerV3(BaseSmartTurn):
enabling offline operation without network dependencies. enabling offline operation without network dependencies.
""" """
def __init__( def __init__(self, *, smart_turn_model_path: str | None = None, cpu_count: int = 1, **kwargs):
self, *, smart_turn_model_path: Optional[str] = None, cpu_count: int = 1, **kwargs
):
"""Initialize the local ONNX smart-turn-v3 analyzer. """Initialize the local ONNX smart-turn-v3 analyzer.
Args: Args:
@@ -138,7 +136,7 @@ class LocalSmartTurnAnalyzerV3(BaseSmartTurn):
return soxr.resample(audio_array, actual_rate, _MODEL_SAMPLE_RATE, quality="VHQ") return soxr.resample(audio_array, actual_rate, _MODEL_SAMPLE_RATE, quality="VHQ")
def _predict_endpoint(self, audio_array: np.ndarray) -> Dict[str, Any]: def _predict_endpoint(self, audio_array: np.ndarray) -> dict[str, Any]:
"""Predict end-of-turn using local ONNX model.""" """Predict end-of-turn using local ONNX model."""
def truncate_audio_to_last_n_seconds( def truncate_audio_to_last_n_seconds(

View File

@@ -7,7 +7,8 @@ Classes:
AICVADAnalyzer: For aic-sdk (uses 'aic_sdk' module) AICVADAnalyzer: For aic-sdk (uses 'aic_sdk' module)
""" """
from typing import Any, Callable, Optional from collections.abc import Callable
from typing import Any
from aic_sdk import VadParameter from aic_sdk import VadParameter
from loguru import logger from loguru import logger
@@ -46,10 +47,10 @@ class AICVADAnalyzer(VADAnalyzer):
def __init__( def __init__(
self, self,
*, *,
vad_context_factory: Optional[Callable[[], Any]] = None, vad_context_factory: Callable[[], Any] | None = None,
speech_hold_duration: Optional[float] = None, speech_hold_duration: float | None = None,
minimum_speech_duration: Optional[float] = None, minimum_speech_duration: float | None = None,
sensitivity: Optional[float] = None, sensitivity: float | None = None,
): ):
"""Create an AIC VAD analyzer. """Create an AIC VAD analyzer.
@@ -77,10 +78,10 @@ class AICVADAnalyzer(VADAnalyzer):
super().__init__(sample_rate=None, params=fixed_params) super().__init__(sample_rate=None, params=fixed_params)
self._vad_context_factory = vad_context_factory self._vad_context_factory = vad_context_factory
self._vad_ctx: Optional[Any] = None self._vad_ctx: Any | None = None
self._pending_speech_hold_duration: Optional[float] = speech_hold_duration self._pending_speech_hold_duration: float | None = speech_hold_duration
self._pending_minimum_speech_duration: Optional[float] = minimum_speech_duration self._pending_minimum_speech_duration: float | None = minimum_speech_duration
self._pending_sensitivity: Optional[float] = sensitivity self._pending_sensitivity: float | None = sensitivity
def bind_vad_context_factory(self, vad_context_factory: Callable[[], Any]): def bind_vad_context_factory(self, vad_context_factory: Callable[[], Any]):
"""Attach or replace the factory post-construction.""" """Attach or replace the factory post-construction."""

View File

@@ -12,7 +12,6 @@ Supports 8kHz, 16kHz, 32kHz, 44.1kHz and 48kHz sample rates.
""" """
import os import os
from typing import Optional
import numpy as np import numpy as np
from loguru import logger from loguru import logger
@@ -38,10 +37,10 @@ class KrispVivaVadAnalyzer(VADAnalyzer):
def __init__( def __init__(
self, self,
*, *,
model_path: Optional[str] = None, model_path: str | None = None,
frame_duration: int = 10, frame_duration: int = 10,
sample_rate: Optional[int] = None, sample_rate: int | None = None,
params: Optional[VADParams] = None, params: VADParams | None = None,
): ):
"""Initialize the Krisp VIVA VAD analyzer. """Initialize the Krisp VIVA VAD analyzer.

View File

@@ -12,7 +12,6 @@ Supports 8kHz and 16kHz sample rates.
""" """
import time import time
from typing import Optional
import numpy as np import numpy as np
from loguru import logger from loguru import logger
@@ -135,7 +134,7 @@ class SileroVADAnalyzer(VADAnalyzer):
with automatic model state management and periodic resets. with automatic model state management and periodic resets.
""" """
def __init__(self, *, sample_rate: Optional[int] = None, params: Optional[VADParams] = None): def __init__(self, *, sample_rate: int | None = None, params: VADParams | None = None):
"""Initialize the Silero VAD analyzer. """Initialize the Silero VAD analyzer.
Args: Args:

View File

@@ -15,7 +15,6 @@ import asyncio
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from enum import Enum from enum import Enum
from typing import Optional
from loguru import logger from loguru import logger
from pydantic import BaseModel from pydantic import BaseModel
@@ -68,7 +67,7 @@ class VADAnalyzer(ABC):
Subclasses must implement the core voice confidence calculation. Subclasses must implement the core voice confidence calculation.
""" """
def __init__(self, *, sample_rate: Optional[int] = None, params: Optional[VADParams] = None): def __init__(self, *, sample_rate: int | None = None, params: VADParams | None = None):
"""Initialize the VAD analyzer. """Initialize the VAD analyzer.
Args: Args:

View File

@@ -12,7 +12,6 @@ and emit events when speech starts, stops, or is actively detected.
import asyncio import asyncio
import time import time
from typing import Optional, Type
from loguru import logger from loguru import logger
@@ -90,7 +89,7 @@ class VADController(BaseObject):
self._vad_analyzer = vad_analyzer self._vad_analyzer = vad_analyzer
self._vad_state: VADState = VADState.QUIET self._vad_state: VADState = VADState.QUIET
self._task_manager: Optional[BaseTaskManager] = None self._task_manager: BaseTaskManager | None = None
# Last time a on_speech_activity was triggered. # Last time a on_speech_activity was triggered.
self._speech_activity_time = 0 self._speech_activity_time = 0
@@ -102,7 +101,7 @@ class VADController(BaseObject):
# while in SPEAKING state (e.g. user mutes mic mid-speech). # while in SPEAKING state (e.g. user mutes mic mid-speech).
self._last_audio_time: float = 0.0 self._last_audio_time: float = 0.0
self._audio_idle_timeout = audio_idle_timeout self._audio_idle_timeout = audio_idle_timeout
self._audio_idle_task: Optional[asyncio.Task] = None self._audio_idle_task: asyncio.Task | None = None
self._register_event_handler("on_speech_started", sync=True) self._register_event_handler("on_speech_started", sync=True)
self._register_event_handler("on_speech_stopped", sync=True) self._register_event_handler("on_speech_stopped", sync=True)
@@ -234,7 +233,7 @@ class VADController(BaseObject):
""" """
await self._call_event_handler("on_push_frame", frame, direction) await self._call_event_handler("on_push_frame", frame, direction)
async def broadcast_frame(self, frame_cls: Type[Frame], **kwargs): async def broadcast_frame(self, frame_cls: type[Frame], **kwargs):
"""Request a frame to be broadcast upstream and downstream. """Request a frame to be broadcast upstream and downstream.
This emits an on_broadcast_frame event that must be handled by a processor This emits an on_broadcast_frame event that must be handled by a processor

View File

@@ -11,7 +11,6 @@ using LLM-based decision making and DTMF tone generation.
""" """
from enum import Enum from enum import Enum
from typing import List, Optional
from loguru import logger from loguru import logger
@@ -72,7 +71,7 @@ class IVRProcessor(FrameProcessor):
*, *,
classifier_prompt: str, classifier_prompt: str,
ivr_prompt: str, ivr_prompt: str,
ivr_vad_params: Optional[VADParams] = None, ivr_vad_params: VADParams | None = None,
): ):
"""Initialize the IVR processor. """Initialize the IVR processor.
@@ -88,7 +87,7 @@ class IVRProcessor(FrameProcessor):
self._classifier_prompt = classifier_prompt self._classifier_prompt = classifier_prompt
# Store saved context messages # Store saved context messages
self._saved_messages: List[dict] = [] self._saved_messages: list[dict] = []
# XML pattern aggregation # XML pattern aggregation
self._aggregator = PatternPairAggregator() self._aggregator = PatternPairAggregator()
@@ -98,7 +97,7 @@ class IVRProcessor(FrameProcessor):
self._register_event_handler("on_conversation_detected") self._register_event_handler("on_conversation_detected")
self._register_event_handler("on_ivr_status_changed") self._register_event_handler("on_ivr_status_changed")
def update_saved_messages(self, messages: List[dict]) -> None: def update_saved_messages(self, messages: list[dict]) -> None:
"""Update the saved context messages. """Update the saved context messages.
Sets the messages that are saved when switching between Sets the messages that are saved when switching between
@@ -109,7 +108,7 @@ class IVRProcessor(FrameProcessor):
""" """
self._saved_messages = messages self._saved_messages = messages
def _get_conversation_history(self) -> List[dict]: def _get_conversation_history(self) -> list[dict]:
"""Get saved context messages without the system message. """Get saved context messages without the system message.
Returns: Returns:
@@ -409,7 +408,7 @@ Remember: Respond with `<dtmf>NUMBER</dtmf>` (single or multiple for sequences),
*, *,
llm: LLMService, llm: LLMService,
ivr_prompt: str, ivr_prompt: str,
ivr_vad_params: Optional[VADParams] = None, ivr_vad_params: VADParams | None = None,
): ):
"""Initialize the IVR navigator. """Initialize the IVR navigator.

View File

@@ -16,7 +16,6 @@ Note:
""" """
import asyncio import asyncio
from typing import List, Optional
from loguru import logger from loguru import logger
@@ -71,7 +70,7 @@ class NotifierGate(FrameProcessor):
self._notifier = notifier self._notifier = notifier
self._task_name = task_name self._task_name = task_name
self._gate_opened = True self._gate_opened = True
self._gate_task: Optional[asyncio.Task] = None self._gate_task: asyncio.Task | None = None
async def setup(self, setup: FrameProcessorSetup): async def setup(self, setup: FrameProcessorSetup):
"""Set up the processor with required components. """Set up the processor with required components.
@@ -143,7 +142,7 @@ class ClassifierGate(NotifierGate):
super().__init__(gate_notifier, task_name="classifier_gate") super().__init__(gate_notifier, task_name="classifier_gate")
self._conversation_notifier = conversation_notifier self._conversation_notifier = conversation_notifier
self._conversation_detected = False self._conversation_detected = False
self._conversation_task: Optional[asyncio.Task] = None self._conversation_task: asyncio.Task | None = None
async def setup(self, setup: FrameProcessorSetup): async def setup(self, setup: FrameProcessorSetup):
"""Set up the processor with required components. """Set up the processor with required components.
@@ -267,7 +266,7 @@ class ClassificationProcessor(FrameProcessor):
# Voicemail timing state # Voicemail timing state
self._voicemail_detected = False self._voicemail_detected = False
self._voicemail_task: Optional[asyncio.Task] = None self._voicemail_task: asyncio.Task | None = None
self._voicemail_event = asyncio.Event() self._voicemail_event = asyncio.Event()
self._voicemail_event.set() self._voicemail_event.set()
@@ -390,7 +389,7 @@ class ClassificationProcessor(FrameProcessor):
self._voicemail_event.wait(), timeout=self._voicemail_response_delay self._voicemail_event.wait(), timeout=self._voicemail_response_delay
) )
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
except asyncio.TimeoutError: except TimeoutError:
await self._call_event_handler("on_voicemail_detected") await self._call_event_handler("on_voicemail_detected")
break break
@@ -423,10 +422,10 @@ class TTSGate(FrameProcessor):
super().__init__() super().__init__()
self._conversation_notifier = conversation_notifier self._conversation_notifier = conversation_notifier
self._voicemail_notifier = voicemail_notifier self._voicemail_notifier = voicemail_notifier
self._frame_buffer: List[tuple[Frame, FrameDirection]] = [] self._frame_buffer: list[tuple[Frame, FrameDirection]] = []
self._gating_active = True self._gating_active = True
self._conversation_task: Optional[asyncio.Task] = None self._conversation_task: asyncio.Task | None = None
self._voicemail_task: Optional[asyncio.Task] = None self._voicemail_task: asyncio.Task | None = None
async def setup(self, setup: FrameProcessorSetup): async def setup(self, setup: FrameProcessorSetup):
"""Set up the processor with required components. """Set up the processor with required components.
@@ -591,7 +590,7 @@ VOICEMAIL SYSTEM (respond "VOICEMAIL"):
*, *,
llm: LLMService, llm: LLMService,
voicemail_response_delay: float = 2.0, voicemail_response_delay: float = 2.0,
custom_system_prompt: Optional[str] = None, custom_system_prompt: str | None = None,
): ):
"""Initialize the voicemail detector with classification and buffering components. """Initialize the voicemail detector with classification and buffering components.

View File

@@ -11,20 +11,16 @@ including data frames, system frames, and control frames for audio, video, text,
and LLM processing. and LLM processing.
""" """
from __future__ import annotations
import time import time
from collections.abc import Awaitable, Callable, Mapping, Sequence
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
Awaitable,
Callable,
Dict,
List,
Literal, Literal,
Mapping,
Optional, Optional,
Sequence,
Tuple,
) )
from pipecat.adapters.schemas.tools_schema import ToolsSchema from pipecat.adapters.schemas.tools_schema import ToolsSchema
@@ -45,7 +41,7 @@ if TYPE_CHECKING:
from pipecat.utils.tracing.tracing_context import TracingContext from pipecat.utils.tracing.tracing_context import TracingContext
def format_pts(pts: Optional[int]): def format_pts(pts: int | None):
"""Format presentation timestamp (PTS) in nanoseconds to a human-readable string. """Format presentation timestamp (PTS) in nanoseconds to a human-readable string.
Converts a PTS value in nanoseconds to a string representation. Converts a PTS value in nanoseconds to a string representation.
@@ -77,20 +73,20 @@ class Frame:
id: int = field(init=False) id: int = field(init=False)
name: str = field(init=False) name: str = field(init=False)
pts: Optional[int] = field(init=False) pts: int | None = field(init=False)
broadcast_sibling_id: Optional[int] = field(init=False) broadcast_sibling_id: int | None = field(init=False)
metadata: Dict[str, Any] = field(init=False) metadata: dict[str, Any] = field(init=False)
transport_source: Optional[str] = field(init=False) transport_source: str | None = field(init=False)
transport_destination: Optional[str] = field(init=False) transport_destination: str | None = field(init=False)
def __post_init__(self): def __post_init__(self):
self.id: int = obj_id() self.id: int = obj_id()
self.name: str = f"{self.__class__.__name__}#{obj_count(self)}" self.name: str = f"{self.__class__.__name__}#{obj_count(self)}"
self.pts: Optional[int] = None self.pts: int | None = None
self.broadcast_sibling_id: Optional[int] = None self.broadcast_sibling_id: int | None = None
self.metadata: Dict[str, Any] = {} self.metadata: dict[str, Any] = {}
self.transport_source: Optional[str] = None self.transport_source: str | None = None
self.transport_destination: Optional[str] = None self.transport_destination: str | None = None
def __str__(self): def __str__(self):
return self.name return self.name
@@ -183,8 +179,8 @@ class ImageRawFrame:
""" """
image: bytes image: bytes
size: Tuple[int, int] size: tuple[int, int]
format: Optional[str] format: str | None
# #
@@ -242,7 +238,7 @@ class TTSAudioRawFrame(OutputAudioRawFrame):
context_id: Unique identifier for the TTS context that generated this audio. context_id: Unique identifier for the TTS context that generated this audio.
""" """
context_id: Optional[str] = None context_id: str | None = None
@dataclass @dataclass
@@ -268,7 +264,7 @@ class URLImageRawFrame(OutputImageRawFrame):
url: URL where the image can be downloaded from. url: URL where the image can be downloaded from.
""" """
url: Optional[str] = None url: str | None = None
def __str__(self): def __str__(self):
pts = format_pts(self.pts) pts = format_pts(self.pts)
@@ -287,7 +283,7 @@ class SpriteFrame(DataFrame):
images: List of image frames that make up the sprite animation. images: List of image frames that make up the sprite animation.
""" """
images: List[OutputImageRawFrame] images: list[OutputImageRawFrame]
def __str__(self): def __str__(self):
pts = format_pts(self.pts) pts = format_pts(self.pts)
@@ -312,7 +308,7 @@ class TextFrame(DataFrame):
""" """
text: str text: str
skip_tts: Optional[bool] = field(init=False) skip_tts: bool | None = field(init=False)
# Whether any necessary inter-frame (leading/trailing) spaces are already # Whether any necessary inter-frame (leading/trailing) spaces are already
# included in the text. # included in the text.
# NOTE: Ideally this would be available at init time with a default value, # NOTE: Ideally this would be available at init time with a default value,
@@ -357,7 +353,7 @@ class AggregatedTextFrame(TextFrame):
""" """
aggregated_by: AggregationType | str aggregated_by: AggregationType | str
context_id: Optional[str] = None context_id: str | None = None
@dataclass @dataclass
@@ -375,7 +371,7 @@ class TTSTextFrame(AggregatedTextFrame):
context_id: Unique identifier for the TTS context that generated this text. context_id: Unique identifier for the TTS context that generated this text.
""" """
context_id: Optional[str] = None context_id: str | None = None
@dataclass @dataclass
@@ -396,8 +392,8 @@ class TranscriptionFrame(TextFrame):
user_id: str user_id: str
timestamp: str timestamp: str
language: Optional[Language] = None language: Language | None = None
result: Optional[Any] = None result: Any | None = None
finalized: bool = False finalized: bool = False
def __str__(self): def __str__(self):
@@ -422,8 +418,8 @@ class InterimTranscriptionFrame(TextFrame):
text: str text: str
user_id: str user_id: str
timestamp: str timestamp: str
language: Optional[Language] = None language: Language | None = None
result: Optional[Any] = None result: Any | None = None
def __str__(self): def __str__(self):
return f"{self.name}(user: {self.user_id}, text: [{self.text}], language: {self.language}, timestamp: {self.timestamp})" return f"{self.name}(user: {self.user_id}, text: [{self.text}], language: {self.language}, timestamp: {self.timestamp})"
@@ -444,7 +440,7 @@ class TranslationFrame(TextFrame):
user_id: str user_id: str
timestamp: str timestamp: str
language: Optional[Language] = None language: Language | None = None
def __str__(self): def __str__(self):
return f"{self.name}(user: {self.user_id}, text: [{self.text}], language: {self.language}, timestamp: {self.timestamp})" return f"{self.name}(user: {self.user_id}, text: [{self.text}], language: {self.language}, timestamp: {self.timestamp})"
@@ -472,7 +468,7 @@ class LLMContextFrame(Frame):
context: The LLM context containing messages, tools, and configuration. context: The LLM context containing messages, tools, and configuration.
""" """
context: "LLMContext" context: LLMContext
@dataclass @dataclass
@@ -489,7 +485,7 @@ class LLMThoughtStartFrame(ControlFrame):
""" """
append_to_context: bool = False append_to_context: bool = False
llm: Optional[str] = None llm: str | None = None
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
@@ -567,8 +563,8 @@ class LLMMessagesAppendFrame(DataFrame):
run_llm: Whether the context update should be sent to the LLM. run_llm: Whether the context update should be sent to the LLM.
""" """
messages: List[dict] messages: list[dict]
run_llm: Optional[bool] = None run_llm: bool | None = None
@dataclass @dataclass
@@ -583,8 +579,8 @@ class LLMMessagesUpdateFrame(DataFrame):
run_llm: Whether the context update should be sent to the LLM. run_llm: Whether the context update should be sent to the LLM.
""" """
messages: List[dict] messages: list[dict]
run_llm: Optional[bool] = None run_llm: bool | None = None
@dataclass @dataclass
@@ -600,8 +596,8 @@ class LLMMessagesTransformFrame(DataFrame):
run_llm: Whether the context update should be sent to the LLM. run_llm: Whether the context update should be sent to the LLM.
""" """
transform: Callable[[List["LLMContextMessage"]], List["LLMContextMessage"]] transform: Callable[[list[LLMContextMessage]], list[LLMContextMessage]]
run_llm: Optional[bool] = None run_llm: bool | None = None
@dataclass @dataclass
@@ -616,7 +612,7 @@ class LLMSetToolsFrame(DataFrame):
tools: List of tool/function definitions for the LLM. tools: List of tool/function definitions for the LLM.
""" """
tools: List[dict] | ToolsSchema | "NotGiven" tools: list[dict] | ToolsSchema | NotGiven
@dataclass @dataclass
@@ -668,8 +664,8 @@ class FunctionCallResultProperties:
Only meaningful for async function calls (``cancel_on_interruption=False``). Only meaningful for async function calls (``cancel_on_interruption=False``).
""" """
run_llm: Optional[bool] = None run_llm: bool | None = None
on_context_updated: Optional[Callable[[], Awaitable[None]]] = None on_context_updated: Callable[[], Awaitable[None]] | None = None
is_final: bool = True is_final: bool = True
@@ -694,8 +690,8 @@ class FunctionCallResultFrame(DataFrame, UninterruptibleFrame):
tool_call_id: str tool_call_id: str
arguments: Any arguments: Any
result: Any result: Any
run_llm: Optional[bool] = None run_llm: bool | None = None
properties: Optional[FunctionCallResultProperties] = None properties: FunctionCallResultProperties | None = None
@dataclass @dataclass
@@ -711,7 +707,7 @@ class TTSSpeakFrame(DataFrame):
""" """
text: str text: str
append_to_context: Optional[bool] = None append_to_context: bool | None = None
@dataclass @dataclass
@@ -752,8 +748,8 @@ class OutputDTMFFrame(DTMFFrame, DataFrame):
:meth:`from_string` to build this from a string like ``"123#"``. :meth:`from_string` to build this from a string like ``"123#"``.
""" """
button: Optional[KeypadEntry] = None button: KeypadEntry | None = None
buttons: Optional[List[KeypadEntry]] = None buttons: list[KeypadEntry] | None = None
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
@@ -766,7 +762,7 @@ class OutputDTMFFrame(DTMFFrame, DataFrame):
return f"{self.name}(buttons: {self.to_string()})" return f"{self.name}(buttons: {self.to_string()})"
@classmethod @classmethod
def from_string(cls, buttons: str, **kwargs) -> "OutputDTMFFrame": def from_string(cls, buttons: str, **kwargs) -> OutputDTMFFrame:
"""Build an ``OutputDTMFFrame`` from a string of DTMF characters. """Build an ``OutputDTMFFrame`` from a string of DTMF characters.
Args: Args:
@@ -820,7 +816,7 @@ class StartFrame(SystemFrame):
enable_tracing: bool = False enable_tracing: bool = False
enable_usage_metrics: bool = False enable_usage_metrics: bool = False
report_only_initial_ttfb: bool = False report_only_initial_ttfb: bool = False
tracing_context: Optional["TracingContext"] = None tracing_context: TracingContext | None = None
@dataclass @dataclass
@@ -834,7 +830,7 @@ class CancelFrame(SystemFrame):
reason: Optional reason for pushing a cancel frame. reason: Optional reason for pushing a cancel frame.
""" """
reason: Optional[Any] = None reason: Any | None = None
def __str__(self): def __str__(self):
return f"{self.name}(reason: {self.reason})" return f"{self.name}(reason: {self.reason})"
@@ -857,8 +853,8 @@ class ErrorFrame(SystemFrame):
error: str error: str
fatal: bool = False fatal: bool = False
processor: Optional["FrameProcessor"] = None processor: FrameProcessor | None = None
exception: Optional[Exception] = None exception: Exception | None = None
def __str__(self): def __str__(self):
return f"{self.name}(error: {self.error}, fatal: {self.fatal})" return f"{self.name}(error: {self.error}, fatal: {self.fatal})"
@@ -891,7 +887,7 @@ class FrameProcessorPauseUrgentFrame(SystemFrame):
processor: The frame processor to pause. processor: The frame processor to pause.
""" """
processor: "FrameProcessor" processor: FrameProcessor
@dataclass @dataclass
@@ -906,7 +902,7 @@ class FrameProcessorResumeUrgentFrame(SystemFrame):
processor: The frame processor to resume. processor: The frame processor to resume.
""" """
processor: "FrameProcessor" processor: FrameProcessor
@dataclass @dataclass
@@ -1050,7 +1046,7 @@ class MetricsFrame(SystemFrame):
data: List of metrics data collected by the processor. data: List of metrics data collected by the processor.
""" """
data: List[MetricsData] data: list[MetricsData]
@dataclass @dataclass
@@ -1156,12 +1152,12 @@ class UserImageRequestFrame(SystemFrame):
""" """
user_id: str user_id: str
text: Optional[str] = None text: str | None = None
append_to_context: Optional[bool] = None append_to_context: bool | None = None
video_source: Optional[str] = None video_source: str | None = None
function_name: Optional[str] = None function_name: str | None = None
tool_call_id: Optional[str] = None tool_call_id: str | None = None
result_callback: Optional[Any] = None result_callback: Any | None = None
def __str__(self): def __str__(self):
return f"{self.name}(user: {self.user_id}, text: {self.text}, append_to_context: {self.append_to_context}, {self.video_source})" return f"{self.name}(user: {self.user_id}, text: {self.text}, append_to_context: {self.append_to_context}, {self.video_source})"
@@ -1244,9 +1240,9 @@ class UserImageRawFrame(InputImageRawFrame):
""" """
user_id: str = "" user_id: str = ""
text: Optional[str] = None text: str | None = None
append_to_context: Optional[bool] = None append_to_context: bool | None = None
request: Optional[UserImageRequestFrame] = None request: UserImageRequestFrame | None = None
def __str__(self): def __str__(self):
pts = format_pts(self.pts) pts = format_pts(self.pts)
@@ -1266,8 +1262,8 @@ class AssistantImageRawFrame(OutputImageRawFrame):
original_mime_type: The MIME type of the original image data. original_mime_type: The MIME type of the original image data.
""" """
original_data: Optional[bytes] = None original_data: bytes | None = None
original_mime_type: Optional[str] = None original_mime_type: str | None = None
@dataclass @dataclass
@@ -1296,8 +1292,8 @@ class OutputDTMFUrgentFrame(DTMFFrame, SystemFrame):
:meth:`from_string` to build this from a string like ``"123#"``. :meth:`from_string` to build this from a string like ``"123#"``.
""" """
button: Optional[KeypadEntry] = None button: KeypadEntry | None = None
buttons: Optional[List[KeypadEntry]] = None buttons: list[KeypadEntry] | None = None
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
@@ -1310,7 +1306,7 @@ class OutputDTMFUrgentFrame(DTMFFrame, SystemFrame):
return f"{self.name}(buttons: {self.to_string()})" return f"{self.name}(buttons: {self.to_string()})"
@classmethod @classmethod
def from_string(cls, buttons: str, **kwargs) -> "OutputDTMFUrgentFrame": def from_string(cls, buttons: str, **kwargs) -> OutputDTMFUrgentFrame:
"""Build an ``OutputDTMFUrgentFrame`` from a string of DTMF characters. """Build an ``OutputDTMFUrgentFrame`` from a string of DTMF characters.
Args: Args:
@@ -1349,8 +1345,8 @@ class SpeechControlParamsFrame(SystemFrame):
turn_params: Current turn-taking analysis parameters. turn_params: Current turn-taking analysis parameters.
""" """
vad_params: Optional[VADParams] = None vad_params: VADParams | None = None
turn_params: Optional[BaseTurnParams] = None turn_params: BaseTurnParams | None = None
@dataclass @dataclass
@@ -1396,7 +1392,7 @@ class ServiceSwitcherRequestMetadataFrame(ControlFrame):
service: The target service that should re-emit its metadata. service: The target service that should re-emit its metadata.
""" """
service: "FrameProcessor" service: FrameProcessor
# #
@@ -1444,7 +1440,7 @@ class EndTaskFrame(TaskFrame, UninterruptibleFrame):
reason: Optional reason for pushing an end frame. reason: Optional reason for pushing an end frame.
""" """
reason: Optional[Any] = None reason: Any | None = None
def __str__(self): def __str__(self):
return f"{self.name}(reason: {self.reason})" return f"{self.name}(reason: {self.reason})"
@@ -1475,7 +1471,7 @@ class CancelTaskFrame(TaskSystemFrame):
reason: Optional reason for pushing a cancel frame. reason: Optional reason for pushing a cancel frame.
""" """
reason: Optional[Any] = None reason: Any | None = None
def __str__(self): def __str__(self):
return f"{self.name}(reason: {self.reason})" return f"{self.name}(reason: {self.reason})"
@@ -1516,7 +1512,7 @@ class EndFrame(ControlFrame, UninterruptibleFrame):
reason: Optional reason for pushing an end frame. reason: Optional reason for pushing an end frame.
""" """
reason: Optional[Any] = None reason: Any | None = None
def __str__(self): def __str__(self):
return f"{self.name}(reason: {self.reason})" return f"{self.name}(reason: {self.reason})"
@@ -1598,7 +1594,7 @@ class FrameProcessorPauseFrame(ControlFrame):
processor: The frame processor to pause. processor: The frame processor to pause.
""" """
processor: "FrameProcessor" processor: FrameProcessor
@dataclass @dataclass
@@ -1613,7 +1609,7 @@ class FrameProcessorResumeFrame(ControlFrame):
processor: The frame processor to resume. processor: The frame processor to resume.
""" """
processor: "FrameProcessor" processor: FrameProcessor
@dataclass @dataclass
@@ -1624,7 +1620,7 @@ class LLMFullResponseStartFrame(ControlFrame):
more TextFrames and a final LLMFullResponseEndFrame. more TextFrames and a final LLMFullResponseEndFrame.
""" """
skip_tts: Optional[bool] = field(init=False) skip_tts: bool | None = field(init=False)
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
@@ -1635,7 +1631,7 @@ class LLMFullResponseStartFrame(ControlFrame):
class LLMFullResponseEndFrame(ControlFrame): class LLMFullResponseEndFrame(ControlFrame):
"""Frame indicating the end of an LLM response.""" """Frame indicating the end of an LLM response."""
skip_tts: Optional[bool] = field(init=False) skip_tts: bool | None = field(init=False)
def __post_init__(self): def __post_init__(self):
super().__post_init__() super().__post_init__()
@@ -1665,7 +1661,7 @@ class LLMSummarizeContextFrame(ControlFrame):
is used. is used.
""" """
config: Optional["LLMContextSummaryConfig"] = None config: LLMContextSummaryConfig | None = None
@dataclass @dataclass
@@ -1692,11 +1688,11 @@ class LLMContextSummaryRequestFrame(ControlFrame):
""" """
request_id: str request_id: str
context: "LLMContext" context: LLMContext
min_messages_to_keep: int min_messages_to_keep: int
target_context_tokens: int target_context_tokens: int
summarization_prompt: str summarization_prompt: str
summarization_timeout: Optional[float] = None summarization_timeout: float | None = None
@dataclass @dataclass
@@ -1718,7 +1714,7 @@ class LLMContextSummaryResultFrame(ControlFrame, UninterruptibleFrame):
request_id: str request_id: str
summary: str summary: str
last_summarized_index: int last_summarized_index: int
error: Optional[str] = None error: str | None = None
@dataclass @dataclass
@@ -1745,7 +1741,7 @@ class FunctionCallInProgressFrame(ControlFrame, UninterruptibleFrame):
tool_call_id: str tool_call_id: str
arguments: Any arguments: Any
cancel_on_interruption: bool = False cancel_on_interruption: bool = False
group_id: Optional[str] = None group_id: str | None = None
@dataclass @dataclass
@@ -1781,7 +1777,7 @@ class TTSStartedFrame(ControlFrame):
context_id: Unique identifier for this TTS context. context_id: Unique identifier for this TTS context.
""" """
context_id: Optional[str] = None context_id: str | None = None
@dataclass @dataclass
@@ -1792,7 +1788,7 @@ class TTSStoppedFrame(ControlFrame):
context_id: Unique identifier for this TTS context. context_id: Unique identifier for this TTS context.
""" """
context_id: Optional[str] = None context_id: str | None = None
@dataclass @dataclass
@@ -1817,8 +1813,8 @@ class ServiceUpdateSettingsFrame(ControlFrame, UninterruptibleFrame):
""" """
settings: Mapping[str, Any] = field(default_factory=dict) settings: Mapping[str, Any] = field(default_factory=dict)
delta: Optional["ServiceSettings"] = None delta: ServiceSettings | None = None
service: Optional["FrameProcessor"] = None service: FrameProcessor | None = None
@dataclass @dataclass
@@ -1942,4 +1938,4 @@ class ManuallySwitchServiceFrame(ServiceSwitcherFrame):
Handled by ServiceSwitcherStrategyManual to switch the active service. Handled by ServiceSwitcherStrategyManual to switch the active service.
""" """
service: "FrameProcessor" service: FrameProcessor

View File

@@ -11,8 +11,6 @@ collected throughout the pipeline, including timing, token usage, and
processing statistics. processing statistics.
""" """
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
@@ -25,7 +23,7 @@ class MetricsData(BaseModel):
""" """
processor: str processor: str
model: Optional[str] = None model: str | None = None
class TTFBMetricsData(MetricsData): class TTFBMetricsData(MetricsData):
@@ -62,9 +60,9 @@ class LLMTokenUsage(BaseModel):
prompt_tokens: int prompt_tokens: int
completion_tokens: int completion_tokens: int
total_tokens: int total_tokens: int
cache_read_input_tokens: Optional[int] = None cache_read_input_tokens: int | None = None
cache_creation_input_tokens: Optional[int] = None cache_creation_input_tokens: int | None = None
reasoning_tokens: Optional[int] = None reasoning_tokens: int | None = None
class LLMUsageMetricsData(MetricsData): class LLMUsageMetricsData(MetricsData):

View File

@@ -12,8 +12,7 @@ for logging, debugging, analytics, and monitoring pipeline behavior.
""" """
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing_extensions import TYPE_CHECKING
from pipecat.frames.frames import Frame from pipecat.frames.frames import Frame
from pipecat.utils.base_object import BaseObject from pipecat.utils.base_object import BaseObject

View File

@@ -13,7 +13,6 @@ understanding frame flow between processors.
from dataclasses import fields, is_dataclass from dataclasses import fields, is_dataclass
from enum import Enum, auto from enum import Enum, auto
from typing import Dict, Optional, Set, Tuple, Type, Union
from loguru import logger from loguru import logger
@@ -75,10 +74,10 @@ class DebugLogObserver(BaseObserver):
def __init__( def __init__(
self, self,
frame_types: Optional[ frame_types: tuple[type[Frame], ...]
Union[Tuple[Type[Frame], ...], Dict[Type[Frame], Optional[Tuple[Type, FrameEndpoint]]]] | dict[type[Frame], tuple[type, FrameEndpoint] | None]
] = None, | None = None,
exclude_fields: Optional[Set[str]] = None, exclude_fields: set[str] | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the debug log observer. """Initialize the debug log observer.

View File

@@ -11,8 +11,6 @@ allowing developers to monitor performance metrics, token usage, and other
statistics in real-time. statistics in real-time.
""" """
from typing import Optional, Set, Type
from loguru import logger from loguru import logger
from pipecat.frames.frames import MetricsFrame from pipecat.frames.frames import MetricsFrame
@@ -60,7 +58,7 @@ class MetricsLogObserver(BaseObserver):
def __init__( def __init__(
self, self,
include_metrics: Optional[Set[Type[MetricsData]]] = None, include_metrics: set[type[MetricsData]] | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the metrics log observer. """Initialize the metrics log observer.

View File

@@ -36,7 +36,6 @@ Example::
import time import time
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple, Type
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -84,7 +83,7 @@ class StartupTimingReport(BaseModel):
start_time: float start_time: float
total_duration_secs: float total_duration_secs: float
processor_timings: List[ProcessorStartupTiming] = Field(default_factory=list) processor_timings: list[ProcessorStartupTiming] = Field(default_factory=list)
class TransportTimingReport(BaseModel): class TransportTimingReport(BaseModel):
@@ -98,8 +97,8 @@ class TransportTimingReport(BaseModel):
""" """
start_time: float start_time: float
bot_connected_secs: Optional[float] = None bot_connected_secs: float | None = None
client_connected_secs: Optional[float] = None client_connected_secs: float | None = None
class StartupTimingObserver(BaseObserver): class StartupTimingObserver(BaseObserver):
@@ -157,7 +156,7 @@ class StartupTimingObserver(BaseObserver):
def __init__( def __init__(
self, self,
*, *,
processor_types: Optional[Tuple[Type[FrameProcessor], ...]] = None, processor_types: tuple[type[FrameProcessor], ...] | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the startup timing observer. """Initialize the startup timing observer.
@@ -171,13 +170,13 @@ class StartupTimingObserver(BaseObserver):
self._processor_types = processor_types self._processor_types = processor_types
# Map processor ID -> arrival info. # Map processor ID -> arrival info.
self._arrivals: Dict[int, _ArrivalInfo] = {} self._arrivals: dict[int, _ArrivalInfo] = {}
# Collected timings in pipeline order. # Collected timings in pipeline order.
self._timings: List[ProcessorStartupTiming] = [] self._timings: list[ProcessorStartupTiming] = []
# Lock onto the first StartFrame we see (by frame ID). # Lock onto the first StartFrame we see (by frame ID).
self._start_frame_id: Optional[str] = None self._start_frame_id: str | None = None
# Whether we've already emitted the startup timing report. # Whether we've already emitted the startup timing report.
self._startup_timing_reported = False self._startup_timing_reported = False
@@ -186,13 +185,13 @@ class StartupTimingObserver(BaseObserver):
self._transport_timing_reported = False self._transport_timing_reported = False
# Timestamp (ns) when we first see a StartFrame arrive at a processor. # Timestamp (ns) when we first see a StartFrame arrive at a processor.
self._start_frame_arrival_ns: Optional[int] = None self._start_frame_arrival_ns: int | None = None
# Bot connected timing (stored for inclusion in the transport report). # Bot connected timing (stored for inclusion in the transport report).
self._bot_connected_secs: Optional[float] = None self._bot_connected_secs: float | None = None
# Wall clock time when the StartFrame was first seen. # Wall clock time when the StartFrame was first seen.
self._start_wall_clock: Optional[float] = None self._start_wall_clock: float | None = None
self._register_event_handler("on_startup_timing_report") self._register_event_handler("on_startup_timing_report")
self._register_event_handler("on_transport_timing_report") self._register_event_handler("on_transport_timing_report")

View File

@@ -14,7 +14,6 @@ is measured. Optionally collects per-service latency breakdown metrics
import time import time
from collections import deque from collections import deque
from typing import Dict, List, Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -48,7 +47,7 @@ class TTFBBreakdownMetrics(BaseModel):
""" """
processor: str processor: str
model: Optional[str] = None model: str | None = None
start_time: float start_time: float
duration_secs: float duration_secs: float
@@ -105,13 +104,13 @@ class LatencyBreakdown(BaseModel):
this cycle. Empty if no function calls occurred. this cycle. Empty if no function calls occurred.
""" """
ttfb: List[TTFBBreakdownMetrics] = Field(default_factory=list) ttfb: list[TTFBBreakdownMetrics] = Field(default_factory=list)
text_aggregation: Optional[TextAggregationBreakdownMetrics] = None text_aggregation: TextAggregationBreakdownMetrics | None = None
user_turn_start_time: Optional[float] = None user_turn_start_time: float | None = None
user_turn_secs: Optional[float] = None user_turn_secs: float | None = None
function_calls: List[FunctionCallMetrics] = Field(default_factory=list) function_calls: list[FunctionCallMetrics] = Field(default_factory=list)
def chronological_events(self) -> List[str]: def chronological_events(self) -> list[str]:
"""Return human-readable event labels sorted by start time. """Return human-readable event labels sorted by start time.
Collects all sub-metrics into a flat list, sorts by ``start_time``, Collects all sub-metrics into a flat list, sorts by ``start_time``,
@@ -120,7 +119,7 @@ class LatencyBreakdown(BaseModel):
Returns: Returns:
List of formatted strings, one per event, in chronological order. List of formatted strings, one per event, in chronological order.
""" """
events: List[tuple] = [] events: list[tuple] = []
if self.user_turn_start_time is not None and self.user_turn_secs is not None: if self.user_turn_start_time is not None and self.user_turn_secs is not None:
events.append((self.user_turn_start_time, f"User turn: {self.user_turn_secs:.3f}s")) events.append((self.user_turn_start_time, f"User turn: {self.user_turn_secs:.3f}s"))
@@ -181,12 +180,12 @@ class UserBotLatencyObserver(BaseObserver):
**kwargs: Additional arguments passed to parent class. **kwargs: Additional arguments passed to parent class.
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
self._user_stopped_time: Optional[float] = None self._user_stopped_time: float | None = None
self._user_turn_start_time: Optional[float] = None self._user_turn_start_time: float | None = None
self._user_turn: Optional[float] = None self._user_turn: float | None = None
# First bot speech tracking # First bot speech tracking
self._client_connected_time: Optional[float] = None self._client_connected_time: float | None = None
self._first_bot_speech_measured: bool = False self._first_bot_speech_measured: bool = False
# Frame deduplication (bounded deque + set pattern) # Frame deduplication (bounded deque + set pattern)
@@ -194,10 +193,10 @@ class UserBotLatencyObserver(BaseObserver):
self._frame_history: deque = deque(maxlen=max_frames) self._frame_history: deque = deque(maxlen=max_frames)
# Per-cycle metric accumulators # Per-cycle metric accumulators
self._ttfb: List[TTFBBreakdownMetrics] = [] self._ttfb: list[TTFBBreakdownMetrics] = []
self._text_aggregation: Optional[TextAggregationBreakdownMetrics] = None self._text_aggregation: TextAggregationBreakdownMetrics | None = None
self._function_call_starts: Dict[str, tuple[str, float]] = {} self._function_call_starts: dict[str, tuple[str, float]] = {}
self._function_call_metrics: List[FunctionCallMetrics] = [] self._function_call_metrics: list[FunctionCallMetrics] = []
self._register_event_handler("on_latency_measured") self._register_event_handler("on_latency_measured")
self._register_event_handler("on_latency_breakdown") self._register_event_handler("on_latency_breakdown")

View File

@@ -12,8 +12,8 @@ tasks that manage the lifecycle and execution of frame processing pipelines.
import asyncio import asyncio
from abc import abstractmethod from abc import abstractmethod
from collections.abc import AsyncIterable, Iterable
from dataclasses import dataclass from dataclasses import dataclass
from typing import AsyncIterable, Iterable
from pipecat.frames.frames import Frame from pipecat.frames.frames import Frame
from pipecat.utils.base_object import BaseObject from pipecat.utils.base_object import BaseObject

View File

@@ -6,7 +6,7 @@
"""LLM switcher for switching between different LLMs at runtime, with different switching strategies.""" """LLM switcher for switching between different LLMs at runtime, with different switching strategies."""
from typing import Any, List, Optional, Type from typing import Any
from pipecat.adapters.schemas.direct_function import DirectFunction from pipecat.adapters.schemas.direct_function import DirectFunction
from pipecat.pipeline.service_switcher import ( from pipecat.pipeline.service_switcher import (
@@ -28,8 +28,8 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
def __init__( def __init__(
self, self,
llms: List[LLMService], llms: list[LLMService],
strategy_type: Type[StrategyType] = ServiceSwitcherStrategyManual, strategy_type: type[StrategyType] = ServiceSwitcherStrategyManual,
): ):
"""Initialize the service switcher with a list of LLMs and a switching strategy. """Initialize the service switcher with a list of LLMs and a switching strategy.
@@ -41,7 +41,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
super().__init__(llms, strategy_type) super().__init__(llms, strategy_type)
@property @property
def llms(self) -> List[LLMService]: def llms(self) -> list[LLMService]:
"""Get the list of LLMs managed by this switcher. """Get the list of LLMs managed by this switcher.
Returns: Returns:
@@ -58,7 +58,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
""" """
return self.strategy.active_service return self.strategy.active_service
async def run_inference(self, context: LLMContext, **kwargs) -> Optional[str]: async def run_inference(self, context: LLMContext, **kwargs) -> str | None:
"""Run a one-shot, out-of-band (i.e. out-of-pipeline) inference with the given LLM context, using the currently active LLM. """Run a one-shot, out-of-band (i.e. out-of-pipeline) inference with the given LLM context, using the currently active LLM.
Args: Args:
@@ -75,11 +75,11 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
def register_function( def register_function(
self, self,
function_name: Optional[str], function_name: str | None,
handler: Any, handler: Any,
*, *,
cancel_on_interruption: bool = True, cancel_on_interruption: bool = True,
timeout_secs: Optional[float] = None, timeout_secs: float | None = None,
): ):
"""Register a function handler for LLM function calls, on all LLMs, active or not. """Register a function handler for LLM function calls, on all LLMs, active or not.
@@ -105,7 +105,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
handler: DirectFunction, handler: DirectFunction,
*, *,
cancel_on_interruption: bool = True, cancel_on_interruption: bool = True,
timeout_secs: Optional[float] = None, timeout_secs: float | None = None,
): ):
"""Register a direct function handler for LLM function calls, on all LLMs, active or not. """Register a direct function handler for LLM function calls, on all LLMs, active or not.

View File

@@ -12,7 +12,6 @@ handling of pipeline lifecycle events.
""" """
from itertools import chain from itertools import chain
from typing import Dict, List
from loguru import logger from loguru import logger
@@ -51,7 +50,7 @@ class ParallelPipeline(BasePipeline):
self._pipelines = [] self._pipelines = []
self._seen_ids = set() self._seen_ids = set()
self._frame_counter: Dict[int, int] = {} self._frame_counter: dict[int, int] = {}
self._synchronizing: bool = False self._synchronizing: bool = False
self._buffered_frames: list[tuple[Frame, FrameDirection]] = [] self._buffered_frames: list[tuple[Frame, FrameDirection]] = []
@@ -93,7 +92,7 @@ class ParallelPipeline(BasePipeline):
return self._pipelines return self._pipelines
@property @property
def entry_processors(self) -> List["FrameProcessor"]: def entry_processors(self) -> list["FrameProcessor"]:
"""Return the list of entry processors for this processor. """Return the list of entry processors for this processor.
Entry processors are the first processors in a compound processor Entry processors are the first processors in a compound processor
@@ -106,7 +105,7 @@ class ParallelPipeline(BasePipeline):
""" """
return self._pipelines return self._pipelines
def processors_with_metrics(self) -> List[FrameProcessor]: def processors_with_metrics(self) -> list[FrameProcessor]:
"""Collect processors that can generate metrics from all parallel branches. """Collect processors that can generate metrics from all parallel branches.
Returns: Returns:

View File

@@ -11,7 +11,7 @@ in sequence and manages frame flow between them, along with helper classes
for pipeline source and sink operations. for pipeline source and sink operations.
""" """
from typing import Callable, Coroutine, List, Optional from collections.abc import Callable, Coroutine
from pipecat.frames.frames import Frame from pipecat.frames.frames import Frame
from pipecat.pipeline.base_pipeline import BasePipeline from pipecat.pipeline.base_pipeline import BasePipeline
@@ -98,10 +98,10 @@ class Pipeline(BasePipeline):
def __init__( def __init__(
self, self,
processors: List[FrameProcessor], processors: list[FrameProcessor],
*, *,
source: Optional[FrameProcessor] = None, source: FrameProcessor | None = None,
sink: Optional[FrameProcessor] = None, sink: FrameProcessor | None = None,
): ):
"""Initialize the pipeline with a list of processors. """Initialize the pipeline with a list of processors.
@@ -116,7 +116,7 @@ class Pipeline(BasePipeline):
# downstream outside of the pipeline. # downstream outside of the pipeline.
self._source = source or PipelineSource(self.push_frame, name=f"{self}::Source") self._source = source or PipelineSource(self.push_frame, name=f"{self}::Source")
self._sink = sink or PipelineSink(self.push_frame, name=f"{self}::Sink") self._sink = sink or PipelineSink(self.push_frame, name=f"{self}::Sink")
self._processors: List[FrameProcessor] = [self._source] + processors + [self._sink] self._processors: list[FrameProcessor] = [self._source] + processors + [self._sink]
self._link_processors() self._link_processors()
@@ -137,7 +137,7 @@ class Pipeline(BasePipeline):
return self._processors return self._processors
@property @property
def entry_processors(self) -> List["FrameProcessor"]: def entry_processors(self) -> list["FrameProcessor"]:
"""Return the list of entry processors for this processor. """Return the list of entry processors for this processor.
Entry processors are the first processors in a compound processor Entry processors are the first processors in a compound processor

View File

@@ -14,7 +14,6 @@ management.
import asyncio import asyncio
import gc import gc
import signal import signal
from typing import Optional
from loguru import logger from loguru import logger
@@ -34,11 +33,11 @@ class PipelineRunner(BaseObject):
def __init__( def __init__(
self, self,
*, *,
name: Optional[str] = None, name: str | None = None,
handle_sigint: bool = True, handle_sigint: bool = True,
handle_sigterm: bool = False, handle_sigterm: bool = False,
force_gc: bool = False, force_gc: bool = False,
loop: Optional[asyncio.AbstractEventLoop] = None, loop: asyncio.AbstractEventLoop | None = None,
): ):
"""Initialize the pipeline runner. """Initialize the pipeline runner.

View File

@@ -6,7 +6,7 @@
"""Service switcher for switching between different services at runtime, with different switching strategies.""" """Service switcher for switching between different services at runtime, with different switching strategies."""
from typing import Any, Generic, List, Optional, Type, TypeVar from typing import Any, Generic, TypeVar
from loguru import logger from loguru import logger
@@ -42,7 +42,7 @@ class ServiceSwitcherStrategy(BaseObject):
... ...
""" """
def __init__(self, services: List[FrameProcessor]): def __init__(self, services: list[FrameProcessor]):
"""Initialize the service switcher strategy with a list of services. """Initialize the service switcher strategy with a list of services.
Note: Note:
@@ -62,7 +62,7 @@ class ServiceSwitcherStrategy(BaseObject):
self._register_event_handler("on_service_switched") self._register_event_handler("on_service_switched")
@property @property
def services(self) -> List[FrameProcessor]: def services(self) -> list[FrameProcessor]:
"""Return the list of available services.""" """Return the list of available services."""
return self._services return self._services
@@ -73,7 +73,7 @@ class ServiceSwitcherStrategy(BaseObject):
async def handle_frame( async def handle_frame(
self, frame: ServiceSwitcherFrame, direction: FrameDirection self, frame: ServiceSwitcherFrame, direction: FrameDirection
) -> Optional[FrameProcessor]: ) -> FrameProcessor | None:
"""Handle a frame that controls service switching. """Handle a frame that controls service switching.
The base implementation returns ``None`` for all frames. Subclasses The base implementation returns ``None`` for all frames. Subclasses
@@ -88,7 +88,7 @@ class ServiceSwitcherStrategy(BaseObject):
""" """
return None return None
async def handle_error(self, error: ErrorFrame) -> Optional[FrameProcessor]: async def handle_error(self, error: ErrorFrame) -> FrameProcessor | None:
"""Handle an error from the active service. """Handle an error from the active service.
Called by ``ServiceSwitcher`` when a non-fatal ``ErrorFrame`` is pushed Called by ``ServiceSwitcher`` when a non-fatal ``ErrorFrame`` is pushed
@@ -103,7 +103,7 @@ class ServiceSwitcherStrategy(BaseObject):
""" """
return None return None
async def _set_active_if_available(self, service: FrameProcessor) -> Optional[FrameProcessor]: async def _set_active_if_available(self, service: FrameProcessor) -> FrameProcessor | None:
"""Set the active service to the given one, if it is in the list of available services. """Set the active service to the given one, if it is in the list of available services.
If it's not in the list, the request is ignored, as it may have been If it's not in the list, the request is ignored, as it may have been
@@ -139,7 +139,7 @@ class ServiceSwitcherStrategyManual(ServiceSwitcherStrategy):
async def handle_frame( async def handle_frame(
self, frame: ServiceSwitcherFrame, direction: FrameDirection self, frame: ServiceSwitcherFrame, direction: FrameDirection
) -> Optional[FrameProcessor]: ) -> FrameProcessor | None:
"""Handle a frame that controls service switching. """Handle a frame that controls service switching.
Args: Args:
@@ -179,7 +179,7 @@ class ServiceSwitcherStrategyFailover(ServiceSwitcherStrategyManual):
... ...
""" """
async def handle_error(self, error: ErrorFrame) -> Optional[FrameProcessor]: async def handle_error(self, error: ErrorFrame) -> FrameProcessor | None:
"""Handle an error from the active service by failing over. """Handle an error from the active service by failing over.
Switches to the next service in the list. The failed service remains Switches to the next service in the list. The failed service remains
@@ -223,8 +223,8 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
def __init__( def __init__(
self, self,
services: List[FrameProcessor], services: list[FrameProcessor],
strategy_type: Type[StrategyType] = ServiceSwitcherStrategyManual, strategy_type: type[StrategyType] = ServiceSwitcherStrategyManual,
): ):
"""Initialize the service switcher with a list of services and a switching strategy. """Initialize the service switcher with a list of services and a switching strategy.
@@ -244,14 +244,14 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
return self._strategy return self._strategy
@property @property
def services(self) -> List[FrameProcessor]: def services(self) -> list[FrameProcessor]:
"""Return the list of available services.""" """Return the list of available services."""
return self._services return self._services
@staticmethod @staticmethod
def _make_pipeline_definitions( def _make_pipeline_definitions(
services: List[FrameProcessor], strategy: ServiceSwitcherStrategy services: list[FrameProcessor], strategy: ServiceSwitcherStrategy
) -> List[Any]: ) -> list[Any]:
pipelines = [] pipelines = []
for service in services: for service in services:
pipelines.append(ServiceSwitcher._make_pipeline_definition(service, strategy)) pipelines.append(ServiceSwitcher._make_pipeline_definition(service, strategy))

View File

@@ -20,7 +20,6 @@ import asyncio
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from itertools import chain from itertools import chain
from typing import List
from loguru import logger from loguru import logger
@@ -215,7 +214,7 @@ class SyncParallelPipeline(BasePipeline):
return self._pipelines return self._pipelines
@property @property
def entry_processors(self) -> List["FrameProcessor"]: def entry_processors(self) -> list["FrameProcessor"]:
"""Return the list of entry processors for this processor. """Return the list of entry processors for this processor.
Entry processors are the first processors in a compound processor Entry processors are the first processors in a compound processor
@@ -228,7 +227,7 @@ class SyncParallelPipeline(BasePipeline):
""" """
return [s["processor"] for s in self._sources] return [s["processor"] for s in self._sources]
def processors_with_metrics(self) -> List[FrameProcessor]: def processors_with_metrics(self) -> list[FrameProcessor]:
"""Collect processors that can generate metrics from all parallel pipelines. """Collect processors that can generate metrics from all parallel pipelines.
Returns: Returns:

View File

@@ -14,8 +14,9 @@ including heartbeats, idle detection, and observer integration.
import asyncio import asyncio
import importlib.util import importlib.util
import os import os
from collections.abc import AsyncIterable, Iterable
from pathlib import Path from pathlib import Path
from typing import Any, AsyncIterable, Dict, Iterable, List, Optional, Set, Tuple, Type, TypeVar from typing import Any, TypeVar
from loguru import logger from loguru import logger
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field
@@ -74,7 +75,7 @@ class IdleFrameObserver(BaseObserver):
""" """
def __init__(self, *, idle_event: asyncio.Event, idle_timeout_frames: Tuple[Type[Frame], ...]): def __init__(self, *, idle_event: asyncio.Event, idle_timeout_frames: tuple[type[Frame], ...]):
"""Initialize the observer. """Initialize the observer.
Args: Args:
@@ -134,7 +135,7 @@ class PipelineParams(BaseModel):
heartbeats_monitor_secs: float = HEARTBEAT_MONITOR_SECS heartbeats_monitor_secs: float = HEARTBEAT_MONITOR_SECS
report_only_initial_ttfb: bool = False report_only_initial_ttfb: bool = False
send_initial_empty_metrics: bool = True send_initial_empty_metrics: bool = True
start_metadata: Dict[str, Any] = Field(default_factory=dict) start_metadata: dict[str, Any] = Field(default_factory=dict)
class PipelineTask(BasePipelineTask): class PipelineTask(BasePipelineTask):
@@ -190,22 +191,22 @@ class PipelineTask(BasePipelineTask):
self, self,
pipeline: BasePipeline, pipeline: BasePipeline,
*, *,
params: Optional[PipelineParams] = None, params: PipelineParams | None = None,
additional_span_attributes: Optional[dict] = None, additional_span_attributes: dict | None = None,
cancel_on_idle_timeout: bool = True, cancel_on_idle_timeout: bool = True,
cancel_timeout_secs: float = CANCEL_TIMEOUT_SECS, cancel_timeout_secs: float = CANCEL_TIMEOUT_SECS,
check_dangling_tasks: bool = True, check_dangling_tasks: bool = True,
clock: Optional[BaseClock] = None, clock: BaseClock | None = None,
conversation_id: Optional[str] = None, conversation_id: str | None = None,
enable_tracing: bool = False, enable_tracing: bool = False,
enable_turn_tracking: bool = True, enable_turn_tracking: bool = True,
enable_rtvi: bool = True, enable_rtvi: bool = True,
idle_timeout_frames: Tuple[Type[Frame], ...] = (BotSpeakingFrame, UserSpeakingFrame), idle_timeout_frames: tuple[type[Frame], ...] = (BotSpeakingFrame, UserSpeakingFrame),
idle_timeout_secs: Optional[float] = IDLE_TIMEOUT_SECS, idle_timeout_secs: float | None = IDLE_TIMEOUT_SECS,
observers: Optional[List[BaseObserver]] = None, observers: list[BaseObserver] | None = None,
rtvi_processor: Optional[RTVIProcessor] = None, rtvi_processor: RTVIProcessor | None = None,
rtvi_observer_params: Optional[RTVIObserverParams] = None, rtvi_observer_params: RTVIObserverParams | None = None,
task_manager: Optional[BaseTaskManager] = None, task_manager: BaseTaskManager | None = None,
): ):
"""Initialize the PipelineTask. """Initialize the PipelineTask.
@@ -246,10 +247,10 @@ class PipelineTask(BasePipelineTask):
self._enable_turn_tracking = enable_turn_tracking self._enable_turn_tracking = enable_turn_tracking
self._idle_timeout_secs = idle_timeout_secs self._idle_timeout_secs = idle_timeout_secs
observers = observers or [] observers = observers or []
self._turn_tracking_observer: Optional[TurnTrackingObserver] = None self._turn_tracking_observer: TurnTrackingObserver | None = None
self._user_bot_latency_observer: Optional[UserBotLatencyObserver] = None self._user_bot_latency_observer: UserBotLatencyObserver | None = None
self._turn_trace_observer: Optional[TurnTraceObserver] = None self._turn_trace_observer: TurnTraceObserver | None = None
self._tracing_context: Optional[TracingContext] = None self._tracing_context: TracingContext | None = None
if self._enable_turn_tracking: if self._enable_turn_tracking:
self._turn_tracking_observer = TurnTrackingObserver() self._turn_tracking_observer = TurnTrackingObserver()
observers.append(self._turn_tracking_observer) observers.append(self._turn_tracking_observer)
@@ -278,13 +279,13 @@ class PipelineTask(BasePipelineTask):
# This queue is the queue used to push frames to the pipeline. # This queue is the queue used to push frames to the pipeline.
self._push_queue = asyncio.Queue() self._push_queue = asyncio.Queue()
self._process_push_task: Optional[asyncio.Task] = None self._process_push_task: asyncio.Task | None = None
# This is the heartbeat queue. When a heartbeat frame is received in the # This is the heartbeat queue. When a heartbeat frame is received in the
# down queue we add it to the heartbeat queue for processing. # down queue we add it to the heartbeat queue for processing.
self._heartbeat_queue = asyncio.Queue() self._heartbeat_queue = asyncio.Queue()
self._heartbeat_push_task: Optional[asyncio.Task] = None self._heartbeat_push_task: asyncio.Task | None = None
self._heartbeat_monitor_task: Optional[asyncio.Task] = None self._heartbeat_monitor_task: asyncio.Task | None = None
# RTVI support # RTVI support
self._rtvi = None self._rtvi = None
@@ -323,7 +324,7 @@ class PipelineTask(BasePipelineTask):
# processor we consider the pipeline is not idle. We use an observer # processor we consider the pipeline is not idle. We use an observer
# which will be listening any part of the pipeline. # which will be listening any part of the pipeline.
self._idle_event = asyncio.Event() self._idle_event = asyncio.Event()
self._idle_monitor_task: Optional[asyncio.Task] = None self._idle_monitor_task: asyncio.Task | None = None
if self._idle_timeout_secs: if self._idle_timeout_secs:
idle_frame_observer = IdleFrameObserver( idle_frame_observer = IdleFrameObserver(
idle_event=self._idle_event, idle_event=self._idle_event,
@@ -365,8 +366,8 @@ class PipelineTask(BasePipelineTask):
# in. This is mainly for efficiency reason because each event handler # in. This is mainly for efficiency reason because each event handler
# creates a task and most likely you only care about one or two frame # creates a task and most likely you only care about one or two frame
# types. # types.
self._reached_upstream_types: Set[Type[Frame]] = set() self._reached_upstream_types: set[type[Frame]] = set()
self._reached_downstream_types: Set[Type[Frame]] = set() self._reached_downstream_types: set[type[Frame]] = set()
self._register_event_handler("on_frame_reached_upstream") self._register_event_handler("on_frame_reached_upstream")
self._register_event_handler("on_frame_reached_downstream") self._register_event_handler("on_frame_reached_downstream")
self._register_event_handler("on_idle_timeout") self._register_event_handler("on_idle_timeout")
@@ -395,7 +396,7 @@ class PipelineTask(BasePipelineTask):
return self._pipeline return self._pipeline
@property @property
def turn_tracking_observer(self) -> Optional[TurnTrackingObserver]: def turn_tracking_observer(self) -> TurnTrackingObserver | None:
"""Get the turn tracking observer if enabled. """Get the turn tracking observer if enabled.
Returns: Returns:
@@ -404,7 +405,7 @@ class PipelineTask(BasePipelineTask):
return self._turn_tracking_observer return self._turn_tracking_observer
@property @property
def turn_trace_observer(self) -> Optional[TurnTraceObserver]: def turn_trace_observer(self) -> TurnTraceObserver | None:
"""Get the turn trace observer if enabled. """Get the turn trace observer if enabled.
Returns: Returns:
@@ -424,7 +425,7 @@ class PipelineTask(BasePipelineTask):
return self._rtvi return self._rtvi
@property @property
def reached_upstream_types(self) -> Tuple[Type[Frame], ...]: def reached_upstream_types(self) -> tuple[type[Frame], ...]:
"""Get the currently configured upstream frame type filters. """Get the currently configured upstream frame type filters.
Returns: Returns:
@@ -433,7 +434,7 @@ class PipelineTask(BasePipelineTask):
return tuple(self._reached_upstream_types) return tuple(self._reached_upstream_types)
@property @property
def reached_downstream_types(self) -> Tuple[Type[Frame], ...]: def reached_downstream_types(self) -> tuple[type[Frame], ...]:
"""Get the currently configured downstream frame type filters. """Get the currently configured downstream frame type filters.
Returns: Returns:
@@ -457,7 +458,7 @@ class PipelineTask(BasePipelineTask):
""" """
await self._observer.remove_observer(observer) await self._observer.remove_observer(observer)
def set_reached_upstream_filter(self, types: Tuple[Type[Frame], ...]): def set_reached_upstream_filter(self, types: tuple[type[Frame], ...]):
"""Set which frame types trigger the on_frame_reached_upstream event. """Set which frame types trigger the on_frame_reached_upstream event.
Args: Args:
@@ -465,7 +466,7 @@ class PipelineTask(BasePipelineTask):
""" """
self._reached_upstream_types = set(types) self._reached_upstream_types = set(types)
def set_reached_downstream_filter(self, types: Tuple[Type[Frame], ...]): def set_reached_downstream_filter(self, types: tuple[type[Frame], ...]):
"""Set which frame types trigger the on_frame_reached_downstream event. """Set which frame types trigger the on_frame_reached_downstream event.
Args: Args:
@@ -473,7 +474,7 @@ class PipelineTask(BasePipelineTask):
""" """
self._reached_downstream_types = set(types) self._reached_downstream_types = set(types)
def add_reached_upstream_filter(self, types: Tuple[Type[Frame], ...]): def add_reached_upstream_filter(self, types: tuple[type[Frame], ...]):
"""Add frame types to trigger the on_frame_reached_upstream event. """Add frame types to trigger the on_frame_reached_upstream event.
Args: Args:
@@ -481,7 +482,7 @@ class PipelineTask(BasePipelineTask):
""" """
self._reached_upstream_types.update(types) self._reached_upstream_types.update(types)
def add_reached_downstream_filter(self, types: Tuple[Type[Frame], ...]): def add_reached_downstream_filter(self, types: tuple[type[Frame], ...]):
"""Add frame types to trigger the on_frame_reached_downstream event. """Add frame types to trigger the on_frame_reached_downstream event.
Args: Args:
@@ -509,7 +510,7 @@ class PipelineTask(BasePipelineTask):
logger.debug(f"Task {self} scheduled to stop when done") logger.debug(f"Task {self} scheduled to stop when done")
await self.queue_frame(EndFrame()) await self.queue_frame(EndFrame())
async def cancel(self, *, reason: Optional[str] = None): async def cancel(self, *, reason: str | None = None):
"""Request the running pipeline to cancel. """Request the running pipeline to cancel.
Args: Args:
@@ -597,7 +598,7 @@ class PipelineTask(BasePipelineTask):
for frame in frames: for frame in frames:
await self.queue_frame(frame, direction) await self.queue_frame(frame, direction)
async def _cancel(self, *, reason: Optional[str] = None): async def _cancel(self, *, reason: str | None = None):
"""Internal cancellation logic for the pipeline task. """Internal cancellation logic for the pipeline task.
Args: Args:
@@ -685,7 +686,7 @@ class PipelineTask(BasePipelineTask):
self._pipeline_end_event.wait(), timeout=self._cancel_timeout_secs self._pipeline_end_event.wait(), timeout=self._cancel_timeout_secs
) )
logger.debug(f"{self}: {frame} reached the end of the pipeline.") logger.debug(f"{self}: {frame} reached the end of the pipeline.")
except asyncio.TimeoutError: except TimeoutError:
logger.warning( logger.warning(
f"{self}: timeout waiting for {frame} to reach the end of the pipeline (being blocked somewhere?)." f"{self}: timeout waiting for {frame} to reach the end of the pipeline (being blocked somewhere?)."
) )
@@ -895,7 +896,7 @@ class PipelineTask(BasePipelineTask):
process_time = (self._clock.get_time() - frame.timestamp) / 1_000_000_000 process_time = (self._clock.get_time() - frame.timestamp) / 1_000_000_000
logger.trace(f"{self}: heartbeat frame processed in {process_time} seconds") logger.trace(f"{self}: heartbeat frame processed in {process_time} seconds")
self._heartbeat_queue.task_done() self._heartbeat_queue.task_done()
except asyncio.TimeoutError: except TimeoutError:
logger.warning( logger.warning(
f"{self}: heartbeat frame not received for more than {wait_time} seconds" f"{self}: heartbeat frame not received for more than {wait_time} seconds"
) )
@@ -913,7 +914,7 @@ class PipelineTask(BasePipelineTask):
try: try:
await asyncio.wait_for(self._idle_event.wait(), timeout=self._idle_timeout_secs) await asyncio.wait_for(self._idle_event.wait(), timeout=self._idle_timeout_secs)
self._idle_event.clear() self._idle_event.clear()
except asyncio.TimeoutError: except TimeoutError:
running = await self._idle_timeout_detected() running = await self._idle_timeout_detected()
async def _idle_timeout_detected(self) -> bool: async def _idle_timeout_detected(self) -> bool:
@@ -972,7 +973,7 @@ class PipelineTask(BasePipelineTask):
if tasks: if tasks:
logger.warning(f"{self} dangling tasks detected: {tasks}") logger.warning(f"{self} dangling tasks detected: {tasks}")
def _create_start_metadata(self) -> Dict[str, Any]: def _create_start_metadata(self) -> dict[str, Any]:
"""Build and return start metadata including user-provided values.""" """Build and return start metadata including user-provided values."""
start_metadata = {} start_metadata = {}
@@ -981,7 +982,7 @@ class PipelineTask(BasePipelineTask):
return start_metadata return start_metadata
def _find_processor(self, processor: FrameProcessor, processor_type: Type[T]) -> Optional[T]: def _find_processor(self, processor: FrameProcessor, processor_type: type[T]) -> T | None:
"""Recursively find a processor of the given type in the pipeline.""" """Recursively find a processor of the given type in the pipeline."""
if isinstance(processor, processor_type): if isinstance(processor, processor_type):
return processor return processor

View File

@@ -12,7 +12,7 @@ the main pipeline execution.
""" """
import asyncio import asyncio
from typing import Any, Dict, List, Optional from typing import Any
from attr import dataclass from attr import dataclass
@@ -61,7 +61,7 @@ class TaskObserver(BaseObserver):
def __init__( def __init__(
self, self,
*, *,
observers: Optional[List[BaseObserver]] = None, observers: list[BaseObserver] | None = None,
task_manager: BaseTaskManager, task_manager: BaseTaskManager,
**kwargs, **kwargs,
): ):
@@ -75,7 +75,7 @@ class TaskObserver(BaseObserver):
super().__init__(**kwargs) super().__init__(**kwargs)
self._observers = observers or [] self._observers = observers or []
self._task_manager = task_manager self._task_manager = task_manager
self._proxies: Optional[Dict[BaseObserver, Proxy]] = ( self._proxies: dict[BaseObserver, Proxy] | None = (
None # Becomes a dict after start() is called None # Becomes a dict after start() is called
) )
@@ -164,7 +164,7 @@ class TaskObserver(BaseObserver):
proxy = Proxy(queue=queue, task=task, observer=observer) proxy = Proxy(queue=queue, task=task, observer=observer)
return proxy return proxy
def _create_proxies(self, observers: List[BaseObserver]) -> Dict[BaseObserver, Proxy]: def _create_proxies(self, observers: list[BaseObserver]) -> dict[BaseObserver, Proxy]:
"""Create proxies for all observers.""" """Create proxies for all observers."""
proxies = {} proxies = {}
for observer in observers: for observer in observers:

View File

@@ -12,7 +12,6 @@ for downstream processing by LLM context aggregators.
""" """
import asyncio import asyncio
from typing import Optional
from pipecat.audio.dtmf.types import KeypadEntry from pipecat.audio.dtmf.types import KeypadEntry
from pipecat.frames.frames import ( from pipecat.frames.frames import (
@@ -62,7 +61,7 @@ class DTMFAggregator(FrameProcessor):
self._prefix = prefix self._prefix = prefix
self._digit_event = asyncio.Event() self._digit_event = asyncio.Event()
self._aggregation_task: Optional[asyncio.Task] = None self._aggregation_task: asyncio.Task | None = None
async def cleanup(self) -> None: async def cleanup(self) -> None:
"""Clean up resources.""" """Clean up resources."""
@@ -130,7 +129,7 @@ class DTMFAggregator(FrameProcessor):
try: try:
await asyncio.wait_for(self._digit_event.wait(), timeout=self._idle_timeout) await asyncio.wait_for(self._digit_event.wait(), timeout=self._idle_timeout)
self._digit_event.clear() self._digit_event.clear()
except asyncio.TimeoutError: except TimeoutError:
if self._aggregation: if self._aggregation:
await self._flush_aggregation() await self._flush_aggregation()

View File

@@ -11,8 +11,6 @@ custom gate open/close functions, allowing for conditional frame buffering
and release in frame processing pipelines. and release in frame processing pipelines.
""" """
from typing import List, Tuple
from loguru import logger from loguru import logger
from pipecat.frames.frames import Frame, SystemFrame from pipecat.frames.frames import Frame, SystemFrame
@@ -48,7 +46,7 @@ class GatedAggregator(FrameProcessor):
self._gate_close_fn = gate_close_fn self._gate_close_fn = gate_close_fn
self._gate_open = start_open self._gate_open = start_open
self._direction = direction self._direction = direction
self._accumulator: List[Tuple[Frame, FrameDirection]] = [] self._accumulator: list[tuple[Frame, FrameDirection]] = []
async def process_frame(self, frame: Frame, direction: FrameDirection): async def process_frame(self, frame: Frame, direction: FrameDirection):
"""Process incoming frames with gated accumulation logic. """Process incoming frames with gated accumulation logic.

View File

@@ -19,8 +19,9 @@ import base64
import copy import copy
import io import io
import wave import wave
from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Callable, List, Optional, TypeAlias, Union from typing import Any, TypeAlias
from loguru import logger from loguru import logger
from openai._types import NOT_GIVEN as OPEN_AI_NOT_GIVEN from openai._types import NOT_GIVEN as OPEN_AI_NOT_GIVEN
@@ -57,7 +58,7 @@ class LLMSpecificMessage:
message: Any message: Any
LLMContextMessage: TypeAlias = Union[LLMStandardMessage, LLMSpecificMessage] LLMContextMessage: TypeAlias = LLMStandardMessage | LLMSpecificMessage
class LLMContext: class LLMContext:
@@ -70,7 +71,7 @@ class LLMContext:
def __init__( def __init__(
self, self,
messages: Optional[List[LLMContextMessage]] = None, messages: list[LLMContextMessage] | None = None,
tools: ToolsSchema | NotGiven = NOT_GIVEN, tools: ToolsSchema | NotGiven = NOT_GIVEN,
tool_choice: LLMContextToolChoice | NotGiven = NOT_GIVEN, tool_choice: LLMContextToolChoice | NotGiven = NOT_GIVEN,
): ):
@@ -81,7 +82,7 @@ class LLMContext:
tools: Available tools for the LLM to use. tools: Available tools for the LLM to use.
tool_choice: Tool selection strategy for the LLM. tool_choice: Tool selection strategy for the LLM.
""" """
self._messages: List[LLMContextMessage] = messages if messages else [] self._messages: list[LLMContextMessage] = messages if messages else []
self._tools: ToolsSchema | NotGiven = LLMContext._normalize_and_validate_tools(tools) self._tools: ToolsSchema | NotGiven = LLMContext._normalize_and_validate_tools(tools)
self._tool_choice: LLMContextToolChoice | NotGiven = tool_choice self._tool_choice: LLMContextToolChoice | NotGiven = tool_choice
@@ -90,7 +91,7 @@ class LLMContext:
*, *,
role: str = "user", role: str = "user",
url: str, url: str,
text: Optional[str] = None, text: str | None = None,
) -> LLMContextMessage: ) -> LLMContextMessage:
"""Create a context message containing an image URL. """Create a context message containing an image URL.
@@ -114,7 +115,7 @@ class LLMContext:
format: str, format: str,
size: tuple[int, int], size: tuple[int, int],
image: bytes, image: bytes,
text: Optional[str] = None, text: str | None = None,
) -> LLMContextMessage: ) -> LLMContextMessage:
"""Create a context message containing an image. """Create a context message containing an image.
@@ -187,7 +188,7 @@ class LLMContext:
return {"role": role, "content": content} return {"role": role, "content": content}
@property @property
def messages(self) -> List[LLMContextMessage]: def messages(self) -> list[LLMContextMessage]:
"""Get the current messages list. """Get the current messages list.
NOTE: This is equivalent to calling `get_messages()` with no filter. If NOTE: This is equivalent to calling `get_messages()` with no filter. If
@@ -201,10 +202,10 @@ class LLMContext:
def get_messages( def get_messages(
self, self,
llm_specific_filter: Optional[str] = None, llm_specific_filter: str | None = None,
*, *,
truncate_large_values: bool = False, truncate_large_values: bool = False,
) -> List[LLMContextMessage]: ) -> list[LLMContextMessage]:
"""Get the current messages list. """Get the current messages list.
Args: Args:
@@ -242,8 +243,8 @@ class LLMContext:
@staticmethod @staticmethod
def _truncate_large_values_from_messages( def _truncate_large_values_from_messages(
messages: List[LLMContextMessage], messages: list[LLMContextMessage],
) -> List[LLMContextMessage]: ) -> list[LLMContextMessage]:
"""Return deep copies of messages with large values replaced by placeholders. """Return deep copies of messages with large values replaced by placeholders.
For standard (universal-format) messages, the following known binary For standard (universal-format) messages, the following known binary
@@ -344,7 +345,7 @@ class LLMContext:
""" """
self._messages.append(message) self._messages.append(message)
def add_messages(self, messages: List[LLMContextMessage]): def add_messages(self, messages: list[LLMContextMessage]):
"""Add multiple messages to the context. """Add multiple messages to the context.
Args: Args:
@@ -352,7 +353,7 @@ class LLMContext:
""" """
self._messages.extend(messages) self._messages.extend(messages)
def set_messages(self, messages: List[LLMContextMessage]): def set_messages(self, messages: list[LLMContextMessage]):
"""Replace all messages in the context. """Replace all messages in the context.
Args: Args:
@@ -361,7 +362,7 @@ class LLMContext:
self._messages[:] = messages self._messages[:] = messages
def transform_messages( def transform_messages(
self, transform: Callable[[List[LLMContextMessage]], List[LLMContextMessage]] self, transform: Callable[[list[LLMContextMessage]], list[LLMContextMessage]]
): ):
"""Transform the current messages using the provided function. """Transform the current messages using the provided function.
@@ -393,7 +394,7 @@ class LLMContext:
format: str, format: str,
size: tuple[int, int], size: tuple[int, int],
image: bytes, image: bytes,
text: Optional[str] = None, text: str | None = None,
role: str = "user", role: str = "user",
): ):
"""Add a message containing an image frame. """Add a message containing an image frame.

View File

@@ -9,7 +9,7 @@
import asyncio import asyncio
import uuid import uuid
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING
from loguru import logger from loguru import logger
@@ -101,7 +101,7 @@ class LLMContextSummarizer(BaseObject):
self, self,
*, *,
context: LLMContext, context: LLMContext,
config: Optional[LLMAutoContextSummarizationConfig] = None, config: LLMAutoContextSummarizationConfig | None = None,
auto_trigger: bool = True, auto_trigger: bool = True,
): ):
"""Initialize the context summarizer. """Initialize the context summarizer.
@@ -122,10 +122,10 @@ class LLMContextSummarizer(BaseObject):
self._auto_config = config or LLMAutoContextSummarizationConfig() self._auto_config = config or LLMAutoContextSummarizationConfig()
self._auto_trigger = auto_trigger self._auto_trigger = auto_trigger
self._task_manager: Optional[BaseTaskManager] = None self._task_manager: BaseTaskManager | None = None
self._summarization_in_progress = False self._summarization_in_progress = False
self._pending_summary_request_id: Optional[str] = None self._pending_summary_request_id: str | None = None
self._register_event_handler("on_request_summarization", sync=True) self._register_event_handler("on_request_summarization", sync=True)
self._register_event_handler("on_summary_applied") self._register_event_handler("on_summary_applied")
@@ -269,9 +269,7 @@ class LLMContextSummarizer(BaseObject):
logger.debug(f"{self}: ✓ Summarization needed - {', '.join(reason)}") logger.debug(f"{self}: ✓ Summarization needed - {', '.join(reason)}")
return True return True
async def _request_summarization( async def _request_summarization(self, config_override: LLMContextSummaryConfig | None = None):
self, config_override: Optional[LLMContextSummaryConfig] = None
):
"""Request context summarization from LLM service. """Request context summarization from LLM service.
Creates a summarization request frame and either handles it directly Creates a summarization request frame and either handles it directly
@@ -338,7 +336,7 @@ class LLMContextSummarizer(BaseObject):
summary=summary, summary=summary,
last_summarized_index=last_index, last_summarized_index=last_index,
) )
except asyncio.TimeoutError: except TimeoutError:
error = f"Context summarization timed out after {timeout}s" error = f"Context summarization timed out after {timeout}s"
logger.error(f"{self}: {error}") logger.error(f"{self}: {error}")
result_frame = LLMContextSummaryResultFrame( result_frame = LLMContextSummaryResultFrame(

View File

@@ -15,8 +15,9 @@ import asyncio
import json import json
import warnings import warnings
from abc import abstractmethod from abc import abstractmethod
from collections.abc import Callable
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Literal, Optional, Set, Type from typing import Any, Literal
from loguru import logger from loguru import logger
@@ -119,14 +120,14 @@ class LLMUserAggregatorParams:
filter_incomplete_user_turns is True. filter_incomplete_user_turns is True.
""" """
user_turn_strategies: Optional[UserTurnStrategies] = None user_turn_strategies: UserTurnStrategies | None = None
user_mute_strategies: List[BaseUserMuteStrategy] = field(default_factory=list) user_mute_strategies: list[BaseUserMuteStrategy] = field(default_factory=list)
user_turn_stop_timeout: float = 5.0 user_turn_stop_timeout: float = 5.0
user_idle_timeout: float = 0 user_idle_timeout: float = 0
vad_analyzer: Optional[VADAnalyzer] = None vad_analyzer: VADAnalyzer | None = None
audio_idle_timeout: float = 1.0 audio_idle_timeout: float = 1.0
filter_incomplete_user_turns: bool = False filter_incomplete_user_turns: bool = False
user_turn_completion_config: Optional[UserTurnCompletionConfig] = None user_turn_completion_config: UserTurnCompletionConfig | None = None
@dataclass @dataclass
@@ -145,14 +146,14 @@ class LLMAssistantAggregatorParams:
""" """
enable_auto_context_summarization: bool = False enable_auto_context_summarization: bool = False
auto_context_summarization_config: Optional[LLMAutoContextSummarizationConfig] = None auto_context_summarization_config: LLMAutoContextSummarizationConfig | None = None
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Deprecated field names — kept for backward compatibility. # Deprecated field names — kept for backward compatibility.
# Use enable_auto_context_summarization and auto_context_summarization_config instead. # Use enable_auto_context_summarization and auto_context_summarization_config instead.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
enable_context_summarization: Optional[bool] = None enable_context_summarization: bool | None = None
context_summarization_config: Optional[LLMContextSummarizationConfig] = None context_summarization_config: LLMContextSummarizationConfig | None = None
def __post_init__(self): def __post_init__(self):
if self.enable_context_summarization is not None: if self.enable_context_summarization is not None:
@@ -198,7 +199,7 @@ class UserTurnStoppedMessage:
content: str content: str
timestamp: str timestamp: str
user_id: Optional[str] = None user_id: str | None = None
@dataclass @dataclass
@@ -259,10 +260,10 @@ class LLMContextAggregator(FrameProcessor):
self._context = context self._context = context
self._role = role self._role = role
self._aggregation: List[TextPartForConcatenation] = [] self._aggregation: list[TextPartForConcatenation] = []
@property @property
def messages(self) -> List[LLMContextMessage]: def messages(self) -> list[LLMContextMessage]:
"""Get messages from the LLM context. """Get messages from the LLM context.
Returns: Returns:
@@ -322,7 +323,7 @@ class LLMContextAggregator(FrameProcessor):
self._context.set_messages(messages) self._context.set_messages(messages)
def transform_messages( def transform_messages(
self, transform: Callable[[List[LLMContextMessage]], List[LLMContextMessage]] self, transform: Callable[[list[LLMContextMessage]], list[LLMContextMessage]]
): ):
"""Transform the context messages using a provided function. """Transform the context messages using a provided function.
@@ -423,7 +424,7 @@ class LLMUserAggregator(LLMContextAggregator):
self, self,
context: LLMContext, context: LLMContext,
*, *,
params: Optional[LLMUserAggregatorParams] = None, params: LLMUserAggregatorParams | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the user context aggregator. """Initialize the user context aggregator.
@@ -473,7 +474,7 @@ class LLMUserAggregator(LLMContextAggregator):
self._user_idle_controller.add_event_handler("on_user_turn_idle", self._on_user_turn_idle) self._user_idle_controller.add_event_handler("on_user_turn_idle", self._on_user_turn_idle)
# VAD controller # VAD controller
self._vad_controller: Optional[VADController] = None self._vad_controller: VADController | None = None
if self._params.vad_analyzer: if self._params.vad_analyzer:
self._vad_controller = VADController( self._vad_controller = VADController(
self._params.vad_analyzer, self._params.vad_analyzer,
@@ -681,7 +682,7 @@ class LLMUserAggregator(LLMContextAggregator):
) )
) )
async def _queued_broadcast_frame(self, frame_cls: Type[Frame], **kwargs): async def _queued_broadcast_frame(self, frame_cls: type[Frame], **kwargs):
"""Broadcasts a frame upstream and queues it for internal processing. """Broadcasts a frame upstream and queues it for internal processing.
Queues the frame so it flows through `process_frame` and is handled Queues the frame so it flows through `process_frame` and is handled
@@ -701,7 +702,7 @@ class LLMUserAggregator(LLMContextAggregator):
): ):
await self.queue_frame(frame, direction) await self.queue_frame(frame, direction)
async def _on_broadcast_frame(self, controller, frame_cls: Type[Frame], **kwargs): async def _on_broadcast_frame(self, controller, frame_cls: type[Frame], **kwargs):
await self._queued_broadcast_frame(frame_cls, **kwargs) await self._queued_broadcast_frame(frame_cls, **kwargs)
async def _on_vad_speech_started(self, controller): async def _on_vad_speech_started(self, controller):
@@ -768,7 +769,7 @@ class LLMUserAggregator(LLMContextAggregator):
async def _maybe_emit_user_turn_stopped( async def _maybe_emit_user_turn_stopped(
self, self,
strategy: Optional[BaseUserTurnStopStrategy] = None, strategy: BaseUserTurnStopStrategy | None = None,
on_session_end: bool = False, on_session_end: bool = False,
): ):
"""Maybe emit user turn stopped event. """Maybe emit user turn stopped event.
@@ -832,7 +833,7 @@ class LLMAssistantAggregator(LLMContextAggregator):
self, self,
context: LLMContext, context: LLMContext,
*, *,
params: Optional[LLMAssistantAggregatorParams] = None, params: LLMAssistantAggregatorParams | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the assistant context aggregator. """Initialize the assistant context aggregator.
@@ -845,9 +846,9 @@ class LLMAssistantAggregator(LLMContextAggregator):
super().__init__(context=context, role="assistant", **kwargs) super().__init__(context=context, role="assistant", **kwargs)
self._params = params or LLMAssistantAggregatorParams() self._params = params or LLMAssistantAggregatorParams()
self._function_calls_in_progress: Dict[str, Optional[FunctionCallInProgressFrame]] = {} self._function_calls_in_progress: dict[str, FunctionCallInProgressFrame | None] = {}
self._function_calls_image_results: Dict[str, UserImageRawFrame] = {} self._function_calls_image_results: dict[str, UserImageRawFrame] = {}
self._context_updated_tasks: Set[asyncio.Task] = set() self._context_updated_tasks: set[asyncio.Task] = set()
self._user_speaking: bool = False self._user_speaking: bool = False
self._bot_speaking: bool = False self._bot_speaking: bool = False
@@ -862,14 +863,14 @@ class LLMAssistantAggregator(LLMContextAggregator):
self._thought_append_to_context = False self._thought_append_to_context = False
self._thought_llm: str = "" self._thought_llm: str = ""
self._thought_aggregation: List[TextPartForConcatenation] = [] self._thought_aggregation: list[TextPartForConcatenation] = []
self._thought_start_time: str = "" self._thought_start_time: str = ""
# Context summarization — always create the summarizer so that manually # Context summarization — always create the summarizer so that manually
# pushed LLMSummarizeContextFrame frames are always handled. # pushed LLMSummarizeContextFrame frames are always handled.
# Auto-triggering based on thresholds is only enabled when # Auto-triggering based on thresholds is only enabled when
# enable_auto_context_summarization is True. # enable_auto_context_summarization is True.
self._summarizer: Optional[LLMContextSummarizer] = LLMContextSummarizer( self._summarizer: LLMContextSummarizer | None = LLMContextSummarizer(
context=self._context, context=self._context,
config=self._params.auto_context_summarization_config, config=self._params.auto_context_summarization_config,
auto_trigger=self._params.enable_auto_context_summarization, auto_trigger=self._params.enable_auto_context_summarization,
@@ -1475,8 +1476,8 @@ class LLMContextAggregatorPair:
self, self,
context: LLMContext, context: LLMContext,
*, *,
user_params: Optional[LLMUserAggregatorParams] = None, user_params: LLMUserAggregatorParams | None = None,
assistant_params: Optional[LLMAssistantAggregatorParams] = None, assistant_params: LLMAssistantAggregatorParams | None = None,
): ):
"""Initialize the LLM context aggregator pair. """Initialize the LLM context aggregator pair.

View File

@@ -13,8 +13,6 @@ components such as TTS services or context aggregators. It can be used to pre-ag
and categorize, modify, or filter direct output tokens from the LLM. and categorize, modify, or filter direct output tokens from the LLM.
""" """
from typing import Optional
from pipecat.frames.frames import ( from pipecat.frames.frames import (
AggregatedTextFrame, AggregatedTextFrame,
EndFrame, EndFrame,
@@ -38,7 +36,7 @@ class LLMTextProcessor(FrameProcessor):
output tokens from the LLM. output tokens from the LLM.
""" """
def __init__(self, *, text_aggregator: Optional[BaseTextAggregator] = None, **kwargs): def __init__(self, *, text_aggregator: BaseTextAggregator | None = None, **kwargs):
"""Initialize the LLM text processor. """Initialize the LLM text processor.
Args: Args:
@@ -91,7 +89,7 @@ class LLMTextProcessor(FrameProcessor):
out_frame.skip_tts = in_frame.skip_tts out_frame.skip_tts = in_frame.skip_tts
await self.push_frame(out_frame) await self.push_frame(out_frame)
async def _handle_llm_end(self, skip_tts: Optional[bool] = None): async def _handle_llm_end(self, skip_tts: bool | None = None):
# Flush any remaining text # Flush any remaining text
remaining = await self._text_aggregator.flush() remaining = await self._text_aggregator.flush()
if remaining: if remaining:

View File

@@ -7,7 +7,8 @@
"""Async generator processor for frame serialization and streaming.""" """Async generator processor for frame serialization and streaming."""
import asyncio import asyncio
from typing import Any, AsyncGenerator from collections.abc import AsyncGenerator
from typing import Any
from pipecat.frames.frames import ( from pipecat.frames.frames import (
CancelFrame, CancelFrame,

View File

@@ -11,8 +11,6 @@ of audio from both user input and bot output sources, with support for various a
configurations and event-driven processing. configurations and event-driven processing.
""" """
from typing import Optional
from pipecat.audio.utils import create_stream_resampler, interleave_stereo_audio, mix_audio from pipecat.audio.utils import create_stream_resampler, interleave_stereo_audio, mix_audio
from pipecat.frames.frames import ( from pipecat.frames.frames import (
BotStartedSpeakingFrame, BotStartedSpeakingFrame,
@@ -55,7 +53,7 @@ class AudioBufferProcessor(FrameProcessor):
def __init__( def __init__(
self, self,
*, *,
sample_rate: Optional[int] = None, sample_rate: int | None = None,
num_channels: int = 1, num_channels: int = 1,
buffer_size: int = 0, buffer_size: int = 0,
enable_turn_audio: bool = False, enable_turn_audio: bool = False,
@@ -263,7 +261,7 @@ class AudioBufferProcessor(FrameProcessor):
silence_needed = target_position - current_len silence_needed = target_position - current_len
buffer.extend(b"\x00" * silence_needed) buffer.extend(b"\x00" * silence_needed)
async def _process_turn_recording(self, frame: Frame, resampled_audio: Optional[bytes] = None): async def _process_turn_recording(self, frame: Frame, resampled_audio: bytes | None = None):
"""Process frames for turn-based audio recording.""" """Process frames for turn-based audio recording."""
# Speaking state (_user_speaking / _bot_speaking) is maintained by # Speaking state (_user_speaking / _bot_speaking) is maintained by
# _process_recording so it is always up-to-date here. # _process_recording so it is always up-to-date here.

View File

@@ -10,8 +10,6 @@ This module provides a VADProcessor that wraps a VADController to process
audio frames and push VAD-related frames into the pipeline. audio frames and push VAD-related frames into the pipeline.
""" """
from typing import Type
from loguru import logger from loguru import logger
from pipecat.audio.vad.vad_analyzer import VADAnalyzer from pipecat.audio.vad.vad_analyzer import VADAnalyzer
@@ -94,7 +92,7 @@ class VADProcessor(FrameProcessor):
await self.push_frame(frame, direction) await self.push_frame(frame, direction)
@self._vad_controller.event_handler("on_broadcast_frame") @self._vad_controller.event_handler("on_broadcast_frame")
async def on_broadcast_frame(_controller, frame_cls: Type[Frame], **kwargs): async def on_broadcast_frame(_controller, frame_cls: type[Frame], **kwargs):
await self.broadcast_frame(frame_cls, **kwargs) await self.broadcast_frame(frame_cls, **kwargs)
async def cleanup(self): async def cleanup(self):

View File

@@ -7,7 +7,7 @@
"""Consumer processor for consuming frames from ProducerProcessor queues.""" """Consumer processor for consuming frames from ProducerProcessor queues."""
import asyncio import asyncio
from typing import Awaitable, Callable, Optional from collections.abc import Awaitable, Callable
from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@@ -42,7 +42,7 @@ class ConsumerProcessor(FrameProcessor):
self._transformer = transformer self._transformer = transformer
self._direction = direction self._direction = direction
self._producer = producer self._producer = producer
self._consumer_task: Optional[asyncio.Task] = None self._consumer_task: asyncio.Task | None = None
async def process_frame(self, frame: Frame, direction: FrameDirection): async def process_frame(self, frame: Frame, direction: FrameDirection):
"""Process incoming frames and handle lifecycle events. """Process incoming frames and handle lifecycle events.

View File

@@ -6,8 +6,6 @@
"""Frame filtering processor for the Pipecat framework.""" """Frame filtering processor for the Pipecat framework."""
from typing import Tuple, Type
from pipecat.frames.frames import EndFrame, Frame, SystemFrame from pipecat.frames.frames import EndFrame, Frame, SystemFrame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@@ -20,7 +18,7 @@ class FrameFilter(FrameProcessor):
automatically allowed to pass through to maintain pipeline integrity. automatically allowed to pass through to maintain pipeline integrity.
""" """
def __init__(self, types: Tuple[Type[Frame], ...]): def __init__(self, types: tuple[type[Frame], ...]):
"""Initialize the frame filter. """Initialize the frame filter.
Args: Args:

View File

@@ -10,7 +10,7 @@ This module provides a processor that filters frames based on a custom function,
allowing for flexible frame filtering logic in processing pipelines. allowing for flexible frame filtering logic in processing pipelines.
""" """
from typing import Awaitable, Callable, Optional from collections.abc import Awaitable, Callable
from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame, SystemFrame from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame, SystemFrame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@@ -29,7 +29,7 @@ class FunctionFilter(FrameProcessor):
def __init__( def __init__(
self, self,
filter: FilterType, filter: FilterType,
direction: Optional[FrameDirection] = FrameDirection.DOWNSTREAM, direction: FrameDirection | None = FrameDirection.DOWNSTREAM,
filter_system_frames: bool = False, filter_system_frames: bool = False,
**kwargs, **kwargs,
): ):

View File

@@ -18,7 +18,6 @@ import re
import time import time
import warnings import warnings
from enum import Enum from enum import Enum
from typing import List
from loguru import logger from loguru import logger
@@ -71,7 +70,7 @@ class WakeCheckFilter(FrameProcessor):
self.wake_timer = 0.0 self.wake_timer = 0.0
self.accumulator = "" self.accumulator = ""
def __init__(self, wake_phrases: List[str], keepalive_timeout: float = 3): def __init__(self, wake_phrases: list[str], keepalive_timeout: float = 3):
"""Initialize the wake phrase filter. """Initialize the wake phrase filter.
.. deprecated:: 0.0.106 .. deprecated:: 0.0.106

View File

@@ -6,7 +6,7 @@
"""Wake notifier filter for conditional frame-based notifications.""" """Wake notifier filter for conditional frame-based notifications."""
from typing import Awaitable, Callable, Tuple, Type from collections.abc import Awaitable, Callable
from pipecat.frames.frames import Frame from pipecat.frames.frames import Frame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@@ -25,7 +25,7 @@ class WakeNotifierFilter(FrameProcessor):
self, self,
notifier: BaseNotifier, notifier: BaseNotifier,
*, *,
types: Tuple[Type[Frame], ...], types: tuple[type[Frame], ...],
filter: Callable[[Frame], Awaitable[bool]], filter: Callable[[Frame], Awaitable[bool]],
**kwargs, **kwargs,
): ):

View File

@@ -11,21 +11,17 @@ audio/video processing pipelines. It includes frame processors, pipeline
management, and frame flow control mechanisms. management, and frame flow control mechanisms.
""" """
from __future__ import annotations
import asyncio import asyncio
import dataclasses import dataclasses
import traceback import traceback
from collections.abc import Awaitable, Callable, Coroutine
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import ( from typing import (
Any, Any,
Awaitable,
Callable,
Coroutine,
List,
Optional, Optional,
Tuple,
Type,
Union,
) )
from loguru import logger from loguru import logger
@@ -79,7 +75,7 @@ class FrameProcessorSetup:
clock: BaseClock clock: BaseClock
task_manager: BaseTaskManager task_manager: BaseTaskManager
observer: Optional[BaseObserver] = None observer: BaseObserver | None = None
class FrameProcessorQueue(asyncio.PriorityQueue): class FrameProcessorQueue(asyncio.PriorityQueue):
@@ -100,7 +96,7 @@ class FrameProcessorQueue(asyncio.PriorityQueue):
self.__high_counter = 0 self.__high_counter = 0
self.__low_counter = 0 self.__low_counter = 0
async def put(self, item: Tuple[Frame, FrameDirection, FrameCallback]): async def put(self, item: tuple[Frame, FrameDirection, FrameCallback]):
"""Put an item into the priority queue. """Put an item into the priority queue.
System frames (`SystemFrame`) have higher priority than any other System frames (`SystemFrame`) have higher priority than any other
@@ -160,9 +156,9 @@ class FrameProcessor(BaseObject):
def __init__( def __init__(
self, self,
*, *,
name: Optional[str] = None, name: str | None = None,
enable_direct_mode: bool = False, enable_direct_mode: bool = False,
metrics: Optional[FrameProcessorMetrics] = None, metrics: FrameProcessorMetrics | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the frame processor. """Initialize the frame processor.
@@ -174,20 +170,20 @@ class FrameProcessor(BaseObject):
**kwargs: Additional arguments passed to parent class. **kwargs: Additional arguments passed to parent class.
""" """
super().__init__(name=name, **kwargs) super().__init__(name=name, **kwargs)
self._prev: Optional["FrameProcessor"] = None self._prev: FrameProcessor | None = None
self._next: Optional["FrameProcessor"] = None self._next: FrameProcessor | None = None
# Enable direct mode to skip queues and process frames right away. # Enable direct mode to skip queues and process frames right away.
self._enable_direct_mode = enable_direct_mode self._enable_direct_mode = enable_direct_mode
# Clock # Clock
self._clock: Optional[BaseClock] = None self._clock: BaseClock | None = None
# Task Manager # Task Manager
self._task_manager: Optional[BaseTaskManager] = None self._task_manager: BaseTaskManager | None = None
# Observer # Observer
self._observer: Optional[BaseObserver] = None self._observer: BaseObserver | None = None
# Other properties # Other properties
self._enable_metrics = False self._enable_metrics = False
@@ -221,8 +217,8 @@ class FrameProcessor(BaseObject):
# frames right away and queues non-system frames for later processing. # frames right away and queues non-system frames for later processing.
self.__should_block_system_frames = False self.__should_block_system_frames = False
self.__input_queue = FrameProcessorQueue() self.__input_queue = FrameProcessorQueue()
self.__input_event: Optional[asyncio.Event] = None self.__input_event: asyncio.Event | None = None
self.__input_frame_task: Optional[asyncio.Task] = None self.__input_frame_task: asyncio.Task | None = None
# The process task processes non-system frames. Non-system frames will # The process task processes non-system frames. Non-system frames will
# be processed as soon as they are received by the processing task # be processed as soon as they are received by the processing task
@@ -231,9 +227,9 @@ class FrameProcessor(BaseObject):
# `resume_processing_frames()` which will wake up the event. # `resume_processing_frames()` which will wake up the event.
self.__should_block_frames = False self.__should_block_frames = False
self.__process_queue = FrameQueue(frame_getter=lambda item: item[0]) self.__process_queue = FrameQueue(frame_getter=lambda item: item[0])
self.__process_event: Optional[asyncio.Event] = None self.__process_event: asyncio.Event | None = None
self.__process_frame_task: Optional[asyncio.Task] = None self.__process_frame_task: asyncio.Task | None = None
self.__process_current_frame: Optional[Frame] = None self.__process_current_frame: Frame | None = None
# Frame processor events. # Frame processor events.
self._register_event_handler("on_before_process_frame", sync=True) self._register_event_handler("on_before_process_frame", sync=True)
@@ -261,7 +257,7 @@ class FrameProcessor(BaseObject):
return self._name return self._name
@property @property
def processors(self) -> List["FrameProcessor"]: def processors(self) -> list[FrameProcessor]:
"""Return the list of sub-processors contained within this processor. """Return the list of sub-processors contained within this processor.
Only compound processors (e.g. pipelines and parallel pipelines) have Only compound processors (e.g. pipelines and parallel pipelines) have
@@ -273,7 +269,7 @@ class FrameProcessor(BaseObject):
return [] return []
@property @property
def entry_processors(self) -> List["FrameProcessor"]: def entry_processors(self) -> list[FrameProcessor]:
"""Return the list of entry processors for this processor. """Return the list of entry processors for this processor.
Entry processors are the first processors in a compound processor Entry processors are the first processors in a compound processor
@@ -287,7 +283,7 @@ class FrameProcessor(BaseObject):
return [] return []
@property @property
def next(self) -> Optional["FrameProcessor"]: def next(self) -> FrameProcessor | None:
"""Get the next processor. """Get the next processor.
Returns: Returns:
@@ -296,7 +292,7 @@ class FrameProcessor(BaseObject):
return self._next return self._next
@property @property
def previous(self) -> Optional["FrameProcessor"]: def previous(self) -> FrameProcessor | None:
"""Get the previous processor. """Get the previous processor.
Returns: Returns:
@@ -372,7 +368,7 @@ class FrameProcessor(BaseObject):
""" """
self._metrics.set_core_metrics_data(data) self._metrics.set_core_metrics_data(data)
async def start_ttfb_metrics(self, *, start_time: Optional[float] = None): async def start_ttfb_metrics(self, *, start_time: float | None = None):
"""Start time-to-first-byte metrics collection. """Start time-to-first-byte metrics collection.
Args: Args:
@@ -384,7 +380,7 @@ class FrameProcessor(BaseObject):
start_time=start_time, report_only_initial_ttfb=self._report_only_initial_ttfb start_time=start_time, report_only_initial_ttfb=self._report_only_initial_ttfb
) )
async def stop_ttfb_metrics(self, *, end_time: Optional[float] = None): async def stop_ttfb_metrics(self, *, end_time: float | None = None):
"""Stop time-to-first-byte metrics collection and push results. """Stop time-to-first-byte metrics collection and push results.
Args: Args:
@@ -396,7 +392,7 @@ class FrameProcessor(BaseObject):
if frame: if frame:
await self.push_frame(frame) await self.push_frame(frame)
async def start_processing_metrics(self, *, start_time: Optional[float] = None): async def start_processing_metrics(self, *, start_time: float | None = None):
"""Start processing metrics collection. """Start processing metrics collection.
Args: Args:
@@ -406,7 +402,7 @@ class FrameProcessor(BaseObject):
if self.can_generate_metrics() and self.metrics_enabled: if self.can_generate_metrics() and self.metrics_enabled:
await self._metrics.start_processing_metrics(start_time=start_time) await self._metrics.start_processing_metrics(start_time=start_time)
async def stop_processing_metrics(self, *, end_time: Optional[float] = None): async def stop_processing_metrics(self, *, end_time: float | None = None):
"""Stop processing metrics collection and push results. """Stop processing metrics collection and push results.
Args: Args:
@@ -458,7 +454,7 @@ class FrameProcessor(BaseObject):
await self.stop_processing_metrics() await self.stop_processing_metrics()
await self.stop_text_aggregation_metrics() await self.stop_text_aggregation_metrics()
def create_task(self, coroutine: Coroutine, name: Optional[str] = None) -> asyncio.Task: def create_task(self, coroutine: Coroutine, name: str | None = None) -> asyncio.Task:
"""Create a new task managed by this processor. """Create a new task managed by this processor.
Args: Args:
@@ -474,7 +470,7 @@ class FrameProcessor(BaseObject):
name = f"{self}::{coroutine.cr_code.co_name}" name = f"{self}::{coroutine.cr_code.co_name}"
return self.task_manager.create_task(coroutine, name) return self.task_manager.create_task(coroutine, name)
async def cancel_task(self, task: asyncio.Task, timeout: Optional[float] = 1.0): async def cancel_task(self, task: asyncio.Task, timeout: float | None = 1.0):
"""Cancel a task managed by this processor. """Cancel a task managed by this processor.
A default timeout if 1 second is used in order to avoid potential A default timeout if 1 second is used in order to avoid potential
@@ -511,7 +507,7 @@ class FrameProcessor(BaseObject):
if self._metrics is not None: if self._metrics is not None:
await self._metrics.cleanup() await self._metrics.cleanup()
def link(self, processor: "FrameProcessor"): def link(self, processor: FrameProcessor):
"""Link this processor to the next processor in the pipeline. """Link this processor to the next processor in the pipeline.
Args: Args:
@@ -546,7 +542,7 @@ class FrameProcessor(BaseObject):
self, self,
frame: Frame, frame: Frame,
direction: FrameDirection = FrameDirection.DOWNSTREAM, direction: FrameDirection = FrameDirection.DOWNSTREAM,
callback: Optional[FrameCallback] = None, callback: FrameCallback | None = None,
): ):
"""Queue a frame for processing. """Queue a frame for processing.
@@ -622,7 +618,7 @@ class FrameProcessor(BaseObject):
async def push_error( async def push_error(
self, self,
error_msg: str, error_msg: str,
exception: Optional[Exception] = None, exception: Exception | None = None,
fatal: bool = False, fatal: bool = False,
): ):
"""Creates and pushes an ErrorFrame upstream. """Creates and pushes an ErrorFrame upstream.
@@ -720,7 +716,7 @@ class FrameProcessor(BaseObject):
await self.broadcast_interruption() await self.broadcast_interruption()
async def broadcast_frame(self, frame_cls: Type[Frame], **kwargs): async def broadcast_frame(self, frame_cls: type[Frame], **kwargs):
"""Broadcasts a frame of the specified class upstream and downstream. """Broadcasts a frame of the specified class upstream and downstream.
This method creates two instances of the given frame class using the This method creates two instances of the given frame class using the
@@ -929,7 +925,7 @@ class FrameProcessor(BaseObject):
"""Reset non-system frame processing queue.""" """Reset non-system frame processing queue."""
self.__process_queue.reset() self.__process_queue.reset()
def has_queued_frame(self, frame_type: Union[Type[Frame], Type[UninterruptibleFrame]]) -> bool: def has_queued_frame(self, frame_type: type[Frame] | type[UninterruptibleFrame]) -> bool:
"""Return True if a frame of the given type is waiting in the processing queue. """Return True if a frame of the given type is waiting in the processing queue.
Delegates to :meth:`FrameQueue.has_frame` so the check is O(distinct Delegates to :meth:`FrameQueue.has_frame` so the check is O(distinct
@@ -951,7 +947,7 @@ class FrameProcessor(BaseObject):
self.__process_frame_task = None self.__process_frame_task = None
async def __process_frame( async def __process_frame(
self, frame: Frame, direction: FrameDirection, callback: Optional[FrameCallback] self, frame: Frame, direction: FrameDirection, callback: FrameCallback | None
): ):
try: try:
await self._call_event_handler("on_before_process_frame", frame) await self._call_event_handler("on_before_process_frame", frame)

View File

@@ -6,8 +6,6 @@
"""Langchain integration processor for Pipecat.""" """Langchain integration processor for Pipecat."""
from typing import Optional, Union
from loguru import logger from loguru import logger
from pipecat.frames.frames import ( from pipecat.frames.frames import (
@@ -45,7 +43,7 @@ class LangchainProcessor(FrameProcessor):
super().__init__() super().__init__()
self._chain = chain self._chain = chain
self._transcript_key = transcript_key self._transcript_key = transcript_key
self._participant_id: Optional[str] = None self._participant_id: str | None = None
def set_participant_id(self, participant_id: str): def set_participant_id(self, participant_id: str):
"""Set the participant ID for session tracking. """Set the participant ID for session tracking.
@@ -76,7 +74,7 @@ class LangchainProcessor(FrameProcessor):
await self.push_frame(frame, direction) await self.push_frame(frame, direction)
@staticmethod @staticmethod
def __get_token_value(text: Union[str, AIMessageChunk]) -> str: def __get_token_value(text: str | AIMessageChunk) -> str:
"""Extract token value from various text types. """Extract token value from various text types.
Args: Args:

View File

@@ -7,7 +7,7 @@
"""RTVI pipeline frame definitions.""" """RTVI pipeline frame definitions."""
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Optional from typing import Any
from pipecat.frames.frames import SystemFrame from pipecat.frames.frames import SystemFrame
@@ -37,7 +37,7 @@ class RTVIClientMessageFrame(SystemFrame):
msg_id: str msg_id: str
type: str type: str
data: Optional[Any] = None data: Any | None = None
@dataclass @dataclass
@@ -53,5 +53,5 @@ class RTVIServerResponseFrame(SystemFrame):
""" """
client_msg: RTVIClientMessageFrame client_msg: RTVIClientMessageFrame
data: Optional[Any] = None data: Any | None = None
error: Optional[str] = None error: str | None = None

View File

@@ -14,12 +14,10 @@ Import this module under the ``RTVI`` alias to use as a namespace::
msg = RTVI.BotReady(id="1", data=RTVI.BotReadyData(version=RTVI.PROTOCOL_VERSION)) msg = RTVI.BotReady(id="1", data=RTVI.BotReadyData(version=RTVI.PROTOCOL_VERSION))
""" """
from collections.abc import Mapping
from typing import ( from typing import (
Any, Any,
Dict,
Literal, Literal,
Mapping,
Optional,
) )
from pydantic import BaseModel from pydantic import BaseModel
@@ -46,7 +44,7 @@ class Message(BaseModel):
label: MessageLiteral = MESSAGE_LABEL label: MessageLiteral = MESSAGE_LABEL
type: str type: str
id: str id: str
data: Optional[Dict[str, Any]] = None data: dict[str, Any] | None = None
# -- Client -> Pipecat messages. # -- Client -> Pipecat messages.
@@ -56,7 +54,7 @@ class RawClientMessageData(BaseModel):
"""Data structure expected from client messages sent to the RTVI server.""" """Data structure expected from client messages sent to the RTVI server."""
t: str t: str
d: Optional[Any] = None d: Any | None = None
class ClientMessage(BaseModel): class ClientMessage(BaseModel):
@@ -64,14 +62,14 @@ class ClientMessage(BaseModel):
msg_id: str msg_id: str
type: str type: str
data: Optional[Any] = None data: Any | None = None
class RawServerResponseData(BaseModel): class RawServerResponseData(BaseModel):
"""Data structure for server responses to client messages.""" """Data structure for server responses to client messages."""
t: str t: str
d: Optional[Any] = None d: Any | None = None
class ServerResponse(BaseModel): class ServerResponse(BaseModel):
@@ -94,10 +92,10 @@ class AboutClientData(BaseModel):
""" """
library: str library: str
library_version: Optional[str] = None library_version: str | None = None
platform: Optional[str] = None platform: str | None = None
platform_version: Optional[str] = None platform_version: str | None = None
platform_details: Optional[Any] = None platform_details: Any | None = None
class ClientReadyData(BaseModel): class ClientReadyData(BaseModel):
@@ -165,7 +163,7 @@ class BotReadyData(BaseModel):
""" """
version: str version: str
about: Optional[Mapping[str, Any]] = None about: Mapping[str, Any] | None = None
class BotReady(BaseModel): class BotReady(BaseModel):
@@ -226,7 +224,7 @@ class SendTextData(BaseModel):
""" """
content: str content: str
options: Optional[SendTextOptions] = None options: SendTextOptions | None = None
class LLMFunctionCallStartMessageData(BaseModel): class LLMFunctionCallStartMessageData(BaseModel):
@@ -236,7 +234,7 @@ class LLMFunctionCallStartMessageData(BaseModel):
the configured function_call_report_level for security. the configured function_call_report_level for security.
""" """
function_name: Optional[str] = None function_name: str | None = None
class LLMFunctionCallStartMessage(BaseModel): class LLMFunctionCallStartMessage(BaseModel):
@@ -270,8 +268,8 @@ class LLMFunctionCallInProgressMessageData(BaseModel):
""" """
tool_call_id: str tool_call_id: str
function_name: Optional[str] = None function_name: str | None = None
arguments: Optional[Mapping[str, Any]] = None arguments: Mapping[str, Any] | None = None
class LLMFunctionCallInProgressMessage(BaseModel): class LLMFunctionCallInProgressMessage(BaseModel):
@@ -295,8 +293,8 @@ class LLMFunctionCallStoppedMessageData(BaseModel):
tool_call_id: str tool_call_id: str
cancelled: bool cancelled: bool
function_name: Optional[str] = None function_name: str | None = None
result: Optional[Any] = None result: Any | None = None
class LLMFunctionCallStoppedMessage(BaseModel): class LLMFunctionCallStoppedMessage(BaseModel):

View File

@@ -7,17 +7,12 @@
"""RTVI observer for converting pipeline frames to outgoing RTVI messages.""" """RTVI observer for converting pipeline frames to outgoing RTVI messages."""
import time import time
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum, StrEnum
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Awaitable,
Callable,
Dict,
List,
Optional, Optional,
Set,
Tuple,
) )
from loguru import logger from loguru import logger
@@ -71,7 +66,7 @@ if TYPE_CHECKING:
from pipecat.processors.frameworks.rtvi.processor import RTVIProcessor from pipecat.processors.frameworks.rtvi.processor import RTVIProcessor
class RTVIFunctionCallReportLevel(str, Enum): class RTVIFunctionCallReportLevel(StrEnum):
"""Level of detail to include in function call RTVI events. """Level of detail to include in function call RTVI events.
Controls what information is exposed in function call events for security. Controls what information is exposed in function call events for security.
@@ -148,18 +143,14 @@ class RTVIObserverParams:
user_audio_level_enabled: bool = False user_audio_level_enabled: bool = False
metrics_enabled: bool = True metrics_enabled: bool = True
system_logs_enabled: bool = False system_logs_enabled: bool = False
ignored_sources: List[FrameProcessor] = field(default_factory=list) ignored_sources: list[FrameProcessor] = field(default_factory=list)
skip_aggregator_types: Optional[List[AggregationType | str]] = None skip_aggregator_types: list[AggregationType | str] | None = None
bot_output_transforms: Optional[ bot_output_transforms: (
List[ list[tuple[AggregationType | str, Callable[[str, AggregationType | str], Awaitable[str]]]]
Tuple[ | None
AggregationType | str, ) = None
Callable[[str, AggregationType | str], Awaitable[str]],
]
]
] = None
audio_level_period_secs: float = 0.15 audio_level_period_secs: float = 0.15
function_call_report_level: Dict[str, RTVIFunctionCallReportLevel] = field( function_call_report_level: dict[str, RTVIFunctionCallReportLevel] = field(
default_factory=lambda: {"*": RTVIFunctionCallReportLevel.NONE} default_factory=lambda: {"*": RTVIFunctionCallReportLevel.NONE}
) )
@@ -180,7 +171,7 @@ class RTVIObserver(BaseObserver):
self, self,
rtvi: Optional["RTVIProcessor"] = None, rtvi: Optional["RTVIProcessor"] = None,
*, *,
params: Optional[RTVIObserverParams] = None, params: RTVIObserverParams | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the RTVI observer. """Initialize the RTVI observer.
@@ -194,7 +185,7 @@ class RTVIObserver(BaseObserver):
self._rtvi = rtvi self._rtvi = rtvi
self._params = params or RTVIObserverParams() self._params = params or RTVIObserverParams()
self._ignored_sources: Set[FrameProcessor] = set(self._params.ignored_sources) self._ignored_sources: set[FrameProcessor] = set(self._params.ignored_sources)
self._frames_seen = set() self._frames_seen = set()
self._bot_transcription = "" self._bot_transcription = ""
@@ -203,13 +194,13 @@ class RTVIObserver(BaseObserver):
# Track bot speaking state for queuing aggregated text frames # Track bot speaking state for queuing aggregated text frames
self._bot_is_speaking = False self._bot_is_speaking = False
self._queued_aggregated_text_frames: List[AggregatedTextFrame] = [] self._queued_aggregated_text_frames: list[AggregatedTextFrame] = []
if self._params.system_logs_enabled: if self._params.system_logs_enabled:
self._system_logger_id = logger.add(self._logger_sink) self._system_logger_id = logger.add(self._logger_sink)
self._aggregation_transforms: List[ self._aggregation_transforms: list[
Tuple[AggregationType | str, Callable[[str, AggregationType | str], Awaitable[str]]] tuple[AggregationType | str, Callable[[str, AggregationType | str], Awaitable[str]]]
] = self._params.bot_output_transforms or [] ] = self._params.bot_output_transforms or []
def add_bot_output_transformer( def add_bot_output_transformer(

View File

@@ -8,7 +8,8 @@
import asyncio import asyncio
import base64 import base64
from typing import Any, Mapping, Optional from collections.abc import Mapping
from typing import Any
from loguru import logger from loguru import logger
from pydantic import BaseModel, ValidationError from pydantic import BaseModel, ValidationError
@@ -51,7 +52,7 @@ class RTVIProcessor(FrameProcessor):
def __init__( def __init__(
self, self,
*, *,
transport: Optional[BaseTransport] = None, transport: BaseTransport | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the RTVI processor. """Initialize the RTVI processor.
@@ -70,7 +71,7 @@ class RTVIProcessor(FrameProcessor):
self._llm_skip_tts: bool = False # Keep in sync with llm_service.py's configuration. self._llm_skip_tts: bool = False # Keep in sync with llm_service.py's configuration.
# A task to process incoming transport messages. # A task to process incoming transport messages.
self._message_task: Optional[asyncio.Task] = None self._message_task: asyncio.Task | None = None
self._register_event_handler("on_bot_started") self._register_event_handler("on_bot_started")
self._register_event_handler("on_client_ready") self._register_event_handler("on_client_ready")
@@ -84,7 +85,7 @@ class RTVIProcessor(FrameProcessor):
self._input_transport = input_transport self._input_transport = input_transport
self._input_transport.enable_audio_in_stream_on_start(False) self._input_transport.enable_audio_in_stream_on_start(False)
def create_rtvi_observer(self, *, params: Optional[RTVIObserverParams] = None, **kwargs): def create_rtvi_observer(self, *, params: RTVIObserverParams | None = None, **kwargs):
"""Creates a new RTVI Observer. """Creates a new RTVI Observer.
Args: Args:

View File

@@ -4,8 +4,6 @@ This module provides integration with Strands Agents for handling conversational
interactions. It supports both single agent and multi-agent graphs. interactions. It supports both single agent and multi-agent graphs.
""" """
from typing import Optional
from loguru import logger from loguru import logger
from pipecat.frames.frames import ( from pipecat.frames.frames import (
@@ -38,9 +36,9 @@ class StrandsAgentsProcessor(FrameProcessor):
def __init__( def __init__(
self, self,
agent: Optional[Agent] = None, agent: Agent | None = None,
graph: Optional[Graph] = None, graph: Graph | None = None,
graph_exit_node: Optional[str] = None, graph_exit_node: str | None = None,
): ):
"""Initialize the Strands Agents processor. """Initialize the Strands Agents processor.

View File

@@ -7,7 +7,6 @@
"""GStreamer pipeline source integration for Pipecat.""" """GStreamer pipeline source integration for Pipecat."""
import asyncio import asyncio
from typing import Optional
from loguru import logger from loguru import logger
from pydantic import BaseModel from pydantic import BaseModel
@@ -58,11 +57,11 @@ class GStreamerPipelineSource(FrameProcessor):
video_width: int = 1280 video_width: int = 1280
video_height: int = 720 video_height: int = 720
audio_sample_rate: Optional[int] = None audio_sample_rate: int | None = None
audio_channels: int = 1 audio_channels: int = 1
clock_sync: bool = True clock_sync: bool = True
def __init__(self, *, pipeline: str, out_params: Optional[OutputParams] = None, **kwargs): def __init__(self, *, pipeline: str, out_params: OutputParams | None = None, **kwargs):
"""Initialize the GStreamer pipeline source. """Initialize the GStreamer pipeline source.
Args: Args:

View File

@@ -7,7 +7,7 @@
"""Idle frame processor for timeout-based callback execution.""" """Idle frame processor for timeout-based callback execution."""
import asyncio import asyncio
from typing import Awaitable, Callable, List, Optional from collections.abc import Awaitable, Callable
from pipecat.frames.frames import Frame, StartFrame from pipecat.frames.frames import Frame, StartFrame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@@ -26,7 +26,7 @@ class IdleFrameProcessor(FrameProcessor):
*, *,
callback: Callable[["IdleFrameProcessor"], Awaitable[None]], callback: Callable[["IdleFrameProcessor"], Awaitable[None]],
timeout: float, timeout: float,
types: Optional[List[type]] = None, types: list[type] | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the idle frame processor. """Initialize the idle frame processor.
@@ -86,5 +86,5 @@ class IdleFrameProcessor(FrameProcessor):
try: try:
await asyncio.wait_for(self._idle_event.wait(), timeout=self._timeout) await asyncio.wait_for(self._idle_event.wait(), timeout=self._timeout)
self._idle_event.clear() self._idle_event.clear()
except asyncio.TimeoutError: except TimeoutError:
await self._callback(self) await self._callback(self)

View File

@@ -6,8 +6,6 @@
"""Frame logging utilities for debugging and monitoring frame flow in Pipecat pipelines.""" """Frame logging utilities for debugging and monitoring frame flow in Pipecat pipelines."""
from typing import Optional, Tuple, Type
from loguru import logger from loguru import logger
from pipecat.frames.frames import ( from pipecat.frames.frames import (
@@ -33,8 +31,8 @@ class FrameLogger(FrameProcessor):
def __init__( def __init__(
self, self,
prefix="Frame", prefix="Frame",
color: Optional[str] = None, color: str | None = None,
ignored_frame_types: Tuple[Type[Frame], ...] = ( ignored_frame_types: tuple[type[Frame], ...] = (
BotSpeakingFrame, BotSpeakingFrame,
UserSpeakingFrame, UserSpeakingFrame,
InputAudioRawFrame, InputAudioRawFrame,

View File

@@ -7,7 +7,6 @@
"""Frame processor metrics collection and reporting.""" """Frame processor metrics collection and reporting."""
import time import time
from typing import Optional
from loguru import logger from loguru import logger
@@ -70,7 +69,7 @@ class FrameProcessorMetrics(BaseObject):
return self._task_manager return self._task_manager
@property @property
def ttfb(self) -> Optional[float]: def ttfb(self) -> float | None:
"""Get the current TTFB value in seconds. """Get the current TTFB value in seconds.
Returns: Returns:
@@ -110,7 +109,7 @@ class FrameProcessorMetrics(BaseObject):
self._core_metrics_data = MetricsData(processor=name) self._core_metrics_data = MetricsData(processor=name)
async def start_ttfb_metrics( async def start_ttfb_metrics(
self, *, start_time: Optional[float] = None, report_only_initial_ttfb: bool self, *, start_time: float | None = None, report_only_initial_ttfb: bool
): ):
"""Start measuring time-to-first-byte (TTFB). """Start measuring time-to-first-byte (TTFB).
@@ -124,7 +123,7 @@ class FrameProcessorMetrics(BaseObject):
self._last_ttfb_time = 0 self._last_ttfb_time = 0
self._should_report_ttfb = not report_only_initial_ttfb self._should_report_ttfb = not report_only_initial_ttfb
async def stop_ttfb_metrics(self, *, end_time: Optional[float] = None): async def stop_ttfb_metrics(self, *, end_time: float | None = None):
"""Stop TTFB measurement and generate metrics frame. """Stop TTFB measurement and generate metrics frame.
Args: Args:
@@ -147,7 +146,7 @@ class FrameProcessorMetrics(BaseObject):
self._start_ttfb_time = 0 self._start_ttfb_time = 0
return MetricsFrame(data=[ttfb]) return MetricsFrame(data=[ttfb])
async def start_processing_metrics(self, *, start_time: Optional[float] = None): async def start_processing_metrics(self, *, start_time: float | None = None):
"""Start measuring processing time. """Start measuring processing time.
Args: Args:
@@ -156,7 +155,7 @@ class FrameProcessorMetrics(BaseObject):
""" """
self._start_processing_time = start_time or time.time() self._start_processing_time = start_time or time.time()
async def stop_processing_metrics(self, *, end_time: Optional[float] = None): async def stop_processing_metrics(self, *, end_time: float | None = None):
"""Stop processing time measurement and generate metrics frame. """Stop processing time measurement and generate metrics frame.
Args: Args:

View File

@@ -7,7 +7,6 @@
"""Sentry integration for frame processor metrics.""" """Sentry integration for frame processor metrics."""
import asyncio import asyncio
from typing import Optional
from loguru import logger from loguru import logger
@@ -72,7 +71,7 @@ class SentryMetrics(FrameProcessorMetrics):
sentry_sdk.flush(timeout=5.0) sentry_sdk.flush(timeout=5.0)
async def start_ttfb_metrics( async def start_ttfb_metrics(
self, *, start_time: Optional[float] = None, report_only_initial_ttfb: bool self, *, start_time: float | None = None, report_only_initial_ttfb: bool
): ):
"""Start tracking time-to-first-byte metrics. """Start tracking time-to-first-byte metrics.
@@ -93,7 +92,7 @@ class SentryMetrics(FrameProcessorMetrics):
f"{self} Sentry transaction started (ID: {self._ttfb_metrics_tx.span_id} Name: {self._ttfb_metrics_tx.name})" f"{self} Sentry transaction started (ID: {self._ttfb_metrics_tx.span_id} Name: {self._ttfb_metrics_tx.name})"
) )
async def stop_ttfb_metrics(self, *, end_time: Optional[float] = None): async def stop_ttfb_metrics(self, *, end_time: float | None = None):
"""Stop tracking time-to-first-byte metrics. """Stop tracking time-to-first-byte metrics.
Args: Args:
@@ -105,7 +104,7 @@ class SentryMetrics(FrameProcessorMetrics):
await self._sentry_queue.put(self._ttfb_metrics_tx) await self._sentry_queue.put(self._ttfb_metrics_tx)
self._ttfb_metrics_tx = None self._ttfb_metrics_tx = None
async def start_processing_metrics(self, *, start_time: Optional[float] = None): async def start_processing_metrics(self, *, start_time: float | None = None):
"""Start tracking frame processing metrics. """Start tracking frame processing metrics.
Args: Args:
@@ -122,7 +121,7 @@ class SentryMetrics(FrameProcessorMetrics):
f"{self} Sentry transaction started (ID: {self._processing_metrics_tx.span_id} Name: {self._processing_metrics_tx.name})" f"{self} Sentry transaction started (ID: {self._processing_metrics_tx.span_id} Name: {self._processing_metrics_tx.name})"
) )
async def stop_processing_metrics(self, *, end_time: Optional[float] = None): async def stop_processing_metrics(self, *, end_time: float | None = None):
"""Stop tracking frame processing metrics. """Stop tracking frame processing metrics.
Args: Args:

View File

@@ -7,7 +7,7 @@
"""Producer processor for frame filtering and distribution.""" """Producer processor for frame filtering and distribution."""
import asyncio import asyncio
from typing import Awaitable, Callable, List from collections.abc import Awaitable, Callable
from pipecat.frames.frames import Frame from pipecat.frames.frames import Frame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@@ -55,7 +55,7 @@ class ProducerProcessor(FrameProcessor):
self._filter = filter self._filter = filter
self._transformer = transformer self._transformer = transformer
self._passthrough = passthrough self._passthrough = passthrough
self._consumers: List[asyncio.Queue] = [] self._consumers: list[asyncio.Queue] = []
def add_consumer(self): def add_consumer(self):
"""Add a new consumer and return its associated queue. """Add a new consumer and return its associated queue.

View File

@@ -6,7 +6,7 @@
"""Stateless text transformation processor for Pipecat.""" """Stateless text transformation processor for Pipecat."""
from typing import Callable, Coroutine, Union from collections.abc import Callable, Coroutine
from pipecat.frames.frames import Frame, TextFrame from pipecat.frames.frames import Frame, TextFrame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@@ -21,7 +21,7 @@ class StatelessTextTransformer(FrameProcessor):
""" """
def __init__( def __init__(
self, transform_fn: Union[Callable[[str], str], Callable[[str], Coroutine[None, None, str]]] self, transform_fn: Callable[[str], str] | Callable[[str], Coroutine[None, None, str]]
): ):
"""Initialize the text transformer. """Initialize the text transformer.

View File

@@ -37,7 +37,6 @@ Example::
import os import os
import time import time
import uuid import uuid
from typing import Dict, List, Optional
import aiohttp import aiohttp
from loguru import logger from loguru import logger
@@ -64,7 +63,7 @@ class DailyRoomConfig(BaseModel):
room_url: str room_url: str
token: str token: str
sip_endpoint: Optional[str] = None sip_endpoint: str | None = None
def __iter__(self): def __iter__(self):
"""Enable tuple unpacking for backward compatibility. """Enable tuple unpacking for backward compatibility.
@@ -78,18 +77,18 @@ class DailyRoomConfig(BaseModel):
async def configure( async def configure(
aiohttp_session: aiohttp.ClientSession, aiohttp_session: aiohttp.ClientSession,
*, *,
api_key: Optional[str] = None, api_key: str | None = None,
room_exp_duration: float = 2.0, room_exp_duration: float = 2.0,
token_exp_duration: float = 2.0, token_exp_duration: float = 2.0,
sip_caller_phone: Optional[str] = None, sip_caller_phone: str | None = None,
sip_enable_video: bool = False, sip_enable_video: bool = False,
sip_num_endpoints: int = 1, sip_num_endpoints: int = 1,
enable_dialout: bool = False, enable_dialout: bool = False,
sip_codecs: Optional[Dict[str, List[str]]] = None, sip_codecs: dict[str, list[str]] | None = None,
sip_provider: Optional[str] = None, sip_provider: str | None = None,
room_geo: Optional[str] = None, room_geo: str | None = None,
room_properties: Optional[DailyRoomProperties] = None, room_properties: DailyRoomProperties | None = None,
token_properties: Optional[DailyMeetingTokenProperties] = None, token_properties: DailyMeetingTokenProperties | None = None,
) -> DailyRoomConfig: ) -> DailyRoomConfig:
"""Configure Daily room URL and token with optional SIP capabilities. """Configure Daily room URL and token with optional SIP capabilities.

View File

@@ -30,7 +30,6 @@ Example::
import argparse import argparse
import os import os
from typing import Optional
from livekit import api from livekit import api
from loguru import logger from loguru import logger
@@ -98,7 +97,7 @@ async def configure():
return (url, token, room_name) return (url, token, room_name)
async def configure_with_args(parser: Optional[argparse.ArgumentParser] = None): async def configure_with_args(parser: argparse.ArgumentParser | None = None):
"""Configure LiveKit room with command-line argument parsing. """Configure LiveKit room with command-line argument parsing.
Args: Args:

View File

@@ -74,7 +74,7 @@ import uuid
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from http import HTTPMethod from http import HTTPMethod
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, TypedDict, Union from typing import Any, TypedDict
import aiohttp import aiohttp
from fastapi.responses import FileResponse, Response from fastapi.responses import FileResponse, Response
@@ -106,7 +106,7 @@ os.environ["ENV"] = "local"
TELEPHONY_TRANSPORTS = ["twilio", "telnyx", "plivo", "exotel"] TELEPHONY_TRANSPORTS = ["twilio", "telnyx", "plivo", "exotel"]
RUNNER_DOWNLOADS_FOLDER: Optional[str] = None RUNNER_DOWNLOADS_FOLDER: str | None = None
RUNNER_HOST: str = "localhost" RUNNER_HOST: str = "localhost"
RUNNER_PORT: int = 7860 RUNNER_PORT: int = 7860
@@ -220,17 +220,17 @@ def _setup_webrtc_routes(app: FastAPI, args: argparse.Namespace):
return return
class IceServer(TypedDict, total=False): class IceServer(TypedDict, total=False):
urls: Union[str, List[str]] urls: str | list[str]
class IceConfig(TypedDict): class IceConfig(TypedDict):
iceServers: List[IceServer] iceServers: list[IceServer]
class StartBotResult(TypedDict, total=False): class StartBotResult(TypedDict, total=False):
sessionId: str sessionId: str
iceConfig: Optional[IceConfig] iceConfig: IceConfig | None
# In-memory store of active sessions: session_id -> session info # In-memory store of active sessions: session_id -> session info
active_sessions: Dict[str, Dict[str, Any]] = {} active_sessions: dict[str, dict[str, Any]] = {}
# Mount the frontend # Mount the frontend
app.mount("/client", SmallWebRTCPrebuiltUI) app.mount("/client", SmallWebRTCPrebuiltUI)
@@ -418,7 +418,7 @@ def _setup_whatsapp_routes(app: FastAPI, args: argparse.Namespace):
return return
# Global WhatsApp client instance # Global WhatsApp client instance
whatsapp_client: Optional[WhatsAppClient] = None whatsapp_client: WhatsAppClient | None = None
@app.get( @app.get(
"/whatsapp", "/whatsapp",
@@ -857,7 +857,7 @@ def _validate_and_clean_proxy(proxy: str) -> str:
return proxy return proxy
def runner_downloads_folder() -> Optional[str]: def runner_downloads_folder() -> str | None:
"""Returns the folder where files are stored for later download.""" """Returns the folder where files are stored for later download."""
return RUNNER_DOWNLOADS_FOLDER return RUNNER_DOWNLOADS_FOLDER
@@ -872,7 +872,7 @@ def runner_port() -> int:
return RUNNER_PORT return RUNNER_PORT
def main(parser: Optional[argparse.ArgumentParser] = None): def main(parser: argparse.ArgumentParser | None = None):
"""Start the Pipecat development runner. """Start the Pipecat development runner.
Parses command-line arguments and starts a FastAPI server configured Parses command-line arguments and starts a FastAPI server configured

View File

@@ -12,7 +12,7 @@ information to bot functions.
import argparse import argparse
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any, Dict, Optional from typing import Any
from fastapi import WebSocket from fastapi import WebSocket
from pydantic import BaseModel from pydantic import BaseModel
@@ -34,9 +34,9 @@ class DialinSettings(BaseModel):
call_id: str call_id: str
call_domain: str call_domain: str
To: Optional[str] = None To: str | None = None
From: Optional[str] = None From: str | None = None
sip_headers: Optional[Dict[str, str]] = None sip_headers: dict[str, str] | None = None
class DailyDialinRequest(BaseModel): class DailyDialinRequest(BaseModel):
@@ -64,8 +64,8 @@ class RunnerArguments:
handle_sigint: bool = field(init=False, kw_only=True) handle_sigint: bool = field(init=False, kw_only=True)
handle_sigterm: bool = field(init=False, kw_only=True) handle_sigterm: bool = field(init=False, kw_only=True)
pipeline_idle_timeout_secs: int = field(init=False, kw_only=True) pipeline_idle_timeout_secs: int = field(init=False, kw_only=True)
body: Optional[Any] = field(default_factory=dict, kw_only=True) body: Any | None = field(default_factory=dict, kw_only=True)
cli_args: Optional[argparse.Namespace] = field(default=None, init=False, kw_only=True) cli_args: argparse.Namespace | None = field(default=None, init=False, kw_only=True)
def __post_init__(self): def __post_init__(self):
self.handle_sigint = False self.handle_sigint = False
@@ -84,7 +84,7 @@ class DailyRunnerArguments(RunnerArguments):
""" """
room_url: str room_url: str
token: Optional[str] = None token: str | None = None
@dataclass @dataclass
@@ -122,4 +122,4 @@ class LiveKitRunnerArguments(RunnerArguments):
room_name: str room_name: str
url: str url: str
token: Optional[str] = None token: str | None = None

View File

@@ -32,7 +32,8 @@ Example::
import json import json
import os import os
import re import re
from typing import Any, Callable, Dict, Optional from collections.abc import Callable
from typing import Any
from fastapi import WebSocket from fastapi import WebSocket
from loguru import logger from loguru import logger
@@ -373,7 +374,7 @@ def _smallwebrtc_sdp_cleanup_fingerprints(text: str) -> str:
return "\r\n".join(result) + "\r\n" return "\r\n".join(result) + "\r\n"
def smallwebrtc_sdp_munging(sdp: str, host: Optional[str]) -> str: def smallwebrtc_sdp_munging(sdp: str, host: str | None) -> str:
"""Apply SDP modifications for SmallWebRTC compatibility. """Apply SDP modifications for SmallWebRTC compatibility.
Args: Args:
@@ -389,7 +390,7 @@ def smallwebrtc_sdp_munging(sdp: str, host: Optional[str]) -> str:
return sdp return sdp
def _get_transport_params(transport_key: str, transport_params: Dict[str, Callable]) -> Any: def _get_transport_params(transport_key: str, transport_params: dict[str, Callable]) -> Any:
"""Get transport parameters from factory function. """Get transport parameters from factory function.
Args: Args:
@@ -415,7 +416,7 @@ def _get_transport_params(transport_key: str, transport_params: Dict[str, Callab
async def _create_telephony_transport( async def _create_telephony_transport(
websocket: WebSocket, websocket: WebSocket,
params: Optional[Any] = None, params: Any | None = None,
transport_type: str = None, transport_type: str = None,
call_data: dict = None, call_data: dict = None,
) -> BaseTransport: ) -> BaseTransport:
@@ -488,7 +489,7 @@ async def _create_telephony_transport(
async def create_transport( async def create_transport(
runner_args: Any, transport_params: Dict[str, Callable] runner_args: Any, transport_params: dict[str, Callable]
) -> BaseTransport: ) -> BaseTransport:
"""Create a transport from runner arguments using factory functions. """Create a transport from runner arguments using factory functions.

View File

@@ -7,7 +7,6 @@
"""Frame serialization interfaces for Pipecat.""" """Frame serialization interfaces for Pipecat."""
from abc import abstractmethod from abc import abstractmethod
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
@@ -39,7 +38,7 @@ class FrameSerializer(BaseObject):
ignore_rtvi_messages: bool = True ignore_rtvi_messages: bool = True
def __init__(self, params: Optional[InputParams] = None, **kwargs): def __init__(self, params: InputParams | None = None, **kwargs):
"""Initialize the FrameSerializer. """Initialize the FrameSerializer.
Args: Args:

View File

@@ -8,7 +8,6 @@
import base64 import base64
import json import json
from typing import Optional
from loguru import logger from loguru import logger
@@ -48,10 +47,10 @@ class ExotelFrameSerializer(FrameSerializer):
""" """
exotel_sample_rate: int = 8000 exotel_sample_rate: int = 8000
sample_rate: Optional[int] = None sample_rate: int | None = None
def __init__( def __init__(
self, stream_sid: str, call_sid: Optional[str] = None, params: Optional[InputParams] = None self, stream_sid: str, call_sid: str | None = None, params: InputParams | None = None
): ):
"""Initialize the ExotelFrameSerializer. """Initialize the ExotelFrameSerializer.

View File

@@ -23,8 +23,8 @@ Audio Format:
import json import json
import uuid import uuid
from datetime import timedelta from datetime import timedelta
from enum import Enum from enum import StrEnum
from typing import Any, Dict, List, Optional from typing import Any
from loguru import logger from loguru import logger
@@ -46,7 +46,7 @@ from pipecat.frames.frames import (
from pipecat.serializers.base_serializer import FrameSerializer from pipecat.serializers.base_serializer import FrameSerializer
class AudioHookMessageType(str, Enum): class AudioHookMessageType(StrEnum):
"""AudioHook protocol message types.""" """AudioHook protocol message types."""
OPEN = "open" OPEN = "open"
@@ -63,7 +63,7 @@ class AudioHookMessageType(str, Enum):
DISCONNECT = "disconnect" DISCONNECT = "disconnect"
class AudioHookChannel(str, Enum): class AudioHookChannel(StrEnum):
"""AudioHook audio channel configuration.""" """AudioHook audio channel configuration."""
EXTERNAL = "external" # Customer audio only (mono) EXTERNAL = "external" # Customer audio only (mono)
@@ -71,7 +71,7 @@ class AudioHookChannel(str, Enum):
BOTH = "both" # Stereo: external=left, internal=right BOTH = "both" # Stereo: external=left, internal=right
class AudioHookMediaFormat(str, Enum): class AudioHookMediaFormat(StrEnum):
"""Supported audio formats.""" """Supported audio formats."""
PCMU = "PCMU" # μ-law, 8kHz PCMU = "PCMU" # μ-law, 8kHz
@@ -146,18 +146,18 @@ class GenesysAudioHookSerializer(FrameSerializer):
""" """
genesys_sample_rate: int = 8000 genesys_sample_rate: int = 8000
sample_rate: Optional[int] = None sample_rate: int | None = None
channel: AudioHookChannel = AudioHookChannel.EXTERNAL channel: AudioHookChannel = AudioHookChannel.EXTERNAL
media_format: AudioHookMediaFormat = AudioHookMediaFormat.PCMU media_format: AudioHookMediaFormat = AudioHookMediaFormat.PCMU
process_external: bool = True process_external: bool = True
process_internal: bool = False process_internal: bool = False
supported_languages: Optional[List[str]] = None supported_languages: list[str] | None = None
selected_language: Optional[str] = None selected_language: str | None = None
start_paused: bool = False start_paused: bool = False
def __init__( def __init__(
self, self,
params: Optional[InputParams] = None, params: InputParams | None = None,
**kwargs, **kwargs,
): ):
"""Initialize the GenesysAudioHookSerializer. """Initialize the GenesysAudioHookSerializer.
@@ -185,12 +185,12 @@ class GenesysAudioHookSerializer(FrameSerializer):
self._position = timedelta(0) self._position = timedelta(0)
# Session metadata # Session metadata
self._conversation_id: Optional[str] = None self._conversation_id: str | None = None
self._participant: Optional[Dict[str, Any]] = None self._participant: dict[str, Any] | None = None
self._custom_config: Optional[Dict[str, Any]] = None self._custom_config: dict[str, Any] | None = None
self._media_info: Optional[List[Dict[str, Any]]] = None self._media_info: list[dict[str, Any]] | None = None
self._input_variables: Optional[Dict[str, Any]] = None # Custom input from Genesys self._input_variables: dict[str, Any] | None = None # Custom input from Genesys
self._output_variables: Optional[Dict[str, Any]] = None # Custom output to Genesys self._output_variables: dict[str, Any] | None = None # Custom output to Genesys
# Event handlers # Event handlers
self._register_event_handler("on_open") self._register_event_handler("on_open")
@@ -207,7 +207,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
return self._session_id return self._session_id
@property @property
def conversation_id(self) -> Optional[str]: def conversation_id(self) -> str | None:
"""Get the Genesys conversation ID.""" """Get the Genesys conversation ID."""
return self._conversation_id return self._conversation_id
@@ -222,21 +222,21 @@ class GenesysAudioHookSerializer(FrameSerializer):
return self._is_paused return self._is_paused
@property @property
def participant(self) -> Optional[Dict[str, Any]]: def participant(self) -> dict[str, Any] | None:
"""Get participant info (ani, dnis, etc.) from the open message.""" """Get participant info (ani, dnis, etc.) from the open message."""
return self._participant return self._participant
@property @property
def input_variables(self) -> Optional[Dict[str, Any]]: def input_variables(self) -> dict[str, Any] | None:
"""Get custom input variables from the open message.""" """Get custom input variables from the open message."""
return self._input_variables return self._input_variables
@property @property
def output_variables(self) -> Optional[Dict[str, Any]]: def output_variables(self) -> dict[str, Any] | None:
"""Get custom output variables to send back to Genesys.""" """Get custom output variables to send back to Genesys."""
return self._output_variables return self._output_variables
def set_output_variables(self, variables: Dict[str, Any]) -> None: def set_output_variables(self, variables: dict[str, Any]) -> None:
"""Set custom output variables to send back to Genesys on close. """Set custom output variables to send back to Genesys on close.
These variables will be included in the 'closed' response when Genesys These variables will be included in the 'closed' response when Genesys
@@ -305,9 +305,9 @@ class GenesysAudioHookSerializer(FrameSerializer):
def _create_message( def _create_message(
self, self,
msg_type: AudioHookMessageType, msg_type: AudioHookMessageType,
parameters: Optional[Dict[str, Any]] = None, parameters: dict[str, Any] | None = None,
include_position: bool = True, include_position: bool = True,
) -> Dict[str, Any]: ) -> dict[str, Any]:
"""Create a protocol message with common fields. """Create a protocol message with common fields.
Based on the Genesys AudioHook protocol, responses include: Based on the Genesys AudioHook protocol, responses include:
@@ -341,9 +341,9 @@ class GenesysAudioHookSerializer(FrameSerializer):
def create_opened_response( def create_opened_response(
self, self,
start_paused: bool = False, start_paused: bool = False,
supported_languages: Optional[List[str]] = None, supported_languages: list[str] | None = None,
selected_language: Optional[str] = None, selected_language: str | None = None,
) -> Dict[str, Any]: ) -> dict[str, Any]:
"""Create an 'opened' response message for the client. """Create an 'opened' response message for the client.
This should be sent in response to an 'open' message from Genesys. This should be sent in response to an 'open' message from Genesys.
@@ -397,8 +397,8 @@ class GenesysAudioHookSerializer(FrameSerializer):
def create_closed_response( def create_closed_response(
self, self,
output_variables: Optional[Dict[str, Any]] = None, output_variables: dict[str, Any] | None = None,
) -> Dict[str, Any]: ) -> dict[str, Any]:
"""Create a 'closed' response message. """Create a 'closed' response message.
This should be sent in response to a 'close' message from Genesys. This should be sent in response to a 'close' message from Genesys.
@@ -422,7 +422,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
} }
) )
""" """
parameters: Optional[Dict[str, Any]] = None parameters: dict[str, Any] | None = None
if output_variables: if output_variables:
parameters = {"outputVariables": output_variables} parameters = {"outputVariables": output_variables}
@@ -437,7 +437,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
return msg return msg
def create_pong_response(self) -> Dict[str, Any]: def create_pong_response(self) -> dict[str, Any]:
"""Create a 'pong' response message. """Create a 'pong' response message.
This should be sent in response to a 'ping' message from Genesys. This should be sent in response to a 'ping' message from Genesys.
@@ -448,7 +448,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
msg = self._create_message(AudioHookMessageType.PONG) msg = self._create_message(AudioHookMessageType.PONG)
return msg return msg
def create_resumed_response(self) -> Dict[str, Any]: def create_resumed_response(self) -> dict[str, Any]:
"""Create a 'resumed' response message. """Create a 'resumed' response message.
This should be sent in response to a 'pause' message when ready to resume. This should be sent in response to a 'pause' message when ready to resume.
@@ -463,7 +463,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
return msg return msg
def create_barge_in_event(self) -> Dict[str, Any]: def create_barge_in_event(self) -> dict[str, Any]:
"""Create a barge-in event message. """Create a barge-in event message.
This notifies Genesys Cloud that the user has interrupted the bot's This notifies Genesys Cloud that the user has interrupted the bot's
@@ -485,9 +485,9 @@ class GenesysAudioHookSerializer(FrameSerializer):
self, self,
reason: str = "completed", reason: str = "completed",
action: str = "transfer", action: str = "transfer",
output_variables: Optional[Dict[str, Any]] = None, output_variables: dict[str, Any] | None = None,
info: Optional[str] = None, info: str | None = None,
) -> Dict[str, Any]: ) -> dict[str, Any]:
"""Create a 'disconnect' message to initiate session termination. """Create a 'disconnect' message to initiate session termination.
Args: Args:
@@ -499,7 +499,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
Returns: Returns:
Dictionary of the disconnect message. Dictionary of the disconnect message.
""" """
parameters: Dict[str, Any] = {"reason": reason} parameters: dict[str, Any] = {"reason": reason}
# Build outputVariables # Build outputVariables
out_vars = {"action": action} out_vars = {"action": action}
@@ -523,7 +523,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
code: int, code: int,
message: str, message: str,
retryable: bool = False, retryable: bool = False,
) -> Dict[str, Any]: ) -> dict[str, Any]:
"""Create an 'error' message. """Create an 'error' message.
Args: Args:
@@ -700,7 +700,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
return audio_frame return audio_frame
async def _handle_control_message(self, message: Dict[str, Any]) -> Frame | None: async def _handle_control_message(self, message: dict[str, Any]) -> Frame | None:
"""Handle a JSON control message from Genesys. """Handle a JSON control message from Genesys.
Args: Args:
@@ -748,7 +748,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
logger.warning(f"Unknown AudioHook message type: {msg_type}") logger.warning(f"Unknown AudioHook message type: {msg_type}")
return None return None
async def _handle_open(self, message: Dict[str, Any]) -> Frame | None: async def _handle_open(self, message: dict[str, Any]) -> Frame | None:
"""Handle an 'open' message from Genesys. """Handle an 'open' message from Genesys.
This initializes the session with metadata from Genesys Cloud and This initializes the session with metadata from Genesys Cloud and
@@ -781,7 +781,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
# media is a list like: [{"type": "audio", "format": "PCMU", "channels": ["external"], "rate": 8000}] # media is a list like: [{"type": "audio", "format": "PCMU", "channels": ["external"], "rate": 8000}]
media_list = self._media_info media_list = self._media_info
if media_list and isinstance(media_list, list) and len(media_list) > 0: if media_list and isinstance(media_list, list) and len(media_list) > 0:
audio_media: Dict[str, Any] = media_list[0] # Get first media entry audio_media: dict[str, Any] = media_list[0] # Get first media entry
channels = audio_media.get("channels", []) channels = audio_media.get("channels", [])
logger.debug( logger.debug(
f"📡 Genesys audio config: format={audio_media.get('format')}, channels={channels}, rate={audio_media.get('rate')}" f"📡 Genesys audio config: format={audio_media.get('format')}, channels={channels}, rate={audio_media.get('rate')}"
@@ -815,7 +815,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
) )
) )
async def _handle_close(self, message: Dict[str, Any]) -> Frame | None: async def _handle_close(self, message: dict[str, Any]) -> Frame | None:
"""Handle a 'close' message from Genesys. """Handle a 'close' message from Genesys.
Automatically responds with a 'closed' message. If output_variables Automatically responds with a 'closed' message. If output_variables
@@ -846,7 +846,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
message=self.create_closed_response(output_variables=self._output_variables) message=self.create_closed_response(output_variables=self._output_variables)
) )
async def _handle_ping(self, message: Dict[str, Any]) -> Frame | None: async def _handle_ping(self, message: dict[str, Any]) -> Frame | None:
"""Handle a 'ping' message from Genesys. """Handle a 'ping' message from Genesys.
Automatically responds with a 'pong' message to maintain the connection. Automatically responds with a 'pong' message to maintain the connection.
@@ -864,7 +864,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
# Return as urgent frame to be sent through pipeline immediately # Return as urgent frame to be sent through pipeline immediately
return OutputTransportMessageUrgentFrame(message=self.create_pong_response()) return OutputTransportMessageUrgentFrame(message=self.create_pong_response())
async def _handle_pause(self, message: Dict[str, Any]) -> Frame | None: async def _handle_pause(self, message: dict[str, Any]) -> Frame | None:
"""Handle a 'pause' message from Genesys. """Handle a 'pause' message from Genesys.
This is used when audio streaming is temporarily suspended This is used when audio streaming is temporarily suspended
@@ -888,7 +888,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
# Note: Application should call create_resumed_response() when ready # Note: Application should call create_resumed_response() when ready
return None return None
async def _handle_update(self, message: Dict[str, Any]) -> Frame | None: async def _handle_update(self, message: dict[str, Any]) -> Frame | None:
"""Handle an 'update' message from Genesys. """Handle an 'update' message from Genesys.
Updates may include changes to participants or configuration. Updates may include changes to participants or configuration.
@@ -910,7 +910,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
return None return None
async def _handle_error(self, message: Dict[str, Any]) -> Frame | None: async def _handle_error(self, message: dict[str, Any]) -> Frame | None:
"""Handle an 'error' message from Genesys. """Handle an 'error' message from Genesys.
Args: Args:
@@ -929,7 +929,7 @@ class GenesysAudioHookSerializer(FrameSerializer):
return None return None
async def _handle_dtmf(self, message: Dict[str, Any]) -> Frame | None: async def _handle_dtmf(self, message: dict[str, Any]) -> Frame | None:
"""Handle a 'dtmf' message from Genesys. """Handle a 'dtmf' message from Genesys.
DTMF (Dual-Tone Multi-Frequency) events are sent when the user DTMF (Dual-Tone Multi-Frequency) events are sent when the user

Some files were not shown because too many files have changed in this diff Show More