一份大模型算法岗的笔试题:给 300 条政务热线(12345)工单文本,做事项拆分、四要素抽取、四级分类、群诉识别,交代码加《结果.xlsx》。题目反复强调两个词——通用性和沟通成本。我把这两点都当成实打实的考点来做。
把”通用性”做进架构,而不是堆工作量
题目有一问要求”做成多解题”。直觉做法是跑三条冗余流水线,但那是堆工作量不是通用性。我的解法是三层可插拔抽象:预处理 → 每个子任务一个可插拔 Strategy(规则/向量/LLM 三类方法)→ 模型抽象成一个可配置 LLMClient。换数据集不改代码,换模型改一个 env,换方法换一个 Strategy 类。
落地是 14 个模块约 2400 行,运行期第三方依赖只有 4 个(openai / openpyxl / numpy / faiss-cpu),没有 langchain、没有 agent 框架、没有一行调用 Claude——面试官当场追问”脱离 AI 助手能独立跑吗”,这套自包含设计就是答案。所有 LLM 调用带磁盘缓存,幂等、可断点续跑、重跑零成本(最终全缓存重跑 36 秒)。
事项拆分:先证明它不是过度设计
题目隐含”一工单 = 一诉求 = 一行”,但真实数据不是——很多工单一条里塞了三四个诉求。硬塞一行会丢信息、逼分类变单标签、让群诉没法把一个工单分到多个群。
所以我加了独立的”事项拆分”步,后面四要素/分类/群诉都在”事项”粒度做,再用”工单号+事项号”映射回工单。三档”保守/平衡/激进”全量跑了对比再选默认:
| 档位 | 事项数 | 事项/单 | 多诉求工单占比 |
|---|---|---|---|
| 保守 | 378 | 1.26 | 19.7% |
| 平衡(选用) | 484 | 1.613 | 39.7% |
| 激进 | 754 | 2.51 | 57.3% |
约 39.7% 的工单含多个诉求——这个实测数字证明拆分不是过度设计,是数据本身要求的。
群诉双路召回:3276 这个数字说明了一切
群诉的定义是”不同工单描述同一个点位、相同问题”,难点全在召回。这里有个坑:数据脱敏把”区名、门牌号”做了字符替换(“安全”→“泰全”这种),所以地名根本不能做精确字符串匹配,群诉必须靠”点位 + 问题”的语义组合。
我没只靠一路,而是双路取并集:点位分块召回(保精确)∪ faiss 向量召回(保召回)→ 三级分类锚定缩候选 → 并查集聚簇 → LLM 逐对裁决。
双路的价值,用一组真实数字就说清了——召回贡献:点位独有 72 条,向量独有 3276 条,两路交集 229 条。也就是说,纯点位分块会漏掉绝大多数,3276 条同点位群诉全靠向量捞回来。这组数字是整个项目最有说服力的地方。
一次过度归并的修正:29 组 → 13 组
第一版群诉跑出 29 组,但其中”开发商违约”那个群里塞了 23 个不同开发商的投诉、点位却是”未提及”——向量召回把”问题像但点位不同”的对也拉进来了,粗粒度下 LLM 裁决偏松,成员一致率只有 63.3%。这违反了”同一点位”的定义。
修法是加硬约束:群必须有可确认的共同点位,点位=未提及的群直接剔除。重跑(全缓存 36 秒)从 29 组降到 13 组,剔掉 16 个伪群,成员归属一致率升到 68.1%。识别得多不等于识别得对,群诉这种任务,召回上去之后真正难的是把”看起来像”和”真的是”分开。
跨厂商交叉验证
分类和裁决这种主观判断,单模型容易自说自话。我用两个不同厂商的模型互相印证:主力 DeepSeek,第二判官智谱 GLM,抽样 40 条算一致率——分类一级 82.5%、四级整路 60%、四要素”信息充分”判定 100%、群诉成员归属 68.1%。跨厂商比同厂商不同档位更有说服力,真正独立的两个判断一致才说明稳。
规模化:候选对只占朴素两两的 0.5%
题目强调通用,那就得考虑规模。群诉的成对裁决朴素做是 O(n²),10 万条会爆。我用向量召回把它降到 O(n·k),并写了压测脚本验证:
| 倍数 | 事项数 | 本方案候选对 | 朴素两两对 | 占比 | 召回耗时 |
|---|---|---|---|---|---|
| 1x | 483 | 424 | 116403 | 0.36% | 0.31s |
| 3x | 1449 | 5053 | 1049076 | 0.48% | 1.32s |
| 5x | 2415 | 14168 | 2914905 | 0.49% | 3.1s |
候选对始终只占朴素两两的 0.36-0.49%,召回耗时近线性增长。对算法岗来说,“能跑通”之外还能讲清”上规模怎么办”,是实打实的加分。
最硬的坑:归纳分类树和余额耗尽
四级分类用”国标锚定 + 数据涌现”——一二级锚 12345 国标,三四级让 LLM 从数据里归纳,归纳完冻结成 JSON 树再逐条枚举约束分类。但”归纳树”这步成了顽固瓶颈:DeepSeek 官方余额中途耗尽(decompose + 四要素约 1400 次调用跑空),救场是前两步吃缓存零调用、其余路由到 GLM-4.6;而 GLM-4.6 是推理模型、归纳极慢(实测 4000 token 要约 6 分钟),还会对聚合的敏感投诉批次整批审核拒答。死磕不下去后我做了个决断:放弃硬磕自动归纳,把兜底树从贫瘠的 L1 升级成一棵基于国标 + 通读数据精心编排的完整四级”策划富树”——归纳成功就用涌现树,失败就用策划树照样出完整四级分类。富树的未归类率 0.6%,反而比涌现路的 1.2% 更好。
embedding 也做成可插拔:默认真 embedding API,TF-IDF 作纯离线兜底。DeepSeek 没有 embedding 端点(正好验证了兜底的必要),拿到智谱 embedding-3(2048 维)后切到真向量,还踩了它单次 input 上限的坑、改成每批 32 分批 embed。另外为试 Milvus 把 numpy 降到 1.26.4,结果本地 faiss 是按 numpy 2.x 编的、降级后 swig 层直接报错,最后 numpy 升回 2.2.6 恢复 faiss,Milvus 降级为”需独立环境”诚实标注——一个 flaky 的可选依赖不该污染主链路。
一个我低估的考点:沟通成本
题目把”沟通成本”明确写进评分,我一开始没当回事。结果交付时用户(模拟 HR/面试官)反复看不懂——“事项拆分""冻结树""双路召回""并查集”全是我堆的黑话。我为此补了字段速查表和一份白话导读(初版还因为”太口语化不专业”被打回重写)。这是个真实教训:算法做得好不等于交付沟通好,面向非技术读者的可读性是隐藏考点,我初版严重低估了。
小结
这道题我最在意的是没被”多解题”带偏去堆工作量,而是把通用性落在 Strategy 抽象、可插拔依赖、规模化设计上;以及群诉那条线——召回靠双路(3276 条的价值)、精度靠”必须同点位”的硬约束(29→13 的修正)。算法岗看的不只是你会调哪个模型,是你怎么把一个具体任务抽象成一个能扩展、能讲清的系统。