Files
Arcrun/kbdb/src/routes/embed.ts
T
kbdb-cc befc63cfe0 fix(kbdb/embed): 語意查詢 metadata 過濾根因修復(Arcrun#11)
根因:Vectorize index 建了卻從沒建 metadata index。CF Vectorize v2 要對某
metadata 欄位下 filter,必須先為該欄建 metadata index,否則帶 owner_id/
entry_type/source 過濾的語意查詢一律回 0 命中(app 端 filter 接線本來就對)。
且 metadata index 只索引「建立後 upsert」的向量 → 既有向量須重推才會被收錄。

- deploy.ts:加 ensureVectorizeMetadataIndexes(),隨部署冪等建 owner_id/
  entry_type/source(string)三個 metadata index(self-host/官方帳號皆自動)。
- embed.ts / routes/embed.ts:backfillEmbeddings 加 reindex+offset,重推「所有
  embeddable(含 is_embedded=1)」既有向量,讓事後建立的 metadata index 收錄;
  POST /embed/backfill {"reindex":true} 觸發,offset 分頁到 remaining=0。
- wrangler.toml:註解補 create-metadata-index 手動步驟 + reindex 提示。
- tests:mock DB 對齊 LIMIT?/OFFSET? 與 reindex predicate;補 reindex 測試。

leo21c 已驗:owner_id=leo / entry_type 過濾修前 0→修後命中,不帶過濾不變。
已知後續(非本 bug 症狀):source 值 89-91 bytes 超過 Vectorize string
metadata index 的 64-byte 索引上限 → source 過濾對長值失效,另案。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJiLCRUU2o3aSpPEzVCt2o
2026-07-06 06:03:43 +00:00

60 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Embed module admin route — backfill existing entries (issue #7 / mira-dissolve T2.4 缺口).
//
// 背景:embed 原本只有「寫入即嵌」(embedOnWrite),對「開 Vectorize binding 之前就寫入」或
// 「embed-on-write 當時漏掉」的既有 entry 沒有回填路徑 → is_embedded=0 且永遠補不回。
// 本 route = 把 embed.ts 既有 embedText/VECTORIZE.upsert 邏輯包成可重複呼叫的批次補嵌端點。
//
// 鐵律對齊:embedding 屬 base optional 模組;模組未開(無 VECTORIZE+AI)→ 誠實回 409,不假裝(mindset §7)。
// base 對內容語意無知:只認通用 metadata.embed===true 旗標,不知 triplet/wiki(解耦)。
import { Hono } from 'hono';
import type { Bindings } from '../types';
import { embedEnabled, backfillEmbeddings, backfillStatus } from '../embed';
export const embedRoutes = new Hono<{ Bindings: Bindings }>();
const OFF_HINT =
'語義補嵌需先開 embed 模組(Vectorize+AI binding)。叫 CC「幫我開語義查詢」(設 kbdb_embed:true + redeploy 注入 binding)後再呼叫本端點。';
// POST /embed/backfill — batch-embed existing embeddable entries with is_embedded=0.
// body(皆選填):{ limit?:1-100(預設25, owner_id?, source?, reindex?, offset? }。
// 冪等:重跑不會重複嵌(已 is_embedded=1 的不再入選;upsert 同 id 冪等)。
// 分批:單次最多 limit 筆;回傳 remaining>0 表示還有 → 重複呼叫直到 remaining=0。
// reindex:trueArcrun#11):改重推「所有 embeddable」既有向量(含 is_embedded=1),
// 讓事後建立的 Vectorize metadata index 收錄它們(否則帶過濾語意查詢回 0);配 offset 分頁。
// 模組未開 → 409 + capability_hint(不假綠)。
embedRoutes.post('/backfill', async (c) => {
if (!embedEnabled(c.env)) {
return c.json(
{ success: false, error: 'embed module not enabled (need VECTORIZE + AI bindings)', capability_hint: OFF_HINT },
409,
);
}
const body = (await c.req.json().catch(() => ({}))) as {
limit?: number | string;
owner_id?: string;
source?: string;
reindex?: boolean;
offset?: number | string;
};
const result = await backfillEmbeddings(c.env, {
limit: body.limit !== undefined ? Number(body.limit) : undefined,
owner_id: body.owner_id || undefined,
source: body.source || undefined,
// reindexArcrun#11):重推既有向量讓事後建立的 Vectorize metadata index 收錄(見 embed.ts)。
reindex: body.reindex === true,
offset: body.offset !== undefined ? Number(body.offset) : undefined,
});
return c.json({ success: true, ...result });
});
// GET /embed/backfill/status?owner_id=&source= — 待補嵌 / 已補嵌計數(回報 + 判斷是否清零用)。
embedRoutes.get('/backfill/status', async (c) => {
const status = await backfillStatus(c.env, {
owner_id: c.req.query('owner_id') || undefined,
source: c.req.query('source') || undefined,
});
return c.json({ success: true, ...status });
});
export default embedRoutes;