调试 Agent 的 3 个姿势:日志、断点、可视化(第49篇-E35)

📅 2026/7/12 11:31:28
调试 Agent 的 3 个姿势:日志、断点、可视化(第49篇-E35)
系列「企业级 AI Agent 实现拆解」E35 篇Part 9 起步篇第五章。你的 Agent 不按预期走或者干脆卡住了——这篇讲怎么看清楚它在哪里、在干什么。读完这篇你会知道姿势一加日志——Agent 执行链里该记什么姿势二devops.Init——HTTP 调试服务器看每个节点的输入输出和耗时姿势三Mermaid 可视化——把 Agent 的 Graph 画出来三种姿势什么时候用哪个为什么调试 Agent 比调试普通程序难普通程序函数入参固定输出确定断点打下去就能看到状态。Agent输入是自然语言不同表述走不同路径LLM 的中间推理不可见你不知道它想了什么多节点编排某个节点慢或者输出错了要追几层才能找到流式输出边出边处理出错时已经输出了一半三个姿势分别对应这三类问题。姿势一加日志最笨但最直接的方法适合刚起步、还没用编排框架的场景。记什么至少记这三件事// 1. 每次 LLM 调用的输入和输出log.Printf([llm] input: %v,messages)resp,err:chatModel.Generate(ctx,messages)log.Printf([llm] output: %s, finish_reason: %s,resp.Content,resp.ResponseMeta.FinishReason)// 2. 工具调用log.Printf([tool] call: %s, args: %s,toolName,argsJSON)result,err:tool.Invoke(ctx,args)log.Printf([tool] result: %s,result)// 3. 循环轮次ReAct 循环最容易死循环log.Printf([react] turn%d, action%s,turn,action)Token 用量放到 INFO 级usage:resp.ResponseMeta.Usage log.Printf([token] prompt%d, completion%d, total%d,usage.PromptTokens,usage.CompletionTokens,usage.TotalTokens)不需要记的中间的字符串拼接过程你的代码逻辑不是 Agent 的决策每个 HTTP 请求的 headers太啰嗦掩盖关键信息用 Callback 统一加日志不污染主流程E31 的 Agent 是直接调agent.Stream(ctx, messages)。如果想加日志但不改主流程用 callbackimportgithub.com/cloudwego/eino/callbackstypeDebugCallbackstruct{}func(d*DebugCallback)OnStart(ctx context.Context,info*callbacks.RunInfo,input callbacks.CallbackInput)context.Context{log.Printf([%s] start: %T,info.Name,input)returnctx}func(d*DebugCallback)OnEnd(ctx context.Context,info*callbacks.RunInfo,output callbacks.CallbackOutput)context.Context{log.Printf([%s] end: %T,info.Name,output)returnctx}func(d*DebugCallback)OnError(ctx context.Context,info*callbacks.RunInfo,errerror)context.Context{log.Printf([%s] error: %v,info.Name,err)returnctx}然后把 callback 注册到 contextctxcallbacks.CtxWithHandlers(ctx,[]callbacks.Handler{DebugCallback{}})sr,err:agent.Stream(ctx,messages)这样不改 Agent 主流程所有节点的生命周期事件都会触发你的日志。姿势二eino-ext devops 交互调试器自己写日志只能看你加的点而且看不到节点之间的数据流转。eino-ext/devops提供一个本地 HTTP 调试服务器每个节点执行后推送一条 SSE 事件包含输入、输出、耗时和 Token 数。启动调试服务器importgithub.com/cloudwego/eino-ext/devopsfuncmain(){ctx:context.Background()// 启动调试 HTTP 服务器默认 127.0.0.1:52538iferr:devops.Init(ctx);err!nil{log.Fatal(err)}// 正常编译和运行你的 Agent// ...}devops.Init在全局注册一个GraphCompileCallback所有后续编译的 Graph/Chain 都会自动被捕获。在浏览器里调试访问http://127.0.0.1:52538或 Eino 官方 IDE 插件能看到所有已编译的 Graph 列表每个 Graph 的节点-边拓扑图Canvas从任意节点开始执行不用从头跑每个节点完成后推送NodeDebugState输入、输出、Token 数、耗时用命令行看 SSE 流不用浏览器# 创建调试线程curl-XPOST http://127.0.0.1:52538/eino/devops/debug/v1/graphs/{graph_id}/threads# 从指定节点开始SSE 流式返回每节点状态curl-N-XPOST http://127.0.0.1:52538/eino/devops/debug/v1/graphs/{graph_id}/threads/{tid}/stream\-HContent-Type: application/json\-d{from_node: ChatModel, input: {...}}什么时候用工具没被调用——看 ChatModel 节点输出有没有tool_calls某个节点耗时异常——每帧NodeDebugState带毫秒级耗时想从中间节点开始重试不用重跑前置节点注意devops 服务器仅用于开发环境不要在生产部署里启动它。姿势三Mermaid 可视化你的 Agent 走了什么路径用可视化直接把 Graph 画出来。importgithub.com/cloudwego/eino/devops// graph 编译前生成 Mermaid 图diagram,err:devops.Visualize(graph)iferr!nil{log.Fatal(err)}fmt.Println(diagram)输出是标准的 Mermaid 文本graph TD __start__ -- ChatTemplate ChatTemplate -- ChatModel ChatModel -- |tool_calls ! nil| ToolsNode ChatModel -- |finish_reason stop| __end__ ToolsNode -- ChatModel把这段文本粘到 mermaid.live就能看到图[start] → [ChatTemplate] → [ChatModel] ⟶(有工具调用) [ToolsNode] → [ChatModel] ⟶(stop) [end]什么时候用新接手一个别人写的 Agent不知道它的结构怀疑分支路由配错了比如工具调用应该回到 ChatModel 但实际走到了 end给团队 review 时说明 Agent 的执行流程在 HTTP handler 里暴露调试端点开发环境// 开发环境加一个 /debug/graph 端点http.HandleFunc(/debug/graph,func(w http.ResponseWriter,r*http.Request){diagram,_:devops.Visualize(graph)w.Header().Set(Content-Type,text/plain)fmt.Fprint(w,diagram)})三种姿势怎么选问题用哪个姿势Agent 没有按预期调工具姿势二devops看 ChatModel 节点输出是否有 tool_call工具返回了什么姿势二devops看 ToolsNode 节点输出某次请求 Token 用了多少姿势一日志在 OnEnd callback 里记 usage新接手 Agent不懂它的结构姿势三Mermaid先画图再读代码某个节点报错但看不到上下文姿势二devops 姿势一OnError callback分支路由配错走了错的路径姿势三Mermaid看图 姿势二确认实际路径实战一次调试全流程假设你的 Agent 问了一个天气问题却没有调用get_weather工具直接给了一个我不知道第一步启动 devops重现问题devops.Init(ctx)// 加这一行启动本地调试服务器访问http://127.0.0.1:52538选择你的 Graph创建调试线程发送问题。看 ChatModel 节点的输出{role:assistant,content:我不知道北京今天的天气}没有tool_calls字段——LLM 没有调工具。第二步看 ChatModel 的输入[{role:system,content:你是一个助手},{role:user,content:北京今天天气怎么样}]Prompt 里没有提到工具LLM 不知道有工具可用。根因工具没有绑定到 ChatModel。修复// 错误直接用 chatModelrunner:chain.AppendChatModel(chatModel)// 正确绑定工具chatModelWithTools,_:chatModel.WithTools([]*schema.ToolInfo{weatherToolInfo})runner:chain.AppendChatModel(chatModelWithTools)小结三种调试姿势日志 Callback精确记录你关心的点适合生产环境监控compose.WithDebug自动记录所有节点 I/O适合开发调试生产关掉Mermaid 可视化把 Graph 画出来适合理解结构和排查路由问题Agent 调试的思路跟传统程序一样缩小范围找到第一个行为偏离预期的节点。WithDebug是缩小范围最快的工具。下一篇 E36 会拆compose.WithDebug的源码看这个一行代码背后怎么在每个节点插入记录逻辑。代码参考eino-examples/quickstart · Eino devops/visualize