恒生电子面经-恒生电子AI Agent开发岗面经-01
- 轮次
- 面试合集
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
(面经 01)
- JVM 的垃圾回收机制介绍一下?
- 聊聊你对 MySQL 索引的理解?什么场景适合建索引?什么场景不适合?
- MCP 了解过吗?介绍一下?
- 你使用 AI 进行开发时候,有没有一套自己的工作流?使用裸的开发 Agent,还是说会搭配一些 skill、spec、workflow?
(面经 02) 5. 实习 6-8 月项目:你做过最难 / 最有成就感的工作是什么? 6. A 君 Agent 项目:是入职前就存在,你只是参与而非主导开发吗? 7. Agent 选型:为什么不基于 local agent,而是选择软管 & rough? 8. 开源 Agent 框架:Masquerade、DeepAgent、DSH 是否了解? 9. 框架选型:能不能直接基于 Deepseek Agent 做开发?会存在什么问题? 10. 项目技术选型是谁决定的? 11. Redis 基础:Redis 有哪几种数据结构? 12. Redis 持久化:持久化分哪几种?各自优缺点? 13. Redis AOF:什么场景下 AOF 会丢数据? 14. ThreadLocal 在线程池复用场景会有什么问题? 15. ThreadLocal 数据污染问题,有什么解决方案 / 相关框架?
(面经 03) 16. Python 的装饰器机制是什么?结合一个实际场景说明 17. 被装饰函数同时包含位置参数、关键字参数等参数时,怎样处理? 18. 有参数和无参数的装饰器有什么区别? 19. InnoDB 执行 UPDATE 时,不同 Key 的加锁机制是什么? 20. MySQL 为什么使用 B+ 树,而不是 B 树? 21. B+ 树叶子页双向链表对范围检索有什么优势? 22. MCP 一次交互的完整过程是什么?特别说明客户端流程 23. 客户端同时请求 MCP 的三类能力时,服务端如何决定响应顺序并协调? 24. 常见的上下文管理机制有哪些? 25. 上下文被删除后,用户偏好记忆怎么办? 26. 陪伴类 Agent 中,长期记忆如何在合适时机检索?系统怎么知道此刻应该取出”用户喜欢吃苹果”? 27. RAG、传统检索生成、模型微调有什么区别,各适合什么场景? 28. 知识库存在过时知识和内容冲突,并且限定知识库无法治理时,要构建问答 Agent,应选择哪种策略? 29. 用户 Query 明确指向旧知识时,怎样避免被改写到新知识库? 30. 介绍一个项目,它解决了什么问题? 31. 受控编辑如何解决 LLM 不安全写入? 32. 为什么选择当前方案,是否考虑过其他方案,为什么放弃? 33. 如何证明受控编辑比 LLM 直接修改效果更好?使用什么指标,怎样评估? 34. 使用过哪些 Agent 框架或自研框架,它们有什么设计巧思? 35. 为什么使用 LangGraph 状态图,不自研? 36. LangGraph Checkpoint 是否与当前业务状态发生冲突,怎样处理?
(面经 04) 37. 听你提到 AI 工具能大幅提升开发迭代效率。你平时最常用的 AI 编码工具是什么? 38. 谈谈你对 Python 装饰器的理解,它的核心作用是什么?举一个你在实际项目中使用装饰器的场景,说明它解决了什么问题? 39. 你提到装饰器可以在不修改业务代码的前提下增强功能。那如果要给一个带位置参数、关键字参数,还有返回值的业务函数加统计执行耗时的装饰器,你会怎么处理被装饰函数的参数传递和返回值透传问题 40. 你刚才提到会用判断做参数校验,那你知道 Python 中用来实现装饰器时,能自动保留被装饰函数源信息,比如函数名文档字符串的标准库装饰器是什么吗? 41. 创建联合索引时字段的放置顺序,会不会影响查询效率?为什么? 42. 如果我有一个联合索引 abc,查询条件是 b 等于 1 and c 等于 2,这个查询能用到这个联合索引吗? 43. 让大模型稳定输出结构化数据,如 JSON 是 AI 应用开发常见需求,但模型有时会输出多余的解释文字或格式不合法,你会用哪些手段提升结构化输出的稳定性和可解析性 44. 如果你在提示词里已经明确要求,只输出 JSON 不要额外解释,但模型依然在 JSON 前后附带自然语言说明,你会用什么方式
《参考解析》
JVM 垃圾回收机制
先讲判断存活:主流用可达性分析,从 GC Roots(栈上引用、静态变量、常量、JNI 引用)出发标记,不用引用计数是因为它解决不了循环引用。再讲分代假设:对象大多朝生夕死,于是堆分新生代(Eden 加两个 Survivor,默认 8:1:1)和老年代,新对象在 Eden 分配,Minor GC 存活对象经 Survivor 之间复制并每次年龄加一,默认熬过 15 次或对象过大、动态年龄判定通过就晋升老年代。算法上,新生代用复制、老年代用标记-整理(CMS 用标记-清除,代价是碎片)。收集器演进要说得出主线:Serial 单线程、ParNew 与 Parallel Scavenge 关注吞吐、CMS 关注低停顿但会并发失败与碎片、G1 把堆切成 Region 按回收收益优先收集并支持预测停顿、ZGC 与 Shenandoah 用染色指针和读屏障把停顿压到毫秒级。调优落点是三件事:堆大小与新生代比例、选择收集器与目标停顿、以及用 GC 日志或 JFR 找内存泄漏与频繁 Full GC 的根因,而不是凭感觉调参数。
MySQL 索引:什么时候适合、什么时候不适合
适合建索引的场景:列在 WHERE、JOIN ON、ORDER BY、GROUP BY 里高频出现;区分度高(性别这种只有两三个值的列选择性差,索引扫描还不如全表);经常用于范围查询且排序方向可利用索引有序性避免 filesort;以及多列组合查询可以靠联合索引的最左前缀覆盖。不适合的场景:表数据量很小、写远多于读且列选择性极低、列频繁更新(更新索引有维护成本)、以及大字段(TEXT/BLOB)做前缀索引之外的全列索引。还要讲清代价:索引占空间、每次写要维护 B+ 树(页分裂与合并)、优化器可能选错索引导致更慢。实践要点是联合索引顺序按「等值列在前、范围列在后」排,尽量做覆盖索引减少回表,用 EXPLAIN 的 key 与 rows 验证,并定期用 sys.schema_unused_indexes 之类的方式清理长期未被使用的索引——索引不是越多越好。
MCP 协议与一次交互的完整流程
MCP 是 Anthropic 提出的开放协议,用来把模型与外部能力的对接标准化,架构是 Host(宿主应用,比如 IDE 或客户端)内含 Client,Client 与 Server 一对一连接,Server 暴露能力。三类能力要分清:Tools(服务端提供的可调用函数,由模型决定调用并传入参数)、Resources(可读取的数据,类似文件或记录,由应用决定放进上下文)、Prompts(预设的提示模板,通常由用户显式触发)。底层用 JSON-RPC 2.0,传输可以是 stdio(本地子进程,最常见)或基于 HTTP 的 SSE / Streamable HTTP(远程)。一次交互的完整过程:客户端启动时先 initialize 握手协商协议版本与能力,收到 initialized 通知后调 tools/list、resources/list、prompts/list 拿到能力清单并转成模型可理解的工具定义;模型决定调用后,客户端发 tools/call 带名字与参数,服务端执行并回 content 数组(文本、图片或结构化内容),客户端把它作为工具结果角色回填给模型继续推理,直到模型不再发起调用。客户端同时请求三类能力的协调问题,本质上是并发请求与顺序语义:协议本身用 request id 支持并发与乱序返回,服务端不该假设到达顺序;要保证顺序就得由客户端串行编排或在工具结果里带上依赖标识,把顺序作为业务约束而不是协议保证。
AI 编码工作流:裸 Agent 还是 skill / spec / workflow
这道题考的是有没有把 AI 当工程工具用。可以直接分层讲:裸 Agent 适合小改、探索性问答、一次性脚本;一旦进入有既定规范的仓库,就要靠上下文文件(AGENTS.md 或项目规则、编码约定、目录级注意事项)把「团队常识」喂进去,否则每轮都要重复解释。skill 适合把可复用的操作流程固化下来(发布流程、数据库变更、某类排障步骤),避免每次重新推导;spec 适合先写清接口契约与验收标准再让模型实现,把「实现前先对齐需求」这一步前移;workflow 适合确定性的多步流水线,把命令、校验、提交顺序写死,让模型只在需要判断的环节介入。核心原则是:确定性的事交给脚本与流水线,需要判断的交给模型;并且每一步都要有可验证的产出(编译、测试、构建、lint),让模型自己看到反馈再迭代。还要补一条风险控制:AI 生成的代码必须人工评审关键路径,尤其是并发、权限、金额这类出错代价高的部分,测试与静态检查是最低保底。
Redis 数据结构与持久化
数据结构常见五种基础类型:String(计数、缓存对象、分布式锁)、Hash(对象字段级读写,比整对象序列化省带宽)、List(双端队列,做简单消息队列或最新列表)、Set(去重与集合运算,如共同关注)、ZSet(排行榜、延迟队列,靠 score 排序)。进阶结构要提:Bitmap(签到)、HyperLogLog(基数统计,误差约 0.81%)、GEO(附近的人)、Stream(带消费组的消息流)。持久化两种:RDB 是某一时刻的全量快照,文件紧凑、恢复快,适合备份与冷启,缺点是两次快照之间的数据会丢、fork 时可能有写时复制的内存与停顿压力;AOF 记录写命令,fsync 策略 always/everysec/no 分别对应几乎不丢、最多丢一秒、交给操作系统,恢复慢但丢得少,体积大所以要配重写(rewrite)压缩。4.0 之后推荐混合持久化,AOF 重写时把 RDB 格式作为全量头加增量命令。AOF 什么场景会丢数据:everysec 下宕机最多丢约一秒的写;磁盘写满、fsync 报错而 Redis 在配置允许继续写时会丢;fork 或重写期间缓冲区过大以至于进程崩溃;以及主从切换时未同步的从节点数据可能被截断。生产上要监控主从偏移与持久化状态,关键数据配 WAIT 或直接用 RDB 加外部 binlog 类方案兜底。
ThreadLocal 在线程池复用下的数据污染
ThreadLocal 的实现是每个 Thread 持有一个 ThreadLocalMap,key 是 ThreadLocal 对象本身(弱引用),value 强引用。问题出在线程池:线程被复用,任务执行完不清理,下一个任务在同一个线程上就能读到上一个任务留下的值——典型事故是用户身份、租户 ID、链路 traceId 串号,导致 A 用户的数据按 B 用户的权限被查出来。第二个问题是内存泄漏:ThreadLocal 对象被回收后 key 变 null,value 仍被 Entry 强引用,只要线程不死(池化线程就是常驻)这块内存就回收不掉,ThreadLocalMap 只在 get/set 时顺带清理部分空 key,属于被动回收。解法:使用处一定用 try-finally 调 remove(),或者在框架层用拦截器在请求结束时统一清理;更稳的是别把可变上下文塞进 ThreadLocal,改成显式传参(把上下文作为方法参数或 RequestContext 对象传递),能从根上避免串号。需要跨线程传递时用 InheritableThreadLocal(仅新建线程继承)或阿里开源的 TransmittableThreadLocal,后者通过装饰任务提交动作把上下文快照带进线程池任务,可以配合线程池的 TtlExecutors 包装使用。
Python 装饰器与参数透传
装饰器本质是「接收函数、返回函数」的高阶函数,@decorator 等价于 func = decorator(func)。无参装饰器直接接收被装饰函数;带参装饰器要多一层闭包——外层接收配置参数、返回真正的装饰器,所以写成三段嵌套,调用时要写成 @retry(times=3)。透传参数与返回值靠 *args, **kwargs 与 wrfapper:def wrapper(*args, **kwargs): return fn(*args, **kwargs),这样位置参数、关键字参数、默认参数都能原样传进去,返回值也能原样返回;如果装饰器是生成器函数或 async 函数,还要用 yield from 或专门写 async wrapper,否则会把协程对象当普通返回。必须用 functools.wraps(fn) 保留 name、doc、module 与 wrapped,否则日志、序列化、框架按名字查找的地方都会出问题——这也是「自动保留源信息的标准库装饰器是什么」的答案:functools.wraps。另一个常见考点是装饰顺序:多个装饰器自上而下应用、自下而上执行(离函数最近的先包裹)。类方法、静态方法、带参数的实例方法要注意 self/cls 也在 args 里,用类实现装饰器(call)时可以只在实例化时接收配置、在 call 里接收函数。
InnoDB 的 UPDATE 加锁与 B+ 树为什么胜出
InnoDB 的加锁对象是索引记录,所以 UPDATE 走哪条索引决定了锁的粒度。命中唯一索引或主键的等值条件时是记录锁(Record Lock);条件是范围或非唯一索引等值时,为避免幻读会加间隙锁(Gap Lock)或临键锁(Next-Key Lock,记录锁加区间锁);没有可用索引会退化成全表扫描并锁住扫过的所有记录,这是线上最容易造成大范围阻塞的情况。还要区分快照读(普通 SELECT 走 MVCC 不加锁)与当前读(UPDATE/DELETE/SELECT FOR UPDATE 读最新版本并加锁),以及插入时靠插入意向锁与唯一键冲突时的共享锁。实践建议:UPDATE 的 WHERE 一定要走索引、尽量用主键或唯一键定位、把大事务拆小以减少持锁时间、并按固定顺序更新多行以降低死锁概率。B+ 树优于 B 树的点:非叶子节点只存键不存数据,同样大小的页能容纳更多键,树更矮、磁盘 IO 次数更少;所有数据都在叶子层,查询路径长度稳定,不像 B 树那样数据可能出现在内部节点导致性能波动;叶子节点用双向链表串联,范围查询与 ORDER BY 顺序扫描即可,不用反复回到上层;以及它是多叉平衡树、高度通常只有三到四层,几千万行也能三次 IO 命中。补充一句 InnoDB 的主键即聚簇索引,二级索引叶子存主键值所以要回表,覆盖索引能免掉这一步。
上下文管理与长期记忆的检索时机
上下文管理机制可以按「压缩、检索、隔离、外置」四类说:压缩包括滑动窗口、摘要(把早期对话摘要成一段)、按重要性淘汰(保留系统提示与关键约束);检索就是 RAG 式的按需注入,把长期记忆放向量库或 KV 里,每轮用当前 query 召回相关片段;隔离是子 Agent 单独上下文、只回传结论(subagent 与 handoff 的常见做法);外置是把结构化状态放进外部存储(用户档案、任务看板、LangGraph 的 State 与 Checkpoint),每轮重新装配而不是靠对话历史携带。上下文被删除后用户偏好怎么办:偏好这类稳定信息不该只活在对话历史里,要落到独立存储(用户档案表或 profile 记忆库)并带来源与时间戳,摘要删除不影响它;同时要能区分「临时上下文」与「长期偏好」两类生命周期,前者可以随时丢,后者要显式写入与显式失效。陪伴类 Agent 的长期记忆检索关键是「不是每次都把所有记忆塞进去」,做法有两层:写入时抽取结构化记忆(subject-predicate-object,例如「用户-喜欢-苹果」,连同场景标签、情绪、时间),检索时用当前 query 做向量召回加规则过滤(话题命中标签、时间衰减、重要性打分),再由模型对候选做一次 rerank 或判断「这条此刻是否相关」。系统怎么知道此刻该取「用户喜欢吃苹果」:靠触发条件而不是靠记忆本身——要么是当前 query 提到食物或点餐场景(语义召回),要么是规则命中(对话进入「推荐餐品」这个流程时按需拉取用户画像),要么是定时或事件驱动(每次新会话开场时注入高优先级偏好)。要把「召回」与「注入」分开讲:召回多了会挤占窗口并干扰,所以要有预算与优先级,宁少而准。
RAG、微调与知识库冲突的选型
三者的差别按「改不改模型参数、知识放哪里」讲最清楚:传统检索生成(基于关键词或 BM25 的检索加拼接)不依赖向量语义,专有名词与代码符号匹配准,但对同义改写和口语提问不友好;RAG 用向量或混合召回把外部知识放进上下文,知识更新只改库、可溯源、成本低,缺点是召回质量决定上限、长文档受窗口限制、对推理型问题帮助有限;微调改的是模型权重,适合固化风格、格式、领域术语与输出习惯(比如稳定产出某种结构),但不适合灌入频繁变更的事实知识,且成本高、迭代慢、容易灾难性遗忘。选型顺序通常是先 RAG 打底,再对「格式与风格不稳」的部分做微调或提示词工程,把两者组合起来(微调管表达、RAG 管事实)。知识库存在过时知识与冲突且无法治理时,策略是「不改知识库、改查询与呈现」:一是保留多版本知识与时间戳,检索时按用户 query 里的时间线索或版本号优先召回对应版本,命中旧版本时如实按旧版本回答并标注生效时间;二是给文档打来源与可信度权重,冲突时按来源权威性与新旧排序,并在答案里显式说明存在不同版本;三是把「旧知识优先」做成查询侧的显式约束——用户明确问旧版本时,改写阶段锁定版本过滤条件,不让查询改写把「旧政策」扩写成「新政策」,这一点要靠改写器与过滤器解耦(改写只做同义扩展,版本过滤由规则单独施加),并在评估集里专门加一类「指向旧知识」的测试用例来守住。
受控编辑与效果评估指标
LLM 不安全写入指的是让模型直接生成写入指令(改代码、改配置、改数据)时,它可能越权、破坏结构、覆盖无关内容或写出不可回滚的变更。受控编辑的思路是把「生成」与「施加」分离:模型不直接落盘,而是产出一个受约束的编辑意图(结构化的 patch 或操作对象,限定在允许的字段与范围内),由确定性的执行器校验并施加。校验清单包括:语法与 schema 合法(能解析成预期结构)、范围白名单(只能改目标文件或目标字段,跨范围直接拒)、前置条件(版本号或哈希匹配,防止基于过期内容改写)、幂等与可回滚(保留原内容以便撤销)、以及危险操作的二次确认。这样模型的能力用在理解与生成上,风险控制交给代码。证明它比模型直接改更好,指标要能分层:正确性用编辑成功率与最终产物通过测试的比例、任务完成率;安全性用越权写入次数、破坏性变更次数、需要人工回滚的比例;效率用平均交互轮数与耗时;稳定性用同一输入的修改方差、以及重复运行的一致性。评估方式建议做成对照实验:同一批任务两组跑(直接改 vs 受控编辑),固定模型与提示词,用自动化校验加人工抽检打分,并特别构造对抗样本(要求删除无关文件、跨越范围修改、基于旧版本改写),看受控路径是否都能拦住。汇报时给绝对数与差值,不给「感觉更稳」。
LangGraph 状态图与 Checkpoint 冲突
选 LangGraph 而不是自研,理由要落到工程事实上:它把 Agent 流程建模成有向图加共享 State,节点是函数、边是控制流,天然支持条件分支、循环、并行与中断恢复;内置 Checkpointer 做状态持久化,能实现人工介入(interrupt 后续跑)、崩溃恢复与时间旅行回溯;有流式输出与事件机制、支持子图与多 Agent 协作,这些能力自研要花几周且容易在并发与恢复上留坑。代价要一并说:框架抽象带来调试成本、State 的 schema 与 reducer 设计不当会互相覆盖、版本升级有破坏性变更,所以核心业务状态建议仍放在自己的库里,框架只管流程状态。Checkpoint 与业务状态冲突的典型形态有三种:一是同一份数据两处写,图恢复后把库里的新值覆盖回旧值;二是外部副作用已经发生(短信已发、订单已建)而图回滚重放,导致重复;三是并发分支各自写 State 的同一字段,最后写入者胜出丢数据。处理办法:把「事实来源」定死——业务数据以自己数据库为准,Checkpoint 只存流程进度与必要引用(ID 而不是全量对象),节点恢复时从库里重新读;把副作用做成幂等(带业务唯一号)或放进「执行前先查是否已完成」的守卫;State 字段用显式 reducer 处理并发合并,或按字段拆给不同分支写;恢复时用版本号或乐观锁校验,发现外部状态已前进就跳到相应节点而不是重放。还要给恢复设计可观测性:记录每个节点的输入输出摘要与线程 ID,出问题能对账。