九方智投 Agent 开发一面:RAG 切块、MCP 权限与 Java 八股
- 轮次
- 一面
- 结果
- 已挂
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 实习主要负责什么?
- 求职公司倾向是什么?大厂吗?
- 项目拷打(围绕项目追问细节)。
- 知识库如何搭建?
- 按照标题切割,有的块很大有的块很小,该怎么办?
- 召回率低怎么办?
- RRF 的原理是什么?
- Plan 模式如何调用工具?MCP 的原理是什么?
- MCP 的权限如何控制?
- Plan 模式里哪里需要人工确认?
- 如何避免一直需要人工确认?
- 测试的数据量有多大?
- HashMap 说一下。
- sleep 和 wait 的区别?
- Java 锁的实现方法有哪些?
- 内存泄漏是怎么回事?
- Redis 是什么?
- 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 加主从同步加业务侧补偿。