Merge pull request #4348 from pipecat-ai/mb/pyright-scripts-docs

Fix type errors in scripts and add to pyright checked set
This commit is contained in:
Mark Backman
2026-04-21 16:56:49 -04:00
committed by GitHub
13 changed files with 54 additions and 41 deletions

View File

@@ -32,7 +32,7 @@ jobs:
run: uv python install 3.12
- name: Install development dependencies
run: uv sync --group dev
run: uv sync --group dev --extra daily --extra tracing
- name: Ruff formatter
id: ruff-format

View File

@@ -3,15 +3,18 @@
"pythonVersion": "3.11",
"pythonPlatform": "All",
"include": [
"scripts",
"src/pipecat/clocks",
"src/pipecat/metrics",
"src/pipecat/transcriptions",
"src/pipecat/frames",
"src/pipecat/observers",
"src/pipecat/extensions",
"src/pipecat/turns",
"src/pipecat/frames",
"src/pipecat/metrics",
"src/pipecat/observers",
"src/pipecat/pipeline",
"src/pipecat/runner"
"src/pipecat/runner",
"src/pipecat/tests",
"src/pipecat/transcriptions",
"src/pipecat/turns",
"src/pipecat/utils"
],
"exclude": ["**/*_pb2.py", "**/__pycache__"],
"ignore": [
@@ -20,13 +23,8 @@
"src/pipecat/processors",
"src/pipecat/serializers",
"src/pipecat/services",
"src/pipecat/sync",
"src/pipecat/tests",
"src/pipecat/transports",
"src/pipecat/utils",
"tests",
"scripts",
"docs"
"tests"
],
"reportMissingImports": false
}

View File

@@ -2,7 +2,14 @@ import asyncio
import os
import signal
from daily import *
from daily import (
AudioData,
CallClient,
CustomAudioSource,
CustomAudioTrack,
Daily,
EventHandler,
)
from dotenv import load_dotenv
from loguru import logger
@@ -33,8 +40,8 @@ class DailyProxyApp(EventHandler):
def __init__(self, sample_rate: int):
super().__init__()
self._sample_rate = sample_rate
self._loop = None
self._audio_queue: asyncio.Queue | None = None
self._loop = asyncio.new_event_loop()
self._audio_queue: asyncio.Queue = asyncio.Queue()
self._audio_task: asyncio.Task | None = None
self._client: CallClient = CallClient(event_handler=self)
@@ -52,7 +59,6 @@ class DailyProxyApp(EventHandler):
self._loop.call_soon_threadsafe(self._loop.stop)
def run(self, meeting_url: str):
self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
self._create_audio_task()
@@ -101,7 +107,6 @@ class DailyProxyApp(EventHandler):
def _create_audio_task(self):
if not self._audio_task:
self._audio_queue = asyncio.Queue()
self._audio_task = self._loop.create_task(self._audio_task_handler())
async def _cancel_audio_task(self):
@@ -113,7 +118,6 @@ class DailyProxyApp(EventHandler):
except asyncio.CancelledError:
pass
self._audio_task = None
self._audio_queue = None
async def capture_participant_audio(self, participant_id: str):
logger.info(f"Capturing participant audio: {participant_id}")
@@ -168,7 +172,7 @@ class DailyProxyApp(EventHandler):
def main():
Daily.init()
room_url = os.getenv("TAVUS_SAMPLE_ROOM_URL")
room_url = os.environ["TAVUS_SAMPLE_ROOM_URL"]
app = DailyProxyApp(sample_rate=24000)
app.run(room_url)

View File

@@ -198,7 +198,7 @@ class EvalRunner:
async def run_example_pipeline(script_path: Path, eval_config: EvalConfig):
room_url = os.getenv("DAILY_ROOM_URL")
room_url = os.environ["DAILY_ROOM_URL"]
module = load_module_from_path(script_path)
@@ -227,7 +227,7 @@ async def run_eval_pipeline(
):
logger.info(f"Starting eval bot")
room_url = os.getenv("DAILY_ROOM_URL")
room_url = os.environ["DAILY_ROOM_URL"]
transport = DailyTransport(
room_url,
@@ -243,7 +243,7 @@ async def run_eval_pipeline(
# We disable smart formatting because some times if the user says "3 + 2 is
# 5" (in audio) this can be converted to "32 is 5".
stt = DeepgramSTTService(
api_key=os.getenv("DEEPGRAM_API_KEY"),
api_key=os.environ["DEEPGRAM_API_KEY"],
settings=DeepgramSTTService.Settings(
language="multi",
smart_format=False,
@@ -251,7 +251,7 @@ async def run_eval_pipeline(
)
tts = CartesiaTTSService(
api_key=os.getenv("CARTESIA_API_KEY"),
api_key=os.environ["CARTESIA_API_KEY"],
settings=CartesiaTTSService.Settings(
voice="97f4b8fb-f2fe-444b-bb9a-c109783a857a", # Nathan
),
@@ -375,7 +375,7 @@ async def run_eval_pipeline(
@task.event_handler("on_pipeline_finished")
async def on_pipeline_finished(task, frame):
if isinstance(frame, EndFrame):
await eval_runner.assert_eval(frame.reason)
await eval_runner.assert_eval(bool(frame.reason))
elif isinstance(frame, CancelFrame):
await eval_runner.assert_eval(False)

View File

@@ -11,7 +11,7 @@ import sys
from pathlib import Path
from dotenv import load_dotenv
from eval import EvalRunner
from eval import EvalConfig, EvalRunner
from loguru import logger
from utils import check_env_variables
@@ -33,7 +33,8 @@ async def main(args: argparse.Namespace):
runner = EvalRunner(examples_dir=script_path, record_audio=args.audio, log_level=log_level)
await runner.run_eval(script_file, args.prompt, args.eval)
eval_config = EvalConfig(prompt=args.prompt, eval=args.eval)
await runner.run_eval(script_file, eval_config)
runner.print_results()

View File

@@ -79,6 +79,8 @@ def load_module_from_path(path: str | Path):
module_name = path.stem
spec = importlib.util.spec_from_file_location(module_name, str(path))
if spec is None or spec.loader is None:
raise ImportError(f"Could not load module spec from {path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module

View File

@@ -71,7 +71,7 @@ async def analyze_audio_file(
frame_duration_ms: int = 20,
chunk_duration_ms: int = 20,
verbose: bool = False,
output_file: str = None,
output_file: str | None = None,
) -> None:
"""Analyze an audio file for turn detection using Krisp VIVA turn analyzer.

View File

@@ -20,7 +20,11 @@ if TYPE_CHECKING:
from loguru import logger
from pipecat.processors.aggregators.llm_context import LLMContext, LLMSpecificMessage
from pipecat.processors.aggregators.llm_context import (
LLMContext,
LLMContextMessage,
LLMSpecificMessage,
)
# Fallback timeout (seconds) used when summarization_timeout is None.
DEFAULT_SUMMARIZATION_TIMEOUT = 120.0
@@ -269,7 +273,7 @@ class LLMMessagesToSummarize:
last_summarized_index: Index of the last message being summarized
"""
messages: list[dict]
messages: list[LLMContextMessage]
last_summarized_index: int
@@ -415,7 +419,7 @@ class LLMContextSummarizationUtil:
@staticmethod
def _get_earliest_function_call_not_resolved_in_range(
messages: list[dict], start_idx: int, summary_end: int
messages: list[LLMContextMessage], start_idx: int, summary_end: int
) -> int:
"""Find the earliest message index with incomplete function calls.
@@ -470,9 +474,10 @@ class LLMContextSummarizationUtil:
if role == "tool":
tool_call_id = msg.get("tool_call_id")
if tool_call_id and tool_call_id in pending_tool_calls:
if not LLMContextSummarizationUtil._is_tool_message_pending(
msg.get("content", "")
):
content = msg.get("content", "")
if not isinstance(content, str):
content = ""
if not LLMContextSummarizationUtil._is_tool_message_pending(content):
pending_tool_calls.pop(tool_call_id)
# Check for async tool completion — a developer message with
@@ -480,7 +485,10 @@ class LLMContextSummarizationUtil:
# async result has arrived and the call is now resolved.
if role == "developer":
try:
parsed = json.loads(msg.get("content", ""))
content = msg.get("content", "")
if not isinstance(content, str):
continue
parsed = json.loads(content)
if (
isinstance(parsed, dict)
and parsed.get("type") == "async_tool"

View File

@@ -58,7 +58,7 @@ class FrameQueue(asyncio.Queue):
Returns:
True if at least one enqueued frame is an instance of ``frame_type``.
"""
for item in self._queue:
for item in self._queue: # pyright: ignore[reportAttributeAccessIssue]
if isinstance(self._frame_getter(item), frame_type):
return True
return False

View File

@@ -234,7 +234,7 @@ class TextPartForConcatenation:
includes_inter_part_spaces: bool
def __str__(self):
return f"{self.name}(text: [{self.text}], includes_inter_part_spaces: {self.includes_inter_part_spaces})"
return f"{type(self).__name__}(text: [{self.text}], includes_inter_part_spaces: {self.includes_inter_part_spaces})"
def concatenate_aggregated_text(text_parts: list[TextPartForConcatenation]) -> str:

View File

@@ -125,7 +125,7 @@ class BaseTextAggregator(ABC):
"""
pass
# Make this a generator to satisfy type checker
yield # pragma: no cover
yield # pyright: ignore[reportReturnType] # pragma: no cover
@abstractmethod
async def flush(self) -> Aggregation | None:

View File

@@ -273,7 +273,7 @@ class PatternPairAggregator(SimpleTextAggregator):
# Which is why we base the return on the first found.
if start_count > end_count:
start_index = text.find(start)
return [start_index, pattern_info]
return (start_index, pattern_info)
return None

View File

@@ -440,7 +440,7 @@ def add_openai_realtime_span_attributes(
if isinstance(tool, dict) and "name" in tool:
tool_names.append(tool["name"])
elif hasattr(tool, "name"):
tool_names.append(tool.name)
tool_names.append(getattr(tool, "name"))
elif isinstance(tool, dict) and "function" in tool and "name" in tool["function"]:
tool_names.append(tool["function"]["name"])
@@ -455,7 +455,7 @@ def add_openai_realtime_span_attributes(
if function_calls:
call = function_calls[0]
if hasattr(call, "name"):
span.set_attribute("function_calls.first_name", call.name)
span.set_attribute("function_calls.first_name", getattr(call, "name"))
elif isinstance(call, dict) and "name" in call:
span.set_attribute("function_calls.first_name", call["name"])