Skip to content

Use create_transport to simplify bot()#91

Merged
markbackman merged 9 commits into
mainfrom
feat/eval-transport-scaffold
Jun 2, 2026
Merged

Use create_transport to simplify bot()#91
markbackman merged 9 commits into
mainfrom
feat/eval-transport-scaffold

Conversation

@markbackman
Copy link
Copy Markdown
Contributor

@markbackman markbackman commented Jun 1, 2026

⚠️ Requires pipecat-ai 1.4.0.

Please describe the changes in your PR.

Simplifies how a scaffolded bot configures its transport. Building on pipecat-ai/pipecat#4615, which makes create_transport the single, feature-complete way to build any transport, this collapses the per-transport boilerplate in generated bots onto one create_transport call — and, while the generated bots were being reworked, brings their telephony personalization in line with the updated pipecat-examples, simplifies the run instructions, and adds a generation-time lint guard.

Consolidate transport construction onto create_transport

Collapse the per-transport match/case boilerplate in generated bots to a single transport_params dict + await create_transport(runner_args, transport_params), and standardize on one run_bot(transport, runner_args) signature across cascade and realtime.

  • Delete the now-unused per-transport bot_entry_* blocks (daily_webrtc, smallwebrtc, websocket, twilio, telnyx, plivo, exotel) — create_transport handles dispatch.
  • Daily PSTN dial-in collapses onto create_transport too (it arrives as a typed DailyRunnerArguments and the framework applies the dial-in body); its registry imports trim to DailyParams.
  • Only dial-out and SIP keep a bespoke flow (room/token + dial-out settings come from runner_args.body, built by server.py), but they now share the same standardized run_bot signature.

Active telephony personalization matching the examples

Align generated personalization with the updated pipecat-examples — emitted active (uncommented) with an "optional" note, using the typed CallData / CallInfo attribute API instead of dict access:

  • get_call_info macro now returns a typed CallInfo model (not a dict), accepts call_sid: str | None, and returns None on failure.
  • Twilio (get_call_infoCallInfo), Telnyx (call_data.from_number), and Daily PSTN (guarded DailyDialinRequest From/To) blocks — added to both bot_cascade and bot_realtime for parity.
  • Exotel/Plivo unchanged (their examples carry no personalization).

Simplify run instructions to uv run bot.py

The dev runner serves all transports by default and the caller selects one (/start for a web/mobile client, /ws for a telephony provider), so -t is a restriction, not a requirement. Every generated run command is now just uv run bot.py (telephony adds a one-line ngrok/webhook note pointing the provider at wss://<ngrok>/ws). Removed the redundant per-transport command listing (_get_run_commands) from both the console "Next steps" and the generated README.

Generation-time lint guard

The generator only ran ruff format + ruff check --select I (import sorting), so F-rule issues shipped uncaught. Added assert_server_ruff_lint_clean (bundled ruff, --select F) across all configs in test_project_generation and _gen_bot. This immediately caught a real bug — exotel-cascade referenced the unregistered playht_tts, so the generator emitted a pipeline using an undefined tts (F821); fixed the stale config to rime_tts. Also cleaned F541/F401/UP045 in the templates and split LLMRunFrame into its own llm_run_frame feature (dial-out bots wait for the callee and never queue it).

Testing

All 435 tests pass, and generated bots are now asserted ruff-clean (--select F) at generation time.

Adds a 'headless text-eval transport' option (new init question, default
yes) for cascade bots that use the match/case entry point. When enabled,
the generated bot.py gains an EvalRunnerArguments case that builds the
EvalTransport via create_transport, so the bot can be driven over stdin
with 'bot.py -t eval' or programmatically via run_eval — no audio, client,
or server.

Daily PSTN / Twilio+SIP flows (separate template branch) are excluded.
Eval imports are emitted in the template for now since EvalRunnerArguments
isn't in a released pipecat yet; move them into FEATURE_DEFINITIONS once it
ships.
Point 2: extract supports_eval_transport(config) so the question's gating
(cascade + non-PSTN/SIP) reads clearly instead of an inline set expression.

Point 3: move the eval imports out of the template and into the registry —
add an 'eval' entry to FEATURE_DEFINITIONS, resolve its symbols via
_MODULE_OVERRIDES (so regeneration doesn't depend on the searched source
tree having them), wire features['eval'] through get_imports_for_services
and the bot generator, and regenerate _imports.py. The template no longer
hardcodes the eval imports; EvalRunnerArguments now merges into the existing
runner.types import line.
The eval transport option was only reachable via the interactive wizard,
which hangs automated/agent runs. Add --eval-transport/--no-eval-transport
(default on) to the non-interactive init path: thread it through
validate_and_build_config, config_to_json, and the JSON config file. The
flag is clamped off via supports_eval_transport for configs that can't use
it (realtime, Daily PSTN / Twilio+SIP).
Collapse the per-transport match/case boilerplate in generated bots to a
single transport_params dict + create_transport, and standardize run_bot
to one signature. Only dial-out and SIP keep a bespoke flow (room/token
+ dial-out settings arrive in the request body, built by server.py).

- Templates: bot() builds transports via `await create_transport(
  runner_args, transport_params)`; delete the now-unused per-transport
  bot_entry blocks (daily_webrtc, smallwebrtc, websocket, twilio, telnyx,
  plivo, exotel).
- Eval everywhere: drop the --eval-transport flag and the per-bot
  EvalTransportParams entry/import; `-t eval` builds the EvalTransport
  through create_transport's defaults. Add an "eval" factory to customize.
- Collapse Daily PSTN dial-in onto create_transport (it arrives as a
  typed DailyRunnerArguments and the framework applies its dial-in body);
  trim its registry imports to DailyParams.
- Standardize run_bot(transport, runner_args) across cascade/realtime and
  the bespoke dial-out/SIP entries; those parse dialout_settings/request
  from runner_args.body internally.
- Registry: import create_transport for the collapsed path only (dial-out
  and SIP construct transports by hand); regenerate _imports.py/_configs.py.

Update and extend tests (collapsed construction, dial-in collapse,
dial-out/SIP bespoke-but-standard-run_bot, uniform run_bot signature).
Align the scaffold's generated personalization with the updated
pipecat-examples (twilio/telnyx/exotel/plivo, daily-pstn-dial-in): emit it
active (uncommented) with an "optional" note, using the typed CallData /
CallInfo attribute API instead of dict access, and without -t eval text
(the natural guards keep it eval-safe).

- get_call_info macro: return a typed CallInfo model (not a dict), accept
  call_sid: str | None, return None on failure.
- bot_cascade + bot_realtime: active personalization for twilio
  (get_call_info -> CallInfo), telnyx (call_data.from_number), and
  daily_pstn_dialin (guarded DailyDialinRequest From/To). Add the
  twilio/telnyx blocks to bot_realtime too for cascade/realtime parity.
- Registry: twilio gains `from pydantic import BaseModel` (CallInfo);
  daily_pstn_dialin gains `DailyDialinRequest`. Regenerate _imports.py.
- Tests: dial-in now expects DailyDialinRequest; add a twilio active-
  personalization/CallInfo test.

Exotel/Plivo unchanged (their examples have no personalization).
The dev runner serves all transports by default and the caller selects which
one (a web/mobile client picks its transport via /start; a telephony provider
connects to /ws). The -t flag is a restriction, not a requirement, so none of
the generated run commands need it:

- Telephony: drop `--transport <provider> --proxy ...` (the provider connects
  to /ws and create_transport auto-detects it; point its webhook at
  wss://<ngrok>/ws).
- Daily: drop `--transport daily` (a Daily client connects via /start).

With every transport now running `uv run bot.py`, the per-transport command
listing only produced redundant duplicates, so remove _get_run_commands and
collapse both the console "Next steps" and the generated README to a single
`uv run bot.py` plus a telephony-only ngrok/webhook note.
The generator only runs `ruff format` + `ruff check --select I` (import
sorting), so F-rule issues shipped uncaught. Clean the templates and add a
generation-time lint guard.

Template fixes:
- F541: drop the `f` prefix on placeholder-less log strings (handler macros,
  SIP dial-in server_utils).
- F401: LLMRunFrame was imported unconditionally but dial-out bots never queue
  it (they wait for the callee). Move it from the always-on `runner` feature to
  its own `llm_run_frame` feature, gated off for dial-out in service_loader.
- UP045: Optional[int] -> int | None in DialoutManager; drop the now-unused
  Optional from the dial-out registry imports. Regenerate the registry.

Tests:
- Add assert_server_ruff_lint_clean (bundled ruff, --select F) and run it in
  test_project_generation (all configs) and _gen_bot (dial-in/out/SIP).
- This immediately caught a real bug: exotel-cascade used playht_tts, which is
  not a registered service, so the generator emitted a pipeline referencing an
  undefined `tts` (F821). Fix the stale config (playht_tts -> rime_tts).

All 435 tests pass.
The branch's original headless eval-transport direction was superseded by the
create_transport consolidation, so the net diff ships no eval code — only
leftover references. Some were also made incorrect by Pipecat's create_transport
changes (pipecat-ai/pipecat#4615), which no longer build an EvalTransport through
create_transport.

- CHANGELOG: replace the "headless text-eval transport" entry with the actual
  change — generated bots configure transports via create_transport, ship active
  telephony personalization, and run with a plain `uv run bot.py`.
- Drop the now-incorrect "`-t eval` builds it through create_transport" comments
  in service_loader, service_metadata, import_generator, and the cascade/realtime
  templates.
- De-eval test_project_generation: remove the eval-framed docstring/comments and
  the assertions checking for the absence of an eval factory / EvalTransportParams.
@markbackman markbackman changed the title Feat/eval transport scaffold Use create_transport to simplify bot() Jun 1, 2026
@markbackman
Copy link
Copy Markdown
Contributor Author

@aconchillo and @filipi87 probably less important to review. I think once pipecat-ai/pipecat#4615 merges, I'm going to move the init command into pipecat. Perhaps I'll cut this final release plus add an archive message to the repo. TBD.

tomllib is always available on the supported Python versions, so the
tomli import fallback in the project-generation tests and the matching
dev dependency are dead code. uv.lock keeps tomli only as coverage's
transitive toml extra.
Copy link
Copy Markdown

@filipi87 filipi87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a huge cleanup. LGTM!

@markbackman markbackman merged commit ef461f8 into main Jun 2, 2026
1 check passed
@markbackman markbackman deleted the feat/eval-transport-scaffold branch June 2, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants