語意門檻改相對式+下架連帶刪向量(leo 兩個實測回饋)
## ① 「關懷型 AI 命中 20 筆、只有前 3 筆相關」⇒ 閾值太寬(leo 判斷正確) 固定門檻兩頭都不對,因為每個查詢的分數尺度不同(實測 youlin 實例): 關懷型 AI 正解 0.645-0.770,雜訊起於 0.547 ← 固定 0.5 放進 6 筆雜訊 閉環機 正解 0.552-0.638,雜訊起於 0.446 ← 固定 0.6 砍到剩 2/4(=早上的 0 命中) 人力媒合系統規劃書 正解 0.842,雜訊起於 0.550 ⇒ 改**相對門檻** max(0.45, top×0.8)。五組實測:固定 0.5 混入 9 筆雜訊/ 固定 0.6 有兩組正解被砍/相對式四組雜訊 0 且正解全留。 ## 🔴 寫測試才發現的真問題:門檻不能在 Vectorize 那層算 Vectorize 的 indexed metadata 沒有 status ⇒ 那層不知道誰已下架。 若最高分是下架殘影(t24 復現案 0.971),拿它算門檻=0.777, 會把 0.6 的正解一起砍光 ⇒ **又變成 0 命中**。 ⇒ 相對門檻移到 routes/entries.ts,接在「hydrate+濾下架」之後; embed.ts 只留絕對下限。新增測試鎖住這個順序。 ## ② leo:「理論上它的向量也要刪掉,就不會有殘影了吧?」——對,補上 單筆真刪已接 deleteByIds(b7af622),但「移除整個庫」走軟刪、向量原地不動。 ⇒ deprecate-by-library 同時 deleteByIds + is_embedded 歸零(D1 與 Vectorize 不說兩套話); backfill 兩條路徑(含 reindex)都排除 deprecated,否則下次補嵌會把殘影養回來。 不違背 t135「資料保留可還原」:D1 那列原封不動,還原後跑 backfill 重嵌即可。 回應新增 vectors_deleted,刪失敗誠實回 0 不假裝清乾淨。 驗:kbdb 91/91 綠(新增 4 項相對門檻測試,含「下架殘影不得決定門檻」) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -134,6 +134,37 @@ export async function deleteEntry(db: D1Database, id: string): Promise<void> {
|
||||
* 沿用既有 deprecated 機制:metadata_json.status='deprecated' → 搜尋端過濾、庫列表排除。
|
||||
* 回 deprecated 的筆數(0 = 庫名不存在或早已全部 deprecated)。
|
||||
*/
|
||||
/**
|
||||
* 撈出某 owner 下某庫、**目前還有向量**的 entry id(供下架時連帶清向量用)。
|
||||
*
|
||||
* 🔴 2026-08-05 leo:「已經被刪掉的內容?理論上它的向量也要刪掉,就不會有殘影了吧?」——對。
|
||||
* 單筆真刪(`DELETE /entries/:id`)已經接了 `VECTORIZE.deleteByIds`(b7af622),
|
||||
* 但「移除整個庫」走軟刪(只標 status),**向量原地不動** ⇒ 殘影就是這樣長出來的:
|
||||
* 搜尋端每次都要靠事後過濾擋它,而它還會頂著高分去影響門檻計算。
|
||||
* ⇒ 標 deprecated 的同時把向量刪掉,讓殘影**在源頭就不存在**。
|
||||
* 不違背 t135「資料保留可還原」:**D1 那列原封不動**,還原後跑
|
||||
* `POST /embed/backfill` 重嵌即可(backfill 已排除 deprecated,所以不會自己跑回來)。
|
||||
*/
|
||||
export async function embeddedIdsByLibrary(db: D1Database, ownerId: string, library: string): Promise<string[]> {
|
||||
const rows = await db
|
||||
.prepare(
|
||||
`SELECT id FROM entries
|
||||
WHERE owner_id = ?
|
||||
AND COALESCE(NULLIF(json_extract(metadata_json, '$.library'), ''), 'general') = ?
|
||||
AND is_embedded = 1`,
|
||||
)
|
||||
.bind(ownerId, library)
|
||||
.all<{ id: string }>();
|
||||
return (rows.results ?? []).map((r) => r.id);
|
||||
}
|
||||
|
||||
/** 把這些 entry 標成「已無向量」(配合 deleteByIds,讓 D1 與 Vectorize 不說兩套話)。 */
|
||||
export async function markUnembedded(db: D1Database, ids: string[]): Promise<void> {
|
||||
if (ids.length === 0) return;
|
||||
const holes = ids.map(() => '?').join(',');
|
||||
await db.prepare(`UPDATE entries SET is_embedded = 0 WHERE id IN (${holes})`).bind(...ids).run();
|
||||
}
|
||||
|
||||
export async function deprecateEntriesByLibrary(db: D1Database, ownerId: string, library: string): Promise<number> {
|
||||
const result = await db
|
||||
.prepare(
|
||||
|
||||
Reference in New Issue
Block a user