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

系列「企业级 AI Agent 实现拆解」E35 篇,Part 9 起步篇第五章。你的 Agent 不按预期走,或者干脆卡住了------这篇讲怎么看清楚它在哪里、在干什么。

读完这篇你会知道

  • 姿势一:加日志------Agent 执行链里该记什么
  • 姿势二:devops.Init------HTTP 调试服务器看每个节点的输入输出和耗时
  • 姿势三:Mermaid 可视化------把 Agent 的 Graph 画出来
  • 三种姿势什么时候用哪个

为什么调试 Agent 比调试普通程序难

普通程序:函数入参固定,输出确定,断点打下去就能看到状态。

Agent:

  • 输入是自然语言,不同表述走不同路径
  • LLM 的中间推理不可见,你不知道它"想"了什么
  • 多节点编排,某个节点慢或者输出错了,要追几层才能找到
  • 流式输出边出边处理,出错时已经输出了一半

三个姿势分别对应这三类问题。


姿势一:加日志

最笨但最直接的方法,适合刚起步、还没用编排框架的场景。

记什么

至少记这三件事:

go 复制代码
// 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 级:

go 复制代码
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)。如果想加日志但不改主流程,用 callback:

go 复制代码
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:

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

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


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

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

启动调试服务器:

go 复制代码
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 流(不用浏览器):

bash 复制代码
# 创建调试线程
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 画出来。

go 复制代码
import "github.com/cloudwego/eino/devops"

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

输出是标准的 Mermaid 文本:

lua 复制代码
graph TD
    __start__ --> ChatTemplate
    ChatTemplate --> ChatModel
    ChatModel --> |tool_calls != nil| ToolsNode
    ChatModel --> |finish_reason == stop| __end__
    ToolsNode --> ChatModel

把这段文本粘到 mermaid.live,就能看到图:

scss 复制代码
[start] → [ChatTemplate] → [ChatModel] ⟶(有工具调用) [ToolsNode] → [ChatModel]
                                          ⟶(stop)        [end]

什么时候用:

  • 新接手一个别人写的 Agent,不知道它的结构
  • 怀疑分支路由配错了(比如工具调用应该回到 ChatModel 但实际走到了 end)
  • 给团队 review 时说明 Agent 的执行流程

在 HTTP handler 里暴露调试端点(开发环境):

go 复制代码
// 开发环境加一个 /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,重现问题

go 复制代码
devops.Init(ctx)  // 加这一行,启动本地调试服务器

访问 http://127.0.0.1:52538,选择你的 Graph,创建调试线程,发送问题。

看 ChatModel 节点的输出:

json 复制代码
{"role": "assistant", "content": "我不知道北京今天的天气"}

没有 tool_calls 字段------LLM 没有调工具。

第二步:看 ChatModel 的输入

json 复制代码
[
  {"role": "system", "content": "你是一个助手"},
  {"role": "user", "content": "北京今天天气怎么样?"}
]

Prompt 里没有提到工具,LLM 不知道有工具可用。

根因: 工具没有绑定到 ChatModel。修复:

go 复制代码
// 错误:直接用 chatModel
runner := 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

相关推荐
ch8561 小时前
智能体13-RAG
agent
小林ixn2 小时前
远程 MCP 实战:让 AI 无缝调用地图、浏览器与文件系统
人工智能·agent·mcp
chanalbert2 小时前
Agent 工程化指南(一):概念与认知架构 — 智能体到底是什么、怎么思考
llm·agent
小林ixn2 小时前
AI Agent的“万能工具箱”:MCP协议实战,让工具调用不再受语言和进程束缚
人工智能·agent·mcp
一个处女座的程序猿4 小时前
Agent之Memory:claude-mem的简介、安装和使用方法、案例应用之详细攻略
agent·memory·claude-mem
To_OC4 小时前
MCP 入门实战:写一个能读本地文件的极简服务
人工智能·agent·mcp
Sirius Wu4 小时前
OpenClaw 四层 Langfuse 全链路追踪完整详解
人工智能·ai·aigc
倾颜15 小时前
AI 聊天长期记忆的向量化处理:从"关键词匹配"到"换种说法也能找到"
llm·agent·next.js
程序员佳佳16 小时前
不是接口调不通,而是链路没拆清:Dify RAG、向量引擎与 timeout 排查实战
gpt·aigc·文心一言·gpu算力