15. 没做好的地方,和重做会怎么改#
本文由本地速背站(docs/review-site)第 18 章转出,文中「第 N 章」指速背站章节号,不是本站编号。
本章 3 句话
- 不足分三类说:项目定位上没打算做的、数据限制做不了的、工程上没做完的。
- 每一条都要配一句「重做会怎么改」,面试官听的是你知不知道差在哪,不是道歉。
- 本章所有问题都在当前代码里核过出处;读代码发现但没复现的,卡里会写明「没复现」。
面试官问「项目有什么不足」,怎么答?#
L1 口诀:三类:没打算做、做不了、没做完
分三类答:定位上没做的、数据限制做不了的、工程上没做完的,每条配重做的改法。
我会分三类说。第一类是定位上就没做的,比如真实下单、支付、物流,项目一开始就定成 Agent 主线加基础设施。第二类是数据限制做不了的,比如商品没有材质字段、库里几乎都是 Amazon。第三类是工程上没做完的,比如运费重量是默认值、对比结果不回到会话。每一条我都能说重做会怎么改。
展开:三类各有哪些、本章卡片索引
三类一张表#
| 类别 | 例子 | 本章哪张卡 |
|---|---|---|
| 定位上没做 | 下单 mock、静态汇率关税 | c02、c03 |
| 数据做不了 | 无材质字段、Amazon 独大、类目脏 | c04、c05、c06 |
| 工程没做完 | 对比不回会话、单份状态文件 | c07、c08 |
| 读代码发现 | WebSocket、断路器、存储层 | c09、c10、c11 |
项目文档里明确写了「不覆盖」的#
真实平台 OAuth、下单 / 支付 / 物流、精细汇率对账、多模态图像检索、反爬风控、库存一致性、隐私合规。这些一开始就没打算做,面试时直接说是定位,不用解释成遗憾。
同一段文字还写着「不做训练 / 微调」,但后来训练做了三件(第 17 章)。文档没有跟着改,面试时以第 17 章为准。
怎么组织回答#
先说一条最大的,说清根因和改法,再说「还有几条小的,要不要展开」。不要一口气报十条,面试官记不住,也显得项目很糙。
出处:CLAUDE.md:29(不覆盖列表)
追问链:面试官接着会问什么
最大的遗憾是哪一条?
商品没有材质、重量这类结构化属性。它同时卡住了硬约束过滤、运费估算和评测里的一条红线。重做会在清洗阶段先把属性抽出来。
这些不足影响线上用户了吗?
影响最大的是「不要塑料」这种约束会漏判,和搜手机出配件。前者只能靠标题关键词,后者已经放到收尾那次模型调用里判了。
为什么不先把数据补好再做 Agent?
项目重点是 Agent 主线和基础设施,数据用的是公开数据集。属性抽取要跑一遍全库,成本不小,当时排在后面。
再给你一个月先做哪个?
先做属性抽取写进向量库 payload,材质、重量、整机还是配件一次解决三个问题。
下单是模拟的,具体缺什么?重做怎么补?#
L1 口诀:只出卡,不下单
下单工具只生成一张确认卡,用户点按钮走 HTTP 才写库;没有支付、物流、库存。
下单这块是模拟的。工具只准备一张确认卡,用户在页面点同意,请求走 HTTP 接口才真正写订单表。模型说「用户已确认」不算数,也没有参数能跳过确认。没有支付、物流和库存扣减。重做的话我会接支付网关,订单加状态流转,库存做预占再扣减。
展开:现在有什么、缺什么、重做清单
现在有的#
-
确认卡写进
trade_confirmations表,刷新页面、重启进程都还在。 -
商品信息按 id 从本轮候选表取,模型不传标题和价格,避免记错要写进数据库的钱。
-
幂等键由「用户 + 会话 + 商品和数量」派生,同一轮重试不会成两单。
-
订单只能在 CONFIRMED 状态取消,取消前要先查订单。
缺的和重做怎么补#
| 缺什么 | 重做怎么补 |
|---|---|
| 支付 | 接支付网关,回调按支付流水号做幂等 |
| 订单状态 | 待支付 → 已支付 → 已发货 → 完成,每步记录 |
| 库存 | 下单先预占,支付成功再扣,超时释放 |
| 物流 | 接物流商回调,订单表加运单号 |
出处:app/tools/create_order.py:1(模块说明写明「mock 交易域」、决议只走 HTTP)、app/trade/usecases.py:41(幂等键)
追问链:面试官接着会问什么
模型能不能绕过确认卡直接下单?
不能。工具没有「已确认」这个参数,落库的接口只有页面按钮会调,写权限也由 PermissionEngine 单独放行。这是第 04 章讲的机制。
接了支付以后,回调重复怎么办?
支付流水号在订单表做唯一约束,重复回调查到已处理就直接返回成功。
库存超卖怎么防?
两种做法:数据库里 UPDATE ... WHERE stock >= n 靠行锁;或者 Redis 用 Lua 原子扣减,再异步同步到数据库。
确认卡 30 分钟有效期怎么定的?
经验值,代码里没有推导依据。重做会按用户从看到卡到点按钮的实际分布定。
汇率、关税、运费是怎么算的?哪里不准?#
L1 口诀:三张静态表 + 半公斤
汇率、关税、运费都是写死的表;商品没有重量字段,运费一律按 0.5 公斤估。
到手价这条线是简化模型。汇率是一张静态表,不接实时汇率源。关税按品类关键词给近似税率,再加免征额,不查 HS 编码。运费是起步价加按重量加价,但商品数据没有重量,代码里一律按 0.5 公斤的默认值算。重做我会接汇率 API 加缓存,再按品类给一个重量先验。
展开:三张表现状与重做
| 项 | 现在 | 问题 | 重做 |
|---|---|---|---|
| 汇率 | 静态表,1 币种 ≈ 多少美元 | 会过时 | 接汇率 API,缓存 1 小时 |
| 关税 | 品类关键词 → 税率 + 免征额 | 命不中给默认 7% | HS 编码表 + 原产国 |
| 运费 | 起步价 + 每公斤加价,分区域 | 重量永远是默认值 | 按品类估重先验 |
重量为什么永远是默认值#
候选商品结构里有 weight_kg 字段,但从数据到候选的整条路上没有任何地方给它赋值,永远是空。运费估算拿到空就用 0.5 公斤。一个行李箱和一副耳机算出来一样的运费。
做对了的一点#
关税先判「发货国是否等于收货国」,国内单直接免税。不然 Amazon 美国发货寄到美国也会被收一笔进口税。
出处:app/recall/fx.py:4(静态汇率表)、app/recall/duty.py:21(税率表)、:40(默认 7%)、:116(国内单先判)、app/recall/shipping.py:38(默认 0.5 公斤)、app/tools/schemas.py:71(weight_kg 默认空)
追问链:面试官接着会问什么
汇率过时会有多大影响?
主流货币一年波动多在个位数百分比,比价结论一般不会反转。但项目定位就说了不做精细汇率对账,面试时直接承认。
接了汇率 API,它挂了怎么办?
缓存上一次成功的值继续用,并在结果里标「汇率时间」。现在的静态表就是最后的退路。
重量先验从哪来?
两个来源:品类知识库里每个品类的典型重量;或者从标题抽「500g」「2kg」这类数字。抽不到再用品类默认。
为什么关税命不中给 7% 而不是 0?
给 0 会让跨境商品显得便宜,用户到手才发现要补税。宁可估高一点。
用户说「不要塑料的」,系统能过滤吗?#
L1 口诀:没有字段,只能匹标题
商品数据没有材质字段,「不要塑料」只能拿关键词匹标题,评测里这条红线判不了。
商品数据里没有材质字段。现在的做法是 planner 把材质当排除关键词,拿它去匹配标题,标题没写材质就漏掉。所以评测里材质这条红线其实判不了,我们决定不把它当回归看。重做我会在数据清洗阶段用模型把材质、颜色这些属性抽出来,写进向量库的 payload,检索时直接过滤。
展开:现在怎么处理、为什么只做排除、重做方案
现在的处理#
-
「不要 X」进排除关键词,命中标题就淘汰。
-
「喜欢 X」进偏好关键词,命中只加分,不淘汰。
-
正向没有硬淘汰档:数据没有可靠的材质字段,只保留命中的会误杀一大片。
评测上的后果#
评测集里有一条「不能出现塑料制品」的红线细则。标题不写材质时,judge 和系统都判不出来。这条细则的分数在 0 和几十分之间来回跳,和改动无关,所以决定不当回归指标。
重做方案#
| 步骤 | 做法 |
|---|---|
| 抽取 | 清洗阶段用模型从标题和描述抽材质、颜色、重量 |
| 存储 | 写进 Qdrant 的 payload,和平台、价格一样可过滤 |
| 质量 | 只对高频品类做,抽样人工核对 |
| 成本 | 全库 138 万条各调一次模型,费用数字待补 |
出处:app/tools/planner.py:343(正向不做硬淘汰的原因)、:381(材质、颜色填排除关键词)、app/recall/category_kb.py:8(品类知识库有「该看哪些维度」,没有商品级取值)
追问链:面试官接着会问什么
为什么不训一个材质分类器?
没有标注数据,而且材质值域开放,分类器不如直接让模型抽取。抽取一次写进库,线上零成本。
标题关键词误杀怎么办?
只做排除不做保留,误杀范围就限制在「标题明确写了塑料」的商品。漏掉的比误杀的多,这是有意的取舍。
这算算法的问题还是数据的问题?
数据的问题。算法层能做的是不假装能过滤,在结果里如实说「按标题匹配」。
抽取错了怎么发现?
抽样人工核对,加上线上用户反馈。抽取结果带置信度,低置信度的不参与过滤。
召回库里 Amazon 占绝大多数,跨平台比价还成立吗?#
L2 口诀:一家独大,决定不修
库里 Amazon 占绝大多数,搜全平台几乎只出它;eBay 已剔除;决定不修。
库里 Amazon 商品占绝大多数。搜全平台时向量近邻基本都是 Amazon,其它平台很难露面。eBay 整个剔除了,因为它的卖家名被掩码、描述全空。我们决定不修,因为用户指定平台可以单搜,跨平台由主环在同一轮里分平台各发一条搜索。重做我会按平台分桶各取前 k 条再合并。
展开:现状、为什么不修、重做方案
现状#
-
全库 138 万点。Amazon 占比具体数字待补,代码注释原话是「占绝大多数」。
-
平台参数是枚举:all、amazon、walmart、shein、lazada、shopee。没有 eBay。
-
「all」等于本轮启用的全部平台合到一起搜,不做平台配额。
为什么决定不修#
跨平台比价靠的是主环同一轮发多条搜索,每条指定一个平台,框架并发执行。这样每个平台都有自己的召回结果,比价在这一步已经成立。「all」只是用户没说平台时的默认。
重做方案#
建库时按平台平衡采样,或者检索时按平台分桶各取前 k 条再合并。代价是各平台的相似度分数不可比,合并后要重排。
出处:app/recall/qdrant_store.py:280(「库里 amazon 占绝大多数」)、app/tools/item_search.py:142(eBay 剔除原因)、:146(平台枚举)、:368(「all」= 启用平台集合)
追问链:面试官接着会问什么
用户没说平台,其它平台怎么露面?
提示词里有一段并行策略,告诉模型跨平台时同一轮发多条搜索、每条指定平台。这是模型的决定,不是机制保证。
为什么不建库时就平衡采样?
其它平台的数据量本来就少,平衡采样等于把 Amazon 砍掉大半,召回质量会掉。分桶取前 k 条更合适。
分桶合并后怎么排?
各平台的向量分数不可比,合并后过一遍 reranker 统一打分,第 11 章讲过。
eBay 不能只用标题和价格进库吗?
可以,但描述全空、卖家名掩码,向量只靠标题,质量会明显差一截。当时判断不值得。
搜手机为什么会出手机壳?最后怎么处理的?#
L2 口诀:类目脏,两方案都证伪
根因是数据的类目字段脏;黑名单和品类过滤都实测无效,最后交给收尾那次模型判。
搜手机常搜出手机壳、贴膜。根因是数据的 category 字段不可信,整机和配件在同一个类目。我试过两种确定性方案,关键词黑名单和品类过滤,用真实数据标定,两边分数完全交叠,都证伪了。最后放在收尾那次模型调用里判,它本来就在逐件读标题写理由,顺手标出不符合意图的商品。重做我会在清洗阶段重新分类。
展开:证据、两个失败方案、现在的做法
数据有多脏#
整机和配件在同一个类目;有电视被标成游戏机类目。商品卡里刻意不显示类目,因为显示出来只会误导用户。
两个失败方案#
| 方案 | 为什么不行 |
|---|---|
| 关键词黑名单 | 「case」「film」也是正常商品词,误杀 |
| 按类目过滤 | 类目字段本身就是错的 |
| 挑货阶段打分阈值 | 配件和真商品的分数完全交叠,任何阈值都两头错 |
现在的做法#
收尾工具那次模型调用本来就要逐件读标题写推荐理由。让它同时输出「和本轮品类明显不符的商品 id」,排版时剔掉。多出来的成本接近零。搜索结果展示头 5 条标题,也是为了让主环有机会自己察觉跑偏。
出处:app/tools/shopping_summary.py:84(类目值域乱)、:199(为什么放收尾判)、:210(off_intent 字段)、app/tools/item_picker.py:179(分数交叠的实测数据)、app/tools/item_search.py:108(展示 5 条的原因)
追问链:面试官接着会问什么
交给模型判,会不会误杀真商品?
会有。但它是在读完标题、写完理由之后判,信息比任何规则都全。实测里它自发写出过「这是配件」,所以才把这个判断正式化。
为什么不在挑货那一步判?
挑货是纯确定性打分,没有模型。要判「这是不是配件」必须读懂标题,只有模型能做。
重做重新分类用什么?
清洗阶段用模型按标题做「整机 / 配件」二分类,再结合价格分布交叉验证。结果写进 payload,检索时直接过滤。
这个问题怎么发现的?
评测集里的手机和相机两条 query 出了坏结果,逐条看候选才定位到类目字段。
对比栏按钮为什么不走 Agent?代价是什么?#
L2 口诀:按钮直达,结论不回会话
对比栏直接调 HTTP,一次快模型出结果,不经主环;代价是结论不进会话。
对比栏的「帮我比一比」是一条 HTTP 接口,不走 Agent 主环。用户已经亲手勾了商品,意图完全确定,再让主环规划一遍,多几十秒还不一定出结构化表。所以一次快模型调用直接填对比表。代价是这个结论不会写回会话,下一轮模型不知道用户比过。重做我会把结果作为一条消息写回会话状态。
展开:两条入口、取舍、重做
两条入口共用一个实现#
| 模型调工具 | 前端按钮 | |
|---|---|---|
| 触发 | 用户在对话里说「比一比」 | 用户勾选后点按钮 |
| 路径 | 主环 → 结束工具 | HTTP → 一次快模型 |
| 结论进会话 | 进 | 不进 |
两条路都调同一个 compare_items,输出结构一样,不会出现两套逻辑。
三条 id 检查#
只收本会话展示过的商品 id;会话归属校验;商品信息从候选表按 id 取,前端不传标题和价格。
重做#
接口返回后,把对比结论作为一条助手消息追加进会话状态文件。下一轮模型就能引用「刚才比的那件」。
出处:app/api/orders.py:190(compare_endpoint,文档字符串写明「不走 AgentLoop」和原因)
追问链:面试官接着会问什么
用户下一轮说「刚才比的那个便宜的」怎么办?
现在模型看不到那次对比,只能靠候选表里的商品猜。这就是要重做写回会话的原因。
为什么不直接往会话里塞一条隐藏的用户消息?
可以,但用户消息会触发一整轮主环,几十秒往返又回来了。写一条助手消息更轻。
这种「按钮直达」的模式还用在哪?
找相似商品也是 HTTP 直达。原则是:用户意图已经完全确定的操作,不经过 Agent 规划。
怎么防止拿别人会话的商品 id?
会话归属校验先过,再查本会话的候选表,不在表里的 id 直接拒绝。
会话状态只存一份文件,坏了怎么办?#
L2 口诀:一份文件,坏了就从头
会话状态只存一份 session.json,读坏就当新会话,不回放消息表。
续聊恢复只有一条路,读上一轮写的 session.json。读不到或解析失败,就当新会话开始,不去回放消息表。这是有意的取舍,一份坏文件让整个会话起不来,比少一段上下文更糟。写文件是先写临时文件再改名,不会留半截。缺点是多副本要共享目录,也没有版本。重做我会把状态放数据库或对象存储,按轮次版本化。
展开:存什么、为什么不回放、缺什么
文件里存什么#
整个 AgentState 序列化成 JSON:消息、框架压缩后的摘要、会话级偏好。消息表只给前端侧栏回看,不参与恢复。
为什么不从消息表回放#
消息表是给人看的投影,工具结果已经被排版过。用它重建模型上下文,要把工具结果反推回原始结构,容易错。第 02 章讲过,以前有 5 份产物和一条回放腿,后来都删了,只留这一份。
现在缺什么、重做怎么补#
| 缺什么 | 后果 | 重做 |
|---|---|---|
| 多副本共享 | 要挂共享目录或固定路由 | 放数据库或对象存储 |
| 版本 | 写坏一次就全丢 | 按轮次保留最近几版 |
| 写失败 | 只记日志,下轮从头 | 写失败告警并重试 |
出处:app/agent/orchestrator.py:12(读坏即空开局、不回放消息表)、:93(恢复是加成不是前提)、:104(解析失败记 warning)、:113(先写临时文件再改名)、:121(写失败只记日志)
追问链:面试官接着会问什么
用户刷新页面会丢上下文吗?
不会。每轮结束都写文件,刷新后下一轮从文件恢复。丢的只有正在跑的那一轮。
为什么不每轮都存数据库?
文件是几十 KB 的整块 JSON,写文件最简单,单机部署够用。多副本时再改成数据库,这是没做完的部分。
写到一半进程被杀会怎样?
临时文件留一半,正式文件还是上一轮的完整版。改名是原子操作,所以正式文件不会是残片。
50 轮以后文件会不会太大?
框架在上下文超过阈值时会压缩成摘要,摘要也在这份文件里,第 07 章讲过。
读代码发现的 WebSocket 问题有哪些?#
L3 口诀:首连早断,任务不起
首连在 ws_ready 前断开会走重连,重连不发任务请求,界面却显示运行中。
这是我读代码发现、没有实际复现的问题。首次连 WebSocket 时,如果在收到 ws_ready 前断线,前端会按重连处理。重连分支不再发任务请求,任务不会启动,界面却显示运行中。修法是单独记「任务请求发过没有」,不用「是不是重连」来判断。另外还有前端不发心跳、一会话只登记一条连接这些小问题。
展开:问题清单与修法
| 问题 | 影响 | 修法 |
|---|---|---|
| 首连早断走重连 | 任务不启动,界面假运行 | 单独记「请求发过没有」 |
| 前端从不发 ping | 后端 ping 分支是死代码 | 前端定时发,或删后端分支 |
| 一会话一条连接 | 第二个标签页顶掉第一个 | 按会话存连接列表 |
| 事件同步发送 | 慢客户端拖慢 Agent | 加发送队列,满了丢非关键事件 |
| 重连间隔无随机 | 大量客户端同时重连 | 加随机抖动 |
第一条为什么会发生#
前端用一个布尔值「这是不是重连」决定要不要发任务请求。首连的 onclose 里,只要状态还没到结束,就以「重连」身份再连一次。而重连分支的设计前提是「任务已经在后台跑」,这个前提在首连早断时不成立。
出处:frontend/src/hooks/useShoppingXTask.ts:429(重连不发请求)、:427(状态设为 running)、:592(onclose 以重连身份再连)、app/api/server.py:928(ping 分支)、app/api/connection.py:50(一会话一条连接)
追问链:面试官接着会问什么
为什么说没复现?
这是读代码推出来的,触发条件是握手成功后、服务端发 ws_ready 前断线,本地很难碰到。面试时如实说是代码审查发现,没有线上记录。
换成 SSE 会不会更简单?
会。浏览器发回后端的消息只有澄清回答一种,走 HTTP 就够。第 15 章的答法是「SSE 也能满足」。
慢客户端拖慢 Agent 怎么解?
发送放到独立队列,Agent 只往队列里放。队列满了先丢掉进度类事件,保留结果类事件。
两个标签页应该都收到吗?
应该。按会话存一个连接列表,广播给所有连接。现在是后连的顶掉先连的。
断路器和下单幂等,哪里不严格?#
L3 口诀:半开不锁,重买当重试
断路器半开状态没限制成单个探测;同一会话再买同样的东西会被当成重试合并。
两个已知的不严格。一是断路器的半开状态没有限制成只放一个探测请求,并发下可能放过一两个。代码注释里写明了,低频外呼不值得加锁。二是下单幂等键由用户、会话、商品和数量派生,同一会话里用户真的想再买一份同样的东西,会被当成重试。重做我会给半开加一个探测令牌,幂等键加上客户端生成的请求 id。
展开:两处的设计理由与修法
断路器#
-
用连续失败计数,不用失败率。一次购物只调几次外部服务,滑动窗口样本太少,失败率会被一两次抖动放大。
-
状态读写都在同步段,单线程 asyncio 下计数不会错。
-
半开期没有限制成单探测。修法:半开时发一个令牌,拿到令牌的请求才能探测,其余直接拒绝。
幂等键#
| 成分 | 为什么 |
|---|---|
| 用户 + 会话 | 隔天新会话再买,是两笔真实订单 |
| 商品 + 数量,排序后 | 顺序无关,同一批货一个键 |
| 不含时间戳 | 含了同一轮重试会各自成单 |
漏洞:同一会话里真想买第二份,键完全一样,会被合并成一笔。修法是前端每张确认卡生成一个请求 id 并持久化,幂等键加上它。
出处:app/utils/circuit_breaker.py:15(连续失败计数的理由)、:19(半开未限单探测,注释自认)、app/trade/usecases.py:41(幂等键组成)
追问链:面试官接着会问什么
半开放多个探测,最坏会怎样?
下游还没恢复时多打过去一两个请求,多一两次失败。对低频外呼没有实际影响,所以当时没改。
幂等键存在哪里?
订单表上做唯一约束。重复请求插入失败,查出已有订单直接返回。
客户端请求 id 丢了怎么办?
确认卡本身在服务端有记录,请求 id 可以跟卡一起存。刷新页面后从卡里取回。
为什么不用 Redis 做幂等?
订单本来就要写数据库,唯一约束是免费的。多一个 Redis 只是多一处会不一致的地方。
存储层还有哪些读代码看出来的小问题?#
L3 口诀:随机主键、多余索引、TTL 不抖
读代码列出的:随机主键、多余索引、TTL 固定、锁不比较值、Redis 单机。
这些是读代码列出来的小问题,没有在线上验证。主键大多是随机字符串,写入会打散 B+ 树。几张表在联合唯一约束外又给 user_id 单独建了索引。检索缓存过期固定 900 秒没加随机,重建锁的值固定、解锁不比较。Redis 单机、没配最大内存。每条我都知道怎么改,但当前流量下都不是瓶颈。
展开:清单与出处
| 问题 | 出处 | 重做 |
|---|---|---|
| 主键是 uuid4 随机串 | models.py 多处 | 有序 id,如时间前缀 |
| 联合唯一外又建 user_id 单列索引 | models.py:218、:222 | 删掉,最左前缀已覆盖 |
| 会话列表排序缺联合索引 | accounts.py:145 | (user_id, updated_at) |
| 缓存 TTL 固定 900 秒 | search_cache.py:46 | 加随机量防同时过期 |
| 重建锁值固定、解锁不比较 | search_cache.py:218、:232 | 值用随机串,Lua 比较再删 |
另外线上 Redis 是单机、没有从库、没有配 maxmemory。这三条依据是对 docker/ 目录搜索无结果,容器有没有被手工改过配置没有核对。
怎么讲这些#
说明是代码审查得出的判断,没跑过 EXPLAIN、没做过压测。当前流量下都不是瓶颈,所以排在后面。面试官要的是你看得出来,不是你全改了。
出处:app/db/models.py:218、:222、app/api/accounts.py:145、app/recall/search_cache.py:46、:218、:232
追问链:面试官接着会问什么
随机主键真的有影响吗?
写入量小时几乎没有。量大了随机插入会让 B+ 树页分裂、缓存命中下降。改成有序 id 是常规做法。
TTL 不加随机会怎样?
同一批缓存同时过期,请求一起打到向量库。加几十秒随机量就能错开。
锁不比较值,最坏后果?
误删别人持有的锁,多一次并发重建,多查一次向量库。这把锁只为省一次查询,后果可接受。
Redis 挂了系统会怎样?
各处都有降级:缓存失效直接查库,令牌桶和断路器放行。第 14 章讲过,但停 Redis 的演练没有做过。
训练做了三件,为什么只上线一件?还有什么没标定?#
L3 口诀:离线涨了,线上没换
reranker、planner 离线涨了但没上线;收藏权重 0.2 没标定。
训练三件事只有 embedding 上线。reranker 离线排序指标涨了,但线上用它挡无关商品,不是排序,训练目标和用途没对上。planner 强化学习 reward 涨了,域判定却退步,线上还用 API 模型。收藏加分的权重 0.2 是拍的初值,没标定。重做我会先定清线上用途,再选训练目标。
展开:三件事为什么没上线、权重怎么标定
| 项 | 离线结果 | 为什么没上线 | 重做 |
|---|---|---|---|
| embedding | 召回率 +12.6% | 上线了 | — |
| reranker | ndcg@8 +26% | 线上用途是挡无关,不是排序 | 按「挡无关」定训练目标 |
| planner | reward 0.790 → 0.813 | 域判定退步,比不过 API 模型 | reward 里加域判定项 |
三个数字取自第 17 章引用的提交正文,本地没有评测原始文件。
收藏加分权重#
挑货打分里有一项「收藏推断的偏好命中」,权重默认 0.2,代码注释写的是弱正向。同一个词至少出现在 2 件收藏里才算偏好。0.2 是拍的初值,没有用评测集扫过。
怎么标定#
固定评测集,权重从 0 到 0.5 扫几个点,看端到端得分曲线。没有峰就说明这一项没用,直接删。
出处:app/tools/item_picker.py:165(PICK_W_AFFINITY 默认 0.2)、:784(弱正向加分)、app/memory/affinity.py:34(至少 2 件证据)、docs/handbook/11、12(训练结果)
追问链:面试官接着会问什么
离线涨了线上不换,训练是不是白做?
不是。结论是「训练目标要从线上用途倒推」,这个教训直接决定了重做怎么选目标。模型和数据都留着。
训练目标怎么和线上用途对上?
先写清线上这一步的输入输出和判定标准,再用同一个标准做离线评测集。离线指标和线上指标是同一个,才算对上。
为什么权重没标定就上线?
它是弱加分,最坏只影响并列商品的顺序。先上线收集收藏数据,有数据了再标定。
GPU 充足会先做哪个?
planner。它决定意图拆解,上游错了下游全错。reward 加上域判定项再训一轮。
合上页面自测 3 题#
- 不足分哪三类?每类各说一条,配一句重做怎么改。
- 搜手机出手机壳,根因是什么?哪两个确定性方案被证伪了?
- 运费重量、汇率、关税各是怎么来的?重做各改成什么?