资讯动态

LangChain实战拆解:从提示词模板到RAG应用落地指南

发布时间:2026/10/19 8:01:56 来源:尧图企业网站定制
说句大实话LangChain刚火起来那阵子被各路教程吹得神乎其神什么“零基础三天搞定智能应用”“一招让你成为AI开发高手”。我当初也就是个写脚本都要查半天的水平看到那一堆术语——Chain、Retriever、Agent、Memory、Prompt Template——第一反应是这东西到底在解决什么问题后来真的把一个文档问答工具从手搓代码改成LangChain实现才明白它没有多玄乎就是一套把“和大模型对话”工程化、流水线化的工具集。这篇文章我从一个实操者的角度结合我踩过的坑把LangChain的核心骨架、搭建LLM应用的完整思路、以及从“能跑通Demo”到“稳定上线”的进阶关键一条条拆开讲。适合刚接触LLM开发、觉得概念一堆不知道从哪下手的小白也适合已经写了几个脚本但没想清楚整体架构的初级开发者。1. 先搞明白LangChain到底帮你解决了什么问题1.1 写LLM应用之前人人都会撞上的三个坎我最早写AI应用的时候任务不复杂给一段商品评论判断它是好评还是差评再总结一句核心观点。我第一个念头是“直接调模型的API不就行了”可一动手就发现事情没那么简单。第一个坎模型接口封装不统一。今天你用某家服务商的接口明天想换另一家的代码的请求格式、参数名、返回值结构全都不一样换一次模型重写一遍调用逻辑。第二个坎业务逻辑根本不是“输入一句话、输出一句话”这么简单。比如文档问答你要先读PDF、把内容切成合适的大小、把每段文字转成向量存到库、用户提问时再检索最相关的段落、最后拼到提示词里让模型回答。这一套流程如果全部手搓光代码就是好几百行而且每一步都可能出错。第三个坎对话状态管理。模型本身是“没有记忆的”多轮对话里要让模型知道你前面聊了啥就得自己做历史记录、拼接消息、控制上下文窗口稍微复杂一点就乱套。所以我当时最大的感受不是“模型不够聪明”而是“工程流程麻烦得要死”。LangChain正是踩着这三个坎来的。1.2 用一个普通人的视角理解LangChain如果抛开那些专业名词LangChain解决的事情跟在厨房里做饭是一模一样的。你想做一道菜不会只关心“炒”这个动作你得先买菜、洗菜、切菜、下锅翻炒、装盘、上桌。中间每一个环节都是独立的一步前后顺序固定上一步的输出就是下一步的输入。LangChain把LLM应用也当成这样一条流水线加载文档是“买菜”切分文档是“洗菜切菜”检索相关片段是“从冰箱里挑出要用的食材”模型生成回答是“下锅炒”输出解析是“装盘上桌”。流水线上每一段都可以替换、可以插拔组合起来就是一条“链”。这个思想叫LCEL也就是LangChain Expression Language本质上就是让你用一根管道符|把各个模块串起来。你不用再像写脚本那样在一个函数里塞几百行逻辑而是把每个处理环节拆成独立单元然后声明式地组合。理解了这一点再回头看那些术语就顺多了Prompt Template是制定菜谱Model是灶台Retriever是帮你找食材的管家Memory是你吃饭时记着前面聊过啥的人。1.3 80%的新手不是卡在写代码而是卡在“不会拆步骤”我带过几个同事入门LangChain发现一个特别有意思的现象他们不是不会写代码而是拿到需求后根本不知道该先从哪个模块入手。比如一个人要做“公司内部规章制度问答机器人”他上来就问我“怎么让模型记住所有文档内容”。这句话本身就暴露了拆解问题的方式有问题——模型不是数据库你不应该让模型“记住”文档而是要让系统在每次提问时去文档库里“找出”最相关的内容再回答。这就是检索增强生成RAG的思路。我在动手写任何LangChain应用之前都会先在一张纸上把流程写出来用户输入进来之后要经过哪几步处理哪些步骤可以复用哪些步骤是可选的比如刚才那个制度问答流程就是加载制度文档 → 切成小段 → 每段做向量化 → 用户提问 → 把问题和文档片段做相似度匹配 → 挑出Top N → 拼成带上下文的提示词 → 发给模型 → 输出回答和引用来源。这个流程写清楚之后代码只是把这些步骤翻译成LangChain的模块而已。所以如果你刚开始学我强烈建议先别急着写代码拿三个项目练一下“拆流程”的手感一个是文档问答一个是多轮对话一个是调用外部工具比如查天气、算算术。拆清楚了LangChain的80%你已经掌握了。2. 从零起步环境准备与第一个能跑的Demo2.1 环境搭建别一上来就“pip install langchain”很多新手装环境的时候就一把梭pip install langchain结果后面写代码时各种报错。这里我建议从一开始就分层安装。LangChain在当前版本体系里已经拆得很细了。最核心的是langchain-core它定义了一整套抽象接口包括消息类型、提示词模板、输出解析器、Runnable协议等等。而langchain这个主包提供的是各类链式组件、Agent运行时、记忆容器等高层封装。langchain-community里则是各个社区维护的集成组件比如各种文档加载器、各种向量库的适配器质量参差不齐后面我会专门讲它的坑。我常用的安装命令是这样pip install langchain langchain-core langchain-community然后根据你实际用的模型服务商再安装对应的集成包。这里我不写死某一家因为不同服务商的包名和模型名差异很大而且更新频繁你自己项目里用哪家就去装对应那家的「LangChain集成包」就行。如果你是用本地模型做开发测试那就装本地推理框架对应的集成包。装完之后最重要的是设置API密钥。通常的做法是放在环境变量里不要在代码里写死export MODELS_API_KEY你的密钥这里多说一句密钥千万不要提交到Git仓库里。我见过不止一个项目因为图省事把Key写在配置文件里结果整个项目公开之后被人刷爆账单。宁可每次运行前手动导出环境变量也别懒这一步。2.2 第一个Chain用管道符串出最小骨架环境准备好之后什么是最小的LangChain应用呢我建议不要一上来就搞Agent、RAG先跑通最基础的三件套一个Prompt模板一个Chat模型一个管道符串起来的Chain。我用代码示范一下from langchain_core.prompts import ChatPromptTemplate from langchain_core.messages import HumanMessage # 第一段编写提示词模板 prompt ChatPromptTemplate.from_template( 请讲一个关于{topic}的冷笑话要求字数控制在50字以内。 ) # 第二段创建模型实例这里用占位函数你需要替换成实际服务商的集成类 llm get_chat_model() # 第三段用管道符把模板和模型串成一条链 chain prompt | llm # 调用 result chain.invoke({topic: 程序员}) print(result)这个chain.invoke({topic: 程序员})做的事情非常清晰先把字典里的topic填充进提示词模板生成一条完整的指令然后交给模型处理最后返回模型输出。我推荐新手上手时多盯着这种“管道式写法”看它会让你逐渐养成“把流程拆成一段段”的思维模式而不是把逻辑全部塞在一个大函数里。2.3 第一个Demo最容易翻车的三个点我带着好几个人跑这个Demo翻车点高度集中在三处。第一invoke输入格式不对。模板里定义了需要topic变量你就必须传一个带有topic键的字典传字符串进去会直接报错。这听起来简单但实际排查的时候很多人盯半天找不到原因。第二提示词模板写错。from_template里的花括号变量和后面的字典键必须一一对应。如果模板里写的是{主题}字典里传的是topic运行时会报“缺少变量”或者直接把花括号当普通文本输出。第三模型返回的内容类型。不同集成包返回的对象有的是字符串有的是带结构的Message对象直接打印没问题但如果你想做后续处理最好先搞清楚result.content到底是什么类型。我的习惯是把模型输出接一个输出解析器强转成字符串from langchain_core.output_parsers import StrOutputParser chain prompt | llm | StrOutputParser() result chain.invoke({topic: 程序员}) print(result) # 这里一定是纯字符串跑通这三件套之后恭喜你LangChain最核心的调用方式已经掌握了。接下来我们拆一拆核心概念。3. 核心概念逐个拆模型、提示词、链与记忆3.1 模型封装为什么聊天模型是默认选择LangChain把所有大模型都抽象成了统一的接口但日常开发中你几乎不需要关心底层HTTP请求长什么样。你需要理解的是两种模型类型文本补全模型和聊天模型。文本补全模型就是“你给它一段文字它接着往下写”。聊天模型则基于一整段消息列表来生成回复消息带有角色比如系统指令、用户消息、助手消息。我现在所有应用几乎都用聊天模型因为它的上下文结构更清晰角色扮演和指令跟随效果更好而且主流服务商现在主推的都是聊天模型的接口。在LangChain里消息类型用SystemMessage、HumanMessage、AIMessage来表示。举个例子你想让模型扮演一个严格的财务审核员from langchain_core.messages import SystemMessage, HumanMessage messages [ SystemMessage(content你是一名严格的财务审核员只回答与报销单规范相关的问题。), HumanMessage(content补打卡的申请单需要附什么材料), ] result llm.invoke(messages) print(result.content)这种方式比起“把所有背景都塞进一个字符串”要清晰得多。你在调试时能一眼看出哪些是系统指令哪些是用户说了什么哪些是模型之前回复过什么。这才是多轮对话的正确打开方式。3.2 提示词模板把提示词工程固化成代码直接拼接字符串写提示词一开始很爽但项目稍微复杂一点就会失控同样的规则要复制到五六个地方改一处漏三处。用Prompt模板的核心价值是“把提示词工程的结构固定下来”。看一个实际场景我想做一个中英文翻译工具要求模型先翻译、再解释关键词。我把它写成模板from langchain_core.prompts import ChatPromptTemplate prompt ChatPromptTemplate.from_messages([ (system, 你是一名专业的翻译。你只做两件事第一将用户输入翻译成{target_language}第二用一句话解释翻译中出现的核心术语。), (human, 需要翻译的内容是{input_text}), ]) chain prompt | llm | StrOutputParser() result chain.invoke({ target_language: 英文, input_text: 我今天学习了如何搭建一条RAG流水线。 }) print(result)注意这里用了from_messages它允许你针对不同角色写不同的提示内容。模板只是第一步后续你可能还会往中间插入“示例少样本”模块、格式约束模块等等但模板本身就是你整个项目提示词工程的基础设施好好维护它比调参重要得多。3.3 链的写法从一条链到多条链的组合单个prompt | llm只是最简单的一条链实际业务中通常需要多条链协同。我拿一个“先判断情绪、再选择回复风格”的场景举例from langchain_core.runnables import RunnableBranch # 第一条链判断情绪 classify_prompt ChatPromptTemplate.from_template( 判断这句话的情绪是正向还是负向只输出两个字\n{text} ) classifier classify_prompt | llm | StrOutputParser() # 第二条链正向回复 positive_prompt ChatPromptTemplate.from_template( 用户很开心请用热情的语气回应{text} ) positive_chain positive_prompt | llm | StrOutputParser() # 第三条链负向回复 negative_prompt ChatPromptTemplate.from_template( 用户情绪不好请用温柔、安抚的语气回应{text} ) negative_chain negative_prompt | llm | StrOutputParser()然后你可以用RunnableBranch做分支根据分类结果决定走哪条链。这种组合方式的好处是每条链都可以单独调试、单独替换甚至单独做缓存。等到应用复杂起来你会发现“组合”比“封装”更容易维护。简单说把每件事做成独立小链再用管道和分支把它们组织起来这就是LangChain比较正确的中级用法。3.4 记忆管理别让“多轮对话”变成死记硬背模型本身不带记忆所以多轮对话里你得把之前的对话内容传给模型。很多人一听这个第一反应是“那我把所有历史消息全部塞进提示词不就行了”这个思路短期能跑但很快你就会遇到两个致命问题Token爆炸和上下文窗口溢出。假设你们聊了五轮每轮平均消耗500 Token把五轮都塞进去就要2500 Token这还是小对话。做客服机器人聊到二十轮再加大段知识库内容模型直接罢工。所以记忆管理真正的核心不是“如何记住全部”而是“如何只保留最重要的部分”。LangChain提供了一些内置的记忆类比如记忆缓冲、滑动窗口记忆、摘要记忆等。但我的经验是生产环境里不要过度依赖那些封装好的类因为你没法精细控制“什么时候忘、什么时候留”。我更喜欢自己维护一个消息列表保留最近N轮用户和助手的原始消息每M轮做一次摘要把超早期对话压缩成一段摘要把摘要和最近N轮消息拼在一起传给模型这种“摘要 滑动窗口”的混合策略是我实际项目里用得最稳的既保证连续性又不至于让Token无限膨胀。后面我会在实战案例里再展开一次。4. 把应用做成真正能用的东西检索增强与工具调用4.1 文档加载与切分这一步决定你检索质量的生死文档问答是LangChain最常见的应用场景但大多数人第一个版本都非常粗糙直接把整篇文档扔给模型然后说“请根据文档回答我”。效果可想而知文档超过上下文窗口就报错没超过的也因为信息量太大而答非所问。正确做法是先加载、再切分、再向量化。LangChain里有一堆文档加载器从PDF、Word、CSV到网页都能读。但真正决定检索效果的不是加载器而是切分器。以RecursiveCharacterTextSplitter为例它的核心参数是chunk_size每块多大和chunk_overlap块之间重叠多少字符。我踩过一个很深的坑刚开始我把chunk_size设成500字符没有设置重叠结果一段文字被硬生生切成两半而且两半的语意完全错开。比如一句“该公司今年营收同比增长了20%”被切成了“该公司今年营收”和“同比增长了20%”检索到后半段时模型根本不知道这20%是谁的。后来我养成了两个习惯优先按文档本身的段落边界切其次再按固定大小切设置chunk_overlap让前后块有50-100字的重叠避免语义断层from langchain_text_splitters import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap150, separators[\n\n, \n, 。, , , , ] )这个切分器会按照separators里的优先顺序尝试在完整句子边界处切分不会像无脑按字符切那样全凭运气。4.2 嵌入与向量库别把RAG的效果差怪到模型头上文档切好之后要做的就是把每块文本转成向量。嵌入模型这个东西你可以把它理解成“语义翻译器”把一段文字变成一个数字列表意思相近的文字在向量空间里距离更近。这样用户提问时我们就能在库里找出语义上最接近的段落。向量库的选型也很看场景。日常开发调试用一个轻量的内存向量库就够了生产环境一定要换持久化的向量数据库不然服务一重启几万条向量全没了。这里我想强调一个新手极易误判的点很多人做完检索增强之后发现回答质量差第一反应是“换个大模型”。但实际上问题往往出在检索结果不够好——你从向量库里捞出来的那三段可能压根不是用户想问的内容。所以调试RAG的优先级应该是排查步骤检查内容常见问题1切分粒度是否合理块太小信息不完整块太大噪音过多2嵌入模型是否适合领域中文场景要挑中文表现好的嵌入模型3检索返回的相关性Top K是否过小是否捞到无关片段4提示词是否把检索结果交代清楚模型有没有被告知“只能依据片段回答”5生成模型的能力与参数温度太高导致自由发挥我建议每次调试RAG时先把“检索到的原文片段”打印出来看一眼。如果你自己都觉得这些片段和问题无关那就别为难模型先回头优化切分和检索。4.3 工具调用让应用从“聊天”升级成“干活”如果你只停留在“对话”LangChain的应用天花板很低。真正让它值钱的是Agent机制也就是模型自己决定要不要调用外部工具比如查数据库、调计算器、访问内部接口。在LangChain里定义一个工具非常简单用tool装饰一个普通函数即可from langchain_core.tools import tool tool def calculate_discount(price: float, discount_rate: float) - float: 根据原价和折扣率返回折后价 return price * discount_rate把工具传给Agent之后模型会根据用户的请求决定要不要调用这个工具、传入什么参数、拿到结果再结合上下文生成最终回答。这个过程看起来是“智能”了但实际用起来也有代价Agent会多轮“思考”甚至调用错误工具。我的经验是Agent用的工具体量宁少勿多工具多了模型反而容易选错同时一定要设置最大迭代次数否则一个死循环能把你的Token烧穿。5. 实战案例一个本地知识库问答机器人的完整搭建5.1 需求拆解先列流程再写代码说了这么多概念用一个完整案例把它们串起来。假设我们要做一个公司制度问答机器人输入是一堆PDF制度文档使用方式是用户提出一个制度问题系统返回答案并且标注这段回答来自哪份文档。我按照前面说的“先拆流程再写代码”原则拆出下面这条流水线加载用文档加载器读取PDF目录下所有文件切分按段落切分块大小800字、重叠150字向量化对每个文本块生成Embedding存入向量库构建索引建好之后这个阶段可以复用提问用户输入问题检索从向量库找出和问题最相近的Top 5文本块组装提示词把用户问题 检索到的文本块塞进模板生成模型仅依据片段回答并注明来源展示返回答案和对应的文档名与片段5.2 代码骨架一条龙实现整个骨架代码我用注释标清每一步你在自己的环境里替换成你使用的模型服务商即可。from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser # Step 12加载和切分 loader PyPDFLoader(制度手册.pdf) documents loader.load() splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap150, separators[\n\n, \n, 。, , , , ] ) chunks splitter.split_documents(documents) # Step 34向量化和建索引这里用内存版向量库做演示 from langchain_community.vectorstores import InMemoryVectorStore vectorstore InMemoryVectorStore.from_documents( chunks, embeddingget_embedding_model() # 嵌入模型占位函数 ) retriever vectorstore.as_retriever(search_kwargs{k: 5}) # Step 567组装检索式问答链 prompt ChatPromptTemplate.from_template( 你是一个公司制度问答助手。请严格根据下面提供的资料回答用户问题。 如果资料中没有提到请直接说“资料中未找到相关信息”不要编造。 回答末尾必须注明参考资料的来源文件名。 资料 {context} 用户问题{question} ) def format_docs(docs): return \n\n.join(f[来源:{doc.metadata.get(source, 未知)}]\n{doc.page_content} for doc in docs) # 核心链检索 - 拼接资料 - 填充模板 - 模型生成 chain ( {context: retriever | format_docs, question: lambda x: x[question]} | prompt | llm | StrOutputParser() ) result chain.invoke({question: 年假可以跨年使用吗}) print(result)这段代码里需要特别注意的是我的第一步检索返回的是Document对象列表但模板里要的是文本字符串所以中间加了一个format_docs函数把文档转成带来源标注的文本块。这就是前面说的“上一步的输出是下一步的输入”你必须在串联之前想清楚每一步的数据结构。5.3 实测中的意外情况与补救办法这个案例我至少跑过十几次每次都会撞到一些和教程无关的真问题。第一个是中文PDF编码问题有些扫描版PDF转出来的文本全是乱码。这种问题没法靠切分器解决只能换OCR方案或者改用原始电子文档。第二个是问题太开放导致检索结果不相关。比如用户问“请假要注意什么”这个词和具体制度条款的向量距离可能很远Top 5捞出来的片段并不理想。我的补救办法是同时做“文本向量检索”和“关键词检索”把两组结果合并去重再取Top N这种多路召回策略可以明显提升召回率。第三个是幻觉。我在提示词里已经反复强调“资料没有就直说”但模型有时仍会强行把不同条款拼接成看似合理的答案。后来我在提示词里加了一条“如果多个资料之间说法矛盾请明确指出矛盾”才把这类风险压下去。你永远不能100%信任生成模型RAG只是降低幻觉概率不是消除幻觉。6. 进阶建议从“能跑”到“稳定可靠”6.1 日志与可观测性上线前必须补的一课很多人做Demo很兴奋一旦要上线就抓瞎因为LangChain的链路太长你根本不知道哪一步耗了多少时间、哪一步传了什么数据。我的建议是给应用加一层“调用追踪”。最简单的做法是在启动脚本里加一个回调处理器把每一层的输入输出、Token耗时全部记录下来from langchain_core.callbacks import BaseCallbackHandler class LoggingHandler(BaseCallbackHandler): def on_llm_start(self, serialized, prompts, **kwargs): print(LLM 开始调用提示词, prompts) def on_llm_end(self, response, **kwargs): print(LLM 返回Token 消耗, response.llm_output)生产环境里可以把这些数据接到日志平台然后用可视化链路追踪来查问题。千万别靠“print大法”在生产环境调试几百个用户的请求你根本盯不过来。6.2 成本控制同样的效果换一条省钱的路大模型调用是按Token计费的成本是LLM应用绕不开的隐性指标。我的省钱经验有三个第一给相同请求加缓存。比如“制度查询”这种高频且答案相对固定的场景命中缓存就直接返回不必每次都调模型。第二能用小模型处理的任务别用大模型比如文本分类、关键词抽取小模型又快又便宜等到生成最终答案时再用大模型。第三严格控制上下文长度只回传检索到的Top N片段不要一股脑把整个文档库塞给模型。6.3 版本更新与社区包拥抱变化但保持冷静LangChain迭代速度相当快接口隔几个月就会变。你会发现网上很多旧教程跑不通不是因为你的问题而是因为版本API改了。我应对更新的方式有三条一是锁定版本号在自己的项目里用固定版本不要每回都盲目升级。二是在看文档时优先看官方文档的最新迁移指南不要死记旧接口。三是langchain-community里的东西要谨慎使用因为它属于社区贡献没有核心团队做严格保证生产环境用之前一定要自己验证。6.4 什么情况下你可以不学LangChain最后说点反话。LangChain不是银弹如果你的应用非常简单比如只是调一下模型API做个翻译那直接用服务商SDK反而更快没必要引入一套框架增加心智负担。LangChain的价值是在你面对五六个模块、需要组合检索、工具调用、记忆管理时才体现出来的。判断标准很简单如果你能一口气用原生代码清晰写出全部流程那就可以不用如果你已经开始觉得“我的代码全是拼接字符串和逻辑分支”那LangChain正好能帮你把流程结构化。我在实际项目中最大的体会是LangChain真正教我的不是某个API怎么调用而是“任何复杂的AI应用都可以被拆成一条清晰的流水线”。这个思维方式比框架本身值钱得多。最后分享一个小技巧跑通一个项目后别急着加功能把所有链路的中间输入输出完整打印一遍哪怕只是看一遍你对这个框架的理解都会扎实一大截。踩过的坑多了你自然就不会再怕它了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑