Skip to content

LangChain+LangGraph 面试题 - 2026-06-14 23:49 ​

【面试题】1. LangChain 中 Chain 和 LCEL(LangChain Expression Language)的区别是什么?什么时候该用 Chain 而不是 LCEL? ​

【标准答案】 简单说,Chain 是旧版写法,LCEL 是新版声明式语法。

Chain 是面向对象模式,定义类、重写方法,适合复杂定制;LCEL 是管道式,把 | 当作调用链串联。LCEL 有三大优势:内置流式输出、异步支持、并行执行自带,代码更简洁。

实际选择:快速原型用 LCEL,团队协作的复杂项目需要高度定制时才用 Chain。新项目默认 LCEL 就对了。


【面试题】2. LangGraph 中 add_edge 和 add_conditional_edges 的核心区别是什么?请举例说明conditional在什么场景下必须用? ​

【标准答案】 add_edge 是固定跳转,从节点 A 直接到节点 B,可控可预测。

add_conditional_edges 是动态路由,根据 state 内容决定下一步去哪。

必须用 conditional 的典型场景:

  • 意图分类后分流:客服场景,识别到"投诉"走投诉链路,"查询"走查询链路
  • LLM 返回的 action 判断:tool 调用返回结果后,判断是继续调用还是结束
  • 循环上限控制:比如重试 3 次失败才走错误处理分支

核心区别就是:固定流程用 edge,动态决策用 conditional edges。


【面试题】3. 在 LangGraph 中实现多轮对话 Memory 记忆系统,你一般怎么设计状态(State)? ​

【标准答案】 State 设计三件套:

python
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]  # 对话历史,自动增量
    user_profile: dict                          # 用户画像,可跨会话
    session_context: dict                       # 当前会话上下文

关键点:add_messages 这个 Annotated 列表,底层是 MessagesToList,每次新消息 append 而不是覆盖,这样历史完整保留。检索时用滑动窗口或 token 上限截断。

跨会话的持久化用 Checkpointer,基于 SQLite 或 PostgreSQL,自动存图状态。下次用户来,load 快照直接恢复对话上下文,用户无感知。


【面试题】4. LangChain 的 Retriever 有哪些类型?在 RAG 场景下你如何选型? ​

【标准答案】 常见四类:

  1. VectorStoreRetriever —— 最常用,基于向量相似度查 top-k,秒级召回
  2. MultiQueryRetriever —— LLM 扩展 query,从多角度召回,解决表述差异问题
  3. ContextualCompressionRetriever —— 先查再压缩,过滤无关 context,节省 token
  4. EnsembleRetriever —— 混合召回,结合 BM25 稀疏检索 + 向量检索,取长补短

选型逻辑:

  • 知识库规模大、语义相似重要 → VectorStoreRetriever + cosine 相似度
  • 用户 query 表述随意 → MultiQueryRetriever 扩展
  • context 经常超长 → 加 ContextualCompression
  • 追求召回质量上限 → EnsembleRetriever

【面试题】5. 用 LangGraph 写一个 Agent 循环时,如何防止死循环?有哪些兜底机制? ​

【标准答案】 三层兜底:

第一层:循环次数限制

python
max_iterations = 5
def should_continue(state):
    return "tool_call" if state["step"] < max_iterations else "end"

通过 step 计数器控制,超过就强制退出。

第二层:重复检测 维护一个 seen_actions 集合,如果连续 3 次返回相同的 tool call,直接终止。可以用 add_messages 的 message 哈希来比对。

第三层:LLM 自我判断 在每次循环末尾,让 LLM 评估"当前结果是否已足够回答问题",返回置信度标识,低于阈值才继续。

工程上通常三层都上,再配一个超时兜底,稳如老狗。


【面试题】6. 场景题:让你从 0 搭建一个客服 Agent,处理售前咨询和售后投诉两类请求,你会如何设计 LangGraph 的图结构? ​

【标准答案】 三段式设计:

第一段:意图识别节点 单一 LLM 调用,返回结构化意图标签(pre_sale / after_sale / other)

第二段:条件分支

python
add_conditional_edges(
    "intent_classifier",
    lambda state: state["intent"].value,
    {
        "pre_sale": "pre_sale_flow",
        "after_sale": "after_sale_flow",
        "other": "handoff_to_human"
    }
)

第三段:各自子图

  • pre_sale_flow:查产品库 → 匹配需求 → 推荐 → 转化
  • after_sale_flow:查订单 → 诊断问题 → 退款/换货/工单

子图内部也用 conditional 处理分支。这样结构清晰,后续加新意图只要加分支,修改一个意图不影响另一个。


【面试题】7. LangChain 中 Tool 的返回值格式有什么讲究?为什么 Tool 返回值设计不好会导致 Agent 行为异常? ​

【标准答案】 Tool 返回值本质是给 LLM 看的文本,所以格式必须遵循两个原则:

原则一:结构化但简洁 返回 {"status": "success", "data": {...}} 比直接吐 dict 好。LLM 需要知道操作成功还是失败,也需要数据本身。

原则二:必须包含 action summary Tool 名 + 结果摘要,LLM 据此决定下一步。如果只返回原始数据,LLM 要自己理解,浪费 token 且容易误判。

常见坑:

  • 返回 None 或空字符串 → LLM 不知道发生了什么,陷入困惑循环
  • 返回超长原始输出 → 上下文爆炸,token 爆炸
  • 不标记错误状态 → LLM 以为成功,继续用错误数据推理

最佳实践:所有 Tool 统一包装成 ToolMessage,带 status + summary + data 三个字段。


【面试题】8. LangGraph 的断点续跑(Breakpoint)是怎么实现的?生产环境中你用它解决什么问题? ​

【标准答案】 断点续跑靠 interrupts API 实现。原理是在某个节点前插入中断点,图执行到这里就暂停,状态持久化到存储(Redis/PostgreSQL)。外部可以检查状态、修改 state,甚至切换分支,然后手动 resume 继续执行。

生产场景三大用法:

  1. 人工审核节点:涉及金额、退款、敏感操作,先中断让客服人员确认后再继续
  2. 分支确认:LLM 分类不确定时,中断让用户二次确认意图
  3. 故障恢复:服务重启后,从最近的 checkpoint 恢复,不丢失长流程进度

实现上核心是 interrupts_before 或 interrupts_after,配合 state_graph.update_state() 可以中途改 state,非常灵活。


【面试题】9. 你在项目中遇到过 LangChain/LangGraph 的哪些坑?是如何解决的? ​

【标准答案】 三个高频坑:

坑一:Streaming 不生效 原因:用了 .invoke() 而不是 .stream(),或者 callback 没有正确传递。解决:确保用 stream() 配合 ConsoleCallbackHandler,或者在 LCEL 链里检查是否有 RunnableGenerator 节点。

坑二:多 Tool 调用时顺序执行慢 原因:默认串行。解决:用 RunnableParallel 或在 LangGraph 里把独立 Tool 用 node 并行执行,配合 asyncio.gather。

坑三:Memory 随对话无限增长 原因:messages 列表只增不减。解决:上线滑动窗口(保留最近 N 条)+ token 预算截断,或者定期 clear 历史。

核心原则:本地先跑通,再用 streaming 测试、压测内存占用,上线前必做边界 case 验证。


【面试题】10. 面试官追问:"如果让你优化一个慢速 RAG 链路,从 Query 进来到最终输出答案,你从哪些维度入手?" ​

【标准答案】 五个优化维度,逐层递进:

1. 召回层

  • 换更快的 Embedding 模型(text-embedding-3-small)
  • 改用 HNSW 索引代替 IVF,召回速度提升明显
  • 加 reranker 做二次排序,top-k 放大但最终精度高

2. Context 层

  • 用 ContextualCompression,去掉无关 chunks
  • 控制 chunk 大小(512~1024 tokens 最佳)

3. Prompt 层

  • 精简 system prompt,去掉废话
  • few-shot 示例控制在 3 个以内

4. LLM 推理层

  • 开启 streaming,体感会快很多
  • 用 vLLM / TGI 推理引擎,吞吐翻倍

5. 工程层

  • 异步并行:召回和生成可以部分并行
  • 缓存:热门 query 的 answer 缓存起来,命中率 30%+ 很常见

实际做法:先打日志找瓶颈在哪,瓶颈通常在召回或 LLM 推理,一个一个改,不要同时改。