第 22 课: Tracers 与 Event Stream -- 生产级可观测

3 阅读5分钟

课程目标

精读 LangChain.js 的 Tracer 系统:BaseTracer 的 Run 管理、ConsoleCallbackHandler 的调试输出、LangChainTracer 的 LangSmith 追踪、EventStreamCallbackHandler 的 SSE 事件流。


22.1 Tracer 的定位

第 21 课介绍了 BaseCallbackHandler 的事件接口。Tracer 是一类特殊的 callback handler,它们不仅监听事件,还会维护一棵 Run 树来记录完整的执行轨迹。

BaseCallbackHandler (事件接口)
  └── BaseTracer (添加 Run 管理)
        ├── ConsoleCallbackHandler  (控制台输出)
        ├── LangChainTracer         (LangSmith 上报)
        ├── EventStreamCallbackHandler (SSE 事件流)
        ├── RunCollectorCallbackHandler (收集 Run)
        └── RootListenersTracer     (顶层监听)

22.2 BaseTracer -- Run 树管理

源码位置: libs/langchain-core/src/tracers/base.ts

22.2.1 Run 数据结构

export interface Run extends BaseRun {
  id: string;
  start_time: number;
  end_time?: number;
  execution_order: number;
  child_runs: this[];
  child_execution_order: number;
  events: Array<{
    name: string;
    time: string;
    kwargs?: Record<string, unknown>;
  }>;
  trace_id?: string;
  dotted_order?: string;
}

关键字段

  • child_runs:子 Run 列表,形成树状结构
  • trace_id:整棵调用树的唯一标识
  • dotted_order:LangSmith 的排序标识,用于重建执行顺序
  • events:时间线事件列表

22.2.2 Run 树的构建

export abstract class BaseTracer extends BaseCallbackHandler {
  protected runMap: Map<string, Run> = new Map();  // runId -> Run
  protected runTreeMap: Map<string, RunTree> = new Map();

  // 同步创建 Run(防止异步竞态)
  _createRunForLLMStart(llm, prompts, runId, parentRunId, ...) {
    // 创建 Run 对象并加入 runMap
    // 如果有 parentRunId,将此 Run 加入父 Run 的 child_runs
  }

  // 子类必须实现:持久化 Run
  protected abstract persistRun(run: Run): Promise<void>;
}

为什么需要同步创建?CallbackManager.handleLLMStart 中,handler 的执行可能是后台异步的。如果 Run 的创建也是异步的,后续到达的 handleLLMNewToken 可能找不到对应的 Run。因此 _createRunForLLMStart 必须同步执行。


22.3 ConsoleCallbackHandler -- 开发调试

源码位置: libs/langchain-core/src/tracers/console.ts

export class ConsoleCallbackHandler extends BaseTracer {
  name = "console_callback_handler" as const;

  protected persistRun(_run: Run) {
    return Promise.resolve();  // 不持久化,只打印
  }

  getParents(run: Run) {
    const parents: Run[] = [];
    let currentRun = run;
    while (currentRun.parent_run_id) {
      const parent = this.runMap.get(currentRun.parent_run_id);
      if (parent) {
        parents.push(parent);
        currentRun = parent;
      } else {
        break;
      }
    }
    return parents;
  }
}

ConsoleCallbackHandler 会根据 Run 的层级缩进输出,用 ANSI 颜色区分不同类型:

[chain/start] [1:chain:RunnableSequence] Entering Chain run...
  [llm/start] [1:chain:RunnableSequence > 2:llm:ChatOpenAI] Entering LLM run...
  [llm/end]   [1:chain:RunnableSequence > 2:llm:ChatOpenAI] [2.3s] Exiting LLM run...
[chain/end]   [1:chain:RunnableSequence] [3.1s] Exiting Chain run...

启用方式

// 方式 1:环境变量
process.env.LANGCHAIN_VERBOSE = "true";

// 方式 2:显式传入
import { ConsoleCallbackHandler } from "@langchain/core/tracers/console";
await chain.invoke(input, {
  callbacks: [new ConsoleCallbackHandler()],
});

22.4 LangChainTracer -- LangSmith 追踪

源码位置: libs/langchain-core/src/tracers/tracer_langchain.ts

22.4.1 核心实现

export class LangChainTracer extends BaseTracer implements LangChainTracerFields {
  name = "langchain_tracer";

  projectName?: string;
  exampleId?: string;
  client: LangSmithTracingClientInterface;

  constructor(fields: LangChainTracerFields = {}) {
    super(fields);
    this.projectName = fields.projectName ?? getDefaultProjectName();
    this.client = fields.client ?? getDefaultLangChainClientSingleton();
  }

  protected async persistRun(run: Run): Promise<void> {
    // 将 Run 数据发送到 LangSmith API
  }
}

22.4.2 启用方式

# 环境变量启用
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=ls-xxx
export LANGCHAIN_PROJECT=my-project

设置后,CallbackManager._configureSync 会自动注入 LangChainTracer,无需修改代码。

22.4.3 Usage Metadata 提取

function _getUsageMetadataFromGenerations(
  generations: ChatGeneration[][]
): UsageMetadata | undefined {
  let output: UsageMetadata | undefined = undefined;
  for (const generationBatch of generations) {
    for (const generation of generationBatch) {
      if (
        AIMessage.isInstance(generation.message) &&
        generation.message.usage_metadata !== undefined
      ) {
        output = mergeUsageMetadata(output, generation.message.usage_metadata);
      }
    }
  }
  return output;
}

LangChainTracer 会自动从生成结果中提取 token 使用信息,随 Run 数据一起上报。


22.5 EventStreamCallbackHandler -- streamEvents 的引擎

源码位置: libs/langchain-core/src/tracers/event_stream.ts

22.5.1 StreamEvent 数据结构

export type StreamEvent = {
  event: string;        // 如 "on_llm_start", "on_chat_model_stream", "on_chain_end"
  name: string;         // Runnable 的名称
  run_id: string;       // 执行 ID
  tags?: string[];
  metadata: Record<string, any>;
  data: StreamEventData;  // { input?, output?, chunk?, error? }
};

22.5.2 事件名称规范

on_[runnable_type]_(start|stream|end)
事件名含义data 内容
on_llm_startLLM 开始{ input }
on_llm_streamLLM 流式 chunk{ chunk }
on_llm_endLLM 结束{ output }
on_chat_model_startChatModel 开始{ input }
on_chat_model_streamChatModel 流式{ chunk }
on_chat_model_endChatModel 结束{ output }
on_chain_startChain 开始{ input }
on_chain_streamChain 流式{ chunk }
on_chain_endChain 结束{ output }
on_tool_startTool 开始{ input }
on_tool_endTool 结束{ output }
on_prompt_startPrompt 开始{ input }
on_prompt_endPrompt 结束{ output }

22.5.3 核心实现

export class EventStreamCallbackHandler
  extends BaseTracer
  implements CallbackHandlerPrefersStreaming
{
  lc_prefer_streaming = true;  // 告诉框架优先使用流式路径

  protected transformStream: TransformStream;
  public writer: WritableStreamDefaultWriter;
  public receiveStream: IterableReadableStream<StreamEvent>;

  private runInfoMap: Map<string, RunInfo> = new Map();

  constructor(fields?: EventStreamCallbackHandlerInput) {
    super({ _awaitHandler: true, ...fields });
    // 创建 TransformStream 管道
    this.transformStream = new TransformStream();
    this.writer = this.transformStream.writable.getWriter();
    this.receiveStream = IterableReadableStream.fromReadableStream(
      this.transformStream.readable
    );
  }

  [Symbol.asyncIterator]() {
    return this.receiveStream;
  }
}

工作原理

  1. 创建一个 TransformStream 管道
  2. 当事件发生时,通过 writer.write()StreamEvent 写入管道
  3. receiveStream 作为可迭代流暴露给外部消费者
  4. streamEvents() API 内部使用这个 handler

22.5.4 事件过滤

constructor(fields?: EventStreamCallbackHandlerInput) {
  this.includeNames = fields?.includeNames;
  this.includeTypes = fields?.includeTypes;
  this.includeTags = fields?.includeTags;
  this.excludeNames = fields?.excludeNames;
  this.excludeTypes = fields?.excludeTypes;
  this.excludeTags = fields?.excludeTags;
}

_includeRun(run: RunInfo): boolean {
  // 基于 name / type / tags 的包含和排除逻辑
}

22.6 RunCollectorCallbackHandler

源码位置: libs/langchain-core/src/tracers/run_collector.ts

export class RunCollectorCallbackHandler extends BaseTracer {
  name = "run_collector";
  tracedRuns: Run[] = [];

  protected async persistRun(run: BaseRun): Promise<void> {
    const run_ = { ...run } as Run;
    run_.reference_example_id = this.exampleId;
    this.tracedRuns.push(run_);
  }
}

用途:收集所有 Run 对象,用于测试断言或后续分析。

const collector = new RunCollectorCallbackHandler();
await chain.invoke(input, { callbacks: [collector] });

// 检查执行结果
console.log(collector.tracedRuns.length);  // Run 数量
console.log(collector.tracedRuns[0].outputs);  // 第一个 Run 的输出

22.7 RootListenersTracer

源码位置: libs/langchain-core/src/tracers/root_listener.ts

export class RootListenersTracer extends BaseTracer {
  name = "RootListenersTracer";
  rootId?: string;

  constructor({ config, onStart, onEnd, onError }) {
    super({ _awaitHandler: true });
    this.argOnStart = onStart;
    this.argOnEnd = onEnd;
    this.argOnError = onError;
  }

  async onRunCreate(run: Run) {
    if (this.rootId) return;  // 只关注根 Run
    this.rootId = run.id;
    await this.argOnStart?.(run, this.config);
  }

  async onRunUpdate(run: Run) {
    if (run.id !== this.rootId) return;  // 只关注根 Run
    if (run.error) {
      await this.argOnError?.(run, this.config);
    } else {
      await this.argOnEnd?.(run, this.config);
    }
  }
}

用途:只监听顶层 Run 的开始/结束/错误,忽略所有子 Run。适合做链级别的监控。


22.8 streamEvents 使用实战

import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { fakeModel } from "@langchain/core/testing";
import { AIMessage } from "@langchain/core/messages";

const model = fakeModel().respond(new AIMessage("LangChain.js 是一个强大的框架"));
const prompt = ChatPromptTemplate.fromMessages([
  ["system", "你是一个技术专家"],
  ["human", "{question}"],
]);
const chain = prompt.pipe(model).pipe(new StringOutputParser());

// 使用 streamEvents 监听完整执行过程
const events = chain.streamEvents(
  { question: "什么是 LangChain?" },
  { version: "v2" }
);

for await (const event of events) {
  if (event.event === "on_chat_model_stream") {
    // 逐 token 输出
    process.stdout.write(event.data.chunk?.content ?? "");
  } else if (event.event === "on_chain_start") {
    console.log(`\n链开始: ${event.name}`);
  } else if (event.event === "on_chain_end") {
    console.log(`\n链结束: ${event.name}`);
  }
}

22.8.1 事件过滤

// 只监听特定类型
const events = chain.streamEvents(input, {
  version: "v2",
  includeTypes: ["chat_model"],  // 只看 ChatModel 事件
});

// 只监听带特定 tag 的 Runnable
const events = chain.streamEvents(input, {
  version: "v2",
  includeTags: ["important"],
});

22.9 Tracer 继承体系图

BaseCallbackHandler (事件接口,第 21 课)
  └── BaseTracer (Run 树管理)
        
        ├── ConsoleCallbackHandler
             persistRun: 空操作
             特点: ANSI 彩色控制台输出
        
        ├── LangChainTracer
             persistRun: 发送到 LangSmith API
             特点: 生产级追踪
        
        ├── EventStreamCallbackHandler
             persistRun: 空操作
             特点: TransformStream 管道  streamEvents
        
        ├── RunCollectorCallbackHandler
             persistRun: 加入 tracedRuns 数组
             特点: 测试和分析
        
        └── RootListenersTracer
              persistRun: 空操作
              特点: 只监听根 Run

22.10 源码精读路线

优先级文件关注点
P0tracers/base.ts:90-120BaseTracer 核心、runMapisBaseTracer
P0tracers/event_stream.ts:153-270EventStreamCallbackHandler、TransformStream 管道、事件过滤
P1tracers/console.tsConsoleCallbackHandlergetParents 层级遍历
P1tracers/tracer_langchain.ts:89-100LangChainTracer 核心、persistRun
P2tracers/run_collector.tsRunCollectorCallbackHandler
P2tracers/root_listener.tsRootListenersTracer
P2tracers/log_stream.tsLogStreamCallbackHandler(streamLog 的引擎)

本课收获总结

级别你应该掌握的
🟢 基础学会用 ConsoleCallbackHandler 打印执行过程;用 streamEvents 监听事件流
🔵 中阶理解 streamEvents("v2") 的事件名称规范和 data 结构
🟡 高阶掌握 LangChainTracer 与 LangSmith 的集成;理解 EventStreamCallbackHandler 的 TransformStream 实现
🟠 资深分析 BaseTracer 的 Run 树管理和同步创建策略;理解 consumeCallback 的上下文隔离
🔴 架构设计分布式追踪方案:parent-child Run 关系、trace_id 贯穿全链

下一课预告

第 23 课学习 Graph 可视化和调试技巧——用 getGraph() + drawMermaid() 可视化链的拓扑,用 FakeModel 系列编写无 API 依赖的测试。