Skip to content

Documentation / Getting started / Project structure

Structure a consumer voicebot

Keep product behavior close to the product, and isolate the VAANI composition in one small module.

voicebot/
├── pyproject.toml
├── config/
│   └── voice_routes.json
├── src/voicebot/
│   ├── prompts.py
│   ├── tools.py
│   ├── domain/
│   ├── vaani_setup.py
│   ├── livekit_entrypoint.py
│   └── result_consumers.py
├── frontend/
└── tests/
    ├── test_offline_call.py
    └── test_live_preflight.py
File Responsibility
prompts.py persona, language instructions, caller context
tools.py RuntimeTool schemas, handlers, effect/retry/audit policy
domain/ business rules and state
voice_routes.json reviewed models, voices, route IDs, fallbacks
vaani_setup.py construct profiles, bindings, policies, and CallSetup
livekit_entrypoint.py translate an already-routed call into a VAANI invocation
result_consumers.py publish or map the immutable terminal result
frontend/ user-facing composition and deployment controls

Configuration rules

  • Parse environment and files in consumer code, then construct typed VAANI values. VAANI does not perform ambient provider credential discovery.
  • Every enabled language needs complete instructions and an end-to-end LanguageProfile.
  • Keep provider-specific IDs in reviewed configuration; keep secrets only in a secret manager or runtime environment.
  • Declare fallbacks explicitly with ExplicitFallback. VAANI never invents one.
  • Treat profile revisions and the conversation compatibility profile as deployable configuration.

One setup per call

Build a fresh immutable CallSetup from validated consumer configuration and caller context. The runtime takes an immutable snapshot during preflight, so later mutation of source dictionaries cannot change the call.

Public configuration values are frozen dataclasses. Construct replacements instead of assigning fields:

from dataclasses import replace

new_generation = replace(
    binding.profile.speech_generation,
    voice_id="approved-replacement-voice",
)
new_binding = replace(
    binding,
    profile=replace(
        binding.profile,
        speech_generation=new_generation,
    ),
)
new_setup = replace(
    setup,
    mode=replace(setup.mode, speech_generation=new_generation),
    language=replace(setup.language, bindings=(new_binding,)),
)

This keeps CascadeMode and the matching LanguageBinding consistent. For a running call, submit a CallUpdate instead of constructing a replacement setup.

Avoid a global mutable "agent config" object. Instead:

deployment config + caller context + business policy
                 immutable CallSetup
                    VoiceRuntime

Frontend composition

A frontend may let an authorized operator choose a persona, language set, voice, or approved model profile. Validate that selection in the consumer service and translate it into typed profiles. Do not expose raw provider kwargs, credential values, or unrestricted route names to browsers.

Next: learn the architecture and configuration model.