LangChain入门指南:核心组件、LCEL、RAG与Agent实战
1. LangChain入门先搞懂它到底在解决什么问题1.1 没有LangChain的时候我们是怎么写代码的先聊点实在的。你在接触LangChain之前肯定写过一段直接调大模型接口的代码大概长这样import openai openai.api_key sk-xxx def chat_with_gpt(prompt): response openai.ChatCompletion.create( modelgpt-4, messages[ {role: system, content: 你是一个智能助手}, {role: user, content: prompt} ] ) return response.choices[0].message.content result chat_with_gpt(介绍一下LangChain) print(result)这段代码跑通完全没问题网上很多教程教你写的东西也差不多到这个程度。但一到真实项目里麻烦事就一个接一个冒出来。你要接一个外部工具API比如查天气、搜数据库你得自己处理工具调用的逻辑和返回结果的解析。你要做一个多轮对话得自己管理历史消息的拼接messages数组越来越长Token消耗越来越高最后还要面对上下文窗口被塞满的窘境。你要做一个知识库问答那就更头疼了——文档加载、文本切分、向量化、向量存储、相似度检索、把检索结果拼进Prompt里这一整套流程全部自己写大概要几百行代码而且你还得在不同向量库之间做适配。这些零零碎碎的活儿本质上是把大模型接入业务系统时所必需的管道工程。管道的每一段都有成熟的第三方库可以替代但如何把它们可靠地串联起来需要一套完整的编排逻辑。LangChain就是在这样的背景下出现的。1.2 LangChain和LangGraph、vLLM、Ollama到底是什么关系在这个话题上网上搜索量最大的一个问题就是langchain、vllm跟pytorch框架是一个类型吗有什么区别。这个问题的出现说明你在LangChain入门阶段就得先把生态位搞清楚。直接给结论它们不是一个层级的东西。PyTorch是大模型训练的底层计算框架负责张量运算、自动求导、模型权重管理这是整个AI大厦的地基。vLLM是模型推理加速引擎它解决的是模型训练完之后怎么高效地对外提供推理服务的问题核心卖点是PagedAttention显存管理、高吞吐并发推理。Ollama是模型部署工具它把模型打包成一条命令就能跑起来的本地服务主打的是极简部署体验。OpenAI是大模型服务的提供商你通过API调用它的模型。LangChain则是应用开发编排层它不关心模型底层推理性能也不关心模型权重怎么存储它专注于把大模型API业务代码外部工具向量库记忆机制这些零部件编排成一条完整的应用链路。如果你用盖房子来类比PyTorch就相当于钢材水泥的生产设备vLLM相当于混凝土搅拌车OpenAI的API相当于装修好的毛坯房而LangChain是帮你做整体装修、水电布局、功能分区的那套设计方案和施工流程。经常有新手在同一个项目里既装了vLLM又装了Ollama又装了LangChain社区版的模型包结果环境冲突一堆然后来问是不是我框架选错了。大概率不是你选错了是你没搞清楚每层在做什么。2. 核心设计拆解五个核心组件撑起整个LangChain生态2.1 LangChain基础架构的六个核心模块LangChain的架构从设计之初就是模块化的整个生态由几个核心模块组合而成分别是Models模型层、Prompts提示词层、Chains链层、Agents智能体层、Memory记忆层、Indexes索引层。在向量化存储场景下索引层承担文档加载、切分、向量化和检索的完整链路也是RAG应用的基础。这个模块化设计带来的直接好处是每个模块都是可替换的。你今天用OpenAI的模型明天想换成本地部署的模型只需要改一行代码。之前用Chroma作为向量库后来发现数据量大了想换Milvus也只需要换一个类。这种插拔式设计对实际项目非常重要因为你无法预判半年后公司的基础设施会变成什么样。2.2 Model层的封装为什么别人都说LangChain过度封装打开LangChain的源码你会发现Model层其实做了很多事。以ChatOpenAI为例当你在代码里初始化这个类的时候它已经帮你处理了API Key的加载、超时重试机制、模型参数的默认配置、多轮对话消息格式的转换等逻辑。有一些人批评LangChain过度封装逻辑太黑盒代码出了问题不好排查。这个批评确实有道理。我的建议是在开发调试阶段尽量多用message打印中间结果看每一步的入参出参遇到问题不要被报错信息吓到报错再长核心信息往往就一两句。不同模型在LangChain中的接入方式也比较统一ChatOpenAI负责OpenAI接口协议兼容的服务包含使用国内各家大模型厂商提供的OpenAI兼容网关HuggingFacePipeline负责走本地推理管线Ollama通过ChatOllama类接入本地模型。这种统一接口设计让你换模型的时候不用改动业务逻辑代码只要改初始化部分就可以。2.3 Prompt模板和输出解析器说完了Model层接下来要说的是Prompt管理。实际场景中你不会像第一条代码那样手动拼字符串而是用LangChain的PromptTemplate来做这件事。from langchain_core.prompts import ChatPromptTemplate prompt_template ChatPromptTemplate.from_messages([ (system, 你是{company}的智能客服请用{style}的风格回答用户问题), (user, {user_question}) ]) formatted_prompt prompt_template.format_messages( company某电商平台, style专业且耐心, user_question我的订单已经付款了为什么还没发货 )为什么要用模板而不是直接拼字符串几个很现实的原因。第一是可维护性。你业务里可能有一百个Prompt每个Prompt经过反复调试后长度可能达到上千字如果全部散落在代码里用f-string拼接后面优化Prompt的时候改起来非常痛苦还容易把代码结构弄乱。把Prompt统一收拢成模板文件项目经理和运营人员也能直接参与修改不依赖开发同学。第二是变量管理的规范性。用户在对话里输入的内容和业务系统传进来的结构化数据都通过模板变量注入后续要加敏感词过滤上下文注入等逻辑时集中在一个位置就能解决。配合Prompt模板的另一半是OutputParser输出解析器。大模型返回的是自然语言文本但业务代码往往需要结构化数据。比如让模型抽取一段会议纪里的参会人名单你希望返回的是JSON数组而不是一段有前后缀说明的文字。from langchain_core.output_parsers import CommaSeparatedListOutputParser from langchain_core.prompts import PromptTemplate parser CommaSeparatedListOutputParser() format_instructions parser.get_format_instructions() prompt PromptTemplate( template列出所有参会人员的姓名。\n{format_instructions}, input_variables[format_instructions] )常见的解析器有StrOutputParser直接返回文本字符串CommaSeparatedListOutputParser返回列表PydanticOutputParser把输出解析成Pydantic结构体适合字段较多时的结构化输出JSONOutputParser返回JSON对象。PydanticOutputParser我建议重点掌握因为它在接后端服务时特别好用。注意让大模型输出严格合规的JSON时偶尔会出现字段缺失或JSON格式不完整的问题。使用PydanticOutputParser能让解析器在解析失败时提供清晰的错误信息配合重试机制比自己在业务代码里写一大堆正则去解析可靠得多。3. LCEL表达式语言LangChain的核心灵魂3.1 管道符写法和背后的设计哲学LangChain开发到后期官方主推的写法是LCELLangChain Expression LanguageLangChain表达式语言。它不是一门新语言只是Python里的一种链式调用语法核心就一个管道符|。from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser model ChatOpenAI(modelgpt-4o-mini, temperature0.7) prompt ChatPromptTemplate.from_messages([ (system, 你是精通{field}的专家), (user, 帮我解答这个问题{question}) ]) chain prompt | model | StrOutputParser() result chain.invoke({ field: Python编程, question: Python的GIL锁对多线程有什么影响 }) print(result)这段代码里prompt是一个带两个变量的模板对象model是一个模型实例StrOutputParser()是一个输出解析器对象它们通过管道符|连成了一条执行链。每次执行chain.invoke()时输入字典会先流进prompt把模板里的变量都替换成实际内容生成一条完整的消息列表然后传给模型模型返回的响应对象再流进解析器最终得到纯文本字符串。这个设计非常像Unix命令行的管道思想——cat file | grep keyword | sort | uniq每个组件只做一件事组件之间通过约定好的输入输出格式衔接。LangChain官方特别强调LCEL的三大优点方便并行执行、方便流式输出、方便异步调用。你可以在链上直接使用chain.stream()拿到逐字返回的流式内容这对开发聊天机器人是刚需能力。3.2 RunnablePassthrough和RunnableParallel的妙用在实际项目里链上的数据流转不会总是单一方向的有时你需要把原始输入原封不动地在链上传递下去有时你需要同时执行两个不同路径的任务再把结果合并。RunnablePassthrough直通的作用是不做什么变换直接返回输入内容。有个经典的使用场景是做RAG问答链路时把用户原始问题一字不改地传递给最后的生成模型同时把检索到的上下文也传过去这样模型才知道用户问的原始问题是什么。RunnableParallel并行的作用是把同一个输入同时分发给多个处理单元最后合并各个单元的输出。比如对一篇文章同时做摘要、提取关键词、打标签三个任务互不依赖就可以并行执行显著提升处理时长。from langchain_core.runnables import RunnableParallel, RunnablePassthrough def extract_keywords(text): return [LangChain, LCEL, RAG] chain RunnableParallel( summarysummary_chain, keywordslambda x: extract_keywords(x[content]), original_contentRunnablePassthrough() )实际项目里数据流一旦复杂起来并行和直通这两个工具的使用频率非常高属于必需掌握的技巧。3.3 LCEL和普通Python函数的取舍之问有朋友在讨论时提到我自己写一个Python函数按顺序调用模型、拼字符串效果难道和LCEL不一样吗为什么非要学这个新语法 我的回答是确实可以不一样但对大多数项目而言LCEL带来的收益远大于学习成本。第一点是统一接口。LCEL链本质上是一个Runnable接口它支持同步调用invoke也支持异步调用ainvoke还支持流式输出stream以及批处理batch。如果你自己手写函数这些能力要么不去实现要么得为每个函数单独写一套最终代码量会膨胀得很厉害。第二点是原生支持LangSmith的可视化追踪。LangSmith是LangChain官方的调试和监控平台它能把一条链上每一步的输入输出都记录下来。这是凭经验手写代码很难获得的能力一旦你的链路涉及多步骤排查问题时这类可视化追踪的价值非常大。第三点是更高的复用性。LCEL链本身可以当作组件内嵌到更大的链中配合RunnableParallel等操作符组合方式非常灵活调试时也更容易定位问题。4. LangChain RAG实战从文档加载到知识库问答的实现4.1 文档加载和文本切分RAG流程的入口RAGRetrieval-Augmented Generation检索增强生成是目前LangChain应用最广泛的方向。其核心思路是不让大模型凭空编造答案而是先从一个外部知识库中检索出相关内容再把检索到的内容拼进Prompt让模型基于参考内容来回答。完整RAG链路的第一步是文档加载。LangChain提供了层出不穷的加载器覆盖PDF、Word、Markdown、网页、数据库等多种数据源。from langchain_community.document_loaders import PyPDFLoader loader PyPDFLoader(./product_manual.pdf) documents loader.load() print(f加载了{len(documents)}页文档)文档加载完后你基本都会面对一个现实问题原始文档太长直接塞进Prompt会被Token上限卡住就算勉强塞进去模型也只会把注意力放在开头和结尾中间的内容很可能被忽略。所以你需要做文档切分。LangChain官方推荐的切分器是RecursiveCharacterTextSplitter它按照一个可配置的分隔符优先级列表来执行递归切分依次对段落、句子、空格进行尝试直到块的大小符合要求。from langchain_text_splitters import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , ., !, ?, , ,, , ] ) chunks text_splitter.split_documents(documents) print(f切分成了{len(chunks)}个文本块)这里有两个参数需要仔细调chunk_size控制每个文本块的最大字符数chunk_overlap控制相邻文本块之间的重叠字符数。为什么要设置重叠因为一个完整的知识点可能刚好被切分在边界上如果不重叠这个知识点就可能会丢失完整性放上重叠部分能在很大程度上保留上下文连贯性。from langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS embedding_model HuggingFaceEmbeddings( model_nameBAAI/bge-large-zh-v1.5 ) vectorstore FAISS.from_documents(chunks, embedding_model)注意embedding模型的选择直接影响检索质量。常见的中文场景里BGE系列和多语言模型是比较好用的。我个人实际测试bge-large-zh-v1.5对中文语义理解能力明显优于部分通用型模型。你先确认自己的显存或内存是否能扛住模型体量再决定用哪个。4.2 向量化存储和检索从语义相似度说起向量化就是把文本变成一串数字数组本质上是把语言改成计算机能计算的数学坐标。这个过程的玄妙之处在于语义接近的两段文本它们在向量空间里的位置也是接近的于是检索就变成了找距离最近的一组向量。如何衡量距离最常用的是余弦相似度计算的是两个向量在方向上的接近程度还有欧式距离衡量的是向量在空间里的直线距离。选哪种度量方式需要和你用的向量数据库保持一致否则检索结果可能出现偏差。LangChain把向量检索封装成一个Retriever对象retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 4} )search_kwargs里的k控制每次召回多少条最相似的文档片段。这个参数也需要按场景调优。知识库问答场景k设4到8基本够用如果是合同审查这种需要严谨性的场景建议k设大一点同时把后续的Prompt调整为只允许基于提供的文档内容回答不要使用用户提供的文档中没有的信息。4.3 完整RAG链路从检索到生成的串联组装完整链路的代码如下from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough from langchain_openai import ChatOpenAI prompt ChatPromptTemplate.from_messages([ (system, 你是企业内部知识库助理。请只基于以下提供的参考内容回答用户问题。 如果参考内容中没有提到答案请直接回复抱歉在知识库中未找到相关信息。\n\n 参考内容\n{context}), (user, 问题{question}) ]) model ChatOpenAI(modelgpt-4o-mini, temperature0.3) def format_docs(docs): return \n\n.join([doc.page_content for doc in docs]) rag_chain ( {context: retriever | format_docs, question: RunnablePassthrough()} | prompt | model | StrOutputParser() ) answer rag_chain.invoke(公司年假制度是怎么规定的) print(answer)这条链的执行流程不再只是单向直通。第一步把用户的原始问题同时分发给两条路径一条路径通过retriever从向量库里检索出相关文档再用format_docs函数对文档做格式化拼装另一条路径通过RunnablePassthrough原样保持用户问题。接下来把这两部分内容分别填入Prompt模板里的context变量与question变量喂给模型最后得到字符串形式的答案。RunnablePassthrough在这里的作用就是保证用户原始问题不被前面环节干扰原封不动地进入Prompt模板。4.4 关于向量库选型的经验做RAG离不开向量库。LangChain支持Chroma、FAISS、Milvus、Pinecone、Weaviate、Qdrant等众多向量数据库。如果只是个人项目、学习入门的阶段FAISS或者Chroma就够了。FAISS是Meta开源的向量检索库本地单机跑的方案部署简单根本不需要启动额外服务。Chroma的特点是轻量、支持持久化适合快速做原型验证。如果到了生产环境数据量大、并发高、需要分布式扩展Milvus和Qdrant这些专业的向量数据库会是更好的选择。选型时没有捷径可走你需要结合数据量、QPS、运维成本、团队技术水平做综合权衡。4.5 和MCP的关系以及和前后端工具的生态位置最近关于langchain prompt rag mcp的搜索多了起来。MCP是Model Context Protocol模型上下文协议它本质上定义了大模型应用和外部工具、数据源之间的统一通信协议。如果说LangChain做的事情是把能用的零件组装成流水线那么MCP做的事情是把零件的接口统一成标准尺寸两者定位层面不同未来会形成互补。现在LangChain已经增加了对MCP工具的支持你可以在Agent中直接调用符合MCP协议的工具。5. Agent实战让LangChain模型学会自己用工具5.1 Agent和普通Chain的核心区别普通的Chain比如前面写的RAG链流程是写死的用户提问检索知识库拿参考内容拼接Promot最后生成答案。它没有自主判断的能力如果你问的问题知识库里没有它只会给你一个等心的兜底回复。Agent智能体则不同。Agent让模型充当决策中枢面对用户任务时它先思考需要借助什么工具然后调用工具获取结果根据结果再决定下一步做什么如此循环直到任务完成。和Agent相关的还有几个高频对比词智能体和skill的区别和LangChain的关系。Agent是具备感知-决策-行动循环的智能单元skill则是智能体可复用的某项技能模块。你可以把Agent理解成一个员工把skill理解为这个员工掌握的某项技能比如熟练使用搜索引擎能编写Python脚本等。Agent负责调度skill负责执行具体动作。LangChain既是构建Agent的框架也是这类概念的早期普及者。5.2 一个完整的LangChain工具调用代码定义一个工具最常见的方式是用LangChain Tools体系的装饰器from langchain_core.tools import tool import datetime tool def get_current_time() - str: 获取当前的日期和时间格式为YYYY-MM-DD HH:MM:SS。 return datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) tool def calculate(expression: str) - str: 计算数学表达式例如2*35返回计算结果。 try: return str(eval(expression, {__builtins__: {}}, {})) except Exception as e: return f计算错误: {e}这里两件事很重要。第一件给每个工具写的docstring非常重要因为模型就是靠你的docstring来理解这个工具是干什么的、什么时候应该用的。第二件calculate工具里的eval调用在实际生产环境一定要慎用否则可能引入安全问题这个例子本身确实使用了危险的简单实现如果有需求建议改用表达式解析安全库。接下来创建Agentfrom langchain_openai import ChatOpenAI from langchain.agents import create_tool_calling_agent, AgentExecutor tools [get_current_time, calculate] model ChatOpenAI(modelgpt-4o-mini, temperature0) prompt ChatPromptTemplate.from_messages([ (system, 你是一个智能助手可以借助工具帮助用户完成任务。), (user, {input}), ]) agent create_tool_calling_agent(model, tools, prompt) agent_executor AgentExecutor(agentagent, toolstools, verboseTrue) response agent_executor.invoke({input: 今天是什么日子3天后的日期是多少}) print(response[output])注意AgentExecutor内部会执行一个循环模型分析输入生成工具调用指令AgentExecutor执行工具拿到结果后把对话历史和工具返回值重新交给模型模型继续判断任务是否完成完成后才输出最终回复。verboseTrue参数可以让你在控制台看到这个推理过程的完整日志新手阶段一定要打开否则遇到Agent行为不符合预期时很难排查。5.3 Agent实战中的两大坑Agent代码比普通Chain要复杂实战中踩坑的概率也高很多这里重点说两个高频问题。第一个问题模型一直不调用工具或者反复调用同一个工具。这个问题的根源大概率在Prompt设置。模型选择工具的依据是系统Prompt加上工具的docstring描述如果你写的工具描述过于含糊比如写成计算一下模型可能不知道该在什么时候触发它。建议把描述写得更明确一些例如当用户提出涉及数学计算的问题时使用本工具。另外模型温度也要尽量调低温度过高会让模型的决策不稳定。第二个问题Agent陷入死循环不停地调用工具却停不下来。我遇到过最常见的原因是工具返回的格式不符合模型预期模型读不懂结果就只能再次调用。排查手段很简单把verbose打开看最后一次工具返回的内容到底是什么是不是返回了模型无法理解的报错信息。还有一个兜底方案是在AgentExecutor里设置max_iterations参数比如max_iterations5超过次数就强制终止循环避免消耗过多Token费用。6. 记忆机制多轮对话怎么让LangChain不失忆6.1 无状态模型的对话困境使用过模型API的开发者都会发现大模型本身是不带记忆的。每轮答调用它看到的只有当前请求中messages列表里的内容。要实现还记得我刚才说过什么这种体验就需要把历史对话信息一并传给模型。最原始的做法是维护一个messages数组每次都把整个对话历史塞进去。但一个现实问题是随着对话轮次增多历史记录会越攒越多Token消耗呈线性上涨。当消息长度接近模型上下文窗口上限时API直接报错服务不可用。所以记忆机制不只是存下来这么简单还要考虑怎么在有限的上下文里保留最有效的信息。6.2 几种LangChain记忆方案的对比LanguageChain提供了多套记忆管理方案各有各的取舍第一种是ConversationBufferMemory把它理解成一个数组回放全部历史消息原文实现简单、信息完整但Token消耗最大适合短期对话场景。第二种是ConversationBufferWindowMemory只保留最近N轮对话超过N轮的直接丢弃。这种方案控制Token消耗很直接但会丢掉更早的关键信息。第三种是ConversationSummaryMemory把已经过去的对话内容做摘要只把摘要最近几轮原文传给模型。Token消耗控制得很好还能保留比较关键的历史信息但代价是摘要本身有额外的大模型调用费用而且摘要过程有信息损失。第四种是VectorStoreRetrieverMemory把历史消息转换成向量后存入向量库需要时按相关性召回。这种方式能走RAG的检索逻辑比较适合对历史信息有精确检索需求的场景例如客服系统里查找用户之前的反馈但实现复杂度会高一些。6.3 LangChain记忆实战示例推荐一种简洁的写法用RunnableWithMessageHistory配合session_id管理from langchain.memory import ChatMessageHistory from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder model ChatOpenAI(modelgpt-4o-mini) prompt ChatPromptTemplate.from_messages([ (system, 你是一个友好的聊天助手。), MessagesPlaceholder(variable_namehistory), (user, {input}) ]) chain prompt | model store {} def get_session_history(session_id: str) - ChatMessageHistory: if session_id not in store: store[session_id] ChatMessageHistory() return store[session_id] chain_with_history RunnableWithMessageHistory( chain, get_session_history, input_messages_keyinput, history_messages_keyhistory ) result1 chain_with_history.invoke( {input: 我叫小李我喜欢爬山。}, config{configurable: {session_id: session-001}} ) print(result1.content) result2 chain_with_history.invoke( {input: 我刚才介绍了什么爱好}, config{configurable: {session_id: session-001}} ) print(result2.content)注意一下运行原理。RunnableWithMessageHistory是自动管理历史的玩法每次invoke的时候get_session_history返回session_id对应的消息历史对象链执行前自动把历史消息填充到MessagesPlaceholder的位置执行完成后把最新的对话内容追加到历史中整体流程清晰可控。生产环境里store里的内存字典建议替换成Redis等外部存储否则服务重启后所有session历史都会丢失。在真实项目里我遇到过因为记忆存储太简单导致会话状态丢失的问题后来在分布式环境下不得不尽快从内存存储切换到按session隔离的Redis方案。注意记忆方案的Token消耗不能忽略建议在每次链执行前后对消息列表打印长度观察整个服务里的Token用量。否则一旦线上用户并发多了费用账单会教你做人。7. LangGraph和LangChain的区别7.1 为什么LangChain要搞一个LangGraph出来LangGraph是LangChain团队后来推出的一个低层级编排框架可以简单理解成给LangChain应用加上了状态机的控制能力。很多人会问LangChain本身不是已经能写Agent了吗为什么还要再整一个LangGraph出来答案在于LangChain早期版本的AgentExecutor在对复杂场景的控制上偏弱。它把模型要不要调用工具、调哪个工具、调用结果怎么处理都封装在一个黑盒循环里你只能通过verbose打印日志观察想干预内部执行流程很费劲。真实业务场景经常会有这样的需求第一步先做敏感信息检测第二步调用检索工具第三步做答案生成第四步做事实校验如果校验不通过回退到第二步重新检索。这种多分支、状态可流转的场景用一个有向图来描述是最自然的。LangGraph就是干这个的它让你把应用流程定义成一个图结构节点里运行函数或模型边负责控制状态流转方向。7.2 LangGraph和LangChain的具体区别两者的关系可以这样概括LangChain提供的是开箱即用的高层组件LangGraph提供的是更底层、更灵活、更可控的编排能力。LangGraph自己构建在LangChain的基础模块之上两者不是替代关系而是互补关系。还是拿做饭做类比。LangChain是那种预制菜料理包打开包装按说明加热就能上桌快但可发挥空间有限。LangGraph则更像是高级厨房里的整套厨具和调料架——每种工具都能用但你得自己规划做菜的流程和火候。它给了你把零件自由组装成复杂应用的能力。从选型角度看如果你的业务场景是标准的RAG问答比较固定的流程编排用LangChain现成的LCEL链路就够了写起来快出活率高。如果你的业务流程分支多、有循环判断、需要支持人在环审核、需要精细控制每一步那用LangGraph更合适它来做这类事情体验好太多。7.3 简单感受一下LangGraph的代码风格为了让你直观理解贴一段LangGraph的最小节点图示例from langgraph.graph import StateGraph, START, END from typing import TypedDict class State(TypedDict): messages: list def node_a(state: State): return {messages: state[messages] [处理A]} def node_b(state: State): return {messages: state[messages] [处理B]} graph_builder StateGraph(State) graph_builder.add_node(a_node, node_a) graph_builder.add_node(b_node, node_b) graph_builder.add_edge(START, a_node) graph_builder.add_edge(a_node, b_node) graph_builder.add_edge(b_node, END) compiled_graph graph_builder.compile() result compiled_graph.invoke({messages: [开始]}) print(result[messages])StateGraph接收一个state定义节点函数接收state并返回state的更新内容边表示节点之间的流转关系。这套思路在有明确流程形态的应用里比AgentExecutor的隐式循环要清晰得多。如果你有精力我建议从最简单的RAG链路开始用LangGraph重写一遍体验到每一步都尽在掌控的感觉后你就能理解为什么LangChain团队现在把主线精力放在LangGraph上了。8. 面试高频题和避坑指南8.1 常见问题与排查技巧总结按照技术社区里大家问得最多的问题整理了一份速查表现象大概率原因排查方向调用模型时报无效ApiKey环境变量没生效或密钥错误检查os.getenv是否正确加载打印env确认提示词没生效模型不听话Prompt里的System消息被覆盖或模型版本差异打印最终拼装后的完整消息列表人工确认模板变量替换位置是否正常检索相关度差切分粒度不合理或embedding模型不匹配visual review chunk切分样例尝试换embedding模型降低chunk_size并增加重叠Agent不调用工具Temperature过高或docstring描述不清晰降低Temperature到0到0.2重写工具描述确认模型本身支持工具调用对话重启后历史丢失Memory存放在了内存字典里迁移到Redis等外部存储按session做持久化链执行非常慢多个环节串行导致性能瓶颈检查是否有不依赖的步骤能并行考虑把同步调用替换成异步调用8.2 面试高频题整理面试相关问题搜索量一直不低大概是因为LangChain相关岗位确实多了起来。结合真实面试经验和面经整理了这几类问题。第一类是概念理解类LangChain的核心模块有哪些说一个你印象深刻的LCEL特性。回答这类问题要结合你实际做过的项目来讲比只背概念好得多。第二类是方案设计类如果要做一个企业知识库问答系统你打算怎么设计这里要能说清楚文档加载、切分、embedding、向量存储、检索、生成这整条链路的选型理由同时要能说明你遇到过的最棘手的问题和解决方案。第三类是框架对比类也就是文中反复提到的LangGraph和LangChain的区别LangChain和LlamaIndex的区别LangChain过时了吗这类问题。框架对比考察的其实是你能不能跳出框架本身看架构本质。这里可以分享我对LangChain过时了吗的判断框架本身在高速迭代底层的抽象思想不会过时。只要大模型还是以API方式对外服务需要编排的环节就会一直存在这些编排能力相关的项目也会一直有新的形态出现。8.3 给LangChain入门学习者的一些建议最后一条经验建议给准备入手LangChain的朋友。框架本身迭代快API变动大很多教程拿半年前的项目来教你照着敲一遍会发现根本跑不通这不是你的问题。学习这门技术关键是把核心概念穿透式地搞明白——搞清楚什么是LCEL、什么时候该用Agent、记忆机制的本质是什么、为什么向量检索是这样做的模型换成哪个这些基础思路不会变。动手方向建议不要直接上手LangGraph先从最简单的LCEL链写起再升级到RAG链路然后再去玩Agent最后再碰LangGraph这类复杂编排工具这个顺序是按照难度层层递进的。每一步都动手敲实实在在的代码亲自跑通跑好比看十篇文章都管用。另外一个比较实用的做法是从第一天起就用LangSmith或者手动print的方式记录每一步的输入输出这会帮助你建立可观测性的习惯。在这个大模型应用开发的领域黑盒式的调用方式会浪费你大量调试时间做好可观测性的设计往往能让排查问题的效率提高很多。可能最开始你会觉得LangChain的包装层次太多封装也多不理解为什么要包这么多层。等你用过一段时间就会慢慢感觉到它的抽象方式能省下不少重复工作也让你把精力更集中到业务逻辑本身。