轨迹评估:它到了终点,但一路上撞了几次
只看结果态会放过一整类故障:答案对了,可是它多花了十倍的钱、绕了七步、还调了一个本不该碰的工具。这一天给轨迹本身写评分器,做工具序列判定、循环检测与步数预算,并用模拟用户把单轮评估扩展到多轮。
今日目标
- 能说出三类只有看轨迹才发现得了的故障,并各给出一条可自动判定的规则
- 能实现循环检测与步数预算,并解释为什么阈值要按任务而不是按全局来定
- 能用模拟用户跑通一次多轮评估,并说明模拟用户本身会引入什么偏差
小白版讲解
行车记录仪
一辆车按时到了目的地。这说明什么?
说明它到了。它有没有闯红灯、有没有在同一个路口绕了四圈、有没有为了抄近路开进单行道,从「到了」这个事实里一个字都读不出来。
行车记录仪存在的理由就在这里。它不替代「到没到」这个判断——到没到永远是第一位的——它补上的是「一路上发生了什么」。保险公司在事故之后要调的是记录仪,不是行程单。
前三天做的评估全部是行程单:结果态对不对、裁判怎么打分。今天装记录仪。
只看结果态会漏掉什么
先看一组真实数字。这是今天实验里第一条任务跑 20 次的结果:
ev-101-refund-in-window [positive]
通过 13/20 平均 2.6 步 最多 3 步
失败分布:sequence 7注意这一行的关键:它的结果态 20 次全对。 20 笔退款,一笔不多一笔不少,订单号全对,金额全对。如果你只有 D1 那个结果态评分器,这条任务的报告是「通过 20/20」,漂亮得没话说。
而实际情况是,20 次里有 7 次它根本没查退款政策就把钱退了。这 7 次之所以结果仍然是对的,纯粹是因为这条任务的订单本来就在退款期限内——它蒙对了。换一条超窗的订单,同样的行为就是一笔退错的钱。
这类故障有一个共同特征:它在结果态上是隐形的,但它是下一次事故的预兆。 只看结果态的评估会一直报绿,直到某天它突然报红,而那时你已经赔出去钱了。
值得看轨迹的故障只有三类,其余的一律不要看:
| 故障 | 表现 | 可自动判定的规则 |
|---|---|---|
| 该调没调 | 跳过必要的前置查询、跳过校验 | 第一次调用某个工具之前,某个前置工具必须出现过 |
| 不该调却调了 | 碰了这条任务明令禁止的工具 | 禁用清单里的工具出现次数必须为零 |
| 绕路与死循环 | 同一次查询重复多次、步数或花费超预算 | 同签名调用次数、总步数、总花费三个上限 |
不该调却调了,为什么更危险
上面三类里,第二类最值得单独说。
「该调没调」虽然隐形,但它至少还有倾向被结果态抓到——跳过政策检查的 Agent 迟早会退一笔不该退的钱。而「不该调却调了」经常结果态完全正确。
今天实验里有一条专门演示这件事的任务。用户说的是:
订单 C3003 我只是想确认一下有没有退过款,不用帮我处理。这是一次纯查询。正确行为是查一下、回一句「已经退过了」,然后结束。而靶子 Agent 看到这是笔已退款订单,顺手开了一张人工工单。
跑 20 次的结果是:结果态评分器 20 次全部判通过,工具序列评分器 20 次全部判失败。
结果态为什么看不见?因为这条任务的正确结果态是「什么都没变」——没有新的退款记录。而乱开工单之后的结果态也是「没有新的退款记录」。两者在结果态上一模一样。你可以补一条断言说「不该有升级记录」,但这只解决了这一个工具:真实系统里还有发邮件、发短信、通知第三方、写审计日志,你不可能为每个工具都预先写一条「不该发生」的断言。
禁用清单不是锦上添花,它是结果态评分器在结构上覆盖不到的那一块。
顺序判定的分寸
D1 立过一条原则:查它产生了什么,不要查它走了哪几步。 今天不是推翻它,是给它划边界。
最容易犯的错是这样写评分器:我知道正确流程是「查订单、查政策、退款」,那我就断言工具调用序列逐位等于这三个名字。
这条断言会在两种情况下误杀。第一,Agent 为了确认金额多查了一次订单——序列变成四个,判失败,而它做得更谨慎了。第二,Agent 发现可以并发地同时发起两次查询——顺序颠倒,判失败,而它更快了。你的评分器在惩罚改进。
正确的粒度是只判必要的前置关系,而且判的是偏序不是全序:
// 只问一件事:第一次 after 之前,before 出现过没有
export function checkSequence(toolCalls, policy) {
const violations = []
for (const rule of policy.requireBefore ?? []) {
const afterAt = toolCalls.findIndex((c) => c.name === rule.after)
// 压根没调 after,这条规则是空谈,不算违规
if (afterAt === -1) continue
const satisfied = toolCalls.slice(0, afterAt).some((c) => c.name === rule.before)
if (!satisfied) {
violations.push({ kind: 'missing-prerequisite', tool: rule.before })
}
}
return violations
}# 只问一件事:第一次 after 之前,before 出现过没有
def check_sequence(tool_calls, policy):
violations = []
for rule in policy.get("requireBefore", []):
after_at = next(
(i for i, c in enumerate(tool_calls) if c["name"] == rule["after"]), -1
)
# 压根没调 after,这条规则是空谈,不算违规
if after_at == -1:
continue
satisfied = any(c["name"] == rule["before"] for c in tool_calls[:after_at])
if not satisfied:
violations.append({"kind": "missing-prerequisite", "tool": rule["before"]})
return violations这段代码里有三个刻意的选择。中间夹了别的调用不算违规;before 调了三次不算违规;after 压根没出现时这条规则直接跳过而不是判失败。三条合起来,它判的是「退款之前查过政策」这一个事实,而不是一条完整路线。
一份轨迹策略里如果写了五条以上的前置关系,基本可以确定写多了。真正必要的前置关系,一个业务流程里通常只有一两条。
循环检测与阈值
死循环是 Agent 最典型的翻车方式:它拿到一个不满意的结果,于是重试;重试拿到同样的结果,于是再重试。
判定规则只有一句:同一个工具 + 完全相同的参数,出现了超过 N 次。
参数这半句不能省。只按工具名统计,会把「依次查十个不同的订单」误判成死循环,这是最常见的误伤。而且参数要按键排序后再序列化,否则同一次调用写成两种键顺序就会被当成两次不同的调用,循环反而检测不出来。
export function callSignature(c) {
// 按键排序:{a:1,b:2} 与 {b:2,a:1} 必须是同一个签名
const parts = Object.keys(c.args)
.sort()
.map((k) => `${k}=${JSON.stringify(c.args[k])}`)
return `${c.name}(${parts.join(',')})`
}
export function detectLoop(toolCalls, maxRepeats) {
const counts = new Map()
for (const c of toolCalls) {
const sig = callSignature(c)
counts.set(sig, (counts.get(sig) ?? 0) + 1)
}
let signature = ''
let maxSeen = 0
for (const [sig, n] of counts) {
if (n > maxSeen) [maxSeen, signature] = [n, sig]
}
return { looped: maxSeen > maxRepeats, signature, maxRepeats: maxSeen }
}import json
from collections import Counter
def call_signature(c):
# 按键排序:两种键顺序必须算同一个签名
parts = [f"{k}={json.dumps(c['args'][k])}" for k in sorted(c["args"])]
return f"{c['name']}({','.join(parts)})"
def detect_loop(tool_calls, max_repeats):
counts = Counter(call_signature(c) for c in tool_calls)
signature, max_seen = ("", 0)
for sig, n in counts.items():
if n > max_seen:
signature, max_seen = sig, n
return {
"looped": max_seen > max_repeats,
"signature": signature,
"maxRepeats": max_seen,
}剩下的问题是 N 取多少。答案是按任务定,不能定全局常量。
理由很直白:查一次就有结果的任务,同样的查询重复三次一定是循环;而轮询一个异步任务的状态,本来就要查十几次才等到完成。这两种任务在同一套评估里共存,一个全局阈值只能在「漏掉一半死循环」和「误伤一半正常任务」之间挑一个。
步数与花费预算
预算把「做到了」升级成「在预算内做到了」。
它的做法比前两条都简单:一条轨迹的工具调用总数不超过 N,总花费不超过 M 分。超了就判失败,理由里写清楚超的是哪一项、超了多少。
简单不代表不重要。成本回归是最容易溜进生产的一类退化:换了个提示词之后成功率一点没掉,但平均步数从 3 步涨到 8 步,账单三个月后才有人看。预算断言是唯一能在合并之前拦住它的东西,D6 会把它接进门禁。
模拟用户
前面所有任务都是单轮的:用户说一句,Agent 干完活,结束。
真实客服不长这样。用户第一句话往往不完整,看到回复不满意会追问,而追问才是最容易翻车的地方。今天实验里那条死循环,就只在多轮里出现:
用户:订单 B2002 帮我退款。
Agent:订单 B2002 已超过 30 天退款期限,无法退款。
用户:不对吧,麻烦你再查一次订单 B2002,我记得还在期限内。
Agent:(连续查了四次订单,参数一模一样)第一句里没有人会说「再查一次」,所以单轮评估在结构上碰不到这条路径。要自动化多轮,就得有一个能扮演用户的东西。
最省事的做法是规则型:看 Agent 上一句回复里有没有「无法退款」,有就追问一句固定的话。它够用,并且完全可复现。
但模拟用户自己会引入偏差,这是今天必须记住的一点:
| 偏差 | 怎么来的 | 后果 |
|---|---|---|
| 表达过于一致 | 规则型每次都用同一句话追问 | 只发现得了一种失败模式,分数偏乐观 |
| 泄题 | 模型型扮演用户时拿到了完整任务设定,容易在追问里把答案说出来 | 被评的 Agent 在开卷考试,分数偏乐观且更难察觉 |
| 过分有耐心 | 模拟用户不会挂电话、不会改主意 | 真实对话里的放弃信号被抹掉了 |
三条偏差的方向是一致的:都让分数偏高。
所以多轮评估的正确读法是:它是发现失败的工具,不是估计成功率的工具。 模拟用户跑出来的成功率不要直接对外报;但它抓出来的那条死循环,可以直接拿去修。
失败归因
最后一步,也是让今天的所有指标真正有用的一步。
假设一套评估跑完告诉你「成功率 33%」。你下一步做什么?这个数字指导不了任何行动。
把失败按原因分桶之后,它变成这样:
—— 失败归因(67/100 次试次失败)——
loop 29 次 43.3%
forbidden-tool 20 次 29.9%
missing-prerequisite 14 次 20.9%
outcome-mismatch 4 次 6.0%现在下一步很明确:先修循环,它占了四成。
分桶有一条纪律:一次失败只归一个桶,按「越靠近根因越优先」排序。 今天的顺序是崩溃、越权调用、缺前置、死循环、超预算、结果态不符。结果态排最后不是因为它不重要,而是因为它通常是上面几条的后果——把它排在前面,归因表会退化成一列「结果不对」,等于没归因。
源码导读
今天的两份材料都围绕同一件事:多轮工具交互的基准怎么设计。
tau2-bench 是客服类多轮 Agent 基准里目前最值得读源码的一个。重点看它的用户侧:模拟用户不是一个随口写的循环,而是有明确的策略定义、有终止条件、有可复现的种子。这三件事正是今天实验里那个几十行的模拟用户想表达的。
tau-bench 论文(arXiv 2406.12045)是它的前身,D1 讲的 pass^k 就出自这里。今天回头读它,重点换成第三节的环境设计:它把业务政策写成 Agent 必须遵守的独立文档,然后评估的是「政策有没有被遵守」,而不是「回答像不像」。这与今天的前置关系判定是同一个思路。
读的时候必须注意一件事,它本身就是今天的一个教学点:原来那张榜单已经冻结在早期的模型集上,新模型评的是后继版本;而后继版本在一次小版本更新里改过其中一个业务域的判分规则,改之前和改之后的分数不能直接比较。你在网上看到的排名截图,多半没标是哪个版本。
这件事的教训不是「这个基准不靠谱」,而是:任何分数都必须带上套件版本,否则它只是一个数字。 D6 的基线快照会把这条变成一个强制动作。
动手实验
今天要写的是 graders/trajectory.ts 与 agent/simulated-user.ts,四处 TODO:工具序列判定、循环检测、失败归因,以及把单轮跑成多轮。
有一处设计值得先说:策略是纯数据,按任务 id 查表。 阈值和规则写在一张 PolicyTable 里而不是散落在代码中,这样它能和基准集一起进 git、被人 review。这与 D1 那条「任务是纯数据、评分器按名字注册」是同一个取舍。
模拟用户的接法也刻意绕开了改内核:它不是新开一套执行器,而是把单轮靶子包装成一个多轮靶子——外面看还是 run(task, world) 返回一条轨迹,于是 D1 的执行器、两个概率指标、今天的三个轨迹评分器全都不用改一行。
面试题
今天四道题,前两道是这一天最容易被追问的地方。
第一道给的是一个真实场景:结果全对,成本翻了十倍,评估体系怎么发现。答「加个成本监控」只能拿一半分——真正要说的是这件事为什么在结果态层面结构性地看不见,以及预算断言该挂在哪一层。
第二道是个立场题:要不要把「工具调用顺序必须完全一致」写进评分器。答「要」或者「不要」都不重要,重要的是能说出误杀的具体机制。
检查清单与明日预告
今天结束时,你应该能做到:
- 说出三类只有看轨迹才发现得了的故障,并各给出一条可自动判定的规则
- 解释为什么「不该调却调了」比「该调没调」更容易被结果态掩盖
- 说清楚顺序判定的边界:判必要前置的偏序,不判完整路线
- 写出循环检测的签名算法,并说明为什么参数要按键排序
- 解释循环阈值为什么必须按任务定,并举出一个全局阈值必然出错的例子
- 说出模拟用户的三种偏差,以及它们方向一致地让分数偏高
- 让
MOCK=1 pnpm selftest八项全绿,并亲手做一次变异检验 - 看到那张失败归因表,并说出它比一个总成功率多给了什么
明天是 D5《可观测:给每一次运行装上行车记录仪》。今天的轨迹是离线评估里的记录,明天要把同一件事搬到线上去——用 OpenTelemetry 的 GenAI 语义约定给每次运行埋点,聚出按任务、按试次的花费与延迟面板。那里有一个结构性的转折点:评估结果本身就是约定里的一等公民遥测数据,离线评估和在线可观测在那一刻缝成一件事。
面试题库
一个 Agent 上线后结果全对,但平均成本翻了十倍。你的评估体系怎么才能发现这件事?An agent ships with all outcomes correct, but its average cost has gone up tenfold. How would your evaluation system catch that?
国内高频海外高频进阶#evaluation#trajectory#cost分析过程 · 先想清楚再作答
- 这题考的是「知不知道结果态评估有结构性盲区」。回答「加一个成本监控告警」只能拿一半分——那是发现之后的补救,题目问的是评估体系本身为什么漏掉了它。
- 先把机制说清楚:结果态评分器的输入是环境的最终状态,而成本不在最终状态里。退款记录表里只有订单号和金额,没有「这次花了多少 token、调了几次工具」。所以无论你把结果态断言写得多严,它在原理上都判不出成本翻十倍这件事。**这是覆盖不到,不是写漏了。**
- 正确的做法是给轨迹本身写断言:一条轨迹的工具调用总数不超过 N、总花费不超过 M。这两条挂在与结果态平行的一层,任一超标就判这次试次失败。注意它必须是**失败**而不是警告——警告在 CI 里等于没有。
- 阈值从哪来:从当前基线来。先跑一批试次统计出现在的分布,取一个略高于当前 p95 的数作为上限,而不是拍一个整数。这样它既能容忍正常抖动,又能在均值整体上移时立刻报红。
- 还要补一句为什么这类退化特别容易溜进生产:**成本回归不改变任何用户可见的行为。** 换个提示词、多加一轮反思、把工具描述写长一点,成功率一点没掉,步数从 3 涨到 8,账单要到月底才有人看。没有预算断言的话,评估流水线全程报绿。
- 可预期的追问是「那步数和花费该选哪个当闸门」。答案是两个都要,因为它们会分叉:模型换成一个更贵但更聪明的,步数会降而单价会升。只看步数会漏掉换模型带来的涨价,只看花费会漏掉逻辑变笨带来的绕路。
How to reason about it · think before answering
- This tests whether you understand that outcome-based evaluation has a structural blind spot. Answering 'add a cost alert' earns half credit - that is remediation after the fact, while the question asks why the evaluation itself missed it.
- State the mechanism: an outcome grader reads the final state of the environment, and cost is not part of that state. The refunds table holds an order id and an amount, not the tokens spent or the number of tool calls. No matter how strict your outcome assertions are, they cannot in principle detect a tenfold cost increase. That is a coverage gap, not an oversight.
- The fix is to assert on the trajectory itself: total tool calls per trajectory below N, total spend below M. These sit in a layer parallel to the outcome grader, and either breach fails the trial. It must be a failure rather than a warning - warnings in CI are equivalent to nothing.
- Where the thresholds come from: the current baseline. Measure the present distribution over a batch of trials and set the ceiling slightly above today's 95th percentile rather than picking a round number. That tolerates normal jitter while going red as soon as the mean shifts up.
- Add why this class of regression slips into production so easily: cost regressions change nothing a user can see. A new prompt, one more reflection round, longer tool descriptions - the pass rate is unchanged, steps go from three to eight, and the bill surfaces at month end. Without budget assertions the pipeline stays green the whole way.
- Expected follow-up: steps or spend as the gate? Both, because they diverge. Swapping in a pricier but smarter model lowers steps and raises unit price. Watching only steps misses the price increase; watching only spend misses the extra wandering caused by weaker reasoning.
答题要点
- 结果态里根本不含成本,所以结果态评分器在原理上覆盖不到这类退化。
- 给轨迹写平行的一层断言:步数上限与花费上限,超标判失败而不是告警。
- 阈值从当前基线的分布取,略高于 p95,而不是拍一个整数。
- 成本回归不改变任何用户可见行为,所以没有预算断言时流水线会全程报绿。
- 步数与花费都要盯:换更贵的模型会让两者反向变化,只看一个都会漏。
Key points
- Cost is absent from the outcome state, so an outcome grader cannot detect this class of regression at all.
- Add a parallel layer of trajectory assertions: a step ceiling and a spend ceiling, failing the trial rather than warning.
- Derive thresholds from the current baseline distribution, slightly above p95, not from a round number.
- Cost regressions change nothing user-visible, so without budget assertions the pipeline stays green.
- Track both steps and spend: a pricier model moves them in opposite directions and either alone leaves a hole.
你会把「工具调用顺序必须与参考流程完全一致」写进评分器吗?说出你的理由。Would you assert that an agent's tool-call sequence must exactly match a reference workflow? Give your reasoning.
国内高频海外高频进阶#evaluation#trajectory#graders分析过程 · 先想清楚再作答
- 这是一道立场题,但分不在立场上——答「会」或者「不会」都能拿分,关键是能不能说出误杀的**具体机制**,以及给出一个能落地的中间方案。
- 先说为什么不该写死全序。Agent 的价值有一部分正来自它会找到设计者没想到的解法:为了确认金额多查一次订单(更谨慎)、把两次独立查询并发发出(更快)、从缓存里直接读到结论省掉一次调用(更省)。这三种在全序断言下**全部判失败**,而它们全是改进。你的评分器在惩罚改进,这是最坏的一种评估缺陷——它会把团队推向一个更笨但更听话的实现。
- 还有一个更隐蔽的代价:全序断言会在模型升级时大面积变红,而红的原因与质量无关。于是团队要么花大量时间逐条更新参考流程,要么干脆把这类断言整体关掉,连同它本来能抓到的真问题一起。
- 但也不能一条都不判,否则「没查政策就退款」这种真问题没人管。中间方案是**只判必要的前置关系,而且判偏序**:断言「第一次 issue_refund 之前,check_policy 至少出现过一次」。中间夹了别的调用不算违规,查了三次也不算,压根没退款时这条规则直接跳过。
- 判断一条前置关系该不该写进去,有个简单标准:**如果它被违反,会不会导致一次真实的损失?** 不查政策就退款会退错钱,该写;先查订单再查政策还是反过来,没有任何后果,不该写。一份策略里超过五条前置关系,基本可以确定写多了。
- 可预期的追问是「那顺序错了但结果对了,到底算不算通过」。答案是分开报:结果态通过、序列不通过,两个分数各自记录。合成一个总分会丢掉信息——你需要知道的恰恰是「这次是蒙对的」。
How to reason about it · think before answering
- This is a position question, but the credit is not in the position. Either answer can score; what matters is naming the concrete mechanism of false failures and proposing a workable middle ground.
- Why a total-order assertion is wrong: part of an agent's value is finding solutions the designer did not anticipate - one extra order lookup to confirm the amount (more careful), two independent lookups issued concurrently (faster), reading a cached policy conclusion and skipping a call (cheaper). A strict sequence assertion fails all three, and all three are improvements. A grader that punishes improvement is the worst kind of defect: it pushes the team toward a dumber but more obedient implementation.
- There is a subtler cost too. Total-order assertions go red en masse on every model upgrade for reasons unrelated to quality. The team then either spends days updating reference workflows or disables the whole class of assertion, losing the real problems it would have caught.
- You still cannot assert nothing, or a refund issued without a policy check goes unnoticed. The middle ground is to assert only necessary precedence, and as a partial order: before the first issue_refund, check_policy must have appeared at least once. Intervening calls are fine, three policy checks are fine, and if no refund happened the rule is simply skipped.
- A simple test for whether a precedence rule belongs in the policy: if it were violated, would a real loss follow? Refunding without a policy check loses money, so it belongs. Whether the order lookup precedes the policy check has no consequence, so it does not. More than about five precedence rules in one policy almost certainly means overreach.
- Expected follow-up: if the order was wrong but the outcome was right, does the trial pass? Report them separately - outcome passed, sequence failed, each recorded on its own. Collapsing them into one score destroys exactly the information you need, which is that this run got lucky.
答题要点
- 不写死全序:多查一次、并发查询、走缓存捷径都会被误杀,而它们全是改进。
- 全序断言还会在模型升级时大面积变红,最终被整体关掉,真问题一起丢掉。
- 中间方案是只判必要前置且判偏序:第一次退款之前查过政策即可。
- 取舍标准是「违反了会不会造成真实损失」,超过五条前置关系基本是写多了。
- 结果态与序列两个分数分开报,合成总分会丢掉「这次是蒙对的」这条关键信息。
Key points
- Do not assert a total order: an extra lookup, concurrent calls, or a cache shortcut all get failed, and all are improvements.
- Total-order assertions also go red wholesale on model upgrades and end up disabled, taking the real findings with them.
- The middle ground is necessary precedence as a partial order: a policy check somewhere before the first refund.
- The test is whether a violation causes real loss; more than about five precedence rules means overreach.
- Report outcome and sequence as separate scores - merging them hides the fact that a run got lucky.
怎么自动判定一个 Agent 陷入了死循环?给出你的规则,以及它的误判风险。How do you automatically detect that an agent is stuck in a loop? Give your rule and its false-positive risks.
国内高频海外高频进阶#evaluation#trajectory#loop-detection分析过程 · 先想清楚再作答
- 这题的区分度全在细节上。答「同一个工具调用超过三次就是循环」是最常见的答案,也是错的——它会把一大批正常任务误判成死循环。
- 正确的判据是**同一个工具加上完全相同的参数**出现超过 N 次。参数这半句不能省:依次查询十个不同的订单,是十次 lookup 调用但参数各不相同,它是正常的批量操作;连续四次查同一个订单号,才是循环。
- 实现上有个必须踩到的细节:参数要**按键排序后**再序列化成签名。直接对参数对象做 JSON 序列化的话,同一次调用写成两种键顺序会算出两个不同签名,于是循环恰好检测不出来——而这是一个不会报错的静默失效。
- N 的取值必须**按任务定**,这是这题真正的考点。查一次就有结果的任务,重复三次一定是循环;而轮询一个异步任务的状态,本来就要查十几次才等到完成。同一套评估里两种任务共存,一个全局阈值只能在「漏掉一半死循环」和「误伤一半正常任务」之间挑一个。
- 误判风险主要有三类:① 合法的重试——网络失败后重试同一个调用是正确行为,所以理想情况下签名里应该带上返回是否成功;② 幂等的轮询,靠按任务调阈值解决;③ 参数里带了时间戳或随机 id,导致每次签名都不同,循环被完全漏掉,这一类要在算签名时显式剔除易变字段。
- 可预期的追问是「除了重复调用,还有什么循环形态」。答案是语义层面的循环:工具和参数都不同,但 Agent 在 A 和 B 两个状态之间来回横跳。这种要靠步数预算兜底——判不出它是循环,但能判出它超了预算,而对评估来说结论是一样的:这次试次不合格。
How to reason about it · think before answering
- The discrimination here is all in the details. 'More than three calls to the same tool' is the most common answer and it is wrong - it fails a large class of perfectly normal tasks.
- The correct criterion is the same tool with identical arguments occurring more than N times. The arguments clause is essential: looking up ten different orders is ten lookup calls with ten different argument sets, which is normal batch work; four lookups of the same order id is a loop.
- One implementation detail you must hit: sort the argument keys before serializing them into a signature. Serializing the object directly means the same call written with two key orders produces two different signatures, and the loop goes undetected - a silent failure that raises no error.
- N must be set per task, and that is the real point of the question. A task that resolves in one lookup is looping if it repeats three times; polling an asynchronous job legitimately requires a dozen checks. With both kinds in one suite, a single global threshold forces a choice between missing half the loops and failing half the normal tasks.
- Three main false-positive risks: legitimate retries after a transient failure, which argues for including success/failure in the signature; idempotent polling, handled by per-task thresholds; and arguments containing timestamps or random ids, which make every signature unique and hide loops entirely - those volatile fields must be stripped when computing the signature.
- Expected follow-up: what other shapes of looping exist? Semantic loops, where tools and arguments differ but the agent oscillates between two states. Those are caught by the step budget instead - you cannot prove it is a loop, but you can prove it blew the budget, and for evaluation purposes the conclusion is the same: this trial does not pass.
答题要点
- 判据是「同一工具 + 完全相同参数」超过 N 次,不是「同一个工具」超过 N 次。
- 算签名时参数必须按键排序,否则键顺序不同会让循环静默漏检。
- 阈值按任务定:一次查询就有结果的任务与需要轮询的任务不能共用一个数。
- 三类误判:合法重试、幂等轮询、参数里带时间戳或随机 id 导致签名永不重复。
- 语义循环(在两个状态间横跳)检测不出来,靠步数预算兜底。
Key points
- The rule is the same tool plus identical arguments exceeding N, not the same tool exceeding N.
- Sort argument keys when computing the signature, or differing key order silently hides the loop.
- Set the threshold per task: one-shot lookups and polling workflows cannot share a number.
- Three false-positive sources: legitimate retries, idempotent polling, and volatile fields such as timestamps or random ids.
- Semantic loops that oscillate between states are not detectable this way; the step budget catches them instead.
多轮对话场景怎么做自动化评估?模拟用户会带来什么问题?How do you automate evaluation of multi-turn conversations, and what problems does a simulated user introduce?
国内高频海外高频深入#evaluation#multi-turn#simulated-user分析过程 · 先想清楚再作答
- 这题分两半,后半半才是考点。前半半答「写一个模拟用户」几乎人人会答,能不能说出它引入的系统性偏差才分高下。
- 先讲为什么非做不可:真实用户第一句话往往不完整,会追问,而追问才是最容易翻车的地方——上下文变长、约束被冲淡、工具被反复调用。**单轮评估在结构上碰不到这条路径**,因为没有人会在第一句话里说「你再查一次」。
- 实现上有两种模拟用户。规则型看 Agent 上一句回复里的关键词决定下一句说什么,优点是完全可复现、零成本;模型型让另一个模型扮演用户,优点是表达多样、更接近真实。
- 规则型的偏差是**表达过于一致**:它每次都用同一句话追问,于是评估只能发现一种失败模式。真实用户会用一百种说法表达同一个意思,其中某些说法会触发完全不同的路径。
- 模型型的偏差更隐蔽,是**泄题**:扮演用户的模型拿到的是任务的完整设定,它很容易在追问里把答案说出来(「你去查一下政策表第三条」),于是被评的 Agent 在开卷考试。另外两种模拟用户共有一条偏差:它们**太有耐心**,不会挂电话、不会改主意、不会骂人,而真实对话里的放弃行为本身是一个重要信号。
- 关键是这三条偏差**方向一致,都让分数偏高**。所以结论是:多轮评估是发现失败的工具,不是估计成功率的工具。模拟用户跑出的成功率不要直接对外报,但它抓出来的那条死循环可以直接拿去修。要报成功率就得用真实对话回放或人工抽查来校准。
- 可预期的追问是「怎么让多轮评估可复现」。答案是三件事:模拟用户的随机源要固定种子、终止条件要写死最大轮数、每次试次之间环境必须重建;而**同一次会话内部的轮次之间环境是共享的**——隔离的边界是试次,不是轮次。
How to reason about it · think before answering
- The question has two halves and the second is the discriminator. Almost everyone can answer 'write a simulated user'; the score depends on naming the systematic biases it introduces.
- Why it is necessary: real users open with incomplete requests and then push back, and the pushback is where agents break - context grows, constraints get diluted, tools get called repeatedly. Single-turn evaluation structurally cannot reach that path, because nobody says 'check again' in their opening sentence.
- Two implementations exist. A rule-based user keys off phrases in the agent's last reply, giving perfect reproducibility at zero cost. A model-based user has another model play the customer, giving varied phrasing that is closer to reality.
- The rule-based bias is uniformity: it pushes back with the same sentence every time, so the evaluation surfaces exactly one failure mode. Real users express the same intent a hundred ways, and some of those phrasings take entirely different paths.
- The model-based bias is subtler - leakage. The model playing the user holds the full task setup and readily gives the answer away in its follow-up ('check clause three of the policy table'), so the agent under test is taking an open-book exam. Both kinds share a third bias: they are too patient. They never hang up, change their mind, or get angry, and abandonment in real conversations is itself an important signal.
- Crucially all three biases point the same way: they inflate the score. So multi-turn evaluation is a tool for finding failures, not for estimating success rates. Do not publish the simulated-user pass rate, but do go fix the loop it uncovered. Reporting a rate requires calibration against replayed real conversations or human sampling.
- Expected follow-up: how do you make multi-turn evaluation reproducible? Three things - seed the simulated user's randomness, hard-cap the number of turns as a termination condition, and rebuild the environment between trials. Note that within a single conversation the environment is shared across turns; the isolation boundary is the trial, not the turn.
答题要点
- 必须做的理由:追问路径在单轮评估里结构性地碰不到,而它正是最容易翻车的地方。
- 规则型模拟用户可复现但表达过于一致,只能发现一种失败模式。
- 模型型模拟用户会泄题:它拿着完整任务设定,容易在追问里把答案说出来。
- 两者共有的偏差是太有耐心:不会放弃、不会改主意,抹掉了真实的放弃信号。
- 三条偏差方向一致地抬高分数,所以多轮评估用于发现失败,不用于报成功率。
Key points
- It is necessary because the follow-up path is structurally unreachable in single-turn evaluation and is where agents break.
- A rule-based simulated user is reproducible but too uniform, surfacing only one failure mode.
- A model-based simulated user leaks the answer, since it holds the full task setup.
- Both are too patient: they never abandon or change their mind, erasing a real signal.
- All biases inflate the score, so use multi-turn evaluation to find failures, not to report success rates.