Conversation
davidzhao
added this pull request to stack #2502
September 15, 2026 06:32
davidzhao
force-pushed
the
dz/telemetry-agent-turn
branch
from
September 15, 2026 06:46
9a563af to
91cea4d
Compare
🦋 Changeset detectedLatest commit: 241cd41 The changes in this PR will be included in the next version bump. This PR includes changesets to release 39 packages
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 |
davidzhao
force-pushed
the
dz/telemetry-agent-turn
branch
from
September 16, 2026 04:28
91cea4d to
dd4e334
Compare
Port of livekit/agents#7143. A reply that calls a tool runs two generations (LLM steps) in two tasks; they were two agent_turn spans linked only by lk.parent_generation_id, so one response rendered as two turns. A speech handle is now exactly one agent_turn. - The speech handle owns the span: the first reply task (pipeline, realtime, or say) opens agent_turn under agent_session; the follow-up generation after a tool call continues the open span. It ends with the speech in SpeechHandle._markDone, recording the speech's error redaction-aware and the gen_ai.invoke_agent.duration histogram for the whole turn (new in JS: otel_metrics.recordInvokeAgentDuration and trace_types.METRIC_GEN_AI_INVOKE_AGENT_DURATION; the Python metric already existed). - Each generation is a `generation` event with lk.generation_id and lk.parent_generation_id (SpeechHandle._generationId / _parentGenerationId, `<speech id>_<step>` like Python); the span carries the latest generation id and the new lk.generation_count. - A preemptive generation discarded for a successor answering the same user turn hands its open agent_turn over (preemptive_generation_discarded event, lk.speech_id follows the speech that answered), both on a newer attempt and on the real reply after onUserTurnCompleted invalidated it. The queue-wait and interruption helpers tolerate an ended span. - `say` gets an agent_turn too, as in Python's _tts_task; JS had none. Tests: agent_turn_span.test.ts (tool call is one turn with two generation events and every step nested; plain reply is one generation; discarded preemptive hand-off; LLM failure fails the turn; duration metric when sampled out; sampled-out hand-off). The preemptive-guard stand-in handle gained _takeAgentTurn; the PII key test skips METRIC_* names, which are not attribute keys. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
davidzhao
force-pushed
the
dz/telemetry-agent-turn
branch
from
September 16, 2026 04:34
dd4e334 to
241cd41
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Port of livekit/agents#7143. Stacked on #2499.
Description
A reply that calls a tool runs two generations (LLM steps) in two tasks. They were two
agent_turnspans linked only bylk.parent_generation_id, so one response rendered as two turns. A speech handle is now exactly oneagent_turn.say) opensagent_turnunderagent_session; the follow-up generation after a tool call continues the open span instead of opening a second one. It ends with the speech inSpeechHandle._markDone, recording the speech's error (redaction-aware) and thegen_ai.invoke_agent.durationmetric for the whole turn.generationevent on the span withlk.generation_idandlk.parent_generation_id; the span'slk.generation_idnames the latest generation and the newlk.generation_counthow many there were.lk.speech_idis set at creation.llm_node,function_tool,tts_node,realtime_inferenceandagent_speakingnest under the one turn.agent_turnto that successor (preemptive_generation_discardedevent;lk.speech_idfollows the speech that answered). An attempt cancelled with no successor still ends as its own turn. The queue-wait and interruption helpers tolerate a span already ended with the speech.Changes Made
voice/speech_handle.ts: span ownership,_generationId/_parentGenerationId,_takeAgentTurn/_continueAgentTurn, end in_markDone.voice/agent_activity.ts:withAgentTurnandcontinueDiscardedTurnwrap the pipeline, realtime andsaytasks; per-step span creation removed.telemetry/otel_metrics.ts:recordInvokeAgentDuration(gen_ai.invoke_agent.duration, units, job attribution);telemetry/trace_types.ts:lk.generation_count, the metric name.Adaptations from the Python source
saypreviously had noagent_turnspan in JS; it now gets one like Python's TTS task.lk.generation_id/lk.parent_generation_idwere never stamped onagent_turnin JS before (the constants existed unused); they are now, as<speech_id>_<step>.Testing
voice/agent_turn_span.test.ts(6 tests): a tool-calling reply is oneagent_turnwith twogenerationevents and bothllm_nodes, the tool,tts_nodeandagent_speakinginside it; a plain reply is one generation; the discarded preemptive hand-off; an LLM failure fails the turn; the duration metric when sampled out.agentssuite green; build, typecheck, lint, API report updated.🤖 Generated with Claude Code