1. 入门之前LangChain到底解决什么问题先说结论LangChain是现阶段把大模型想法快速变成原型、甚至直接推到生产环境最顺手的框架之一没有夸张。你给它几个组件——模型、提示词、工具、记忆——它就能帮你把这些零散的部分编排成一条完整的处理流水线让模型不再只是“你问一句、它答一句”的孤岛。我第一次接触LangChain时的状态和很多人差不多模型API已经能调通但代码一多就乱成一团。每个功能模块都要自己封装请求、拼接上下文、处理历史记录、处理异常所有逻辑耦合在一起改一个地方就牵一发动全身。后来换成LangChain之后最大的感受就是编排能力把整个开发节奏带起来了。各环节的代码边界变清晰复用性也上来了后来有些项目换模型服务商只需要替换中间一层配置业务代码基本不用动。这篇文章适合三类人。第一类是Python基础扎实、想快速把模型能力集成进自己应用的开发者第二类是刚接触大模型开发不久、想系统了解“链”“记忆”“代理”这些概念到底怎么用的转行者第三类是已经在裸调API、但代码组织越来越别扭、想找一套更工程化解法的同学。整篇教程尽量不依赖高级概念代码片段基本可以直接跑核心组件会在用到的时候顺带讲清楚。前置知识方面最好有Python的基础语法和函数式编程的一点感觉比如“函数作为参数传来传去”这种概念因为LangChain的新版调用风格大量用了管道操作链里的每一步本质上都是把一个函数的结果传给下一个函数。没有也没关系我后面会用生活化的例子解释不要被术语吓住。同时需要准备好一个能调用的模型API至于用哪家的、有没有Key下面会细说。我个人的学习建议是不要一上来就背组件清单也别想着把所有概念看完再动手。LangChain的组件体系对新手来说太庞大了先跑通一个最小流程再往里逐步加东西比顺序读书效率高得多。这也是这篇文章的编排逻辑环境搭完直接跑第一个对话然后按“提示模板、链、记忆、代理、知识库”的顺序每步往里加功能每步都能看到实际效果。2. 快速搭建环境十分钟跑通第一个对话2.1 安装与版本选择的经验安装本身不复杂pip一条命令的事pip install langchain。但这里有个隐患LangChain发展太快不同版本的API差异大得离谱。早些年版本的LLMChain、ConversationBufferMemory这些写法在最新版里有些已经迁移甚至废弃了。网上教程满天飞作者用的版本跟你装的版本不一致把旧代码粘进去猛报错这是新手最常见的天坑。建议直接装最新稳定版并且把版本号记住。我个人习惯喜欢用pip freeze | grep langchain查看当前环境中所有LangChain相关包的版本。比如你会看到langchain、langchain-core、langchain-community等好几个包它们是一个整体版本必须协同。如果发现装完之后其他包版本冲突建议直接用虚拟环境隔离别跟全局环境混在一起。另外实操中大多数功能还需要按需装额外的集成包。对接模型服务商就用对应的包比如langchain-openai对接向量数据库就装对应数据库SDK。我踩过一次坑只装了一个langchain以为连模型API是内置能力结果运行时报模块不存在对着报错找半天才发现少了配套包。所以宁可在看文档时多看一眼“安装”章节也别等报错再回来补课。2.2 配置模型接口的几个要点LangChain本身不内置大模型它是连接层要接哪个模型服务商就引入哪个服务商对应的适配包。这里以最常见的OpenAI兼容接口为例其他服务商大同小异核心就是两个参数api_key和base_url。from langchain_openai import ChatOpenAI llm ChatOpenAI( modelgpt-4o-mini, temperature0.7, api_keysk-xxxx, # 有环境变量的话可以不传 )这里有几个实际经验。第一不要把Key硬编码在代码里就算只是学习项目也不行因为你保不准哪天就把它推到公开仓库里了。优先用环境变量或者项目里的.env文件加载。第二很多第三方模型服务商为了兼容OpenAI协议会开放一个中转地址你把base_url填成它的地址就能连上这是个非常实用的偷懒方式学习期可以大幅降低试错成本。第三temperature参数控制随机性值越高回答越发散。做严谨的知识问答时设0到0.3做创意生成时设0.7以上。这不是玄学是对应的采样逻辑决定的。2.3 第一次调用三行代码跑通对话配置好之后第一次调用可以简单到让人惊讶from langchain_openai import ChatOpenAI llm ChatOpenAI(modelgpt-4o-mini) response llm.invoke(一句话解释什么是递归) print(response.content)invoke是新版LangChain的核心调用方法。过去你可能会看到llm(问题)这种写法新版本里统一用invoke传入完整消息或字符串返回的不再是简单的字符串而是一个包含content属性的响应对象。之所以这样做是为了让模型调用和链里其他组件保持同一个接口规范全家都是invoke后面要串成链就无比顺滑。到这里环境其实就是通了。很多人以为跑通大模型应用很难什么都没有问题。但真正的开发才刚刚开始你的思路从“问一句、答一句”升级成“让模型完成一项复杂任务”于是就需要把多个调用组织成流程这就进入了LangChain真正擅长的领域。3. 手把手五步实战从简单调用到能落地的对话应用说句实话如果你只追求“能跑”看到2.3就可以停了。但LangChain的价值在于把零散的模型调用变成真正能用的应用接下来这五步是核心建议实际操作完再休息。3.1 第1步用提示模板替换写死的提示词第一步先解决提示词写死的问题。直接在代码里拼接提示词第一次没问题需求一变就得改源码很快失控。提示模板的作用就是让提示词变成可填参数的结构化模板。from langchain_core.prompts import ChatPromptTemplate template ChatPromptTemplate.from_messages([ (system, 你是一个精通{field}领域的资深专家), (human, 请用简洁的语言解释{question}), ]) prompt template.invoke( {field: 数据结构, question: 什么是栈} ) print(prompt.to_messages())from_messages接收一个列表每个元素是消息角色加内容的二元组。system消息负责设定模型的人设和任务背景human消息代表用户输入。模板里的{field}、{question}是变量槽位调用invoke时传入字典把它们填上。这里有一个设计细节值得注意模板不应该只是单纯的“问题前缀拼接”而是可以把身份、背景、约束、示例全放进去。比如知识库问答场景里把“只能根据以下资料回答”写进system消息比放在用户消息里通用得多。我建议把模板单独放到一个文件里管理尤其是工程里提示词经常更新迭代。你完全可以建一个prompts.py模块统一导出所有模板。少用字符串拼接多用模板代码清爽程度很快就能感受到差异。3.2 第2步用LCEL链把“取数-处理-输出”串起来提示模板能填参数了但如果每次都手动先取数、再填模板、再调模型还是没有摆脱流水线工人的角色。LangChain提出了一套叫LCEL的语言表达方式核心是|管道符号把多个组件像水管一样接起来。左端输出自动变成右端输入每一段互相解耦。刚才的提示模板和模型可以极简地组合成一条链from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate llm ChatOpenAI(modelgpt-4o-mini) template ChatPromptTemplate.from_messages([ (system, 你是一个精通数据结构的专家回答尽量简明), (human, {question}), ]) chain template | llm result chain.invoke({question: 二叉树和链表有什么区别}) print(result.content)chain就是一个可调用对象跟调用单模型几乎一样输入模板要求的字典输出最终结果。这背后的思路叫可组合性只要组件实现了相同接口就能用|拼装template输出PromptValuellm接收PromptValue输出模型响应。不需要写胶水代码不需要处理中间结果每条链只负责自己的转换逻辑。熟悉这种风格的开发者会立刻联想到函数式编程里的管道。事实上这就是函数管道思想在LLM应用里的落地实现。它带来一个很实际的好处中间任何一个环节被替换都不影响其他环节。比如你想给模型输出加个输出解析器把它插在llm后面就行了链的结构依然一目了然from langchain_core.output_parsers import StrOutputParser parser StrOutputParser() chain template | llm | parser这行的意思就是填好模板 → 调用模型 → 把响应对象里的content提取成纯字符串。别小看这三步组合很多真实应用的基本骨架就是这样搭起来的。我见过有人用这样的链做客服问答自动摘要、用这样的链做代码评审意见生成、用这样的链做论文要点提取入口数据不一样但逻辑骨架完全一样。3.3 第3步用记忆让对话不“失忆”模型的API本质上是无状态的——每次调用它它都不记得之前说过什么。聊天应用当然不能接受这种“失忆”所以需要记忆组件把历史消息存起来在每次请求前把历史一起拼给模型。先用最简单的内存记忆实现多轮对话from langchain_community.chat_message_histories import ChatMessageHistory from langchain_core.messages import HumanMessage, AIMessage history ChatMessageHistory() history.add_user_message(我叫小明) history.add_ai_message(好我记住了) for msg in history.messages: print(msg.type, msg.content)ChatMessageHistory是把消息列表在进程内存里维护起来。实际使用时要把它接入链里让链在每次调用时自动读取并追加消息。目前最稳定的方式是使用RunnableWithMessageHistory这类封装。需要注意一点LangChain不同版本对记忆模块的封装差别很大早期流行的ConversationBufferMemory在新的LCEL风格里已经不那么主流了新项目建议直接习惯消息历史对象的方式更贴近新版设计。from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_community.chat_message_histories import ChatMessageHistory chain_with_history RunnableWithMessageHistory( chain, lambda session_id: ChatMessageHistory(), input_messages_keyquestion, history_messages_keyhistory, ) result chain_with_history.invoke( {question: 我叫小明}, config{configurable: {session_id: user-001}} ) print(result.content) result chain_with_history.invoke( {question: 我叫什么名字}, config{configurable: {session_id: user-001}} ) print(result.content)这里的关键是session_id。不同用户分配不同的会话ID记忆就按会话隔离。这是真实项目里特别重要的设计同一个模型实例要服务成百上千个用户绝不能让张三的对话历史串到李四头上。我在一个早期项目里因为偷懒把历史存成单例结果所有用户共享同一份记忆闹出了A用户问的问题B用户看到回复的笑话排查了半天才找到根因。内存记忆只适合单机、低并发的场景。项目一旦部署成多实例、或者需要持久化就必须替换成Redis、数据库等存储介质。但概念模型一样保证同session_id能取出一致的消息历史即可。3.4 第4步用工具调用与代理让模型具备“动手能力”聊到这一步应用开始具备“自主性”了。所谓代理就是让模型自己决定要调用哪个工具、按什么顺序调用、何时给最终答复。如果说链是固定的流水线代理就是流水线调度员接到任务自己拆解自己调用工具。先定义一个工具。典型的工具就是普通函数加上一个装饰器from langchain_core.tools import tool tool def add_numbers(a: int, b: int) - int: 计算两个数字的和。 return a b这个函数一旦被tool装饰LangChain就会自动提取它的函数名、参数注解、docstring生成一份结构化描述。模型看到这份描述就知道“哦我有个工具可以做加法”从而在需要时自动发起调用。关键点全在注释和参数注解上——docstring写得太模糊模型就不知道何时该用这个工具参数类型不写注解模型就不知道怎么传参。所以工具说明的质量直接决定代理的智能程度。接着把模型配置成能调用工具的模式再把工具列表交给它循环执行from langchain_openai import ChatOpenAI llm ChatOpenAI(modelgpt-4o-mini) llm_with_tools llm.bind_tools([add_numbers]) response llm_with_tools.invoke(请计算123加上456等于多少) print(response.tool_calls)bind_tools把可用工具绑定到模型上模型输出里会包含工具调用指令但并没真正执行。要真正跑起来需要用一个循环把模型输出的工具调用请求接住执行对应函数把结果返回给模型让它继续处理。这个循环LangChain有现成的组件帮我们实现比如AgentExecutor或create_tool_calling_agent加AgentExecutor的组合。from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate agent_prompt ChatPromptTemplate.from_messages([ (system, 你是一个智能助手可以借助工具完成用户请求), (human, {input}), (placeholder, {agent_scratchpad}), ]) agent create_tool_calling_agent(llm, [add_numbers], agent_prompt) agent_executor AgentExecutor(agentagent, tools[add_numbers], verboseTrue) result agent_executor.invoke({input: 计算100加200的结果然后加50}) print(result[output])agent_scratchpad是一个永久未变的占位符用来存放模型之前的推理过程和工具执行结果。模型就是根据这个scratchpad来“记得”自己已经算到了哪一步。整个循环看起来很有“思考”的观感但本质就是调用、执行、回填、再调用的循环。verboseTrue在调试时非常有用它会打印每一步的内部日志解释模型为什么调用这个工具、工具返回了什么。3.5 第5步引入外部文档做知识库问答最后一步是当前应用最广的RAG模式。解决的问题很直接模型的知识有截止日期也没有你的私有数据。与其用很久前的数据进行深度微调不如把检索到的相关资料当作上下文塞给模型让它“带着答案来作答”。这种方式叫检索增强生成。整个流程分两步走先离线把文档切片、向量化、存入向量数据库再在线检索相关资料填入提示模板让模型基于资料回答。离线建索引的代码大致这样from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import FAISS # 加载文档 loader TextLoader(docs/product_manual.txt) documents loader.load() # 切片 splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, ) chunks splitter.split_documents(documents) # 向量化并存入本地向量库 embeddings OpenAIEmbeddings() vectorstore FAISS.from_documents(chunks, embeddings) vectorstore.save_local(faiss_index)chunk_size和chunk_overlap是两个需要调的核心参数。切片太小上下文语义容易断切片太大又可能超过模型输入长度限制也影响检索精度。我通常把500到1000作为起始区间然后拿几个真实问题测对比检索相关度后再微调。chunk_overlap设置成chunk_size的10%左右让相邻切片的头尾有一段重叠避免语义恰好被切断。在线问答这段把检索器和提示模板、模型串成一条RAG链from langchain_core.prompts import ChatPromptTemplate vectorstore FAISS.load_local( faiss_index, embeddings, allow_dangerous_deserializationTrue ) retriever vectorstore.as_retriever(search_kwargs{k: 3}) rag_prompt ChatPromptTemplate.from_messages([ (system, 请仅根据以下资料回答如果资料中没有相关内容就明确说不知道。\n\n资料{context}), (human, {question}), ]) rag_chain rag_prompt | llm | StrOutputParser() def ask(question): docs retriever.invoke(question) context \n\n.join([doc.page_content for doc in docs]) return rag_chain.invoke({context: context, question: question}) print(ask(这款设备的保修期是多久))这里有一个很值得体会的设计提示词里明确说了“如果资料中没有相关内容就明确说不知道”。没有这句约束模型在检索结果跟问题相关度不高的时候会强行根据自己训练数据里的泛化知识编答案这是RAG应用最严重的幻觉问题之一。加上约束之后模型“拒答”的概率会明显提高。生产环境的RAG提示词里这种边界说明一定要写清楚甚至还要再加上“被引用的内容必须逐字来自资料”之类的强约束。4. 踩坑记录与排查指南4.1 最常见的几类报错说句不客气的话LangChain新手期的报错大半都是版本问题代码本身反而不难。下面这些我已经见过无数次了。第一类模块找不到。ModuleNotFoundError: No module named langchain_community。原因是没有安装配套包。LangChain把官方集成、社区集成拆成独立包之后很多组件不在核心包里用啥装啥。报错直接告诉你缺哪个包看清名字pip install即可。第二类API不存在。AttributeError: ChatOpenAI object has no attribute predict。大概率是照着老版本教程写了一行新版本没有的代码。遇到这类报错不要慌先查当前版本的官方文档或接口签名确认替代写法。我踩过太多次代码在使用旧接口而对新版来说它已经改了名字。第三类Key认证失败。AuthenticationError: Incorrect API key provided。这种没什么技巧检查环境变量有没有正确加载就行。我通常先跑一个print(os.getenv(OPENAI_API_KEY))确认环境变量真的存在再排查其他可能性。第四类提示模板字段不匹配。KeyError: question。调用链时传入的字典里少了一个模板需要的变量或者模板里写了一个字典里没有的变量。这个错误很微妙出错位置在提示模板内部但根因往往在调用侧排查时把变量名对清楚就发现了。4.2 上下文溢出与超时问题模型输入长度是硬限制。当多轮对话的历史消息越来越多把全部历史都拼进去时总token数超过模型上限调用直接报错。两种路线处理要么缩短历史保留最近N轮要么做摘要把较久的对话压成几句话。from langchain_core.messages import HumanMessage, AIMessage, SystemMessage def trim_history(messages, max_messages10): if len(messages) max_messages: return messages # 保留系统消息和最近 max_messages - 1 条消息 system_msgs [m for m in messages if m.type system] recent messages[-(max_messages - 1):] return system_msgs recent这个简化版本已经可以应付学习场景。生产环境里更推荐用LangChain的trim_messages工具它可以基于token数裁剪而不是简单按条数来。初学者常常习惯把所有历史一股脑传给模型直到踩了截断报错才后悔提前做一个裁剪策略能省下大量排查时间。超时问题也很常见。模型服务商接口响应慢、网络波动或者请求数据太大都会触发TimeoutError。可以给ChatOpenAI设置timeout参数和max_retries参数提升稳定性llm ChatOpenAI( modelgpt-4o-mini, temperature0.7, timeout30, max_retries2, )不过设置重试也要谨慎有些接口不支持重复执行比如扣款成功的支付接口一重试可能造成重复扣费。模型调用一般幂等性还好但批量任务里出现超时后最好还是确认一下实际执行状态心里有数再决定是否重试。4.3 调试技巧与参数调整经验LangChain的链报错设计比较隐蔽一条链里的某个环节出错错误信息往往会指向一个笼统的组件调用失败。这时候最有效的调试方式不是看堆栈猜问题而是拆链。把链拆回一个个独立步骤逐步调用、逐步打印中间结果。比如一条链是prompt | llm | parser那就分成三步调试。先打印prompt.invoke({question: ...})看生成的PromptValue对不对再手动把PromptValue传给llm.invoke看模型输出是否正常最后再用parser解析看解析器是否成功。三个环节都单独验证过了再拼回顾链。这个思路适用所有复杂链的调试。另一个经验是善用verboseTrue。执行代理时它能把模型每步的思考、调用了哪些工具、工具返回了什么全打印出来。这个输出很有价值——你可以完整看到模型决策的逻辑链路而不是面对一个黑盒猜来猜去。参数调整方面我通常遵循原则是保业务稳定性时降低temperature追求多样性时调高一点max_tokens不要给得太保守否则回答会中途被截断。token数在中文场景下比英文场景消耗更大一个汉字大约对应1到2个token计划上下文时不能按英文字符来算。5. 进阶方向与我的使用心得如果以上五步你都跑通了恭喜你已经掌握了LangChain的主干。接下来拓展的方向主要有三条。第一条是更丰富的异构数据接入。文中的TextLoader只是一个开始PDF、Markdown、HTML、JSON、甚至数据库表都可以加载。每条数据源的加载器不同切片策略也不同。遇到扫描版PDF这种格式建议先用OCR工具转成文本再进入流程。第二条是持久化层。线上应用里的对话历史、向量库、代理状态都是要落库的。Redis、PostgreSQL、云上的向量数据库各有适用场景。学习期用FAISS本地文件没问题生产环境必须考虑容量、并发和可用性。我见过有人把学习期的本地向量库直接部署到生产环境结果内存爆掉例子比较极端但很常见。第三条是从单条链走向复杂工作流。LangChain不只是串链还可以用LangGraph来构建带条件和循环的图结构让“决策”不再是代理的模糊行为而是显式的状态节点跳转。对复杂任务、对流程有强可控性要求的项目这是很大的进阶加分项。最后分享一个我个人用着很顺的调试习惯先让每个环节单独输出再拼到链里。别指望一条链从头到尾一次跑通。把任务拆成最小单元分别打印串起来反而特别快。还有一个感受是LangChain迭代太快了与其背API不如学会查文档、看变更日志核心概念稳定接口细节过半年可能就变了。遇到报错先从“版本不一致”下手查你八成能少走很多弯路。这个框架的好处之一就是它把繁琐的底层编排兜底了自己的精力可以专注在业务逻辑和效果调优上。先把这个最小流程跑起来后面加功能就是水到渠成的事。