Building Your Own Pipeline
This is the deepest extension tier in NeMo Labs Voice Agent: you write your own run_bot_websocket()
instead of reusing the shipped one. Every service, processor, and pipeline stage becomes yours. Only the
transport and real-time voice inference (RTVI) control-plane contracts must remain compatible.
Use this approach only after the lower-scope tiers are exhausted. Swapping a model behind an existing stage is YAML-only (Server Configuration). A transformation between two stages requires one class (Custom Frame Processors). This tier supports a different pipeline shape or services that the builders do not cover.
Prerequisites
Before you build a replacement pipeline, complete the following preparation:
- Run the Quickstart with the shipped pipeline.
- Choose whether the replacement must serve the browser client, the evaluation harness, or both.
- Identify the stage ordering or service requirement that the shipped builders cannot express.
Two Entry Points, Two Contracts
There are two run_bot_websocket() implementations, and they require different behavior from a replacement:
Both call the shared runner run_bot_websocket_server() from nemo_voice_agent/pipecat/bot_server.py,
which owns the boilerplate around the task: transport connect/disconnect handlers, the RTVI
on_client_ready kickoff, audio-logger finalization, and shutdown. It makes no assumptions about
pipeline contents, so a custom pipeline can keep using it.
The Contract
A replacement bot must satisfy exactly two requirements.
1. Speak Pipecat’s WebSocket server protocol. Use SingleClientWebsocketServerTransport from
pipecat.transports.websocket.server with a ProtobufFrameSerializer, bound to the port in
WEBSOCKET_PORT. build_ws_transport() in nemo_voice_agent/pipecat/services/nemo/builders.py
constructs exactly this and is the path of least resistance — it also reads the transport sample
rates from your config. Since Pipecat 1.0 the transport no longer runs VAD, so a VAD processor goes
in the pipeline right after transport.input().
2. Carry an RTVIProcessor with the handlers your consumer expects. Build the handlers with the
factories in nemo_voice_agent/pipecat/processors/frameworks/rtvi_actions.py and install them in one
shot with register_client_message_handlers(). Each factory returns a (message_type, handler) pair.
The registrar installs a single on_client_message dispatcher over all of them. An unhandled type
produces an error-response rather than a silent hang.
The six factories, keyed by the wire message type they answer:
Refer to RTVI Control Plane for handler semantics and RTVI Messages for payload shapes.
What You May Not Change
Preserve the following contracts if the browser client or evaluation harness must connect to the new pipeline:
- The wire protocol. Use a Pipecat WebSocket server transport. Re-implementing protobuf framing by hand is out of scope.
- The six message types and their response shapes. The bridge sends and parses these literally.
Renaming
get_scenario_summary, or returning something other than theactionsplusdb_hashpair, silently breaks scoring. The run completes with unusable metrics. - The tool-registry namespace key. The bridge passes
scenario.domainastool_domain. Your tool-registration callback must accept the sametool_factory(name, domain=...)interface thatget_schema_tool_for_evalimplements. Refer to Custom Tools.
Free-Choice Points
Everything outside the connection and RTVI contracts can be adapted to the agent’s requirements.
Pass your turn-taking service to build_context_and_aggregators() if you use it. In Pipecat 1.0+, the
pipeline permits exactly one component to emit user-speaking frames, and that argument determines the component. Omitting it derives
the answer again from turn_taking.enabled, which is incorrect for a bot that builds the service inline.
Skeleton
Mirrors the structure of evaluation/bot_server.py, trimmed to the required parts.
task_ref.task and task_ref.running are populated by run_bot_websocket_server(), so construct the
TaskRef before the handlers and hand it over unpopulated. To keep the FastAPI /connect endpoint the
browser client uses, wrap the coroutine with create_fastapi_app() and run_bot_with_fastapi() from
the same module.
Verify Against the Eval Harness
The eval bridge is the strictest consumer of the contract, so run one scenario through it. All three
commands start from evaluation/ — SERVER_CONFIG_PATH resolves against the current working
directory, not the script directory.
Then read eval_results/eval_<timestamp>/restaurant__pizza_pepperoni/:
For the full run procedure and artifact reference, refer to Evaluation Quickstart and Reading Results.
Non-Pipecat Agents
Evaluating an agent that is not built on Pipecat means re-implementing the transport protocol and the
RTVI control plane yourself. Treat pipecat.transports.websocket.server and
pipecat.serializers.protobuf as the wire specification. Refer to
External Agents for the harness-side view.
Next Steps
Continue with the implementation guide for the extension surface you need next:
- The Builder API explains how to reuse or replace component construction.
- Create a Custom Frame Processor shows how to add a focused transformation without replacing the full pipeline.
- RTVI Control Plane documents the handlers a compatible evaluation bot must register.