Skip to content

feat(telemetry): interruption detail, handoff span, fallback events, text input - #2499

Draft
davidzhao wants to merge 1 commit into
dz/telemetry-rpcfrom
dz/telemetry-coverage
Draft

davidzhao wants to merge 1 commit into
dz/telemetry-rpcfrom
dz/telemetry-coverage

Conversation

@davidzhao

@davidzhao davidzhao commented Sep 15, 2026

Copy link
Copy Markdown
Member

Port of livekit/agents#7137. Stacked on #2498.

Description

Smaller coverage gaps on existing spans, each of which came up when reading a trace and not being able to answer a question from it.

Interruptions. agent_turn carried a single lk.interrupted boolean. It now also carries lk.interruption.source, set by the caller that knows the cause: audio_activity (barge-in from VAD / STT activity), user_turn (a committed user turn preempting the reply), programmatic (session.interrupt(), a tool, teardown). The first cause wins. The pipeline and say paths also stamp lk.playout.position, how many seconds had actually played when the user cut in.

Agent handoff. updateAgent() spans an update_agent bar (parent agent_session) with lk.previous_agent_label and lk.agent_label. The old agent's drain_agent_activity (with on_exit inside) and the new agent's start_agent_activity nest under it. The initial start stays under session_start.

Fallback adapters (LLM, TTS, STT). FallbackAdapter.model / .provider follow the instance that serves next, so llm_node, tts_node and start_agent_activity name a real model instead of the adapter. The attempt span carries lk.fallback.label / lk.fallback.index, and a failover mid-request is recorded on the response side: gen_ai.response.model and gen_ai.provider.name of the serving instance on the adapter's request span and on llm_node / tts_node. Usage metrics needed no change.

Text input. A keyterm_detection span around the keyterm-detection LLM pass, nested under the agent_turn that answers the user message (falling back to agent_session), so its llm_request no longer looks like a second inference step. Attributes are counts only (lk.keyterms.count/added/removed) plus model and provider; the terms stay in the session report as PII.

Changes Made

  • voice/speech_handle.ts: InterruptionSource, interrupt(force, source); voice/agent_activity.ts: recordInterruption, source threaded through every interrupt path, lk.playout.position; voice/agent_session.ts: update_agent span.
  • llm|tts|stt/fallback_adapter.ts: nextInstance(), model / provider overrides, served-instance attribution; llm/llm.ts, tts/tts.ts: protected request-span getters.
  • voice/keyterm_detection.ts: keyterm_detection span.
  • telemetry/trace_types.ts: eight new attributes (none PII).

Adaptations from the Python source

  • lk.agent_label / lk.previous_agent_label use agent.id.
  • recordInterruption also stamps lk.interrupted=true: the JS say and realtime paths never set it before (only the pipeline reply did).
  • STT: the now-unused _activeStt last-served tracking is removed with the getters that read it.
  • Python's llm_fallback_adapter span name has no JS counterpart; the adapter's request span keeps the fixed llm_request name and is found through its llm_request_run child.

Testing

  • New voice/coverage_spans.test.ts (barge-in source and playout position through a fake session, update_agent nesting, LLM fallback serving provider, first-cause-wins) and voice/keyterm_detection_span.test.ts; fallback adapter tests updated for next-instance identity; agent_session_handoff.test.ts accepts the threaded trace context.
  • Full agents suite green; build, typecheck, lint, API report updated.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Sep 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a6e6bd6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 39 packages
Name Type
@livekit/agents Minor
@livekit/agents-plugin-anam Minor
@livekit/agents-plugin-anthropic Minor
@livekit/agents-plugin-assemblyai Minor
@livekit/agents-plugin-azure Minor
@livekit/agents-plugin-baseten Minor
@livekit/agents-plugin-bey Minor
@livekit/agents-plugin-cartesia Minor
@livekit/agents-plugin-cerebras Minor
@livekit/agents-plugin-deepgram Minor
@livekit/agents-plugin-did Minor
@livekit/agents-plugin-elevenlabs Minor
@livekit/agents-plugin-fishaudio Minor
@livekit/agents-plugin-google Minor
@livekit/agents-plugin-hume Minor
@livekit/agents-plugin-inworld Minor
@livekit/agents-plugin-krisp Minor
@livekit/agents-plugin-lemonslice Minor
@livekit/agents-plugin-liveavatar Minor
@livekit/agents-plugin-livekit Minor
@livekit/agents-plugin-meta Minor
@livekit/agents-plugin-minimax Minor
@livekit/agents-plugin-mistral Minor
@livekit/agents-plugin-mistralai Minor
@livekit/agents-plugin-neuphonic Minor
@livekit/agents-plugin-openai Minor
@livekit/agents-plugin-perplexity Minor
@livekit/agents-plugin-phonic Minor
@livekit/agents-plugin-protoface Minor
@livekit/agents-plugin-resemble Minor
@livekit/agents-plugin-rime Minor
@livekit/agents-plugin-runway Minor
@livekit/agents-plugin-sarvam Minor
@livekit/agents-plugin-silero Minor
@livekit/agents-plugin-soniox Minor
@livekit/agents-plugin-tavus Minor
@livekit/agents-plugins-test Minor
@livekit/agents-plugin-trugen Minor
@livekit/agents-plugin-xai Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…text input

Port of livekit/agents#7137.

Interruptions: `agent_turn` carries `lk.interruption.source`, set by the
caller that knows the cause (`audio_activity` for a VAD/STT barge-in and the
realtime server's own speech detection, `user_turn` for a committed turn or a
final transcript ending a pause, `programmatic` for session.interrupt(),
tools and teardown); the first interruption's cause stands. The pipeline and
`say` paths stamp `lk.playout.position`, the seconds actually played when the
user cut in. The say and realtime paths now also stamp `lk.interrupted`,
which only the pipeline reply did before.

Agent handoff: `updateAgent()` opens an `update_agent` span under
`agent_session` with `lk.previous_agent_label` / `lk.agent_label`; the old
agent's `drain_agent_activity` (with `on_exit`) and the new agent's
`start_agent_activity` / `resume_agent_activity` nest under it. The initial
start stays under `session_start`.

Fallback adapters: LLM, TTS and STT `model` / `provider` follow the instance
that serves next (first available, else the primary), so `llm_node`,
`tts_node` and `start_agent_activity` name a real model. The LLM and TTS
attempt span carries `lk.fallback.label` / `lk.fallback.index` and the
serving instance's request model and provider; the adapter's request span
and the caller's node span get `gen_ai.response.model` / provider of the
instance that answered, per request. The STT adapter's last-served tracking
is replaced by the same next-instance rule.

Text input: the keyterm-detection LLM pass runs in its own
`keyterm_detection` span under the `agent_turn` whose reply added the user
message, else under `agent_session`, with counts only
(`lk.keyterms.count/added/removed`) plus model and provider.

Adaptations: agents carry `id` where Python has `label`; JS `SpeechHandle`
and `AgentActivity.interrupt` take the source as a second positional
parameter / option instead of a keyword. The LLM and TTS stream base classes
expose their request span to subclasses (`llmRequestSpan`,
`ttsRequestSpan`), the JS counterpart of Python's `_llm_request_span`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@davidzhao
davidzhao force-pushed the dz/telemetry-coverage branch from 8b62e64 to a6e6bd6 Compare September 16, 2026 04:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant