From 414dcf9810a91e163b679cd5996cc1d818549293 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Thu, 12 Dec 2024 11:06:09 -0500 Subject: [PATCH 1/9] Improve TOC in sidebar, fix missing services --- .github/workflows/generate_docs.yaml | 47 ----------- docs/api/conf.py | 112 +++++++++++++++++++++++++-- docs/api/generate_docs.py | 104 ------------------------- docs/api/index.rst | 56 ++++++-------- docs/api/requirements.txt | 2 +- src/pipecat/services/assemblyai.py | 18 +++-- src/pipecat/services/rime.py | 6 ++ 7 files changed, 146 insertions(+), 199 deletions(-) delete mode 100644 .github/workflows/generate_docs.yaml delete mode 100644 docs/api/generate_docs.py diff --git a/.github/workflows/generate_docs.yaml b/.github/workflows/generate_docs.yaml deleted file mode 100644 index 242984d8a..000000000 --- a/.github/workflows/generate_docs.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: Generate API Documentation - -on: - release: - types: [published] # Run on new release - workflow_dispatch: # Manual trigger - -jobs: - update-docs: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r docs/api/requirements.txt - pip install . - - - name: Generate API documentation - run: | - cd docs/api - python generate_docs.py - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 - with: - commit-message: 'docs: Update API documentation' - title: 'docs: Update API documentation' - body: | - Automated PR to update API documentation. - - - Generated using `docs/api/generate_docs.py` - - Triggered by: ${{ github.event_name }} - branch: update-api-docs - delete-branch: true - labels: | - documentation diff --git a/docs/api/conf.py b/docs/api/conf.py index fffaf90b2..c046092e2 100644 --- a/docs/api/conf.py +++ b/docs/api/conf.py @@ -1,5 +1,12 @@ +import importlib +import logging import sys from pathlib import Path +from typing import Dict, List, Optional + +# Configure logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") +logger = logging.getLogger("sphinx-build") # Add source directory to path docs_dir = Path(__file__).parent @@ -17,6 +24,7 @@ extensions = [ "sphinx.ext.napoleon", "sphinx.ext.viewcode", "sphinx.ext.intersphinx", + "sphinx.ext.coverage", ] # Napoleon settings @@ -32,13 +40,72 @@ autodoc_default_options = { "undoc-members": True, "exclude-members": "__weakref__", "no-index": True, + "show-inheritance": True, } # HTML output settings html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] autodoc_typehints = "description" -html_show_sphinx = False # Remove "Built with Sphinx" +html_show_sphinx = False + + +def get_installed_services() -> Dict[str, Optional[str]]: + """Scan for installed pipecat services and return their status. + Returns a dictionary of service names and their import status/error message. + """ + services_dir = project_root / "src" / "pipecat" / "services" + services_status = {} + + if not services_dir.exists(): + logger.warning(f"Services directory not found: {services_dir}") + return services_status + + for item in services_dir.iterdir(): + if item.is_dir() and not item.name.startswith("_") and not item.name == "to_be_updated": + service_name = item.name + try: + module = importlib.import_module(f"pipecat.services.{service_name}") + services_status[service_name] = None # None indicates success + logger.info(f"Found service: {service_name} at {module.__file__}") + except ImportError as e: + services_status[service_name] = str(e) + logger.warning(f"Failed to import {service_name}: {e}") + + return services_status + + +def generate_services_rst() -> str: + """Generate RST content for services section.""" + services = get_installed_services() + + # Sort services into successful and failed imports + successful = [name for name, status in services.items() if status is None] + failed = [(name, status) for name, status in services.items() if status is not None] + + rst_content = [ + "Services", + "~~~~~~~~", + "", + "Successfully Detected Services:", + "", + ] + + for service in sorted(successful): + rst_content.append(f"* :mod:`pipecat.services.{service}`") + + if failed: + rst_content.extend( + [ + "", + "Services with Import Issues:", + "", + ] + ) + for service, error in sorted(failed): + rst_content.append(f"* {service} (Import failed: {error})") + + return "\n".join(rst_content) def setup(app): @@ -55,12 +122,21 @@ def setup(app): import shutil shutil.rmtree(output_dir) + logger.info(f"Cleaned existing documentation in {output_dir}") - print(f"Generating API documentation...") - print(f"Output directory: {output_dir}") - print(f"Source directory: {source_dir}") + logger.info(f"Generating API documentation...") + logger.info(f"Output directory: {output_dir}") + logger.info(f"Source directory: {source_dir}") + + # Get installed services + services = get_installed_services() + logger.info(f"Found {len(services)} services") + for service, status in services.items(): + if status is None: + logger.info(f"Service available: {service}") + else: + logger.warning(f"Service import failed: {service} - {status}") - # Similar exclusions as in your generate_docs.py excludes = [ str(project_root / "src/pipecat/processors/gstreamer"), str(project_root / "src/pipecat/transports/network"), @@ -72,7 +148,27 @@ def setup(app): ] try: - main(["-f", "-e", "-M", "--no-toc", "-o", output_dir, source_dir] + excludes) - print("API documentation generated successfully!") + main( + [ + "-f", + "-e", + "-M", + "--no-toc", + "--separate", + "--module-first", + "-o", + output_dir, + source_dir, + ] + + excludes + ) + + logger.info("API documentation generated successfully!") + + # Generate services index file + services_index = Path(output_dir) / "services_index.rst" + services_index.write_text(generate_services_rst()) + logger.info(f"Generated services index at {services_index}") + except Exception as e: - print(f"Error generating API documentation: {e}") + logger.error(f"Error generating API documentation: {e}", exc_info=True) diff --git a/docs/api/generate_docs.py b/docs/api/generate_docs.py deleted file mode 100644 index 972ea3c89..000000000 --- a/docs/api/generate_docs.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 - -import shutil -import subprocess -from pathlib import Path - - -def run_command(command: list[str]) -> None: - """Run a command and exit if it fails.""" - print(f"Running: {' '.join(command)}") - try: - subprocess.run(command, check=True) - except subprocess.CalledProcessError as e: - print(f"Warning: Command failed: {' '.join(command)}") - print(f"Error: {e}") - - -def main(): - docs_dir = Path(__file__).parent - project_root = docs_dir.parent.parent - - # Install documentation requirements - requirements_file = docs_dir / "requirements.txt" - run_command(["pip", "install", "-r", str(requirements_file)]) - - # Install from project root, not docs directory - run_command(["pip", "install", "-e", str(project_root)]) - - # Install all service dependencies - services = [ - "anthropic", - "assemblyai", - "aws", - "azure", - "canonical", - "cartesia", - # "daily", - "deepgram", - "elevenlabs", - "fal", - "fireworks", - "gladia", - "google", - "grok", - "groq", - "langchain", - # "livekit", - "lmnt", - "moondream", - "nim", - "noisereduce", - "openai", - "openpipe", - "playht", - "silero", - "soundfile", - "websocket", - "whisper", - ] - - extras = ",".join(services) - try: - run_command(["pip", "install", "-e", f"{str(project_root)}[{extras}]"]) - except Exception as e: - print(f"Warning: Some dependencies failed to install: {e}") - - # Clean old files - api_dir = docs_dir / "api" - build_dir = docs_dir / "_build" - for dir in [api_dir, build_dir]: - if dir.exists(): - shutil.rmtree(dir) - - # Generate API documentation - run_command( - [ - "sphinx-apidoc", - "-f", # Force overwrite - "-e", # Put each module on its own page - "-M", # Put module documentation before submodule - "--no-toc", # Don't generate modules.rst (cleaner structure) - "-o", - str(api_dir), # Output directory - str(project_root / "src/pipecat"), - # Exclude problematic files and directories - str(project_root / "src/pipecat/processors/gstreamer"), # Optional gstreamer - str(project_root / "src/pipecat/transports/network"), # Pydantic issues - str(project_root / "src/pipecat/transports/services"), # Pydantic issues - str(project_root / "src/pipecat/transports/local"), # Optional dependencies - str(project_root / "src/pipecat/services/to_be_updated"), # Exclude to_be_updated - "**/test_*.py", # Test files - "**/tests/*.py", # Test files - ] - ) - - # Build HTML documentation - run_command(["sphinx-build", "-b", "html", str(docs_dir), str(build_dir / "html")]) - - print("\nDocumentation generated successfully!") - print(f"HTML docs: {build_dir}/html/index.html") - - -if __name__ == "__main__": - main() diff --git a/docs/api/index.rst b/docs/api/index.rst index 68afcb980..a878c0c10 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -13,61 +13,55 @@ Quick Links * `GitHub Repository `_ * `Website `_ - API Reference ------------- Core Components ~~~~~~~~~~~~~~~ -* :mod:`pipecat.frames` -* :mod:`pipecat.processors` -* :mod:`pipecat.pipeline` +* :mod:`Frames ` +* :mod:`Processors ` +* :mod:`Pipeline ` Audio Processing ~~~~~~~~~~~~~~~~ -* :mod:`pipecat.audio` -* :mod:`pipecat.vad` - -Services -~~~~~~~~ - -* :mod:`pipecat.services` +* :mod:`Audio ` +* :mod:`VAD ` Transport & Serialization ~~~~~~~~~~~~~~~~~~~~~~~~~ -* :mod:`pipecat.transports` -* :mod:`pipecat.serializers` +* :mod:`Transports ` +* :mod:`Serializers ` Utilities ~~~~~~~~~ -* :mod:`pipecat.clocks` -* :mod:`pipecat.metrics` -* :mod:`pipecat.sync` -* :mod:`pipecat.transcriptions` -* :mod:`pipecat.utils` +* :mod:`Clocks ` +* :mod:`Metrics ` +* :mod:`Sync ` +* :mod:`Transcriptions ` +* :mod:`Utils ` .. toctree:: :maxdepth: 2 :caption: API Reference :hidden: - api/pipecat.audio - api/pipecat.clocks - api/pipecat.frames - api/pipecat.metrics - api/pipecat.pipeline - api/pipecat.processors - api/pipecat.serializers - api/pipecat.services - api/pipecat.sync - api/pipecat.transcriptions - api/pipecat.transports - api/pipecat.utils - api/pipecat.vad + Audio + Clocks + Frames + Metrics + Pipeline + Processors + Serializers + Services + Sync + Transcriptions + Transports + Utils + VAD Indices and tables ================== diff --git a/docs/api/requirements.txt b/docs/api/requirements.txt index f571b6ce0..d57bcd00f 100644 --- a/docs/api/requirements.txt +++ b/docs/api/requirements.txt @@ -3,4 +3,4 @@ sphinx-rtd-theme sphinx-markdown-builder sphinx-autodoc-typehints toml -pipecat-ai[anthropic,assemblyai,aws,azure,canonical,cartesia,deepgram,elevenlabs,fal,fireworks,gladia,google,grok,groq,krisp,langchain,lmnt,moondream,nim,noisereduce,openai,openpipe,playht,silero,soundfile,websocket,whisper] +pipecat-ai[anthropic,assemblyai,aws,azure,canonical,cartesia,deepgram,elevenlabs,fal,fireworks,gladia,google,grok,groq,krisp,langchain,lmnt,moondream,nim,noisereduce,openai,openpipe,playht,riva,silero,simli,soundfile,websocket,whisper] \ No newline at end of file diff --git a/src/pipecat/services/assemblyai.py b/src/pipecat/services/assemblyai.py index a96589e5a..36a7e92ff 100644 --- a/src/pipecat/services/assemblyai.py +++ b/src/pipecat/services/assemblyai.py @@ -1,3 +1,9 @@ +# +# Copyright (c) 2024, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + import asyncio from typing import AsyncGenerator @@ -67,8 +73,7 @@ class AssemblyAISTTService(STTService): await self._disconnect() async def run_stt(self, audio: bytes) -> AsyncGenerator[Frame, None]: - """ - Process an audio chunk for STT transcription. + """Process an audio chunk for STT transcription. This method streams the audio data to AssemblyAI for real-time transcription. Transcription results are handled asynchronously via callback functions. @@ -83,8 +88,7 @@ class AssemblyAISTTService(STTService): yield None async def _connect(self): - """ - Establish a connection to the AssemblyAI real-time transcription service. + """Establish a connection to the AssemblyAI real-time transcription service. This method sets up the necessary callback functions and initializes the AssemblyAI transcriber. @@ -95,8 +99,7 @@ class AssemblyAISTTService(STTService): logger.info(f"{self}: Connected to AssemblyAI") def on_data(transcript: aai.RealtimeTranscript): - """ - Callback for handling incoming transcription data. + """Callback for handling incoming transcription data. This function runs in a separate thread from the main asyncio event loop. It creates appropriate transcription frames and schedules them to be @@ -121,8 +124,7 @@ class AssemblyAISTTService(STTService): asyncio.run_coroutine_threadsafe(self.push_frame(frame), self._loop) def on_error(error: aai.RealtimeError): - """ - Callback for handling errors from AssemblyAI. + """Callback for handling errors from AssemblyAI. Like on_data, this runs in a separate thread and schedules error handling in the main event loop. diff --git a/src/pipecat/services/rime.py b/src/pipecat/services/rime.py index 6cf2c8129..79614cb76 100644 --- a/src/pipecat/services/rime.py +++ b/src/pipecat/services/rime.py @@ -1,3 +1,9 @@ +# +# Copyright (c) 2024, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + from typing import AsyncGenerator, Optional import aiohttp From e14399727bbae871fd7057abb8487ae8c16a5b0c Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Thu, 12 Dec 2024 11:06:53 -0500 Subject: [PATCH 2/9] Add README and build script for local testing --- docs/api/README.md | 78 ++++++++++++++++++++++++++++++++++++++++++ docs/api/build-docs.sh | 10 ++++++ 2 files changed, 88 insertions(+) create mode 100644 docs/api/README.md create mode 100755 docs/api/build-docs.sh diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 000000000..c0943653f --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,78 @@ +# Pipecat Documentation + +This directory contains the source files for auto-generating Pipecat's server API reference documentation. + +## Setup + +1. Install documentation dependencies: + +```bash +pip install -r requirements.txt +``` + +2. Make the build script executable: + +```bash +chmod +x build-docs.sh +``` + +## Building Documentation + +From this directory, run either: + +```bash +# Using the build script (automatically opens docs when done) +./build-docs.sh + +# Or directly with sphinx-build +sphinx-build -b html . _build/html -W --keep-going +``` + +## Viewing Documentation + +The built documentation will be available at `_build/html/index.html`. To open: + +```bash +# On MacOS +open _build/html/index.html + +# On Linux +xdg-open _build/html/index.html + +# On Windows +start _build/html/index.html +``` + +## Directory Structure + +``` +. +├── api/ # Auto-generated API documentation +├── _build/ # Built documentation +├── _static/ # Static files (images, css, etc.) +├── conf.py # Sphinx configuration +├── index.rst # Main documentation entry point +├── requirements.txt # Documentation dependencies +└── build-docs.sh # Build script matching ReadTheDocs configuration +``` + +## Notes + +- Documentation is auto-generated from Python docstrings +- Service modules are automatically detected and included +- The build process matches our ReadTheDocs configuration +- Warnings are treated as errors (-W flag) to maintain consistency +- The --keep-going flag ensures all errors are reported + +## Troubleshooting + +If you encounter missing service modules: + +1. Verify the service is installed with its extras: `pip install pipecat-ai[service-name]` +2. Check the build logs for import errors +3. Ensure the service module is properly initialized in the package + +For more information: + +- [ReadTheDocs Configuration](.readthedocs.yaml) +- [Sphinx Documentation](https://www.sphinx-doc.org/) diff --git a/docs/api/build-docs.sh b/docs/api/build-docs.sh new file mode 100755 index 000000000..46d025404 --- /dev/null +++ b/docs/api/build-docs.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Clean previous build +rm -rf _build + +# Build docs matching ReadTheDocs configuration +sphinx-build -b html -d _build/doctrees . _build/html -W --keep-going + +# Open docs (MacOS) +open _build/html/index.html \ No newline at end of file From f3ed12c30b0a93407fafb0c62f36117a7ab81c6d Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Thu, 12 Dec 2024 11:11:53 -0500 Subject: [PATCH 3/9] Clean up module and package display names --- docs/api/conf.py | 98 ++++++++++++++---------------------------------- 1 file changed, 29 insertions(+), 69 deletions(-) diff --git a/docs/api/conf.py b/docs/api/conf.py index c046092e2..d4203f16e 100644 --- a/docs/api/conf.py +++ b/docs/api/conf.py @@ -1,8 +1,6 @@ -import importlib import logging import sys from pathlib import Path -from typing import Dict, List, Optional # Configure logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") @@ -24,7 +22,6 @@ extensions = [ "sphinx.ext.napoleon", "sphinx.ext.viewcode", "sphinx.ext.intersphinx", - "sphinx.ext.coverage", ] # Napoleon settings @@ -50,62 +47,26 @@ autodoc_typehints = "description" html_show_sphinx = False -def get_installed_services() -> Dict[str, Optional[str]]: - """Scan for installed pipecat services and return their status. - Returns a dictionary of service names and their import status/error message. - """ - services_dir = project_root / "src" / "pipecat" / "services" - services_status = {} +def clean_title(title: str) -> str: + """Automatically clean module titles.""" + # Remove everything after space (like 'module', 'processor', etc.) + title = title.split(" ")[0] - if not services_dir.exists(): - logger.warning(f"Services directory not found: {services_dir}") - return services_status + # Get the last part of the dot-separated path + parts = title.split(".") + title = parts[-1] - for item in services_dir.iterdir(): - if item.is_dir() and not item.name.startswith("_") and not item.name == "to_be_updated": - service_name = item.name - try: - module = importlib.import_module(f"pipecat.services.{service_name}") - services_status[service_name] = None # None indicates success - logger.info(f"Found service: {service_name} at {module.__file__}") - except ImportError as e: - services_status[service_name] = str(e) - logger.warning(f"Failed to import {service_name}: {e}") + # Handle special cases for common acronyms + acronyms = ["ai", "aws", "api", "vad"] + words = title.split("_") + cleaned_words = [] + for word in words: + if word.lower() in acronyms: + cleaned_words.append(word.upper()) + else: + cleaned_words.append(word.capitalize()) - return services_status - - -def generate_services_rst() -> str: - """Generate RST content for services section.""" - services = get_installed_services() - - # Sort services into successful and failed imports - successful = [name for name, status in services.items() if status is None] - failed = [(name, status) for name, status in services.items() if status is not None] - - rst_content = [ - "Services", - "~~~~~~~~", - "", - "Successfully Detected Services:", - "", - ] - - for service in sorted(successful): - rst_content.append(f"* :mod:`pipecat.services.{service}`") - - if failed: - rst_content.extend( - [ - "", - "Services with Import Issues:", - "", - ] - ) - for service, error in sorted(failed): - rst_content.append(f"* {service} (Import failed: {error})") - - return "\n".join(rst_content) + return " ".join(cleaned_words) def setup(app): @@ -128,15 +89,6 @@ def setup(app): logger.info(f"Output directory: {output_dir}") logger.info(f"Source directory: {source_dir}") - # Get installed services - services = get_installed_services() - logger.info(f"Found {len(services)} services") - for service, status in services.items(): - if status is None: - logger.info(f"Service available: {service}") - else: - logger.warning(f"Service import failed: {service} - {status}") - excludes = [ str(project_root / "src/pipecat/processors/gstreamer"), str(project_root / "src/pipecat/transports/network"), @@ -165,10 +117,18 @@ def setup(app): logger.info("API documentation generated successfully!") - # Generate services index file - services_index = Path(output_dir) / "services_index.rst" - services_index.write_text(generate_services_rst()) - logger.info(f"Generated services index at {services_index}") + # Process generated RST files to update titles + for rst_file in Path(output_dir).glob("*.rst"): + content = rst_file.read_text() + lines = content.split("\n") + + # Find and clean up the title + if lines and "=" in lines[1]: # Title is typically the first line + old_title = lines[0] + new_title = clean_title(old_title) + content = content.replace(old_title, new_title) + rst_file.write_text(content) + logger.info(f"Updated title: {old_title} -> {new_title}") except Exception as e: logger.error(f"Error generating API documentation: {e}", exc_info=True) From b5d5a0e9236046f11e1d3516c5db07cb7dbd4624 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Thu, 12 Dec 2024 11:15:36 -0500 Subject: [PATCH 4/9] Add special cases for displaying some names --- docs/api/conf.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/api/conf.py b/docs/api/conf.py index d4203f16e..843700feb 100644 --- a/docs/api/conf.py +++ b/docs/api/conf.py @@ -56,13 +56,32 @@ def clean_title(title: str) -> str: parts = title.split(".") title = parts[-1] - # Handle special cases for common acronyms - acronyms = ["ai", "aws", "api", "vad"] + # Special cases for service names and common acronyms + special_cases = { + "ai": "AI", + "aws": "AWS", + "api": "API", + "vad": "VAD", + "assemblyai": "AssemblyAI", + "deepgram": "Deepgram", + "elevenlabs": "ElevenLabs", + "openai": "OpenAI", + "openpipe": "OpenPipe", + "playht": "PlayHT", + "xtts": "XTTS", + "lmnt": "LMNT", + } + + # Check if the entire title is a special case + if title.lower() in special_cases: + return special_cases[title.lower()] + + # Otherwise, capitalize each word words = title.split("_") cleaned_words = [] for word in words: - if word.lower() in acronyms: - cleaned_words.append(word.upper()) + if word.lower() in special_cases: + cleaned_words.append(special_cases[word.lower()]) else: cleaned_words.append(word.capitalize()) From 276fd86ecb4e4ee898eb29eac331dd4e58b68084 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Thu, 12 Dec 2024 13:25:13 -0500 Subject: [PATCH 5/9] More fixes for missing packages --- .readthedocs.yaml | 64 ++++++++++++++++++++++++++++++- docs/api/conf.py | 62 ++++++++++++++++++++++++++++++ docs/api/index.rst | 1 + docs/api/requirements-base.txt | 36 +++++++++++++++++ docs/api/requirements-playht.txt | 3 ++ docs/api/requirements-riva.txt | 3 ++ docs/api/requirements.txt | 6 --- docs/api/rtd-test.sh | 36 +++++++++++++++++ pyproject.toml | 2 +- src/pipecat/services/anthropic.py | 7 ++-- 10 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 docs/api/requirements-base.txt create mode 100644 docs/api/requirements-playht.txt create mode 100644 docs/api/requirements-riva.txt delete mode 100644 docs/api/requirements.txt create mode 100755 docs/api/rtd-test.sh diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 667e789d9..ea03b508e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,12 +4,74 @@ build: os: ubuntu-22.04 tools: python: '3.12' + jobs: + pre_build: + # Commands to run before the build + - python -m pip install --upgrade pip + - pip install wheel setuptools + post_create_environment: + # Install system dependencies required by some packages + - apt-get update + - apt-get install -y portaudio19-dev python3-pyaudio + post_build: + # Commands to run after the build + - echo "Build completed" sphinx: configuration: docs/api/conf.py + fail_on_warning: false # Set to true if you want builds to fail on warnings python: install: - - requirements: docs/api/requirements.txt + - requirements: docs/api/requirements-base.txt + # Try to install Riva first, fall back to PlayHT if it fails + - requirements: docs/api/requirements-riva.txt || true + - requirements: docs/api/requirements-playht.txt || true - method: pip path: . + extra_requirements: + - anthropic + - assemblyai + - aws + - azure + - canonical + - cartesia + - deepgram + - elevenlabs + - fal + - fireworks + - gladia + - google + - grok + - groq + - krisp + - langchain + - livekit + - lmnt + - moondream + - nim + - noisereduce + - openai + - openpipe + - silero + - simli + - soundfile + - websocket + - whisper + +formats: + - pdf + - epub + - htmlzip + +# Cache dependencies to speed up builds +search: + ranking: + api/*: 5 + getting-started/*: 4 + guides/*: 3 + +# Configure submodules if needed +submodules: + include: all + recursive: true diff --git a/docs/api/conf.py b/docs/api/conf.py index 843700feb..ad969df8c 100644 --- a/docs/api/conf.py +++ b/docs/api/conf.py @@ -40,6 +40,31 @@ autodoc_default_options = { "show-inheritance": True, } +# Mock imports for optional dependencies +autodoc_mock_imports = [ + "riva", + "livekit", + "pyht", + "anthropic", + "assemblyai", + "boto3", + "azure", + "cartesia", + "deepgram", + "elevenlabs", + "fal", + "gladia", + "google", + "krisp", + "langchain", + "lmnt", + "noisereduce", + "openai", + "openpipe", + "simli", + "soundfile", +] + # HTML output settings html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] @@ -47,6 +72,39 @@ autodoc_typehints = "description" html_show_sphinx = False +def verify_modules(): + """Verify that required modules are available.""" + required_modules = { + "services": [ + "assemblyai", + "aws", + "cartesia", + "deepgram", + "google", + "lmnt", + "riva", + "simli", + ], + "serializers": ["livekit"], + "vad": ["silero", "vad_analyzer"], + } + + missing = [] + for category, modules in required_modules.items(): + for module in modules: + try: + __import__(f"pipecat.{category}.{module}") + logger.info(f"Successfully imported pipecat.{category}.{module}") + except ImportError as e: + missing.append(f"pipecat.{category}.{module}") + logger.warning( + f"Optional module not available: pipecat.{category}.{module} - {str(e)}" + ) + + if missing: + logger.warning(f"Some optional modules are not available: {missing}") + + def clean_title(title: str) -> str: """Automatically clean module titles.""" # Remove everything after space (like 'module', 'processor', etc.) @@ -151,3 +209,7 @@ def setup(app): except Exception as e: logger.error(f"Error generating API documentation: {e}", exc_info=True) + + +# Run module verification +verify_modules() diff --git a/docs/api/index.rst b/docs/api/index.rst index a878c0c10..a8cba911d 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -22,6 +22,7 @@ Core Components * :mod:`Frames ` * :mod:`Processors ` * :mod:`Pipeline ` +* :mod:`Services ` Audio Processing ~~~~~~~~~~~~~~~~ diff --git a/docs/api/requirements-base.txt b/docs/api/requirements-base.txt new file mode 100644 index 000000000..6739a1962 --- /dev/null +++ b/docs/api/requirements-base.txt @@ -0,0 +1,36 @@ +# Sphinx dependencies +sphinx>=8.1.3 +sphinx-rtd-theme +sphinx-markdown-builder +sphinx-autodoc-typehints +toml + +# Install all extras individually to ensure they're properly resolved +pipecat-ai[anthropic] +pipecat-ai[assemblyai] +pipecat-ai[aws] +pipecat-ai[azure] +pipecat-ai[canonical] +pipecat-ai[cartesia] +pipecat-ai[deepgram] +pipecat-ai[elevenlabs] +pipecat-ai[fal] +pipecat-ai[fireworks] +pipecat-ai[gladia] +pipecat-ai[google] +pipecat-ai[grok] +pipecat-ai[groq] +pipecat-ai[krisp] +pipecat-ai[langchain] +pipecat-ai[livekit] +pipecat-ai[lmnt] +pipecat-ai[moondream] +pipecat-ai[nim] +pipecat-ai[noisereduce] +pipecat-ai[openai] +pipecat-ai[openpipe] +pipecat-ai[silero] +pipecat-ai[simli] +pipecat-ai[soundfile] +pipecat-ai[websocket] +pipecat-ai[whisper] \ No newline at end of file diff --git a/docs/api/requirements-playht.txt b/docs/api/requirements-playht.txt new file mode 100644 index 000000000..1f0bc24ea --- /dev/null +++ b/docs/api/requirements-playht.txt @@ -0,0 +1,3 @@ +# Force specific grpcio version for PlayHT +grpcio>=1.68.0 +pipecat-ai[playht] \ No newline at end of file diff --git a/docs/api/requirements-riva.txt b/docs/api/requirements-riva.txt new file mode 100644 index 000000000..6bd4c69f9 --- /dev/null +++ b/docs/api/requirements-riva.txt @@ -0,0 +1,3 @@ +# Force specific grpcio version for Riva +grpcio==1.65.4 +pipecat-ai[riva] \ No newline at end of file diff --git a/docs/api/requirements.txt b/docs/api/requirements.txt deleted file mode 100644 index d57bcd00f..000000000 --- a/docs/api/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -sphinx>=8.1.3 -sphinx-rtd-theme -sphinx-markdown-builder -sphinx-autodoc-typehints -toml -pipecat-ai[anthropic,assemblyai,aws,azure,canonical,cartesia,deepgram,elevenlabs,fal,fireworks,gladia,google,grok,groq,krisp,langchain,lmnt,moondream,nim,noisereduce,openai,openpipe,playht,riva,silero,simli,soundfile,websocket,whisper] \ No newline at end of file diff --git a/docs/api/rtd-test.sh b/docs/api/rtd-test.sh new file mode 100755 index 000000000..87d8880b4 --- /dev/null +++ b/docs/api/rtd-test.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -e + +# Configuration +DOCS_DIR=$(pwd) +PROJECT_ROOT=$(cd ../../ && pwd) +TEST_DIR="/tmp/rtd-test-$(date +%Y%m%d_%H%M%S)" + +echo "Creating test directory: $TEST_DIR" +mkdir -p "$TEST_DIR" +cd "$TEST_DIR" + +# Create single virtual environment +python -m venv venv +source venv/bin/activate + +echo "Installing base dependencies..." +pip install --upgrade pip wheel setuptools +pip install -r "$DOCS_DIR/requirements-base.txt" + +# Try to install optional dependencies, but don't fail if they don't work +echo "Installing Riva dependencies..." +pip install -r "$DOCS_DIR/requirements-riva.txt" || echo "Failed to install Riva dependencies" + +echo "Installing PlayHT dependencies..." +pip install -r "$DOCS_DIR/requirements-playht.txt" || echo "Failed to install PlayHT dependencies" + +echo "Building documentation..." +cd "$DOCS_DIR" +sphinx-build -b html . "_build/html" + +echo "Build complete. Check _build/html directory for output." + +# Print installed packages for verification +echo "Installed packages:" +pip freeze \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f3f7d6050..f8b122580 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,11 +66,11 @@ openpipe = [ "openpipe~=4.38.0" ] playht = [ "pyht~=0.1.8", "websockets~=13.1" ] riva = [ "nvidia-riva-client~=2.17.0" ] silero = [ "onnxruntime~=1.20.1" ] +simli = [ "simli-ai~=0.1.7"] soundfile = [ "soundfile~=0.12.1" ] together = [ "openai~=1.57.2" ] websocket = [ "websockets~=13.1", "fastapi~=0.115.0" ] whisper = [ "faster-whisper~=1.1.0" ] -simli = [ "simli-ai~=0.1.7"] [tool.setuptools.packages.find] # All the following settings are optional: diff --git a/src/pipecat/services/anthropic.py b/src/pipecat/services/anthropic.py index 48102eda7..f0c033375 100644 --- a/src/pipecat/services/anthropic.py +++ b/src/pipecat/services/anthropic.py @@ -11,7 +11,7 @@ import json import re from asyncio import CancelledError from dataclasses import dataclass -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Union from loguru import logger from PIL import Image @@ -75,8 +75,7 @@ class AnthropicContextAggregatorPair: class AnthropicLLMService(LLMService): - """ - This class implements inference with Anthropic's AI models. + """This class implements inference with Anthropic's AI models. Can provide a custom client via the `client` kwarg, allowing you to use `AsyncAnthropicBedrock` and `AsyncAnthropicVertex` clients @@ -328,7 +327,7 @@ class AnthropicLLMContext(OpenAILLMContext): tools: list[dict] | None = None, tool_choice: dict | None = None, *, - system: str | NotGiven = NOT_GIVEN, + system: Union[str, NotGiven] = NOT_GIVEN, ): super().__init__(messages=messages, tools=tools, tool_choice=tool_choice) From 44c5220104de8b70881b6114a18d4b5f2b3e853c Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Thu, 12 Dec 2024 13:28:05 -0500 Subject: [PATCH 6/9] Update README --- docs/api/README.md | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/docs/api/README.md b/docs/api/README.md index c0943653f..22b62d45e 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -10,15 +10,17 @@ This directory contains the source files for auto-generating Pipecat's server AP pip install -r requirements.txt ``` -2. Make the build script executable: +2. Make the build scripts executable: ```bash -chmod +x build-docs.sh +chmod +x build-docs.sh rtd-test.py ``` ## Building Documentation -From this directory, run either: +From this directory, you can build the documentation in several ways: + +### Local Build ```bash # Using the build script (automatically opens docs when done) @@ -28,6 +30,24 @@ From this directory, run either: sphinx-build -b html . _build/html -W --keep-going ``` +### ReadTheDocs Test Build + +To test the documentation build process exactly as it would run on ReadTheDocs: + +```bash +./rtd-test.py +``` + +This script: + +- Creates a fresh virtual environment +- Installs all dependencies as specified in requirements files +- Handles conflicting dependencies (like grpcio versions for Riva and PlayHT) +- Builds the documentation in an isolated environment +- Provides detailed logging of the build process + +Use this script to verify your documentation will build correctly on ReadTheDocs before pushing changes. + ## Viewing Documentation The built documentation will be available at `_build/html/index.html`. To open: @@ -52,8 +72,11 @@ start _build/html/index.html ├── _static/ # Static files (images, css, etc.) ├── conf.py # Sphinx configuration ├── index.rst # Main documentation entry point -├── requirements.txt # Documentation dependencies -└── build-docs.sh # Build script matching ReadTheDocs configuration +├── requirements-base.txt # Base documentation dependencies +├── requirements-riva.txt # Riva-specific dependencies +├── requirements-playht.txt # PlayHT-specific dependencies +├── build-docs.sh # Local build script +└── rtd-test.py # ReadTheDocs test build script ``` ## Notes @@ -63,6 +86,7 @@ start _build/html/index.html - The build process matches our ReadTheDocs configuration - Warnings are treated as errors (-W flag) to maintain consistency - The --keep-going flag ensures all errors are reported +- Dependencies are split into multiple requirements files to handle version conflicts ## Troubleshooting @@ -71,6 +95,13 @@ If you encounter missing service modules: 1. Verify the service is installed with its extras: `pip install pipecat-ai[service-name]` 2. Check the build logs for import errors 3. Ensure the service module is properly initialized in the package +4. Run `./rtd-test.py` to test in an isolated environment matching ReadTheDocs + +For dependency conflicts: + +1. Check the requirements files for version specifications +2. Use `rtd-test.py` to verify dependency resolution +3. Consider adding service-specific requirements files if needed For more information: From 8631d71d5ac358107d73601a94660da32a526780 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Thu, 12 Dec 2024 15:16:37 -0500 Subject: [PATCH 7/9] Fix more missing docs --- .readthedocs.yaml | 8 +--- docs/api/README.md | 2 +- docs/api/conf.py | 68 ++++++++++++++++++++++++---------- docs/api/index.rst | 11 +++++- docs/api/requirements-base.txt | 2 + 5 files changed, 63 insertions(+), 28 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index ea03b508e..e4f64ce91 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,10 +9,6 @@ build: # Commands to run before the build - python -m pip install --upgrade pip - pip install wheel setuptools - post_create_environment: - # Install system dependencies required by some packages - - apt-get update - - apt-get install -y portaudio19-dev python3-pyaudio post_build: # Commands to run after the build - echo "Build completed" @@ -36,6 +32,7 @@ python: - azure - canonical - cartesia + - daily - deepgram - elevenlabs - fal @@ -48,6 +45,7 @@ python: - langchain - livekit - lmnt + - local - moondream - nim - noisereduce @@ -64,14 +62,12 @@ formats: - epub - htmlzip -# Cache dependencies to speed up builds search: ranking: api/*: 5 getting-started/*: 4 guides/*: 3 -# Configure submodules if needed submodules: include: all recursive: true diff --git a/docs/api/README.md b/docs/api/README.md index 22b62d45e..392430071 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -7,7 +7,7 @@ This directory contains the source files for auto-generating Pipecat's server AP 1. Install documentation dependencies: ```bash -pip install -r requirements.txt +pip install -r requirements-base.txt requirements-playht.txt requirements-riva.txt ``` 2. Make the build scripts executable: diff --git a/docs/api/conf.py b/docs/api/conf.py index ad969df8c..2744ded19 100644 --- a/docs/api/conf.py +++ b/docs/api/conf.py @@ -63,6 +63,16 @@ autodoc_mock_imports = [ "openpipe", "simli", "soundfile", + # Add these new mocks + "pyaudio", + "_tkinter", + "tkinter", + "daily", + "daily_python", + "pydantic.BaseModel", # Mock base pydantic to avoid model conflicts + "pydantic.Field", + "pydantic._internal._model_construction", + "pydantic._internal._fields", ] # HTML output settings @@ -87,19 +97,40 @@ def verify_modules(): ], "serializers": ["livekit"], "vad": ["silero", "vad_analyzer"], + "transports": { + "services": ["daily", "livekit"], + "local": ["audio", "tk"], + "network": ["fastapi_websocket", "websocket_server"], + }, } missing = [] for category, modules in required_modules.items(): - for module in modules: - try: - __import__(f"pipecat.{category}.{module}") - logger.info(f"Successfully imported pipecat.{category}.{module}") - except ImportError as e: - missing.append(f"pipecat.{category}.{module}") - logger.warning( - f"Optional module not available: pipecat.{category}.{module} - {str(e)}" - ) + if isinstance(modules, dict): + # Handle nested structure + for subcategory, submodules in modules.items(): + for module in submodules: + try: + __import__(f"pipecat.{category}.{subcategory}.{module}") + logger.info( + f"Successfully imported pipecat.{category}.{subcategory}.{module}" + ) + except (ImportError, TypeError, NameError) as e: + missing.append(f"pipecat.{category}.{subcategory}.{module}") + logger.warning( + f"Optional module not available: pipecat.{category}.{subcategory}.{module} - {str(e)}" + ) + else: + # Handle flat structure + for module in modules: + try: + __import__(f"pipecat.{category}.{module}") + logger.info(f"Successfully imported pipecat.{category}.{module}") + except (ImportError, TypeError, NameError) as e: + missing.append(f"pipecat.{category}.{module}") + logger.warning( + f"Optional module not available: pipecat.{category}.{module} - {str(e)}" + ) if missing: logger.warning(f"Some optional modules are not available: {missing}") @@ -167,10 +198,8 @@ def setup(app): logger.info(f"Source directory: {source_dir}") excludes = [ + str(project_root / "src/pipecat/pipeline/to_be_updated"), str(project_root / "src/pipecat/processors/gstreamer"), - str(project_root / "src/pipecat/transports/network"), - str(project_root / "src/pipecat/transports/services"), - str(project_root / "src/pipecat/transports/local"), str(project_root / "src/pipecat/services/to_be_updated"), "**/test_*.py", "**/tests/*.py", @@ -179,12 +208,13 @@ def setup(app): try: main( [ - "-f", - "-e", - "-M", - "--no-toc", - "--separate", - "--module-first", + "-f", # Force overwriting + "-e", # Don't generate empty files + "-M", # Put module documentation before submodule documentation + "--no-toc", # Don't create a table of contents file + "--separate", # Put documentation for each module in its own page + "--module-first", # Module documentation before submodule documentation + "--implicit-namespaces", # Added: Handle implicit namespace packages "-o", output_dir, source_dir, @@ -195,7 +225,7 @@ def setup(app): logger.info("API documentation generated successfully!") # Process generated RST files to update titles - for rst_file in Path(output_dir).glob("*.rst"): + for rst_file in Path(output_dir).glob("**/*.rst"): # Changed to recursive glob content = rst_file.read_text() lines = content.split("\n") diff --git a/docs/api/index.rst b/docs/api/index.rst index a8cba911d..f4773eddc 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -22,7 +22,6 @@ Core Components * :mod:`Frames ` * :mod:`Processors ` * :mod:`Pipeline ` -* :mod:`Services ` Audio Processing ~~~~~~~~~~~~~~~~ @@ -30,10 +29,18 @@ Audio Processing * :mod:`Audio ` * :mod:`VAD ` +Services +~~~~~~~~ + +* :mod:`Services ` + Transport & Serialization ~~~~~~~~~~~~~~~~~~~~~~~~~ * :mod:`Transports ` + * :mod:`Local ` + * :mod:`Network ` + * :mod:`Services ` * :mod:`Serializers ` Utilities @@ -46,7 +53,7 @@ Utilities * :mod:`Utils ` .. toctree:: - :maxdepth: 2 + :maxdepth: 3 :caption: API Reference :hidden: diff --git a/docs/api/requirements-base.txt b/docs/api/requirements-base.txt index 6739a1962..ff397d0ef 100644 --- a/docs/api/requirements-base.txt +++ b/docs/api/requirements-base.txt @@ -12,6 +12,7 @@ pipecat-ai[aws] pipecat-ai[azure] pipecat-ai[canonical] pipecat-ai[cartesia] +pipecat-ai[daily] pipecat-ai[deepgram] pipecat-ai[elevenlabs] pipecat-ai[fal] @@ -24,6 +25,7 @@ pipecat-ai[krisp] pipecat-ai[langchain] pipecat-ai[livekit] pipecat-ai[lmnt] +pipecat-ai[local] pipecat-ai[moondream] pipecat-ai[nim] pipecat-ai[noisereduce] From ec082d08888c12217d5addafc3e7b57b60538f85 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Thu, 12 Dec 2024 15:32:38 -0500 Subject: [PATCH 8/9] Remove deprecated VAD module --- docs/api/conf.py | 1 + docs/api/index.rst | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/api/conf.py b/docs/api/conf.py index 2744ded19..51e000cbf 100644 --- a/docs/api/conf.py +++ b/docs/api/conf.py @@ -201,6 +201,7 @@ def setup(app): str(project_root / "src/pipecat/pipeline/to_be_updated"), str(project_root / "src/pipecat/processors/gstreamer"), str(project_root / "src/pipecat/services/to_be_updated"), + str(project_root / "src/pipecat/vad"), # deprecated "**/test_*.py", "**/tests/*.py", ] diff --git a/docs/api/index.rst b/docs/api/index.rst index f4773eddc..ce7c22113 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -27,7 +27,6 @@ Audio Processing ~~~~~~~~~~~~~~~~ * :mod:`Audio ` -* :mod:`VAD ` Services ~~~~~~~~ @@ -69,7 +68,6 @@ Utilities Transcriptions Transports Utils - VAD Indices and tables ================== From 67804edce6eafef06e3bd8d05b3077cccf17fd3a Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Thu, 12 Dec 2024 15:39:19 -0500 Subject: [PATCH 9/9] Remove formats from .readthedocs.yaml --- .readthedocs.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index e4f64ce91..379e31465 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -57,11 +57,6 @@ python: - websocket - whisper -formats: - - pdf - - epub - - htmlzip - search: ranking: api/*: 5