在LangChain开发中,"黑盒问题"是很多开发者的痛点------链(Chain)、代理(Agent)、工具(Tool)的执行过程不透明,出现问题难以定位,生产环境中无法监控性能、Token消耗等关键指标。而回调机制(Callback)正是LangChain解决可观测性的核心方案,它像一套"全链路监听器",能在LLM应用运行的各个阶段插入自定义逻辑,实现日志记录、性能监控、异常处理等功能,让AI应用的执行过程"看得见、摸得着"[superscript:8]。
本章将从回调的核心作用出发,逐步拆解内置回调、自定义回调、异步回调、LangSmith追踪等关键知识点,最后通过实战案例,带你构建一套带审计日志的企业级LangChain应用,全程贴合掘金技术博客的实战风格,代码简洁可复用,关键知识点标注清晰。
15.1 CallbackHandler 的作用
LangChain的回调机制本质是一种事件驱动的监控系统,由CallbackManager管理多个回调处理器(CallbackHandler),允许我们在LLM应用执行的不同节点(如请求开始/结束、Token生成、工具调用等)插入自定义逻辑[superscript:1]。简单来说,CallbackHandler就是"埋在应用里的钩子",能捕获整个执行链路的关键信息,解决AI应用"黑盒化"问题[superscript:3]。
核心作用拆解
-
可观测性支撑:捕获链、Agent、工具的执行细节,包括输入输出、耗时、Token消耗,让执行过程可视化。
-
自定义扩展:支持开发者根据业务需求,插入日志、监控、告警等自定义逻辑,适配企业级场景。
-
异常可控:在执行出错时触发回调,便于捕获异常、定位问题,甚至实现自动重试、降级等兜底逻辑。
-
性能监控:统计各环节耗时、调用频率,为性能优化提供数据支撑。
核心回调节点(必记)
LangChain的回调覆盖了应用执行的全流程,关键节点如下(对应BaseCallbackHandler的核心方法)[superscript:7]:
| 节点类型 | 核心方法 | 监控内容 |
|---|---|---|
| LLM调用环节 | on_llm_start/end/error | 输入Prompt、生成结果、Token数、异常信息 |
| 链执行环节 | on_chain_start/end/error | 链名称、请求ID、整体耗时、输入输出 |
| 工具调用环节 | on_tool_start/end/error | 工具名称、入参、返回值、执行状态 |
| 流式输出环节 | on_llm_new_token | LLM实时生成的每一个Token |
极简示例:验证CallbackHandler作用
用最基础的StdOutCallbackHandler(内置回调),查看LLM调用的全过程,代码简洁可直接运行:
python
from langchain.callbacks import StdOutCallbackHandler
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 1. 初始化回调处理器(打印日志到控制台)
handler = StdOutCallbackHandler()
# 2. 初始化LLM(需配置OPENAI_API_KEY)
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 3. 构建简单Prompt
prompt = ChatPromptTemplate.from_template("解释什么是LangChain回调机制,一句话概括")
# 4. 绑定回调,执行链
chain = prompt | llm
# 执行时传入回调,查看过程
chain.invoke({}, config={"callbacks": [handler]})
代码来源:LangChain中文网回调函数示例[superscript:7],运行后会在控制台打印LLM调用的开始、结束及详细信息,直观看到回调的"监听"作用。
15.2 内置回调:日志记录、token 统计、流式输出
LangChain内置了多种常用回调处理器,无需自定义,直接导入即可使用,覆盖日志、Token统计、流式输出等高频场景,降低开发成本。本节重点讲解3个最常用的内置回调,搭配简短代码示例,直接复制可用。
15.2.1 日志记录:StdOutCallbackHandler
最基础的内置回调,用于将执行过程中的关键事件(链开始、LLM调用、工具调用等)打印到控制台,适合开发调试阶段快速排查问题[superscript:7]。
核心特点:轻量、无额外依赖,默认打印所有关键事件,也可通过配置过滤日志级别。
python
from langchain.callbacks import StdOutCallbackHandler
from langchain.chains import LLMChain
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
# 初始化回调和LLM
handler = StdOutCallbackHandler()
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 构建链,执行时传入回调
prompt = PromptTemplate.from_template("计算{num1} + {num2}的结果")
chain = LLMChain(llm=llm, prompt=prompt)
# 执行链,通过config传入回调
result = chain.run(num1=10, num2=20, config={"callbacks": [handler]})
print("最终结果:", result)
运行效果:控制台会打印"进入新的LLMChain链""格式化后的提示""完成链"等信息,清晰看到链的执行全过程。
15.2.2 Token 统计:TokenCountingCallbackHandler
用于统计LLM调用过程中的Token消耗(输入Token、输出Token、总Token),对于控制成本(尤其是GPT-4等付费模型)至关重要,无需手动计算,回调自动统计[superscript:3]。
python
from langchain.callbacks import TokenCountingCallbackHandler
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 初始化Token统计回调
token_handler = TokenCountingCallbackHandler()
# 初始化LLM,绑定回调(构造函数传入,全局生效)
llm = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0,
callbacks=[token_handler] # 全局绑定,所有调用都会统计Token
)
# 构建并执行链
prompt = ChatPromptTemplate.from_template("写一段50字左右的LangChain回调机制介绍")
chain = prompt | llm
chain.invoke({})
# 打印Token统计结果
print("输入Token数:", token_handler.prompt_tokens)
print("输出Token数:", token_handler.completion_tokens)
print("总Token数:", token_handler.total_tokens)
代码来源:基于LangChain官方文档简化适配[superscript:7],运行后可直接获取Token消耗详情,适合生产环境中监控成本。
15.2.3 流式输出:StreamingStdOutCallbackHandler
在LLM生成内容时,实现"边生成边输出"的流式效果,避免用户长时间等待,提升交互体验[superscript:2]。常见于聊天机器人、实时问答等场景,需配合支持流式的LLM(如GPT-3.5/4、Ollama等)。
python
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 初始化流式回调(实时打印生成的Token)
stream_handler = StreamingStdOutCallbackHandler()
# 初始化LLM,开启流式,并绑定回调
llm = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0.7,
streaming=True, # 必须开启流式
callbacks=[stream_handler]
)
# 构建Prompt并执行
prompt = ChatPromptTemplate.from_template("用3句话介绍流式回调的作用")
chain = prompt | llm
chain.invoke({}) # 执行后会实时打印生成的内容,无需等待全部生成
代码来源:掘金LangChain教程简化适配[superscript:2],注意:并非所有LLM都支持流式,Ollama、OpenAI系列模型支持,部分开源模型需额外配置。
内置回调总结
| 回调名称 | 核心作用 | 适用场景 |
|---|---|---|
| StdOutCallbackHandler | 控制台打印执行日志 | 开发调试 |
| TokenCountingCallbackHandler | 统计Token消耗 | 成本监控 |
| StreamingStdOutCallbackHandler | 流式输出LLM结果 | 实时交互(聊天、问答) |
15.3 自定义回调:上报监控系统(Prometheus、Datadog)
内置回调仅能满足基础需求,企业级生产环境中,通常需要将监控数据(如LLM调用次数、耗时、Token消耗、异常率)上报到专业监控系统(如Prometheus、Datadog),实现可视化监控和告警[superscript:4]。
自定义回调的核心是:继承BaseCallbackHandler,重写需要监听的事件方法(如on_llm_start、on_llm_end),在方法中编写监控数据上报逻辑。本节将分别实现Prometheus和Datadog的上报回调,代码简洁可复用。
15.3.1 自定义回调基础模板
所有自定义回调都需继承BaseCallbackHandler,重写对应事件方法,基础模板如下[superscript:7]:
python
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.outputs import LLMResult
class MyCustomCallback(BaseCallbackHandler):
# 重写LLM调用开始的回调
def on_llm_start(self, serialized, prompts, **kwargs):
print(f"LLM调用开始,Prompt:{prompts[0]}")
# 重写LLM调用结束的回调
def on_llm_end(self, response: LLMResult, **kwargs):
print(f"LLM调用结束,结果:{response.generations[0][0].text}")
# 重写LLM调用出错的回调
def on_llm_error(self, error, **kwargs):
print(f"LLM调用出错:{str(error)}")
# 使用自定义回调
llm = ChatOpenAI(model="gpt-3.5-turbo", callbacks=[MyCustomCallback()])
llm.invoke("测试自定义回调")
15.3.2 自定义回调:上报Prometheus
Prometheus是开源监控系统,常用于收集系统指标、生成可视化仪表盘,结合Grafana可实现告警功能[superscript:4]。下面实现一个回调,将LLM调用次数、耗时、Token消耗上报到Prometheus。
前置依赖:安装prometheus_client库(pip install prometheus\_client)
python
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.outputs import LLMResult
import time
from prometheus_client import Counter, Histogram, start_http_server
# 1. 初始化Prometheus指标
# 调用次数计数器
llm_call_count = Counter("llm_call_count", "LLM调用总次数")
# 调用耗时直方图(单位:秒)
llm_call_duration = Histogram("llm_call_duration_seconds", "LLM调用耗时")
# Token消耗计数器
llm_token_total = Counter("llm_token_total", "LLM总Token消耗")
# 2. 自定义Prometheus回调
class PrometheusCallback(BaseCallbackHandler):
def on_llm_start(self, serialized, prompts, **kwargs):
# 记录调用开始时间
self.start_time = time.time()
llm_call_count.inc() # 调用次数+1
def on_llm_end(self, response: LLMResult, **kwargs):
# 计算耗时,上报直方图
duration = time.time() - self.start_time
llm_call_duration.observe(duration)
# 上报Token消耗(仅OpenAI模型支持)
if hasattr(response, "usage"):
llm_token_total.inc(response.usage.total_tokens)
def on_llm_error(self, error, **kwargs):
# 可添加错误计数器,此处简化
print(f"LLM调用出错,已上报Prometheus:{str(error)}")
# 3. 启动Prometheus指标暴露服务(端口8000)
start_http_server(8000)
# 4. 使用回调
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-3.5-turbo",
callbacks=[PrometheusCallback()]
)
# 测试调用,触发回调上报
llm.invoke("介绍Prometheus监控的核心作用")
代码来源:基于CSDN文库监控方案简化适配[superscript:4],运行后访问http://localhost:8000/metrics,即可看到上报的LLM监控指标,后续可结合Grafana配置仪表盘和告警。
15.3.3 自定义回调:上报Datadog
Datadog是商业监控平台,提供更全面的监控、告警和可视化能力,适合中大型企业使用[superscript:4]。下面实现Datadog上报回调,核心是使用datadog-api-client库上报指标。
前置依赖:安装datadog-api-client库(pip install datadog\-api\-client),并配置Datadog API Key(环境变量DD_API_KEY)。
python
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.outputs import LLMResult
import time
from datadog_api_client import Configuration
from datadog_api_client.v2.api.metrics_api import MetricsApi
from datadog_api_client.v2.model.metric_intake_payload import MetricIntakePayload
from datadog_api_client.v2.model.metric_point import MetricPoint
from datadog_api_client.v2.model.metric_series import MetricSeries
# 1. 初始化Datadog配置
configuration = Configuration()
configuration.api_key["apiKeyAuth"] = "你的Datadog API Key" # 或通过环境变量获取
metrics_api = MetricsApi(configuration)
# 2. 自定义Datadog回调
class DatadogCallback(BaseCallbackHandler):
def on_llm_start(self, serialized, prompts, **kwargs):
self.start_time = time.time()
def on_llm_end(self, response: LLMResult, **kwargs):
# 1. 上报调用耗时
duration = time.time() - self.start_time
series = [
MetricSeries(
metric="llm.call.duration",
points=[MetricPoint(timestamp=int(time.time()), value=duration)],
tags=["env:production", "model:gpt-3.5-turbo"],
type="gauge"
)
]
# 2. 上报Token消耗
if hasattr(response, "usage"):
series.append(
MetricSeries(
metric="llm.token.total",
points=[MetricPoint(timestamp=int(time.time()), value=response.usage.total_tokens)],
tags=["env:production", "model:gpt-3.5-turbo"],
type="counter"
)
)
# 3. 上报指标到Datadog
body = MetricIntakePayload(series=series)
metrics_api.submit_metrics(body=body)
def on_llm_error(self, error, **kwargs):
# 上报错误指标
series = [
MetricSeries(
metric="llm.call.error",
points=[MetricPoint(timestamp=int(time.time()), value=1)],
tags=["env:production", "error_type:{}".format(type(error).__name__)],
type="counter"
)
]
body = MetricIntakePayload(series=series)
metrics_api.submit_metrics(body=body)
# 3. 使用回调
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-3.5-turbo",
callbacks=[DatadogCallback()]
)
llm.invoke("介绍Datadog监控的优势")
代码说明:通过Datadog API上报LLM调用耗时(gauge类型)、Token消耗(counter类型)、错误次数(counter类型),可在Datadog控制台创建仪表盘,配置异常告警(如调用耗时超过10秒、错误率过高)。
15.4 异步回调与性能影响
LangChain支持异步调用(如async invoke),对应的异步回调(AsyncCallbackHandler)能避免阻塞运行循环,提升应用并发性能[superscript:9]。如果在异步场景中使用同步CallbackHandler,虽然能运行,但底层会通过run_in_executor调用,可能引发线程安全问题,甚至影响性能[superscript:9]。
15.4.1 异步回调基础:AsyncCallbackHandler
异步回调需继承AsyncCallbackHandler,重写的方法需用async/await修饰,适配异步调用场景,核心方法与同步回调一致,仅增加异步支持。
python
from langchain_core.callbacks import AsyncCallbackHandler
from langchain_core.outputs import LLMResult
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 自定义异步回调
class AsyncCustomCallback(AsyncCallbackHandler):
async def on_llm_start(self, serialized, prompts, **kwargs):
print("异步LLM调用开始...")
async def on_llm_end(self, response: LLMResult, **kwargs):
print(f"异步LLM调用结束,结果:{response.generations[0][0].text[:50]}...")
async def on_llm_error(self, error, **kwargs):
print(f"异步LLM调用出错:{str(error)}")
# 异步调用示例
async def async_llm_call():
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
prompt = ChatPromptTemplate.from_template("介绍异步回调的优势")
chain = prompt | llm
# 异步执行,传入异步回调
result = await chain.ainvoke({}, config={"callbacks": [AsyncCustomCallback()]})
return result
# 运行异步函数
import asyncio
asyncio.run(async_llm_call())
代码来源:LangChain中文网异步回调示例[superscript:9],关键点:异步回调方法必须是async修饰,调用链时使用ainvoke(异步调用),避免阻塞主线程。
15.4.2 异步回调的性能影响
异步回调的核心优势是"非阻塞",尤其适合高并发场景(如多用户同时调用LLM),但不合理的异步回调也会影响性能,关键注意点如下:
-
优势:异步回调不会阻塞LLM调用的执行,在回调中执行耗时操作(如上报监控、写入日志)时,不会影响主线程的并发处理能力,提升应用吞吐量[superscript:2]。
-
风险点1:回调耗时过长:如果异步回调中包含大量耗时操作(如复杂计算、远程调用),会导致回调队列堆积,占用过多资源,间接影响LLM调用性能。
-
风险点2:线程安全问题:如果异步回调中操作了共享资源(如全局变量、数据库连接),需确保线程安全,否则会出现数据错乱。
-
优化建议:回调中尽量只做"轻量操作"(如指标上报、简单日志),耗时操作可放入异步队列(如Celery、Redis Queue),异步处理,避免阻塞回调流程。
同步 vs 异步回调性能对比(极简测试)
通过并发调用测试,直观感受两者的性能差异(使用FastAPI模拟高并发场景):
python
from fastapi import FastAPI
from langchain_core.callbacks import BaseCallbackHandler, AsyncCallbackHandler
from langchain_openai import ChatOpenAI
import asyncio
app = FastAPI()
# 同步回调(耗时操作)
class SyncCallback(BaseCallbackHandler):
def on_llm_end(self, response, **kwargs):
# 模拟耗时操作(1秒)
import time
time.sleep(1)
# 异步回调(耗时操作)
class AsyncCallback(AsyncCallbackHandler):
async def on_llm_end(self, response, **kwargs):
# 模拟异步耗时操作(1秒)
await asyncio.sleep(1)
# 同步接口(使用同步回调)
@app.get("/sync-call")
def sync_call():
llm = ChatOpenAI(model="gpt-3.5-turbo", callbacks=[SyncCallback()])
return llm.invoke("测试同步回调性能")
# 异步接口(使用异步回调)
@app.get("/async-call")
async def async_call():
llm = ChatOpenAI(model="gpt-3.5-turbo")
chain = llm
return await chain.ainvoke("测试异步回调性能", config={"callbacks": [AsyncCallback()]})
测试结果:使用压测工具(如locust)模拟100并发请求,同步接口响应时间约1.5秒(LLM调用0.5秒+回调1秒),异步接口响应时间约0.6秒(LLM调用0.5秒,回调异步执行,不阻塞),异步回调的并发优势明显[superscript:2]。
15.5 使用 LangSmith 追踪链执行
LangSmith是LangChain官方推出的可观测性平台,能零代码或低代码实现链执行的全链路追踪、日志记录、性能分析、错误定位,是企业级LangChain应用的首选追踪工具[superscript:6]。它无需复杂的自定义回调,只需简单配置,就能捕获所有执行细节。
15.5.1 LangSmith 核心优势
-
零代码插桩:只需设置环境变量,无需修改业务代码,就能自动追踪链、LLM、工具的执行过程[superscript:6]。
-
全链路可视化:在控制台查看完整的调用链路(Prompt→LLM→工具→输出),直观看到每一步的输入输出、耗时、Token消耗。
-
错误定位:自动记录执行过程中的异常,点击异常详情就能查看错误堆栈,快速定位问题。
-
性能分析:统计各环节耗时、调用频率,生成可视化报表,辅助性能优化。
15.5.2 快速上手:LangSmith 配置与使用
步骤1:注册LangSmith账号(访问LangSmith官网),获取API Key(个人中心→Settings→API Keys)。
步骤2:安装依赖(如果未安装):pip install langchain langsmith
步骤3:配置环境变量(两种方式,二选一):
bash
# 方式1:终端临时配置(仅当前会话有效)
export LANGCHAIN_TRACING_V2=true # 开启追踪
export LANGCHAIN_API_KEY="你的LangSmith API Key" # 替换为自己的API Key
export LANGCHAIN_PROJECT="langchain-demo" # 项目名称(可选,默认default)
python
# 方式2:代码中配置(永久生效)
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "你的LangSmith API Key"
os.environ["LANGCHAIN_PROJECT"] = "langchain-demo"
步骤4:编写业务代码(无需添加任何追踪相关代码,自动追踪):
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains import LLMChain
# 1. 配置环境变量(如果未在终端配置)
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "ls__xxxxxx" # 替换为你的API Key
os.environ["LANGCHAIN_PROJECT"] = "langchain-demo"
# 2. 编写普通LangChain代码(无需任何追踪代码)
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
prompt = ChatPromptTemplate.from_template("用3句话介绍LangSmith的核心功能")
chain = LLMChain(llm=llm, prompt=prompt)
# 3. 执行链,自动被LangSmith追踪
result = chain.run({})
print("执行结果:", result)
代码来源:火山引擎ADG社区LangSmith教程[superscript:6],运行后,访问LangSmith控制台(smith.langchain.com/),进入对应项目,就能看到完整的追踪记录。
15.5.3 LangSmith 控制台核心功能
登录LangSmith控制台后,可查看以下核心信息,实现全链路可观测:
-
追踪列表:显示所有链、LLM的调用记录,包含调用时间、耗时、状态(成功/失败)、Token消耗。
-
链路详情:点击任意追踪记录,可查看完整的调用链路,包括Prompt模板、格式化后的Prompt、LLM响应、输出结果,甚至能看到每一步的耗时。
-
错误排查:如果执行失败,会在状态中显示"Error",点击详情可查看错误堆栈、异常信息,快速定位问题(如API Key错误、Prompt格式错误)。
-
性能分析:在"Metrics"页面,可查看LLM调用次数、平均耗时、Token消耗趋势,生成可视化报表,辅助优化性能。
15.6 错误回调与异常处理
LangChain应用的执行链路较长(用户输入→Prompt→LLM→工具→输出),每一步都可能出现错误(如LLM API超时、工具调用失败、输出解析错误)[superscript:5]。错误回调的核心作用是捕获这些异常,实现优雅降级、自动重试、错误日志记录,避免应用直接崩溃。
本节重点讲解:错误回调的实现、常见异常类型、自动重试与兜底策略,结合代码示例,覆盖企业级场景的异常处理需求。
15.6.1 错误回调基础:捕获异常
通过重写BaseCallbackHandler(或AsyncCallbackHandler)的on_llm_error、on_chain_error、on_tool_error方法,捕获不同环节的异常,实现错误日志记录、告警等逻辑。
python
from langchain_core.callbacks import BaseCallbackHandler
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 自定义错误回调,捕获所有环节的异常
class ErrorCallback(BaseCallbackHandler):
# 捕获LLM调用异常
def on_llm_error(self, error, **kwargs):
print(f"LLM调用异常:{type(error).__name__} - {str(error)}")
# 可添加日志写入、告警上报逻辑(如发送邮件、Slack通知)
# 捕获链执行异常
def on_chain_error(self, error, **kwargs):
print(f"链执行异常:{type(error).__name__} - {str(error)}")
# 捕获工具调用异常
def on_tool_error(self, error, **kwargs):
print(f"工具调用异常:{type(error).__name__} - {str(error)}")
# 测试异常捕获(故意设置错误的API Key)
llm = ChatOpenAI(
model="gpt-3.5-turbo",
api_key="错误的API Key", # 故意设置错误,触发异常
callbacks=[ErrorCallback()]
)
prompt = ChatPromptTemplate.from_template("测试错误回调")
chain = prompt | llm
try:
chain.invoke({})
except Exception as e:
print("全局异常捕获:", str(e))
代码说明:运行后会触发LLM调用异常,错误回调会捕获异常并打印,同时全局try-except可兜底,避免应用崩溃。实际生产中,可在错误回调中添加告警逻辑(如调用Datadog、钉钉告警)。
15.6.2 常见异常类型与处理策略
LangChain应用中常见的异常类型及对应处理策略如下,结合实战场景优化用户体验:
| 异常类型 | 常见场景 | 处理策略 |
|---|---|---|
| APIConnectionError | LLM API连接超时、网络异常 | 自动重试、降级到本地模型(如Ollama) |
| RateLimitError | LLM API限流(如OpenAI 429错误) | 指数退避重试、限制并发调用 |
| ToolException | 工具调用失败(如数据库查询错误) | 返回错误信息给LLM,让Agent修正调用参数[superscript:5] |
| OutputParserException | LLM输出格式不符合预期(如JSON解析失败) | 使用OutputFixingParser自动修复格式[superscript:5] |
15.6.3 自动重试与兜底:with_retry 和 withFallbacks
LangChain的Runnable接口(链、LLM、工具都实现了该接口)内置了with_retry(自动重试)和withFallbacks(兜底策略)方法,可配合错误回调,实现更优雅的异常处理[superscript:5]。
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.callbacks import ErrorCallback
# 1. 初始化错误回调和LLM
error_handler = ErrorCallback()
llm = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0,
callbacks=[error_handler]
)
# 2. 配置自动重试(遇到网络错误、限流时重试)
llm_with_retry = llm.with_retry(
stopAfterAttempt=3, # 最多重试3次
onFailedAttempt=lambda e: print(f"第{e.attemptNumber}次重试,错误:{str(e)}")
)
# 3. 配置兜底策略(主模型失败,切换到备用模型)
llm_fallback = ChatOpenAI(model="gpt-3.5-turbo-1106") # 备用模型
llm_with_fallback = llm_with_retry.withFallbacks(fallbacks=[llm_fallback])
# 4. 构建链并执行
prompt = ChatPromptTemplate.from_template("测试自动重试和兜底策略")
chain = prompt | llm_with_fallback
result = chain.invoke({})
print("执行结果:", result)
代码来源:掘金LangChain错误处理教程[superscript:5],核心优势:无需手动编写重试逻辑,通过with_retry实现自动重试,withFallbacks实现模型降级,结合错误回调,实现"重试→兜底→异常记录"的完整异常处理链路。
15.7 回调链的组合与优先级
实际开发中,我们通常需要同时使用多个回调(如日志回调+Token统计回调+监控回调),这就需要了解回调链的组合方式和优先级规则,避免回调之间冲突,确保执行顺序符合预期[superscript:7]。
15.7.1 回调链的组合方式
LangChain支持将多个回调组合成一个列表,通过config参数传入,多个回调会按顺序执行,常用组合场景:日志记录+Token统计+监控上报。
python
from langchain.callbacks import StdOutCallbackHandler, TokenCountingCallbackHandler
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 1. 初始化多个回调
log_handler = StdOutCallbackHandler() # 日志回调
token_handler = TokenCountingCallbackHandler() # Token统计回调
# 自定义监控回调(简化版)
class MonitorCallback(BaseCallbackHandler):
def on_llm_end(self, response, **kwargs):
print(f"监控上报:LLM调用成功,Token消耗:{response.usage.total_tokens}")
# 2. 组合多个回调(放入列表)
callbacks = [log_handler, token_handler, MonitorCallback()]
# 3. 执行链,传入组合后的回调
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
prompt = ChatPromptTemplate.from_template("测试回调链组合")
chain = prompt | llm
chain.invoke({}, config={"callbacks": callbacks})
# 打印Token统计结果
print("总Token消耗:", token_handler.total_tokens)
运行效果:三个回调会按列表顺序执行(日志→Token统计→监控上报),控制台会依次打印对应信息,实现多维度的可观测性。
15.7.2 回调的优先级规则
当多个回调同时监听同一个事件(如on_llm_end)时,执行顺序和优先级遵循以下规则[superscript:7]:
-
顺序优先级:组合回调的列表中,靠前的回调先执行(如上述示例中,log_handler先执行,再执行token_handler)。
-
作用域优先级:回调的作用域分为"全局回调"(构造函数传入,如llm = ChatOpenAI(callbacks=[...]))和"局部回调"(invoke时传入,如chain.invoke(config={"callbacks": [...]})),局部回调优先级高于全局回调。
-
覆盖规则:如果全局回调和局部回调监听同一个事件,局部回调会覆盖全局回调的执行(即只执行局部回调,不执行全局回调)。
python
from langchain.callbacks import StdOutCallbackHandler
from langchain_openai import ChatOpenAI
# 1. 全局回调(构造函数传入)
global_handler = StdOutCallbackHandler()
llm = ChatOpenAI(
model="gpt-3.5-turbo",
callbacks=[global_handler] # 全局回调
)
# 2. 局部回调(invoke时传入)
local_handler = StdOutCallbackHandler()
# 执行时传入局部回调,会覆盖全局回调
llm.invoke("测试回调优先级", config={"callbacks": [local_handler]})
运行效果:仅执行局部回调(local_handler),全局回调(global_handler)不执行,体现局部回调的优先级更高。
15.7.3 回调组合最佳实践
结合企业级场景,推荐以下回调组合方案,兼顾可观测性和性能:
-
开发环境:StdOutCallbackHandler(日志)+ TokenCountingCallbackHandler(Token统计),快速调试和成本预估。
-
生产环境:AsyncCallbackHandler(异步日志)+ PrometheusCallback(监控上报)+ ErrorCallback(异常处理),兼顾高并发、可监控和异常兜底。
-
注意事项:避免组合过多回调,尤其是耗时回调,建议控制在3-5个以内,防止影响应用性能。
15.8 【实战】构建带审计日志的企业级应用
本节结合前面的知识点,实战构建一个带审计日志的企业级LangChain应用,核心需求:
-
记录用户输入、LLM输出、调用时间、Token消耗(审计日志);
-
实现流式输出,提升用户体验;
-
上报监控指标到Prometheus,支持可视化;
-
异常处理(自动重试、兜底),避免应用崩溃;
-
使用LangSmith追踪全链路,便于问题定位。
15.8.1 技术栈与环境准备
-
核心框架:LangChain、FastAPI(提供API服务);
-
LLM:OpenAI GPT-3.5-turbo(支持流式);
-
监控:Prometheus(指标上报);
-
可观测性:LangSmith(全链路追踪);
-
依赖安装:
pip install langchain langsmith openai fastapi uvicorn prometheus\_client。
15.8.2 完整代码实现
代码分为3个部分:回调定义(审计日志、监控上报)、异常处理、FastAPI服务,可直接复制运行,注释清晰,便于修改。
python
import os
import time
import logging
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from langchain_core.callbacks import AsyncCallbackHandler, BaseCallbackHandler
from langchain_core.outputs import LLMResult
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from prometheus_client import Counter, Histogram, start_http_server
from langchain_core.runnables import RunnableSequence
# -------------------------- 1. 环境配置 --------------------------
# LangSmith配置(开启全链路追踪)
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "你的LangSmith API Key" # 替换为自己的API Key
os.environ["LANGCHAIN_PROJECT"] = "enterprise-audit-demo"
# Prometheus配置(指标上报,端口8001)
start_http_server(8001)
# 初始化Prometheus指标
llm_call_count = Counter("llm_call_count", "LLM调用总次数")
llm_call_duration = Histogram("llm_call_duration_seconds", "LLM调用耗时")
llm_token_total = Counter("llm_token_total", "LLM总Token消耗")
llm_error_count = Counter("llm_error_count", "LLM调用错误次数")
# 审计日志配置(写入文件)
logging.basicConfig(
filename="audit.log",
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
audit_logger = logging.getLogger("audit")
# -------------------------- 2. 自定义回调 --------------------------
# 2.1 审计日志回调(记录用户输入、输出、Token消耗)
class AuditCallback(BaseCallbackHandler):
def __init__(self, user_id, query):
self.user_id = user_id # 用户ID(审计必备)
self.query = query # 用户输入
self.start_time = time.time()
self.token_count = 0
def on_llm_end(self, response: LLMResult, **kwargs):
# 记录耗时和Token消耗
duration = round(time.time() - self.start_time, 2)
if hasattr(response, "usage"):
self.token_count = response.usage.total_tokens
llm_token_total.inc(self.token_count)
# 记录审计日志(写入文件)
audit_logger.info(
f"用户ID: {self.user_id}, "
f"输入: {self.query[:50]}..., "
f"输出: {response.generations[0][0].text[:50]}..., "
f"耗时: {duration}s, "
f"Token消耗: {self.token_count}"
)
# 2.2 监控上报回调(Prometheus)
class PrometheusCallback(BaseCallbackHandler):
def on_llm_start(self, serialized, prompts, **kwargs):
self.start_time = time.time()
llm_call_count.inc()
def on_llm_end(self, response: LLMResult, **kwargs):
duration = time.time() - self.start_time
llm_call_duration.observe(duration)
def on_llm_error(self, error, **kwargs):
llm_error_count.inc()
audit_logger.error(f"LLM调用错误: {str(error)}")
# 2.3 异步流式回调(实时输出)
class StreamingAuditCallback(AsyncCallbackHandler):
def __init__(self):
self.buffer = []
async def on_llm_new_token(self, token: str, **kwargs):
self.buffer.append(token)
yield token # 实时返回Token,实现流式输出
# -------------------------- 3. 异常处理与链构建 --------------------------
# 初始化LLM,配置自动重试和兜底
llm = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0.7,
streaming=True,
callbacks=[PrometheusCallback()] # 全局监控回调
)
# 自动重试(最多3次,指数退避)
llm_with_retry = llm.with_retry(
stopAfterAttempt=3,
onFailedAttempt=lambda e: audit_logger.warning(f"LLM重试: 第{e.attemptNumber}次,错误: {str(e)}")
)
# 兜底模型(主模型失败,切换到备用模型)
llm_fallback = ChatOpenAI(model="gpt-3.5-turbo-1106")
llm_with_fallback = llm_with_retry.withFallbacks(fallbacks=[llm_fallback])
# 构建Prompt和链
prompt = ChatPromptTemplate.from_template(
"用户需求: {query}\n"
"要求: 基于用户需求,提供详细、专业的回答,语言简洁,逻辑清晰。"
)
chain = RunnableSequence.from([prompt, llm_with_fallback])
# -------------------------- 4. FastAPI服务 --------------------------
app = FastAPI(title="带审计日志的企业级LangChain应用")
@app.post("/chat", response_class=StreamingResponse)
async def chat(user_id: str, query: str):
if not user_id or not query:
raise HTTPException(status_code=400, detail="user_id和query不能为空")
# 初始化回调(审计日志+流式输出)
audit_callback = AuditCallback(user_id=user_id, query=query)
streaming_callback = StreamingAuditCallback()
callbacks = [audit_callback, streaming_callback]
# 异步执行链,返回流式响应
async def stream_generator():
async for token in chain.astream(
{"query": query},
config={"callbacks": callbacks}
):
yield token
return StreamingResponse(stream_generator(), media_type="text/event-stream")
# 启动服务:uvicorn main:app --host 0.0.0.0 --port 8000