一个 Agent 任务,到底发了多少次请求
文字稿约 2300 字 · 按画面章节整理展开收起
一句话的任务,模型被请求了几次?
上一期,一次工具调用,模型被请求了两次。这次,换一个真要干活的任务。跟编程助手说一句:“测试没通过,帮我修好。”它自己跑测试、读代码、改代码,再跑一遍测试,修好了。就这一句话,模型被请求了 5 次;输入的 token 加起来,一共 8314 个。为什么要请求这么多次?这么多 token,又是从哪来的?
今天,我们把这个任务从头到尾回放一遍,一次一次地数。
打个比方:顾问又接了一件事
先接着上期的比方:模型,就像一位关在房间里的顾问,懂得多,可出不了门,干活全靠门外的助手。这次,你交给他一件要好几步才能办完的事。他先写一张纸条:帮我跑一下测试。助手跑完,把结果递回去。他看了结果,又写了两张:把这两个文件拿给我看看。看完文件,再写两张:这两处,各这样改。改完了,他还要再跑一遍测试。测试通过,他才写出回答:修好了。别忘了,这位顾问记性特别差,转头就忘。所以助手每次递进去的,都是从头到现在的整摞纸,一摞比一摞厚。换成大模型:递进去一次,就是请求一次模型;那一摞纸,就是全部的对话历史。
把这个过程画成一个圈,就是 Agent 循环,英文叫 Agent Loop。能像这样自己决定下一步、调用工具,一直干到完成的程序,就叫 Agent。第一步,客户端把全部对话发给模型;第二步,模型回复:要么写纸条,要么直接回答。是哪一种?看回复里的 stop_reason。如果是 tool_use,说明还有纸条:客户端执行工具,把结果接在对话后面,再发一次。如果变成 end_turn,说明模型说完了,循环就结束了。所以,转几圈,不是客户端事先定好的,而是模型每一步自己决定的。
实测:一个很小的项目,埋了两个错
来看实测。项目很小:一个算差旅补贴的文件,加一个测试文件。我们事先在代码里,埋了两个错。第一个:餐补,出发和返回那两天,本该各按半天算,代码却按整天算了;第二个:两人合住一间,本该报一点五倍,代码写成了两倍。我们给模型三个工具:运行测试,读文件,还有改文件:把一段文字,换成另一段。
回放:一次一次地数
第 1 次请求,模型先写了一张纸条:run_tests,运行测试。结果:3 个测试,2 个没通过。报错写着:应该是 1800,算出来 2400;应该是 200,算出来 300。第 2 次请求,这一回,它一次写了两张纸条:把两个文件都读一遍。几张纸条,客户端挨个执行完,结果一起送回去,省掉一来一回。第 3 次请求,它又写了两张,改两处:餐补,改成天数减 1;合住,从乘 2 改成乘 1.5。第 4 次请求,改完了,它要求再跑一遍测试,这次全部通过。第 5 次请求,它不再写纸条,直接写出报告:改了哪两处,还提醒了一个没处理的情况:当天往返。stop_reason 是 end_turn,循环停了。一共 5 次请求,执行了 6 次工具。等模型回复,一共 20.8 秒;执行工具,一共 0.1 秒。
每次请求的输入:像滚雪球
再看每次请求的输入,有多少 token。第 1 次,628 个:系统提示词、工具定义和调用说明,加上你那句话。第 2 次,1176 个;第 3 次,1760 个;第 4 次,2295 个;第 5 次,2455 个。浅色这一截,上一次都已经发过了;深色的才是新增的:上一次的回复,连同思考,加上工具送回的结果。顾问记性差,每次都得从头递,前面的内容,就被一遍遍重新发送。5 次加起来,8314 个,是最后一次的 3.4 倍。圈数越多,这个倍数越大。模型的输出,5 次加起来是 858 个:数量少得多,但单价是输入的 5 倍。其中 228 个,是第 3 次改代码之前的思考:看不见,但也算在输出里,照样计费。之后每次请求,这段思考还会作为输入再发一遍,再算一次输入。
重复发送的部分,正好用得上缓存按官方规则推算
一遍遍重发的内容,正好用得上缓存。缓存怎么命中,第四期实测过;这里按官方规则,推算一下。在 Claude Sonnet 5.5 上,写缓存,按原价的 1.25 倍算;读缓存,只要原价的 0.05 倍。按规则:第 1 次,缓存是空的,全部写进去;之后每次,上一次发过的,从缓存读;新增的,写进缓存。前提是两次请求之间,不超过 5 分钟;这一遍,最长只隔了 6 秒。算一下输入这部分的账:不用缓存,按原价算是 100%;按规则推算,用上缓存,大约是 40%。实际命中了多少,要看每次返回里的 cache_read_input_tokens。
请求几次,看模型怎么拆步骤
同一个任务,我们一共跑了 5 遍:每一遍都是 5 次请求,步骤一模一样。不一样的,是想多少、写多少:第 3 次的回复,从 399 到 541 个 token 不等。再换个设置:让模型每次回复,只许写一张纸条。跑了 3 遍,请求变成了 7、6、6 次,输入也跟着多了。读文件、改文件,只能一张一张来,一来一回就多了。所以,请求几次,要看模型怎么拆步骤,也要看客户端怎么设置。
停不下来怎么办:客户端设上限
万一模型一直写纸条,停不下来呢?客户端得设一个上限。我们实测时,上限是 20 次。现在把上限改成 3 次,再跑一遍。第 3 次回复里,模型已经写好了两处修改,可客户端到了上限:不执行,也不再请求。任务停在半路,测试还是 2 个没通过。Anthropic 官方 SDK 自带的循环,叫 Tool Runner,也有类似的上限,参数叫 max_iterations。所以,这个循环最常见的两个出口:模型说完了,或者撞上客户端的上限。
三句话
总结三句话。第一,一个 Agent 任务,是一串请求,不是一次:模型决定下一步,客户端执行工具,再请求,直到模型说完。第二,每次请求都带上全部历史,输入像滚雪球,加起来是最后一次的好几倍;重复的部分,要靠缓存。第三,什么时候停,通常由模型决定,客户端设上限兜底;请求几次,要看模型怎么拆步骤,也要看客户端怎么设置。
看一个 Agent 任务的用量,先问四件事
下次看一个 Agent 任务的用量,先问四件事:一共请求了几次?每次的输入多少,加起来又是多少?缓存真的命中了多少?看返回里的 cache_read_input_tokens。客户端的上限,设的是几次?
今天,我们把一个 Agent 任务,一次一次地数清楚了。下一期聊:对话越来越长,上下文窗口满了,会怎样?