Skip to content

fix(pipeline): batch-embed diffs by token budget, not input count #236

Description

@smileygames

purpose

commit diff の batch embed の分割軸を、file 件数から推定 token 予算へ変更する。

observation (2026-08-14, Cloudflare Observability)

毎 cron 以下が出力されている。

Failed to batch-embed diffs for Liplus-Project/neuron-graph-rag@1fb0f6b... chunk offset 0:
  3030: Max context reached 60678 tokens but model supports only 60000

観測されたトークン数: 60,678 / 64,413 / 74,980 / 85,920。model 側の batch 集約上限は 60,000。

その結果、forward watermark が停止している。

repo forward watermark boundary commit
Liplus-Project/neuron-graph-rag 2026-08-11T15:33:02Z 1fb0f6b (failed)
Liplus-Project/github-webhook-mcp 2026-08-02T08:48:18Z add43f55 (failed)

失敗は決定論的(同一 commit・同一分割で毎回同じ結果)。cron 再試行では回復しない。同居している Too many subrequests 系の一過性エラーとは性質が異なる。

premise

現状の src/pipeline/embed-diff.tsMAX_EMBEDDING_BATCH_SIZE(20)による件数だけで chunk を切っている。1 input は MAX_EMBEDDING_INPUT_CHARS(8000 文字)まで truncate されるため、1 回の Workers AI 呼び出しに最大 20 × 8000 = 160,000 文字が載りうる。

「1 token あたりの文字数」は内容によって桁で変わる。ASCII のソースコードで約 3 文字/token、CJK の散文では約 1 文字/token。つまり件数上限は投入 token 量を全く縛れておらず、「どの patch も小さい」という暗黙の仮定の上でだけ成り立っている。

さらに src/pipeline/hash.tsprepareDiffEmbeddingInput は commit message を file ごとに複製して先頭へ付ける。長い日本語 commit 本文 × 20 file で、patch 本体を数える前に上限へ到達する。

影響は「その commit だけが失敗する」で止まらない。batch call が失敗すると chunk 全体(最大 20 file)が failed に計上され、src/poller.tsingestCommitDiff はそれを未取り込み commit として扱う。#178 で入れた watermark 不変条件によりその commit を追い越さないので、恒久的に失敗する commit が 1 つあると diff surface 全体がそこで停止する。

これは不変条件が正しく効いた結果であり、不変条件側の欠陥ではない。修正対象は embed 側のみ。

constraints

  • 分割は推定 token 予算で行う。件数軸は残さない(残せば同じ仮定が別の場所に残る)
  • 単独で予算を超える input も必ず 1 件の batch として送る。欠落させない、無限ループさせない。さらに削るのは truncate 軸(MAX_EMBEDDING_INPUT_CHARS)の責務
  • input の順序を保持する。返却 vector と file の対応は位置で取っているので、分割時は input 配列と file 配列を同じ境界で切る
  • chunk 単位の失敗隔離は現状維持(1 batch の失敗が後続 batch を止めない)
  • 通常サイズの commit は従来どおり 1 call にまとめる。call 数を無用に増やさない。この worker では Too many subrequests by single Worker invocation が現に発生しており、1 commit あたりの subrequest 数は逼迫している軸である
  • generateEmbeddingBatch の呼び出し元は src/pipeline/embed-diff.ts のみ(doc / issue / comment / release は単発 generateEmbedding 経路のため影響なし)
  • 既存 vector の再 embed は不要。停止した watermark は修正後の通常 cron で前進すれば足りる
  • 要件記述(docs/0-requirements.md / docs/0-requirements.ja.md)を同一 PR で更新する

target files

  • src/pipeline/embedding.ts — token 推定と batch 分割の実装。MAX_EMBEDDING_BATCH_SIZE を退役させる
  • src/pipeline/embed-diff.ts — 呼び出し側を新しい分割へ差し替え
  • src/pipeline/embedding.test.ts(新規)— 推定関数と分割関数の契約
  • src/pipeline/embed-diff.test.ts(新規)— pipeline 経路での分割挙動
  • docs/0-requirements.md / docs/0-requirements.ja.md — Embedding Pipeline 節の該当記述(source ⇔ docs の対)

acceptance

  • 1 batch の推定 token 数が上限(安全マージン込み)を超えない
  • 上記 2 repo の forward watermark が boundary commit を越えて前進する
  • 既存テストが通り、上限超過ケースの回帰テストが追加されている

Metadata

Metadata

Assignees

Labels

bug動いていない、壊れているready本文が実装開始できる形まで収束している状態。ただし更新は継続可能review-pending実装フェーズ完了。orchestration (brake eval / review / merge / close) 待ち

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions