From 10e58d6e42794bae18f18431c9b3915d59609f8c Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Tue, 21 Apr 2026 16:17:49 -0400 Subject: [PATCH 1/5] Fix type errors in scripts and add to pyright checked set --- pyrightconfig.json | 7 +++---- scripts/daily/test_tavus_transport.py | 18 +++++++++++------- scripts/evals/eval.py | 10 +++++----- scripts/evals/run-eval.py | 5 +++-- scripts/evals/utils.py | 2 ++ .../krisp/test_krisp_viva_turn_audiofile.py | 2 +- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/pyrightconfig.json b/pyrightconfig.json index 94c0114b6..e453d8c60 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -11,7 +11,8 @@ "src/pipecat/extensions", "src/pipecat/turns", "src/pipecat/pipeline", - "src/pipecat/runner" + "src/pipecat/runner", + "scripts" ], "exclude": ["**/*_pb2.py", "**/__pycache__"], "ignore": [ @@ -24,9 +25,7 @@ "src/pipecat/tests", "src/pipecat/transports", "src/pipecat/utils", - "tests", - "scripts", - "docs" + "tests" ], "reportMissingImports": false } diff --git a/scripts/daily/test_tavus_transport.py b/scripts/daily/test_tavus_transport.py index fa8afe835..ffb6b8ee6 100644 --- a/scripts/daily/test_tavus_transport.py +++ b/scripts/daily/test_tavus_transport.py @@ -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) diff --git a/scripts/evals/eval.py b/scripts/evals/eval.py index cf7ecf257..fdec8238c 100644 --- a/scripts/evals/eval.py +++ b/scripts/evals/eval.py @@ -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) diff --git a/scripts/evals/run-eval.py b/scripts/evals/run-eval.py index 72d4902b7..5ccb4f7c6 100644 --- a/scripts/evals/run-eval.py +++ b/scripts/evals/run-eval.py @@ -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() diff --git a/scripts/evals/utils.py b/scripts/evals/utils.py index 7ce9dfe84..997ef8f73 100644 --- a/scripts/evals/utils.py +++ b/scripts/evals/utils.py @@ -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 diff --git a/scripts/krisp/test_krisp_viva_turn_audiofile.py b/scripts/krisp/test_krisp_viva_turn_audiofile.py index 6580e8e90..99557c171 100644 --- a/scripts/krisp/test_krisp_viva_turn_audiofile.py +++ b/scripts/krisp/test_krisp_viva_turn_audiofile.py @@ -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. From 847bd8af4bd5521f98dd13d56c4de81eb11375f7 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Tue, 21 Apr 2026 16:21:46 -0400 Subject: [PATCH 2/5] Remove src/pipecat/sync which doesn't exist --- pyrightconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/pyrightconfig.json b/pyrightconfig.json index e453d8c60..694e9ab8a 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -21,7 +21,6 @@ "src/pipecat/processors", "src/pipecat/serializers", "src/pipecat/services", - "src/pipecat/sync", "src/pipecat/tests", "src/pipecat/transports", "src/pipecat/utils", From c244a950eb82ccc2bd5c83ce9913d864f73bf6c3 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Tue, 21 Apr 2026 16:24:53 -0400 Subject: [PATCH 3/5] Add src/pipecat/tests to include list, alphabetize list --- pyrightconfig.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyrightconfig.json b/pyrightconfig.json index 694e9ab8a..822ccbf22 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -3,16 +3,17 @@ "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", - "scripts" + "src/pipecat/tests", + "src/pipecat/transcriptions", + "src/pipecat/turns" ], "exclude": ["**/*_pb2.py", "**/__pycache__"], "ignore": [ @@ -21,7 +22,6 @@ "src/pipecat/processors", "src/pipecat/serializers", "src/pipecat/services", - "src/pipecat/tests", "src/pipecat/transports", "src/pipecat/utils", "tests" From 21f5cfe21ad943007ad65730e316ada8d9b587d7 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Tue, 21 Apr 2026 16:47:12 -0400 Subject: [PATCH 4/5] Fix type errors in utils and add to pyright checked set --- pyrightconfig.json | 4 ++-- .../context/llm_context_summarization.py | 22 +++++++++++++------ src/pipecat/utils/frame_queue.py | 2 +- src/pipecat/utils/string.py | 2 +- .../utils/text/base_text_aggregator.py | 2 +- .../utils/text/pattern_pair_aggregator.py | 2 +- .../utils/tracing/service_attributes.py | 4 ++-- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/pyrightconfig.json b/pyrightconfig.json index 822ccbf22..31bac33c8 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -13,7 +13,8 @@ "src/pipecat/runner", "src/pipecat/tests", "src/pipecat/transcriptions", - "src/pipecat/turns" + "src/pipecat/turns", + "src/pipecat/utils" ], "exclude": ["**/*_pb2.py", "**/__pycache__"], "ignore": [ @@ -23,7 +24,6 @@ "src/pipecat/serializers", "src/pipecat/services", "src/pipecat/transports", - "src/pipecat/utils", "tests" ], "reportMissingImports": false diff --git a/src/pipecat/utils/context/llm_context_summarization.py b/src/pipecat/utils/context/llm_context_summarization.py index 85a7e6e19..59de6f4fa 100644 --- a/src/pipecat/utils/context/llm_context_summarization.py +++ b/src/pipecat/utils/context/llm_context_summarization.py @@ -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" diff --git a/src/pipecat/utils/frame_queue.py b/src/pipecat/utils/frame_queue.py index 1b787362d..14347681a 100644 --- a/src/pipecat/utils/frame_queue.py +++ b/src/pipecat/utils/frame_queue.py @@ -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 diff --git a/src/pipecat/utils/string.py b/src/pipecat/utils/string.py index 55b1c2d53..c464e189f 100644 --- a/src/pipecat/utils/string.py +++ b/src/pipecat/utils/string.py @@ -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: diff --git a/src/pipecat/utils/text/base_text_aggregator.py b/src/pipecat/utils/text/base_text_aggregator.py index 99ca145b6..b1b933c81 100644 --- a/src/pipecat/utils/text/base_text_aggregator.py +++ b/src/pipecat/utils/text/base_text_aggregator.py @@ -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: diff --git a/src/pipecat/utils/text/pattern_pair_aggregator.py b/src/pipecat/utils/text/pattern_pair_aggregator.py index 975413c78..67e044152 100644 --- a/src/pipecat/utils/text/pattern_pair_aggregator.py +++ b/src/pipecat/utils/text/pattern_pair_aggregator.py @@ -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 diff --git a/src/pipecat/utils/tracing/service_attributes.py b/src/pipecat/utils/tracing/service_attributes.py index d9b86a9a4..a8b33e225 100644 --- a/src/pipecat/utils/tracing/service_attributes.py +++ b/src/pipecat/utils/tracing/service_attributes.py @@ -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"]) From 4d9dc64af8067cba202618fa1e5cd2ecf8095b17 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Tue, 21 Apr 2026 16:51:27 -0400 Subject: [PATCH 5/5] Install all extras in format workflow for pyright CI was running `uv sync --group dev` without extras. Adds daily and tracing to extras. --- .github/workflows/format.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml index c02245708..7ac009bdc 100644 --- a/.github/workflows/format.yaml +++ b/.github/workflows/format.yaml @@ -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