Agent调试、错误处理与成本优化

21 阅读5分钟

调试、错误处理与成本优化

系列第 6 篇 · 前置:第 1 篇第 2 篇第 3 篇第 4 篇第 5 篇

Workflow 跑起来不难,难的是跑出来的结果不对、或者跑得太贵。这篇讲实际踩坑中总结的调试方法和省钱技巧。


一、Agent 不按预期行事

这是最常见的问题:你让它列大纲,它写了篇全文;你让它返回 JSON,它给你加了段说明文字。

1. Prompt 不够具体

"帮我审查一下代码"和"逐行检查以下代码,每条问题输出:文件:行号 - 问题描述 - 严重程度(致命/一般/建议),没问题输出'通过'",效果天差地别。

改法:给格式、给枚举值、给示例。参考第 2 篇讲的 Prompt 写法。

2. 输出格式不对

让 Agent 返回结构化数据,它总爱加"好的,这是结果:"这种前缀。

改法:用 schema 参数。传了 schema 之后,系统通过 constrained decoding 保证输出合法 JSON,返回的直接是 JS 对象,不用 JSON.parse

const result = await agent('列出5个要点', {
  schema: {
    type: 'object',
    properties: {
      points: { type: 'array', items: { type: 'string' } }
    },
    required: ['points']
  }
})
// result.points 直接用,不用解析

3. 该触发的 Custom Agent 没触发

主 Claude 靠 description 判断什么时候派子 Agent。description 写得太模糊,它不知道什么时候该用。

改法:description 里写清楚"什么时候用",用动词开头,包含触发场景关键词。

# 不好
description: 代码审查员

# 好
description: 审查代码变更,检查Bug、安全问题和代码风格。当用户要求review代码、检查提交、或分析代码质量时使用。

4. 上下文溢出

给 Agent 塞了太多内容,它处理不了或开始丢东西。

改法:任务拆小。不要把整个项目代码丢给一个 Agent,先让它定位文件,再让它读相关文件。搜索类任务只给相关片段,不给全文。


二、Workflow 常见错误

忘了 () =>

// 错:agent 立即执行,parallel 拿到的是结果不是任务
await parallel([agent('A'), agent('B')])

// 对
await parallel([() => agent('A'), () => agent('B')])

症状:三个任务看起来"同时"跑了,但其实是串行的,或者报错。

忘了 await

// 错:result 不是结果,是个 Promise
const result = agent('搜一下')
log(result)  // [object Promise]

// 对
const result = await agent('搜一下')

meta 里写变量

// 错:meta 必须是纯字面量
export const meta = {
  name: TOPIC + '-workflow',  // 不行
  phases: [getPhases()],       // 不行
}

meta 在脚本执行前就被解析,不能有运行时的值。

phase 名字对不上

meta.phases 里的 title 必须和代码里 phase('xxx') 的字符串完全一致,否则进度面板分组不对。

Agent 返回 null 没处理

某个 Agent 失败了返回 null,直接拼字符串会出现 "null" 这几个字。

// 防空
const allMaterials = [chinese || '', english || '', github || ''].join('\n')

三、调试技巧

1. 用 phase 和 label 看清执行过程

phase('搜资料')
const result = await agent('搜中文资料', { label: '搜中文' })

进度面板会显示当前在哪个阶段、哪个 Agent 在跑、跑了多久。不写这些也能跑,但出问题时你不知道卡在哪。

2. 用 log 输出中间值

const outline = await agent('列大纲')
log('大纲内容:' + outline.slice(0, 200))  // 只打前200字

log() 会显示在进度面板上。调试时把关键中间产物打出来看,比跑完看最终结果猜哪里错了高效。

3. 返回中间产物

不要只 return 最终结果,把中间产物也带上:

return { draft, reviews, final }

跑完可以直接看到审查意见是什么、改了哪里。确认没问题后再精简返回值。

4. 先用小数据跑通

不要一上来就喂 10 个主题。先用 1 个主题跑通整个流程,确认每一步的输出符合预期,再加量。

5. 单独测试某个 Agent

Workflow 里某个 Agent 输出不对,把它的 Prompt 复制出来,在对话里单独跑,调 Prompt。比改完整脚本再跑一遍快。


四、错误处理

Agent 失败不影响其他

parallel 里一个 Agent 失败返回 null,其他的照常返回。这是特性不是 bug。但你要处理 null:

const results = await parallel(tasks)
const valid = results.filter(Boolean)

给默认值

const TOPIC = args.topic || '默认主题'
const materials = chinese || '(中文搜索无结果)'

关键步骤可以加重试逻辑

async function agentWithRetry(prompt, opts, retries = 2) {
  for (let i = 0; i <= retries; i++) {
    const result = await agent(prompt, opts)
    if (result && result.length > 10) return result
    log(`第${i + 1}次结果为空,重试...`)
  }
  return null
}

不过一般不需要,Agent 失败概率不高。关键步骤加一下就行。


五、成本优化

跑 Workflow 最容易忽视的就是 token 消耗。几个立竿见影的做法:

1. 模型分层:haiku 干粗活,sonnet/opus 干细活

任务推荐模型
搜索、分类、找问题、格式化haiku
写正文、综合分析、改稿sonnet(默认)
复杂推理、架构决策opus(很少需要)
// 搜资料用 haiku
() => agent('搜中文资料', { model: 'haiku' })

// 写初稿用默认(sonnet)
const draft = await agent('写初稿')

agent() 选项里传 model 就行,哪个 Agent 该用便宜模型就给哪个加。phases 只管进度面板显示,不控制模型。

2. 能用 JS 处理的别派 Agent

// 浪费:让Agent去重
const deduped = await agent(`把以下列表去重:${list.join('\n')}`)

// 省钱:JS去重,瞬间完成,0 token
const deduped = [...new Set(list)]

去重、过滤、排序、统计、字符串拼接,这些全用 JS。Agent 只干需要"理解"的活。

3. 只给片段,不给全文

// 浪费:把整个搜索结果塞给Agent
const summary = await agent(`总结:${fullSearchResults}`)

// 省钱:先提取相关段落
const relevant = extractRelevant(fullSearchResults, TOPIC)
const summary = await agent(`总结:${relevant}`)

4. 审查类任务用 haiku

找 Bug、查格式、检查 AI 味,这些不需要强推理。haiku 便宜好几倍,效果差别不大。只有最终综合改稿时用好模型。

5. 别让 Agent 干它不需要干的事

// 浪费:Agent搜完还写了篇总结
() => agent('搜资料并总结成一篇文章')

// 省钱:只搜,总结交给下一步
() => agent('搜资料,列出3个来源和摘要', { model: 'haiku' })

六、常见问题速查

现象原因解决
Agent 没自动触发description 写得不好写清触发场景和关键词
输出不是想要的格式Prompt 不够具体用 schema 参数
parallel 里的任务串行执行了忘了 () =>包成箭头函数
结果里出现 "null"Agent 失败返回 null|| ''
进度面板不显示阶段phase 名字和 meta 对不上检查字符串完全一致
跑起来特别贵都在用默认模型搜索/分类换 haiku
Agent 输出被截断内容太长任务拆小,只给相关片段
新 Agent 文件不生效需要重启会话重启 Claude Code
同样的输入结果不一样温度/随机性关键步骤加 schema 约束,或多跑几次取一致的

小结

调试:phase + label 看进度,log 打中间值,return 带产物
排错:先查 () => 和 await,再查 meta 字面量和 phase 名
省成本:haiku 干粗活、JS 干杂活、只给片段不给全文、能并行不串行