Agent 答案错了怎么排查?先把 LLM 和工具调用事件打出来

📅 2026/7/15 11:39:08
Agent 答案错了怎么排查?先把 LLM 和工具调用事件打出来
「Regnexe Python 实战系列」第 8 篇共 10 篇对应仓库examples/readme/08_observability.py。上一篇07. Agent 记不住上下文别再手写 history先把 session_id 设计对。摘要Agent 可观测性是排查问题的关键——一次ainvoke()背后可能涉及多次 LLM 调用、工具调用、缓存命中与 reasoning 消耗。本文介绍 regnexe-py 的事件监听机制默认模式只看 Agent 骨架工具调用链详细模式可查看 LLM 调用全过程、token 用量与缓存命中情况还提供should_handle()过滤规则查询接口。生产环境可通过继承AgentEventListener将事件路由到 JSON 日志、OpenTelemetry、SSE 前端流等目标。排查 Agent 问题最痛苦的地方普通业务代码出错看日志、看堆栈大概率能定位。Agent 不一样。最终回答错了可能是模型没选对工具可能是工具参数错了可能是工具结果没被正确使用也可能是某次 LLM 调用成本异常。如果没有事件流你看到的只有一句答案不对。regnexe-py 的解法是把 Agent 生命周期、LLM 调用、工具调用、工具结果都做成事件通过AgentEventListener统一暴露业务侧只管订阅。默认模式只看 Agent 骨架ConsoleEventListener()不带参数时LLM 内部事件被过滤掉只打印 Agent 启动、工具调用和完成agent(RegnexeAgentBuilder().with_default_model(Vendor.DEEPSEEK,deepseek-v4-flash).with_plugin(WeatherPlugin()).with_event_listener(ConsoleEventListener()).build())resultawaitagent.ainvoke(Check todays weather in Beijing. Is it good for running?,app_idreadme,user_idreader,session_id08-observability-default,)实际输出 [AGENT ▶] RegnexeAgent goal: Check todays weather in Beijing. Is it good for running? [TOOL ▶] mcp_tool:get_weather input{city: Beijing} [TOOL ■] mcp_tool:get_weather outputBeijing: sunny, 22 C, excellent air quality. [AGENT ■] statuscompleted Status : completed Output : Beijings weather today is **sunny, 22°C, with excellent air quality**. This is **ideal for running** ...这几行已经回答了排查最常见的两个问题工具有没有被调用[TOOL ▶]行说明模型确实选择了get_weather。参数和返回值是什么input{city: Beijing}和outputBeijing: sunny...一目了然。如果这两行都正确问题就出在模型对结果的处理上排查方向就变了。详细模式看 LLM 调用全过程默认模式看不到 LLM 被调用了几次、每次拿到什么消息、用了多少 token。加两个参数打开ConsoleEventListener(show_llm_eventsTrue,show_token_usageTrue)实际输出 [AGENT ▶] RegnexeAgent goal: Check todays weather in Beijing. Is it good for running? [LLM ▶] ChatOpenAI [system] 7588 chars — set show_system_promptTrue to display [human] Check todays weather in Beijing. Is it good for running? [LLM ■] ChatOpenAI tokens{input_tokens: 6041, output_tokens: 71, total_tokens: 6112, input_token_details: {cache_read: 6016}, output_token_details: {reasoning: 26}} [TOOL ▶] mcp_tool:get_weather input{city: Beijing} [TOOL ■] mcp_tool:get_weather outputBeijing: sunny, 22 C, excellent air quality. [LLM ▶] ChatOpenAI [system] 7588 chars — set show_system_promptTrue to display [human] Check todays weather in Beijing. Is it good for running? [ai→tool_call] get_weather({city: Beijing}) [tool] (tool_call_idcall_00_feosLv17JcLfgOHqse9J6319) Beijing: sunny, 22 C, excellent air quality. [LLM ■] ChatOpenAI tokens{input_tokens: 6135, output_tokens: 190, total_tokens: 6325, input_token_details: {cache_read: 6016}, output_token_details: {reasoning: 73}} → **Beijing Weather Today:** Sunny, 22°C, excellent air quality. **Verdict: Yes, its great for running.** ... [AGENT ■] statuscompleted 这段输出信息密度很高逐条解读一次ainvoke()调了两次 LLM第一次模型拿到用户问题决定调get_weather工具output_tokens: 71只输出了一个 tool call。第二次模型拿到工具返回结果组织最终答案output_tokens: 190输出完整回答。这个两次 LLM 调用的结构是 ReAct 模式的基本形态。如果你发现某次调用里 LLM 没有选工具或者消息列表里缺少了[tool]行问题就定位到了具体的哪一步。cache_read: 6016——系统提示在复用缓存第一次调用input_tokens: 6041其中cache_read: 6016说明 6016 个 token 从缓存命中。系统提示7588 字符大部分走了 prompt cache没有重复计费。第二次调用input_tokens: 6135多了工具消息但cache_read仍然是 6016系统提示继续复用。这对成本排查很有用如果cache_read一直是 0意味着系统提示没有被缓存每次都在全量计费。reasoning: 26 / 73——模型用了内部思考output_token_details.reasoning是模型在给出输出前的内部推理 tokenchain-of-thought。第一次调用 reasoning 26 token第二次 73 token。这些 token 计入成本但通常不出现在最终回答里。如果 reasoning token 异常高可能是模型在绕弯子值得检查。过滤规则可以直接查询不用实际跑一次才知道某个事件会不会被打印should_handle()可以直接检查quietConsoleEventListener()verboseConsoleEventListener(show_llm_eventsTrue)print(quiet.should_handle(AGENT_STARTED))# Trueprint(quiet.should_handle(LLM_START))# Falseprint(verbose.should_handle(LLM_START))# True输出quiet.should_handle(AGENT_STARTED) - True quiet.should_handle(LLM_START) - False verbose.should_handle(LLM_START) - TrueAGENT_STARTED两种模式都处理LLM_START默认被屏蔽只有show_llm_eventsTrue时才开放。这个接口适合在测试里断言监听器的过滤行为不需要构造实际的 Agent 调用。生产环境怎么做生产环境通常不会直接打 stdout。继承AgentEventListener或AbstractEventListener把事件路由到需要的地方目标用法JSON 结构化日志在dispatch()里序列化事件写 loggerOpenTelemetry把AGENT_STARTED / AGENT_COMPLETED映射成 spanSSE 前端流把TOOL_CALLED / TOOL_RESULT推给前端显示进度Token 成本统计提取LLM_END事件的usage字段累加审计表把完整事件序列写入数据库仓库里的examples/06_custom_event_listener.py和examples/07_streaming_api.py是贴近生产的参考实现。小结可观测性不是附加功能而是 Agent 从 Demo 到应用必须补的一层。一次ainvoke()背后可能有多次 LLM 调用、多次工具调用、缓存命中和 reasoning 消耗靠最终答案根本看不出来。通过事件流至少能回答Agent 有没有开始执行目标是什么工具选对了吗参数和返回值是什么LLM 被调了几次每次 token 用量和缓存命中情况reasoning token 是否异常两行代码的差距——默认模式看骨架详细模式看全貌ConsoleEventListener()# 只看工具调用链ConsoleEventListener(show_llm_eventsTrue,show_token_usageTrue)# 看完整 LLM 流水 上一篇07. Agent 记不住上下文别再手写 history先把 session_id 设计对 下一篇09. 停止生成只停前端后端 Agent 还在跑acancel 才是真取消 项目地址https://github.com/flower-trees/regnexe-py