WIP(kbdb): 語意搜尋零命中的排查——⚠️ 被總管中途叫停,未完成驗證
leo 2026-08-11 判斷「如果是 Vectorize 沒完成就不用查了」,總管據此停線。 真因已經寫在 repo 自己的註解裡(kbdb/wrangler.toml:43-51,Arcrun#11): metadata index 只收「建立後 upsert」的向量,既有向量須 reindex, 否則帶 owner_id filter 一律 0 命中——與實測每一格吻合 (805 筆在、關鍵字搜得到、語意 0、拿自己查自己也 0 ⇒ 不是分數門檻)。 ⚠️ 這批改動是排查途中的產物,**沒有走完驗證**,不要當成可用的修法。 保留只是不讓它憑空消失(總管中斷造成,不是它做壞)。 接手的人請先讀 Arcrun#85 上的結論再決定要不要用。 真正的補救是 reindex,而 reindex 要燒 AI 額度 ⇒ 卡在 Arcrun#85 的每日額度閘上線之後才能做。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,70 @@ describe('embedSelfTest(檢修孔:卡片自我查詢,驗證 index 真的
|
||||
expect(r.passed).toBeNull();
|
||||
});
|
||||
|
||||
// ── Arcrun#85 D70(2026-08-11 leo21c 全盲事故)──────────────────────────────
|
||||
// 兩種故障的**處方相反**,舊版都只回一句「需要重新 reindex」:
|
||||
// ① metadata filter 死掉(metadata index 沒建)→ 先建 index 再 reindex;只 reindex 無效
|
||||
// ② 向量不在現役 index(換世代沒重嵌) → reindex 才是對的
|
||||
// 判法=拿掉 filter 再查一次。下面的假 VECTORIZE 依「有沒有帶 filter」回不同結果,
|
||||
// 精確重現 leo21c 的現場(不帶 filter score 0.8957 命中、帶 owner_id 0 命中)。
|
||||
function makeFilterAwareEnv(
|
||||
store: Entry[],
|
||||
opts: { unfilteredMatches: { id: string; score: number }[]; filteredMatches: { id: string; score: number }[] },
|
||||
): Bindings {
|
||||
return {
|
||||
DB: makeFakeDB(store),
|
||||
ENVIRONMENT: 'test',
|
||||
AI: { async run() { return { data: [[0.1, 0.2, 0.3]] }; } },
|
||||
VECTORIZE: {
|
||||
async query(_v: number[], o?: { filter?: Record<string, unknown> }) {
|
||||
const filtered = !!(o?.filter && Object.keys(o.filter).length > 0);
|
||||
return { matches: filtered ? opts.filteredMatches : opts.unfilteredMatches };
|
||||
},
|
||||
},
|
||||
} as unknown as Bindings;
|
||||
}
|
||||
|
||||
it('不帶 filter 搜得到、帶 owner_id 搜不到 → filter_blind:true,處方是「先建 metadata index 再 reindex」', async () => {
|
||||
const store = [mkEntry('e1', '淡水河口的黑面琵鷺在退潮時會集體覓食', 'bfezv28v')];
|
||||
const env = makeFilterAwareEnv(store, {
|
||||
unfilteredMatches: [{ id: 'e1', score: 0.8957 }], // leo21c 實測分數
|
||||
filteredMatches: [],
|
||||
});
|
||||
const r = await embedSelfTest(env, { owner_id: 'bfezv28v' });
|
||||
expect(r.tested).toBe(true);
|
||||
expect(r.passed).toBe(false);
|
||||
expect(r.filter_blind).toBe(true);
|
||||
expect(r.note).toContain('metadata');
|
||||
// 🔴 處方順序必須寫出來——只叫人 reindex 正是 leo21c 修不好的原因
|
||||
expect(r.note).toContain('reindex');
|
||||
expect(r.note).toMatch(/先.*建.*再/s);
|
||||
});
|
||||
|
||||
it('帶不帶 filter 都搜不到 → filter_blind:false,處方才是 reindex', async () => {
|
||||
const store = [mkEntry('e1', 'content', 'o1')];
|
||||
const env = makeFilterAwareEnv(store, { unfilteredMatches: [], filteredMatches: [] });
|
||||
const r = await embedSelfTest(env, { owner_id: 'o1' });
|
||||
expect(r.passed).toBe(false);
|
||||
expect(r.filter_blind).toBe(false);
|
||||
expect(r.note).toContain('reindex');
|
||||
expect(r.note).not.toContain('metadata index 沒建');
|
||||
});
|
||||
|
||||
it('帶 filter 就搜得到 → passed:true、filter_blind:null,且不多花第二次查詢', async () => {
|
||||
const store = [mkEntry('e1', 'content', 'o1')];
|
||||
let queries = 0;
|
||||
const env = {
|
||||
DB: makeFakeDB(store),
|
||||
ENVIRONMENT: 'test',
|
||||
AI: { async run() { return { data: [[0.1, 0.2, 0.3]] }; } },
|
||||
VECTORIZE: { async query() { queries++; return { matches: [{ id: 'e1', score: 0.9 }] }; } },
|
||||
} as unknown as Bindings;
|
||||
const r = await embedSelfTest(env, { owner_id: 'o1' });
|
||||
expect(r.passed).toBe(true);
|
||||
expect(r.filter_blind).toBeNull();
|
||||
expect(queries).toBe(1); // 健康的情況不該多打一次(成本紀律)
|
||||
});
|
||||
|
||||
it('回應絕不含卡片內容或 entry id(隱私紅線)', async () => {
|
||||
const store = [mkEntry('e1', 'this is the secret card body, must never leak')];
|
||||
const env = makeEnv(store, { matches: [{ id: 'e1', score: 0.9 }] });
|
||||
|
||||
Reference in New Issue
Block a user