药明康德 AI 数据方向二面:LangGraph 与知识库方案深挖
- 轮次
- 二面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 你做了很多 Agent 框架,简历里提到 LangGraph,你的 state 具体是怎么设计的?里面放了哪些字段?节点返回之后是覆盖还是合并,讲下相关技术细节。
- 这套 state graph 是你们自己设计的,还是项目组里你只参与了一部分?
- 在 LangGraph 中你主要交付的是哪部分内容?
- LangGraph 里面条件边是怎么写的?异常分支怎么处理,是不是写了 flag?
- LangGraph 里用到断点跑、快照存储,怎么实现快照持久化、HITL 人工介入,中断之后如何恢复?
- 你了解 Pai 框架吗?讲讲 LangGraph 和 LangChain 的区别。
- 多智能体编排,宏观调度和微观执行做了两层拆分,解释这么做的好处与代价,为什么单框架不够、必须两层架构?
- 这个两层框架是由你来设计的,还是同事设计你来实现?
- Select group chat 里面 Select prompt 是怎么写的?如果 Agent 选错执行者、来回踢皮球,如何解决,靠 prompt 还是别的手段?
- 多智能体系统中,上下文以及 memory 记忆问题你们是如何解决的?
- 你们这套系统常用什么模型?国产模型用哪些?
- 结合 LangGraph 这套体系,从成本、准确率综合性价比看,中文场景下哪个模型效果比较好?是千问 3.8-7B 吗?
- 你们有做过模型微调吗?
- 工具副作用分级授权模型,两阶段提交 + 执行期零信任二次校验,具体指什么,如何落地?
- RAG 中向量、BM25、图谱多跳做 IF 融合,IF 怎么计算?为什么不用分数直接加和做融合?79%-95% 的评测指标是如何构建的?
- 如果搭建财务知识库,里面有制度文档、Excel 报表表格,不同文件怎么做 Chunking 切分?如何规避新旧版本制度冲突导致回答错误?
- 如果要搭建前面说的财务知识库,做到高精度,需要多长时间?
- 搭建这个财务知识库需要哪些硬件、技术条件?
- 你平时使用什么 AI 开发工具?
- Claude 和 Codex 二者有什么差别,哪个更好?
- 企业内部不能使用 Claude、Codex 这类国外工具,只能使用国产内部模型,会不会影响你的开发产出效率?
- 你是不是对写 prompt、skills 有一定心得?
- 你平时有没有关注最新的模型消息,会不会尝试用新模型来优化旧项目?
《参考解析》
LangGraph 的 state 设计与覆盖 / 合并:state 一般用 TypedDict 或 Pydantic 模型声明,里面的字段按「跨节点共享」来定:用户输入与消息历史、检索到的证据、当前计划与待办、工具调用结果、错误信息与重试计数、最终答案。关键细节是节点返回的是局部更新而不是整份 state,LangGraph 会把它合并回全局状态,而合并语义按字段决定:默认是覆盖(后写的赢),需要追加或累加的字段必须显式挂 reducer,例如消息用 Annotated[list, add_messages]、计数器用 Annotated[int, operator.add]。这就是为什么「并发分支」场景下不加 reducer 会丢数据——两个节点在同一 super-step 里都返回了同一个 key,覆盖语义下只有一个能留下。另外 state 必须可序列化(checkpointer 要落盘),把数据库连接、模型客户端这类对象塞进去,恢复时会直接失败。
条件边与异常分支:为什么不该靠 flag:条件边就是 add_conditional_edges(源节点, 路由函数, 目标节点映射),路由函数读 state 返回一个字符串(或返回 Send 做动态分发 / map-reduce)决定下一步去哪儿。异常处理的正确姿势是「把错误变成状态」:节点内部 try/except 捕获后写进 state 的错误字段并累加重试次数,再用条件边路由到重试节点、降级节点或直接 END;同时给图设 recursion_limit 兜住意外死循环。用 flag 从节点一路贯穿到出图的问题在于:状态机语义被削弱(外部看不出当前处于哪个阶段)、并发分支写同一个 flag 会互相覆盖、失败原因只能挤在一个 bool 里丢掉上下文,而且没法针对不同错误类型走不同恢复路径。
checkpoint 持久化与 HITL 恢复:checkpointer 在每个 super-step 结束把 state 快照写入后端(MemorySaver / SqliteSaver / PostgresSaver),按 thread_id 分组,一个 thread 就是一条可回放的执行历史。中断有两条路:编译时声明 interrupt_before/after,或在节点里调 interrupt() 主动抛出中断并携带要给人工看的信息。恢复时用同一个 thread_id 重新 invoke,并用 Command(resume=<人工输入>) 把结果喂回去;如果人审时想改状态,先 update_state 再继续。落地要处理的三个坑:①副作用幂等——重放会把「发邮件、写库」这类动作再做一遍,必须有幂等键;②恢复时状态里有不可序列化对象会失败;③快照表随步数膨胀,生产上要配 TTL 清理和独立的存储,别和业务库混用。它的价值不只是断点续跑,还包括失败重放、时间旅行调试和多轮会话记忆。
多智能体为什么拆两层,代价是什么:宏观调度层(supervisor / planner)负责把用户目标拆成子任务、决定交给谁、汇总结果并判断是否结束;微观执行层(worker)各自带一套专用工具和 prompt,只干自己那一摊。好处很直接:上下文隔离——每个 worker 只吃子任务相关的上下文,避免单 Agent 把全部工具描述和历史塞进一个 prompt 导致工具选错、注意力被稀释;职责单一后可以按任务换模型(规划用强模型、执行用便宜模型)、并行跑独立子任务、局部失败只重试那一步。代价也很真实:多一层就多一跳 LLM 调用,延迟和 token 成本都上去;信息在层间传递容易丢,所以交接要用结构化字段(任务描述、约束、期望输出格式)而不是让模型自由转述;错误归因变难,最后答错时说不清是规划错还是执行错;还可能出现「踢皮球」——路由函数在两个 worker 之间来回指,必须靠 max turns、路由白名单、终止条件这些硬约束兜底,光靠 prompt 里写「请谨慎选择」是不够的。所以判据是任务复杂度:步骤少、没有并行与专精需求时,单 Agent 加几个工具反而更稳,两层架构是给「长链路 + 多领域 + 需要并行」的场景准备的。
混合检索为什么用 RRF 而不是分数直接加和:向量相似度、BM25、图谱置信分是三套不同量纲的分数——余弦相似度在 0~1 之间且分布随 query 漂移,BM25 没有上界、随语料长度变化,图谱分数取决于跳数和路径权重。直接加权求和要先做归一化,而 min-max 归一化对离群值极其敏感、权重还得针对每类 query 调,线上经常出现「某一路分数天然偏大、永远霸榜」。RRF(Reciprocal Rank Fusion)只用排名:每个文档的得分是 Σ 1/(k + rank_i)(各路检索器各给一项,k 常取 60),天然无量纲、对异常分数不敏感、不需要调权重,实现也简单,所以工程上是默认首选;代价是丢掉了「第一名领先第二名很多」这类幅度信息,追求极致效果时会在 RRF 召回后用交叉编码器重排补回来。至于指标构建,说的是:从真实业务问题里分层抽样(单跳事实、多跳、跨文档聚合、应当拒答),对每条问题人工标注 gold 证据片段与标准答案要点,检索层看 recall@k / MRR / nDCG,生成层看要点命中与事实一致性(LLM 打分 + 人工抽检校准),报数字时必须带上评测集规模、分层口径和「是否含拒答样本」——否则 79% 到 95% 这种区间没有可比性。
财务知识库的切分与新旧版本冲突:制度文档和 Excel 表格不能用同一套切法。制度类(PDF/Word)按条款层级切:先解析标题层级,再以「章—条—款」为最小单元,超长条款按语义段落二次切分并保留父级标题路径作为元数据,这样召回出来的片段自带「出自哪份制度的哪一条」;纯按固定字数滑窗会把一条制度拦腰截断,答案必然缺条件。Excel 报表类要结构化入库:把 sheet 当作表,保留表头 + 行维度作为列名,一行一条记录转成「列名: 值」的文本片段(或直接落成 SQL 表让模型生成查询),因为表格的语义在二维结构里,线性化成一行行文字后数字和指标会错位;带合并单元格的多级表头要先把表头拍平成完整列名。版本冲突是这类知识库最容易出事的地方,工程上的做法是:①每份文档带生效日期、失效日期、版本号、适用范围这些元数据,入库时只把「当前生效」的版本标为可召回,旧版本保留但默认不检索(留档用于追溯);②检索结果里同一条制度的多版本命中时按生效日期择新,并在答案里显式标注「依据 2025 版《XX 管理办法》第 X 条」;③冲突检测放到离线——同一制度号的新旧版本做差异比对,生成变更摘要交业务确认;④时效性问题不要指望模型自己判断,正文里出现「自发布之日起施行」这类表述要在入库阶段就解析成结构化字段。