LangChain实战:30分钟构建RAG文档问答与AI智能体

📅 2026/8/13 1:42:59
LangChain实战:30分钟构建RAG文档问答与AI智能体
1. 从“胶水代码”到“智能应用流水线”我为什么选择LangChain如果你最近在捣鼓大语言模型LLM想把ChatGPT、Claude或者本地部署的Llama、Qwen这些“大脑”真正用起来而不是仅仅停留在聊天窗口里那你大概率已经听过或者正在被“LangChain”这个词包围。我第一次接触它时感觉就像回到了Web开发早期每个项目都要自己手写一堆连接数据库、处理表单、管理会话的“胶水代码”繁琐且重复。LangChain的出现就是为了解决这个痛点它不是一个新模型而是一个框架一套工具链或者说一个专门为构建基于LLM的应用而设计的“智能应用流水线”。简单来说LangChain帮你把“调用大模型API”这件简单的事升级成了“构建一个可靠、可扩展、具备复杂逻辑的AI应用”这件系统工程。它把常见的模式抽象成组件比如如何把用户的问题和你的知识库文档结合起来这就是RAG如何让大模型学会使用工具比如查天气、执行计算如何管理多轮对话的上下文以及如何把多个步骤串联成一个自动化的工作流。没有它你可能需要自己处理提示词工程、上下文窗口管理、工具调用解析、异步流式输出等一系列令人头疼的细节。有了它你可以像搭积木一样快速组合出功能强大的AI智能体Agent或者问答系统。网上很多人争论LangChain和它的“兄弟”LangGraph或者和Dify、CrewAI这些后起之秀有什么区别。我的看法是LangChain更像是一个底层工具箱和标准件库它提供了最大的灵活性和控制力适合开发者深入定制而Dify、CrewAI等则是在这个工具箱基础上封装好的一体化解决方案或高级工作台开箱即用但定制性相对受限。对于想真正理解LLM应用架构、并拥有完全掌控权的开发者而言从LangChain入门是必经之路。最近甚至看到有说法OpenAI内部团队用类似LangChain的方法论在5个月内零手写代码产出了百万行级别的系统这虽然无从考证但足以说明模块化、链式编排的思想在AI工程化中的巨大价值。那么这篇快速入门的目标就是让你在30分钟内绕过那些复杂的概念堆砌直接动手搭建起两个最核心、最实用的LangChain应用场景——文档问答RAG和智能体Agent并理解其背后的运作原理。我们会用最少的依赖最多的注释带你走通整个流程。2. 环境搭建与核心概念“祛魅”在开始写代码之前我们需要一个清晰的地图。LangChain的体系看似庞大但核心就是几个概念一旦理解后面就是组合使用的问题。2.1 极简环境准备只安装必要的我不建议一开始就pip install langchain[all]那会引入大量你可能暂时用不到的依赖。我们聚焦核心。# 1. 安装LangChain核心包 pip install langchain-core langchain # 2. 安装社区集成包这里以OpenAI为例你需要有自己的API Key # 如果你用国产模型比如DeepSeek可以安装 langchain-deepseek 或类似社区包 pip install langchain-openai # 3. 安装文本处理和向量数据库客户端用于RAG示例 # 我们选用轻量级的ChromaDB作为本地向量库以及文本分割器 pip install chromadb langchain-chroma tiktoken # 4. 可选但推荐安装用于Agent示例的工具调用模拟包 pip install langchain-community安装完成后建议你准备好一个LLM的API Key。本文以OpenAI GPT-3.5-turbo为例因为它最通用。如果你没有可以使用开源的Ollama本地运行一个模型如Llama 3.1只需将后续代码中的ChatOpenAI替换为ChatOllama并指定模型名称即可。2.2 五大核心概念五分钟掌握LangChain的文档里概念很多但入门只需抓住这五个模型 I/O (Model I/O)这是最底层的一环负责与大模型对话。主要包括LLM 纯文本补全模型如早期的GPT-3。ChatModel 专为对话设计的模型如GPT-3.5-turbo, Claude。这是我们最常用的。提示词模板 (PromptTemplate) 避免在代码中硬编码提示词。你可以创建一个模板把{topic}这样的占位符在运行时替换成具体内容。检索 (Retrieval) 这是RAG检索增强生成的核心。当模型需要回答超出其训练数据或最新的问题时就从你自己的知识库如文档、数据库中查找相关信息然后连同问题和信息一起发给模型。关键组件有文档加载器 (Document Loader) 从PDF、网页、Notion等处加载文档。文本分割器 (Text Splitter) 将长文档切成模型上下文窗口能容纳的小块。向量存储 (Vector Store) 将文本块转换成向量嵌入并存储实现相似性搜索。链 (Chain) LangChain的灵魂。它不是简单的顺序调用而是将多个组件模型、提示词、工具等按特定逻辑组合成一个可执行的工作流。最简单的链是LLMChain提示词 模型复杂的链可以包含条件判断、循环等。智能体 (Agent) 链的升级版。智能体的核心是让模型学会自主决策使用哪些工具。你给模型一套工具如计算器、搜索引擎API、数据库查询和一个目标模型会自己规划步骤“要解决这个问题我需要先查天气再用结果进行计算。” Agent LLM 工具集 决策逻辑。记忆 (Memory) 让对话或应用拥有“记忆”能力记住之前交互的内容。可以是简单的对话缓冲区也可以是更复杂的、基于向量存储的长期记忆。注意 你可能还听过LangGraph它是基于LangChain构建的用于描述有状态、多环节、可能循环或分支的复杂工作流。如果把Chain比作一条直线LangGraph就是一张流程图。对于入门来说我们先掌握Chain和Agent就够了。理解了这些我们就可以开始实战了。下面两个例子将分别对应检索RAG和智能体这两个最核心的应用。3. 实战一构建你的第一个RAG文档问答系统RAG是目前LangChain最火的应用场景。假设你有一份公司内部的产品手册PDF你想让AI根据这份手册来回答问题。3.1 步骤拆解与代码实现整个过程分为四步加载文档 - 分割文本 - 向量化存储 - 检索问答。# 导入必要的模块 import os from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_community.document_loaders import TextLoader # 示例用文本实际可用PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_chroma import Chroma from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate # 步骤1: 设置你的OpenAI API Key (关键请替换成你自己的) os.environ[OPENAI_API_KEY] sk-你的真实api-key # 初始化一个性价比高的聊天模型 llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # 步骤2: 加载与分割文档 # 假设我们有一个 product_manual.txt 文件 loader TextLoader(./product_manual.txt) documents loader.load() # 使用递归字符分割器它尝试按段落、句子、单词等自然边界分割 text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 每个块大约500字符 chunk_overlap50, # 块之间重叠50字符避免信息被割裂 separators[\n\n, \n, 。, , , , , ] # 分割优先级 ) chunks text_splitter.split_documents(documents) print(f原始文档被分割成了 {len(chunks)} 个文本块。) # 步骤3: 向量化并存入向量数据库 # 使用OpenAI的嵌入模型将文本转换为向量 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) # 将分割后的文本块存入ChromaDB。persist_directory 参数让数据持久化到磁盘 vector_store Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db # 数据将保存在这个目录 ) vector_store.persist() # 确保写入磁盘 print(向量数据库已创建并持久化。) # 步骤4: 创建检索问答链 # 首先定义一个提示词模板告诉模型如何利用检索到的上下文 qa_prompt PromptTemplate.from_template( 请根据以下上下文信息来回答问题。如果你不知道答案就说不知道不要编造。 上下文 {context} 问题{question} 答案 ) # 创建检索器从向量库中搜索最相关的3个文本块 retriever vector_store.as_retriever(search_kwargs{k: 3}) # 构建RetrievalQA链它封装了检索问答的过程 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # “stuff”模式将所有检索到的上下文塞进提示词。还有map_reduce、refine等处理长上下文的方式。 retrieverretriever, chain_type_kwargs{prompt: qa_prompt}, # 使用我们自定义的提示词 return_source_documentsTrue # 返回检索到的源文档便于调试 ) # 步骤5: 提问 question 我们产品的主要优势是什么 result qa_chain.invoke({query: question}) print(f问题{question}) print(f答案{result[result]}) print(\n--- 检索到的参考来源 ---) for i, doc in enumerate(result[source_documents][:2]): # 打印前两个来源 print(f[来源{i1}] {doc.page_content[:200]}...) # 只打印前200字符3.2 关键细节与避坑指南文本分割是门艺术chunk_size和chunk_overlap没有银弹。500/50是一个通用起点。如果答案总是支离破碎尝试减小chunk_size如果答案缺乏连贯性尝试增大overlap。对于中文separators里加入句号、逗号很重要。嵌入模型的选择示例用了OpenAI的嵌入模型需要计费。对于本地或低成本方案可以考虑开源的BAAI/bge-small-zh-v1.5等模型配合langchain-huggingface包。关键点问答用的LLM和生成嵌入的模型最好在语义空间上对齐例如都用OpenAI系或都用BGE系否则检索精度可能下降。chain_type的选择stuff最简单直接把所有检索到的上下文拼接起来发给模型。适合上下文总长度不超过模型限制的情况。map_reduce先对每个文本块单独生成答案Map再汇总所有答案生成最终答案Reduce。适合处理大量文档但成本高、可能丢失全局信息。refine迭代式处理用第一个块生成初始答案然后用后续块不断“优化”这个答案。通常质量更高但速度慢。对于入门stuff足够了。当你发现提示词因上下文过长而被截断时再考虑后两者。向量数据库持久化示例中Chroma数据保存到了本地./chroma_db。这意味着下次启动程序时你可以直接加载已有的数据库无需重新嵌入节省大量时间和API费用# 第二次及以后运行直接加载 vector_store Chroma(persist_directory./chroma_db, embedding_functionembeddings) retriever vector_store.as_retriever()这是生产级应用必须考虑的一步。4. 实战二创建一个能使用工具的AI智能体Agent智能体让AI从“答题者”变成了“执行者”。我们创建一个能进行简单数学计算和获取当前日期的智能体。4.1 定义工具与初始化AgentLangChain提供了多种Agent类型我们使用最通用、功能最强的ReAct范式Agent。from langchain.agents import AgentExecutor, create_react_agent from langchain import hub from langchain.tools import Tool from datetime import datetime import math # 步骤1: 自定义工具函数 # 工具1: 一个能计算平方根的函数 def calculate_sqrt(input_str: str) - str: 计算一个数的平方根。输入应该是一个数字字符串。 try: number float(input_str) if number 0: return 错误不能计算负数的平方根。 result math.sqrt(number) return f{number} 的平方根是 {result:.4f} except ValueError: return 错误请输入一个有效的数字。 # 工具2: 一个能返回当前日期和时间的函数 def get_current_time(input_str: str ) - str: 返回当前的日期和时间。输入参数被忽略。 now datetime.now() # 忽略输入参数是LangChain工具定义的一个常见模式 return f当前日期和时间是{now.strftime(%Y-%m-%d %H:%M:%S)} # 步骤2: 将函数包装成LangChain Tool对象 # func参数指向我们的函数description至关重要Agent靠它来决定是否使用该工具。 tools [ Tool( nameSquareRootCalculator, funccalculate_sqrt, description在需要计算一个非负数的平方根时使用。输入应该是一个数字字符串。例如如果问题是‘16的平方根是多少’输入就是‘16’。 ), Tool( nameCurrentTime, funcget_current_time, description在用户询问当前时间、今天日期或类似关于现在时刻的问题时使用。此工具不需要输入参数。 ) ] # 步骤3: 从LangChain Hub拉取一个优秀的ReAct提示词模板 # 这是一个社区维护的、经过优化的提示词比我们自己写要可靠得多。 prompt hub.pull(hwchase17/react) # 步骤4: 创建ReAct Agent # create_react_agent 将模型、工具和提示词组合成一个Agent对象。 agent create_react_agent(llm, tools, prompt) # 步骤5: 创建Agent执行器它负责运行Agent并处理工具调用循环 agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, # 设置为True可以看到Agent的思考过程这对调试和学习至关重要。 handle_parsing_errorsTrue, # 当Agent输出无法解析为工具调用时自动处理错误 max_iterations5, # 限制最大迭代次数防止陷入死循环 early_stopping_methodgenerate # 当Agent认为任务完成时可以提前停止 ) # 步骤6: 运行Agent print( Agent 思考过程演示 ) result agent_executor.invoke({ input: 请先告诉我现在的时间然后计算一下25的平方根。 }) print(f\n最终答案{result[output]})当你运行这段代码并将verboseTrue时会在控制台看到类似下面的精彩输出这正是ReActReasoning Acting思想的体现 Entering new AgentExecutor chain... 我需要按顺序回答两个问题当前时间和25的平方根。 我有两个工具CurrentTime 可以获取当前时间SquareRootCalculator 可以计算平方根。 首先我应该获取当前时间。 Action: CurrentTime Action Input: Observation: 当前日期和时间是2024-05-27 14:30:15 好的我已经知道时间了。现在需要计算25的平方根。 Action: SquareRootCalculator Action Input: 25 Observation: 25 的平方根是 5.0000 现在我有了两个信息时间和计算结果。我可以给出最终答案了。 Final Answer: 当前时间是2024年5月27日 14:30:15。25的平方根是5。 Finished chain. 最终答案当前时间是2024年5月27日 14:30:15。25的平方根是5。4.2 Agent开发的核心心法工具描述是灵魂description字段必须清晰、无歧义地说明工具的用途、适用场景和输入格式。Agent完全依赖这个描述来做决策。写得模糊Agent就会用错或不用。善用verboseTrue在开发阶段务必打开这个选项。它能让你亲眼看到Agent的思考链Chain of Thought理解它为什么做出某个决策在哪里卡住了。这是调试Agent最强大的手段。处理解析错误handle_parsing_errorsTrue是救命稻草。有时模型输出格式不符合工具调用规范这个设置能防止整个程序崩溃让模型重试。更高级的做法是自定义一个错误处理回调函数。设置迭代限制max_iterations必须设置。防止Agent陷入“思考-调用-再思考”的死循环消耗大量Token。从简单工具开始先让Agent能稳定调用一两个简单工具再逐步增加复杂度。不要一开始就给它十几种工具那会大大增加决策难度和出错概率。5. 进阶流式输出、复杂链与调试技巧当你掌握了基本用法接下来会遇到一些实际开发中的挑战。5.1 实现流式输出提升用户体验在Web应用中让答案一个字一个字地“流”出来体验远好于等待长时间后一次性显示。LangChain对流式输出有很好的支持。from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler # 方法1: 在模型层面启用流式输出原始Token streaming_llm ChatOpenAI( modelgpt-3.5-turbo, temperature0, streamingTrue, # 关键参数 callbacks[StreamingStdOutCallbackHandler()] # 回调函数将流输出到标准输出 ) # 注意这种方式下invoke会边生成边打印。但如果你用Agent或复杂链流式可能只体现在模型生成部分中间步骤还是会一次性输出。 # 方法2: 对于Chain使用 astream 或 astream_events (异步) # 这是更现代、更推荐的方式可以流式输出整个链的每一步。 import asyncio async def stream_qa_chain(): qa_chain RetrievalQA.from_chain_type(llmstreaming_llm, retrieverretriever, chain_typestuff) async for chunk in qa_chain.astream({query: 产品优势是什么}): # chunk 是一个字典包含中间状态和最终输出 if result in chunk: print(chunk[result], end, flushTrue) # 逐块打印结果 # asyncio.run(stream_qa_chain())关于你搜索词中提到的“流式输出吞掉reasoning-content字段”这通常发生在使用某些特定Agent或复杂事件流时。解决方案是使用astream_events并正确过滤事件类型或者检查回调函数的处理逻辑确保不是只捕获了最终输出而忽略了中间推理步骤。5.2 构建自定义链串联多个步骤当内置链不够用时你需要用LCELLangChain Expression Language来定义自己的链。LCEL使用管道符|非常直观。from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough # 假设我们想先让模型把用户问题改写成更好的搜索查询再用这个查询去检索最后回答。 # 1. 定义两个提示词模板 rewrite_prompt ChatPromptTemplate.from_template( 你是一个专业的搜索查询改写助手。请将以下用户问题改写成更适合用于向量数据库检索的简短关键词查询。\n原问题{question}\n改写后的查询 ) answer_prompt ChatPromptTemplate.from_template( 基于以下上下文回答问题\n上下文{context}\n问题{original_question}\n答案 ) # 2. 用LCEL组合链 custom_chain ( { original_question: RunnablePassthrough(), # 传递原始问题 enhanced_query: rewrite_prompt | llm | StrOutputParser(), # 改写问题 } | { context: lambda x: retriever.invoke(x[enhanced_query]), # 用改写后的问题检索 original_question: lambda x: x[original_question], # 继续传递原始问题 } | answer_prompt # 组合上下文和原始问题形成最终提示词 | llm # 发送给模型 | StrOutputParser() # 解析输出 ) result custom_chain.invoke(你们公司产品的售后服务政策怎么样) print(result)LCEL的优势在于声明式和组合性。每个步骤清晰可见而且这些Runnable对象本身可以嵌套组合构建出极其复杂的工作流。5.3 必须掌握的调试与日志记录技巧LangChain应用一旦复杂调试起来可能像黑盒。以下几个方法是我的必备工具箱verboseTrue无处不在 不仅在AgentExecutor在LLMChain、RetrievalQA等对象初始化时也可以设置它会打印内部的LLM调用和输入输出。使用回调函数 LangChain提供了强大的回调系统。你可以自定义回调来记录每次LLM调用的提示词、完成词、Token用量等。from langchain.callbacks import FileCallbackHandler import logging logging.basicConfig(levellogging.INFO, filenamelangchain.log) handler FileCallbackHandler(langchain.log) llm ChatOpenAI(..., callbacks[handler])手动检查中间结果 对于RAG经常需要检查检索到的文档是否相关。可以在调用链之前先单独测试检索器test_docs retriever.invoke(你们的产品优势) for doc in test_docs: print(doc.page_content[:300]) print(---)简化问题定位 如果链不工作先绕过链直接测试最基础的组件模型能正常响应吗提示词模板格式化对吗检索器能返回文档吗一步步隔离问题。6. 生态、选型与未来学习路径当你完成上面两个实战你已经掌握了LangChain最核心的60%功能。接下来你可以根据兴趣深入深入RAG 研究更高级的检索策略如MultiQueryRetriever生成多个查询以提升召回率、ContextualCompressionRetriever在检索后对文档进行压缩摘要。学习ParentDocumentRetriever来处理文档层次结构。深入Agent 尝试OpenAI ToolsAgent直接利用GPT-4等模型的原生函数调用能力格式更稳定。给你的Agent添加记忆让它能在多轮对话中记住上下文。探索Plan-and-Execute类型的Agent进行更复杂的任务分解。探索LangGraph 当你需要处理有循环、有状态、多角色协作的工作流时例如一个模拟辩论的Agent系统或者一个需要反复审核修改的写作流程LangGraph是你的下一个台阶。它用图的方式来定义和控制流程。关注部署与生产化 使用LangServe快速将你的链或Agent部署为API服务。用LangSmithLangChain官方平台来跟踪、监控、调试和评估你的所有LLM调用这是团队协作和项目上线的神器。关于选型最后再总结一下LangChain vs LangGraph LangChain是基础和标准库LangGraph是用于复杂工作流的扩展库。先学好LangChain。LangChain vs Dify/CrewAI 如果你需要快速搭建一个标准化的AI应用如客服机器人、知识库且不想写太多代码Dify这类低代码平台很棒。如果你要构建高度定制、逻辑复杂、需要深度集成到现有系统的AI能力LangChain提供的编程控制能力是不可替代的。CrewAI则更聚焦于多智能体协作场景。学习资源方面除了官方文档多关注GitHub上的示例项目并在自己的项目中大胆实践。从解决一个具体的小问题开始比如“用LangChain自动总结我每天收到的邮件”在实战中成长是最快的。记住这个领域变化飞快保持动手和阅读最新博客、论文的习惯比死记硬背API更重要。