DuckDB 的 Agent 模式为何被 JSON 绕过
作者实测 DuckDB 2.0 CLI 在 Agent 环境且输出经管道传递时自动启用特殊输出,同一条 5000 行查询返回约 1.4KB,而指定 -json 后约为 247KB。文章分析输出格式参数如何影响 Agent 模式及返回体积。
完整中文译文
DuckDB 2.0 会识别编程代理运行的 shell,把查询结果改成更紧凑的输出。但作者实测发现,真正有用的是结果末尾的截断说明、结果哈希和提前停止机制。加上代理常用的 -json,这些保护就消失了。
这对让 Claude Code 调数据库的程序员很有参考价值:同一个 5,000 行查询,默认代理输出只有 1,385 字节,JSON 却达到 246,729 字节。格式选择会直接影响代理收到多少数据,以及它能否知道自己只看到了部分结果。
自动识别代理之后,究竟省了什么
原文讨论的是 DuckDB 2.0.0「Cinnamon Teal」,一个至少支持到 2027 年 10 月的长期支持版本。该版本还包括 Quack 客户端与服务器协议、Parquet 和 CSV 异步 I/O、VARIANT 类型、触发器及新的 PEG 解析器,作者重点测试了 CLI 的 agent mode。
自动启用需要同时满足三个条件:存在代理环境变量、标准输出不是终端、没有指定输出格式。作者的 Claude Code shell 同时带有 AI_AGENT 和 CLAUDECODE。启用后,CLI 输出紧凑 Markdown 表格、JSON 错误、大查询执行前的成本估计,以及结果页脚。-no-agent 可以关闭,.help agent 可以查看说明。
作者使用官方 duckdb-cli-osx-arm64 二进制,在 Apple M1 Max 上查询自行生成的 200 万订单、5,000 名客户数据。文件校验和匹配,并有 DuckDB Foundation 签名;机器同期负载均值在 30 到 98 之间,所以耗时只能按区间理解。
下面保留七类查询的输出字节数,用来判断压缩收益来自哪里:
query box agent mode -json
status totals 864 396 308
orders by country 687 453 307
top 10 customers 1,876 1,033 1,179
per customer, 5k rows 2,561 1,385 246,729
SUMMARIZE orders 3,200 982 1,130
EXPLAIN a join 2,932 734 2,932
a typo'd column 206 384 206
total 12,326 5,367 252,791
要看最后一行,也要看错误那一行:代理模式总量比方框表格小 56%,但错误输出反而更大,因为 JSON 错误携带了位置。统计还包含 stderr,不能只算表格。
小结果未必受益。代理模式每次调用都有 124 字节启动提示,普通 JSON 有时更省。DuckDB 团队在 TPC-H 问题上测得约 59% 的 token 降幅,但其 Claude Code 实验里,查询输出从 123.6k 降到 50.8k tokens,只约占总输入的 0.5%;原文把原因归于每轮重新读取系统提示。
查询输出更短,不等于代理总成本会同比下降。
页脚让代理知道,自己没看到全部数据
传统方框输出也会截断,把 5,000 行缩到 40 行。代理模式更进一步,把省略说明直接放进表格,并在末尾告诉代理怎样获取完整结果:
| 19 | 428 | 111142.39 |
| … 4960 rows omitted … |
| 4980 | 367 | 95714.21 |
...
first 20 and last 20 of 5000 rows (.maxbytes 0 for all), hash e1cd177a85aae05e
要看最后一行:这里只展示前后各 20 行,.maxbytes 0 可以请求全部输出,哈希则覆盖包括被省略的 4,960 行在内的完整结果。
作者测试了三种排序,哈希相同;把 200 万订单中的一笔金额改动一分钱,哈希变化;撤销后恢复。NULL 与字符串 'NULL' 也产生不同哈希。因此,代理检查修改是否影响结果时,可以先比较短字符串,避免读取两份大表。
提前停止同样实用。执行 FROM range(1_000_000_000),代理模式三次耗时为 0.05 到 0.15 秒,返回 20 行,说明至少已有 102400 行且查询提前停止;方框模式耗时 2.3 到 3.4 秒,才报告完整的 10 亿行规模。
这里必须分清:完整结果哈希与提前停止是不同情况。原文没有证明提前停止后仍能拿到完整结果指纹,不能把两种能力自动合并理解。
为什么加上 JSON,输出上限就失效了
-json 会主动关闭自动代理模式,因为文档把显式格式参数视为用户的有意选择。5,000 行客户查询因此完整输出,体积达到代理模式的 178 倍。
强制加回 -agent 也无法恢复全部保护。作者执行 FROM range(1_000_000) 时:
-agent -json输出 17,888,891 字节。-agent -csv输出 6,888,896 字节。- 单独使用
-agent只输出 229 字节。
前两种仍会在 stderr 打印启动提示和估计信息,却完整输出每一行。JSON 模式下增加 .maxrows 40 也没有作用,仍约 17.9 MB。
截断、省略标记、结果哈希和提前停止,都属于 Markdown 渲染路径。
成本估计也有边界。作者按客户连接订单表,估计提示约读取 400 万行,实际连接产生 802,046,608 对记录。查询在其机器上约一秒完成,但这个提示统计的是读取行数,无法直接代表结果规模。
我的判断
我认同作者把重点放在页脚上。对编程代理来说,明确说明数据不完整,并提供下一步操作,比单纯缩短输出更有价值。相比普通表格和完整 JSON,这种设计更适合交互探索。
但这仍是 CLI 行为测试。作者明确表示没有运行代理基准,因此不能据此断言模型会更少犯错,或一定会主动比较哈希。七类查询的体积差异有实测支撑,代理收益仍需要对照实验。
哈希适合初筛,也有明确局限:作者测到它不区分行序。如果你修改的是排序逻辑,哈希相同无法验证排序正确。
版本和输出稳定性也要单独处理。原文称,2.0 新建的数据库文件不能被旧版本读取,已有文件仍可双向读取;让代理创建文件前,应核对消费它的 1.x 服务。首日问题 #27635 还报告弃用警告携带颜色码进入 stdout,即使使用 -csv 也会发生,作者在 Mac 上复现了。脚本解析不能只凭格式参数就假定输出干净。
能拿来做什么
- 在 Claude Code 等代理的项目指令中约定:交互探索 DuckDB 时不指定输出格式,让默认代理模式处理展示,并要求代理读取截断说明。
- Node 程序确实需要 JSON 时,在 SQL 中显式添加
LIMIT,需要完整数据则设计分页或导出流程,不依赖.maxrows 40限制 JSON 输出。 - 检查查询修改时,可以先比较完整结果的页脚哈希;涉及排序时另查顺序,执行重查询前按任务设置
SET max_execution_time。原文没有给出通用超时值。 - 升级到 2.0 CLI 前,核对数据库文件的创建版本,并让解析脚本处理警告污染 stdout 的失败情况,避免直接把完整输出交给模型。