The Builder API
nemo_voice_agent/pipecat/services/nemo/builders.py holds one build_* function per pipeline stage.
Each is a thin wrapper around a service constructor that reads the relevant block of the loaded
ConfigManager, so a bot script does not repeat the same config plumbing. The builders are
independent — import only the ones you need, and construct anything they do not cover inline.
Two bots in this repo use them: examples/generic_voice_agent/server/server.py and
evaluation/bot_server.py. Both call the same eleven builders in the same order.
Every Builder
Each builder owns one runtime component and reads the corresponding configuration block.
Two helpers in the same module are not pipeline stages:
Details worth knowing:
build_ws_transportacceptsvad_analyzerbut ignores it. Since Pipecat 1.0 the input transport no longer runs VAD. The argument is kept only so existing call sites still work. Pass the analyzer tobuild_vad_processorand place the result right aftertransport.input()instead.build_diartakes the diarization device fromstt.device, not fromdiar.device.build_diarandbuild_turn_takingacceptaudio_loggerfor call-site symmetry. Onlybuild_turn_takingforwards it to the service.build_sttandbuild_ttsforward it into theirget_stt_service_from_config/get_tts_service_from_configfactory.build_turn_takingis annotated as returningNeMoTurnTakingServicebut returnsNonewhenturn_taking.enabledis false — treat it as optional like the others.build_llm_text_processorexists because Pipecat 1.0 droppedTTSService(text_aggregator=...). Pipecat silently ignores unknown constructor kwargs, so passing an aggregator to the TTS service would fall back to plain sentence splitting with no error.
Optional Stages
build_audio_logger, build_diar, build_turn_taking, and build_llm_text_processor return None
to mean “omit this stage”. build_vad_processor returns None only if you hand it None, and
build_vad_analyzer always returns an analyzer — so VAD is never omitted on the shipped
path. Assemble the pipeline behind if x is not None checks rather than leaving placeholders:
Call Order
Three dependencies constrain the order:
build_vad_analyzerbeforebuild_vad_processorandbuild_ws_transport.build_llmbeforebuild_context_and_aggregators.build_turn_takingbeforebuild_context_and_aggregators— and pass its result through.
The third dependency matters most. In Pipecat 1.0+, the pipeline permits exactly one component to emit
UserStartedSpeakingFrame and UserStoppedSpeakingFrame. The turn_taking argument determines that
component. Given a service, the builder selects ExternalUserTurnStrategies so
NeMoTurnTakingService owns turn detection and the aggregator stays quiet. Passing None is
indistinguishable from omitting the argument — None is the parameter’s default — so in both cases
the builder re-derives the answer from turn_taking.enabled. When that key is false (the
*_nvidia.yaml configs) it builds UserTurnStrategies from VADUserTurnStartStrategy plus
SpeechTimeoutUserTurnStopStrategy, so the aggregator drives the turn directly from VAD frames. When the key
is true or absent (the shipped default.yaml), it still selects ExternalUserTurnStrategies. Nothing is
then left in the pipeline to emit the user-turn frames. That fallback is correct for the stock builders but
not for a bot that constructs its turn-taking service inline. Such a bot must pass the service explicitly.
original_messages, the fourth tuple element, is a fresh deep copy of the initial message list. Hand
it to the reset and update-prompt RTVI handler factories. Refer to
RTVI Actions.
Substituting Your Own Service
Every builder returns a stock Pipecat base type, so swapping one out is a one-line change in the bot
script. Replace the call, keep the object in the same position in pipeline_list, and the rest of
the pipeline is unaffected.
The contracts your replacement must honor:
Two extra behaviors are duck-typed rather than enforced by the base classes:
- Reset. The bot passes a
resettablelist tocreate_reset_context_action. The handler calls.reset()on each entry that defines one and skipsNoneentries, so a service withoutreset()is not reset between scenarios. - Tool calling. A service only exposes tools if it mixes in
ToolCallingMixinfromnemo_voice_agent/utils/tool_calling/mixins.pyand is listed in thetool_mixinsargument ofregister_direct_tools_to_llm. Refer to Custom Tools.
If your service needs its own config, add a block to the YAML and read it from
config_manager.server_config. ConfigManager passes unknown keys through untouched, so you do not need to
thread new arguments through the shipped builders. Refer to
Server Config.
Next Steps
Choose the next guide based on whether you need to replace an assembly or transform frames within it:
- Build a custom pipeline — writing a bot script from scratch.
- Write a custom processor — a new stage rather than a replacement one.
- How it works — where each stage sits in the frame flow.