UNPKG

@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)

165 lines (143 loc) 5.88 kB
/** * TracedTaskExecutor — 非侵入式 Trace 装饰器(门面层) * * 包装原始 TaskExecutor,为每个 L1/L2/L3 任务执行创建 OTel Span, * 并从 TaskPayload.data 中恢复跨异步边界的 Trace Context。 * * 使用方式(在 server.ts 中): * const rawExecutor = this.buildTaskExecutor(); * const tracedExecutor = new TracedTaskExecutor(rawExecutor); * this.pipelineWorker = new PipelineWorker(backend, tracedExecutor, ...); * * 不修改任何业务代码,纯可观测性组件。 * 公开 API 签名保持不变,调用方无需修改。 */ import type { TaskPayload } from "../state/types.js"; import type { TaskExecutor } from "../../services/pipeline-worker.js"; import { getObservabilityBackend } from "./factory.js"; import { obsLogger } from "./obs-logger.js"; /** 任务类型 → Span Name 映射 */ const TASK_SPAN_NAMES: Record<string, string> = { L1: "core.l1.extraction", L2: "core.l2.extraction", L3: "core.l3.generation", flush: "core.flush", }; /** * TracedTaskExecutor — 装饰器模式包装 TaskExecutor。 * * 对每个 executeL1/L2/L3 调用: * 1. 从 task.data 反序列化恢复上游 Trace Context * 2. 创建 CONSUMER 类型 Span(follow-from link) * 3. 在 Span context 中执行原始 executor * 4. 记录 instance_id、session_id、task_type 等业务属性 * 5. 错误时设置 Span Error 状态 */ export class TracedTaskExecutor implements TaskExecutor { private readonly inner: TaskExecutor; constructor(inner: TaskExecutor) { this.inner = inner; } async executeL1(task: TaskPayload): Promise<void> { return this.executeWithTrace("L1", task, () => this.inner.executeL1(task)); } async executeL2(task: TaskPayload): Promise<void> { return this.executeWithTrace("L2", task, () => this.inner.executeL2(task)); } async executeL3(task: TaskPayload): Promise<void> { return this.executeWithTrace("L3", task, () => this.inner.executeL3(task)); } async executeFlush?(task: TaskPayload): Promise<void> { if (this.inner.executeFlush) { return this.executeWithTrace("flush", task, () => this.inner.executeFlush!(task)); } return this.executeL1(task); } async executeOffloadL1?(task: TaskPayload, signal?: AbortSignal): Promise<void> { if (this.inner.executeOffloadL1) { return this.executeWithTrace("offload-l1", task, () => this.inner.executeOffloadL1!(task, signal)); } } async executeOffloadL15?(task: TaskPayload, signal?: AbortSignal): Promise<void> { if (this.inner.executeOffloadL15) { return this.executeWithTrace("offload-l15", task, () => this.inner.executeOffloadL15!(task, signal)); } } async executeOffloadL2?(task: TaskPayload, signal?: AbortSignal): Promise<void> { if (this.inner.executeOffloadL2) { return this.executeWithTrace("offload-l2", task, () => this.inner.executeOffloadL2!(task, signal)); } } /** * 核心方法:在 Trace Context 中执行任务。 * * 优先使用 traceMiddleware.withSpan() 让 fn 在 active span context 中执行, * 使得 fn 内部调用 metricProducer.send() 时能通过 serializeTraceContext() * 自动获取当前 span 的 traceId。 * * 降级策略:若 withSpan 不可用,回退到 trace.start()/end() 模式。 */ private async executeWithTrace( taskType: string, task: TaskPayload, fn: () => Promise<void>, ): Promise<void> { const backend = getObservabilityBackend(); const spanName = TASK_SPAN_NAMES[taskType] ?? `core.task.${taskType.toLowerCase()}`; // 提取业务属性 const instanceId = task.instanceId ?? (typeof task.data?.instanceId === "string" ? task.data.instanceId : "unknown"); const sessionId = task.sessionId ?? "unknown"; const attrs: Record<string, string | number | boolean> = { "instance_id": instanceId, "session_id": sessionId, "task_type": taskType, "task_id": task.id, "event_name": spanName, "messaging.system": "redis_stream", "messaging.operation": "process", }; // 优先路径:使用 withSpan 让 fn 在 active span context 中执行 if (typeof backend.traceMiddleware?.withSpan === "function") { return backend.traceMiddleware.withSpan(spanName, attrs, async (span) => { try { await fn(); span.setStatus({ code: 0 /* SpanStatusCode.OK */ }); } catch (err) { const errMsg = err instanceof Error ? err.message : String(err); span.setStatus({ code: 2 /* SpanStatusCode.ERROR */, message: errMsg }); span.recordException(err instanceof Error ? err : new Error(errMsg)); obsLogger.error(`core.${taskType.toLowerCase()}.failed`, { instance_id: instanceId, session_id: sessionId, task_type: taskType, task_id: task.id, error: errMsg, }, err instanceof Error ? err : undefined); throw err; } }); } // 降级路径:withSpan 不可用时,回退到 start/end(不激活 context) const span = backend.trace.start(spanName, 4 /* SpanKind.CONSUMER */); span.setAttributes(attrs); try { await fn(); span.setStatus({ code: 0 /* SpanStatusCode.UNSET → OK */ }); } catch (err) { const errMsg = err instanceof Error ? err.message : String(err); span.setStatus({ code: 2 /* SpanStatusCode.ERROR */, message: errMsg }); span.recordException(err instanceof Error ? err : new Error(errMsg)); obsLogger.error(`core.${taskType.toLowerCase()}.failed`, { instance_id: instanceId, session_id: sessionId, task_type: taskType, task_id: task.id, error: errMsg, }, err instanceof Error ? err : undefined); throw err; } finally { span.end(); } } }