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:
@@ -107,3 +107,80 @@ describe('GET /entries/search?mode=semantic — 零命中時分辨「為什麼
|
||||
expect(body.capability_hint).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── 第四態 filter_blind(Arcrun#85 D70,2026-08-11 leo21c 實撞)─────────────────
|
||||
//
|
||||
// 現場:現役 Vectorize index `arcrun-kbdb-embed-m3` 上**一個 metadata index 都沒有**
|
||||
// (真兇=arcrun-rag 安裝器把端點寫成 `metadata-index/create`,連字號版 CF 回 404,
|
||||
// 底線 `metadata_index/create` 才是對的;而該安裝器把失敗降級成一行 ⚠ 就宣告成功)。
|
||||
// ⇒ Vectorize 對 owner_id 下 filter 一律回 0 筆,而**每一條真實使用者路徑都帶 owner_id**
|
||||
// 做租戶隔離 ⇒ 語意搜尋 100% 全盲。
|
||||
// 實測(leo21c,同一句查詢):不帶 filter → 1 命中 score 0.8957;帶 owner_id → 0 命中。
|
||||
//
|
||||
// 舊行為把這個歸成 no_match,回「換個說法或更具體的關鍵字再試試看」
|
||||
// =**把系統故障說成使用者的問題**,正是 leo 2026-08-09 直令禁止的那件事,
|
||||
// 而且沒有人會因為「搜不到」去翻 Cloudflare 的 Vectorize 設定。
|
||||
function makeFilterAwareEnv(
|
||||
dbOpts: Parameters<typeof makeFakeDB>[0],
|
||||
opts: { unfiltered: { id: string; score: number }[]; filtered: { id: string; score: number }[] },
|
||||
): Bindings {
|
||||
return {
|
||||
DB: makeFakeDB(dbOpts),
|
||||
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.filtered : opts.unfiltered };
|
||||
},
|
||||
},
|
||||
} as unknown as Bindings;
|
||||
}
|
||||
|
||||
describe('empty_reason=filter_blind — Vectorize metadata 過濾整個是死的', () => {
|
||||
it('帶 owner_id 零命中、拿掉 filter 有命中 → filter_blind,且照實說是我們的故障', async () => {
|
||||
const app = makeApp();
|
||||
const env = makeFilterAwareEnv(
|
||||
{ embeddedCount: 805, hydrateEntry: mkEntry('e1') },
|
||||
{ unfiltered: [{ id: 'e1', score: 0.8957 }], filtered: [] },
|
||||
);
|
||||
const res = await app.request('/entries/search?q=黑面琵鷺&mode=semantic&owner_id=bfezv28v', {}, env);
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
expect(body.count).toBe(0);
|
||||
expect(body.empty_reason).toBe('filter_blind');
|
||||
const hint = body.capability_hint as string;
|
||||
// 🔴 誠實鐵律:是故障、不是使用者的錯,且**明說換個說法沒有用**
|
||||
expect(hint).toContain('故障');
|
||||
expect(hint).toContain('不是你');
|
||||
expect(/換個說法也不會有用/.test(hint)).toBe(true);
|
||||
// 🔴 人話紅線:不准把 Vectorize/owner_id 這類內部詞漏給使用者
|
||||
expect(/vectorize|owner_id|metadata|index/i.test(hint)).toBe(false);
|
||||
// 技術細節與**處方順序**留給維運者
|
||||
const admin = body.admin_hint as string;
|
||||
expect(admin).toContain('metadata index');
|
||||
expect(admin).toContain('reindex');
|
||||
});
|
||||
|
||||
it('沒帶任何 filter 的查詢不做探針,維持 no_match(不多花一次查詢)', async () => {
|
||||
const app = makeApp();
|
||||
let queries = 0;
|
||||
const env = {
|
||||
DB: makeFakeDB({ embeddedCount: 42 }),
|
||||
ENVIRONMENT: 'test',
|
||||
AI: { async run() { return { data: [[0.1, 0.2, 0.3]] }; } },
|
||||
VECTORIZE: { async query() { queries++; return { matches: [] }; } },
|
||||
} as unknown as Bindings;
|
||||
const res = await app.request('/entries/search?q=x&mode=semantic', {}, env);
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
expect(body.empty_reason).toBe('no_match');
|
||||
expect(queries).toBe(1);
|
||||
});
|
||||
|
||||
it('帶 filter 但拿掉 filter 也零命中 → 仍是 no_match(別把正常的找不到誣賴成故障)', async () => {
|
||||
const app = makeApp();
|
||||
const env = makeFilterAwareEnv({ embeddedCount: 42 }, { unfiltered: [], filtered: [] });
|
||||
const res = await app.request('/entries/search?q=x&mode=semantic&owner_id=t1', {}, env);
|
||||
const body = (await res.json()) as Record<string, unknown>;
|
||||
expect(body.empty_reason).toBe('no_match');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user