面试知识库

16. 5 个 STAR 故事#

本文由本地速背站(docs/review-site)第 19 章转出,文中「第 N 章」指速背站章节号,不是本站编号。

本章 3 句话

  • 五个故事:价格币种错 7 倍、前缀缓存命中率崩盘、只留一个 Agent、一条问题从 83 秒降到 40 秒、测试绿但线上不生效。
  • 每个故事按 STAR 讲:当时什么情况、我要解决什么、我做了什么、结果的数字是多少。
  • 五个故事的共同点:先拿到数字再动手,能删就不加,靠代码机制不靠提示词。

「讲一个你解决过的难题」,怎么开口?#

L1 口诀:情况、任务、动作、结果,结果带数字

先说结果的数字,再倒回去讲当时的情况、我做了什么、为什么这么做。

我会先用一句话报结果,比如「同一条问题从 83 秒降到 40 秒」。然后回头讲当时的情况和我要解决的问题。接着讲我具体做了什么,重点讲为什么选这个做法、还有哪个做法被我否掉了。最后回到数字,说清怎么量出来的。整段控制在 90 秒,面试官想深挖再展开。

展开:五个故事一张表、每个故事讲什么

五个故事索引#

故事一句话结果本章哪张卡
币种错 7 倍删一条矛盾规则,三遍全对c02、c07
前缀缓存崩盘命中率 31.6% 升到 67.5%c03、c08
只留一个 Agent440 / 60 / 0,删掉子 Agentc04、c09
延迟 83 到 40 秒四项改动,9 轮变 5 轮c05、c10
静默失效迁移后查出 4 个 + 1 个c06、c11

怎么选故事#

面试官问「最难的问题」,选前缀缓存或静默失效,这两个排查过程最长。问「做过什么决策」,选只留一个 Agent。问「怎么做性能优化」,选延迟那条。问「踩过什么坑」,选币种错 7 倍。

STAR 各段的时间分配#

情况和任务加起来 20 秒,动作 50 秒,结果 20 秒。动作段一定要说「否掉了哪个做法、为什么」,这是面试官判断你有没有真做过的地方。

出处:五个故事的提交正文分别是 80c698c、5a27c27、2242ef5 + cccdf1a、eb9d61b、ae4073e + 74dc2a5

追问链:面试官接着会问什么

这些数字你自己量的吗?

是。每个故事改之前先跑一遍拿基线,改完同样的输入再跑一遍。数字都在提交正文里,改动和数字是一起提交的。

有没有改了之后变差的?

有。币种那条第一版修法是加规则「价格一律用美元」,结果模型把用户的预算 300 元也说成 300 美元,错换了个地方。后来改成删规则才对。

这些是你一个人做的吗?

这个项目是我一个人做的,排查、改动、测量都是我。参考过 Anthropic 公开的购物 Agent 示例和一本 18 章的教学文档。

故事一:清单里的价格币种错了 7 倍,怎么修的?#

L2 口诀:删一条矛盾规则,不加新规则

提示词一边要模型算价格区间、一边说别写价格;删掉算区间那条,三遍全对。

线上清单出过「¥19.54 到 ¥37.99」,但 19.54 这个数在候选商品里根本不存在。查下来是收尾工具的提示词自己打架:一条要求模型讲「价格落在什么区间」,另一条说价格系统会自动带上、你不要写。模型只好跨商品取最大最小,还把美元当成人民币,差了 7 倍。修法是把要区间那条删掉。同样的输入跑三遍全对。

展开:90 秒完整版、根因、修法取舍、出处

90 秒完整版(S / T / A / R)#

情况:购物清单最后一步由一个收尾工具排版,文案由模型写。线上出现价格写成人民币、数字不在候选里的情况。同一份输入跑两遍,一遍写「38 美元」,一遍写「到手约 ¥26」。

任务:让清单里的每个价格都来自工具返回,不许模型自己算。

动作:先读提示词,发现两条规则互斥。一条要「价格落在什么区间」,这需要模型跨候选取最小最大做运算;另一条写「价格系统按商品 id 自动带上,你不用也不要写」。我先试了加规则「价格一律用美元符号」,结果模型把用户说的「预算 300」也写成「预算 300 美元」,用户的预算是人民币,还是错 7 倍。于是改成删掉要区间那条。

结果:删后同样输入三遍全对。价格数字的唯一出口改成商品卡,由代码用工具返回的数字直接渲染。

为什么是删不是加#

提示词自相矛盾时,再叠一条规则只是让模型换个地方犯错。矛盾删掉,模型没有理由去算价格,错误就没有来源。

现在价格从哪来#

商品卡上的价格由 _card_reason 用工具返回的 landed_usd 或 price_usd 直接格式化,模型写的文案不含价格数字。

出处:提交 80c698c(2026-09-07)正文、prompt/prompts.yml:243(注释记录了这条被删规则和反面教训)、app/tools/shopping_summary.py:215(_card_reason)、:230(价格取值)

追问链:面试官接着会问什么

为什么美元会被写成人民币?

到手价字段是美元,但用户的预算是人民币。模型算区间时把两个币种混在一起,没有换算,所以差了大约 7 倍。

怎么保证以后不再出现?

两层。第一,提示词里明确写价格由系统带上、模型不要写。第二,商品卡价格由代码渲染,数字直接来自工具返回,模型碰不到。

这个问题是怎么发现的?

线上用户看到的清单里出现了候选里没有的数字。我拿同一份输入复跑两遍,两遍写法不同,确认是模型在自己算。

提示词自相矛盾,为什么之前没发现?

两条规则相隔几十行,是不同时间加的。加「价格区间」那条时没回头看后面那条。教训是改提示词要通读整段,不能只加一句。

故事二:前缀缓存命中率崩到 31.6%,怎么查的?#

L2 口诀:提醒只进当轮不进历史,每轮书签作废

临时提醒只拼进当轮请求、没写进对话历史,下一轮前缀和上一轮对不上;写进历史后 31.6% 升到 67.5%。

前缀缓存就是模型服务商把请求开头相同的部分记住,下次不用重算。评测里有四条问题的命中率掉到 31.6% 到 64.1%,而且卡在系统提示词那一段,说明后面的对话历史一次都没命中。查下来是一类临时提醒,比如「你跑偏了」「该收尾了」,只拼进当轮请求、没写进对话历史。下一轮请求的开头就和上一轮不一样,缓存每轮都作废。修法是这类提醒用完就写进历史。复跑那条问题,命中率从 31.6% 升到 67.5%。

展开:90 秒完整版、根因、修法、出处

90 秒完整版(S / T / A / R)#

情况:主环就是项目里唯一的那个 Agent 循环。它每轮都把系统提示词和全部对话历史发给模型。前缀缓存要求这次请求的开头和上次逐字一样,所以正常情况下命中率应该逐轮上升。评测批次里有四条问题的命中率反而卡在 31.6% 到 64.1%,只有系统提示词那 2048 个 token 命中。

任务:找出为什么对话历史一次都没命中。

动作:把每轮真正发出去的请求打出来逐轮比对。发现套在模型外面的检查代码会在某些轮插一条临时提醒,比如「你在重复搜索」「必须调收尾工具」。这条提醒只拼进当轮请求,没有写进对话历史。下一轮请求里它不见了,第 N+1 轮的开头就不再是第 N 轮的延伸,缓存断掉。修法是提醒消费之后随模型回复一起写进历史。同类还有两处:催收尾时重发的首答,和一条每轮重复注入的预算提示,改成只在换档那一次注一次。

结果:那条最差的问题复跑,8 次搜索加 3 次跑偏提醒的病态路径,命中率从 31.6% 升到 67.5%。注入那一轮付一次重算,之后逐轮递增。

一个容易说错的点#

命中率高不是 cache_control 标记的功劳。本项目用的服务商是隐式前缀缓存,带标记和不带标记的对照组第二次都命中。标记继续打是因为零成本,真正起作用的是「请求开头逐字稳定」。

出处:提交 5a27c27(2026-07-16)正文、app/harness/adapter.py:74(_persist_injections)、:77(注释「不落 state 的代价是缓存塌方」)、app/harness/formatter.py:9(标记不是命中率的原因)

追问链:面试官接着会问什么

为什么不直接不插这些提醒?

提醒本身有用,跑偏和不收尾是 Agent 最常见的失败。问题不在插,在插了不留痕。写进历史之后它既起作用又不破坏前缀。

写进历史不会让上下文越来越长吗?

会长一点,但提醒只在触发时插,一次会话最多几条。每轮重复注的那条预算提示我改成只注一次,就是为了不攒一摞。

怎么防止以后再有人犯同样的错?

有一个基线脚本跑固定请求并输出命中率,改注入逻辑后可以对比。它要调真实模型,没放进每次提交都跑的测试。

前缀缓存和 KV cache 是一回事吗?

前缀缓存是服务商把前缀的 KV cache 存下来复用。对调用方来说,能控制的只有请求开头是否逐字一致。第 07 章有展开。

故事三:为什么把两个子 Agent 都删了?#

L2 口诀:单环三数:440 / 60 / 0

数据显示子 Agent 基本没被用上,所以删掉了;并行改成同一轮发多条工具调用。

这是个看数据做的架构决定。项目当时是一个主 Agent 带两个子 Agent,一个管检索、一个管交易。我统计了线上会话:交易子 Agent 在 440 个会话里一次都没被派到。检索子 Agent 被派了 60 次,但每次派出去只搜一次就返回,等于多了一层没有价值的转发。所以我把派发机制整个删了。跨平台检索改成主 Agent 在同一轮里发 3 到 5 条搜索,框架并发执行。删完用真模型验收 4 遍,派发出现 0 次,测试 1292 个全过。

展开:90 秒完整版、删了什么、留了什么、出处

90 秒完整版(S / T / A / R)#

情况:架构改过两次。第一版子 Agent 是主 Agent 的完整克隆,第二版改成主从:主 Agent 分配,检索子 Agent 和交易子 Agent 各管一类工具。第二版跑了一段时间,我想确认它值不值。

任务:用线上数据判断子 Agent 有没有被用上,再决定留还是删。

动作:统计会话记录。交易子 Agent 经历 440 个会话,派发 0 次,因为下单和取消本来就只出一张确认卡,让用户点按钮走 HTTP 接口,主 Agent 自己做也不碰安全边界。检索子 Agent 派发 60 次,每次只调一次搜索工具就回来,定点调查 0 次、真实用户 0 次。派发的价值在于子任务要跑多步或者中间结果太大,这两条都不满足。于是分两步删:先删交易子 Agent,再删整条派发机制,包括派发工具、派发预算、子 Agent 的提示词段。权限文件保留,因为写工具仍要精准放行。

结果:跨平台和多槽位检索改成同一轮发多条 item_search。真模型验收 4 遍,每遍同轮 3 到 5 条,派发 0 次。这和 Anthropic 公开的购物 Agent 示例一致,那个示例也是一个模型一个循环。

删了什么,留了什么#

删掉保留
派发工具、子 Agent 装配权限引擎,写工具精准放行
派发预算、子搜上限循环检测、结果截断、检索预算
子 Agent 提示词段各阶段数据,存在 docs/milestones/

出处:提交 2242ef5(2026-09-15,删交易子 Agent)、cccdf1a(2026-09-16,删派发机制)、2592361(合并)、app/agent/agents.py:8(模块说明)、CLAUDE.md §2.2

追问链:面试官接着会问什么

60 次派发都只搜一次,是不是提示词没写好?

可能有一部分原因。但就算提示词写好了,我们的子任务本身就是「搜一次」,多一层转发不会让它变成多步任务。

删掉之后,安全边界靠什么?

靠三样代码机制:工具标好是不是只读,权限引擎只给点名的写工具开门,下单只出确认卡、用户点按钮走 HTTP 才真下单。这三样删子 Agent 前后没变。

前两版算白做了吗?

不算。第一版暴露了权限只靠提示词的问题,第二版拆了读写。这次能下决心删,依据就是第二版跑出来的数据。

什么情况下你会改回多 Agent?

子任务自己要跑很多步,或者中间结果很大不适合放进主上下文。比如让一个子 Agent 读几十个网页做研究。

故事四:同一条问题从 83.2 秒降到 39.7 秒,做了什么?#

L2 口诀:关思考 27.5→2.8,收尾不复述

四项都是删东西:planner 关思考、收尾不复述、比价合一步、提前提示下一步。

先拆到每一轮,最大的单点在 planner 工具里。它内部调一次模型,开着思考要 27.5 秒,关掉降到 2.8 秒,结果一样。第二项,收尾工具执行完直接结束,模型不再念一遍清单,省 7.5 秒。第三项比价合成一步,第四项提前提示下一步。合起来 83.2 秒到 39.7 秒,9 轮变 5 轮。

展开:90 秒完整版、耗时拆解表、四项改动现状、出处

90 秒完整版(S / T / A / R)#

情况:前两轮提速已经把每轮多余的 token 压掉了,比如候选商品不再整段重复输出。剩下的每个 token 看起来都有用,但一条验收问题还要 83.2 秒。

任务:在看起来没有浪费的流程上再找出能删的东西。

动作:先做两份数据。一份是全量事件时间线,每个事件带时间戳;一份是逐轮 token 复盘,每轮的输入、输出、思考、缓存命中四个数。两份对上之后,83.2 秒的构成很清楚,见下表。然后四项改动全是删东西:planner 换成同模型关思考的档位,改前先做对照实验,两档解析产出一致;收尾工具的返回本来就是给用户看的完整清单,模型再念一遍只多一句开场白,所以执行层看到收尾产物就直接结束;比价和运费两个工具都是零秒的纯计算,拆成两步却要模型中间多解码一轮,合成一步;模型曾在候选够了之后又发搜索、被规则拒绝,白烧一轮,所以把下一步能调什么提前告诉它。

结果:83.2 秒降到 39.7 秒,主循环 9 轮变 5 轮。690 个测试全过。

83.2 秒的构成#

耗时块时间说明
planner 内部模型调用27.5 秒思考占输出 70%
主循环 9 轮解码约 42 秒思考占输出 45%
收尾工具内部模型8.3 秒给 10 件写理由
全部真实工具不到 2.5 秒不是瓶颈

四项改动现在的状态#

前三项都还在。第四项依赖的四阶段状态机在 2026-09-21 删掉了,现在的做法是搜索成功后自动跑比价和精挑,模型不用自己决定下一步。

出处:提交 eb9d61b(2026-07-13)正文、docs/milestones/Mperf.2 归因表、app/tools/planner.py:597(get_planner_llm)、app/harness/adapter.py:162(收尾直接结束)、app/tools/price_compare.py:8(合并原因)、app/harness/hooks/progress.py:13(状态机已删)

追问链:面试官接着会问什么

关掉思考模式,质量不会掉吗?

planner 是结构化抽取,输出受 schema 约束。对照实验两档的预算、品类、排除词一致。风险是只测了一条问题,所以还有评测种子集做回归。

为什么最大的耗时藏在工具里没人发现?

事件时间线上工具就是一个「开始、结束」,看不到里面调了模型。教训是工具耗时要拆成工具逻辑和工具内模型调用两部分。

收尾不让模型复述,用户体验有变化吗?

有。用户看到的从模型转述变成收尾工具原文,语气少一层人味。好处是聊天回复、下载的清单文件、商品卡三处文案从此一定一致。

三轮提速的数字能连起来说吗?

不能。三轮用的测试问题不同,基线也不同。第 09 章有三轮的分表。

故事五:测试全绿,线上却有 4 个功能没生效,怎么查的?#

L2 口诀:绿的是测试,不是线上;4 + 1

换框架后测试全过,但预置 planner、入参校验计熔断等 4 处线上没生效;后来又查出结构化输出只回存根。

静默失效指的是代码不报错、测试也绿,但线上的行为不是你以为的那样。项目从 LangChain 迁到 AgentScope 时,两套运行时并存了一段。摘掉旧的那步,我逐个功能对照,查出 4 处在新框架侧没生效:开局预跑 planner 根本没实现,每轮多一次没意义的模型往返;工具入参校验失败被算成工具故障,触发熔断;还有派发预算指向已删的工具名、用量统计遇到畸形数据抛异常。之后又查出第 5 个:模型被强制指定函数时只回一张全默认值的存根,schema 校验照样通过。

展开:90 秒完整版、五处失效一张表、出处

90 秒完整版(S / T / A / R)#

情况:运行时从 LangChain 换成 AgentScope 2.0,中间有个开关让两套并存。测试都绿,线上也在跑,但有些行为对不上:比如每轮开头多一次模型调用,图搜功能整条不通。

任务:摘掉 LangChain,同时把新框架侧「看起来在跑、其实没生效」的功能全找出来。

动作:不是删旧代码就完,而是每个功能在两套运行时里对照。开局预跑 planner 在新框架从没实现,补进适配器。工具入参校验失败原本是模型传错参数,却被计进进程级共享的熔断器,补上校验异常豁免。派发预算指着两个已经删掉的工具名,改成按次数限制。用量统计拿到畸形数据会把异常甩回主循环,改成记日志跳过。测试从旧适配器迁到新适配器,不是删掉,31 个检查代码的用例改用新的会话对象驱动。两天后又查出第 5 个:某个模型在被强制指定函数时只回一张存根,全默认值的 schema 校验照样过,线上多数轮次拿着空表在跑。改成先用 auto 模式,再加一道空表检查。

结果:LangChain 依赖全部移除,1060 个测试全过。五处失效全部有对应测试。

五处失效一张表#

失效点线上表现修法
开局预跑 planner 没实现每轮多一次模型往返补进适配器
入参校验失败计熔断模型传错参数就熔断工具校验异常豁免
派发预算指向已删工具预算完全不生效改按次数限
畸形用量抛异常主循环被打断记日志跳过
强制函数只回存根拿空表继续跑auto 优先 + 空表检查

派发预算那条随派发机制在 2026-09-16 一起删了,现在不存在。

出处:提交 ae4073e(2026-09-06)正文、74dc2a5、app/harness/prefill.py:1(开局预跑)、app/harness/adapter.py:488(校验异常不计熔断)、app/agent/invoke.py:148(call_structured)、:162(存根问题的记录)

追问链:面试官接着会问什么

为什么测试没抓到?

测试是按旧运行时写的,新运行时那侧根本没有对应用例。所以这次的原则是测试迁过去,不是删掉。

存根那个问题,怎么发现的?

planner 拆出来的字段太单一,每条问题都只有一个默认任务。回头看原始返回,发现模型被强制指定函数时只回了最短的合法 JSON。

空表检查怎么判「空」?

不按值比较,按字段有没有出现过。显式传 null 是合法结果,按值比较会把它误判成空表,每条无预算的问题都会多采样一次。

迁移前做了什么准备?

先做了 4 个小实验钉死工具返回形态和缓存标记能不能传到请求里,确认框架能承接再动手。

追问币种故事:加规则那版为什么会失败?#

L3 口诀:规则套错对象:预算 300 变 300 美元

「价格一律用美元」被模型套到了用户预算上,人民币 300 写成 300 美元,还是差 7 倍。

第一版修法是加一句「价格一律用美元符号」。实测模型把这条规则套到了用户的预算上,用户说预算 300 元人民币,清单里写成预算 300 美元。错误没消失,只是换了个地方。原因是提示词里两条互斥规则还在,加第三条只是给模型更多可以选错的组合。所以最后是删掉要区间那条。删了之后模型没有任何理由去写价格,错误没有来源。

展开:两版修法对比、判断标准、出处

两版修法对比#

加规则版删规则版
改动加「价格一律用 $」删「讲价格区间」
矛盾还在吗在不在
实测预算币种错三遍全对

判断标准#

改提示词时我会问一句:这个改动是让问题不发生,还是让问题测不出来?加规则属于后者,它只覆盖我测到的那个例子。删矛盾属于前者。

出处:提交 80c698c 正文第二段、prompt/prompts.yml:248(注释里的反面教训)

追问链:面试官接着会问什么

如果非要在文案里提价格怎么办?

让工具把带币种的价格字符串算好传给模型,模型只能原样引用。数字从代码来,模型不做运算。

这类提示词矛盾怎么系统性地发现?

目前靠评测:同一输入跑多遍,输出不稳定就说明模型在两条规则之间摇摆。没有自动化的矛盾检测。

为什么不用更强的模型?

矛盾的提示词换什么模型都是赌。而且强模型更贵更慢,收尾这步是延迟大头。

追问缓存故事:变的内容挪出系统提示词就够了吗?#

L3 口诀:越会变的越往后放,放前面等于没挪

不够。每轮会变的偏好如果放在历史前部,照样让后面全部历史失效;必须放到最后一条消息。

前缀缓存要求从第一个变化的字符开始,后面全部作废。早期系统提示词中间夹着每轮都变的用户偏好和近期行为,它们一变,后面 2600 多个 token 的固定指令加全部对话历史都作废。直觉是把它们挪到系统提示词后面的第一条消息,但那是错的,它们还在历史前部,后面照样失效。正确做法是放到最后一条用户消息里,那里本来就在缓存断点之后。现在的做法是 planner 跑完后以系统消息追加偏好,一次任务内不变。

展开:三个位置对比、现状、出处

三个位置对比#

放哪后果
系统提示词中间后面固定指令 + 全部历史作废
历史第一条消息全部历史作废,等于没挪
最后一条消息只有它自己不命中

现状#

现在偏好由检查代码在 planner 跑完后作为系统消息追加,不碰系统提示词前缀。它在一次任务内不变,所以任务内缓存不受影响。formatter.py:76 的注释写「系统提示词纯静态、无运行时注入」已经过时,以 context_shaping.py:211 为准。

出处:docs/milestones/M6.1 第 2、3 节、app/harness/hooks/context_shaping.py:103(偏好注入钩子)、:211(追加系统提示词块)

追问链:面试官接着会问什么

命中率怎么量?

模型返回的用量里有 cached_tokens 字段,除以输入 token 就是命中率。请求不报错不代表缓存生效,必须看这个数字。

服务商不支持显式标记怎么办?

本项目的服务商就是隐式缓存,不看标记,只看前缀是否逐字一致。所以真正要做的是让前缀稳定,标记只是顺手打。

历史越来越长,缓存还有用吗?

有。历史只在尾部增长,前面不变,命中的部分逐轮变多。超过阈值由框架做压缩,压缩后前缀会变一次。

追问单环故事:怎么确认「0 派发」不是统计错了?#

L3 口诀:第一次对照作废:24 轮零派发,两组同一份字节

第一次对照实验就作废过一回,两组跑的是同一份配置;重跑时换了问题类型,并且子 Agent 的调用只能用探针看见。

我在这件事上踩过一次坑。第一次做克隆版和主从版的对照,六条问题跑了 24 轮,派发全是 0,两组的结果一模一样。查下来是默认配置只启用了 amazon 一个平台,跨平台问题根本不会触发派发,两组跑的其实是同一份字节,数据作废。第二次换成「按品类并列」的问题重跑。还有一个细节:子 Agent 的工具调用不回传主循环,从主线程的记录里永远数不到,必须在工具调用前的探针里计数。所以后来的 440 和 60 是探针数出来的,不是从聊天记录里翻的。

展开:对照实验采集什么、两次实验的区别、出处

对照实验采集六列#

评测分数、总耗时、模型调用次数、成本、子 Agent 误调写工具次数、派发次数。六条问题串行跑,因为并发会被排队噪声淹没。

两次实验的区别#

第一次第二次
问题类型跨平台按品类并列
启用平台只有 amazon同上,但不依赖平台
结果24 轮零派发,作废有派发,可比

出处:提交 09edc3b(2026-09-06)正文、docs/handbook/01「走过的弯路」表、对照脚本已归档到 scripts/archive/

追问链:面试官接着会问什么

只启用 amazon,跨平台功能还有意义吗?

召回库里 amazon 占绝大多数,这是数据限制,第 18 章有讲。同轮多发搜索现在主要用在套装的多个槽位上,不只是多平台。

440 个会话里有多少是真实用户?

提交正文没有拆分,只写了检索子 Agent 那 60 次派发里真实用户 0 次。这个数字面试时要说清是混合数据。

为什么不直接看日志数派发?

子 Agent 的消息不回传主循环,主线程日志里看不到它调了什么工具。只有在工具调用前挂探针才数得到。

追问延迟故事:83.2 秒是怎么拆到每一轮的?#

L3 口诀:两份数据对上:时间线 + 逐轮 token

一份带时间戳的全量事件时间线,一份每轮输入、输出、思考、缓存四个数的复盘,两份对上才能定位。

只看总耗时改性能等于猜。我做了两份数据。第一份是全量事件时间线,把每个事件都带上单调时间戳。第二份是逐轮 token 复盘,跑完从保存的消息历史里取每轮的输入、输出、思考、缓存命中四个数。时间戳告诉我哪一轮慢,token 告诉我为什么慢。两份对上之后发现,最大的单点是 planner 里那次 27.5 秒的模型调用,时间线上它只是一个比较慢的工具。还发现有两轮不产生任何决策:一轮是模型撞规则被拒,一轮是收尾后复述清单。

展开:两轮浪费的细节、机制的成本、出处

两轮不产生决策的浪费#

  • 撞规则轮:候选够了之后模型又发两条搜索,被规则拒绝。规则是对的,但模型花了 3.1 秒解码去撞墙,下一轮思考飙到 498 个 token 在消化被拒。

  • 复述轮:收尾工具的返回就是完整清单,模型又花 729 个 token、7.5 秒把它重新排版一遍,还有抄错数字的风险。

机制正确不等于机制免费#

检查代码拒绝得对,但模型撞上去的那轮解码是实打实的延迟。所以后来把信息提前给模型,检查从「拦截」退成「保险丝」,正常路径碰不到它。

出处:docs/milestones/Mperf.2「归因」与「结论」两节、提交 eb9d61b 正文

追问链:面试官接着会问什么

96% 耗时在模型解码,这个数怎么来的?

上一轮延迟审计量的。全部真实工具加起来不到 2.5 秒,其余都是模型在生成 token。所以优化方向是减 token 和减轮数,不是优化工具。

为什么不换更快的模型或服务商?

这轮零新增基础设施,没加缓存层、没加并行框架、没换供应商。每一项都是把不产生价值的 token 拿掉,没有新东西要维护。

现在线上怎么看这些数据?

接了 Langfuse 看每次调用的耗时和 token,工具耗时超阈值有告警。第 09 章有讲。

追问静默失效:以后怎么防「测试绿、线上没生效」?#

L3 口诀:测试迁不删,空表看字段不看值

三条:换框架时测试迁过去不删掉;结构化输出按字段有没有出现判空;工具入参错算模型的错、不算工具故障。

我从这件事上留下三条做法。第一,换框架时测试是迁过去,不是删掉。旧适配器上的 31 个检查代码用例改用新会话对象驱动,四组核心流程重写,每个功能在新框架都有对应用例。第二,结构化输出的空表检查不按值比较,按字段有没有出现过。显式传 null 是合法结果,按值比会误判。第三,熔断器只对真正的工具故障计数,模型传错参数导致的校验失败豁免,否则模型犯一次错就把工具熔断了。

展开:空表检查的三个坑、熔断计数的判据、出处

空表检查踩过的坑#

  • 按值比较:显式 null 会被当成缺席,每条无预算的问题都多采样一次。

  • 用 model_fields_set 判:存根里凭空多出默认字段名,检查永远不触发。

  • 强制指定函数:模型只回最短合法 JSON,全默认值校验照过。所以先 auto,再检查。

熔断计数的唯一判据#

工具异常里只有非校验类的才算失败。校验失败是调用方的问题,熔断器是进程级共享的,计进去会让一个模型错误影响所有会话。

出处:app/agent/invoke.py:111(空表异常说明)、:131、:138(两个坑的记录)、:162(第四个坑)、app/harness/adapter.py:488(校验异常豁免)、提交 ae4073e 正文「测试从旧适配器迁到新适配器」

追问链:面试官接着会问什么

为什么不用集成测试直接跑真模型?

真模型的测试贵而且不稳定,没放进每次提交都跑的那批。有一个评测种子集在改动大的时候跑,属于回归。

连续两次空表之后怎么办?

抛一个专门的异常,调用方按自己的方式降级。解析失败和空表是两种问题,一个是这次请求坏了,一个是模型在这条提示词上退化了,日志要分开记。

迁移时怎么保证行为不变?

先做 4 个小实验,钉死工具返回形态和缓存标记能不能进请求。再让测试在两套适配器上都跑。

五个故事有什么共同点?#

L3 口诀:先量、再删、靠代码

先拿到数字再动手,能删就不加,边界靠代码机制不靠提示词。

五个故事的做法是一样的。第一,先量再改:延迟先拆到每一轮,子 Agent 先数派发次数,缓存先看命中率数字。第二,能删就不加:币种删矛盾规则,子 Agent 整个删掉,延迟四项全是删东西。第三,靠代码不靠提示词:价格由代码渲染,写工具靠权限引擎,注入靠写进历史。面试官如果问「你的工程方法是什么」,我就说这三条。

展开:三条各对应哪些故事、反例

三条对应表#

做法故事数字
先量再改延迟、单环、缓存83.2 秒拆表、440 / 60 / 0、31.6%
能删就不加币种、单环、延迟三遍全对、0 派发、9 轮变 5 轮
靠代码机制币种、静默失效价格代码渲染、空表检查

反例也要说#

币种加规则那版、第一次对照实验作废、缓存标记被误以为是命中率的原因,这三个都是先做错再改对的。讲出来比只讲成功可信。

出处:本章 c02 到 c11 各卡的提交正文

追问链:面试官接着会问什么

「能删就不加」会不会删过头?

会。所以删之前要有数据,删之后要有验收。子 Agent 删完用真模型跑 4 遍,延迟改完 690 个测试全过。

这些方法在团队里怎么推?

把数字写进提交正文。改动和数字一起提交,别人看历史就知道为什么改、改了多少。

哪个故事你觉得最有价值?

静默失效。它说明测试绿不等于功能在跑,这个意识比任何单个修复都值钱。

合上页面自测 3 题#

  1. 五个故事各自的结果数字是什么?
  2. 币种错 7 倍,第一版修法为什么被否掉?
  3. 「静默失效」指的是什么现象?怎么查出来的?