LangChain Callbacks 与事件驱动架构
LangChain Callbacks 与事件驱动架构
一、概念速查
核心概念
| 概念 | 定义 |
|---|---|
| Callback | LangChain 执行过程中触发的生命周期事件的回调函数 |
| BaseCallbackHandler | 所有回调处理器的抽象基类,定义各事件的接口 |
| CallbackManager | 事件分发中心,管理一组 CallbackHandler 的注册与调用 |
| 事件驱动架构 | 执行流程中异步/同步地产生事件,由注册的处理器消费的模式 |
BaseCallbackHandler 核心接口
| 事件方法 | 触发时机 | 典型场景 |
|---|---|---|
on_llm_start |
LLM 调用开始时 | 记录请求 prompt、计费 |
on_llm_end |
LLM 调用完成时 | 记录响应 token 数、延迟 |
on_llm_error |
LLM 调用抛出异常时 | 告警、重试逻辑 |
on_chat_model_start |
ChatModel 调用开始时 | 同上,仅 ChatModel 路径 |
on_chain_start |
RunnableChain 开始时 | 追踪链路入口 |
on_chain_end |
RunnableChain 完成时 | 记录链路输出 |
on_chain_error |
RunnableChain 异常时 | 链路级告警 |
on_tool_start |
Tool 调用开始时 | 审计工具参数 |
on_tool_end |
Tool 调用完成时 | 记录工具输出 |
on_tool_error |
Tool 抛出异常时 | 工具级告警 |
on_retry |
Retry 机制触发重试时 | 监控重试频率 |
on_text |
输出文本时 | 日志记录 |
on_agent_action |
Agent 决定执行一个 Action 时 | Agent 步骤追踪 |
on_agent_finish |
Agent 完成全部步骤时 | Agent 最终输出 |
内置处理器
| 处理器 | 作用 | 依赖 |
|---|---|---|
StdOutCallbackHandler |
将事件输出到 stdout | langchain-core |
FileCallbackHandler |
将事件写入文件 | langchain-core |
ConsoleCallbackHandler |
LangChain CLI 控制台输出 | langchain-cli |
二、底层原理
2.1 事件分发机制
CallbackManager 内部维护一个 handlers: list[BaseCallbackHandler] 列表。当一个事件被触发时(例如 LLM 开始调用),CallbackManager.on_llm_start() 会遍历所有注册的 handler,依次调用其 on_llm_start() 方法:
每个 Runnable(Chain、LLM、Tool、Retriever 等)都持有自己的 CallbackManager 实例。当外层 Runnable 调用内层 Runnable 时,callback 继承链会自动传递——子 Runnable 会继承父 Runnable 的 callback handlers,同时可以追加自己的 handler。
2.2 继承链机制
LangChain 通过 RunnableConfig 中的 callbacks 字段传递 handler。当一个 chain 包含一个 LLM 调用时:
# langchain>=0.3.0
from langchain_core.runnables import RunnableConfig
config = RunnableConfig(callbacks=[my_handler])
chain.invoke({"input": "hello"}, config=config)
# 内部 LLM 自动继承 config 中的 callbacks
继承链的实现路径:
Runnable的invoke()从RunnableConfig提取callbacks- 如果
RunnableConfig没有显式传callbacks,从 parent run 继承 CallbackManager新建时合并 parent 的 handlers 与当前配置的 handlers- 事件冒泡:内层事件先通知本地 handler,再通知 parent handler
2.3 同步与异步双通道
BaseCallbackHandler 同时定义同步和异步接口:
# langchain>=0.3.0
from langchain_core.callbacks import BaseCallbackHandler
class MyHandler(BaseCallbackHandler):
def on_llm_start(self, serialized, prompts, **kwargs):
# 同步路径
pass
async def on_llm_start(self, serialized, prompts, **kwargs):
# 异步路径
pass
CallbackManager 会在同步执行链(invoke)中只调用同步方法,在异步执行链(ainvoke)中只调用异步方法。如果处理器只实现了同步方法,异步路径会自动降级为同步调用。
2.4 构造函数传参 vs 运行时传参
传参方式有两种:
# langchain>=0.3.0
from langchain_openai import ChatOpenAI
from langchain_core.callbacks import StdOutCallbackHandler
# 方式一:构造时传入(生命周期绑定)
model = ChatOpenAI(
model="gpt-4o-mini",
callbacks=[StdOutCallbackHandler()],
)
# 方式二:运行时传入(每次调用独立)
model.invoke("你好", config={"callbacks": [StdOutCallbackHandler()]})
方式一适用于固定 logger,方式二适用于请求级别的追踪 ID 注入。
三、架构设计原则
3.1 处理器应当无状态或幂等
Callback handler 可能在多线程环境下被共享。如果 handler 持有状态(如累加 token 计数器),需要确保线程安全或使用 run_inline 隔离。
推荐模式:每个请求创建一个新的 handler 实例,在 on_chain_start 中注入 run_id:
# langchain>=0.3.0
import json
from uuid import UUID
from pathlib import Path
from langchain_core.callbacks import BaseCallbackHandler
class AuditHandler(BaseCallbackHandler):
"""审计日志处理器:记录每次 LLM 调用的入参和出参"""
def __init__(self, log_dir: str = "audit_logs"):
self.log_dir = Path(log_dir)
self.log_dir.mkdir(exist_ok=True)
def on_llm_start(self, serialized, prompts, **kwargs):
run_id = kwargs.get("run_id")
entry = {"event": "llm_start", "run_id": str(run_id), "prompts": prompts}
(self.log_dir / f"{run_id}.jsonl").write_text(
json.dumps(entry, ensure_ascii=False) + "\n", encoding="utf-8"
)
def on_llm_end(self, response, **kwargs):
run_id = kwargs.get("run_id")
entry = {"event": "llm_end", "run_id": str(run_id), "llm_output": response.llm_output}
with open(self.log_dir / f"{run_id}.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
def on_llm_error(self, error, **kwargs):
run_id = kwargs.get("run_id")
entry = {"event": "llm_error", "run_id": str(run_id), "error": repr(error)}
with open(self.log_dir / f"{run_id}.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
3.2 不要阻塞主流程
Callback handler 与主执行流程是同步关系——handler 的执行时间直接累加到 invoke() 的总耗时。如果需要异步持久化(写入数据库、发送 HTTP 请求),使用队列解耦:
# langchain>=0.3.0
import threading
from queue import Queue
from langchain_core.callbacks import BaseCallbackHandler
class AsyncLogHandler(BaseCallbackHandler):
"""写日志到远端,通过队列解耦不阻塞主流程"""
def __init__(self):
self.queue = Queue()
self._worker = threading.Thread(target=self._consumer, daemon=True)
self._worker.start()
def on_llm_start(self, serialized, prompts, **kwargs):
self.queue.put(("llm_start", serialized, prompts, kwargs.get("run_id")))
def on_llm_end(self, response, **kwargs):
self.queue.put(("llm_end", response.llm_output, kwargs.get("run_id")))
def _consumer(self):
while True:
event = self.queue.get()
# 实际写入远端存储
...
3.3 使用 tags 细粒度过滤
在生产环境中可能注册多个 handler,每个 handler 只关心特定链路:
# langchain>=0.3.0
chain = prompt | model | parser
chain.invoke(
{"input": "hello"},
config={
"tags": ["critical", "user-facing"],
"callbacks": [alert_handler],
},
)
在 handler 内检查 tags:
# langchain>=0.3.0
class AlertHandler(BaseCallbackHandler):
def on_chain_error(self, error, **kwargs):
tags = kwargs.get("tags", [])
if "critical" in tags:
send_alert(error) # 仅关键链路触发告警
3.4 典型场景组合
| 场景 | 推荐处理器 | 关键事件 |
|---|---|---|
| 调试开发 | StdOutCallbackHandler |
全部事件 |
| 生产日志 | 自定义 JsonLogHandler |
on_llm_start/end/error |
| 合规审计 | 自定义 AuditHandler |
on_tool_start/end(记录工具参数+输出) |
| 性能监控 | 自定义 MetricHandler |
on_llm_end(计算耗时 + token 数) |
| 异常告警 | 自定义 AlertHandler |
on_llm_error, on_chain_error, on_tool_error |
| 调用追踪 | LangSmithTracer(远端 handler) |
全部事件 |
3.5 与 LangSmith 的关系
LangSmith 的追踪功能本质上是注册了一个远端 CallbackHandler。当设置 LANGCHAIN_TRACING_V2=true 时,LangChain 自动将 LangSmithTracer(继承自 BaseCallbackHandler)注入到 CallbackManager 中:
LangSmithTracer 在各事件回调中收集运行数据(prompt、response、token 计数、延迟、错误),序列化为 protobuf 或 JSON,异步 POST 到 LangSmith 后端。用户可在 LangSmith Web UI 中查看 trace 树、对比实验、分析成本。本质上,LangSmith 就是一个运行在远端的、自带存储和可视化能力的超级 CallbackHandler。
四、快速参考
完整导入路径
from langchain_core.callbacks import BaseCallbackHandler # langchain>=0.3.0
from langchain_core.callbacks import CallbackManager # langchain>=0.3.0
from langchain_core.callbacks import StdOutCallbackHandler # langchain>=0.3.0
from langchain_core.callbacks import FileCallbackHandler # langchain>=0.3.0
from langchain_core.callbacks import CallbackManagerForLLMRun
from langchain_core.runnables import RunnableConfig
最小自定义 handler 模板
# langchain>=0.3.0
from langchain_core.callbacks import BaseCallbackHandler
class MyHandler(BaseCallbackHandler):
def on_llm_start(self, serialized, prompts, **kwargs):
# serialized: dict, LLM 配置的序列化表示
# prompts: list[str], 传入的 prompt 列表
# kwargs: run_id, parent_run_id, tags, metadata 等
pass
def on_llm_end(self, response, **kwargs):
# response: LLMResult, 包含 generations 和 llm_output
# llm_output 中通常包含 token_usage
pass
def on_tool_start(self, serialized, input_str, **kwargs):
# input_str: str, 工具的输入(已转为字符串)
pass
def on_tool_end(self, output, **kwargs):
# output: str, 工具的输出
pass
注意事项
- Handler 中的异常不会中断主流程(LangChain 默认
raise_error=False),但可以通过配置CallbackManager(raise_error=True)改变 - 同一个 handler 实例不要在多线程 invoke 中共享可变状态
on_retry事件在重试发生前触发,可用来记录重试原因和次数run_id是UUID类型,可作为链路追踪的全局标识符
更多推荐



所有评论(0)