openrtc 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openrtc-0.2.2 → openrtc-0.2.3}/PKG-INFO +71 -1
- {openrtc-0.2.2 → openrtc-0.2.3}/README.md +70 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/changelog.md +19 -4
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/execution/prewarm.py +18 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/observability/metrics.py +37 -0
- openrtc-0.2.3/tests/test_savings_readout.py +76 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.coderabbit.yaml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.editorconfig +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.env.example +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/FUNDING.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/dependabot.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/audit.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/bench.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/build.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/canary.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/deploy-docs.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/docs.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/integration.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/lint.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/publish.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.github/workflows/test.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.gitignore +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/.pre-commit-config.yaml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/AGENTS.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/CLAUDE.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/CONTRIBUTING.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/LICENSE +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/Makefile +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/assets/banner.png +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/assets/logo.png +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/codecov.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docker-compose.test.yml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/.vitepress/config.ts +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/.vitepress/theme/custom.css +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/.vitepress/theme/index.ts +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/api/pool.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/audit-2026-05-02.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/benchmarks/density-v0.1.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/cli.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/concepts/architecture.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/deployment/github-pages.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/design/agent-server-integration.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/design/job-executor-protocol.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/design/proc-pool-surface.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/design/v0.1.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/examples.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/getting-started.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/index.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/package-lock.json +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/package.json +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/public/banner.png +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/public/logo.png +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/public/logo.svg +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/docs/release-v0.1.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/agents/dental.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/agents/restaurant.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/density_demo.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/.dockerignore +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/.env.example +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/.gitignore +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/Dockerfile +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/README.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/app.css +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/components/agents-ui/agent-audio-visualizer-wave.tsx +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/components/agents-ui/agent-chat-transcript.tsx +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/components/agents-ui/agent-session-provider.tsx +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/components/demo-call-page.tsx +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/root.tsx +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/routes/api.token.ts +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/routes/dentist.tsx +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/routes/home.tsx +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/routes/restaurant.tsx +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/routes.ts +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/welcome/logo-dark.svg +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/welcome/logo-light.svg +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/welcome/welcome.tsx +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/package-lock.json +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/package.json +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/public/favicon.ico +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/react-router.config.ts +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/tsconfig.json +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/vite.config.ts +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/examples/main.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/pyproject.toml +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/__init__.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/cli/__init__.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/cli/commands.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/cli/dashboard.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/cli/entry.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/cli/livekit.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/cli/params.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/cli/reporter.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/cli/types.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/core/__init__.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/core/config.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/core/discovery.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/core/pool.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/core/routing.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/core/serialization.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/core/turn_handling.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/execution/__init__.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/execution/coroutine.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/execution/coroutine_server.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/execution/file_watcher.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/observability/__init__.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/observability/snapshot.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/observability/stream.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/py.typed +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/tui/__init__.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/tui/app.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/src/openrtc/types.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/benchmarks/__init__.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/benchmarks/density.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/benchmarks/throughput.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/conftest.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/execution/__init__.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/execution/test_file_watcher.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/execution/test_file_watcher_smoke.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/integration/README.md +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/integration/__init__.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/integration/conftest.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/integration/test_concurrent_real_calls.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/integration/test_coroutine_realroom.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/integration/test_dev_server_fixture.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_cli.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_cli_optional_extra_integration.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_cli_params.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_config.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_coroutine_backpressure.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_coroutine_coverage.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_coroutine_drain.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_coroutine_isolation.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_coroutine_job_context.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_coroutine_lifecycle.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_coroutine_server.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_coroutine_skeleton.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_coroutine_smoke.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_dashboard.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_discovery.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_isolation_process_parity.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_metrics_stream.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_pool.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_resources.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_routing.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_serialization.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_throughput_bench.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_tui_app.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/tests/test_turn_handling.py +0 -0
- {openrtc-0.2.2 → openrtc-0.2.3}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrtc
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Run multiple LiveKit voice agents in a single shared worker process.
|
|
5
5
|
Project-URL: Homepage, https://github.com/mahimailabs/openrtc
|
|
6
6
|
Project-URL: Repository, https://github.com/mahimailabs/openrtc
|
|
@@ -203,6 +203,76 @@ If a module has no `@agent_config`, the agent name defaults to the filename stem
|
|
|
203
203
|
|
|
204
204
|
Discovered 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.
|
|
205
205
|
|
|
206
|
+
## Migrating from livekit-agents
|
|
207
|
+
|
|
208
|
+
Already running one or more `livekit-agents` workers? Each is its own process that
|
|
209
|
+
loads the same VAD and turn-detector models. Collapse them into one `AgentPool`
|
|
210
|
+
worker without changing your agents.
|
|
211
|
+
|
|
212
|
+
**Before** (one worker per agent, N processes):
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
# restaurant_worker.py (plus a near-identical dental_worker.py, support_worker.py, ...)
|
|
216
|
+
from livekit import agents
|
|
217
|
+
from livekit.agents import Agent, AgentSession
|
|
218
|
+
from livekit.plugins import openai, silero
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class RestaurantAgent(Agent):
|
|
222
|
+
def __init__(self) -> None:
|
|
223
|
+
super().__init__(instructions="You help callers book tables.")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
async def entrypoint(ctx: agents.JobContext) -> None:
|
|
227
|
+
session = AgentSession(
|
|
228
|
+
stt=openai.STT(), llm=openai.LLM(), tts=openai.TTS(), vad=silero.VAD.load()
|
|
229
|
+
)
|
|
230
|
+
await session.start(agent=RestaurantAgent(), room=ctx.room)
|
|
231
|
+
await ctx.connect()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if __name__ == "__main__":
|
|
235
|
+
agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**After** (one worker, N agents, one shared prewarm):
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# worker.py
|
|
242
|
+
from livekit.agents import Agent
|
|
243
|
+
from livekit.plugins import openai
|
|
244
|
+
from openrtc import AgentPool
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class RestaurantAgent(Agent): # unchanged
|
|
248
|
+
def __init__(self) -> None:
|
|
249
|
+
super().__init__(instructions="You help callers book tables.")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class DentalAgent(Agent): # unchanged
|
|
253
|
+
def __init__(self) -> None:
|
|
254
|
+
super().__init__(instructions="You help callers manage appointments.")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
pool = AgentPool(default_stt=openai.STT(), default_llm=openai.LLM(), default_tts=openai.TTS())
|
|
258
|
+
pool.add("restaurant", RestaurantAgent)
|
|
259
|
+
pool.add("dental", DentalAgent)
|
|
260
|
+
pool.run()
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Your `Agent` subclasses, tools, and provider objects are unchanged. You delete the
|
|
264
|
+
per-worker boilerplate (`entrypoint`, `AgentSession` wiring, `cli.run_app`) and
|
|
265
|
+
register the agents on one pool; OpenRTC owns prewarm, routing, and per-call
|
|
266
|
+
session construction. On the first run the worker logs the win, for example:
|
|
267
|
+
|
|
268
|
+
```text
|
|
269
|
+
OpenRTC: 2 agents in 1 worker (baseline ~410 MB). 2 separate livekit-agents
|
|
270
|
+
workers would cost ~820 MB; sharing one worker saves ~410 MB of idle baseline
|
|
271
|
+
(assumes equal per-worker baselines).
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
See [Routing](#routing) for how each incoming call resolves to one registered agent.
|
|
275
|
+
|
|
206
276
|
## Memory: before and after
|
|
207
277
|
|
|
208
278
|
Assume 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.
|
|
@@ -171,6 +171,76 @@ If a module has no `@agent_config`, the agent name defaults to the filename stem
|
|
|
171
171
|
|
|
172
172
|
Discovered 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.
|
|
173
173
|
|
|
174
|
+
## Migrating from livekit-agents
|
|
175
|
+
|
|
176
|
+
Already running one or more `livekit-agents` workers? Each is its own process that
|
|
177
|
+
loads the same VAD and turn-detector models. Collapse them into one `AgentPool`
|
|
178
|
+
worker without changing your agents.
|
|
179
|
+
|
|
180
|
+
**Before** (one worker per agent, N processes):
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
# restaurant_worker.py (plus a near-identical dental_worker.py, support_worker.py, ...)
|
|
184
|
+
from livekit import agents
|
|
185
|
+
from livekit.agents import Agent, AgentSession
|
|
186
|
+
from livekit.plugins import openai, silero
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class RestaurantAgent(Agent):
|
|
190
|
+
def __init__(self) -> None:
|
|
191
|
+
super().__init__(instructions="You help callers book tables.")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def entrypoint(ctx: agents.JobContext) -> None:
|
|
195
|
+
session = AgentSession(
|
|
196
|
+
stt=openai.STT(), llm=openai.LLM(), tts=openai.TTS(), vad=silero.VAD.load()
|
|
197
|
+
)
|
|
198
|
+
await session.start(agent=RestaurantAgent(), room=ctx.room)
|
|
199
|
+
await ctx.connect()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**After** (one worker, N agents, one shared prewarm):
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
# worker.py
|
|
210
|
+
from livekit.agents import Agent
|
|
211
|
+
from livekit.plugins import openai
|
|
212
|
+
from openrtc import AgentPool
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class RestaurantAgent(Agent): # unchanged
|
|
216
|
+
def __init__(self) -> None:
|
|
217
|
+
super().__init__(instructions="You help callers book tables.")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class DentalAgent(Agent): # unchanged
|
|
221
|
+
def __init__(self) -> None:
|
|
222
|
+
super().__init__(instructions="You help callers manage appointments.")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
pool = AgentPool(default_stt=openai.STT(), default_llm=openai.LLM(), default_tts=openai.TTS())
|
|
226
|
+
pool.add("restaurant", RestaurantAgent)
|
|
227
|
+
pool.add("dental", DentalAgent)
|
|
228
|
+
pool.run()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Your `Agent` subclasses, tools, and provider objects are unchanged. You delete the
|
|
232
|
+
per-worker boilerplate (`entrypoint`, `AgentSession` wiring, `cli.run_app`) and
|
|
233
|
+
register the agents on one pool; OpenRTC owns prewarm, routing, and per-call
|
|
234
|
+
session construction. On the first run the worker logs the win, for example:
|
|
235
|
+
|
|
236
|
+
```text
|
|
237
|
+
OpenRTC: 2 agents in 1 worker (baseline ~410 MB). 2 separate livekit-agents
|
|
238
|
+
workers would cost ~820 MB; sharing one worker saves ~410 MB of idle baseline
|
|
239
|
+
(assumes equal per-worker baselines).
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
See [Routing](#routing) for how each incoming call resolves to one registered agent.
|
|
243
|
+
|
|
174
244
|
## Memory: before and after
|
|
175
245
|
|
|
176
246
|
Assume 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.
|
|
@@ -147,12 +147,27 @@ contributor onboarding matches what's in the repo.
|
|
|
147
147
|
|
|
148
148
|
<!-- releases -->
|
|
149
149
|
|
|
150
|
+
## [0.2.2] - 2026-05-30
|
|
151
|
+
|
|
152
|
+
### Fixed
|
|
153
|
+
- Coroutine mode now establishes the LiveKit job context for the session duration, so `get_job_context()` works inside agents and sessions and shutdown callbacks run (MAH-158).
|
|
154
|
+
- Coroutine sessions are held open until the call ends (room disconnect or `ctx.shutdown()`) instead of being marked SUCCESS when the entrypoint returns, so `max_concurrent_sessions` backpressure and runtime session counts are accurate (MAH-160).
|
|
155
|
+
|
|
156
|
+
### Added
|
|
157
|
+
- Real-audio throughput benchmark (`tests/benchmarks/throughput.py`) reporting steady-state event-loop p99 vs session count, separating startup from steady state (MAH-163).
|
|
158
|
+
- `examples/density_demo.py`: a no-server demo comparing process-per-session vs coroutine-pool resident memory.
|
|
159
|
+
|
|
160
|
+
### Changed
|
|
161
|
+
- The coroutine real-room integration test is now a correctness gate (job context plus no-failure); throughput moved to the dedicated benchmark.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
150
165
|
## [0.2.1] - 2026-05-06
|
|
151
166
|
|
|
152
|
-
## What's Changed
|
|
153
|
-
* [v0.2.1] File watcher infrastructure for agent code (MAH-80) by @mahimairaja in https://github.com/mahimailabs/openrtc-runtime/pull/39
|
|
154
|
-
|
|
155
|
-
|
|
167
|
+
## What's Changed
|
|
168
|
+
* [v0.2.1] File watcher infrastructure for agent code (MAH-80) by @mahimairaja in https://github.com/mahimailabs/openrtc-runtime/pull/39
|
|
169
|
+
|
|
170
|
+
|
|
156
171
|
**Full Changelog**: https://github.com/mahimailabs/openrtc-runtime/compare/v0.1.0...v0.2.1
|
|
157
172
|
|
|
158
173
|
---
|
|
@@ -10,13 +10,21 @@ Adding a new shared resource means adding it to ``_prewarm_worker``.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import logging
|
|
13
14
|
from typing import TYPE_CHECKING, Any
|
|
14
15
|
|
|
15
16
|
from livekit.agents import JobProcess
|
|
16
17
|
|
|
18
|
+
from openrtc.observability.metrics import (
|
|
19
|
+
format_prewarm_savings,
|
|
20
|
+
process_resident_set_bytes,
|
|
21
|
+
)
|
|
22
|
+
|
|
17
23
|
if TYPE_CHECKING:
|
|
18
24
|
from openrtc.core.pool import _PoolRuntimeState
|
|
19
25
|
|
|
26
|
+
logger = logging.getLogger("openrtc.execution.prewarm")
|
|
27
|
+
|
|
20
28
|
|
|
21
29
|
def _prewarm_worker(
|
|
22
30
|
runtime_state: _PoolRuntimeState,
|
|
@@ -29,6 +37,16 @@ def _prewarm_worker(
|
|
|
29
37
|
proc.userdata["vad"] = silero_module.VAD.load()
|
|
30
38
|
proc.userdata["turn_detection_factory"] = turn_detector_model
|
|
31
39
|
|
|
40
|
+
# Day-one payoff: surface the fleet-collapse idle-baseline saving once per
|
|
41
|
+
# worker, post-prewarm (RSS is now the per-worker baseline), so existing
|
|
42
|
+
# livekit-agents users see the win on first run without --dashboard.
|
|
43
|
+
logger.info(
|
|
44
|
+
format_prewarm_savings(
|
|
45
|
+
agent_count=len(runtime_state.agents),
|
|
46
|
+
shared_worker_bytes=process_resident_set_bytes(),
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
32
50
|
|
|
33
51
|
def _load_shared_runtime_dependencies() -> tuple[Any, type[Any]]:
|
|
34
52
|
"""Load the optional LiveKit runtime dependencies used during prewarm."""
|
|
@@ -352,6 +352,43 @@ def estimate_shared_worker_savings(
|
|
|
352
352
|
)
|
|
353
353
|
|
|
354
354
|
|
|
355
|
+
def format_prewarm_savings(*, agent_count: int, shared_worker_bytes: int | None) -> str:
|
|
356
|
+
"""One honest, human-readable line about the shared-worker idle-baseline win.
|
|
357
|
+
|
|
358
|
+
Emitted once per worker at prewarm so the fleet-collapse saving is visible on
|
|
359
|
+
first run. It claims only idle-baseline memory saved by hosting N per-agent
|
|
360
|
+
workers as one shared worker; it never implies per-session density or a speed
|
|
361
|
+
multiple, and it names its equal-baseline assumption whenever it shows a
|
|
362
|
+
number. Stays graceful when RSS is unavailable (e.g. Windows).
|
|
363
|
+
"""
|
|
364
|
+
estimate = estimate_shared_worker_savings(
|
|
365
|
+
agent_count=agent_count, shared_worker_bytes=shared_worker_bytes
|
|
366
|
+
)
|
|
367
|
+
agents = "1 agent" if agent_count == 1 else f"{agent_count} agents"
|
|
368
|
+
|
|
369
|
+
if estimate.shared_worker_bytes is None or estimate.estimated_saved_bytes is None:
|
|
370
|
+
return (
|
|
371
|
+
f"OpenRTC: {agents} in 1 worker; per-worker memory estimate "
|
|
372
|
+
"unavailable on this platform."
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
baseline_mb = estimate.shared_worker_bytes / (1024 * 1024)
|
|
376
|
+
if agent_count <= 1:
|
|
377
|
+
return (
|
|
378
|
+
f"OpenRTC: {agents} in this worker (baseline ~{baseline_mb:.0f} MB). "
|
|
379
|
+
"Register more agents on the pool to amortize the shared prewarm."
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
separate_mb = (estimate.estimated_separate_workers_bytes or 0) / (1024 * 1024)
|
|
383
|
+
saved_mb = estimate.estimated_saved_bytes / (1024 * 1024)
|
|
384
|
+
return (
|
|
385
|
+
f"OpenRTC: {agents} in 1 worker (baseline ~{baseline_mb:.0f} MB). "
|
|
386
|
+
f"{agent_count} separate livekit-agents workers would cost "
|
|
387
|
+
f"~{separate_mb:.0f} MB; sharing one worker saves ~{saved_mb:.0f} MB "
|
|
388
|
+
"of idle baseline (assumes equal per-worker baselines)."
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
355
392
|
def _linux_rss_bytes() -> int | None:
|
|
356
393
|
"""Read VmRSS (kiB in procfs) and convert to bytes."""
|
|
357
394
|
try:
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Day-one savings readout: the format_prewarm_savings line and its prewarm emit.
|
|
2
|
+
|
|
3
|
+
The readout makes the fleet-collapse idle-baseline win visible on first run. It
|
|
4
|
+
claims only idle-baseline memory saved (never per-session density or a speed
|
|
5
|
+
multiple) and stays graceful when RSS is unavailable.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from types import SimpleNamespace
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from openrtc.execution import prewarm as prewarm_module
|
|
16
|
+
from openrtc.execution.prewarm import _prewarm_worker
|
|
17
|
+
from openrtc.observability.metrics import format_prewarm_savings
|
|
18
|
+
|
|
19
|
+
_MB = 1024 * 1024
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_savings_line_for_multiple_agents() -> None:
|
|
23
|
+
line = format_prewarm_savings(agent_count=3, shared_worker_bytes=400 * _MB)
|
|
24
|
+
assert "3 agents" in line
|
|
25
|
+
assert "400 MB" in line # shared baseline
|
|
26
|
+
assert "1200 MB" in line # 3 separate workers
|
|
27
|
+
assert "800 MB" in line # idle baseline saved: (3 - 1) * 400
|
|
28
|
+
assert "assumes" in line.lower()
|
|
29
|
+
assert "saves" in line.lower()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_neutral_line_for_single_agent() -> None:
|
|
33
|
+
line = format_prewarm_savings(agent_count=1, shared_worker_bytes=400 * _MB)
|
|
34
|
+
assert "1 agent" in line
|
|
35
|
+
assert "saves" not in line.lower() # no boast for a single agent
|
|
36
|
+
assert "amortize" in line.lower()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_graceful_when_rss_unavailable() -> None:
|
|
40
|
+
line = format_prewarm_savings(agent_count=3, shared_worker_bytes=None)
|
|
41
|
+
assert "3 agents" in line
|
|
42
|
+
assert "unavailable" in line.lower()
|
|
43
|
+
assert "MB" not in line # no fabricated number
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_prewarm_emits_one_savings_line(
|
|
47
|
+
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
|
48
|
+
) -> None:
|
|
49
|
+
class _FakeVAD:
|
|
50
|
+
@staticmethod
|
|
51
|
+
def load() -> str:
|
|
52
|
+
return "vad"
|
|
53
|
+
|
|
54
|
+
class _FakeSilero:
|
|
55
|
+
VAD = _FakeVAD
|
|
56
|
+
|
|
57
|
+
class _FakeTurnDetector:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
monkeypatch.setattr(
|
|
61
|
+
prewarm_module,
|
|
62
|
+
"_load_shared_runtime_dependencies",
|
|
63
|
+
lambda: (_FakeSilero, _FakeTurnDetector),
|
|
64
|
+
)
|
|
65
|
+
runtime_state = SimpleNamespace(agents={"a": object(), "b": object()})
|
|
66
|
+
proc = SimpleNamespace(userdata={}, inference_executor=None)
|
|
67
|
+
|
|
68
|
+
with caplog.at_level(logging.INFO, logger="openrtc.execution.prewarm"):
|
|
69
|
+
_prewarm_worker(runtime_state, proc) # type: ignore[arg-type]
|
|
70
|
+
|
|
71
|
+
records = [r for r in caplog.records if r.name == "openrtc.execution.prewarm"]
|
|
72
|
+
assert len(records) == 1
|
|
73
|
+
message = records[0].getMessage()
|
|
74
|
+
assert message.startswith("OpenRTC:")
|
|
75
|
+
assert "2 agents" in message
|
|
76
|
+
assert "saves" in message.lower() # RSS is available in-process, so a number
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openrtc-0.2.2 → openrtc-0.2.3}/examples/frontend/app/components/agents-ui/agent-chat-transcript.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|