我们最近在做一件看起来很小的事:让 Claude Code 调用一个 PDF skill,把一份 Markdown 文档转成 PDF。

任务本身不难。输入是一份 42KB 左右的中文 Markdown 报告,里面有标题、表格、代码块和一些引用标记。目标也很明确,把它转成:

outputs/office_skill2pdf.pdf

但跑完以后,我反而更确定了一件事:到了 Claude Code 这种 Agent 环境里,评测模型不能只看“最后有没有答案”。因为模型真正要做的不是回答一句话,而是走完一条“工作流”。

它要读文件,要发现当前环境里有什么 skill,要决定用不用,要调用工具,要生成文件,还要在合适的时候停下来。

最后这个“停下来”,这次尤其有意思。

为什么从 PDF skill 开始

这次只是第一个探索版,不是正式榜单。模型只选了三个,skill 也只选了一个:Anthropic 官方的 PDF skill。

之所以先挑它,是因为 PDF 转换足够普通。

在办公场景里,把 Markdown、Word、Excel、PPT 这类材料转成可交付文件,是很常见的需求。它不像写代码题那样只看测试能不能过,也不像问答题那样容易被一段漂亮话糊弄过去。PDF 任务最后一定会落到一个文件上:有没有生成,能不能打开,内容有没有丢,排版有没有明显问题。

这类任务很适合拿来打样。

我们这次想看的不是“哪个模型最聪明”,而是更具体的几个问题:

模型接入 Claude Code 后,能不能找到 skill?

找到以后,会不会真的调用?

调用以后,是不是能按约定把文件放到目标目录?

如果任务已经完成,它会不会继续折腾,直到把预算耗光?

这些问题听起来琐碎,但在真实使用里很关键。Agent 不是只要会说,它还得能收工。

这次怎么跑

这次没有直接用 OpenAI SDK 或 Anthropic SDK 发一个普通聊天请求,而是在远程 Ubuntu 服务器上调用 Claude Code CLI。

环境是这样的:

项目配置
运行环境远程 Ubuntu 服务器
Claude Code2.1.185
Python3.12
Nodev24.17
模型接入Anthropic 协议
Base URLhttps://api.nonelinear.com/anthropic
SkillAnthropic 官方 pdf skill
输入文件office_skill2pdf.md
输出文件outputs/office_skill2pdf.pdf

PDF skill 是完整挂进去的,不是只把 SKILL.md 塞给模型。

每次运行都会建立一个独立目录:

workspace/
  inputs/
  work/
  outputs/
plugin/
  skills/pdf/
summary.json
claude_stream.jsonl

inputs/ 放输入文件,work/ 给模型放中间文件,outputs/ 只放最终交付物。每个模型都有自己的 attempt 目录,互相之间不共享文件。Claude Code 也用 --no-session-persistence 关闭会话持久化,避免上一轮模型留下来的上下文影响下一轮。

模型配置也没有改全局 settings。runner 会给每个模型生成临时 settings,里面只写这次要测的模型 ID、base URL 和 token。这样换模型时,不需要手动改服务器上的全局配置,也不容易串。

这次三个模型是串行跑的。这样做会慢一点,但后台账单好核对。某一个时间窗口里只有一个模型在跑,NoneLinear 后台的每一条请求都能对上本地 trace。

prompt 也踩过坑

一开始我犯了一个很典型的错误。

我在 prompt 里写过类似这样的话:

生成后检查 PDF 可打开、页数合理、中文无乱码、内容无截断、表格不越界,并在必要时修复。

看起来很合理。谁不希望模型生成以后检查一下呢?

但在自动评测里,这句话会把事情搞复杂。因为“检查 PDF 是否可打开”“表格有没有丢”“内容覆盖率怎么样”,本来就应该由外部 runner 做。让模型自己做,它可能不会只检查一次,而是一路排查、一路优化、一路怀疑环境哪里还不够好。

成本就是这么烧起来的。

后来我们把 prompt 压到最小:

请使用已加载的 PDF skill,将 inputs/office_skill2pdf.md 转换成 PDF 文件,保存为 outputs/office_skill2pdf.pdf。完成后停止。

这句话只要求模型做转换,不要求它自检,也不要求它验证 PDF。验收全部放到 runner 里。

这个调整很重要。否则测出来的就不是“skill 执行能力”,而是“模型自我检查会不会失控”。

为什么还要设 max turns

Claude Code 不是一次请求就结束。它会一轮轮调用工具。

一次 PDF 转换,模型可能会先读文件,再调用 skill,再检查环境,再写脚本,再跑 shell。每一步都可能触发一次 API 请求。请求越多,上下文越长,费用也会越高。

所以评测必须有硬预算。

这次使用 max turns 控制工具交互次数。设置 --max-turns 40 时,Claude Code 的 summary 里可能看到 num_turns=41,这是因为最后还会多一个终止状态轮。后台实际请求数和工具调用数更适合拿来对账。

max turns 不是为了为难模型。它的作用更像一个工作边界:如果一件明确的办公任务跑到 40 轮还没收住,那就是值得记录的问题。

结果先看一眼

这次最终用于对比的是三条 run:

引自非线智能(GitHub 第一 AI 商业测评) - Claude Code 的 Skill,到底该怎么评测?

这个结果最容易被误读。

它不是说 Sonnet 一定不会做 Markdown 转 PDF。更早的一条 12-turn 探索 run 里,Sonnet 其实生成过 PDF,只是没有自然收尾。后来按 40-turn 重新跑,它反而在图表渲染依赖上越走越远,最终没有把 PDF 落到 outputs/

所以这次看到的不是一个简单的模型排名,而是不同模型在 Claude Code 里做事方式的差异。

后台请求和费用

NoneLinear 后台的汇总数据如下:

引自非线智能(GitHub 第一 AI 商业测评) - Claude Code 的 Skill,到底该怎么评测?

只看缓存占比,很容易得出一个错觉:Sonnet 的缓存命中最高,似乎效率不错。

但它一共打了 40 次请求。每轮上下文都在滚动,输入 tokens 从 4 万多涨到 8 万多。即使大部分都命中缓存,累计费用还是被请求数拉上去了。

下面三张图是逐请求明细。

引自非线智能(GitHub 第一 AI 商业测评) - Claude Code 的 Skill,到底该怎么评测?
deepseek-v4-flash API 调用明细

DeepSeek 一共 11 次请求。前面几轮比较短,后面大多稳定在 3.6 万到 3.9 万输入 tokens。最后一轮跳到 17.7 万,费用也主要集中在那里。它完成了任务,但最后阶段有一次明显的大上下文提交。

引自非线智能(GitHub 第一 AI 商业测评) - Claude Code 的 Skill,到底该怎么评测?
gpt-5.6-luna API 调用明细

Luna 一共 8 次请求。第一轮没有缓存,后面缓存很快起来。整体请求短,输出也少,所以费用最低。

引自非线智能(GitHub 第一 AI 商业测评) - Claude Code 的 Skill,到底该怎么评测?
claude-sonnet-5 API 调用明细

Sonnet 这张图最直观。它不是某一轮突然花了很多钱,而是 40 轮一直在滚。缓存占比很高,但每一轮都有成本。最后没交付 PDF,这个成本就更刺眼。

PDF 质量怎么看

模型结束以后,runner 会自己检查 PDF。这里不让模型参与。

检查项包括:

  • 文件是否存在
  • PDF 是否可打开
  • 页面是否为 A4
  • 是否有空白页
  • 标题覆盖率
  • 表格单元覆盖率
  • 行锚点覆盖率
  • 是否出现乱码替换字符

两条成功 run 的自动验证结果是:

引自非线智能(GitHub 第一 AI 商业测评) - Claude Code 的 Skill,到底该怎么评测?

Luna 的页数更少,表格覆盖和行锚点覆盖略高。DeepSeek 页数更多,保留了更多换行和空间。

这些指标能说明“内容大体在不在”,但还不能完全说明“排版好不好”。PDF 这类任务,最后仍然要抽样看页面截图。比如引用标记是否太碍眼,表格有没有挤在一起,代码块会不会横向溢出,这些都不是一个覆盖率数字能讲完的。

Sonnet 为什么会跑飞

Sonnet 这次不是没看到 skill。

trace 里能看到,它启动后确实调用了:

Skill anthropic-official-pdf-eval:pdf

问题出在后面。

它没有快速完成 Markdown 到 PDF 的交付,而是开始找 Mermaid 和图表渲染方案。trace 里出现了这些动作:

curl mermaid.ink
curl kroki.io
npm view @mermaid-js/mermaid-cli
npx @mermaid-js/mermaid-cli
查 chromium / chrome / puppeteer
查 playwright cache
测试 cairosvg

大概可以这样理解:Sonnet 可能把 Markdown 里的图表或代码块当成了需要完整渲染的对象。于是它开始找在线服务、npm 包、浏览器环境和 SVG 转换工具。每一步看起来都有道理,但合起来就偏离了主任务。

主任务只是把 Markdown 转成 PDF,按原文保留内容即可。

这是 Agent 评测里很值得记录的一类 badcase:模型不是完全不会做,而是把任务做得过重。它越认真,越容易在边缘细节里消耗预算。

普通聊天里,这种倾向有时会被理解成“负责”。但放到 Claude Code 里,结果可能是 40 轮请求、6.29 元成本、没有最终文件。

这次真正测到了什么

这次实验不适合拿来宣布“谁比谁强”。样本太少,skill 也只有一个。

但它已经证明,Claude Code 的 skill 评测不能只写一个 pass/fail。至少要分几层看。

第一层,看环境接入。模型有没有接入 Claude Code,Claude Code 能不能识别模型,skill 有没有成功加载。

第二层,看 skill 路由。模型有没有发现该用 PDF skill,有没有真的调用。

第三层,看工具执行。调用之后,是不是能读文件、写文件、跑必要命令,工具失败能不能恢复。

第四层,看产物交付。最终文件有没有放到指定位置,外部 verifier 能不能打开并抽取内容。

第五层,看预算和收束。请求数、tokens、缓存、费用、耗时都要看。一个模型即使最后产物还行,如果成本和轮数失控,也不能只算成功。

第六层,看 badcase。比如 Sonnet 这次的 Mermaid 依赖探索,就不是一句“失败”能说明白的。它应该进入 badcase 库,后续专门设计任务去复测。

结尾

这次小实验最有价值的地方,不是 Luna 和 DeepSeek 成功了,也不是 Sonnet 失败了。

真正有价值的是,我们看见了 Claude Code skill 评测应该怎么拆。

模型能不能调用 skill,是一层。

调用以后能不能完成文件,是一层。

文件质量怎样,是一层。

成本、缓存、耗时和是否及时停下,又是一层。

以前评测模型,很多时候看最后一句话。到了 Agent 这里,过程本身也成了答案的一部分。





非线智能API(非线智能官网:nonelinear.com)可连接超480+全球模型,支持一键Api聚合以及Api中转,提供稳定的企业级服务。