返回

DuckDB CLI 推出 Agent 模式,优化 AI 代理查询

RSS DuckDB Engineering Blog AI 逐段翻译 发布 2026-10-09 08:00
DataHot 速览

DuckDB v2.0 CLI 新增 Agent 模式,检测到 AI 代理时会输出紧凑 Markdown 表、明确截断、提前停止失控查询、以 JSON 报错,并在长查询前预告成本。DuckDB 让 Claude Code 用英文回答 TPC-H scale factor 100 的 22 道题,每题分别在 agent 模式和 -no-agent 下运行 3 次,132 次全部正确。agent 模式下模型读取的 token 从 123.6k 降至 50.8k,减少 59%;但总成本和运行时间未明显节省,因每轮重读系统提示,节省约占总输入 0.5%,运行轮次略多(237 vs 224)。文章公开了实验设置、完整结果和后续实验报告。

为什么值得关注:数据从业者可了解数据库 CLI 如何为 AI 代理优化输出与错误处理,并看到含 token 收益和成本边界的可复现实验,对构建 Text-to-SQL/分析 Agent 有参考价值。

本文目录 16 节
  1. 让我们问问 Claude
  2. 背景
  3. 检测代理
  4. Agent 模式下有何变化
  5. 紧凑表格
  6. 大结果
  7. 提前停止
  8. 结果的哈希
  9. 以 JSON 输出错误
  10. 紧凑查询计划
  11. 成本估算与进度
  12. 列出表
  13. 启动行
  14. 试用一下
  15. 结论
  16. 延伸阅读

译文

AI 逐段翻译

DuckDB 团队 2026-10-09 | 11 分钟

TL;DR:DuckDB v2.0 CLI 具有 Agent 模式,能让 AI 编码代理更快、用更少的 token 得到正确结果。当 CLI 检测到代理时,它会打印紧凑的 Markdown 表格,而不是带填充的方框,明确说明结果何时被截断,提前停止失控查询,并以 JSON 报告错误。长查询会在开始前公布其预期成本,以便代理决定是否等待。

让我们问问 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

这些节省并未体现在总成本或运行时间上。每一轮都会重新读取代理的系统提示,而系统提示远大于查询输出,因此节省的 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 等编码代理每次 shell 调用运行一次 duckdb -c "...",通过管道捕获 stdout,并将文本传递给语言模型。这已迅速成为使用 DuckDB 的一种常见方式。

这种读者在几个方面与人不同:

  • 它没有终端,因此没有滚动,也没有分页器。
  • 它无法要求更多。打印出来的就是它看到的全部。
  • 它要为读取的每个 token 付费,包括填充和边框字符。
  • 它通过对文本进行模式匹配来处理错误。

对于这种读者,方框渲染器并不合适。对齐填充消耗 token,对模型而言不携带任何信息。被截断的结果看起来也很像完整结果。页脚写着 (40 shown),但模型可能略过那一行,然后根据它从未看到的数据得出结论。

Agent 模式为这种读者改变了 CLI 的默认设置。本文介绍 CLI 如何检测代理、输出有何变化以及如何尝试。

Agent 模式随 DuckDB v2.0 提供。

检测代理

代理会用环境变量标记它们运行的命令。目前对此还没有单一约定,因此 shell 会检查一个列表:

变量设置者
AI_AGENT、AGENTClaude Code、Goose、Amp
CLAUDECODEClaude Code
CODEX_CI、CODEX_SANDBOX、CODEX_THREAD_IDCodex
CURSOR_AGENTCursor
GEMINI_CLIGemini CLI
COPILOT_AGENT、COPILOT_CLI、COPILOT_AGENT_SESSION_IDGitHub Copilot

当以下三个条件同时成立时,Agent 模式会开启:设置了这些变量之一,stdout 不是终端,并且命令行上没有给出输出格式。如果你传入 -csv、-json 或 -markdown,说明你要求了特定格式,Agent 模式就不会介入。

有两个标志可以覆盖检测。-agent 强制开启该模式,-no-agent 强制关闭。这两个标志可与格式标志组合使用,因此 duckdb -agent -csv 会保留代理行为,只改变行的打印方式。.show 命令会报告该模式是否处于活动状态以及检测到了哪个代理:

agent: claude-code

并非每个代理都会设置变量。对于这些代理,只要没有识别出代理且两个标志都未给出,在写入管道时失败的运行会在 stderr 上打印一行提示:

hint: -agent renders errors as JSON and results compactly for AI coding agents (duckdb -help lists all options)

一个没人告知该模式的代理可以在第一次出错时通过这种方式发现它。

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,阅读代理转录的人会看到一张渲染后的表格。在典型结果上,这种格式比方框输出小 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 行后,查询会被停止,并将计数报告为下界。例如:

FROMrange(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 只需一次调用即可获得模式信息。

启动行

当 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 用于获取模式
  • duckdb_functions() 用于获取函数文档

如果你不想要启动行,请将 .startup_text none 添加到你的 ~/.duckdbrc 中。

试用一下

如果你使用编码 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 被搞糊涂的记录尤其有用。

延伸阅读

这篇内容对你有用吗?

反馈只用于改善内容筛选,不等同于收藏

分享这条资讯
分享海报
保存图片
iOS 也可以长按图片保存