面灵AI→

淘天集团 AI Agent 开发岗面经合集:RAG 链路与场景题

轮次
多轮面试合集
时间
2026-09
来源
牛客网

《面试题目》

  1. 请做一下自我介绍,并介绍项目中的模型调优和 RAG 工作
  2. 场景题:某 URL 在一秒内涌入大量请求,但系统只能处理 100 个,应选择什么数据结构和处理策略?
  3. 场景题:如何维护实时商品销量 Top-10 排行榜?
  4. AI Coding:设计可动态更新会员价、满减等优惠策略的商品计价系统
  5. 日常如何使用 AI Coding,遇到过哪些问题?
  6. 介绍 RAG 系统的核心功能和整体链路
  7. 知识库权限如何按用户、团队和组织进行控制?
  8. 系统主要处理哪些类型的业务文档,面向内部还是外部用户?
  9. 文档上传后,切分和向量化流程如何自动触发?
  10. 如果不依赖 Dify,使用 LangChain 或 Spring AI 时有哪些文档切分策略?
  11. Markdown 多级标题和正文如何合并切分,其他格式如何处理?
  12. RAG 的 Top-K 如何确定,为什么选择 6 或 8?
  13. Top-K 增大会不会引入干扰和幻觉,如何权衡?
  14. 召回效果不佳可能由哪些因素导致?
  15. 针对用户问题可以如何优化 RAG 检索?

《参考解析》

1. 一秒内涌入大量请求但只能处理 100 个。这题考的是「超额流量怎么被挡掉并且不把系统拖垮」,不是选哪个容器。数据结构上,需要一个有界队列(ArrayBlockingQueue、Disruptor 环形缓冲,或 Redis List)承接突发请求,容量就是 100 对应的处理窗口;再配令牌桶/漏桶做入口限流,速率按下游真实处理能力设置,桶容量允许小幅突发。策略上分四层:入口层限流与快速失败,超出的请求直接返回 429 或排队号,不要无限堆积;排队层按优先级和超时时间排序,等待超过 SLA 的请求提前丢弃;处理层用固定大小线程池或协程池做背压,池满即拒绝,避免线程堆积把内存和上下文切换打爆;最后给客户端退避重试加抖动,并在重试链路上用请求 ID 幂等去重,防止重试风暴。如果场景是同一个热点 key(比如秒杀某个商品),可以先把请求在 Redis 里做原子预扣减,只有扣减成功的少数请求才进入后端,这是把「都能处理」变成「只让该处理的进来」的典型做法。必要时再对同一 URL 的结果加短 TTL 缓存。

2. 实时商品销量 Top-10 排行榜。商品量在百万级以内,最直接的方案是 Redis ZSET:每次成交 ZINCRBY rank:2026-09-01 <delta> <skuId>,取榜用 ZREVRANGE rank:2026-09-01 0 9 WITHSCORES,复杂度是 O(log N + 10),天然按分数排序。要注意的是分片:单 key 在热点大促下会成为瓶颈,可以按 skuId 哈希分 N 个 ZSET 写,读榜时对每个分片取 Top-10 再在应用侧归并(N 个有序列表做 k 路归并,堆或者直接排序 10N 个元素都很便宜)。销量口径要提前和面试官对齐:是累计销量还是滑动窗口销量——后者用时间分桶(每分钟一个 ZSET,查询时合并最近 10 个桶)或者直接用 Redis 的 ZINCRBY + 定时衰减。如果商品量大到千万级、内存放不下全量,就用 Count-Min Sketch 做重频预筛得到候选集,再对候选集精确计数并用小顶堆维护 Top-K,牺牲少量精度换内存。

3. 会员价与满减计价系统的设计(AI Coding)。核心是把「不断新增的优惠类型」与「计价主流程」解耦,标准答案是策略模式 + 责任链/组合模式:定义 PricingRule 接口(boolean match(Cart ctx) / Money apply(Money price, Cart ctx)),会员价、满减、优惠券、限时折扣各是一个实现类;计价器按优先级和互斥关系(是否可叠加、叠加顺序)串成链依次执行。几个必须主动提到的点:金额一律用 BigDecimal(setScale(2, RoundingMode.HALF_UP))或分为单位的整数,绝不用 double;每条规则要能给出「优惠明细」,前端要展示「原价 - 会员价 - 满减 = 实付」,所以规则应用时产出 DiscountItem 列表而不只是最终价;规则要配置化、可版本化并且下单时快照(订单保存当时命中的规则 id 与参数),否则事后改规则会对不上历史订单;叠加顺序会显著影响结果(先满减再打折 ≠ 先打折再满减),这个顺序必须由业务明确写成配置而不是代码里隐式决定。最后补测试:满减门槛的边界(正好等于门槛、差 1 分)、规则互斥、并发下同一张券被用两次。

4. RAG 的整体链路与文档切分。链路答「离线 + 在线」两段:离线是解析(PDF/Word/Markdown/表格/图片 OCR)→ 清洗 → 切分 → 向量化 → 写入向量库并附元数据;在线是 query 改写 → 检索(向量 + 关键词)→ 重排 → 拼上下文 → 生成 → 引用回填。切分上,Markdown 最合理的做法是按标题层级递归切:用解析器拿到 #/##/### 的树,先把每个叶子小节作为一个 chunk,如果某节超过阈值再按段落二次切;相邻的小节标题不全丢——每个 chunk 的文本前面拼上祖先标题路径(如「员工手册 > 报销 > 差旅标准」),检索命中小节时模型才知道语境。超长章节用滑动窗口加 10%~15% overlap,避免答案正好被切断。表格单独处理:大表按行切并保留表头,或转成「表头: 值」的 KV 文本。不依赖 Dify 时,LangChain 用 MarkdownHeaderTextSplitter 先按标题切、再套 RecursiveCharacterTextSplitter,Spring AI 则用 TokenTextSplitter 配合自定义 DocumentTransformer。落地上传流程一般是异步的:上传 → 存对象存储 → 发消息(MQ/任务表)→ 后台 worker 解析切分向量化 → 回写文档状态,前端轮询或推送进度。

5. Top-K 怎么定,增大为什么会引入幻觉。K 不是拍出来的,是离线评测扫出来的:固定一批「问题 → 标准答案出处」,让 K 从 1 扫到 20,同时看 Recall@K 和最终答案正确率,取答案正确率不再上升的那个点。典型曲线是 Recall 还在涨但正确率先涨后跌——因为 K 变大后召回进来的低相关文档会带来两个问题:一是无关内容挤占了宝贵的上下文预算,真正有用的段落被迫截断;二是上下文里出现与问题主题相近但事实不同的表述时,模型容易被误导,忠实度下降。为什么常选 6 或 8:大多数知识问答的答案集中在一两个段落,K=68 已能覆盖绝大多数场景,再往上收益递减,而延迟和 token 成本是线性涨的。工程上更好的做法不是单纯调 K,而是「多路召回各取少量 + 重排后只保留 35 条进 prompt」,再加一句「资料不足时明确回答不知道」,用拒答兜住幻觉。如果压完还压不住,就上引用约束:要求逐句标注来自哪条资料,无法标注就删掉。

6. 召回效果不佳的排查与优化。先定位是哪个环节断的,别一上来就换模型:用「答案所在文档在不在库里」判断是数据问题(解析丢内容、表格没切好、增量同步漏了),「在库里但没被召回」判断是检索问题(切片太碎或太长、embedding 不适配领域语料、纯向量检索对专有名词/型号/编号不敏感),「召回了但答错」判断是生成或排序问题(相关文档排到 K 之外、上下文被截断、prompt 没约束)。优化手段按性价比排序:检索侧引入 BM25 混合召回并用 RRF 融合,对专有名词和精确匹配特别有效;加 rerank 模型(bge-reranker 之类)把正确的段落顶到前面;query 侧做改写与多查询扩展(同义改写、要点拆解、基于对话历史的指代消解);切片侧加父子块(用小块检索、用大块喂模型);数据侧补齐元数据做过滤,避免跨权限、跨产品线的文档互相干扰。每一步都用同一套评测集量化收益,别凭感觉上线。

7. 知识库权限如何控制。原则是「检索前过滤,而不是检索后再裁剪」——先按权限收窄候选集再排序,否则 K 个名额会被无权文档占掉,用户还会看到不该看的标题或摘要。实现上给每个 chunk 打上归属元数据(租户 id、团队 id、文档密级、可见用户组),检索时把当前用户的可见范围编译成过滤表达式交给向量库做前置过滤(Milvus 的 boolean expr、pgvector 的 WHERE 条件都支持),再叠加行级 ACL:用户、团队、组织三层取并集,敏感文档额外走白名单。权限变更要能立刻生效,所以权限不要烘焙进向量,而是查权限服务实时判定或走短 TTL 缓存;文档被移出权限范围时,删除或标记对应向量,避免残留。私人 RAG 也仍然需要访问控制:同一个账号可能被多人共用、浏览器会话可能被窃取,更重要的是企业合规要求「谁能看到哪份文件」有据可查,所以要看操作审计日志,而不只是拦住界面。