面灵AI→

九方智投 Agent 开发一面:RAG 切块、MCP 权限与 Java 八股

轮次
一面
结果
已挂
时间
2026-09
来源
牛客网

《面试题目》

  1. 自我介绍。
  2. 实习主要负责什么?
  3. 求职公司倾向是什么?大厂吗?
  4. 项目拷打(围绕项目追问细节)。
  5. 知识库如何搭建?
  6. 按照标题切割,有的块很大有的块很小,该怎么办?
  7. 召回率低怎么办?
  8. RRF 的原理是什么?
  9. Plan 模式如何调用工具?MCP 的原理是什么?
  10. MCP 的权限如何控制?
  11. Plan 模式里哪里需要人工确认?
  12. 如何避免一直需要人工确认?
  13. 测试的数据量有多大?
  14. HashMap 说一下。
  15. sleep 和 wait 的区别?
  16. Java 锁的实现方法有哪些?
  17. 内存泄漏是怎么回事?
  18. Redis 是什么?
  19. Redis 宕机后如何恢复?

《参考解析》

知识库搭建:切块决定了召回的上限

一条 RAG 链路的基本环节是:文档解析(抽取正文与标题层级)→ 清洗(去页眉页脚、合并断行、表格与图片特殊处理)→ 切块 → 向量化与索引 → 检索(向量、关键词或混合)→ 重排 → 组装上下文交给模型生成。最容易出问题的就是切块。按固定长度硬切会劈开句子和论点;只按标题切,则大标题下可能塞了几千字、小标题下只有一句话,块的语义密度差了几个量级,而嵌入模型对长文本是把语义”平均”掉,长块的向量会失去区分度。实用解法是”结构 + 长度”双约束的递归切分:先按标题层级切,超长的块再按段落、句子递归细分到接近目标长度(常见几百 token 的量级);过短的块与相邻块合并。再叠两个常见优化:一是 overlap,相邻块重叠一小段,避免关键信息正好落在边界被切断;二是”小块检索、大块回填”,用句子级小块做嵌入提升匹配精度,命中后把它所在的父段落或完整章节回填给模型,兼顾精度与上下文完整。

召回率低怎么排查:先分清是”没检索到”还是”没答对”

要把问题定位到具体环节,而不是直接换模型。常见原因与对策:① 查询与文档用词不一致——加查询改写(同义扩展、HyDE、把口语问题改写成检索式),或引入关键词检索做混合召回;② 块切得不好——按上面的方式重建索引;③ 嵌入模型不适配中文垂域——换更强的中文或多语嵌入模型,必要时做领域微调;④ 只有单路召回且 top-k 太小——先粗召回几十条再重排精挑;⑤ 缺元数据过滤——按时间、业务线、权限先缩小范围再检索;⑥ 没有评估集,改动全靠感觉——先造一批”问题→应命中文档”的标注样本,用 Recall@k、MRR、命中率量化,每次改一个变量就跑一遍。面试里主动说”先建评估集”通常比背一长串优化清单更得分。

RRF:按排名而不是按分数融合

RRF(Reciprocal Rank Fusion)把多路召回结果合成一个排序,公式是 score(d) = Σ 1 / (k + rank_i(d)),其中 rank_i(d) 是文档 d 在第 i 路结果里的名次,k 是平滑常数(常用 60)。它的价值在于只用排名、不用分数:向量相似度、BM25、模型打分这些分数量纲完全不同,直接加权需要归一化而且很脆弱,按排名融合就绕开了这个问题,也天然抑制了某一路给出极端高分的影响。工程细节上要注意去重(同一文档被多路命中要合并计分)和每路截断长度(每路取前几十条即可,过长会稀释信号)。如果某一路质量明显更高,可以额外加权重,但引入权重就要重新评估,别凭感觉调。

Plan 模式、工具调用与 MCP 的分工

Plan 模式先让模型把任务拆成步骤再逐步执行;工具调用(Tool Calling)是模型输出结构化的调用意图,由宿主程序执行并把结果回填。差别在控制流:前者是”先规划再执行”,适合步骤多、依赖明确的复杂任务,代价是多一轮模型调用、规划错了整条跑偏;后者是”边想边调”,适合单步可解、需要按中间结果动态决策的任务。MCP(Model Context Protocol)解决的不是”怎么调工具”,而是”工具从哪来”:它把工具、资源、提示词以标准协议暴露出来,工具在服务端实现,客户端通过 JSON-RPC 发现并调用,于是同一份实现不必为每个宿主重写。判断标准可以简化成一句:工具只服务自己这一套系统,直接内置 Tool Calling 最省事;要跨应用复用、或工具背后是别人维护的服务(数据库、内部平台、第三方 SaaS),才值得上 MCP——多一层协议就多一份部署与鉴权成本。

MCP 的权限控制与”人工确认”的边界

权限必须在服务端做,不能靠提示词约束模型。可落地的几层:① 工具白名单,只暴露本场景需要的工具,读与写分开关;② 参数级约束,强制携带租户与用户身份,查询强制带时间范围与行数上限,禁止模型自由拼 SQL;③ 按资源授权,MCP server 侧用令牌代表用户身份,用户没有权限的数据直接拒绝而不是返回空;④ 审计,记录每次调用的调用者、参数与结果摘要。人工确认的原则是看影响面:只读、可逆、影响范围限于本人数据的操作自动执行;写库、发消息、对外提交、花钱、权限变更这类必须显式确认。想少打扰用户,可以按”高风险单次确认 + 低风险首次确认后记住这个模式”分层,并把确认做成结构化的差异预览(明确展示将要改什么),让用户一次点清,而不是每步弹窗——这也是”如何避免一直需要人工确认”的答题落点。

Java 八股:HashMap、sleep/wait、锁与内存泄漏

HashMap 是数组 + 链表 + 红黑树(链表长到 8 且容量到 64 才转树),默认容量 16、负载因子 0.75,容量不足时按 2 倍扩容并重新散列(JDK 8 用高低位拆分避免重算 hash);它线程不安全,多线程扩容会出乱象,并发场景用 ConcurrentHashMap(JDK 8 用 CAS 加锁头节点,粒度比分段锁更细)。sleep 是 Thread 的静态方法,不释放锁,到点或被中断后继续/抛异常;wait 是 Object 的方法,必须先持有该对象的锁,调用后释放锁并进入等待队列,需要 notify/notifyAll 唤醒,实践里要用 while 循环判断条件以抵御虚假唤醒。Java 的锁可以分层说:synchronized 从偏向锁、轻量级锁到重量级锁(monitor,底层是 mutex),ReentrantLock 基于 AQS 的 state 加等待队列,读写锁与 StampedLock 各有适用场景,条件等待用 Condition。内存泄漏在 Java 里指对象已不再使用却仍被 GC Root 引用:常见来源是静态集合只增不减、ThreadLocal 用完不 remove、监听器与回调未注销、线程池任务持有大对象、缓存没有淘汰策略;排查手段是堆快照对比加引用链分析,先定位”谁持有引用”。

Redis 与宕机恢复

先分清宕机程度。进程崩溃或重启:靠持久化恢复——RDB 是周期性快照,文件小、恢复快,但可能丢最后一段数据;AOF 记录写命令,appendfsync 可选 always / everysec / no,everysec 是常用折中;Redis 4.0 起支持混合持久化(RDB 全量加增量 AOF),兼顾恢复速度与丢数据量,重启时优先用 AOF。机器或机房级故障:靠主从加哨兵、或 Cluster 做主备切换,注意主从复制是异步的,切换时可能丢最后一批写。配置上要设 maxmemory-policy,否则内存打满后写入会失败甚至触发 OOM;恢复慢也是常见痛点,大实例加载 RDB 会阻塞,可行方向是拆小实例、控制单实例内存规模。被追问”如何做到不丢数据”时,诚实的答案是单机做不到绝对不丢,只能靠 AOF always 加主从同步加业务侧补偿。