面灵AI→

快手后端一二面:Agent 执行链路与 SSE 推送、RAG 检索判断、索引下推与引用类型

轮次
一二面
时间
2026-10
来源
牛客网

《面试题目》

  1. 介绍一下你自己。
  2. 讲一下 Agent 的完整执行链路。
  3. Agent 最大执行 20 步和 RAG 最多补查 3 轮有什么区别?
  4. Agent 异步执行后,如何接收执行结果并通过 SSE 推送到前端?
  5. 请求线程直接返回后,为什么还能继续推送模型的输出?
  6. 不同用户之间如何实现隔离?不同用户的会话如何区分?
  7. topK 是如何设置的?有什么依据?
  8. 检索结果能不能回答用户的问题,由谁来判断?
  9. RAG 的完整执行流程是什么?
  10. 你的向量数据库目前有多大规模?检索相关性如何判断,用的是欧式距离吗?
  11. Agent 的思考与执行模式基于什么思想?
  12. 这个项目是自己做的还是基于网上项目改造的?第二个项目你改造了哪些部分?
  13. 将用户信息从 ThreadLocal 改为 RequestScope,有什么区别?
  14. 如果有几亿用户使用这个系统,每次提问和回答都存数据库吗?你会如何优化?
  15. 如果只把会话上下文放在缓存中,用户过一段时间回来继续聊天,缓存已经过期了怎么办?
  16. MySQL 的事务隔离级别有哪些?分别解决了什么问题?MVCC 的原理是什么?
  17. 缓存穿透、缓存击穿和缓存雪崩有什么区别?分别如何解决?
  18. HashMap 的底层原理是什么?线程池的核心参数有哪些?
  19. 线上出现慢 SQL,你会如何排查和解决?什么情况下索引会失效?
  20. 手撕:力扣 128 最长连续序列。
  21. 二面:Agent 知识库项目如何防止恶意会话请求、避免用户绕过权限?
  22. 稠密向量和稀疏向量有什么区别?
  23. Java 中的强引用、软引用、弱引用和虚引用有什么区别?
  24. 谈谈你对 ThreadLocal 的理解。
  25. 什么是索引下推?
  26. Lock 和 synchronized 有什么区别?synchronized 除了修饰方法还可以怎么用?
  27. 公平锁和非公平锁有什么区别?ReentrantLock 如何实现?非公平锁具体如何抢锁?
  28. 手撕:力扣 560 和为 K 的子数组。

《参考解析》

「20 步上限 vs 补查 3 轮」考的是你分不分得清两种控制流:Agent 步数是整个「思考-调用工具-观察」循环的全局预算,防的是无限循环与成本失控;RAG 补查轮数是检索子流程内部的局部预算,防的是「检索不到就一直换 query」的死循环。前者超限要终止任务并给用户交代,后者超限要带着已有证据作答或明确说「资料里没有」。两者都要有单调递减的计数器和可观测的日志。

SSE 推流为什么能「请求线程返回了还在推」:HTTP 响应是一个流,Servlet 3 的 AsyncContext 或 Spring 的 SseEmitter/ResponseBodyEmitter 把请求线程释放回容器,但连接和输出流仍然活着,后台线程(或响应式流)继续往里写;关键是超时与清理——设置 emitter 超时、客户端断开时移除监听、写出失败要吞掉并清理资源。用 WebFlux / Flux 更自然,因为整条链路是非阻塞的。若前端在网关后面,还要关掉代理缓冲(X-Accel-Buffering: no)、设置 text/event-stream 与心跳。

用户隔离与几亿用户的存储:会话按 userId + conversationId 分区,向量库按租户字段过滤(metadata filter),缓存 key 带用户前缀,权限校验在检索前而不是生成后。海量问答不要全量入库热库——冷数据归档到对象存储 / ClickHouse,热会话放 Redis 并设 TTL,正文与向量分表;写入走异步队列批量落库。缓存过期后「用户回来接着聊」要靠落库的最近 N 轮对话重建上下文,而不是假设缓存还在。

topK 的依据要说成实验与预算共同决定的:先用 20 测召回率,再看上下文 token 预算与延迟,最终取 5~10 并配 rerank;相关性判断可以是分数阈值 + 让模型在生成前先判断「资料是否足够」,不足则明确拒答而不是硬编。

索引下推(ICP):MySQL 5.6 起在存储引擎层就用索引中的列做条件下推,减少回表次数——例如 where name like '张%' and age = 20 在联合索引 (name, age) 上,age 的过滤在引擎层完成再看是否需要回表,EXPLAIN 的 Extra 里出现 Using index condition。四种引用:强引用(不回收)、软引用(内存不足时回收,适合缓存)、弱引用(下次 GC 必回收,ThreadLocalMap 的 key 用它)、虚引用(只为回收时收到通知,配合 ReferenceQueue 管理堆外内存)。ThreadLocal 每个线程一个 ThreadLocalMap,key 是弱引用的 ThreadLocal 本身,value 强引用,所以线程池复用时必须 remove() 防内存泄漏与串号。公平锁靠 hasQueuedPredecessors() 判断队列里有没有更早的等待者,非公平锁在 lock() 时先 CAS 抢一次再入队,这正是它吞吐更高的原因(减少线程唤醒的上下文切换),代价是可能饿死。