Repository navigation
feat: record LLM input as deltas on trace spans - #2646
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Note
Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.
Devin Review found 6 potential issues.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
- isEquivalent keeps comparing function-call thoughtSignature, groupId and extra, as it did before the port; fingerprints hash the same fields - restore the original placeholder output id (lk_running_placeholder/<callId>) and its prefix-based cleanup, so a real output is never stripped by suffix - a request whose input was not captured (setCaptureContent(false)) no longer becomes a delta parent, matching Python - media fingerprints drop the per-image token caches: a URL counts as itself, inline data by its length and a video frame by its identity - the tracker keeps its baselines in an internal field instead of a WeakMap, and the module documents the design (ASD-STE100), as in Python - changeset is a patch, like the repo's other changesets Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: abed250 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 |
- inline image data counts by a digest computed once per image object (and again when its data changes), so a replacement of the same length is detected; the data itself is never kept - media stand-ins apply only to a message's content entries: a function call's extra is fingerprinted as plain data, so an object in it that looks like chat content no longer crashes fingerprinting - isEquivalent compares audio frames by identity, as Python's does, so a preemptive answer is not reused for different audio Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
davidzhao
approved these changes
Oct 5, 2026
Merged
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.
Summary
record: { inputDelta: true }telemetry forllm_nodeand providerllm_requestspansPort of livekit/agents#7605.
Validation
npx --yes node@24 /usr/local/bin/pnpm test agents --silent(180 files, 3043 passed, 5 skipped)pnpm build:agentspnpm --filter @livekit/agents typecheckpnpm --filter @livekit/agents throws:checkpnpm --filter @livekit/agents api:checkThe repository-wide
pnpm lintandpnpm format:checkcommands were also run. They remain blocked by existing formatting/lint findings outside this port (295 lint findings and 42 files reported by Prettier).cue-cliruntime driving was not available in this environment: the binary is not installed anduvx cue-clicannot resolve a registry package. Equivalent runtime behavior is covered by the new realAgentSessionspan tests, including scheduling/commit and disabled-option paths. There is no target infrastructure or behavior gap.Source diff coverage
livekit-agents/livekit/agents/llm/chat_context.py: adapted toagents/src/llm/chat_context.ts; TypeScript chat-item shapes share essential-field equality and media-safe fingerprints.livekit-agents/livekit/agents/llm/llm.py: adapted toagents/src/llm/llm.ts; provider request spans use the async-local delta scope and fallback wrapper spans omit duplicate input only in delta mode.livekit-agents/livekit/agents/telemetry/__init__.py: adapted toagents/src/telemetry/index.ts; exports the TypeScriptinputDeltamodule through the telemetry barrel.livekit-agents/livekit/agents/telemetry/gen_ai.py: adapted toagents/src/telemetry/gen_ai.ts; canonical instruction splitting, in-place system messages, and message layout use JS GenAI types.livekit-agents/livekit/agents/telemetry/input_delta.py: adapted as newagents/src/telemetry/input_delta.ts; usesAsyncLocalStorageand OpenTelemetry JS span/link APIs.livekit-agents/livekit/agents/telemetry/trace_types.py: ported toagents/src/telemetry/trace_types.ts.livekit-agents/livekit/agents/voice/agent_activity.py: adapted toagents/src/voice/agent_activity.ts; each activity owns a tracker, inference tasks inherit the scope, and scheduled speech commits it.livekit-agents/livekit/agents/voice/agent_session.py: adapted toagents/src/voice/agent_session.ts; the public option uses idiomaticinputDeltaand remains off by default.livekit-agents/livekit/agents/voice/generation.py: adapted toagents/src/voice/generation.ts; node spans record deltas and running-tool placeholders use stable IDs.tests/test_chat_ctx.py: adapted toagents/src/llm/chat_context.test.tsusing Vitest.tests/test_input_delta.py: adapted to newagents/src/telemetry/input_delta.test.tsandagents/src/voice/input_delta_session.test.ts; tracker, reconstruction, option-off, fallback, and real session lifecycle scenarios are covered.tests/test_running_tool_placeholders.py: ported toagents/src/voice/running_tool_placeholders.test.ts.tests/test_trace_types_pii.py: ported toagents/src/telemetry/trace_types.test.ts.No source files were not applicable. Target-only recording-option fixtures, the generated API report, and a minor changeset for
@livekit/agentswere updated as required adaptations.Ported from livekit/agents#7605
Original PR description
Summary
At each turn, the LLM spans record all of the conversation and all of the system instructions. Thus, the size of the spans increases at each turn of a call.
This change adds an option. When you set the option, the spans record only the input that changed. The model always receives all of the input. The option changes only the telemetry.
The option is off by default. When the option is off, the spans do not change.
Design
The module
telemetry/input_delta.pycontains the full description. This is a summary.Terms
llm_nodespan, the record islk.pii.chat_ctx, with one entry for each chat item. On anllm_requestspan, the record isgen_ai.input.messages, with one entry for each GenAI message.llm_requestspan, the instructions are ingen_ai.system_instructions, not in the record. All other system messages are entries of the record, at their position. This change togen_ai.system_instructionsalso applies when the option is off.Procedure for each span
lk.input.dropped_from_basegives the number of parent entries after this prefix.llm_requestspan, if the instructions are the same as in the parent, the span does not recordgen_ai.system_instructions.Rebuild
lk.input.base_span_id). Use this procedure again if the parent also has a parent.lk.input.dropped_from_baseentries.If an
llm_requestspan with a parent has nogen_ai.system_instructions, use the instructions of the parent.Rules
gen_ai.input.messages, consecutive tool calls of an assistant turn are one message, also when a skipped item (for example, a config update) is between them. The prefix never stops inside such a message.llm_nodespan, the instructions are the first entry oflk.pii.chat_ctx. Thus, a change of the instructions causes a full record oflk.pii.chat_ctx.AgentActivityhas its own tracker. After a handoff, the first span records all of the input.Example (expressive mode)
llm_nodelk.input.dropped_from_base[I, u1, G][I, u1, G](full)[I, u1, a1, u2, G][a1, u2, G][I, …, u2, a2, u3, G][a2, u3, G][I, …, u3, G, fc, fo][fc, fo]Attributes on a delta span:
lk.input.delta,lk.input.base_span_id(and a span link to the parent),lk.input.dropped_from_base.Relation to #7539
This change replaces the delta mode of #7539. It keeps these items from #7539: content fingerprints, no input on delegating spans, and the test scenarios.
This change is different from #7539 in these items:
lk.pii.chat_ctxstays on the span as a delta. #7539 removes it.Refactor
Each
ChatItemtype now has the base class_ChatItemBase. This base class has no fields. Each type declares its essential fields one time in_essential_fields().ChatContext.is_equivalentcompares these fields, and_fingerprint()hashes them. The operation ofis_equivalentdoes not change. New tests intests/test_chat_ctx.pyexamine it.Tests
tests/test_input_delta.pycontains unit tests for the tracker. It also contains session tests: two turns with a tool step, the option off, and a fallback.