详情

首页手游攻略 调试 Agent 的三个姿势:日志、断点、可视化(第49篇-E35)

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

佚名 2026-07-13 08:51:06

读完这篇你会知道

为什么调试 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.Usagelog.Printf("[token] prompt=%d, completion=%d, total=%d",usage.PromptTokens, usage.CompletionTokens, usage.TotalTokens)

不需要记的:

  • 中间的字符串拼接过程(你的代码逻辑,不是 Agent 的决策)
  • 每个 HTTP 请求的 headers(太啰嗦,掩盖关键信息)

用 Callback 统一加日志(不污染主流程)

E31 的 Agent 是直接调 agent.Stream(ctx, messages)。如果想加日志但不改主流程,用 callback:

import "github.com/cloudwego/eino/callbacks"type DebugCallback struct{}func (d *DebugCallback) OnStart(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context {log.Printf("[%s] start: %T", info.Name, input)return ctx}func (d *DebugCallback) OnEnd(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) context.Context {log.Printf("[%s] end: %T", info.Name, output)return ctx}func (d *DebugCallback) OnError(ctx context.Context, info *callbacks.RunInfo, err error) context.Context {log.Printf("[%s] error: %v", info.Name, err)return ctx}

然后把 callback 注册到 context:

ctx = callbacks.CtxWithHandlers(ctx, []callbacks.Handler{&DebugCallback{}})sr, err := agent.Stream(ctx, messages)

这样不改 Agent 主流程,所有节点的生命周期事件都会触发你的日志。

姿势二:eino-ext devops 交互调试器

自己写日志只能看你加的点,而且看不到节点之间的数据流转。eino-ext/devops 提供一个本地 HTTP 调试服务器,每个节点执行后推送一条 SSE 事件,包含输入、输出、耗时和 Token 数。

启动调试服务器:

import "github.com/cloudwego/eino-ext/devops"func main() {ctx := context.Background()// 启动调试 HTTP 服务器(默认 127.0.0.1:52538)if err := 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 -X POST http://127.0.0.1:52538/eino/devops/debug/v1/graphs/{graph_id}/threads# 从指定节点开始,SSE 流式返回每节点状态curl -N -X POST http://127.0.0.1:52538/eino/devops/debug/v1/graphs/{graph_id}/threads/{tid}/stream -H 'Content-Type: application/json' -d '{"from_node": "ChatModel", "input": {...}}'

什么时候用:

  • 工具没被调用——看 ChatModel 节点输出有没有 tool_calls
  • 某个节点耗时异常——每帧 NodeDebugState 带毫秒级耗时
  • 想从中间节点开始重试,不用重跑前置节点

注意: devops 服务器仅用于开发环境,不要在生产部署里启动它。

姿势三:Mermaid 可视化

你的 Agent 走了什么路径?用可视化直接把 Graph 画出来。

import "github.com/cloudwego/eino/devops"// graph 编译前,生成 Mermaid 图diagram, err := devops.Visualize(graph)if err != nil {log.Fatal(err)}fmt.Println(diagram)

输出是标准的 Mermaid 文本:

graph TD__start__ --> ChatTemplateChatTemplate --> ChatModelChatModel --> |tool_calls != nil| ToolsNodeChatModel --> |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)

小结

三种调试姿势:

  1. 日志 + Callback:精确记录你关心的点,适合生产环境监控
  2. compose.WithDebug:自动记录所有节点 I/O,适合开发调试(生产关掉)
  3. Mermaid 可视化:把 Graph 画出来,适合理解结构和排查路由问题

Agent 调试的思路跟传统程序一样:缩小范围,找到第一个行为偏离预期的节点。WithDebug 是缩小范围最快的工具。

下一篇 E36 会拆 compose.WithDebug 的源码,看这个一行代码背后怎么在每个节点插入记录逻辑。

代码参考:eino-examples/quickstart · Eino devops/visualize

相关资讯
点击查看更多
游戏推荐
推荐专题
热门阅读
推荐下载