跳转到内容

用智能体调试失败的测试

本指南介绍如何让智能体在打印语句解释不了的失败上使用调试器。

debug 工具驱动一次真正的 Debug Adapter Protocol 会话。一个被暂停的栈帧能一次回答关于那一刻的所有问题——每个局部变量、每个调用者,以及在该栈帧自身作用域里求值的表达式——而打印语句只能回答你在运行之前就想到要问的那个问题。

该工具会一直留在工具面上,这样智能体可以询问为什么什么都不可用,而不是靠猜。只有当下面两者同时成立时,适配器才能真正启动:

  • 检出目录中存在项目标记,并且
  • 适配器的二进制文件可被解析到。

让智能体检查一下:

debug(op: "adapters")

它会回答自己发现了什么,或者解释适配器需要同时满足这两半。

语言 适配器 标记 安装
Dart / Flutter dart debug_adapter pubspec.yaml 随 SDK 提供——无需额外操作
Python python3 -m debugpy.adapter pyproject.toml, setup.py, requirements.txt pip install debugpy
Rust / C / C++ lldb-dap Cargo.toml, CMakeLists.txt 随 LLVM 提供
Go dlv dap go.mod go install github.com/go-delve/delve/cmd/dlv@latest

顺序很重要,工具也会强制这个顺序:断点可以在会话存在之前设置,因为协议要求的就是这个次序。

这样对智能体说:

bin/main.dart 里的 compute 函数返回了错误的值。在给 product 赋值的那一行设置一个断点,在调试器下启动它,然后告诉我 a 和 b 实际上是什么。

它大致会执行:

debug(op: "breakpoints", file: "bin/main.dart", lines: [7])
debug(op: "launch", program: "bin/main.dart")

启动会报告它停在了哪里:

Launched bin/main.dart on dart (session dart-1). Stopped: breakpoint. Use `stack` to see where.
debug(op: "stack")
→ #1 compute (bin/main.dart:7:3)
#2 main (bin/main.dart:2:18)

然后走进这个栈帧:

debug(op: "scopes", frame_id: 1)
→ Locals (reference 12)
debug(op: "variables", reference: 12)
→ a (int) = 6
b (int) = 7
product (int) = 42

带有子项的对象会报告 [expand: reference N]——但仅当它背后确实有东西时才报告,因此智能体用不上的展开永远不会被提供。

对于任何不是以局部变量形式可见的内容:

debug(op: "evaluate", expression: "config.timeout.inSeconds", frame_id: 1)
你想要什么 操作
运行到下一个停止点 continue
下一行,同一栈帧 step_over
进入调用 step_in
返回到调用者 step_out
停止正在运行的程序 pause

针对的是一次卡死的进程,而不是你正在启动的那个:

debug(op: "attach", port: 5858)
debug(op: "terminate")

务必结束会话。调试适配器拥有一个被暂停的进程,它占着那个进程所占有的一切——一个端口、一把锁、一个数据库连接。

对深层对象图做一次 variables 展开,本质上就是无界的——一个 Flutter 组件就能触达整棵元素树。每个作用域的子项上限是 50,值的上限是 400 个字符,栈帧的上限是 30。一个塞满上下文窗口的工具结果,就是一个会终结它本来在帮忙的那次运行的工具。