Iostream-Li's picture
Add files using upload-large-folder tool
ff78003 verified
/**
* Telemetry — 把每次 external-knowledge 调用写一行到
* `capability_external_knowledge_calls`。
*
* 设计跟 quarantine/index.ts:recordQuarantineHit 一致:
* - 测试 hook 优先(in-memory recorder);
* - 生产路径走 DB,DB 失败退化为 log,不阻塞调用方。
* - 异步 fire-and-forget,不在调用方关键路径上。
*/
import { createHash } from "node:crypto";
import type { Logger } from "pino";
import { newId } from "../../ids.ts";
import type { KnowledgeKind } from "./types.ts";
export interface TelemetryRecord {
adapter: KnowledgeKind;
capabilityId: string | null;
latencyMs: number;
hitCount: number;
cacheHit: "hit" | "miss" | null;
error: string | null;
paramsFingerprint: string | null;
}
type TestHook = (rec: TelemetryRecord) => void;
let _testHook: TestHook | null = null;
/** 测试用:注入 in-memory recorder,跳过 DB / log。 */
export function _setTelemetryTestHook(hook: TestHook | null): void {
_testHook = hook;
}
export function fingerprintParams(params: Record<string, unknown>): string {
// 稳定序列化 — Object.keys 按字母排序,确保同输入永远同 fingerprint。
const sortedKeys = Object.keys(params).sort();
const stable = sortedKeys.reduce<Record<string, unknown>>((acc, k) => {
acc[k] = params[k];
return acc;
}, {});
return createHash("sha1").update(JSON.stringify(stable)).digest("hex");
}
export async function recordCall(rec: TelemetryRecord): Promise<void> {
if (_testHook) {
try {
_testHook(rec);
} catch {
/* test hook 抛错也不能影响产线 */
}
return;
}
// 动态 import 让单测可以不引 DB / pino。
let db: typeof import("@workspace/db").db | undefined;
let capabilityExternalKnowledgeCalls:
| typeof import("@workspace/db").capabilityExternalKnowledgeCalls
| undefined;
let logger: Logger | undefined;
try {
const dbMod = await import("@workspace/db");
db = dbMod.db;
capabilityExternalKnowledgeCalls = dbMod.capabilityExternalKnowledgeCalls;
} catch {
/* 测试环境无 DATABASE_URL 时 import 会抛 — 退化到纯 log */
}
try {
const loggerMod = await import("../../logger.ts");
logger = loggerMod.logger;
} catch {
/* 无 logger 也继续 */
}
if (db && capabilityExternalKnowledgeCalls) {
try {
await db.insert(capabilityExternalKnowledgeCalls).values({
id: newId("cek"),
adapter: rec.adapter,
capabilityId: rec.capabilityId,
latencyMs: rec.latencyMs,
hitCount: rec.hitCount,
cacheHit: rec.cacheHit,
error: rec.error,
paramsFingerprint: rec.paramsFingerprint,
});
return;
} catch (err) {
logger?.debug?.(
{ err, rec },
"capability_external_knowledge_calls insert failed (non-blocking)",
);
}
}
// 兜底:落 log。永远不阻塞 caller。
logger?.info?.({ rec }, "[external-knowledge] call (no-db fallback log)");
}