Skip to content

fix(agents): make mergeFrames linear instead of quadratic#2081

Open
adaro wants to merge 1 commit into
livekit:mainfrom
adaro:fix/merge-frames-linear
Open

fix(agents): make mergeFrames linear instead of quadratic#2081
adaro wants to merge 1 commit into
livekit:mainfrom
adaro:fix/merge-frames-linear

Conversation

@adaro

@adaro adaro commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #1995.

mergeFrames in agents/src/utils.ts rebuilt the accumulated buffer with a boxed spread on every iteration (data = new Int16Array([...data, ...frame.data])), so merging k frames of S total samples cost O(S*k) element copies with per-element boxing instead of O(S). This function sits on several hot paths: the Silero VAD inference loop calls it twice per 32ms window, all the buffer-based STT plugins' recognize paths pass whole-utterance frame arrays, and TTS ChunkedStream.collect() merges every synthesized frame.

Measured on the repro from the issue (10ms 48kHz mono frames, median of repeated runs):

N frames before (ms) after (ms)
10 0.63 0.002
100 65.9 0.013
500 1654 0.055
3000 (30s utterance) 61957 0.219

Before the fix, ratios grow quadratically (N=500 is ~2600x N=10); after, growth is linear and a 30s utterance merge drops from ~62s of blocked event loop to 0.22ms.

Changes Made

  • mergeFrames now validates and sums frame lengths in one pass, then copies each frame once into a preallocated Int16Array via set() — same approach as the local helper already in agents/src/inference/vad.ts. Semantics are unchanged: same TypeErrors for empty/mismatched buffers, always returns a fresh frame for array input, passthrough for single-frame input.
  • Added unit tests for mergeFrames (ordering, multi-channel, error cases) — there was no existing coverage.
  • Added a changeset (@livekit/agents patch).

Pre-Review Checklist

  • Build passes: All builds (lint, typecheck, tests) pass locally
  • AI-generated code reviewed: Removed unnecessary comments and ensured code quality
  • Changes explained: All changes are properly documented and justified above
  • Scope appropriate: All changes relate to the PR title, or explanations provided for why they're included
  • Video demo: N/A (no behavior change; perf fix with benchmark above)

Testing

  • Automated tests added/updated (if applicable)
  • All tests pass

pnpm build (tsup + tsc --declaration), eslint, prettier --check, and the full @livekit/agents vitest suite (1336 passed) all pass locally. Note: src/telemetry/traces.otel2.type.test.ts fails to import on a clean checkout of main too (missing optional otel2 dep, see #1879) — unrelated to this change.

Additional Notes

@livekit/rtc-node's combineAudioFrames has the same shape of problem (buffer.map((x) => [...x.data]).flat() — linear but heavily boxed), which is why this PR fixes mergeFrames in place rather than delegating to it.

🤖 Generated with Claude Code

mergeFrames rebuilt the accumulated buffer with a boxed spread on every
frame (new Int16Array([...data, ...frame.data])), costing
O(total_samples * frame_count) copies. Merging a 30s utterance of 10ms
frames (k=3000) blocked the event loop for ~60s; it now sums lengths in
one pass and copies each frame once into a preallocated Int16Array.

Hot callers: the Silero VAD inference loop (twice per 32ms window), the
STT plugins' recognize paths (whole-utterance buffers), and TTS
ChunkedStream.collect().

Fixes livekit#1995

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@adaro
adaro requested a review from a team as a code owner July 21, 2026 22:29
@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b0d5a26

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

This PR includes changesets to release 38 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen 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

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

Copy link
Copy Markdown
Contributor

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 bugs or issues to report.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant