Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,38 @@ jobs:
tests/tts/test_tts_openai.py
tests/server/test_webrtc_routes.py

langchain-versions:
name: LangChain ${{ matrix.line }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- line: v0
extra: langchain-v0
- line: v1
extra: langchain
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.1"
enable-cache: true
python-version: "3.12"
- name: Create isolated LangChain environment
run: uv venv --python 3.12 .venv-langchain
- name: Install documented LangChain line
run: >-
uv pip install --python .venv-langchain/bin/python
-e ".[quickstart,${{ matrix.extra }}]"
- name: Smoke examples and bridge runtime
run: >-
.venv-langchain/bin/python
scripts/smoke_langchain_versions.py --line ${{ matrix.line }}

coverage:
name: Coverage + guard suite (diff-cover report-only)
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/nightly-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ jobs:
# - pydantic-ai and pydantic-ai-v2 are mutually exclusive pins; they
# never co-install because each cell syncs exactly one extra into a fresh
# environment (uv sync is exact and removes anything not requested).
# - langchain and langchain-v0 are separate 1.x / 0.3.x lines. Each cell
# installs one complete line and runs the same exact bridge contract.
# - ten-vad is deliberately excluded: its non-permissive license is an
# end-user opt-in, and installing it here would have project CI accept
# those terms nightly. See EXCLUDED_EXTRAS in scripts/extras_matrix.py.
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ raw equivalent in the table below. Install it with `uv tool install rust-just`,
| Lint auto-fix | `just lint-fix` | `uv run ruff check --fix .` |
| Format | `just fmt` | `uv run ruff format .` |
| Format check | `just fmt-check` | `uv run ruff format --check .` |
| Type gate (mypy, whole package) | `just typecheck` | `uv run mypy src/easycat` |
| Type gate (mypy + LangChain smoke script) | `just typecheck` | `uv run mypy src/easycat scripts/smoke_langchain_versions.py` |
| Fast types (ty, advisory) | `just typecheck-fast` | `uvx ty check src/easycat` |
| Coverage | `just cov` | `uv run pytest -n auto --dist load --cov --cov-report=term-missing -m "not integration_socket and not integration_live and not integration_external and not contract and not latency and not slow and not stress and not serial and not flaky and not guard"` |
| Validate (quick) | `just validate-quick` | `uv run easycat validate quick` |
Expand All @@ -63,7 +63,7 @@ raw equivalent in the table below. Install it with `uv tool install rust-just`,
| Validate (release) | `just validate-release` | `uv run easycat validate release` |
| Validate report | `just validate-report .easycat/validation/latest.json` | `uv run easycat validate report .easycat/validation/latest.json` |
| Pre-commit hooks | `just pre-commit` | `uv run pre-commit run --all-files` |
| Pre-PR gauntlet | `just check` | `uv run pre-commit run --all-files && uv run mypy src/easycat && uv run pytest -n auto --dist loadscope -m "not integration_live and not integration_external and not serial" && uv run pytest -o faulthandler_timeout=0 -o timeout=0 -m "serial and not integration_live and not integration_external"` |
| Pre-PR gauntlet | `just check` | `uv run pre-commit run --all-files && uv run mypy src/easycat scripts/smoke_langchain_versions.py && uv run pytest -n auto --dist loadscope -m "not integration_live and not integration_external and not serial" && uv run pytest -o faulthandler_timeout=0 -o timeout=0 -m "serial and not integration_live and not integration_external"` |

`just check` mirrors CI's core source-quality gates, but it is not a literal
replay of the workflow: CI also covers the supported Python matrix, minimum
Expand Down
7 changes: 4 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,10 @@ duration-drift, AEC reference, VAD/pre-roll, and transport accounting tests.
can faithfully consume the committed lockfile. The `uv_build` requirement is
an independent bounded build-backend range.
- A dependency extra never spans behaviorally incompatible SDK majors.
PydanticAI v2 is the primary supported line; any v1 compatibility extra is
explicitly named, tested separately, and given a documented retirement
path.
PydanticAI and LangChain major lines are explicitly named and tested in
isolated environments. PydanticAI v2 is the primary supported line, and its
v1 compatibility extra has a documented retirement path; LangChain 0.3 and
1.x remain separate supported lines.
- Extras describe dependencies, not whether a capability exists. Provider
discovery comes from the catalog, so empty marker extras are not a capability
registry.
Expand Down
16 changes: 12 additions & 4 deletions docs/browser-playground.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,15 @@ Useful options:
defaults.
- `--token` (or `EASYCAT_SERVE_TOKEN`) — shared secret required by the
signaling endpoints. `easycat serve` refuses a non-loopback `--host`
without a token. The printed Open URL embeds it as `?token=...` and the
bundled client forwards it as an `Authorization: Bearer` header.
without a token. For loopback, the printed Open URL embeds it in a
`#token=...` fragment, which is not sent in the HTTP request; the bundled
client removes it from the visible URL and forwards it as an
`Authorization: Bearer` header.
- `--public-url` (or `EASYCAT_SERVE_PUBLIC_URL`) — external HTTPS origin used
for the printed browser link when a non-loopback server sits behind a
TLS-terminating proxy. Without this option, EasyCat does not print a
token-bearing direct-HTTP URL; bearer credentials must not cross an
unencrypted network.
- `--agent-model` / `--instructions` — swap the playground agent's OpenAI
Responses API model or its guidance.
- `--manifest` / `--profile` — build a manifest-backed `VoiceServer` instead
Expand Down Expand Up @@ -93,8 +100,9 @@ Outbound (server → client):
Audio flows over the Opus peer connection. Signaling is HTTP
(`POST /offer`, `GET /config`, `POST /stats`, `GET /health`); when
`WebRTCTransportConfig.auth_token` is set, `/config`, `/offer`, and `/stats`
require the token as `Authorization: Bearer <token>` or a `?token=` query
parameter.
require the token as `Authorization: Bearer <token>`. A `?token=` query
parameter is accepted only when `allow_query_token=True`; it is disabled by
default because URLs leak more readily through logs and browser history.
Session event messages arrive on a client-created data channel named
`events`.

Expand Down
24 changes: 18 additions & 6 deletions docs/deployment/production-servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,19 @@ ICE server configuration for the bundled browser client, `POST /stats` accepts
sanitized WebRTC stats snapshots, and `/health` reports `status`,
`active_sessions`, and `max_sessions` for readiness checks.
For public deployments, put the signaling server behind HTTPS so
`getUserMedia()` works, configure TURN, set `SIGNALING_AUTH_TOKEN` so `/offer`
and `/stats` require a bearer/query token, and tune `WEBRTC_MAX_SESSIONS` from
load-test data before raising the default cap.
`getUserMedia()` works, configure TURN, set `WEBRTC_SIGNALING_TOKEN` so
`/config`, `/offer`, and `/stats` require a bearer token, and tune
`WEBRTC_MAX_SESSIONS` from load-test data before raising the default cap. The
bundled client can read that token from its initial `#token=` fragment, removes
it from the visible URL, and forwards it in the `Authorization` header. URL
fragments are not included in HTTP requests, while direct `?token=` query
authentication remains off unless `allow_query_token=True` is set explicitly.

The server can use configured TURN credentials without returning TURN entries
from `/config`: the browser receives STUN-only config while the server peer can
still gather a relay candidate. Browser-side relay requires
`WEBRTC_EXPOSE_ICE_CREDENTIALS=1`; expose only short-lived TURN credentials (or
use this with a trusted demo), because every authorized client can read them.

### Flat routes vs. the `VoiceServer` `/webrtc/*` namespace

Expand All @@ -232,9 +242,11 @@ implementation (`easycat.server.webrtc_routes.WebRTCRoutes`):

The SAME bundled client HTML serves both. It resolves its route base from a
`?webrtc=<prefix>` query parameter (defaulting to `""` for the flat helper);
`VoiceServer`'s root redirect appends `?webrtc=/webrtc` (preserving any
`?token=`) so the served client targets the namespaced routes. A custom client
can target either surface by setting `?webrtc=` (or its own base) accordingly.
`VoiceServer`'s root redirect appends `?webrtc=/webrtc` so the served client
targets the namespaced routes. It drops any legacy `?token=` rather than copy a
secret into another HTTP request; bootstrap the bundled client with the
`#token=` fragment instead. A custom client can target either surface by
setting `?webrtc=` (or its own base) accordingly.

## WebTransport servers

Expand Down
19 changes: 14 additions & 5 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ Commands below are for this repository. In an application, use the equivalent
| OpenAI Agents SDK | `uv sync --extra openai-agents --group dev` |
| PydanticAI stable v1 | `uv sync --extra pydantic-ai --group dev` |
| PydanticAI stable v2 | `uv sync --extra pydantic-ai-v2 --group dev` |
| LangChain core | `uv sync --extra langchain --group dev` |
| LangChain 1.x | `uv sync --extra langchain --group dev` |
| LangChain 0.3 | `uv sync --extra langchain-v0 --group dev` |
| LangGraph | `uv sync --extra langgraph --group dev` |
| LlamaAgents/LlamaIndex workflows | `uv sync --extra llama-agents --group dev` |
| OpenAI providers | `uv sync --extra openai --group dev` |
Expand All @@ -121,12 +122,19 @@ uv pip install krisp_audio
```

Deepgram, ElevenLabs, and Cartesia use EasyCat's core WebSocket/HTTP stack, so
their extras are install markers and do not add vendor SDKs. LangChain model
packages such as `langchain-openai` remain application choices.
their extras are install markers and do not add vendor SDKs. The `langchain`
and `langchain-v0` extras each install a complete, compatible LangChain and
`langchain-openai` line for the maintained examples. Other LangChain model
integrations remain application choices.

The `pydantic-ai` extra targets stable v1. The `pydantic-ai-v2` extra installs
`pydantic-ai>=2.24.0,<3.0.0`; the two extras are mutually exclusive.

The `langchain` extra targets 1.x and `langchain-v0` targets 0.3.x. Both lines
run the same EasyCat bridge contract in isolated CI environments. They are
mutually exclusive, and `langchain-v0` also cannot be combined with `langgraph`
or `all` because those selections require `langchain-core` 1.x.

## Broad evaluation install

For a downstream application evaluating most integrations:
Expand All @@ -146,8 +154,9 @@ uv sync --extra all --extra pydantic-ai-v2 --group dev
```

The `all` extra deliberately omits `ten-vad` because of its non-permissive
license and omits the mutually exclusive `pydantic-ai` and `pydantic-ai-v2`
extras.
license, omits the mutually exclusive `pydantic-ai` and `pydantic-ai-v2`
extras, and omits `langchain-v0` because the broad install includes LangChain
1.x and LangGraph. Install `langchain-v0` separately when targeting 0.3.x.

## Credentials without accidental overwrites

Expand Down
8 changes: 4 additions & 4 deletions docs/teaching/13-swap-providers-and-transports/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,10 @@ Provider choice and transport choice optimise **different axes**:
| Axis you care about | Choose this |
|-----------------------------|-------------|
| First-audio latency | Provider mix — compare `easycat latency` on repeated, matched turns |
| Jitter + packet loss | Transport — WebRTC preserves UDP end-to-end |
| Codec quality | Transport — Local / WebRTC (24 kHz) vs Twilio (μ-law 8 kHz) |
| Jitter + packet loss | Transport — inspect WebRTC's selected ICE path and client stats; TURN can relay media over UDP or TCP |
| Codec quality | Transport — Local uses 24 kHz PCM; WebRTC uses 48 kHz media frames with Opus around a 16 kHz pipeline; Twilio uses μ-law at 8 kHz on the wire |
| Cost per turn | Provider mix — usually the dominant cost driver |
| Offline / on-device | Provider mix — (future: Cartesia / local models) |
| Offline / on-device | Provider mix — use a custom local/self-hosted provider; the bundled STT/TTS providers are hosted |
| Reach a regular phone | Transport — Twilio only |

Measure the production bundles with `easycat latency`; choose with
Expand Down Expand Up @@ -290,7 +290,7 @@ numbers from your own environment:
| Phone IVR | ⭐ | ⭐ | ⭐⭐⭐ | ⭐ | `openai` on Twilio |
| Retail kiosk (noisy) | ⭐⭐ | ⭐⭐⭐ | ⭐ | ⭐ | `deepgram-eleven` on Local |
| Multilingual hotline | ⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | `deepgram-eleven` on Twilio |
| Offline embedded device | ⭐⭐⭐ | ⭐⭐ | ⭐ | ⭐⭐⭐ | (future: local models) |
| Offline embedded device | ⭐⭐⭐ | ⭐⭐ | ⭐ | ⭐⭐⭐ | Custom local providers on Local |

Cost is not a measured axis in this chapter — it's an annotation
from provider pricing pages. Chapter 12 deliberately stops short
Expand Down
4 changes: 3 additions & 1 deletion docs/teaching/15-operate-in-production/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,9 @@ Run easycat explain json-schema for CLI JSON.
`Open http://localhost:8080` URL, with live transcript, interruption
indicator, and per-turn latency readout in the page. Needs an OpenAI
key; a non-loopback `--host` requires `--token` (or
`EASYCAT_SERVE_TOKEN`).
`EASYCAT_SERVE_TOKEN`). Put a remote browser serve behind TLS and pass its
HTTPS origin with `--public-url` (or `EASYCAT_SERVE_PUBLIC_URL`); EasyCat
will not print a token-bearing direct-HTTP link.
- **`uv run easycat plan`** — resolves an `easycat.toml` profile into its
provider/capability plan across all seven pipeline roles
(`src/easycat/cli/plan.py`), reporting the selected provider per role plus
Expand Down
5 changes: 3 additions & 2 deletions docs/using-easycat/05-agent-bridges/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ already own:
|---|---|---|---|
| OpenAI Agents SDK `Agent` | `OpenAIAgentsBridge` | Tools, handoffs, SDK context, previous-response chaining | `--extra quickstart` or `--extra openai-agents` |
| PydanticAI `Agent` | `PydanticAIBridge` | Typed dependencies, tools, and structured results | `--extra pydantic-ai` or `--extra pydantic-ai-v2` |
| LangChain `Runnable` | `LangChainBridge` | LCEL chains, runnable events, and message history | `--extra langchain`; install the model package separately |
| LangChain `Runnable` | `LangChainBridge` | LCEL chains, runnable events, and message history | `--extra langchain` for 1.x or `--extra langchain-v0` for 0.3.x |
| Compiled LangGraph graph | `LangGraphBridge` | Nodes, checkpoints, resumable state, and native state edits | `--extra langgraph`; install the model package separately |
| LlamaIndex Workflow | `LlamaAgentsBridge` | Local/remote workflows and human-in-the-loop resumption | `--extra llama-agents` |
| Remote Responses API base URL | `RemoteResponsesAPIBridge` | An agent running behind an HTTP/SSE service boundary | Core; provide `agent_model` and remote auth as needed |
Expand All @@ -99,7 +99,8 @@ already own:

`quickstart` already includes OpenAI Agents SDK. Do not redundantly add its
bundled extra. PydanticAI v1 and v2 are separate, conflicting install choices;
select the one your application uses.
select the one your application uses. LangChain 0.3 and 1.x are likewise
separate, conflicting install choices, and both run the same bridge contract.

## Auto-detection is the default path

Expand Down
Loading
Loading