回调与监控,用Callbacks追踪Agent的每一步执行过程

📅 2026/8/7 10:36:21
回调与监控,用Callbacks追踪Agent的每一步执行过程
回调与监控用Callbacks追踪Agent的每一步执行过程上一篇把LCEL表达式语言讲完了管道符一路接到底链跑得也挺顺。但有个事一直憋着没说链跑起来之后里面到底发生了什么哪一步调了模型哪一步调了工具token花在哪耗时多少出问题怎么定位到具体那一段。靠肉眼盯控制台打印是盯不出来的得靠回调机制。这篇就聊回调与监控把Agent的执行过程扒开看个明白。为什么需要回调Agent跑起来就像个黑盒。你invoke一下等几秒结果出来了中间发生了什么一无所知。模型被调了几次工具被调了几次每次的输入输出是什么全藏在底层。调试的时候这事特别烦。我之前做一个带工具调用的Agent线上偶发返回空结果本地又复现不了。日志里只看到最终输出是空的至于哪一步出了问题是模型没生成工具调用还是工具执行报错被吞了根本看不出来。后来加了回调把每一步的入参出参都打出来才发现是工具偶尔超时异常没被上层接住链静默吞掉继续跑最后就吐了个空。回调机制就是干这个的。它在链的每个关键节点埋了钩子节点开始时触发对应的回调方法结束时再触发一次。你写一个处理器把这些钩子接住就能拿到整条链的执行轨迹。有了这东西Agent对你来说就不再是个黑盒。BaseHandler自定义回调处理器LangChain的回调机制核心是BaseCallbackHandler在langchain_core.callbacks模块下。继承它重写需要的方法就能接管各个节点的通知。fromlangchain_core.callbacksimportBaseCallbackHandlerclassTraceHandler(BaseCallbackHandler):defon_llm_start(self,serialized,prompts,**kwargs):print([LLM开始] 准备调用模型)defon_llm_end(self,response,**kwargs):print([LLM结束] 模型返回了)defon_chain_start(self,serialized,inputs,**kwargs):print([Chain开始] 链开始执行)defon_chain_end(self,outputs,**kwargs):print([Chain结束] 链执行完毕)defon_tool_start(self,serialized,input_str,**kwargs):print(f[Tool开始] 工具被调用输入是{input_str})defon_tool_end(self,output,**kwargs):print(f[Tool结束] 工具返回{output})写好处理器之后塞进invoke的config参数就行。resultchain.invoke({question:LCEL是什么},config{callbacks:[TraceHandler()]},)也可以构造链时就绑定回调这样每次调用自动带上。chainchain.with_config({callbacks:[TraceHandler()]})几个核心回调方法BaseCallbackHandler里方法不少常用的就那几个对应链的不同节点。on_llm_start和on_llm_end模型调用的开始和结束。on_llm_start能拿到序列化的模型信息和提示词on_llm_end能拿到模型返回的完整响应。统计模型调用次数就看这俩。on_chain_start和on_chain_end链的开始和结束。一条LCEL链里可能有多个子链每一段都会触发一次。嵌套调用时这俩层层触发靠run_id能理清父子关系。on_tool_start和on_tool_end工具调用的开始和结束。Agent调搜索、调数据库、调自定义工具都走这俩。排查工具相关问题盯这里最直接。每个方法都有对应的error版本on_llm_error、on_chain_error、on_tool_error节点抛异常时触发。生产环境里这几个特别重要能帮你把错误逮个正着。还有一个on_llm_new_token流式输出时每生成一个token触发一次。下一篇聊流式输出会重点用到它这里先按下不表。打印执行日志把处理器用起来跑一个带工具的Agent看看效果。fromlangchain_openaiimportChatOpenAIfromlangchain_core.toolsimporttoolfromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholderfromlangchain.agentsimportcreate_tool_calling_agent,AgentExecutortooldefsearch(query:str)-str:搜索网络内容returnf关于{query}的搜索结果modelChatOpenAI(modelgpt-4o-mini)promptChatPromptTemplate.from_messages([(system,你是一个搜索助手),(human,{input}),MessagesPlaceholder(agent_scratchpad),])agentcreate_tool_calling_agent(model,[search],prompt)executorAgentExecutor(agentagent,tools[search],verboseFalse)executor.invoke({input:LangChain是什么},config{callbacks:[TraceHandler()]},)跑起来你会看到一串日志LLM开始、Tool开始、Tool结束、LLM开始、LLM结束按顺序排下来。模型先决定调工具工具执行完返回结果模型再拿到结果生成最终回答。整个执行流程清清楚楚哪一步慢、哪一步出错瞄一眼日志就知道。统计Token消耗光看流程还不够token花在哪里也得心里有谱。模型调用的response里带着token用量信息在on_llm_end里捞出来累加就行。classTokenCounter(BaseCallbackHandler):def__init__(self):self.prompt_tokens0self.completion_tokens0self.call_count0defon_llm_end(self,response,**kwargs):usage(response.llm_outputor{}).get(token_usage,{})self.prompt_tokensusage.get(prompt_tokens,0)self.completion_tokensusage.get(completion_tokens,0)self.call_count1defreport(self):totalself.prompt_tokensself.completion_tokensprint(f模型调用{self.call_count}次总token{total})print(f输入{self.prompt_tokens}输出{self.completion_tokens})用法和前面一样塞进callbacks里。跑完链调用report方法一次执行花了多少token清清楚楚。这个数对成本控制特别要紧链一长token烧得快哪一步最费钱定位到了优化才有方向。有些模型不返回token_usage这种情况下计数器读到的是0接入前先确认你用的模型支持。集成LangSmith自己写回调够用但要做认真的监控还是得上专业工具。LangSmith是LangChain官方的可观测平台和LangChain原生集成配置几行就能用。最简单的方式是设环境变量。importos os.environ[LANGCHAIN_TRACING_V2]trueos.environ[LANGCHAIN_API_KEY]ls__你的keyos.environ[LANGCHAIN_PROJECT]my-agent设完之后所有LangChain调用会自动上报到LangSmith业务代码一行不用改。打开网页端每次执行都是一条完整的trace树状结构展开每一步的输入输出、耗时、token、错误信息全都有。嵌套调用画成调用树哪层调了哪层一目了然。我第一次接入LangSmith的时候挺惊讶之前自己写回调只能看个大概LangSmith直接把调用关系可视化线上问题排查从大海捞针变成顺藤摸瓜。生产环境监控建议回调和LangSmith用熟了生产环境还有几点要叮嘱。第一别在生产环境开verbose打印。verboseTrue会把所有中间步骤打到控制台调试方便生产环境里日志量爆炸还可能泄露用户输入。用LangSmith这种结构化追踪代替裸打印。第二回调里别做重活。回调跑在主流程线程里里面写文件、调网络会拖慢整条链。需要持久化的话扔到队列里异步处理。第三错误回调一定要接。on_tool_error特别容易漏工具抛异常默认会往上冒但你可能想在异常时返回默认值让流程继续。在on_tool_error里记日志配合AgentExecutor的handle_tool_error参数做兜底。第四监控要带上下游标识。多用户并发时每次调用带一个user_id或session_idLangSmith里按这个过滤定位某个用户的问题才快。这篇聊了回调与监控从为什么需要回调到BaseCallbackHandler怎么用几个核心回调方法的职责打印执行日志、统计token、集成LangSmith最后给了几条生产环境建议。Agent跑起来不再是黑盒每一步都看得见。下一篇聊流式输出看看怎么让模型一个字一个字往外吐回调里的on_llm_new_token正好派上用场。