fix(kbdb): /entries/search 服務端濾 deprecated(keyword+semantic),修下架不生效
daemon-beta t24(t11 斷點①②,總管 0.971 親復現):rag_takedown_direct 下架只把 metadata_json.status 標 deprecated(軟刪,append-only),但 /entries/search 兩 mode 都不濾,已下架內容照樣回傳——semantic 甚至最高分照吐。過去唯一的濾層只在 cypher-executor/portal-data.ts 客端(Arcrun#46),沒部署 rag_chat 的實例等於完全沒濾。 - entry-crud.ts:新增 json_extract($.status) NOT_DEPRECATED_PREDICATE(同 source/library 既有 json_extract 模式,issue #5.1/#18;不用 LIKE 避免 JSON 序列化格式誤判); searchEntries 新增 includeDeprecated 參數(尾端新增,向後相容)。 新增 isDeprecatedEntry:semantic 路徑用(Vectorize metadata 沒存 status,只能 hydrate 回完整 entry 後 JS 側判斷)。 - entries.ts:/entries/search 新增 include_deprecated 開關(預設 false,濾掉;true 給 管理面查殘留)。semantic 分支補位——過濾生效時先以 topK×3(封頂 100)向 Vectorize 多撈,hydrate+濾完再截斷回 caller 要求的量,避免命中大半下架時整頁被吃光 (t11 ZZ-T10 實測案例)。 - Vectorize 向量殘留本 PR 不動(只做查詢時過濾,not 同步刪向量)——取捨見 PR 描述。 測試:新增 search-deprecated-filter.test.ts 15 案(keyword 濾/include_deprecated 開關/ semantic 濾+補位+封頂+截斷/isDeprecatedEntry 單元);同步更新 search-source-and-score.test.ts 3 案的 topK 期望值(route 補位改變送進 Vectorize 的實際 topK,回應對外契約不變)。 kbdb 全套 60/60 綠、tsc --noEmit 乾淨。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -162,26 +162,32 @@ describe('#67 — route GET /entries/search(semantic)top_k / min_score / sco
|
||||
return makeApp(captured, makeSemanticEnv(calls));
|
||||
}
|
||||
|
||||
it('?top_k=5&min_score=0.5 → topK 透傳、低分截掉、entry 附 score', async () => {
|
||||
// daemon-beta t24(07-24 補位變更):route 現在對 Vectorize 的實際查詢 topK 會做「補位」
|
||||
// (預設過濾 deprecated 時 ×3 封頂 100,見 entries.ts 補位註解),不再是 top_k 原封透傳到
|
||||
// VECTORIZE.query。route 對 caller 的回應仍會照 top_k 截斷(見 body.count/entries 斷言不變)
|
||||
// ——這裡改的只是「送進 Vectorize 那次呼叫的 topK 參數」,非對外契約。三筆測試同步更新
|
||||
// calls[0].opts.topK 期望值(5→15=5×3、20→60=20×3),其餘斷言(回應筆數/內容/score)不動。
|
||||
|
||||
it('?top_k=5&min_score=0.5 → Vectorize 補位 topK=15(5×3)、回應仍照 top_k 截後低分尾、entry 附 score', async () => {
|
||||
const calls: { opts: Record<string, unknown> }[] = [];
|
||||
const { app, env } = makeSemanticApp(calls);
|
||||
const res = await app.request('/entries/search?q=x&mode=semantic&top_k=5&min_score=0.5', {}, env);
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as { mode: string; count: number; entries: (Entry & { score?: number })[] };
|
||||
expect(body.mode).toBe('semantic');
|
||||
expect(calls[0].opts.topK).toBe(5);
|
||||
expect(calls[0].opts.topK).toBe(15); // t24 補位:5 × 3
|
||||
expect(body.count).toBe(2); // 0.2 的低分尾被 min_score 截掉
|
||||
expect(body.entries.map((e) => e.id)).toEqual(['e-high', 'e-mid']);
|
||||
expect(body.entries.map((e) => e.score)).toEqual([0.9, 0.5]);
|
||||
});
|
||||
|
||||
it('不帶新參數 → topK=20、全量回傳(行為不變),entry 仍附 score(加欄不改形)', async () => {
|
||||
it('不帶新參數 → Vectorize 補位 topK=60(預設 20×3),回應仍全量回傳(行為不變),entry 仍附 score(加欄不改形)', async () => {
|
||||
const calls: { opts: Record<string, unknown> }[] = [];
|
||||
const { app, env } = makeSemanticApp(calls);
|
||||
const res = await app.request('/entries/search?q=x&mode=semantic', {}, env);
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as { count: number; entries: (Entry & { score?: number })[] };
|
||||
expect(calls[0].opts.topK).toBe(20);
|
||||
expect(calls[0].opts.topK).toBe(60); // t24 補位:預設 20 × 3
|
||||
expect(body.count).toBe(3);
|
||||
expect(body.entries[0].score).toBe(0.9);
|
||||
// 原有欄位一個不少(回應形狀向後相容)
|
||||
@@ -189,14 +195,14 @@ describe('#67 — route GET /entries/search(semantic)top_k / min_score / sco
|
||||
expect(body.entries[0].entry_type).toBe('block');
|
||||
});
|
||||
|
||||
it('壞值防呆:top_k=abc / top_k=0 / min_score=-1 → 視同沒帶(回預設,不 400)', async () => {
|
||||
it('壞值防呆:top_k=abc / top_k=0 / min_score=-1 → 視同沒帶(回預設 20,補位後 Vectorize topK=60,不 400)', async () => {
|
||||
for (const qs of ['top_k=abc', 'top_k=0', 'min_score=-1', 'top_k=abc&min_score=xyz']) {
|
||||
const calls: { opts: Record<string, unknown> }[] = [];
|
||||
const { app, env } = makeSemanticApp(calls);
|
||||
const res = await app.request(`/entries/search?q=x&mode=semantic&${qs}`, {}, env);
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as { count: number };
|
||||
expect(calls[0].opts.topK).toBe(20);
|
||||
expect(calls[0].opts.topK).toBe(60); // t24 補位:預設 20 × 3
|
||||
expect(body.count).toBe(3); // 無閾值 → 全量
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user