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:
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.