03 · 延迟优化#
简历原话:延迟优化:通过日志定位瓶颈在串行模型调用;将高频组合步骤交由框架自动执行以减少模型调用次数,商品 ID 引用替代完整信息来压缩上下文。单任务平均模型调用 9→4 次,累计输入 token 79k→22.5k,端到端耗时 28.3s→12.5s。
30 秒口述版#
一个普通的购物任务原来端到端要 28 秒。我按调用链拆时间,发现工具合计只占 2 秒多,其余全是串行的模型调用;其中「比价」「精挑」两轮模型没做任何决策,只是把上一步结果搬进下一步参数。于是我做了两件事:一是检索返回后由框架自动跑比价和精挑,结果以提示注入,模型下一步直接收尾;二是下游工具只收商品 ID,完整信息放在会话级登记表里按 ID 取,模型不用读长 JSON,也不用在参数里重写商品。单任务平均模型调用 9→4 次,累计输入 token 79k→22.5k,端到端 28.3 秒→12.5 秒。
背景与问题#
系统形态:ShoppingX 是单模型单环的 Agent,一个主模型持有全部业务工具,按 Think → Act → Observe → Reflect 循环,直到调用收尾工具结束。开局前框架先跑一次意图解析(planner),把预算、品类、排除项等结构化约束写进会话状态。
原来一条普通单品任务的调用链(以「通勤背包,能装 16 寸笔记本,防泼水,预算 400 以内」为代表):
- 意图解析:1 次模型调用
- 主循环第 1 轮:模型决定查品类知识(爆款、典型属性)
- 主循环第 2 轮:模型发起跨平台检索(同轮多发,框架并发)
- 主循环第 3 轮:模型调比价工具,把候选折算成到手价
- 主循环第 4 轮:模型调精挑工具,按偏好选出 3 件
- 主循环第 5 轮:模型调收尾工具,工具内部再调一次模型写文案和逐件理由
- 另有每轮之间的轨迹漂移检查(小模型),平均 2 次
合计平均 9 次模型调用,全部串行。
用户感知到的现象:
- 发出问题后,前端事件流里能看到一段段「思考中」,每段 2~3 秒,中间夹着几十毫秒就结束的工具事件。
- 同样的需求,我对照过一个结构更简单的参考实现,一轮只有 3 次模型调用、12~15 秒出结果,我们要 28 秒。
- 长会话里越往后越慢,因为每一轮都把前面所有工具返回原样再喂一遍。
日志里看到的三个具体问题:
- 时间几乎全在模型上。一次 28.3 秒的任务里,工具逻辑(检索、精排、到手价计算)合计约 2.5 秒;主循环 5 轮解码共 13 秒左右,收尾工具内部那次模型调用 5.5 秒,意图解析 3.7 秒。瓶颈是「串行的模型调用次数 × 每次的解码量」,不是某个慢工具。
- 有两轮是纯搬运。比价和精挑两轮,模型的输出只是「调某工具,参数 = 上一步结果里的候选」。精挑需要的预算、排除词、偏好,意图解析阶段早就确定性地写进了会话状态,模型没有新信息要加。
- 上下文被商品全量 JSON 撑大。检索工具一条返回约 12k 字符,将近一半是商品链接、图片链接和空字段,模型推理根本用不上;更糟的是下游工具的参数要求传「候选列表」,模型要在输出里把几十个商品的标题、价格、链接逐字重写一遍,输出解码是最慢的部分。
做法与取舍#
1. 先量再改:按调用链拆时间#
- 怎么做:每次模型调用在 Langfuse 里有一条 generation 记录(输入/输出 token、缓存命中、耗时),前端事件流里每个工具有开始/结束时间戳,两者按会话 ID 对齐,拼出一张「谁在什么时候占了多少秒」的时间线。另外写了一个固定口径的测量脚本:固定 query、每遍新会话、3 遍取中位,输出墙钟、模型调用次数、输入/输出 token、工具序列。
- 为什么:最初的直觉是「检索和精排慢」,想加缓存。时间线一拉出来,品类知识查询 0.6 秒、比价 0.04 秒、精排 0.3 秒,加缓存几乎没收益。先量避免了在错误的地方下功夫。
- 否掉的:只看总耗时做 A/B。总耗时受模型随机性影响大(同一 query 模型有时多搜一次),必须同时看调用次数和工具序列是否一致,才能判断改动是信号还是噪声。
2. 高频组合步骤交给框架自动执行#
- 怎么做:检索工具成功返回后,框架给本轮会话打一个「已武装」标记;下一次调用模型之前(此时同轮并发的几路检索都已合流),框架自己依次跑比价和精挑,精挑按会话里已确定的约束执行,结果拼成一段「系统已自动完成比价与精挑,勿再调用,精选结果如下」的提示注入给模型。模型读到后下一步直接调收尾工具。
- 自动执行走的是和模型亲手调用同一条后处理管线:结果截断、schema 校验、进展信号、候选计数都一样,所以后面的防死循环、收尾校验等规则看到的状态与模型亲手调一致,不需要为自动步骤写特例。
- 边界:
- 套装轮(一次买多个槽位,比如「旅行三件套」)不自动,因为它要先经用户确认组成,再按槽位组合优选;
- 模型显式调了精挑工具就解除武装,不重复执行;
- 自动步骤任何异常都吞掉并解除武装,退回「模型自己调」的老路径,失效方向只会回到改前,不会更差;
- 比价、精挑两个工具本身保留,模型仍可在追问轮显式调(比如「只看亚马逊的」)。
- 为什么选这个:这两步是「输入确定、零决策」的步骤,让模型来调只是在花一整轮解码时间把数据搬过去。机制能做对的事,不交给模型。
- 否掉的方案:
- 把比价 + 精挑合并进检索工具:检索是同轮多发、按平台并发的,合并后每一路各自精挑,拿不到跨平台合流后的全局视角;而且检索工具职责会变得很重。放在「下一次模型调用前」这个时点,天然就是合流之后。
- 写成固定 Workflow:普通单品轮确实是固定序列,但追问轮、换品类、召回为空时需要模型判断,硬编码流程会丢掉 Agent 的灵活性。现在是「常见路径机制跑,异常路径模型接」。
- 靠提示词让模型一次发完三个工具:比价和精挑依赖检索结果,同一轮发不了;而且提示词约束不稳定。
3. 收尾文案由主模型在参数里写,工具只排版#
- 怎么做:收尾工具原来内部再调一次模型写开场白和逐件理由,这次调用要把整段上下文重新送一遍。现在把「总述、逐件理由、偏离意图的说明」改成收尾工具的入参,由主模型在调用时直接写;工具只按商品 ID 从登记表取回标题、到手价、图片、链接,确定性地排成商品卡。前端的逐字流式从模型写参数的增量里取,体验不变。
- 为什么:主模型读完精选结果后本来就「知道」为什么选这几件,让它顺手写出来,比再起一个模型、重新喂一遍上下文便宜得多。
- 否掉的:给收尾工具内部换更快的小模型。实测只省不到 1 秒,而且小模型写的理由和主模型的判断脱节;直接删掉这次调用更彻底。
4. 商品 ID 引用替代完整信息#
- 会话级候选登记表:检索工具把完整候选(含链接、图片、原价币种等)按商品 ID 登记到本轮会话的状态表里;比价回写到手价,精挑回写入选理由,登记表是本轮候选的唯一真相。
- 下游工具只收 ID:比价、运费、收尾、下单工具的模型可见参数只有商品 ID 列表,完整候选在工具内部按 ID 取回;精挑工具更进一步,连 ID 都不收,直接吃登记表全集。候选体参数对模型不可见,模型想传也传不了。
- 工具返回给模型的是紧凑投影:只留模型做决策要用的字段(ID、标题、美元价、评分、品类,加已填的到手价和理由),去掉链接、召回相似度、恒为 0 的字段和尚未填充的阶段字段。检索返回从约 12k 字符降到约 3k 字符,且这份返回每轮都会被重读。
- 跨轮引用:登记表只活一轮,用户下一轮说「买第 2 个」时,按 ID 回源向量库取回,顺手再登记。
- 为什么:输入侧,每次模型调用都要重读全部历史工具返回,12k 字符的返回在 4 次调用里就是 4 倍放大;输出侧,模型逐字重写几十个商品是最贵的解码。ID 同时压了两边。
- 附带收益:链接和价格不再经过模型,杜绝了模型改写链接、抄错价格的可能。
- 否掉的:
- 只删字段、仍按值传:输入能省一些,但下游工具参数仍是候选列表,输出重吐的问题还在。
- 靠上下文压缩:框架自带的压缩在上下文超过阈值时才触发,并且会生成摘要、破坏前缀缓存;我们的问题是「每一轮都背着用不上的字段」,应该从源头不放进去。
5. 几处顺手的小改#
- 品类知识预取与意图解析并发:原来主循环第 1 轮固定花一整轮去查品类知识。现在框架在开局跑意图解析的同时并发预取品类知识,结果直接放进上下文,省掉一轮模型调用。
- 工具说明瘦身:15 个工具的说明文字(模型每次都要读)从 7395 字符压到 1945 字符,只留参数语义和「何时调用」。
- 精排候选封顶 15 条:精排一批 30 条约 335 毫秒,封顶后只省 0.3 秒左右。这一刀收益小,但成本几乎为零。
- 没做的:到手价计算和精排并发。实测到手价算 240 件只要 0.04 秒,并发收益不到 0.05 秒,不值得加一层复杂度。
流程图 / 架构图#
改前 vs 改后:一次普通单品任务的时序#
sequenceDiagram
autonumber
participant U as 用户
participant F as 框架
participant M as 主模型
participant T as 工具
rect rgb(245,235,235)
Note over U,T: 改前:9 次模型调用,全部串行
U->>F: 购物需求
F->>M: 意图解析
F->>M: 第1轮
M->>T: 查品类知识
F->>M: 第2轮
M->>T: 并发检索多平台
T-->>F: 候选全量 JSON 约 12k 字符
F->>M: 第3轮
M->>T: 比价 参数里逐字重写候选
F->>M: 第4轮
M->>T: 精挑 参数里逐字重写候选
F->>M: 第5轮
M->>T: 收尾
T->>M: 工具内部再调一次模型写文案
end
rect rgb(235,245,235)
Note over U,T: 改后:4 次模型调用
U->>F: 购物需求
par 并发
F->>M: 意图解析
and
F->>T: 预取品类知识
end
F->>M: 第1轮
M->>T: 并发检索多平台
T-->>F: 紧凑投影约 3k 字符 全量进登记表
F->>T: 自动比价加精挑
F->>M: 第2轮 注入精选结果提示
M->>T: 收尾 参数只有商品ID加文案
T-->>U: 按ID取回全量 排成商品卡
end
改后的 4 次 = 意图解析 1 次 + 主循环 2 轮 + 轨迹漂移检查 1 次(收尾轮跳过)。
自动比价精挑的武装与执行#
flowchart TD
A["检索工具成功返回"] --> B{"本轮是套装轮?"}
B -- 是 --> Z["不武装 由模型走套装流程"]
B -- 否 --> C["会话打上已武装标记"]
C --> D["同轮其余检索并发返回 合流"]
D --> E["下一次调用模型之前"]
E --> F{"已武装 且登记表有候选?"}
F -- 否 --> M["照常调用模型"]
F -- 是 --> G["解除武装"]
G --> H["比价 按ID折算到手价 回写登记表"]
H --> I["精挑 按会话约束选3件 回写理由"]
I --> J["结果过同一条后处理管线"]
J --> K["注入提示: 已自动完成 勿再调用"]
K --> M
H -. 任何异常 .-> X["吞掉异常 退回模型自己调"]
I -. 任何异常 .-> X
X --> M
N["模型显式调了精挑工具"] --> G2["解除武装 不重复执行"]
商品 ID 引用的数据流#
flowchart LR
S["检索工具"] -- 全量候选 --> R[("本轮候选登记表<br/>按商品ID索引")]
S -- 紧凑投影 ID 标题 价格 评分 --> CTX["模型上下文"]
CTX -- 只写商品ID加理由 --> SUM["收尾工具"]
R -- 按ID取回 链接 图片 到手价 --> SUM
PC["比价"] -- 回写到手价 --> R
PK["精挑"] -- 回写入选理由 --> R
SUM --> CARD["商品卡 发给前端"]
Q[("向量库")] -. 跨轮按ID回源 .-> R
数字怎么来的#
测量口径#
- 测试集:普通单品轮的固定 query,每条跑 3 遍,每遍用全新会话 ID(复用会变成追问轮,数字不真实),墙钟取中位后再对 query 求平均。「通勤背包,16 寸笔记本,防泼水,预算 400 以内」是序列最稳定的一条,也是逐步对比时的主参照;套装轮不在这组数字里。
- 模型调用次数:从每个任务的 token 记账树里数,包括意图解析、主循环每一轮、工具内部的模型调用、漂移检查小模型,不只数主循环。
- 累计输入 token:一次任务内所有模型调用的输入 token 之和(含命中缓存的部分),来自模型返回的用量字段。它反映的是「整个任务一共让模型读了多少」,每次调用重读历史上下文都会累加进去。
- 端到端耗时:从服务端收到任务到发出最终结果事件的墙钟时间,不含回合结束后在后台跑的长期记忆抽取(用户感知不到)。
- 工具序列一致性:3 遍的工具名序列完全相同才算稳定;不一致的批次数字只作参考,不当作信号。
逐步对照(主参照 query,3 遍中位)#
| 阶段 | 端到端耗时 | 模型调用 | 累计输入 token | 说明 |
|---|---|---|---|---|
| 改前 | 28.3s | 9 | 79k | 意图解析 + 主循环 5 轮 + 收尾内部 1 次 + 漂移检查 2 次 |
| + 自动比价精挑 | 21.3s | 6 | 43.7k | 主循环少两轮,连带少一次漂移检查 |
| + 收尾文案入参化 | 19.9s | 5 | 42.7k | 收尾工具内部那次调用消失 |
| + 品类知识预取并发、精排封顶 | 17.1s | 4 | 32.6k | 主循环第 1 轮不再专门查品类知识 |
| + ID 引用与紧凑投影、工具说明瘦身 | 12.5s | 4 | 22.5k | 调用次数不变,每次读得少、写得少 |
- 9→4 次:前三步各去掉一类调用;第四步不减次数。
- 79k→22.5k:一半来自调用次数减少(每少一次就少重读一遍全部上下文),一半来自每次读的东西变少(检索返回 12k→3k 字符、工具说明 7395→1945 字符)。
- 最后一步为什么能省 4.6 秒:输入少了 1 万 token 只贡献 1 秒左右的首 token 延迟;主要收益在输出侧,收尾参数从「整包商品」变成「ID + 每件一句理由」,输出 token 从约 1.2k 降到约 0.7k,这部分解码大约 3 秒。
- 改后 12.5 秒的构成:意图解析约 3.7 秒(品类知识预取藏在它里面)、检索 + 精排 + 自动比价精挑约 2.5 秒、主循环两轮解码约 5 秒(收尾轮写文案占大头)、其余是网络与事件推送。
质量有没有掉#
延迟改动最怕「快了但答错了」。每一步合入前跑全量单测,另外看三件事:工具序列是否稳定、最终清单件数和到手价是否与改前一致、收尾理由是否仍逐件贴合意图。收尾文案改由主模型写、工具说明大幅删减这两步,额外用 Rubric 评测的 15 条子集复核了业务红线项(到手价口径、不编造商品),没有新增红线失败。
追问 Q&A#
Q1:你说「通过日志定位瓶颈」,具体看的什么日志? 两类。一是 Langfuse 里每次模型调用的 generation 记录,有输入/输出 token、缓存命中和耗时;二是我们自己的事件流,每个工具有开始和结束时间戳。按会话 ID 把两者对齐,就能画出一次任务的时间线。一拉出来就很清楚:28 秒里工具合计 2.5 秒,其余是模型调用,而且一次接一次串行。每轮主循环 2~3 秒,收尾工具内部那次 5.5 秒。
Q2:为什么不直接换个更快的模型? 换过意图解析的模型,那是链路外的一次性调用,换起来风险小。但主循环模型换掉等于换全场主力,工具选择、收尾文案、异常处理全要重新评测。而且时间线显示问题是「调用次数 × 每次解码量」,换快模型只是把每次变快一点,9 次串行还是 9 次。先减次数、减解码量,这是和模型无关的收益,以后换模型也能叠加。
Q3:自动执行比价和精挑,不就退化成 Workflow 了吗?还算 Agent 吗? 只有「检索成功、非套装轮」这条最常见的路径是机制跑的,而且模型仍然看得到结果、决定收尾还是重搜。召回跑题时模型会换词再搜,追问轮会显式调精挑,套装轮走确认流程——这些需要判断的地方还是模型在决策。我的原则是:输入确定、零决策的步骤交给机制,需要判断的交给模型。
Q4:自动精挑要用的偏好和约束从哪来?会不会和模型的理解不一致? 从会话状态里来。开局意图解析会把预算、品类、排除词、软偏好写成结构化字段,精挑工具无参调用时就按这份约束执行。改前模型调精挑时,传的其实也是这份约束,并没有加新信息,所以两者一致。如果模型认为约束需要调整,它可以显式调精挑工具覆盖,框架看到后就解除自动执行。
Q5:质疑一下:同一个 query 模型有时多搜一次,你怎么知道 28.3→12.5 不是随机波动? 三个办法。第一,每条 query 跑 3 遍取中位,不看单次。第二,同时看工具序列是否一致,主参照 query 在每一步都是 3 遍序列完全相同,所以对比有效;序列不稳定的 query(比如会被拆成套装的礼物类需求)我只当参考,不拿它的墙钟说事。第三,每一步都能在调用次数上解释:少了哪一轮、少了哪次内部调用,墙钟下降和调用次数下降是对得上的。
Q6:如果自动比价精挑执行失败了怎么办? 所有异常在自动步骤内部吞掉并记日志,同时解除武装。模型下一次调用时就看不到注入的精选结果,会按原来的方式自己调比价和精挑。失效方向就是退回改前的行为,慢一点但结果正确,不会卡住主循环。
Q7:ID 引用有什么风险?模型把 ID 写错了怎么办? 收尾和下单工具拿到 ID 后按登记表取回候选,取不到的直接跳过,所以写错的 ID 不会变成一件编出来的商品;下单工具还要求 ID 必须在本轮登记表或历史候选里,这是写操作边界的一部分。另一个风险是 ID 会被模型写进给用户看的文案里,我们在推给前端前把文案里的 ID 替换成商品短名。反过来说,ID 引用让链接和价格完全不经过模型,模型改写链接、抄错价格的问题反而没了。
Q8:用户隔了几轮说「就买第二个」,登记表只活一轮,怎么找回来? 登记表只保留本轮,是为了防止状态无限增长。跨轮引用时按 ID 回源向量库,把商品 payload 取回来再登记一次,本轮后续的工具就不用再回源。到手价这类派生字段会重新算,因为汇率和收货国可能变了。
Q9:累计输入 token 降了 72%,但前缀缓存命中本来就高,真的能省时间吗? 缓存命中能省计算和费用,但不是免费:命中部分仍然占首 token 延迟的一部分,没命中的新增内容更要完整算一遍。而且我们的问题一半在于「每轮都有新的 12k 字符工具返回进来」,这部分每次都是新内容、必然不命中。实测输入降 1 万 token 贡献约 1 秒;最后那步省下的 4.6 秒里,大头其实是输出侧:模型不用在参数里逐字重写商品了。
Q10:为什么不把检索、比价、精挑合成一个大工具,让模型一次调完? 检索是按平台同轮多发、框架并发的,每一路只看到自己平台的结果。比价和精挑需要在所有平台合流之后做,才有全局视角。把它们放在「下一次模型调用前」这个时点,天然就是合流之后,不用在工具之间自己做同步。另外大工具的参数会很复杂,模型更容易填错。
Q11:如果以后要继续降,下一步做什么? 改后 12.5 秒里意图解析占约 3.7 秒,主循环两轮解码约 5 秒。意图解析可以换更小的专用模型——这正是我做的 Query 解析模型后训练的方向。收尾轮的文案解码可以通过流式把「首屏」提前,用户看到第一个字的时间比完整耗时更重要。检索和精排那 2.5 秒已经不是主要矛盾。
Q12:这些改动对套装类需求有帮助吗? 自动比价精挑按设计不介入套装轮,因为套装要先让用户确认组成,再按槽位做组合优选,所以那条路径主要受益于 ID 引用和工具说明瘦身,输入 token 同样降了一半以上,但墙钟主要取决于模型拆几个槽、每个槽搜几次,波动大,我没有把它放进简历的数字里。
相关八股#
1. LLM 推理的 Prefill 和 Decode 两个阶段有什么区别?各自的瓶颈是什么?
- Prefill:把整段输入一次性并行算完,生成全部位置的 KV,产出第一个 token;计算密集,耗时随输入长度大致线性增长,决定首 token 延迟(TTFT)。
- Decode:之后每一步只生成一个 token,每步都要读一遍模型权重和已有 KV cache;访存密集,GPU 算力用不满,耗时约等于「输出 token 数 × 每 token 间隔」。
- 所以同样 1000 个 token,放在输出里比放在输入里贵一个数量级以上。
- 关联:这就是 ID 引用的主要收益在输出侧的原因——模型不再逐字重写商品,比少读 1 万输入 token 省得多。
2. KV Cache 和前缀缓存(Prompt Cache)是什么关系?
- KV Cache:单次请求内部,decode 每步复用前面位置的 Key/Value,避免重复计算。
- 前缀缓存:跨请求复用,服务端把某段前缀算好的 KV 存下来,下一个请求前缀逐 token 完全一致时直接取用,只算新增部分。
- 命中条件是「前缀精确匹配」,前面任何一个字符变了,后面全部失效;所以稳定内容放前面、变化内容放后面。
- 命中能省计算和费用,但命中部分仍占用上下文窗口,新增内容仍要完整 prefill。
- 关联:Agent 每轮重读全部历史,累计输入 token 随调用次数近似平方增长;减少调用次数和缩小工具返回是从源头少读。
3. 串行调用链怎么估算优化上限?(Amdahl 定律)
- 加速比 = 1 / ((1 − p) + p / s),p 是可优化部分占比,s 是该部分的加速倍数。
- 如果工具只占 10%,就算把工具优化到 0,整体也只快约 11%。
- 优化前先量占比,把力气花在占比最大的部分。
- 关联:时间线显示工具只占 2.5/28.3 秒,所以放弃了给工具加缓存,转而减少模型调用次数。
4. 并发执行多个 IO 任务的常见方式?Java 里怎么写?
- 思路:多个互不依赖的 IO 调用同时发出,总耗时取最慢那个,而不是求和。
- Java:
CompletableFuture.supplyAsync各自提交到线程池,allOf(...).join()等全部完成;要注意自定义线程池,别用默认的 ForkJoinPool 公共池跑阻塞 IO。 - 有依赖的步骤用
thenCompose串起来,无依赖的用thenCombine/allOf并起来。 - 异常处理:单个任务失败时用
exceptionally/handle给默认值,避免一个失败拖垮整批。 - 关联:跨平台检索同轮多发由框架并发;意图解析和品类知识预取也是并发发出。
5. 为什么性能对比要看中位数和分位数,而不是平均值?
- 平均值容易被个别长尾拉偏;中位数(P50)反映典型情况,P95/P99 反映长尾体验。
- 样本少时(比如每组 3 遍),中位数比平均值更抗异常点。
- 做 A/B 时要控制变量:同一 query、同一模型、新会话、排除缓存预热差异,并确认执行路径一致。
- 关联:每条 query 3 遍取中位,并要求工具序列一致才把墙钟差异当作信号。
6. 按引用传递和按值传递的区别?分布式系统里的对应模式?
- 按值传递:把完整数据复制给被调用方;按引用传递:只传一个句柄,被调用方按需取。
- 消息系统里的 Claim Check 模式:大消息体放存储,消息里只带一个 key,消费方凭 key 取数据,降低消息体积。
- 代价:多一次查询,要处理引用失效(数据被删、过期)的情况。
- 关联:候选登记表就是 Claim Check——上下文里只流转商品 ID,全量数据在登记表里,跨轮失效时回源向量库。
7. 流式输出(Streaming)能降低延迟吗?
- 不降低总耗时,但降低用户感知延迟:首个 token 一出来就推给前端,用户看到的是「开始回答」的时间。
- 常见实现:服务端 SSE 或 WebSocket 逐块推送;模型侧开 stream 模式,工具调用的参数也是按增量吐出的。
- 衡量指标要区分 TTFT(首 token 时间)和总耗时。
- 关联:收尾文案改成主模型在工具参数里写之后,前端逐字流式改为从参数增量里截取,保证体验不退化。
8. 性能优化路径的降级设计原则是什么?
- 优化路径出错时,应该退回到一条慢但正确的路径,而不是报错或给错结果(fail-safe 而不是 fail-fast)。
- 常见做法:开关控制、异常吞掉加日志、降级后打标记便于统计降级率。
- 优化必须可关闭,方便对照实验和线上回退。
- 关联:自动比价精挑任何异常都退回「模型自己调」,并保留开关一键回到改前行为做对照。
9. Workflow 和 Agent 怎么取舍?
- Workflow:步骤和顺序由代码写死,可预测、快、便宜,但遇到计划外情况只能报错或走默认分支。
- Agent:模型在循环里自己决定下一步调什么,灵活,能处理异常路径,但每一步都要一次模型调用,慢且有随机性。
- 常见折中:高频、确定的路径由代码执行,模型只在需要判断的节点介入;Anthropic 的 Agent 实践文章也建议「能用简单方案就别上 Agent」。
- 关联:检索后比价 + 精挑是高频确定路径,交给框架;换词重搜、追问、套装确认仍由模型判断。
10. 上下文越长,除了慢和贵还有什么问题?
- 注意力被稀释:长上下文中间位置的信息更容易被忽略(Lost in the Middle),无关字段越多,模型越容易被干扰。
- 触发上下文压缩:超过阈值后要做摘要,摘要是二手信息,可能丢细节,还会让前缀缓存失效。
- 输出也会变长:输入里有大段 JSON 时,模型倾向于在输出里照抄结构。
- 关联:紧凑投影去掉链接、召回分、空字段,不只是省 token,也让模型只看到做决策要用的字段。