tau2_telecom
tau2_telecom
tau2_telecom provides telecom technical-support scenarios ported from tau2-bench. It is the first
dual-side domain in the NeMo Labs Voice Agent evaluation harness. The simulated user owns a mock phone
(TelecomUserDB) with 30 large language model (LLM)-callable tools. The agent owns a carrier back office
(TelecomDB) with
13 tools. Neither side can access the other’s database, so the bridge reconciles them after every write.
The following table summarizes the two registrations, split state model, tool surfaces, and scoring signals.
Every one of the 114 tasks ships db_state_assertions and initialization_actions. None currently
ship nl_assertions.
Run It
Use three terminals. SERVER_CONFIG_PATH resolves against the current working directory, so run cd
first in each terminal.
--domain filters on the <domain>__ prefix, so --domain tau2_telecom selects only the manual
variant and --domain tau2_telecom_workflow only the workflow variant. Single scenarios go through
--scenarios <name>. Refer to the
Evaluation Command-Line Interface (CLI) Reference. If the user bot is
started with tool calling disabled, the simulated user cannot operate its phone and most scenarios
stall.
Two Databases, Two Tool Surfaces
Tau2TelecomBaseScenario.has_user_state = True makes gold replay seed a user-side DB alongside the
agent-side DB. It also tells the bridge to pull a scenario summary from both WebSockets at the end of
the run. Each bot still follows the one-DB-per-bot convention: the agent bot’s shared_state["db"]
is the TelecomDB, the user bot’s is the TelecomUserDB.
Agent-side tools (13, in nemo_voice_agent/evaluation/tools/tau2_telecom_tools.py):
The agent also gets the harness-level EndConversationTool, which resolves through the registry’s
default namespace fallback.
User-side tools (30, in nemo_voice_agent/evaluation/tools/tau2_telecom_user_tools.py):
Telecom tools declare a class-level snake_case name, which is simultaneously the registry key, the
LLM-visible function name, the name field of the recorded action, and the gold-replay dispatch key.
The two surfaces have disjoint names, so _build_tool_map merges them into one map without
collisions.
Policy Variants
Upstream registers telecom twice, one time per technical-support policy prose style. This repository mirrors that
with two scenario classes per task that differ only in policy_variant:
Both resolve the same tau2_id, DBs, reference_answer, db_state_assertions,
initialization_actions, and tool surface, so running both isolates the effect of the policy prose. domain
stays tau2_telecom on the workflow class, keeping tool lookup, data paths, predicate registration,
and sync dispatch identical.
Prompt Addenda
Telecom appends three blocks to the agent prompt after the parent’s voice-realization notes, and two
to the user-sim’s user_actions. All five are module constants in the telecom base.py.
Scoring
Telecom overrides success_signals to DB_STATE_ASSERTION plus CLEAN_EXIT (adding NL_ASSERTION
when a task declares assertions). DB_STATE_MATCH and ACTION_MATCH are still computed and written to
metrics.json, but they land in success_breakdown.excluded rather than gating the verdict. Telecom has an
open solution space in which several valid action sequences produce different whole-DB states while
satisfying the same outcome predicates.
Assertion records are translated at load time from upstream’s env_assertions: the env_type field
becomes side, and the value "assistant" becomes "agent". The runner dispatches each record to
the agent DB or user DB based on that side field, then evaluates the predicate. A scenario’s
db_state_assertion_pass_rate must be 1.0 for the signal to count as passed.
Registered predicates (nemo_voice_agent/evaluation/tools/tau2_telecom_predicates.py, all under
domain tau2_telecom):
Because the runner needs a real DB to evaluate predicates against, the bridge sets include_db: true
on get_scenario_summary whenever a scenario has db_state_assertions, and dual-pulls so both DBs
come back. Refer to Scoring Signals and
Metrics.
Scenario Initialization
The apply_initialization real-time voice interface (RTVI) client message dispatches each task’s
initialization_actions
before the conversation starts, after shared_state_init is merged and db_path is resolved to a
loaded DB. Telecom registers 20 initialization functions in
nemo_voice_agent/evaluation/tools/tau2_telecom_init_functions.py — turn_airplane_mode_on,
unseat_sim_card, break_apn_settings, set_data_usage, suspend_line_for_overdue_bill, and so
on. Each mutates the DB in place. The bridge filters records by side first, so each bot only
applies the mutations meant for its own DB.
Cross-Side State Sync
Upstream tau2 runs both DBs inside one Environment and calls sync_tools() to reconcile them. In
voice mode, the DBs live in two separate bot processes, so the bridge maintains in-process shadow
DBs and pushes deltas. The pipeline turns on only when a scenario overrides Scenario.sync_state.
Telecom delegates to the pure function sync_telecom_state(agent_db, user_db) in
nemo_voice_agent/evaluation/tools/tau2_telecom_sync.py. The function mutates both dictionaries in place and
returns a per-side delta that maps dotted paths to values.
Propagation paths:
Two invocation points mirror upstream’s call sites:
- Post-initialization. After
apply_initializationsucceeds, the bridge loads shadow DBs and replays the init actions onto them. It runssync_stateone time and dispatches the resulting deltas so both bots start from coherent cross-side state. - Per action. Every
WriteScenarioTool._record_actionpushes anaction-appliedRTVI server message. The bridge replays that action onto the shadow DBs using the scenario’s_build_tool_map. Each tool exposes a synchronousinvoke. The bridge then runssync_stateand sends each non-empty side delta as anapply_sync_deltaRTVI client message.
Bot-side, apply_sync_delta dispatches through the per-domain applier registry in
nemo_voice_agent/evaluation/sync_appliers.py. Telecom registers apply_telecom_sync_delta. It handles
plain dotted paths and list-by-ID paths such as bills[B1002].status, which the generic applier cannot
parse. It also re-derives network connection state when a surroundings field changes. Refer to
RTVI messages.
Any new dual-side domain that overrides sync_state must also provide _build_tool_map(state)
returning tools with a synchronous invoke, since that is what the bridge uses for shadow replay.
Single-side domains keep the inherited no-op and skip the pipeline entirely.
Related
Use these pages to compare state models, inspect the source fixtures, or review the scoring contract.
- tau2_airline and tau2_retail — single-side tau2 domains
- Authoring domains — adding your own
- Data provenance — upstream pin and license for the telecom fixtures