TOBi managed LLM
TOBi (type vodafone_tobi) is a managed LLM integration, not a modular STT/TTS vendor. The platform hosts the conversational brain; your flow configures how Delphi forwards SIP context to the bot and which managed actions (transfer, hangup) the bot may trigger.
Rendered on the canvas as the Managed LLM node.
Pipeline role
| Stage | TOBi |
|---|---|
| STT / TTS / Realtime | Not configured here — handled by the managed integration |
| LLM | vodafone_tobi in pipeline LLM slot or Managed LLM node |
Key fields
| Field | Description |
|---|---|
| Base URL | Vodafone chatbot API host (with or without /v1/chatbot path suffix) |
| Client ID / Client Secret | OAuth-style credentials via team variables |
| Bot name | Managed bot identifier |
| Callback / response paths | Advanced routing for bot callbacks |
| Channel Data Forwarding | SIP header → bot context rules — see SIP header context rules |
| Application Metadata | Optional VGW metadata attributes sent to TOBi at conversation creation. |
| DTMF defaults | Managed DTMF collection defaults for the integration |
Managed transfer and hangup actions are configured on the Managed LLM / Managed action inspectors and validated at runtime against platform egress and transfer restrictions.
For bot-triggered hangup, TOBi hangupSipHeaders are added to the configured BYE headers; a bot value wins a case-insensitive name collision for that call. If the merged optional set exceeds the SIP budget, Delphi drops the bot set, retains valid configured headers, and still disconnects. Platform SIP/Q.850 causes and carrier-facing BYE behavior are documented in Call termination and transfer signaling.
TOBi often sends multiple text messages before hangup; pipeline mode plays them in order and defers hangup until speech finishes. See Speech playback and hangup.
Application Metadata
v0.9.15 adds an Application Metadata section to the Managed TOBi panel. Select only the VGW attributes the bot should receive; Delphi sends nothing by default until a flow opts in.
| Attribute | Value source |
|---|---|
VGW-Environment | VGW_ENVIRONMENT, then ENVIRONMENT, then OTEL_DEPLOYMENT_ENVIRONMENT. |
VGW-ConversationID | TOBi/managed conversation identifier when available. |
VGW-IpTelpro | TelPro edge IP propagated by TelPro. |
VGW-IpVoice | Voice instance IP. |
VGW-CallId | SIP Call-ID / Delphi call correlation ID. |
VGW-Callee | Called number or endpoint. |
VGW-Caller | Caller number. |
VGW-CallerHost | Caller host from SIP signaling when available. |
Application metadata is separate from Channel Data Forwarding. Internal Delphi headers, including TelPro-provided VGW helper headers, are not forwarded through channelDataRules, but they can still be resolved into selected Application Metadata attributes.
After changing Application Metadata checkboxes (or related Channel Data rules), publish the flow. Draft-only changes are not served to live endpoints, so metadata will not appear in channelData until publish.
Set optional VGW_ENVIRONMENT on the Voice host when VGW-Environment should be a deployment label distinct from generic ENVIRONMENT — see the environment variable index.
Transaction correlation (transactionId)
Every TOBi conversation gets a stable transactionId — a UUID generated by the TOBi LLM component at construction time. This id is separate from the distributed trace_id and provides a single correlation point for all BOT requests, SIP signaling, and OTel spans in one conversation.
How it flows
-
Outbound BOT requests — The TOBi LLM component sends
transactionIdas thevf-trace-transaction-idHTTP header on every request to the TOBi API. This is a provider-side header contract: the TOBi API expects it and echoes it back on callbacks. -
Inbound webhook callbacks — When the TOBi API posts a callback to
POST /api/v1/webhooks/tobi/{channelId}, it includes the samevf-trace-transaction-idheader. TelAPI extracts it and uses it as thetransactionIdfor the rest of the call. See Webhooks. -
Redis propagation — TelAPI writes
transactionIdinto thevoiceai:calls:{callId}Redis key so Kamailio can read it on in-dialog requests (re-INVITE, BYE) and forward it as theX-Transaction-IDSIP header to TelSys. -
TelSys dialplan — Asterisk reads
X-Transaction-IDat INVITE time and includestransaction_idin all structured log events. TelPhi also sets it via ARIsetChannelVarfor resumed sessions. -
OTel spans — TelPhi attaches
transactionIdas a span attribute on the call span and on all four modular audio pipeline spans (user.speech,user.transcript,ai.transcript,ai.response). The log-to-span sidecar injects it on Kamailio, TelSys, and RTPEngine spans.
Searching by transactionId
In SigNoz, filter by transactionId to see every event for one conversation across all services:
attributes.transactionId = "<uuid>"
See Transaction correlation for the full propagation chain, SigNoz query patterns, and diagnostic guidance.
OpenAPI reference
The vf-trace-transaction-id header is a provider-side contract header — it belongs to the TOBi API, not to Delphi's own API surface. It is therefore not modelled as a Delphi OpenAPI parameter. The callback body itself is documented as the TobiCallbackBody schema in the generated OpenAPI reference.
| Reference | Link |
|---|---|
| TOBi webhook endpoint | Redoc |
TobiCallbackBody schema | Redoc |
TobiBotControlEvent schema | Redoc |
The callback body does not contain transactionId as a field — the id travels as the vf-trace-transaction-id HTTP header, not inside the JSON payload.
Sensitive storage (transcripts and logs)
TOBi can request that Delphi treat a session as sensitive so transcript / log persistence is suppressed or restored mid-call. The bot sends this via channel data / runtime commands (sensitiveInfoOnStorage / update_sensitive_storage). Delphi applies the update in arrival order so older protected text is not written after a later enable.
Use this when a conversation must not retain STT/TTS transcript artefacts even if the app Recording toggle is on. See also Recording and transcript storage.
DTMF collection
Managed DTMF defaults (max digits, timeouts, inter-digit timeout, terminator) are configured on the TOBi panel and can be updated live with the update_dtmf_config callback action (interDigitTimeoutMs, timeoutMs, and related fields). The same config event also carries barge-in / interruption-prevention flags (bargeIn, bargeInOnDTMF, bargeInMinWordCount); those apply immediately so a bargeIn: false update can lock the announcement sent in the same callback. Field reference: Managed callback actions.
Disabling the TTS cache for one response
TOBi supports the AudioCodes/Vodafone disableTtsCache parameter at the per-response level. When the bot sets activityParams.disableTtsCache: true on a single text message, the audio pipeline bypasses the TTS media cache for only that one spoken sentence — no cache.fetch, no cache.upload. The cache stays on for every other reply in the call, so the optimisation that keeps repeated phrases cheap is preserved.
This is useful for dynamic or personalised content where a cached clip would be wrong — one-time codes, account numbers, names, balances, OTPs.
Wire shape
{
"conversation": {"identifier": {"id": "<conversationId>", "name": "TOBi"}},
"from": {"identifier": {"id": "tobi", "name": "TOBi"}},
"messages": {
"timestamp": "<ISO>",
"message": [
{
"messageId": 2,
"sequence": 2,
"content": {
"textMessage": {"textPlain": "I have something sensitive to tell you."},
"activityParams": {"disableTtsCache": true}
}
}
]
},
"metadata": {}
}
The activityParams object is attached sibling to textMessage on each messages.message[].content entry. Other messages in the same callback that omit activityParams cache normally.
How the bypass is observed
The flag is forwarded through the pipeline and produces structured log lines at each hop:
| Stage | Log event | Field |
|---|---|---|
| TelAPI inbound webhook | tobi.callback.received | disableTtsCache: true |
| TelAPI → Redis stream publish | tobi.callback.published | disableTtsCache: true |
| TelPhi stream reader | tobi.message.inbound | disableTtsCache: true |
| Audio pipeline (per sentence) | tts_cache_bypassed_for_response | providerType, cacheKey, textLength |
Filter the audit log on disableTtsCache: true or tts_cache_bypassed_for_response to confirm the bypass actually fired on a given reply.
What it does not change
- The platform-wide TTS media cache and the
ttsMediaCachefeature flag — unaffected. - Other replies in the same call — they continue to hit the cache.
- The bot's runtime configuration —
update_tts_configruntime commands still drive whole-call TTS settings; see Managed callback actions. - The voice-bot channel —
disableTtsCacheis not applicable when the framework performs speech-to-text on the bot side (AudioCodes note).
For turning the cache off for the whole call, send an update_tts_config runtime command with ttsCaching: 'off' from a bot-originated configuration event. For turning it off for one sentence, use the per-response activityParams.disableTtsCache shown above.
Termination reason propagation
When a call ends, Delphi tells TOBi why it ended. Every endConversation PUT (PUT /conversation) carries two metadata fields — metadata.reason and metadata.reasonCode — so Vodafone Ops can group terminations by category and read the specific cause. Both are populated regardless of who initiated the end (the bot or the SIP side). The internal Delphi field reasonDetails is serialized to the wire name reasonCode at the provider boundary only; everywhere else in Delphi (logs, OTel, types) it stays reasonDetails.
This is the SCRUM-742 acceptance criterion: Ops must be able to see why a call terminated, not just that it did.
Wire shape
{
"conversation": {"identifier": {"id": "<conversationId>", "name": "TOBi"}},
"from": {"identifier": {"id": "<userId>", "name": "<userName>"}},
"forwardEventToBot": true,
"text": "conversationEnded",
"metadata": {
"channel": "Voice AI",
"reason": "<BotSide|ClientSide|Error|SpeechService|Media|System|Transferred>",
"reasonCode": "<human-readable detail>"
}
}
reason and reasonCode are not fields in the published v2.0.0 swagger. They ride the MetaData free-form bag, which is additionalProperties: true (the same bag that already carries channel and closeConversation). When Delphi captured no reason for a call, both keys are omitted from metadata rather than sent empty.
Reason enum
reason | Meaning |
|---|---|
BotSide | The bot (TOBi sandbox tool, external-LLM action, or closing-phrase detection) ended it. |
ClientSide | The caller / SIP side hung up (normal clear, busy, rejected, SIP Cancel, …). |
Error | A server-side or integration error forced the end. |
SpeechService | An STT or TTS error forced the end (failover). |
Media | A media / RTP / transport-capacity problem forced the end. |
System | Housekeeping: graceful shutdown, max-call-age reap, generic stasis_end. |
Transferred | The call was transferred to another destination (SIP transfer / Refer). |
How the reason is chosen (precedence)
Delphi resolves a single termination reason per call, and first caller wins — once a reason is stashed for a channel it is not overwritten by a later signal. The precedence, highest to lowest:
- Bot-side callback —
closeConversation, a bothangupruntime command, or closing-phrase detection fires aBotSidereason into the gateway before SIP tear-down. - Gateway trigger stash — failover, channel-setup errors, transfers, and the max-call-age reaper stash a reason on the channel data before hangup.
- ARI Q.850 cause code — when nothing was stashed, the Asterisk hangup
cause(andcause_txt) is mapped to a reason. - Default —
System/stasis_end.
reasonCode examples
reason | reasonCode | When |
|---|---|---|
Transferred | transferred: <target>; <reason> | SIP transfer / Refer (see Transfers) |
BotSide | tobi_close_conversation | Bot sent metadata.closeConversation: true |
BotSide | external_llm_hangup / external_llm_hangup:<reason> | Bot hangup runtime command (conversationCompleted, userSilent, …) |
BotSide | tobi_closing_phrase:<reason> | Closing-phrase detection |
BotSide | failover_bot_error / failover_bot_timeout | Bot failover |
SpeechService | failover_stt_error / failover_tts_error | STT / TTS failover |
Error | failover_system_error | System failover |
ClientSide | <cause_txt> (e.g. Normal Clearing) | Caller hung up |
Media | <cause_txt> | Media/RTP cause code |
System | max_call_age_reap | Max-call-age reaper |
System | provider_stop / provider_destroy | Graceful provider shutdown |
System | stasis_end | Default when nothing else applied |
Bot close signals (inbound)
TOBi can end the conversation in two ways, and Delphi stashes a BotSide termination reason from either before the SIP tear-down so it reaches the endConversation PUT:
metadata.closeConversation: trueon a callback — Delphi fires the bot-side termination callback withtobi_close_conversation.- A
hangupruntime command with areason— Delphi fires the callback withexternal_llm_hangup(orexternal_llm_hangup:<reason>when the bot supplied one). Common bot reasons includeconversationCompleted,silentCollCustomer,telcoTesterHangUp, anduserSilent.
Both are idempotent under first-caller-wins: if the bot already signalled close (for example a hangup with conversationCompleted followed by closeConversation), the second signal does not overwrite the first.
Transfers
A bot- or flow-initiated transfer stashes a Transferred reason with reasonCode transferred: <target>; <reason> (with No target / No reason fallbacks when either is absent) before the SIP Refer, so the endConversation PUT reflects the actual destination. This matches the Vodafone Xfer sip:<target>; <reason> sample shape. The stash covers all transfer paths — runtime transfer command, the setTransferHandler defaults, and the flow-engine executeCallAction (including the failover-policy ladder that flows through it).
Observability
The same reason feeds two more surfaces alongside the wire payload, so a termination can be traced end-to-end:
- OTel span attributes on the call span:
call.end_reason_category(=reason) andcall.end_reason_details(=reasonDetails). - Structured log fields:
terminationReasonandterminationReasonDetailson theendConversationlog event.
See SigNoz monitoring to group calls by call.end_reason_category, and Application flow logging to filter the structured logs.
A few AC cases need new detectors that do not exist in the gateway yet, and currently rely on the Q.850 cause-code / cause_txt fallback (or the System / stasis_end default) rather than a specific reasonCode:
- media-connect-failed / SBC data underrun — no RTP-liveness watchdog.
- user-inactivity timeout — no dedicated inactivity watchdog (STT-side
inactivityTimeoutSecondsis a passthrough, not a termination source). - upstream TOBi websocket / posting-activity errors — detected, but the specific error text (401, timeout, websocket closed) is not yet threaded into
reasonCode; the call fails over withfailover_bot_errorand the detail is logged separately.
These are tracked as SCRUM-742 follow-ups.
API reference
Bot callback payloads (transferTarget, hangupSipHeaders, failover, and related fields) are documented in Managed callback actions.
See also
- Transaction correlation — how
transactionIdpropagates across all services and how to search by it in SigNoz - Speech playback and hangup — multi-message TTS queue and hangup timing for managed integrations
- SIP header context rules
- Managed callback actions
- Call termination and transfer signaling
- Providers overview
- Egress trunks — outbound transfer routing
- TTS media cache — platform-wide cache that
disableTtsCachebypasses per-response