问题所在
你的Agent一直在逍遥法外。
在第1到第4模块期间,你们一直在执行短任务。读一个文件,找到TODO,列出目录。五步,最多十步。上下文窗口保持舒适,Agent 保持敏锐,一切都很顺利。
这部分我们要转身看看真实任务中会发生什么。二十步。三十。那种Agent读取三个文件,搜索代码库两次,运行测试,修复一个问题,再运行测试,然后写摘要的任务。
在解决问题之前,我们需要先看到它。修正不大。看见才是难的部分。
学习成果
onStepFinish在每一步记录输入和输出token。运行多步任务使上下文增长可见:输入token线性上升,而输出大致保持平稳。
快速路径
- 给
ToolLoopAgent加onStepFinish回调 - 每步记录
usage.inputTokens和usage.outputTokens - 运行一个多步任务,看着输入数字不断上升
动手练习 5.1
接线token日志,运行一个足够显示曲线的任务。
要求:
- 把
onStepFinish: ({ usage, stepNumber }) => { ... }传给ToolLoopAgent - 使用
console.error(这样它会显示在Agent正常输出旁边,但不会出现在stdout) - 运行一个强制4+工具调用的提示词,这样曲线就能看到
- 读数字。别急着修复任何东西
实现提示:
onStepFinish每一步都跑,不仅仅是成功的。usage字段是相关的- 用
console.error来遥测。console.log和Agent的反应混在一起,变得很糟糕 - 提示词需要做真正的工作。“读package.json”只是一步。“读package.json,然后tsconfig,再读入口点,最后总结”是四。
添加日志记录
const agent = new ToolLoopAgent({
// ... existing config
onStepFinish: ({ usage, stepNumber }) => {
console.error(
`Step ${stepNumber}: ${usage.inputTokens} input, ${usage.outputTokens} output`,
);
},
});这就是全部插桩代码。每一步后都会SDK 会替你调用。
执行一项痛苦的任务
bun run index.ts . "Read package.json, then tsconfig.json, then index.ts, then summarize everything"你应该看到类似这样的内容:
Step 0: 1,200 input, 450 output
Step 1: 2,800 input, 200 output
Step 2: 4,100 input, 180 output
Step 3: 8,900 input, 350 output
Step 4: 9,200 input, 600 output具体数字会在你的项目中有所不同。形状不会变。输入token每一步都要攀爬。输出token大致保持平稳。
为什么输入token增长
每一步都会将整个消息历史发送给模型。用户提示词。系统提示词。Agent的每一次工具调用。Agent收到的每一个工具结果。
第一步的package.json在第四步仍然处于上下文中,尽管Agent已经结束了。第二步的tsconfig还在。没有什么东西会自己离开。
| 组成部分 | token | 行为 |
|---|---|---|
| 系统提示词 | ~500 | 修复了,每隔一调用 |
| 每一个工具结果 | 200到2000 | 始终保留在历史记录中 |
| 20工具后调用 | 4000到40000 | 线性累积 |
上下文窗口是20万token。一个忙碌的Agent读取大文件,通常只需30到50步就能完成。当它出现时:
- 顶部的指示被忽视了
- 模型开始忽视自己的系统提示词
- 工具选择会退化
- Agent会循环或产生幻觉
什么不行
三个诱人的非修复方案:
- **Hoping这并不happen.**在实际任务中总是这样。
- **Reducing步count.**十步对真正的工作来说太少了。五十是正常的
- **Using更大的model.**更大的上下文窗口会拖延问题,并不能解决问题。而且每只token的费用也更高
解决办法是把旧工具结果从消息历史中移除,避免它们溢出注意力。第5.2课正是做到这一点的。
**注意:遥测先修,修第二**
这课并不能解决问题。这是故意的。除非先测量问题,否则无法判断修复是否有效。token日志会保留到模块的其余部分,方便你在每一步前后进行比较。
动手试试
执行多步骤任务。看看这些数字。确认:
- 输入token随着每一步增长
- 产量token保持相对较小
- 到最后一步,你发送的token是第0步的三倍以上
bun run index.ts . "Read package.json, then tsconfig.json, then index.ts, then summarize everything"试试更长的任务,看看曲线变陡:
bun run index.ts . "Read every .ts file in src/, then tell me what each one does"npx tsc --noEmit提交
git add index.ts
git commit -m "feat(telemetry): log token usage per step"完成标准
- [ ]
onStepFinish接线用于日志和usage.inputTokens和usage.outputTokens - [ ] 一个4+步骤的任务显示输入token攀爬
- [ ] 输出token在各步间保持相对平稳
- [ ] 日志记录是
console.error的,不是console.log - [ ]
npx tsc --noEmit
**注意:绘制曲线**
记录数字给stderr没问题。把它们记录在CSV上更好。在onStepFinish中将step,inputTokens,outputTokens附加到文件中,然后加载到你选择的电子表格中。曲线的形状告诉你任务是读取量大(陡峭爬升)还是计算量大(较缓和爬升)。Agent一样,曲线不同,取决于它在做什么。
参考实现
const agent = new ToolLoopAgent({
model: "anthropic/claude-haiku-4-5",
instructions: buildSystemPrompt({
workingDirectory: cwd,
sandboxType: sandbox.type,
toolNames: Object.keys(tools),
projectContext,
}),
tools,
stopWhen: stepCountIs(15),
onStepFinish: ({ usage, stepNumber }) => {
console.error(
`Step ${stepNumber}: ${usage.inputTokens} input, ${usage.outputTokens} output`,
);
},
});