课程目标
精读 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_start | LLM 开始 | { input } |
on_llm_stream | LLM 流式 chunk | { chunk } |
on_llm_end | LLM 结束 | { output } |
on_chat_model_start | ChatModel 开始 | { input } |
on_chat_model_stream | ChatModel 流式 | { chunk } |
on_chat_model_end | ChatModel 结束 | { output } |
on_chain_start | Chain 开始 | { input } |
on_chain_stream | Chain 流式 | { chunk } |
on_chain_end | Chain 结束 | { output } |
on_tool_start | Tool 开始 | { input } |
on_tool_end | Tool 结束 | { output } |
on_prompt_start | Prompt 开始 | { input } |
on_prompt_end | Prompt 结束 | { 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;
}
}
工作原理:
- 创建一个
TransformStream管道 - 当事件发生时,通过
writer.write()将StreamEvent写入管道 receiveStream作为可迭代流暴露给外部消费者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 源码精读路线
| 优先级 | 文件 | 关注点 |
|---|---|---|
| P0 | tracers/base.ts:90-120 | BaseTracer 核心、runMap、isBaseTracer |
| P0 | tracers/event_stream.ts:153-270 | EventStreamCallbackHandler、TransformStream 管道、事件过滤 |
| P1 | tracers/console.ts | ConsoleCallbackHandler、getParents 层级遍历 |
| P1 | tracers/tracer_langchain.ts:89-100 | LangChainTracer 核心、persistRun |
| P2 | tracers/run_collector.ts | RunCollectorCallbackHandler |
| P2 | tracers/root_listener.ts | RootListenersTracer |
| P2 | tracers/log_stream.ts | LogStreamCallbackHandler(streamLog 的引擎) |
本课收获总结
| 级别 | 你应该掌握的 |
|---|---|
| 🟢 基础 | 学会用 ConsoleCallbackHandler 打印执行过程;用 streamEvents 监听事件流 |
| 🔵 中阶 | 理解 streamEvents("v2") 的事件名称规范和 data 结构 |
| 🟡 高阶 | 掌握 LangChainTracer 与 LangSmith 的集成;理解 EventStreamCallbackHandler 的 TransformStream 实现 |
| 🟠 资深 | 分析 BaseTracer 的 Run 树管理和同步创建策略;理解 consumeCallback 的上下文隔离 |
| 🔴 架构 | 设计分布式追踪方案:parent-child Run 关系、trace_id 贯穿全链 |
下一课预告
第 23 课学习 Graph 可视化和调试技巧——用 getGraph() + drawMermaid() 可视化链的拓扑,用 FakeModel 系列编写无 API 依赖的测试。