befc63cfe0
根因: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
60 lines
3.0 KiB
TypeScript
60 lines
3.0 KiB
TypeScript
// 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:true(Arcrun#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,
|
||
// reindex(Arcrun#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;
|