八、开发框架与工程实践
这一章考察框架选型的判断力和 LLM 应用的工程化底座。答题关键不是说 API,而是讲清楚每个抽象解决什么问题、什么场景该用 / 该弃,以及对可观测性、事务性这些生产硬指标有自己的落地经验。
82. LangChain 的核心抽象有哪些?(Chain、Agent、Retriever 等)各解决什么问题?
高频·基础
参考答案
LangChain 的核心抽象围绕"把 LLM 和外部世界组装成应用"展开:
- ChatModel / LLM:统一的多厂商模型接入层,一套代码切换 OpenAI / Anthropic / 自部署模型,解决厂商 API 异构问题;
- PromptTemplate:模板化管理和复用 Prompt,变量注入、few-shot 组织,解决 Prompt 硬编码问题;
- Output Parser / Structured Output:解析模型输出为结构化数据(JSON、Pydantic),并支持解析失败重试,解决输出不可控问题;
- Chain:把上面几步串成管道(prompt → model → parser)。新版本中由 LCEL(LangChain Expression Language) 承担——用管道操作符
|组合 Runnable 组件,统一了同步 / 异步 / 流式 / 批量接口,这是当前 LangChain 编排的核心; - Retriever:检索抽象,
invoke(query) → docs,把向量库、BM25、混合检索统一成统一接口,与 Chain 组合即成 RAG; - Tool:把函数包装成模型可调用的工具(name + description + parameters schema),供 Agent 使用;
- Agent(及 AgentExecutor):让 LLM 在循环中自主决定调用哪个 Tool、观察结果、继续决策,是 ReAct 模式的产品化封装(新项目中该角色已由 LangGraph 接替);
- Memory:会话历史的管理抽象(历史消息读写、注入),旧版核心组件,现多自行管理状态。
答题时建议补一句定位认知:这些抽象解决的是原型期的组装效率,深度定制时它的抽象层反而成为负担——这也顺势引出下一题"去 LangChain 化"。
面试官可能追问
- LCEL 相比旧的 Chain 类好在哪?(Runnable 统一接口:同步 / 流式 / 批量 / 异步一步到位,组合可测试性更好)
- Memory 抽象为什么逐渐被弱化?(会话状态本质是应用状态,放框架里管反而和业务状态割裂,LangGraph 用共享 State 取代)
83. LangChain 的优缺点?为什么很多团队用着用着就"去 LangChain 化"了?
高频·中等
参考答案
先给结论:LangChain 是最好的原型工具之一,但抽象层太厚,深度定制期收益转负,所以成熟团队普遍"Demo 用 LangChain,生产去 LangChain"。
优点:
- 生态集成最全:几百家模型 / 向量库 / 工具的连接器开箱即用,省大量胶水代码;
- 抽象统一:LCEL 编排 + 流式 / 异步接口一致,换模型换向量库改动小;
- 社区与文档活跃,例子多,上手快,和 LangSmith / LangGraph 形成工具链。
缺点与"去 LangChain 化"的原因:
- 抽象泄漏:出了问题要读三层封装才能定位到底是 Prompt、序列化还是 SDK 的问题,调试成本高——LLM 应用本来就难调试,框架再加一层黑盒;
- 升级破坏性大:历史上有多次大版本 API 重构(如 0.1 → 0.2 → 0.3 的包拆分、AgentExecutor 转向 LangGraph),生产代码被迫跟随迁移;
- 核心链路其实很薄:生产 LLM 应用的主干就是"拼消息 → 调 API → 解析 → 工具循环",几百行代码的事,框架的价值密度随项目成熟度下降;
- 定制受限:框架按它的假设组织代码,复杂状态管理、特殊重试策略、精细 Token 预算控制都要绕开框架实现,最后框架只剩连接器作用;
- 依赖链重:引入为传递依赖的安全问题和体积问题。
典型的"去化"路径:起步用 LangChain 快速验证 → 稳定后把核心链路换成自写的薄封装(直接调厂商 SDK),只保留
langchain-community里的集成或干脆自己写连接器 → 可观测性换 Langfuse(自部署、无厂商绑定)。也有反向操作:Agent 重构时直接迁到 LangGraph,因为图编排 + checkpoint 的复杂度自研不划算。
答题时给出明确立场加分:"我保留它的集成生态,剥离它的编排抽象,按模块取舍而不是全盘接受 / 拒绝"。
面试官可能追问
- 什么信号说明该去 LangChain 化了?(调试频繁读框架源码、为绕开抽象写的代码比框架本身还多、版本升级成本超过重写)
- 只用它的集成包算不算"还在用"?(算分层使用:连接器层价值仍高,编排层才是去化对象)
84. LangGraph 是什么?相比 LangChain 的 AgentExecutor 解决了什么问题?
必刷·中等
参考答案
LangGraph 是 LangChain 团队推出的图编排框架:把应用建模为状态图——节点(Node)是计算步骤(LLM 调用、工具执行、路由判断),边(Edge)是流转逻辑,全体节点共享一个可变的 State(如消息列表 + 业务字段),支持条件边、循环和分支。
相比 AgentExecutor(LangChain 经典的 Agent 运行时),它解决的问题:
- 可控性:AgentExecutor 是一个不透明的黑盒循环(模型决定 → 调工具 → 回给模型,直到模型不再调工具),循环逻辑无法干预;LangGraph 把循环显式画成图,哪一步可以重试、走哪条边、循环几次退出,都由开发者定义,可控性从"靠 Prompt 求模型"变成"代码保证";
- 复杂拓扑:多 Agent 协作、分支汇合、人工审批再继续、map-reduce 并行扇出——这些 AgentExecutor 表达不了(或要 hack)的结构,图模型天然支持;
- 持久化与恢复:内置 Checkpoint 机制,每步之后可存快照,长任务崩溃后从断点恢复、时间旅行调试,AgentExecutor 做不到;
- 人机协同:内置
interrupt原语,运行到指定节点可暂停等人工输入再继续(HITL),这是生产 Agent 的硬需求; - 多 Agent 结构:Supervisor、Hierarchical 等模式在图模型里就是子图嵌套与消息传递,官方提供了 langgraph-supervisor 等预置库。
适用判断:简单线性链用 LCEL / 纯 SDK 就够;有循环、分支、状态持久化、人工介入需求的 Agent 系统是 LangGraph 的主场。代价是学习与建模成本上升,要自己设计 State Schema 和图结构。
面试官可能追问
- LangGraph 和自己写一个 while 循环 + 状态对象有什么区别?(checkpoint / 恢复 / interrupt / 子图这些分布式与持久化细节自研成本高,图结构还带来可视化和调试红利)
- LangGraph 只能配 LangChain 的模型封装吗?(不是,节点里跑任意函数,可以直接调任意 SDK 或纯 Python 逻辑)
85. LangGraph 里 State、Node、Edge、Checkpoint 分别是什么?怎么用它实现人机协同和断点恢复?
中频·中等
参考答案
四个概念一句话:State 是共享黑板,Node 是加工步骤,Edge 是路由,Checkpoint 是存档点。
State:全图共享的数据结构,通常是 TypedDict / Pydantic 模型,最典型字段是
messages(消息列表)。关键机制是 reducer:每个字段声明合并策略(如messages用add_messages追加而非覆盖),保证多节点写入不互相踩;Node:普通 Python 函数,接收当前 State、返回 State 的增量更新。可以是 LLM 调用、工具执行、路由判断、纯业务逻辑;
Edge:节点间流转。普通 Edge 固定跳转;条件 Edge 接一个函数,按 State 返回下一个节点名,分支和循环都靠它表达(如"工具调用结束就回模型,否则去 END");
Checkpoint:每步执行后由 checkpointer(SQLite / Postgres / Redis 等)把完整 State 按线程(thread_id)持久化,是断点恢复、时间旅行调试、多轮记忆的基础。
人机协同(HITL):两种方式——
interrupt():在节点内调用,图执行到此处暂停并让出控制,等待外部输入;应用拿到用户的输入后用Command(resume=...)恢复执行,resume的值回到 interrupt 的调用点;- 断点(
interrupt_before/interrupt_after某节点):执行到指定节点前 / 后停下,常用于"工具执行前人工审批"——检查 / 修改 State 后用更新后的 checkpoint 继续跑。
断点恢复:因为每步都有 checkpoint,进程挂掉后用同一
thread_id重新invoke(None),图从最后成功的步骤继续,不重放已完成节点。会话多轮对话也基于此:同一 thread 的 State(含 messages)自动延续。
工程细节:checkpoint 里含完整消息历史,注意敏感字段加密与存储成本;thread_id 设计要与业务会话 ID 对齐,便于审计回放。
面试官可能追问
- 条件 Edge 和在 Node 里直接返回不同结果有什么区别?(路由逻辑集中在图定义里可审计可可视化,散在节点里会隐式化控制流)
- 恢复时外部环境已经变了(如工具结果过期)怎么办?(按业务在恢复节点做状态校验与失效重算,checkpoint 保状态不保外部世界一致性)
86. LlamaIndex 和 LangChain 的定位差异?数据密集型应用更该选哪个?
中频·基础
参考答案
定位差异一句话:LangChain 是通用 LLM 应用编排框架,LlamaIndex 是数据接入与检索(RAG)特化框架。
LlamaIndex 的重心在"数据 → 索引 → 查询":
- 数据连接器(readers)覆盖 PDF、网页、Notion、数据库等几十种来源,统一成 Document;
- 索引抽象丰富:向量索引、关键词 / BM25 索引、知识图谱索引、树索引等;
- 内置检索策略与查询引擎:子问题拆解、路由多索引、父块检索(small-to-big)、句子窗口检索等 RAG 进阶玩法开箱即用;
- 也提供 Agent 与 workflow 能力,但生态重心始终在数据侧。
LangChain 的重心在"编排":模型 / 工具 / 记忆的组合、LCEL 管道、Agent 循环,加上 LangGraph / LangSmith 构成的全家桶;RAG 能力有但不如 LlamaIndex 分层细致。
选型建议:
- 数据密集型(知识库问答、文档分析、企业搜索)优先 LlamaIndex:它的数据摄取管道和检索抽象更成熟,切分策略、节点后处理、引用溯源这些 RAG 关键环节封装到位,能省大量自研代码;
- 流程密集型(多工具 Agent、复杂业务流、多 Agent 协作)优先 LangChain / LangGraph;
- 两者也可以组合:用 LlamaIndex 做 Retriever,嵌入 LangChain / LangGraph 的编排里——LlamaIndex 的检索器可被包装成 LangChain 的 Retriever 接口。
补充一个务实观点:两者都偏"原型友好",生产化的考量(可控性、调试、升级成本)与第 83 题相同,选型之外还要回答"哪些部分未来要自己接管"。
面试官可能追问
- 句子窗口检索是什么?(检索以句为单位保精度,生成时扩展为该句上下文窗口保完整性,精度与上下文的折中方案)
- 只做 RAG 不用任何框架行不行?(行,几百行代码可实现主链路,框架价值主要在数据连接器和进阶检索策略的现成实现)
87. Dify、Coze 这类低代码平台适合什么场景?能力上限在哪里?
中频·中等
参考答案
适合的场景:流程标准、需求长尾、迭代要求快的业务侧自助开发。
- 企业内部知识问答、标准客服 FAQ:拖拽配置 RAG(上传文档 → 切分索引 → 发布应用)当天上线;
- 长尾轻应用:营销文案生成器、会议纪要助手、表单预填,业务人员自己搭,不占研发排期;
- 快速 PoC 与 A/B:产品经理直接验证想法,跑通后再决定是否投入工程资源自研;
- Coze 更偏 C 端 / 轻生态(插件市场、Bot 发布到各渠道),Dify 更偏企业私有化(支持自部署、多租户、模型接自有网关)。
能力上限(什么时候必须离开低代码):
- 复杂控制流:复杂条件分支、动态规划、循环 + 中断恢复这类逻辑,画布表达不了或画出来无法维护;平台工作流的循环 / 异常处理能力明显弱于代码;
- 深度定制:精细的 Token 预算控制、自定义记忆策略、特殊重试与降级、复杂的多 Agent 交互协议,平台参数不开门就是做不了;
- 工程治理:版本管理、自动化测试、CI/CD、代码 review 这些软件工程能力,在拖拽画布上严重缺失,变更不可 review、不可回归;
- 性能与成本优化空间有限:请求路径固定,无法做激进的缓存、批处理、模型路由优化;
- 迁移与锁定:平台 DSL 不通用,业务复杂后被平台能力锁死,重写成本随时间增大。
务实的定位:低代码平台是"需求漏斗的上层过滤器"——80% 的长尾轻需求终结在平台里,剩下 20% 高价值复杂需求由代码接管。团队最好预先约定升级路径(什么复杂度阈值触发自研),避免在平台里堆出无法维护的"画布怪物"。
面试官可能追问
- Dify 私有化部署要注意什么?(模型网关对接、向量库选型、知识库更新的管道、多租户权限与审计)
- 怎么评估低代码搭的应用要不要迁自研?(看变更频率、流程复杂度、测试需求三个指标,任一持续超阈值就迁)
88. 自研框架 vs 开源框架怎么选?你们为什么选(或为什么不选)LangChain / LangGraph?
高频·中等
参考答案
选型框架:按项目阶段、团队规模、需求复杂度三个维度决策,而不是按个人好恶。
- 选开源框架的理由:编排、持久化、HITL、可观测性这些共性问题已解决,避免重复造轮子;社区踩坑在前;招人上手快。适合 0→1 阶段、小团队、需求还在快速探索的项目;
- 选自研的理由:核心链路薄(消息组装 → 调用 → 解析 → 工具循环,几百行),自研后每一行都可调试可定制;无升级与锁定风险;性能与成本优化空间完全打开。适合链路稳定、有平台团队、需求深度定制的项目;
- 主流的成熟形态是混合:自研薄编排层 + 复用开源组件。例如:核心执行循环与状态管理自写,直接调厂商 SDK;Agent 编排与 checkpoint 用 LangGraph(这块自研性价比低);连接器按需引
langchain-community或自己写;可观测性用 Langfuse(开源可自部署,避免绑定 LangSmith)。
如果面试官问"你们为什么选 LangGraph":图编排显式化控制流、内置 checkpoint 与 interrupt、节点内可以完全不用 LangChain 抽象,恰好补上自研循环里最贵的三块(持久化、恢复、人机协同),且被绑定的面很小。如果问"为什么不用":需求只是线性 RAG 管道时引入图框架是过度设计;或者团队已有沉淀好的自研运行时,迁移无收益。
答题要点:给出明确选择 + 当时的量化依据(如"引入 LangGraph 后长任务断点恢复开发从预估 2 周降到 2 天")+ 承认付出的代价(学习成本、版本升级),体现这是权衡而非站队。
面试官可能追问
- 自研编排最容易漏做的是什么?(幂等重试、超时治理、全链路 trace、Prompt 版本管理这些"非功能"部分)
- 框架升级出破坏性变更你们怎么应对?(锁版本 + 抽象出自己的防腐层,升级走评测集回归)
89. LLM 应用的可观测性怎么做?LangSmith、Langfuse、OpenTelemetry 的实践了解吗?
中频·中等
参考答案
LLM 应用的可观测性要回答三个问题:这次回答为什么是这样(trace 回放)、质量趋势如何(指标)、钱和延迟花在哪(成本性能)。
Trace 模型:核心是树状 span——一次请求为一个 trace,下面嵌套 LLM 调用、检索、工具调用、rerank 等子 span,每个 span 记录输入输出、Token 用量、延迟、模型名、Prompt 版本、检索命中率所需的中间数据(召回的 chunk 及分数)。没有 trace 的 LLM 应用等于盲飞,bad case 无法归因。
工具选型:
- LangSmith:LangChain 官方,trace + 数据集 + 评估 + Prompt 管理一体化,与 LangChain / LangGraph 集成最深;SaaS 为主,数据出境是顾虑;
- Langfuse:开源可自部署,trace + Prompt 管理 + 数据集评估 + 用量分析,SDK 不绑定 LangChain(裸 SDK 也可埋点),国内合规敏感团队的主流选择;
- OpenTelemetry:不是 LLM 专用,是通用遥测标准。价值在于把 LLM span 并入公司统一监控体系(与 DB、RPC 的 trace 串联),另有 semconv 生成式 AI 语义约定在推进;自建平台型方案的地基。
典型架构:Langfuse(或 LangSmith)做 LLM 应用层观测 + OpenTelemetry / Prometheus / Grafana 做基础设施层(QPS、错误率、P95 延迟)+ 业务指标(采纳率、转人工率)单独上报,三层用 trace ID 关联。
落地要点:
- 埋点覆盖全链路而非只记模型调用:检索 query、召回结果与分数、工具入参出参、最终组装的完整 Prompt,这些是归因的关键;
- 记录 Prompt 版本与模型版本,效果回归才能定位是哪次变更引入的退化;
- 采样策略:全量 trace 成本高,通常错误请求全采 + 正常请求按比例采样;
- 敏感信息脱敏后再上报(用户 PII、密钥),观测数据本身要过合规审查;
- 建看板与告警:Token 成本日环比、错误率、P95 延迟、拒答率 / 人工接管率的突变告警。
面试官可能追问
- trace 里 Prompt 全量记录会不会太贵?(可截断存储 + 按需展开全量,或只对错误与采样请求存全文)
- 在线指标怎么和离线评估闭环?(线上 bad case 一键入评估集,Prompt / 模型变更跑回归后再发布)
90. LLM 应用的事务性和幂等怎么保证?工具执行到一半服务挂了怎么办?
低频·较难
参考答案
先说结论:LLM 应用拿不到数据库那种 ACID 事务,工程上靠"幂等 + 断点恢复 + 补偿"组合逼近最终一致性。
为什么难:Agent 一个任务 = 多次 LLM 调用 + 多次工具调用(可能有副作用:发邮件、写库、付款)+ 中间状态,任何一步挂掉,已执行的副作用无法回滚,未执行的步骤丢失上下文;LLM 调用本身不确定性高,重试还可能得到不同决策。
幂等设计(事前):
- 请求级幂等键:客户端生成请求 ID,服务端去重(Redis setnx),同一请求重发只执行一次;
- 副作用工具全部幂等:写操作带唯一业务键(用 upsert 代替 insert)、发邮件带 messageId 查重、支付走幂等下单接口——工具层幂等是整个方案的地基,因为恢复必然伴随重放;
- 步骤级幂等记录:每个工具执行前先写"意图日志"(intention),执行后写"结果",恢复时先查结果表,已成功的直接跳过。
断点恢复(事中):
- 状态持久化:用 LangGraph 这类带 checkpoint 的运行时(每步后自动存 State),或自研时在每步后把完整中间态落库;
- 恢复语义:服务重启后按会话 / 任务 ID 加载最后 checkpoint,从断点节点继续执行,已成功节点靠幂等记录跳过,不重复执行副作用;
- 关键节点先行(write-ahead 思路):先落库"即将做什么",再执行,崩溃后恢复时能区分"打算做但没做"与"做了没记结果"。
补偿与对账(事后):
- 不可补偿的操作(付款)前置人机审批或独立核对,可补偿的操作(发消息)设计反向操作;
- 定时对账任务:比对本方记录与实际外部系统状态,发现"记了没做成 / 做了没记"两类不一致并自动补 / 告警。
"工具执行到一半挂了"的具体处置:以"调 API 发邮件"为例——执行前写 intention(含唯一键),调用带超时,崩溃重启后:查结果表 → 有记录则跳过;无记录且外部系统支持按唯一键查询,则先查实际状态(可能已发出)再决定跳过或重发;两边都无法确认时标记该步骤为"待人工核对",宁可停也不盲目重试造成重复副作用。
一句话总结:幂等保证重放安全,checkpoint 保证恢复位置,意图日志保证执行确定性,对账兜住一切漏网——四层防线叠出来的"准事务"。
面试官可能追问
- LLM 调用本身要不要幂等?(纯生成调用天然可重试;但注意重试会改变后续决策路径,恢复后建议从 checkpoint 的既有输出继续而非重新生成)
- 分布式多 Agent 场景怎么办?(每个 Agent 独立 checkpoint + 消息队列的 exactly-once / 至少一次 + 消费幂等,任务级 saga 编排补偿)
