Skip to content

Migrate user span#1027

Open
toubatbrian wants to merge 9 commits intomainfrom
brian/user-span-migration
Open

Migrate user span#1027
toubatbrian wants to merge 9 commits intomainfrom
brian/user-span-migration

Conversation

@toubatbrian
Copy link
Contributor

@toubatbrian toubatbrian commented Feb 5, 2026

Summary by CodeRabbit

  • New Features

    • Expanded telemetry attributes for assistant turns, provider/tools, response timing (TTFB/TTFT), end-to-end latency, and interruption metrics.
    • Enhanced speech-to-text tracing to include STT model/provider and linked-participant context; spans now carry participant metadata via new span-attribute helpers.
    • Added participant accessors to expose local/linked participant info for span attribution.
    • Exposed a Future.result getter to read resolved values.
  • Tests

    • Added in-memory tests validating audio recognition span creation, parent/child relationships, and telemetry attributes.

@changeset-bot
Copy link

changeset-bot bot commented Feb 5, 2026

🦋 Changeset detected

Latest commit: 08502b9

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

This PR includes changesets to release 20 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-xai Patch

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

Adds new telemetry attribute constants, surfaces STT model/provider and participant information into voice telemetry, creates and propagates user_turn spans in AudioRecognition, exposes RoomIO.linkedParticipant/localParticipant, and passes STT/provider/participant through AgentActivity/AgentSession to AudioRecognition.

Changes

Cohort / File(s) Summary
Telemetry Constants
agents/src/telemetry/trace_types.ts
Added new public trace attribute constants: agent turn IDs, provider/tools/tool sets, response timing (TTFB/TTFT), E2E latency, GenAI provider name, function tool id, and adaptive-interruption attributes (is_interruption, probability, durations, detection_delay).
AudioRecognition & Tests
agents/src/voice/audio_recognition.ts, agents/src/voice/audio_recognition_span.test.ts
Added sttModel/sttProvider/getLinkedParticipant options and exported ParticipantLike; implemented ensureUserTurnSpan and userTurnContext for creating/propagating user_turn spans; wrapped STT/VAD/EOU hooks in span context; added in-memory OTEL tests validating span hierarchy and attributes.
Agent Session / Activity plumbing
agents/src/voice/agent_session.ts, agents/src/voice/agent_activity.ts
Renamed internal roomIO_roomIO and updated lifecycle usage; added getSttProvider() and pass sttModel/sttProvider/getLinkedParticipant into AudioRecognition; set participant span attributes where available.
Room IO
agents/src/voice/room_io/room_io.ts
Added linkedParticipant and localParticipant getters to expose current participant info for telemetry attribution.
Voice utils / Future
agents/src/voice/utils.ts, agents/src/utils.ts
Added setParticipantSpanAttributes utility to annotate spans with participant metadata; Future<T> now stores resolved value and exposes result getter.
Tests / Misc
agents/src/voice/...
New tests and minor manifest/test harness adjustments accompanying span behavior verification.

Sequence Diagram

sequenceDiagram
    participant Speech as Speech/Event
    participant AR as AudioRecognition
    participant OTel as OpenTelemetry
    participant RoomIO as RoomIO
    participant Agent as AgentActivity/AgentSession

    Speech->>AR: START_OF_SPEECH (STT or VAD)
    AR->>OTel: ensureUserTurnSpan()
    OTel-->>AR: user_turn Span (bound to context)
    AR->>RoomIO: getLinkedParticipant()
    RoomIO-->>AR: ParticipantInfo
    AR->>AR: attach attributes (participant, sttModel, sttProvider)
    AR->>OTel: enter userTurnContext(span) and run hooks (onStartOfSpeech / EOU detection)
    Speech->>AR: END_OF_SPEECH
    AR->>OTel: end user_turn Span
    Agent->>AR: close / cleanup
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • chenghao-mou
  • lukasIO

Poem

🐰 I hop through spans with nimble feet,
I link the voices, model, and seat.
Provider, participant, timing in view,
Traces hum softly — hop, hop — anew.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is entirely missing. The repository requires a structured description with sections including Description, Changes Made, Pre-Review Checklist, Testing, and Additional Notes. Provide a complete PR description following the template, detailing what the migration accomplishes, listing all changes, and confirming the pre-review checklist items are addressed.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Migrate user span' clearly reflects the main objective of the PR, which introduces new user_turn span telemetry tracking throughout the voice agent codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch brian/user-span-migration

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 641c9af and 561ec7f.

📒 Files selected for processing (1)
  • agents/src/utils.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/agent-core.mdc)

Add SPDX-FileCopyrightText and SPDX-License-Identifier headers to all newly added files with '// SPDX-FileCopyrightText: 2025 LiveKit, Inc.' and '// SPDX-License-Identifier: Apache-2.0'

Files:

  • agents/src/utils.ts
**/*.{ts,tsx}?(test|example|spec)

📄 CodeRabbit inference engine (.cursor/rules/agent-core.mdc)

When testing inference LLM, always use full model names from agents/src/inference/models.ts (e.g., 'openai/gpt-4o-mini' instead of 'gpt-4o-mini')

Files:

  • agents/src/utils.ts
**/*.{ts,tsx}?(test|example)

📄 CodeRabbit inference engine (.cursor/rules/agent-core.mdc)

Initialize logger before using any LLM functionality with initializeLogger({ pretty: true }) from '@livekit/agents'

Files:

  • agents/src/utils.ts
🔇 Additional comments (2)
agents/src/utils.ts (2)

129-130: LGTM — new backing fields for result/error tracking.

Clean additions that pair with the resolve/reject methods below.


164-175: LGTM — resolve/reject correctly populate backing fields before settling the promise.

Setting #result/#error before calling the promise callbacks ensures the result getter is consistent if accessed synchronously from a downstream handler.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@toubatbrian toubatbrian changed the base branch from main to feat/barge-in February 5, 2026 21:06
@toubatbrian toubatbrian changed the base branch from feat/barge-in to main February 5, 2026 21:06
@toubatbrian toubatbrian changed the base branch from main to brian/session-usage February 5, 2026 21:06
Base automatically changed from brian/session-usage to feat/barge-in February 5, 2026 21:07
@toubatbrian toubatbrian changed the base branch from feat/barge-in to main February 5, 2026 21:11
coderabbitai[bot]

This comment was marked as resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
agents/src/voice/audio_recognition.ts (2)

758-765: ⚠️ Potential issue | 🟡 Minor

End userTurnSpan on close to prevent orphaned spans.

If close() is called while a user turn is in progress, the userTurnSpan will remain recording but never ended, leading to incomplete telemetry data.

🛡️ Proposed fix
 async close() {
+  if (this.userTurnSpan?.isRecording()) {
+    this.userTurnSpan.setStatus({ code: 2, message: 'Session closed' }); // SpanStatusCode.ERROR = 2
+    this.userTurnSpan.end();
+    this.userTurnSpan = undefined;
+  }
   this.detachInputAudioStream();
   this.silenceAudioWriter.releaseLock();
   await this.commitUserTurnTask?.cancelAndWait();
   await this.sttTask?.cancelAndWait();
   await this.vadTask?.cancelAndWait();
   await this.bounceEOUTask?.cancelAndWait();
 }

701-714: ⚠️ Potential issue | 🟡 Minor

Consider ending userTurnSpan when clearing the user turn.

When clearUserTurn() is called, the transcript state is reset but the userTurnSpan may still be active. This could cause the next user turn to reuse the same span (if still recording), potentially merging distinct turns into one span and causing incorrect telemetry attribution.

🛡️ Proposed fix
 clearUserTurn() {
+  if (this.userTurnSpan?.isRecording()) {
+    this.userTurnSpan.setStatus({ code: 2, message: 'User turn cleared' });
+    this.userTurnSpan.end();
+    this.userTurnSpan = undefined;
+  }
   this.audioTranscript = '';
   this.audioInterimTranscript = '';
   this.audioPreflightTranscript = '';
   this.finalTranscriptConfidence = [];
   this.userTurnCommitted = false;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between facde8a and a761332.

📒 Files selected for processing (2)
  • agents/src/voice/audio_recognition.ts
  • agents/src/voice/audio_recognition_span.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • agents/src/voice/audio_recognition_span.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/agent-core.mdc)

Add SPDX-FileCopyrightText and SPDX-License-Identifier headers to all newly added files with '// SPDX-FileCopyrightText: 2025 LiveKit, Inc.' and '// SPDX-License-Identifier: Apache-2.0'

Files:

  • agents/src/voice/audio_recognition.ts
**/*.{ts,tsx}?(test|example|spec)

📄 CodeRabbit inference engine (.cursor/rules/agent-core.mdc)

When testing inference LLM, always use full model names from agents/src/inference/models.ts (e.g., 'openai/gpt-4o-mini' instead of 'gpt-4o-mini')

Files:

  • agents/src/voice/audio_recognition.ts
**/*.{ts,tsx}?(test|example)

📄 CodeRabbit inference engine (.cursor/rules/agent-core.mdc)

Initialize logger before using any LLM functionality with initializeLogger({ pretty: true }) from '@livekit/agents'

Files:

  • agents/src/voice/audio_recognition.ts
🧬 Code graph analysis (1)
agents/src/voice/audio_recognition.ts (4)
plugins/google/src/beta/gemini_tts.ts (1)
  • opts (165-167)
agents/src/telemetry/traces.ts (1)
  • tracer (150-150)
agents/src/telemetry/index.ts (1)
  • tracer (24-24)
agents/src/stt/index.ts (1)
  • SpeechEventType (11-11)
🔇 Additional comments (6)
agents/src/voice/audio_recognition.ts (6)

5-11: LGTM!

OpenTelemetry imports are correctly structured and all are utilized in the implementation.


67-79: LGTM!

The new tracing options and ParticipantInfo type are well-documented and provide appropriate flexibility for dynamic participant resolution.


164-197: LGTM!

The ensureUserTurnSpan method correctly handles span reuse with the isRecording() check, and properly propagates participant and STT metadata. The userTurnContext helper correctly builds the context hierarchy.


346-364: LGTM!

The span context wrapping for STT-based speech events is correctly implemented with appropriate variable scoping.


435-479: LGTM!

The EOU detection span is correctly linked as a child of the user_turn span through the propagated context.


639-671: LGTM!

The VAD event handling correctly backfills the span start time using speechDuration for accurate timing, and consistently wraps hooks with the appropriate OpenTelemetry context.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

coderabbitai[bot]

This comment was marked as resolved.

export interface ParticipantLike {
sid: string | undefined;
identity: string;
kind: number;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be of type ParticipantKind instead?

return this.#done;
}

get result(): T {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't feel great. It introduces a the notion of (false) synchronicity in a - by definition - async utility.

What speaks against using await ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only been used in 1 place currently, in RoomIO.linkedParticipant, which is a getter method that expect synchronous access

  get isParticipantAvailable(): boolean {
    return this.participantAvailableFuture.done;
  }

  get linkedParticipant(): RemoteParticipant | undefined {
    if (!this.isParticipantAvailable) {
      return undefined;
    }

    return this.participantAvailableFuture.result;
  }

Agree that this does not looks great but it's kind of a work around. Also, we have the guard of checking isParticipantAvailable before getting the participantAvailableFuture.result so it should not throw but simply return undefined if the future is not done in this case

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 6 additional findings.

Open in Devin Review

@toubatbrian toubatbrian requested a review from lukasIO February 17, 2026 21:08
devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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.

2 participants