{"id":47619898,"url":"https://github.com/mahimairaja/openrtc-python","last_synced_at":"2026-04-01T21:58:34.319Z","repository":{"id":345538533,"uuid":"1186307340","full_name":"mahimairaja/openrtc-python","owner":"mahimairaja","description":"OpenRTC lets developers run multiple LiveKit voice agents in one Python worker, sharing heavy models instead of duplicating them per process.","archived":false,"fork":false,"pushed_at":"2026-03-19T19:15:00.000Z","size":759,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-20T06:48:53.712Z","etag":null,"topics":["agent-deployments","livekit","livekit-agents","openrtc","voice-agent","voice-ai"],"latest_commit_sha":null,"homepage":"https://mahimairaja.github.io/openrtc-python/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mahimairaja.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-19T13:44:11.000Z","updated_at":"2026-03-19T19:15:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mahimairaja/openrtc-python","commit_stats":null,"previous_names":["mahimairaja/openrtc-python"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/mahimairaja/openrtc-python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahimairaja%2Fopenrtc-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahimairaja%2Fopenrtc-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahimairaja%2Fopenrtc-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahimairaja%2Fopenrtc-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mahimairaja","download_url":"https://codeload.github.com/mahimairaja/openrtc-python/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahimairaja%2Fopenrtc-python/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31292631,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-deployments","livekit","livekit-agents","openrtc","voice-agent","voice-ai"],"created_at":"2026-04-01T21:58:31.284Z","updated_at":"2026-04-01T21:58:34.313Z","avatar_url":"https://github.com/mahimairaja.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://github.com/mahimailabs/openrtc\"\u003e\n    \u003cimg src=\"assets/banner.png\" alt=\"OpenRTC Banner\" width=\"100%\" /\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr /\u003e\n\n# openrtc-python\n\nRun N LiveKit voice agents in one worker. Pay the model-load cost once.\n\n*PyPI package name: [`openrtc`](https://pypi.org/project/openrtc/).*\n\n\u003cbr /\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" alt=\"License: MIT\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.python.org/downloads/\"\u003e\u003cimg src=\"https://img.shields.io/badge/python-3.11+-blue.svg\" alt=\"Python Version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/astral-sh/ruff\"\u003e\u003cimg src=\"https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json\" alt=\"Ruff\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://pypi.org/project/openrtc/\"\u003e\u003cimg src=\"https://img.shields.io/pypi/v/openrtc.svg\" alt=\"PyPI version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/mahimairaja/openrtc-python\"\u003e\u003cimg src=\"https://codecov.io/gh/mahimairaja/openrtc-python/graph/badge.svg?token=W7VQ5FGSA9\" alt=\"codecov\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/mahimairaja/openrtc-python/actions/workflows/test.yml\"\u003e\u003cimg src=\"https://github.com/mahimairaja/openrtc-python/actions/workflows/test.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr /\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003eTable of Contents\u003c/b\u003e\u003c/summary\u003e\n  \u003col\u003e\n    \u003cli\u003e\u003ca href=\"#the-problem\"\u003eThe problem\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#what-openrtc-does\"\u003eWhat openrtc does\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#installation\"\u003eInstallation\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#quick-start-explicit-registration-with-add\"\u003eQuick start: explicit registration with add()\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#quick-start-one-python-file-per-agent-with-discover\"\u003eQuick start: one Python file per agent with discover()\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#memory-before-and-after\"\u003eMemory: before and after\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#routing\"\u003eRouting\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#greetings-and-session-options\"\u003eGreetings and session options\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#provider-configuration\"\u003eProvider configuration\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#cli-and-tui\"\u003eCLI and TUI\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#public-api-at-a-glance\"\u003ePublic API at a glance\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#project-structure\"\u003eProject structure\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#contributing\"\u003eContributing\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#license\"\u003eLicense\u003c/a\u003e\u003c/li\u003e\n  \u003c/ol\u003e\n\u003c/details\u003e\n\n\u003cbr /\u003e\n\n## The problem\n\nYou already ship three voice agents with `livekit-agents`. Each agent is its own worker on the same VPS. Every worker process loads the same shared stack: Python runtime, Silero VAD, and the turn-detection model. You are not loading three different models. You are loading the same stack three times because the process boundary forces it. On a 1–2 GB instance, that shows up as duplicate resident set for every idle worker. You pay RAM for copies you do not need.\n\n## What openrtc does\n\n`openrtc` gives you one `AgentPool` in one worker: prewarm runs once, each incoming call still gets its own `AgentSession`, and you register multiple `Agent` subclasses on the pool so dispatch can pick one per session from metadata or fallbacks. This package does not replace your agent code. It does not sit between you and `livekit.agents.Agent`, `@function_tool`, `RunContext`, `on_enter`, `on_exit`, `llm_node`, `stt_node`, or `tts_node`. You keep your subclasses and tools as they are. You change how many workers you run, not how you write an agent.\n\n## Installation\n\nOpenRTC **requires Python 3.11 or newer**. The LiveKit Silero / turn-detector\nplugins depend on `onnxruntime`, which does not ship supported wheels for\nPython 3.10 in current releases—use 3.11+ to avoid install failures.\n\n```bash\npip install openrtc\n```\n\nThe base install pulls in `livekit-agents[openai,silero,turn-detector]` so shared prewarm has the plugins it expects. The package ships a **PEP 561** `py.typed` marker for downstream type checkers.\n\nWith **uv** (recommended in [CONTRIBUTING.md](CONTRIBUTING.md)):\n\n```bash\nuv add openrtc\nuv add \"openrtc[cli,tui]\"\n```\n\n```bash\npip install 'openrtc[cli]'\n```\n\nOptional Textual sidecar for live metrics:\n\n```bash\npip install 'openrtc[cli,tui]'\n```\n\nSet the same variables you use for any LiveKit worker:\n\n```bash\nexport LIVEKIT_URL=ws://localhost:7880\nexport LIVEKIT_API_KEY=devkey\nexport LIVEKIT_API_SECRET=secret\n```\n\nFor OpenAI-backed plugins, set `OPENAI_API_KEY` as you already do.\n\n## Quick start: explicit registration with `add()`\n\nUse this when you want every agent registered in one place with explicit names and providers.\n\n```python\nfrom livekit.agents import Agent\nfrom livekit.plugins import openai\nfrom openrtc import AgentPool\n\n\nclass RestaurantAgent(Agent):\n    def __init__(self) -\u003e None:\n        super().__init__(instructions=\"You help callers make restaurant bookings.\")\n\n\nclass DentalAgent(Agent):\n    def __init__(self) -\u003e None:\n        super().__init__(instructions=\"You help callers manage dental appointments.\")\n\n\npool = AgentPool()\npool.add(\n    \"restaurant\",\n    RestaurantAgent,\n    stt=openai.STT(model=\"gpt-4o-mini-transcribe\"),\n    llm=openai.responses.LLM(model=\"gpt-4.1-mini\"),\n    tts=openai.TTS(model=\"gpt-4o-mini-tts\"),\n    greeting=\"Welcome to reservations.\",\n)\npool.add(\n    \"dental\",\n    DentalAgent,\n    stt=openai.STT(model=\"gpt-4o-mini-transcribe\"),\n    llm=openai.responses.LLM(model=\"gpt-4.1-mini\"),\n    tts=openai.TTS(model=\"gpt-4o-mini-tts\"),\n)\npool.run()\n```\n\n## Quick start: one Python file per agent with `discover()`\n\nUse this when you prefer one module per agent and optional `@agent_config(...)` on each class.\n\nCreate a directory (for example `agents/`) and add one `.py` file per agent. Then:\n\n```python\nfrom pathlib import Path\n\nfrom livekit.plugins import openai\nfrom openrtc import AgentPool\n\npool = AgentPool(\n    default_stt=openai.STT(model=\"gpt-4o-mini-transcribe\"),\n    default_llm=openai.responses.LLM(model=\"gpt-4.1-mini\"),\n    default_tts=openai.TTS(model=\"gpt-4o-mini-tts\"),\n)\npool.discover(Path(\"./agents\"))\npool.run()\n```\n\nExample file `agents/restaurant.py`:\n\n```python\nfrom livekit.agents import Agent\nfrom openrtc import agent_config\n\n\n@agent_config(name=\"restaurant\", greeting=\"Welcome to reservations.\")\nclass RestaurantAgent(Agent):\n    def __init__(self) -\u003e None:\n        super().__init__(instructions=\"You help callers make restaurant bookings.\")\n```\n\nIf a module has no `@agent_config`, the agent name defaults to the filename stem. STT, LLM, TTS, and greeting fall back to the pool defaults.\n\nDiscovered agents work with `livekit dev` and spawn-based workers on macOS. For `add()`, define agent classes at module scope so worker reload can import them.\n\n## Memory: before and after\n\nAssume an illustrative **~400 MB** idle baseline per worker for the shared stack (VAD, turn detector, and similar). Your measured RSS will differ by provider, model, and OS.\n\n| | Before openrtc | After openrtc |\n| --- | --- | --- |\n| Three workers, same stack | about **3 × 400 MB ≈ 1.2 GB** idle baseline (three loads) | — |\n| One worker, three registered agents | — | about **one × 400 MB** idle baseline (one load) plus per-session overhead |\n\nExact numbers depend on your providers, concurrency, and call patterns. The win is not loading that stack once per agent worker.\n\n## Routing\n\nOne process hosts several agent classes, so each session must resolve to a single registered name. `AgentPool` resolves the agent in this order:\n\n1. `ctx.job.metadata[\"agent\"]`\n2. `ctx.job.metadata[\"demo\"]`\n3. `ctx.room.metadata[\"agent\"]`\n4. `ctx.room.metadata[\"demo\"]`\n5. room name prefix match, such as `restaurant-call-123`\n6. the first registered agent\n\nIf metadata names an agent that is not registered, you get a `ValueError` instead of a silent fallback.\n\n## Greetings and session options\n\nYou can pass a greeting and extra `AgentSession` options per registration.\n\n```python\npool.add(\n    \"restaurant\",\n    RestaurantAgent,\n    greeting=\"Welcome to reservations.\",\n    session_kwargs={\"turn_handling\": {\"interruption\": {\"enabled\": False}}},\n    max_tool_steps=4,\n    preemptive_generation=True,\n)\n```\n\nDirect keyword arguments win over the same keys inside `session_kwargs`.\n\nBy default, OpenRTC sets explicit `turn_handling` with the multilingual turn detector and VAD-based interruption. To opt into adaptive interruption, pass `session_kwargs={\"turn_handling\": {\"interruption\": {\"mode\": \"adaptive\"}}}`.\n\n## Provider configuration\n\nPass instantiated provider objects through to `livekit-agents` unchanged, for example:\n\n- `openai.STT(model=\"gpt-4o-mini-transcribe\")`\n- `openai.responses.LLM(model=\"gpt-4.1-mini\")`\n- `openai.TTS(model=\"gpt-4o-mini-tts\")`\n\nIf you pass strings such as `openai/gpt-4.1-mini`, OpenRTC leaves them as-is and the LiveKit runtime interprets them for your deployment.\n\n## CLI and TUI\n\nInstall `openrtc[cli]` to get `openrtc` on your PATH. Subcommands follow the LiveKit Agents CLI shape (`dev`, `start`, `console`, `connect`, `download-files`), plus `list` and `tui`. For most commands you can pass the agents directory (or, for `tui`, the metrics JSONL file) as the first path argument instead of `--agents-dir` / `--watch`.\n\n**List what discovery would register** (defaults are string passthroughs for `livekit-agents`, not constructed provider objects):\n\n```bash\nopenrtc list \\\n  ./agents \\\n  --default-stt openai/gpt-4o-mini-transcribe \\\n  --default-llm openai/gpt-4.1-mini \\\n  --default-tts openai/gpt-4o-mini-tts\n```\n\n**Run a production worker** (after exporting `LIVEKIT_*`):\n\n```bash\nopenrtc start ./agents\n```\n\n**Run a development worker**:\n\n```bash\nopenrtc dev ./agents\n```\n\nSame as ``openrtc dev --agents-dir ./agents``. The metrics JSONL file is **optional**: add a second path only when you want JSONL output (same as ``--metrics-jsonl``), e.g. ``openrtc dev ./agents ./openrtc-metrics.jsonl`` for ``openrtc tui``.\n\nOptional visibility: `--dashboard` prints a Rich summary in the terminal. `--metrics-json-file ./runtime.json` overwrites a JSON snapshot on each tick. Use that for scripts, dashboards, or CI. For JSON Lines plus a separate terminal UI, use `--metrics-jsonl ./openrtc-metrics.jsonl` on the worker and `openrtc tui` in another terminal (it tails `./openrtc-metrics.jsonl` by default; override with `--watch`) after `pip install 'openrtc[cli,tui]'`.\n\nStable machine output: `openrtc list --json` and `--plain`. Combine `--resources` when you want footprint hints. OpenRTC-only flags are stripped before the handoff to LiveKit’s CLI parser.\n\nFull flag lists live in [docs/cli.md](docs/cli.md).\n\n## Public API at a glance\n\nEverything openrtc exposes publicly is listed here. Anything else is internal and not treated as stable.\n\n- `AgentPool`\n- `AgentConfig`\n- `AgentDiscoveryConfig`\n- `agent_config(...)`\n- `ProviderValue` — type alias for STT/LLM/TTS slot values (provider ID strings or LiveKit plugin instances)\n\nOn `AgentPool`:\n\n- `add(...)`\n- `discover(...)`\n- `list_agents()`\n- `get(name)`\n- `remove(name)`\n- `run()`\n- `runtime_snapshot()`\n- `drain_metrics_stream_events()` — for JSONL export paths (mainly CLI; rare in app code)\n- `server`\n\n## Project structure\n\n```text\nsrc/openrtc/\n├── __init__.py\n├── py.typed\n├── cli.py                 # lazy console entry / missing-extra hints\n├── cli_app.py             # Typer commands and programmatic main()\n├── cli_types.py           # shared CLI option aliases\n├── cli_dashboard.py     # Rich dashboard and list output\n├── cli_reporter.py        # background metrics reporter thread\n├── cli_livekit.py         # LiveKit argv/env handoff, pool run\n├── cli_params.py          # shared worker handoff option bundles\n├── metrics_stream.py      # JSONL metrics schema\n├── provider_types.py      # ProviderValue and related typing\n├── tui_app.py             # optional Textual sidecar\n└── pool.py                # AgentPool, discovery, routing\n```\n\n- `pool.py` — `AgentPool`, discovery, routing\n- `cli.py` / `cli_app.py` — Typer/Rich CLI (`openrtc[cli]`)\n- `metrics_stream.py` — JSONL metrics schema\n- `tui_app.py` — optional Textual sidecar (`openrtc[tui]`)\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). CI runs **Ruff** and **mypy** on pull\nrequests alongside the test suite.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmahimairaja%2Fopenrtc-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmahimairaja%2Fopenrtc-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmahimairaja%2Fopenrtc-python/lists"}