资讯动态

LangChain实战:从零构建大模型RAG应用与核心模块解析

发布时间:2026/10/22 3:36:38 来源:尧图企业网站定制
1. 为什么大模型应用开发需要LangChain1.1 从“能聊天”到“能干活”的鸿沟很多人第一次调用大模型接口时感觉就像发现了新大陆——几行代码就能让模型回答问题、写文案、做翻译。但当你真正想把它做成一个能落地的应用时问题就来了模型记不住上一轮对话怎么办想让模型查一下公司内部文档再回答怎么做模型输出的格式乱七八糟怎么解析调用一次要好几秒用户等不及怎么优化这些问题的本质是大模型本身只是一个“文本进、文本出”的黑盒函数而一个真实的应用需要记忆、需要工具、需要流程编排、需要错误处理。LangChain做的事情就是在这个黑盒外面搭一套脚手架把“调用模型”这件事变成“构建应用”这件事。我刚开始接触LangChain的时候觉得它不过是把API调用包装了一层没什么了不起。但用久了才发现它真正解决的是标准化的问题——当你需要把模型、提示词、记忆、工具、输出解析器这些东西组合起来时如果没有一套统一的抽象代码会迅速变成一团乱麻。LangChain提供的就是这套抽象让你用搭积木的方式构建复杂应用。1.2 LangChain到底解决了哪些核心痛点具体来说LangChain在以下几个层面提供了价值第一统一模型接口。不同厂商的模型API参数名、返回格式、调用方式都不一样。LangChain用一套接口把它们统一起来切换模型时只需要改一行配置。这在做模型对比、降级容错时特别有用。第二提示词模板化。把提示词写死在代码里是新手常犯的错误。LangChain的PromptTemplate让你把提示词当成可配置的模板支持变量注入、少样本示例、格式控制方便复用和迭代。第三记忆管理。多轮对话需要把历史消息传给模型但历史不能无限增长。LangChain提供了多种记忆策略从简单的全量保留到滑动窗口、摘要压缩帮你控制上下文长度和成本。第四工具调用与Agent。让模型能调用外部函数查天气、算数学、搜数据库是扩展能力的关键。LangChain定义了工具的标准接口并提供了Agent机制让模型自主决定何时调用哪个工具。第五检索增强生成RAG。这是目前最火的应用模式——把私有文档向量化存储用户提问时先检索相关片段再让模型基于这些片段回答。LangChain把文档加载、切分、向量化、检索、生成的完整链路都封装好了。第六输出解析与结构化。模型返回的是自然语言但程序需要的是结构化数据。LangChain的OutputParser能把模型输出转成JSON、列表、Pydantic对象等格式方便后续处理。1.3 适合哪些人学习和使用LangChain并不是只有算法工程师才能用。根据我的观察以下几类人都能从中获益后端开发者想给自己的服务加上AI能力但不想从头造轮子。产品经理/创业者想快速验证一个AI应用的想法做原型。数据科学家熟悉Python和数据处理想把模型能力接入现有分析流程。学生/爱好者想系统学习大模型应用开发的完整知识体系。前提是你得会一点Python理解基本的API调用概念。不需要懂模型训练不需要GPU一台能上网的电脑就够了。2. 核心概念拆解与设计思路2.1 六大核心模块的职责划分LangChain的模块很多但核心就那么几个。我用一个做菜的场景来类比帮你快速建立认知模块类比职责关键类Models灶台调用大模型生成文本ChatOpenAI、ChatAnthropicPrompts菜谱组织输入给模型的指令PromptTemplate、ChatPromptTemplateChains流水线把多个步骤串起来LLMChain、SequentialChainMemory备忘录保存对话历史ConversationBufferMemoryTools/Agents助手让模型调用外部能力Tool、AgentExecutorIndexes/Retrievers图书馆存储和检索文档VectorStore、Retriever这个类比不一定严谨但能帮你理解每个模块在应用中的位置。实际开发时你通常是从Models和Prompts开始然后根据需要逐步引入其他模块。2.2 为什么选择“链”作为核心抽象LangChain最核心的概念是“Chain”链。为什么用链而不是别的因为大模型应用的本质就是一系列步骤的编排用户输入经过提示词模板变成完整提示提示送给模型生成输出输出经过解析器变成结构化数据数据再送给下一个步骤……Chain就是把这种“步骤串联”模式抽象出来。最简单的LLMChain就是“提示词模型输出解析器”三件套。复杂一点的有SequentialChain把多个Chain按顺序执行前一个的输出作为后一个的输入。还有RouterChain根据输入内容动态选择走哪条链。我个人的经验是不要一上来就用复杂的Chain。很多场景下一个LLMChain加几个函数调用就够了。Chain的价值在于当流程变复杂时它能让代码保持清晰。如果你只有两三个步骤直接写Python函数反而更直观。2.3 提示词工程在LangChain中的落地方式提示词写得好不好直接决定应用效果的上限。LangChain提供了几个层次的提示词工具最基础的是PromptTemplate支持变量替换from langchain.prompts import PromptTemplate template 你是一个专业的{role}请用{style}的风格回答以下问题 问题{question} 回答 prompt PromptTemplate( input_variables[role, style, question], templatetemplate )进阶一点是ChatPromptTemplate支持角色区分system、human、aifrom langchain.prompts import ChatPromptTemplate prompt ChatPromptTemplate.from_messages([ (system, 你是一个乐于助人的助手回答要简洁准确。), (human, {user_input}) ])再复杂一点是FewShotPromptTemplate可以注入示例让模型模仿from langchain.prompts import FewShotPromptTemplate, PromptTemplate examples [ {input: 开心, output: 喜悦}, {input: 难过, output: 悲伤} ] example_prompt PromptTemplate( input_variables[input, output], template输入{input}\n输出{output} ) few_shot_prompt FewShotPromptTemplate( examplesexamples, example_promptexample_prompt, prefix请将以下情绪词转换为更书面的表达, suffix输入{input}\n输出, input_variables[input] )提示提示词模板不要写得太长太复杂。我见过有人把几千字的提示词塞进模板里结果变量一多就乱套。建议把提示词拆成多个部分用组合的方式构建。2.4 记忆机制的设计取舍多轮对话的记忆管理是个看似简单实则坑很多的问题。LangChain提供了多种Memory类选择哪种取决于你的场景ConversationBufferMemory全量保留历史消息。简单直接但对话长了会超出上下文限制而且成本高。ConversationBufferWindowMemory只保留最近K轮对话。适合大多数场景K一般取5-10。ConversationSummaryMemory用模型把历史对话压缩成摘要。省token但摘要本身也要调用模型有额外成本。ConversationSummaryBufferMemory混合策略最近几轮保留原文更早的压缩成摘要。这是我认为最实用的方案。选择记忆策略时核心权衡是信息完整性 vs 上下文长度 vs 成本。我的建议是先用BufferWindowMemory跑起来观察实际对话轮次和token消耗再决定要不要升级到摘要方案。3. 从零搭建一个RAG应用完整实操3.1 环境准备与依赖安装先搞定环境。我习惯用虚拟环境隔离项目依赖python -m venv langchain-demo source langchain-demo/bin/activate # Windows用 langchain-demo\Scripts\activate然后安装核心依赖。这里要注意版本兼容性LangChain生态更新很快不同版本API差异不小pip install langchain langchain-community langchain-openai pip install chromadb # 向量数据库 pip install pypdf # PDF解析 pip install tiktoken # token计数如果你用的是其他模型厂商把langchain-openai换成对应的包即可。国内模型也有相应的LangChain集成包。注意LangChain在0.1版本之后做了大量重构很多教程还是老版本的写法。建议锁定版本比如pip install langchain0.1.0避免API变动导致代码跑不通。3.2 文档加载与切分策略RAG的第一步是把私有文档变成可检索的片段。假设我们有一批PDF格式的产品手册from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 加载文档 loader PyPDFLoader(product_manual.pdf) documents loader.load() # 切分文档 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个片段的目标字符数 chunk_overlap50, # 相邻片段的重叠字符数 length_functionlen, separators[\n\n, \n, 。, , , , , , ] ) chunks text_splitter.split_documents(documents) print(f原始文档数{len(documents)}切分后片段数{len(chunks)})切分策略是RAG效果的关键。chunk_size太小片段缺乏上下文太大检索精度下降且浪费token。我的经验是中文文档chunk_size取300-500字比较合适。英文文档chunk_size取500-1000字符。技术文档/代码按段落或函数切分不要硬按字符数切。chunk_overlap一般取chunk_size的10%-20%保证片段间语义连贯。RecursiveCharacterTextSplitter的聪明之处在于它会按分隔符优先级递归切分优先在段落边界切实在不行才在句子边界切最后才硬切。这比简单按字符数切效果好很多。3.3 向量化存储与检索配置切分完的片段需要转成向量存起来。这里用OpenAI的嵌入模型举例from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma # 初始化嵌入模型 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) # 构建向量库 vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db # 持久化目录 ) # 持久化 vectorstore.persist() # 检索测试 query 产品保修期是多久 results vectorstore.similarity_search(query, k3) for i, doc in enumerate(results): print(f--- 结果 {i1} ---) print(doc.page_content[:200])检索时k值的选择也有讲究。k太小可能漏掉关键信息k太大则引入噪声且增加token消耗。一般k3到5比较合适。如果效果不理想可以尝试调整切分策略让片段更完整。换更好的嵌入模型。加入重排序Rerank步骤先检索较多结果再精排。3.4 构建问答链并接入模型最后把检索和生成串起来from langchain_openai import ChatOpenAI from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate # 初始化模型 llm ChatOpenAI(modelgpt-4o-mini, temperature0) # 自定义提示词 qa_prompt PromptTemplate( template基于以下参考信息回答用户问题。如果参考信息中没有相关内容请如实说不知道不要编造。 参考信息 {context} 用户问题{question} 回答, input_variables[context, question] ) # 构建检索问答链 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 把所有检索结果塞进提示词 retrievervectorstore.as_retriever(search_kwargs{k: 3}), return_source_documentsTrue, chain_type_kwargs{prompt: qa_prompt} ) # 提问 result qa_chain.invoke({query: 产品保修期是多久}) print(回答, result[result]) print(来源, [doc.metadata for doc in result[source_documents]])chain_type有几种选择stuff最简单把所有检索结果拼进提示词map_reduce先对每个片段单独总结再合并refine逐步精炼答案。大多数场景用stuff就够了除非检索结果特别多导致超出上下文限制。4. 常见问题与排查技巧实录4.1 模型输出格式不稳定的解决思路这是最常见的问题之一。你让模型返回JSON它有时候返回带markdown代码块的有时候字段名拼错有时候干脆多写一段解释。解决办法有几个层次第一层提示词约束。在提示词里明确格式要求并给出示例。比如请严格按照以下JSON格式输出不要添加任何其他内容 {name: 姓名, age: 年龄, city: 城市}第二层使用OutputParser。LangChain提供了多种解析器from langchain.output_parsers import PydanticOutputParser from pydantic import BaseModel, Field class PersonInfo(BaseModel): name: str Field(description姓名) age: int Field(description年龄) city: str Field(description城市) parser PydanticOutputParser(pydantic_objectPersonInfo) # 把格式说明注入提示词 prompt PromptTemplate( template提取以下文本中的人物信息。\n{format_instructions}\n文本{text}, input_variables[text], partial_variables{format_instructions: parser.get_format_instructions()} )第三层重试机制。如果解析失败把错误信息反馈给模型让它重新生成。LangChain的RetryOutputParser就是干这个的。第四层换模型。有些小模型在格式遵循上确实差一些。如果格式要求严格考虑用能力更强的模型或者用支持结构化输出的模型接口。4.2 检索效果差的排查清单RAG应用最怕的就是“答非所问”。当检索结果不相关时按以下顺序排查排查项可能问题解决方向文档切分片段太碎或太大调整chunk_size和overlap嵌入模型模型不适合中文/领域换多语言或领域微调模型检索数量k值太小增大k配合重排序查询表达用户问法和文档用词不一致加入查询改写步骤向量库索引未更新重新构建或增量更新提示词模型没用好检索结果优化提示词强调基于参考信息回答我踩过的一个坑是文档里有大量表格按字符切分后表格结构全乱了检索出来的片段根本读不懂。后来改成先把表格转成文本描述再切分效果好了很多。4.3 成本与延迟优化的实战经验大模型应用的成本和延迟是绕不开的问题。几个实用的优化手段缓存。相同的问题没必要重复调用模型。LangChain支持多种缓存后端from langchain.globals import set_llm_cache from langchain_community.cache import SQLiteCache set_llm_cache(SQLiteCache(database_path.langchain.db))流式输出。用户不需要等完整回答生成完才看到内容。用streamingTrue可以让模型边生成边返回体验好很多。模型分级。简单任务用小模型复杂任务用大模型。比如意图识别用便宜的小模型最终回答用大模型。减少检索片段数。k从5降到3token消耗能省不少效果未必差多少。关键是提高检索精度。异步调用。如果应用需要同时调用多个模型或工具用异步接口能显著降低总延迟。4.4 版本升级导致的兼容性问题LangChain的迭代速度在开源项目里算快的这导致一个尴尬局面你搜到的教程可能是半年前的代码已经跑不通了。我的应对策略是锁定版本在requirements.txt里写死版本号不要用。关注迁移指南大版本升级时官方会发迁移文档花半小时读一下能省很多调试时间。抽象隔离层不要把LangChain的API散落在业务代码各处封装一层自己的接口升级时只改封装层。测试覆盖核心链路写单元测试升级后跑一遍就知道有没有问题。提示如果你在生产环境用LangChain建议等新版本发布后观察一两周再升级让社区先踩坑。5. 进阶方向与个人实践体会5.1 Agent与工具调用的落地场景当应用需要“自主决策”时Agent就派上用场了。比如一个客服助手需要根据用户问题决定是查订单、查物流还是转人工。LangChain的Agent机制让模型自己选择调用哪个工具。from langchain.agents import Tool, AgentExecutor, create_openai_tools_agent from langchain_openai import ChatOpenAI def query_order(order_id: str) - str: 根据订单号查询订单状态 # 实际业务逻辑 return f订单{order_id}已发货 def query_logistics(order_id: str) - str: 根据订单号查询物流信息 return f订单{order_id}预计明天送达 tools [ Tool(name查询订单, funcquery_order, description输入订单号返回订单状态), Tool(name查询物流, funcquery_logistics, description输入订单号返回物流信息) ] llm ChatOpenAI(modelgpt-4o-mini) agent create_openai_tools_agent(llm, tools, prompt) executor AgentExecutor(agentagent, toolstools, verboseTrue) result executor.invoke({input: 帮我查一下订单12345的物流})Agent的坑在于模型可能选错工具、传错参数、陷入循环。生产环境一定要加最大迭代次数限制和超时控制。5.2 从原型到生产的差距原型跑通和上线能用是两回事。我总结几个容易被忽视的点错误处理。模型调用可能超时、限流、返回异常。每个外部调用都要有try-except和降级方案。可观测性。记录每次调用的输入、输出、耗时、token消耗。出了问题能快速定位。LangChain有LangSmith等工具可以接入。安全过滤。用户输入可能包含注入攻击模型输出可能包含不当内容。输入输出都要过一遍过滤。并发控制。模型接口通常有QPS限制需要加队列和限流。数据隐私。敏感数据不要发给第三方模型考虑本地部署或脱敏处理。5.3 我个人的使用建议用了大半年LangChain最大的体会是它是脚手架不是银弹。它能帮你快速搭建应用但不能替你思考应用逻辑。提示词怎么写、检索怎么优化、流程怎么设计这些才是决定效果的关键。另外不要为了用LangChain而用LangChain。如果你的应用只是简单调用一下模型直接写API请求反而更清爽。LangChain的价值在流程复杂时才能体现。最后分享一个实用技巧把LangChain的调试模式打开。设置export LANGCHAIN_VERBOSEtrue能看到每一步的输入输出排查问题时非常有用。刚开始可能会被大量日志淹没但习惯了之后你会发现自己对应用的理解深了很多。

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

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

免费获取报价 →
↑