@tencentdb-agent-memory/memory-tencentdb
Version:
Four-layer local memory system plugin for OpenClaw — auto-captures, structures, and profiles conversational knowledge using local LLM + SQLite vector search (L0→L1→L2→L3 pipeline)
174 lines (152 loc) • 5.4 kB
text/typescript
/**
* Langfuse 过滤型 SpanProcessor(门面层)。
*
* 只转发 LLM 相关的 span(ai.* / gen_ai.* 前缀)到 Langfuse OTLP 端点,
* 其他工程调用 span 被丢弃,避免流量过大。
*
* 设计原则:
* - 不影响现有 span 生命周期
* - exporter 失败时静默忽略
* - 配置缺失时 graceful degradation
*
* 公开 API 签名保持不变,调用方无需修改。
* 具体实现由 ILLMTraceBackend 提供。
*/
import { getObservabilityBackend } from "./factory.js";
import type { ISpanProcessor } from "./types.js";
// ============================
// 配置类型(保持向后兼容导出)
// ============================
export interface LangfuseConfigEnabled {
enabled: true;
host: string;
publicKey: string;
secretKey: string;
}
export interface LangfuseConfigDisabled {
enabled: false;
}
export type LangfuseConfig = LangfuseConfigEnabled | LangfuseConfigDisabled;
// ============================
// 配置解析(保持向后兼容导出)
// ============================
/**
* 从环境变量解析 Langfuse 配置。
*
* 环境变量:
* - LANGFUSE_ENABLED : "true" 启用(默认 "false")
* - LANGFUSE_HOST : Langfuse 实例地址(如 http://langfuse.example.local:3000)
* - LANGFUSE_PUBLIC_KEY : 公钥
* - LANGFUSE_SECRET_KEY : 私钥
*
* 任何必要配置缺失时返回 { enabled: false }。
*/
export function parseLangfuseConfig(): LangfuseConfig {
const enabled = process.env.LANGFUSE_ENABLED === "true";
if (!enabled) {
return { enabled: false };
}
const host = process.env.LANGFUSE_HOST;
const publicKey = process.env.LANGFUSE_PUBLIC_KEY;
const secretKey = process.env.LANGFUSE_SECRET_KEY;
// 任何必要配置缺失时 graceful degradation
if (!host || !publicKey || !secretKey) {
return { enabled: false };
}
return { enabled: true, host, publicKey, secretKey };
}
// ============================
// Span 过滤逻辑(保持向后兼容导出)
// ============================
/**
* 判断 span 是否为 LLM 相关。
*
* 放行规则(Vercel AI SDK 的 experimental_telemetry 产生的 span):
* - `ai.*` : ai.generateText, ai.streamText, ai.toolCall, ai.generateObject 等
* - `gen_ai.*` : gen_ai.chat, gen_ai.embeddings 等(OpenTelemetry GenAI 语义约定)
*
* 其他所有 span(gateway.*, core.*, queue.*, http.* 等)被过滤。
*/
export function isLLMRelatedSpan(spanName: string): boolean {
if (!spanName) return false;
return spanName.startsWith("ai.") || spanName.startsWith("gen_ai.");
}
// ============================
// LangfuseFilteringProcessor(门面层)
// ============================
/**
* 创建 Langfuse SpanProcessor 实例。
* 通过 ILLMTraceBackend 接口获取实际的 processor。
*
* @returns SpanProcessor 实例,或 null(如果 Langfuse 未启用)
*/
export function createLangfuseSpanProcessor(): ISpanProcessor | null {
try {
return getObservabilityBackend().llmTrace.createSpanProcessor();
} catch {
return null;
}
}
/**
* 兼容层:LangfuseFilteringProcessor 类。
* 保持向后兼容,内部委托给 ILLMTraceBackend。
*
* 当 ILLMTraceBackend 返回有效 processor 时,委托给它处理;
* 否则回退到使用传入的 exporter 做过滤 + export(直接转发 LLM span)。
*/
export class LangfuseFilteringProcessor implements ISpanProcessor {
/** 来自 ILLMTraceBackend 的 processor(仅在无 exporter 时使用) */
private _processor: ISpanProcessor | null;
/** 由 otel-sdk-init.ts 传入的真正 exporter(优先级最高) */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _exporter: any;
constructor(exporter?: unknown) {
if (exporter) {
// 当传入了 exporter 时,优先使用 exporter 做实际 export。
// otel-sdk-init.ts 已经创建了指向 Langfuse OTLP 端点的 HttpTraceExporter,
// 这里直接使用它,不再委托给 ILLMTraceBackend(其 processor 可能是空壳)。
this._exporter = exporter;
this._processor = null;
} else {
// 无 exporter 时,尝试从 ILLMTraceBackend 获取 processor
this._processor = getObservabilityBackend().llmTrace.createSpanProcessor();
this._exporter = null;
}
}
onStart(span: unknown, parentContext: unknown): void {
this._processor?.onStart(span, parentContext);
}
onEnd(span: unknown): void {
try {
const s = span as { name?: string };
// 统一过滤:只转发 LLM 相关 span
if (!s.name || !isLLMRelatedSpan(s.name)) {
return;
}
if (this._exporter) {
// 使用真正的 exporter 发送到 Langfuse OTLP 端点
this._exporter.export([span], () => {});
} else if (this._processor) {
// 委托给 ILLMTraceBackend 的 processor
this._processor.onEnd(span);
}
} catch {
// 静默失败,不影响其他 SpanProcessor
}
}
async forceFlush(): Promise<void> {
if (this._exporter?.forceFlush) {
await this._exporter.forceFlush();
} else if (this._processor) {
await this._processor.forceFlush();
}
}
async shutdown(): Promise<void> {
if (this._exporter?.shutdown) {
await this._exporter.shutdown();
}
if (this._processor) {
await this._processor.shutdown();
}
}
}