fix(portal): 檢修孔 library_count/triplet_count 恆 0 的根因+加統計自我檢查
真因:/portal/data/diagnostics 讀 GET /map,那是 library_map 快取 block,只能 靠 POST /map/recompute 產生;核實過整個 repo 沒有任何呼叫點會打 /map/recompute (library-map SDD 的 ingest 自動重算 M3 從沒接上,status: draft)。⇒ /map 對任何 租戶恆回空 libraries,與實際資料量無關——2026-08-07 leo 實測抓到:3 庫、大量 triplets,診斷檔卻回 0/0。 修法:改走 GET /portal/admin/libraries 已在用、驗證過的即時查詢組合(不依賴任何 快取):listRecordsByTemplate(portal_library) + /entries/libraries(t52 蓋章即 現身)+ /records/triplet-stats(t142 即時聚合 SQL)。 附帶:加 library_scope_check 統計自我檢查(呼應 embedding.self_test 的精神)。 兩個計數都是 0 時,用完全不同的查詢路徑(不分庫/模板,只問這個租戶底下有沒有 任何 entries)交叉驗證,區分「真的是空」與「查詢方式或 owner_id 對不上」 (2026-08-01 t161 前科同型病:手動補的 record owner_id 存成 None,全量查得到、 按 owner_id 過濾的畫面永遠空)。 三個 diagnostics 測試全綠:即時查對出正確 library_count/triplet_count(且不再 外流庫名/內容,只回數字)、真空情境自我探測誠實回空、t161 同型病情境自我探測抓到 「查得到但統計回 0」的矛盾。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
import { Hono } from 'hono';
|
||||
import type { Context } from 'hono';
|
||||
import type { Bindings } from '../types';
|
||||
import { kbdbFetch, run, requirePortalUser, parseLibraries, portalTenant, hasGraphAccess, workflowsVisible, uploadEnabled } from './portal';
|
||||
import { kbdbFetch, run, requirePortalUser, parseLibraries, portalTenant, hasGraphAccess, workflowsVisible, uploadEnabled, listRecordsByTemplate, LIBRARY_TEMPLATE } from './portal';
|
||||
import { graphBase } from './kbdb-proxy';
|
||||
import { executeWebhookGraph } from '../actions/webhook-handlers';
|
||||
|
||||
@@ -642,21 +642,80 @@ portalDataRouter.get('/portal/data/diagnostics', (c) =>
|
||||
notes.push(`embed 健康狀態查詢失敗:${e instanceof Error ? e.message : String(e)}`);
|
||||
}
|
||||
|
||||
// ② 卡片與知識圖譜規模(只取數字,不取 /map 回應裡的 narrative/top_entities 這些內容欄位)。
|
||||
// ② 卡片與知識圖譜規模(只取數字,不取內容欄位)。
|
||||
//
|
||||
// 🔴 2026-08-08 修正(leo 實測抓到:3 庫、大量 triplets,診斷檔卻回 0/0):
|
||||
// 原本讀 GET /map,那是 library_map**快取 block**(kbdb/src/actions/library-map.ts),
|
||||
// 只能靠 POST /map/recompute 產生。核實過:整個 repo 沒有任何呼叫點會打
|
||||
// /map/recompute(`grep -rn "map/recompute" --include=*.ts` 只中 map.ts 自己的路由定義與
|
||||
// mcp 的說明字串)——library-map SDD(system-dev/docs/3-specs/library-map/design.md,
|
||||
// status: draft)的「ingest 尾端自動重算(M3)」從沒接上。⇒ /map 的 libraries 對**任何
|
||||
// 租戶**恆是空陣列,library_count/triplet_count 恆為 0,與實際資料量無關,是全租戶通病,
|
||||
// 不是 leo 這個實例特有。
|
||||
//
|
||||
// 改走 GET /portal/admin/libraries(本檔以外、portal.ts 973 行)驗證過在用的**即時查詢**
|
||||
// 同一套組合,不依賴任何快取:
|
||||
// - listRecordsByTemplate(portal_library):已登記的庫(t159)
|
||||
// - GET /entries/libraries:資料裡實際蓋章出現過的庫,登記與否都算(t52,
|
||||
// 「蓋章即現身」);'general' 是未標庫的系統 fallback 桶,不算使用者眼中的一個庫,
|
||||
// 與 admin/libraries 同慣例排除。
|
||||
// - GET /records/triplet-stats:per-library 即時聚合 SQL(t142,COUNT,非快取)。
|
||||
let library_count = 0;
|
||||
let triplet_count = 0;
|
||||
const ownerParam = new URLSearchParams({ owner_id: tenant }).toString();
|
||||
try {
|
||||
const mapRes = await kbdbFetch(c.env, `/map?${new URLSearchParams({ owner_id: tenant }).toString()}`);
|
||||
const mapBody = (await mapRes.json().catch(() => null)) as
|
||||
| { success?: boolean; libraries?: { triplet_count?: number }[] }
|
||||
const [registeredLibs, autoRes, tripletRes] = await Promise.all([
|
||||
listRecordsByTemplate(c.env, LIBRARY_TEMPLATE).catch(() => []),
|
||||
kbdbFetch(c.env, `/entries/libraries?${ownerParam}`),
|
||||
kbdbFetch(c.env, `/records/triplet-stats?${ownerParam}`),
|
||||
]);
|
||||
const knownLibs = new Set(
|
||||
registeredLibs.map((r) => (r.values.name ?? '').trim()).filter((n): n is string => !!n),
|
||||
);
|
||||
const autoBody = (await autoRes.json().catch(() => null)) as { success?: boolean; libraries?: string[] } | null;
|
||||
for (const name of autoBody?.libraries ?? []) {
|
||||
const n = String(name ?? '').trim();
|
||||
if (n && n !== 'general') knownLibs.add(n);
|
||||
}
|
||||
library_count = knownLibs.size;
|
||||
|
||||
const tripletBody = (await tripletRes.json().catch(() => null)) as
|
||||
| { success?: boolean; stats?: { library: string; triplet_count?: number }[] }
|
||||
| null;
|
||||
const libs = Array.isArray(mapBody?.libraries) ? mapBody!.libraries! : [];
|
||||
library_count = libs.length;
|
||||
triplet_count = libs.reduce((sum, l) => sum + (Number(l.triplet_count) || 0), 0);
|
||||
triplet_count = (tripletBody?.stats ?? []).reduce((sum, s) => sum + (Number(s.triplet_count) || 0), 0);
|
||||
} catch (e) {
|
||||
notes.push(`知識庫規模查詢失敗:${e instanceof Error ? e.message : String(e)}`);
|
||||
}
|
||||
|
||||
// ②.5 統計自我檢查(呼應下面 embedding.self_test 的精神——leo 直接指令:「不要讓『查不到』
|
||||
// 和『沒有』長得一樣」)。library_count/triplet_count 兩者都是 0 時,才另外花一次查詢,
|
||||
// 用完全不同的路徑(不分庫、不分模板,只問「這個 owner_id 底下到底有沒有任何 entries」)
|
||||
// 做交叉驗證——如果探測到有資料,代表問題出在查詢方式或 owner_id 對不上(2026-08-01
|
||||
// t161 前科:手動補的 record owner_id 存成 None,kbdb_query 全量查得到、按 owner_id 過濾
|
||||
// 的畫面永遠空,比真的沒資料更難查);如果探測也是空,才比較像真的是空庫。
|
||||
let library_scope_check: Record<string, unknown> = { ran: false };
|
||||
if (library_count === 0 && triplet_count === 0) {
|
||||
try {
|
||||
const probeRes = await kbdbFetch(c.env, `/entries?${new URLSearchParams({ owner_id: tenant, limit: '1' }).toString()}`);
|
||||
const probeBody = (await probeRes.json().catch(() => null)) as { total?: number } | null;
|
||||
const total = probeBody?.total ?? 0;
|
||||
library_scope_check = {
|
||||
ran: true,
|
||||
any_entries_found: total > 0,
|
||||
note:
|
||||
total > 0
|
||||
? `這個租戶底下查得到其他資料(entries 共 ${total} 筆),但庫/三元組統計仍回 0——像是查詢方式或租戶對不上,不像真的沒資料,需要人再查一次`
|
||||
: '這個租戶底下完全查不到任何資料——比較像是真的還沒有資料,不是查詢方式錯了',
|
||||
};
|
||||
} catch (e) {
|
||||
library_scope_check = {
|
||||
ran: true,
|
||||
any_entries_found: null,
|
||||
note: `自我探測查詢本身失敗:${e instanceof Error ? e.message : String(e)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ③ 最近一次萃取(daemon → /portal/daemon/extract)成功與否:目前沒有雲端側的失敗歷史
|
||||
// 記錄可讀(該端點是同步請求/回應,失敗只回給呼叫當下的 daemon,雲端不落地保存)——
|
||||
// 誠實列出這個缺口,不假裝有數字(mindset §7 禁假綠)。
|
||||
@@ -668,6 +727,7 @@ portalDataRouter.get('/portal/data/diagnostics', (c) =>
|
||||
bundle_version: c.env.ARCRUN_BUNDLE_VERSION ?? null,
|
||||
library_count,
|
||||
triplet_count,
|
||||
library_scope_check,
|
||||
embedding,
|
||||
notes,
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ const LOCK_TTL_SECONDS = 15 * 60; // 鎖 15 分鐘(KV TTL 自然過期)
|
||||
const DEFAULT_SESSION_TTL = 604800; // 7 天(design §4.3,比 console 30 天緊)
|
||||
|
||||
const USER_TEMPLATE = 'portal_user';
|
||||
const LIBRARY_TEMPLATE = 'portal_library';
|
||||
export const LIBRARY_TEMPLATE = 'portal_library';
|
||||
|
||||
// ── 基礎 helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user