Skip to content

feat(retrieval): hybrid search with Vectorize + D1 FTS5 and RRF fusion - #86

Merged
liplus-lin-lay merged 1 commit into
mainfrom
85-enhancement-hybrid-retrieval-vectorize-+-d1-fts5-with-rrf-fusion
Apr 19, 2026
Merged

feat(retrieval): hybrid search with Vectorize + D1 FTS5 and RRF fusion#86
liplus-lin-lay merged 1 commit into
mainfrom
85-enhancement-hybrid-retrieval-vectorize-+-d1-fts5-with-rrf-fusion

Conversation

@liplus-lin-lay

Copy link
Copy Markdown
Member

Refs #85

概要

Dense-only retrieval を hybrid search (dense + sparse BM25) に置換します。Vectorize BGE-M3 (dense) と D1 FTS5 BM25 (sparse) を Reciprocal Rank Fusion (k=60) で合成し、特に Diff RAG における judgment-history retrieval の recall を引き上げることが狙いです。

主な変更

  • D1 binding 追加 (wrangler.toml): DB_FTS (github-rag-fts) + migrations_dir = "migrations"
  • migration 新規 (migrations/0001_fts5_init.sql): search_docs content table + 2 つの FTS5 virtual table (porter + unicode61 自然言語用 / trigram コード・SHA・identifier 用) + trigger で FTS5 側の自動同期
  • src/fts.ts 新規: upsert / delete helper、BM25 query (両 tokenizer への UNION)、RRF 合成 util
  • pipeline.ts: issue / PR / release / doc / diff いずれも Vectorize upsert 後に同じ content を D1 FTS5 へ mirror。tokenizer_kind は type から自動選択。FTS5 失敗は Vectorize upsert を無効化しない
  • webhook.ts / poller.ts: delete 経路 (issue / PR / release / doc) で D1 FTS5 同期削除。diff は append-only なので対象外
  • mcp.ts search_issues: dense (Vectorize) と sparse (D1 FTS5 BM25) を並列 query → RRF で rank 合成 → label / assignee post-filter。fusion パラメータで rrf (default) / dense_only / sparse_only を切替可能。result に dense_score / sparse_score / dense_rank / sparse_rank を追加、top-level に fusion / dense_candidates / sparse_candidates
  • admin endpoint: /admin/reset-hashessearch_docs の該当 repo 行も削除
  • docs (ja + en): Architecture 図、Hybrid Retrieval section、Reciprocal Rank Fusion 説明、Free-tier hard-stop 記述、search_issues の fusion パラメータを追記
  • installation docs (ja + en): wrangler d1 create github-rag-ftswrangler d1 migrations apply 手順を追加

後方互換性

  • breaking: search_issuesscore の意味が cosine similarity (dense only) から RRF fused score に変わります。dense_score で従来の cosine 値を別途参照可能
  • result のフィールド追加 (dense_score / sparse_score / dense_rank / sparse_rank) は non-breaking
  • Issue enhancement: hybrid retrieval (Vectorize + D1 FTS5) with RRF fusion #85 は breaking を明示的に許容しています

デプロイ前に必要な手作業

  1. wrangler d1 create github-rag-fts で D1 database を作成
  2. 返却された database_idwrangler.toml の placeholder (REPLACE_WITH_D1_DATABASE_ID) に差し替え
  3. wrangler d1 migrations apply github-rag-fts --remote で初回 migration を適用
  4. wrangler deploy 後、必要なら /admin/reset-hashes?repo=… で既存 repo を reindex

動作確認

  • npx tsc --noEmit: pass
  • npx wrangler deploy --dry-run: pass (env.DB_FTS (github-rag-fts) が bindings に含まれることを確認)

注意

database_id は placeholder のまま commit しています。CI/CD が D1 database_id 実在性を validate する場合、最初のデプロイ前に Cloudflare 側で database を作成して ID を埋める必要があります。

Milestone

v0.6.0

🤖 Generated with Claude Code

Replace dense-only retrieval with DIY hybrid search: Vectorize BGE-M3
embeddings on the dense side and D1 FTS5 BM25 on the sparse side, combined
via Reciprocal Rank Fusion (k=60).

- Add D1 binding (DB_FTS) and migrations/0001_fts5_init.sql providing
  search_docs content table + two FTS5 virtual tables (porter tokenizer
  for natural language, trigram for diffs). Triggers fan out automatically
  so DELETE on search_docs clears both virtual tables.
- New src/fts.ts: upsert/delete helpers, BM25 query across both tokenizers,
  and the reciprocal_rank_fusion utility.
- pipeline.ts: mirror every Vectorize upsert (issue/PR/release/doc/diff)
  into D1 FTS5 with the matching tokenizer_kind. FTS5 failures are logged
  but never invalidate a successful Vectorize write.
- webhook/poller delete paths fan out to D1 FTS5 for issue/PR/release/doc;
  diffs remain append-only by design.
- mcp.ts search_issues runs dense and sparse queries in parallel and
  fuses them via RRF. Adds a fusion parameter (rrf / dense_only /
  sparse_only) for debugging and surfaces per-result dense_score,
  sparse_score, dense_rank, sparse_rank alongside the fused score.
- admin reset-hashes now also clears search_docs for the given repo.
- docs (ja + en) updated with hybrid retrieval architecture, D1 FTS5 /
  BM25 / RRF sections, fusion toggle, and Free tier hard-stop guarantees.
- installation docs (ja + en) now describe `wrangler d1 create` and
  `wrangler d1 migrations apply` steps for DB_FTS.

dense-only retrieval is known to lose recall on short identifiers, SHA
prefixes, and exact-term queries. Hybrid BM25 + dense is the 2026
production baseline and directly targets the Diff RAG judgment-history
surface where commit messages, file paths, and SHAs dominate.

Refs #85

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
github-rag-mcp 316899a Apr 19 2026, 11:06 AM

@liplus-lin-lay
liplus-lin-lay merged commit 3d09e0e into main Apr 19, 2026
2 of 3 checks passed
@liplus-lin-lay
liplus-lin-lay deleted the 85-enhancement-hybrid-retrieval-vectorize-+-d1-fts5-with-rrf-fusion branch April 19, 2026 11:08
liplus-lin-lay added a commit that referenced this pull request Apr 19, 2026
Refs #87

PR #86 で導入した D1 binding の database_id placeholder を
実際に作成済みの D1 database (github-rag-fts, APAC region) の ID に差替え。
これで Cloudflare Workers Builds が placeholder で落ちる状態を解消する。

The D1 migration 0001_fts5_init.sql has already been applied to the remote
database via `wrangler d1 execute --file` (since the multi-statement BEGIN...END
triggers trip up wrangler's migration splitter). Migration state is recorded
in the d1_migrations tracking table.
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.

enhancement: hybrid retrieval (Vectorize + D1 FTS5) with RRF fusion

1 participant