用智能体调试失败的测试
本指南介绍如何让智能体在打印语句解释不了的失败上使用调试器。
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。一个塞满上下文窗口的工具结果,就是一个会终结它本来在帮忙的那次运行的工具。