第15章 、LangChain回调机制与可观测性

在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控制台后,可查看以下核心信息,实现全链路可观测:

  1. 追踪列表:显示所有链、LLM的调用记录,包含调用时间、耗时、状态(成功/失败)、Token消耗。

  2. 链路详情:点击任意追踪记录,可查看完整的调用链路,包括Prompt模板、格式化后的Prompt、LLM响应、输出结果,甚至能看到每一步的耗时。

  3. 错误排查:如果执行失败,会在状态中显示"Error",点击详情可查看错误堆栈、异常信息,快速定位问题(如API Key错误、Prompt格式错误)。

  4. 性能分析:在"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]:

  1. 顺序优先级:组合回调的列表中,靠前的回调先执行(如上述示例中,log_handler先执行,再执行token_handler)。

  2. 作用域优先级:回调的作用域分为"全局回调"(构造函数传入,如llm = ChatOpenAI(callbacks=[...]))和"局部回调"(invoke时传入,如chain.invoke(config={"callbacks": [...]})),局部回调优先级高于全局回调。

  3. 覆盖规则:如果全局回调和局部回调监听同一个事件,局部回调会覆盖全局回调的执行(即只执行局部回调,不执行全局回调)。

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
相关推荐
不会敲代码17 小时前
手写一个「迷你 Cursor」:从零构建 AI 编程助手(LangChain + Tool Calling 实战)
langchain·node.js·cursor
kyle-fang14 小时前
langchain各类文档加载
windows·python·langchain
donglianyou15 小时前
Agent技术详解与实战
python·langchain·agent·langgraph
凌奕15 小时前
LangChain 接外部工具:MCP 和 CLI 到底该选哪个?
langchain
liu****16 小时前
LangGraph-AI应用开发框架(五)
python·langchain·大模型·langgraph
花千树-01017 小时前
三个 Agent 并行调研:用 concurrent 节点构建并发-汇聚式旅游规划助手
java·langchain·agent·function call·multi agent·mcp·harness
是小蟹呀^17 小时前
【整理】Agent中的ReAct架构
langchain·agent·react
舒一笑1 天前
大模型根本不是“学会了”,它只是会“看例子”:一文讲透 In-context Learning(ICL)
langchain·llm·openai
Java后端的Ai之路1 天前
LangChain ReAct Agent 核心技术问答
前端·react.js·langchain