DuckDB v2.0 CLI 推出 agent mode,面向编码智能体优化输出
Agent Mode in the DuckDB CLI
DuckDB v2.0 CLI 新增 agent mode,在检测到 Claude Code、Codex、Cursor、Gemini CLI、GitHub Copilot 等编码智能体时自动切换输出方式。
TL;DR:DuckDB v2.0 CLI 有一个 agent 模式,能让 AI 编码 agent 更快、用更少的 token 得到正确结果。当 CLI 检测到 agent 时,它会打印紧凑的 Markdown 表格而不是带填充的方框,明确说明结果何时被截断,提前停止失控的查询,并以 JSON 报告错误。长查询会在开始前公布其预期成本,这样 agent 就能决定是否等待。
让我们问问 Claude
我们让 Claude 测量 agent 模式的效果。在实验中,Claude Code 在规模因子为 100 的数据集上回答了 22 个 TPC-H 问题。问题用平实的英语表述,不含任何 SQL。每个问题分别用 agent 模式和 -no-agent 各回答三次。全部 132 次运行都产生了正确答案。使用 agent 模式时,模型读取的 DuckDB 输出从 123.6k token 降至 50.8k token,减少了 59%。Claude 对结果的总结如下:
引述:Agent 模式修复了终端 shell 中那些会悄悄误导模型的问题。当我通过 DuckDB CLI 回答那 22 个 TPC-H 问题时,agent 模式把我读回的 token 减少了 60%,而且每个答案仍然正确。
– Claude Fable 5.1
节省并没有体现在总成本或运行时间上。每一轮都会重新读取 agent 的系统提示,它比查询输出大得多,因此节省的 token 约占总输入的 0.5%。agent 模式的运行还略微多用了轮次(237 对 224)。Claude 在一份报告中写下了实验设置、完整结果和后续实验。
背景
DuckDB CLI 是为坐在终端前的人构建的。它的默认渲染器会填充列以使其对齐,在结果周围绘制边框,并在列名下方打印列类型:
┌────────────┬───────┬────────┬───────────────┐
│ name │ n │ total │ avg_amount │
│ varchar │ int64 │ int128 │ decimal(10,2) │
├────────────┼───────┼────────┼───────────────┤
│ customer_0 │ 3 │ 777 │ 259.00 │
│ customer_1 │ 3 │ 888 │ 296.00 │
└────────────┴───────┴────────┴───────────────┘
对于大型结果,它会显示前 20 行和后 20 行,中间用一行点表示省略:
┌───────────────┬───────┬────────┬───────────────┐
│ name │ n │ total │ avg_amount │
│ varchar │ int64 │ int128 │ decimal(10,2) │
├───────────────┼───────┼────────┼───────────────┤
│ customer_0 │ 3 │ 777 │ 259.00 │
│ customer_1 │ 3 │ 888 │ 296.00 │
│ · │ · │ · │ · │
│ · │ · │ · │ · │
│ customer_4998 │ 4 │ 1203 │ 300.75 │
│ customer_4999 │ 2 │ 510 │ 255.00 │
├───────────────┴───────┴────────┴───────────────┤
│ 5000 rows (40 shown) 4 columns │
└────────────────────────────────────────────────┘
人看到这些点和 (40 shown) 页脚,就知道有行缺失了。
如今,许多 CLI 调用根本不是来自坐在终端前的人。Claude Code、Codex、Cursor、Gemini CLI 和 GitHub Copilot 等编码 agent 每次 shell 调用都会运行一次 duckdb -c "...",通过管道捕获 stdout,并将文本传给语言模型。这已迅速成为使用 DuckDB 的常见方式。
这类读者与人有几点不同:
- 它没有终端,因此没有滚动,也没有分页器。
- 它无法要求更多。打印出来的就是它看到的全部。
- 它为读到的每个 token 付费,包括填充和边框字符。
- 它通过对文本进行模式匹配来处理错误。
对这类读者来说,方框渲染器并不合适。对齐填充消耗 token,对模型不携带任何信息。被截断的结果看起来也很像完整结果。页脚写着 (40 shown),但模型可能略过那一行,然后从它从未看到的数据中得出结论。
Agent 模式为这类读者改变了 CLI 的默认行为。本文介绍 CLI 如何检测 agent、输出有哪些变化,以及如何试用。
Agent 模式随 DuckDB v2.0 一起发布。
检测 Agent
Agent 会用环境变量标记它们运行的命令。目前还没有统一的约定,因此 shell 会检查一系列变量:
| 变量 | 设置者 |
|---|---|
AI_AGENT、AGENT |
Claude Code、Goose、Amp |
CLAUDECODE |
Claude Code |
CODEX_CI、CODEX_SANDBOX、CODEX_THREAD_ID |
Codex |
CURSOR_AGENT |
Cursor |
GEMINI_CLI |
Gemini CLI |
COPILOT_AGENT、COPILOT_CLI、COPILOT_AGENT_SESSION_ID |
GitHub Copilot |
当以下三个条件同时成立时,Agent 模式会开启:这些变量之一被设置、stdout 不是终端,并且命令行上没有给出输出格式。如果你传入 -csv、-json 或 -markdown,说明你要求了特定格式,Agent 模式就不会介入。
有两个标志可以覆盖检测结果。-agent 强制开启该模式,-no-agent 强制关闭。这两个标志与格式标志组合使用,因此 duckdb -agent -csv 会保留 Agent 行为,只改变行的打印方式。.show 命令会报告该模式是否处于活动状态,以及检测到了哪个 agent:
agent: claude-code
并非每个 agent 都会设置变量。对于这些情况,只要没有识别出 agent 且两个标志都未给出,一次在向管道写入时失败的运行会在 stderr 上打印一行提示:
hint: -agent renders errors as JSON and results compactly for AI coding agents (duckdb -help lists all options)
一个没人告知该模式的 agent,可以在第一次出错时通过这种方式发现它。
Agent 模式下的变化
以下所有内容都是默认值。诸如 .mode、.maxrows、EXPLAIN (FORMAT ...) 或你 .duckdbrc 中的任何显式设置仍然优先。
紧凑表格
结果以 Markdown 表格形式打印,不带对齐填充,列类型放在表头单元格中:
| name:VARCHAR | n:BIGINT | total:HUGEINT | avg_amount:DECIMAL(10,2) |
|---|---|---|---|
| customer_0 | 3 | 777 | 259.00 |
| customer_1 | 3 | 888 | 296.00 |
模型会读取 | 分隔符,不需要列对齐。由于这仍是有效的 Markdown,阅读 agent 记录的人会看到渲染后的表格。在典型结果上,这种格式比框线输出小 25% 到 65%,在宽结果上节省最多。
大型结果
打印每一行可以避免静默截断,但一百万行在管道上就是 17 MB。Agent 模式会完整打印最多 1,000 行且 10,000 字节的结果。任何更大的结果都会变成一个样本:在一个显式标记行前后各取前 20 行和后 20 行,并带有一个说明发生了什么的页脚:
| 19 | customer_19 | 13.3 |
| … 4960 rows omitted … |
| 4980 | customer_30 | 86.0 |
...
first 20 and last 20 of 5000 rows (.maxbytes 0 for all), hash 7389c2f5ff3cefb6
页脚还会告诉读者如何获取其余内容。这些限制可以用 .maxrows N 和 .maxbytes N 更改,其中 -1 或 0 表示无限制。长单元格值会在 500 个字符处截断,并带有可见标记,例如 …(+4500 chars),可用 .maxcellwidth N 调整。
提前停止
紧凑表格不需要列宽,因此结果可以流式输出。一旦达到上限,剩余的行只进行计数和哈希。再经过 100,000 行后,查询会被停止,计数会作为下界报告。例如:
FROM range(1_000_000_000);
这会在 30 毫秒内返回,页脚为 first 20 of at least 102400 rows (query stopped early; .maxrows -1 for all)。
结果的哈希
页脚包含整个结果的哈希,涵盖超过上限的行。该哈希不依赖于行顺序,并能区分 NULL 和字符串 'NULL'。一个想知道结果在编辑后是否发生变化的 agent,可以比较两个页脚,而不必打印两个结果。
对于空结果、10 行或更多行的结果,以及输出被截断时,都会打印页脚。当查询被提前停止时会省略它,因为哈希无法覆盖整个结果。
以 JSON 表示错误
错误会以 JSON 形式输出到 stderr,使用现有的 errors_as_json 设置。由 shell 自身引发的错误,例如未知的点命令,会被包装在相同的结构中:
{"exception_type":"Binder","exception_message":"Referenced column \"foo\" not found in FROM clause!\nCandidate bindings: \"bar\"","error_subtype":"COLUMN_NOT_FOUND","location":"[7,3]","position":"7","name":"foo"}
shell 会去掉 candidates 字段,因为消息已经列出了候选项。如果没有这一改动,单个“no matching function”错误就重达 2.7 kB。
紧凑查询计划
EXPLAIN 默认采用新的 compact 格式,每行一个算子,按深度缩进。估算值、实际行数和耗时放在括号中,后面跟着算子的属性:
UNGROUPED_AGGREGATE (est=1) Aggregates: count_star()
HASH_JOIN (est=0) Join Type: INNER; Conditions: range = range
FILTER (est=2) Expression: (r > 2)
RANGE (est=10) Function: RANGE
FILTER (est=1) Expression: (s > 2)
RANGE (est=5) Function: RANGE
EXPLAIN ANALYZE 以一行摘要开头,例如 QUERY (time=0.0012s, read=1.2 MB)。这是常规格式,所以即使在 agent 模式之外也可以使用 EXPLAIN (FORMAT compact)。
成本估算与进度
当规划器预计某条语句要读取一百万行或更多时,它的估算值会在执行开始前输出到 stderr:
estimate: ~7501215 rows read (lineitem ~6001215, orders ~1500000), ~4 rows returned
然后 agent 可以决定是等待,还是中断并重写查询。较小的查询不会打印任何内容,因为那行只会是噪音。当长查询运行时,终端进度条会被替换为每 5 秒在 stderr 上输出的一行纯文本:
progress: 42% (elapsed 12.0s, remaining ~16.5s)
progress: done (elapsed 28.7s)
列出表
.tables 为每张表打印一行,包含近似行数和列信息:
memory.main.t1 (table, ~15 rows): a INTEGER PK, b VARCHAR
通常的方框布局通过管道很难阅读,而这样可以让 agent 在一次调用中获得 schema。
启动行
当 agent 模式通过检测开启时,shell 会在运行任何内容之前先在 stderr 上打印一行:
duckdb agent mode on: CLAUDECODE is set and stdout is not a terminal; -no-agent turns it off, .help agent explains the output
该行说明了模式为何开启以及如何关闭它。
第一个版本每次运行都会打印三行说明(634 字节)。这比大多数查询结果都大,而且模型在多次运行之间会保留上下文,所以它只需要一次说明。
更长的文本可通过 .help agent 获取。它描述了输出规则和设置,并指出了一些 agent 可能自己发现不了的功能:
SET max_execution_time = <ms>用于为查询运行时间设置上限DESCRIBE <query>用于在不运行查询的情况下获取结果列SUMMARIZE用于快速查看表或查询的概况.tables用于获取 schemaduckdb_functions()用于函数文档
如果你不想要启动行,请在 ~/.duckdbrc 中添加 .startup_text none。
试用一下
如果你使用编码 agent,则无需配置。Claude Code、Codex、Cursor、Gemini CLI 和 GitHub Copilot 等 agent 已经设置了上表中的一个环境变量,并且它们通过管道而不是终端来捕获命令输出。这是 agent 模式三个条件中的两个。
因此,只要 agent 路径上的 duckdb 二进制文件是 v2.0 或更新版本,并且 agent 没有通过诸如 -csv、-json 或 -markdown 之类的标志请求特定输出格式,该模式就会自行开启。格式标志会被视为有意选择,因此它会关闭 agent 模式:CLI 假定你想要的正是该格式,并且不会覆盖它。
要查看 agent 看到的内容,请在设置该标志的情况下通过管道运行查询:
duckdb -agent -c "SUMMARIZE FROM 'my_data.parquet'" | cat
结论
借助 agent 模式,agent 每次调用消耗的 token 更少,并且不会把部分结果当作完整结果来处理。这意味着达到正确结果所需的步骤更少。
当前行为只是首次尝试,围绕 agent 的约定仍在不断完善。如果你的 agent 未被检测到,或者输出让它困惑,请 提交 issue 或在 pull request 下评论。agent 感到困惑的记录尤其有用。
延伸阅读
- Shell:agent 模式,检测到 AI 编码 agent 时为其渲染(pull request #26167)
- Shell:添加
.mode llm的提案(Carlo Piovesan 的提案,字节预算和提前停止的基础)
一个没有数据的 DuckDB 数据库
DuckDB 团队
使用 Java 表函数导入数据
客座作者,Geertjan Wielenga,Alex Kasko
使用维度表实现更快的字符串聚合
DuckDB 团队
来源:Hacker News · duckdb.org