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"]
logger.debug(f"loading conversation from {filename}")
try:
with open(filename, "r") as file:
with open(filename) as file:
params.context.set_messages(json.load(file))
logger.debug(
f"loaded conversation from {filename}\n{json.dumps(params.context.get_messages(), indent=4)}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.
"""
from typing import Sequence
from collections.abc import Sequence
import numpy as np
from loguru import logger

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.
"""
from typing import Optional
from pipecat.frames.frames import (
AggregatedTextFrame,
EndFrame,
@@ -38,7 +36,7 @@ class LLMTextProcessor(FrameProcessor):
output tokens from the LLM.
"""
def __init__(self, *, text_aggregator: Optional[BaseTextAggregator] = None, **kwargs):
def __init__(self, *, text_aggregator: BaseTextAggregator | None = None, **kwargs):
"""Initialize the LLM text processor.
Args:
@@ -91,7 +89,7 @@ class LLMTextProcessor(FrameProcessor):
out_frame.skip_tts = in_frame.skip_tts
await self.push_frame(out_frame)
async def _handle_llm_end(self, skip_tts: Optional[bool] = None):
async def _handle_llm_end(self, skip_tts: bool | None = None):
# Flush any remaining text
remaining = await self._text_aggregator.flush()
if remaining:

View File

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

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.
"""
from typing import Optional
from pipecat.audio.utils import create_stream_resampler, interleave_stereo_audio, mix_audio
from pipecat.frames.frames import (
BotStartedSpeakingFrame,
@@ -55,7 +53,7 @@ class AudioBufferProcessor(FrameProcessor):
def __init__(
self,
*,
sample_rate: Optional[int] = None,
sample_rate: int | None = None,
num_channels: int = 1,
buffer_size: int = 0,
enable_turn_audio: bool = False,
@@ -263,7 +261,7 @@ class AudioBufferProcessor(FrameProcessor):
silence_needed = target_position - current_len
buffer.extend(b"\x00" * silence_needed)
async def _process_turn_recording(self, frame: Frame, resampled_audio: Optional[bytes] = None):
async def _process_turn_recording(self, frame: Frame, resampled_audio: bytes | None = None):
"""Process frames for turn-based audio recording."""
# Speaking state (_user_speaking / _bot_speaking) is maintained by
# _process_recording so it is always up-to-date here.

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.
"""
from typing import Type
from loguru import logger
from pipecat.audio.vad.vad_analyzer import VADAnalyzer
@@ -94,7 +92,7 @@ class VADProcessor(FrameProcessor):
await self.push_frame(frame, direction)
@self._vad_controller.event_handler("on_broadcast_frame")
async def on_broadcast_frame(_controller, frame_cls: Type[Frame], **kwargs):
async def on_broadcast_frame(_controller, frame_cls: type[Frame], **kwargs):
await self.broadcast_frame(frame_cls, **kwargs)
async def cleanup(self):

View File

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

View File

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

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.
"""
from typing import Awaitable, Callable, Optional
from collections.abc import Awaitable, Callable
from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame, SystemFrame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@@ -29,7 +29,7 @@ class FunctionFilter(FrameProcessor):
def __init__(
self,
filter: FilterType,
direction: Optional[FrameDirection] = FrameDirection.DOWNSTREAM,
direction: FrameDirection | None = FrameDirection.DOWNSTREAM,
filter_system_frames: bool = False,
**kwargs,
):

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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.
"""
from typing import Optional
from loguru import logger
from pipecat.frames.frames import (
@@ -38,9 +36,9 @@ class StrandsAgentsProcessor(FrameProcessor):
def __init__(
self,
agent: Optional[Agent] = None,
graph: Optional[Graph] = None,
graph_exit_node: Optional[str] = None,
agent: Agent | None = None,
graph: Graph | None = None,
graph_exit_node: str | None = None,
):
"""Initialize the Strands Agents processor.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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