
1. 项目概述为什么我们需要LangChain如果你最近在捣鼓AI应用开发尤其是想把手头的OpenAI API或者某个开源大模型用起来大概率会听到一个词LangChain。我第一次接触它时想法很简单——不就是个调用API的封装库吗我自己写个HTTP请求不也一样但真正上手几个项目后我发现之前的想法太天真了。LangChain解决的远不止是“发送一个提示词然后等待回复”这么简单。想象一下这个场景你想开发一个智能客服助手它需要能查询内部知识库比如产品手册PDF能根据用户问题决定是直接回答、还是去查数据库、甚至是调用一个计算天气的API。如果只用原始的API调用你需要自己处理文档加载与分割、向量化存储与检索、对话历史管理、多步骤推理的逻辑编排……这些“胶水代码”会迅速让你的项目变得臃肿且难以维护。而LangChain本质上就是一套精心设计的“乐高积木”和“搭建说明书”它把这些构建AI应用所需的通用模块模型I/O、记忆、检索、代理、链标准化了让你能专注于业务逻辑的拼接而不是重复造轮子。简单说LangChain是一个用于开发由大语言模型驱动的应用程序的框架。它的核心价值在于标准化和编排。它把与大模型交互、处理外部数据、管理应用状态这些复杂任务抽象成一个个组件并提供了一套优雅的方式来将它们组合成更复杂的应用。无论是简单的问答机器人还是涉及多工具调用、长文档分析、复杂工作流的智能体AgentLangChain都提供了现成的模式和组件来加速开发。对于开发者而言这意味着更快的开发速度、更清晰的代码结构以及更容易复用的最佳实践。2. LangChain核心架构与组件深度解析要玩转LangChain不能只停留在调用ChatOpenAI这个层面必须理解其核心的架构思想。LangChain将应用构建过程分解为多个层次从底层的模型交互到高层的端到端链和代理。2.1 模型I/O与AI对话的基石这是最基础的一层负责与大模型的直接通信。LangChain在这里做了关键抽象将不同的模型提供商OpenAI, Anthropic 本地部署的Llama 通义千问等的API统一成相同的接口。这主要通过三个核心对象实现提示词模板Prompt Templates这是避免硬编码提示词的关键。你可以创建一个模板其中包含变量比如“请根据以下上下文回答问题{context}\n问题{question}”。在实际调用时再传入具体的context和question。这样做不仅使代码更清晰还便于进行A/B测试比较不同提示词的效果。语言模型Language Models分为两类。LLM对象接收字符串返回字符串适用于补全类任务。ChatModel对象接收一组消息如SystemMessage, HumanMessage, AIMessage返回AIMessage更适合多轮对话场景。这种抽象让你可以轻松地在GPT-4和Claude之间切换而业务逻辑代码几乎不用改动。输出解析器Output Parsers大模型的输出是自由文本但我们的程序往往需要结构化的数据比如JSON对象、列表甚至是Pydantic模型实例。输出解析器指导模型按照特定格式回复并负责将文本解析成结构化的对象。例如你可以定义一个CommaSeparatedListOutputParser让模型用逗号分隔列表来回答然后解析器会将其自动转换成Python列表。实操心得不要小看输出解析器。在构建需要稳定、自动化处理模型输出的流水线时例如从一段文本中提取实体并存入数据库使用PydanticOutputParser定义你期望的数据结构能让整个流程的健壮性提升一个数量级大大减少后处理代码的复杂度。2.2 检索让模型拥有“长期记忆”和“专业知识”大模型本身的知识受限于其训练数据且无法感知训练截止日期后的信息或你的私有数据。检索Retrieval组件就是为了解决这个问题它通常与“RAG”检索增强生成架构紧密相关。其工作流程可以分解为文档加载Document Loaders从各种来源PDF、Word、网页、Notion、数据库加载原始文档。LangChain提供了海量的集成几乎涵盖了所有常见的数据源。文档分割Text Splitters大模型有上下文长度限制。一篇长文档必须被切分成语义相关的“块”Chunks。简单的按字符或换行分割效果很差LangChain提供了递归字符分割、按标记分割等多种策略更高级的还有基于语义的滑动窗口分割旨在保持上下文的完整性。向量化与存储Vectorstores这是检索的核心。使用嵌入模型Embedding Model将文本块转换为高维向量即 embeddings然后存入向量数据库如Chroma Pinecone Weaviate。向量之间的“距离”如余弦相似度代表了文本语义的相似度。检索器Retrievers给定一个问题将其同样向量化然后在向量数据库中搜索最相似的文本块。这些相关的块将作为“上下文”插入到给模型的提示词中从而让模型基于这些信息生成答案。2.3 记忆Memory实现连贯的多轮对话如果每次对话模型都“失忆”那体验将非常糟糕。Memory组件负责持久化和管理对话历史。LangChain提供了多种记忆方案对话缓存ConversationBufferMemory简单地将所有历史对话都保存在内存中。适用于短对话但长对话会导致提示词过长。对话摘要缓存ConversationSummaryMemory让模型定期对之前的对话进行摘要只将摘要和最近几条记录作为历史。这能有效控制token消耗但可能丢失细节。向量存储记忆VectorStoreRetrieverMemory将历史对话片段向量化存储每次根据当前查询检索最相关的历史片段。这种方式更智能能回忆起很久以前但相关的内容。2.4 链Chains将组件编排成工作流链是LangChain的灵魂。如果说组件是乐高积木链就是拼装说明书。一个链将多个组件或其他链按预定顺序组合起来完成一个特定任务。最简单的链是LLMChain它组合了一个提示词模板和一个语言模型。但链的强大之处在于其组合性。顺序链SequentialChain多个链按顺序执行前一个链的输出作为后一个链的输入。例如第一个链总结一篇长文第二个链根据总结回答问题。转换链TransformChain允许你插入自定义的Python函数来处理数据提供了极大的灵活性。检索问答链RetrievalQA这是一个非常经典且强大的预置链。它内部集成了检索器、提示词模板和语言模型你只需要提供向量数据库和问题它就能自动完成“检索相关文档 - 组合提示词 - 调用模型 - 返回答案”的全流程。2.5 代理Agents让模型学会“使用工具”代理是LangChain中最具想象力的部分。它赋予了大模型“行动”的能力。其核心思想是模型本身不直接回答问题而是通过一个“推理-行动”循环来决定下一步该做什么。工具Tools代理可以调用的函数。这可以是搜索引擎API、计算器、数据库查询函数或者任何你能用代码实现的能力。LangChain内置了许多工具也支持轻松自定义。代理执行器AgentExecutor这是代理的运行时引擎。它将用户的输入、可用的工具列表交给代理即大模型进行思考。模型会输出一个“动作”Action比如“我需要搜索天气调用Search工具参数是‘北京今天天气’”。执行器则调用对应的工具获取结果Observation再将这个结果连同历史一起交还给模型进行下一步思考。这个过程循环进行直到模型认为它已经收集到足够信息可以给出最终答案Final Answer。注意事项代理非常强大但也容易失控。模型可能会陷入思考循环或者调用不需要的工具。务必为代理设置max_iterations最大迭代次数参数并仔细设计工具的提示词描述确保模型能准确理解每个工具的用途。对于复杂任务使用StructuredTool为工具参数提供明确的JSON Schema能显著提升调用的准确性。3. 从零搭建一个基于LangChain的RAG问答系统理论讲得再多不如动手实践。我们来一步步构建一个最经典的RAG应用基于本地文档的智能问答系统。我们将使用Chroma作为向量数据库OpenAI的嵌入和聊天模型。3.1 环境准备与依赖安装首先创建一个新的Python虚拟环境并安装核心包。我强烈建议使用uv或poetry进行依赖管理这里我们用pip演示。# 创建并激活虚拟环境可选但推荐 python -m venv .venv source .venv/bin/activate # Linux/Mac # .venv\Scripts\activate # Windows # 安装LangChain及其相关依赖 pip install langchain langchain-community langchain-openai chromadb tiktoken pypdflangchain: 核心框架。langchain-community: 社区维护的第三方集成很多文档加载器、工具在这里。langchain-openai: OpenAI模型的官方集成。chromadb: 轻量级、可本地运行的向量数据库。tiktoken: OpenAI用于计算token的库。pypdf: 用于读取PDF文档。确保你已设置好OpenAI的API密钥可以通过环境变量设置export OPENAI_API_KEYyour-api-key-here3.2 文档加载、分割与向量化假设我们有一个名为product_manual.pdf的产品手册。第一步是将其内容处理并存入向量数据库。from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma # 1. 加载文档 loader PyPDFLoader(./product_manual.pdf) documents loader.load() # 2. 分割文档 # 这里使用递归字符分割器尝试按段落、句子等自然分隔符进行分割 text_splitter RecursiveCharacterTextSplitter( chunk_size1000, # 每个块的最大字符数 chunk_overlap200, # 块之间的重叠字符数有助于保持上下文连贯 separators[\n\n, \n, 。, , , , , ] # 分割优先级 ) chunks text_splitter.split_documents(documents) print(f原始文档被分割成 {len(chunks)} 个文本块。) # 3. 创建嵌入模型并向量化存储 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) # 使用较小的嵌入模型以节省成本 # 持久化存储到本地目录 ./chroma_db vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db ) vectorstore.persist() # 显式持久化到磁盘 print(文档已成功向量化并存储到Chroma数据库。)关键参数解析chunk_size这是最重要的参数之一。太小会导致信息碎片化太大会超出模型上下文窗口并降低检索精度。通常根据模型上下文窗口如GPT-4的128K和你的文档特点来定。对于通用文档500-1500是一个常见范围。chunk_overlap重叠部分可以防止一个完整的句子或概念被硬生生切断使得检索到的块能包含更完整的上下文。一般设置为chunk_size的10%-20%。embedding modeltext-embedding-3-small是OpenAI性价比很高的模型。如果你的文档是中文为主可以考虑使用专门优化过的中文嵌入模型如bge-large-zh并通过HuggingFaceEmbeddings集成。3.3 构建检索链并进行问答数据库建好后我们就可以构建一个问答链了。from langchain.chains import RetrievalQA from langchain_openai import ChatOpenAI from langchain.prompts import PromptTemplate # 1. 从磁盘加载已有的向量数据库 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore Chroma(persist_directory./chroma_db, embedding_functionembeddings) # 2. 将向量数据库转换为检索器 # search_kwargs{k: 4} 表示每次检索返回最相似的4个文档块 retriever vectorstore.as_retriever(search_kwargs{k: 4}) # 3. 定义提示词模板指导模型如何利用上下文 prompt_template 请严格根据以下提供的上下文信息来回答问题。如果你在上下文中找不到明确答案请直接说“根据提供的资料我无法回答这个问题”不要编造信息。 上下文 {context} 问题{question} 请基于上下文给出答案 PROMPT PromptTemplate( templateprompt_template, input_variables[context, question] ) # 4. 初始化大语言模型 llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # temperature0使输出更确定、更少随机性 # 5. 创建检索问答链 qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 最常用的类型将所有检索到的文档“塞”进提示词 retrieverretriever, return_source_documentsTrue, # 返回源文档便于调试和溯源 chain_type_kwargs{prompt: PROMPT} # 使用我们自定义的提示词 ) # 6. 进行提问 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]}...) # 截取片段chain_type详解stuff最简单直接将所有检索到的文档拼接后一次性传给模型。优点是信息完整缺点是有可能超出模型上下文限制。map_reduce先为每个检索到的文档单独生成一个答案map再将这些答案汇总成一个最终答案reduce。适合处理大量文档但成本更高且可能丢失中间细节。refine迭代式处理。用第一个文档生成初始答案然后用后续文档不断去“精炼”这个答案。通常能产生质量很高的答案但速度较慢。map_rerank为每个文档生成答案并打分选择分数最高的答案。适用于答案可能明确存在于某个单一文档的场景。对于大多数中小型文档库stuff方法因其简单高效而成为首选。务必监控提示词的token消耗。4. 进阶实战构建一个多工具AI代理现在让我们挑战一个更复杂的场景构建一个能联网搜索并计算的AI代理。这个代理将能回答“北京和上海现在的温差是多少”这类需要结合实时信息和计算的问题。4.1 定义工具我们将定义两个工具一个用于网络搜索一个用于数学计算。from langchain.agents import Tool, AgentExecutor, create_react_agent from langchain_openai import ChatOpenAI from langchain import hub import requests import json import math # 工具1一个简单的网络搜索工具使用DuckDuckGo即时答案API示例 def search_web(query: str) - str: 用于搜索网络上的实时信息如天气、新闻等。 try: # 注意这是一个简化的示例实际可使用SerpAPI、Tavily等专业搜索API url fhttps://api.duckduckgo.com/?q{requests.utils.quote(query)}formatjson response requests.get(url, timeout10) data response.json() # 提取摘要信息 abstract data.get(AbstractText, ) if abstract: return abstract[:500] # 限制返回长度 else: return 未找到相关的即时信息。 except Exception as e: return f搜索过程中出现错误{e} # 工具2一个数学计算工具 def calculator(expression: str) - str: 用于执行数学计算。输入应为一个数学表达式如 3 5 * 2 或 sqrt(16)。 try: # 安全警告在生产环境中使用eval是极度危险的这里仅作演示。 # 应使用ast.literal_eval或专门的数学解析库如numexpr。 # 此处为简化我们仅支持一个非常有限的、安全的操作集。 safe_dict {__builtins__: None, sqrt: math.sqrt, sin: math.sin, cos: math.cos, pi: math.pi} result eval(expression, {__builtins__: None}, safe_dict) return str(result) except Exception as e: return f计算错误{e}。请确保输入合法的数学表达式。 # 将函数包装成LangChain工具 tools [ Tool( nameWebSearch, funcsearch_web, description当需要获取实时、最新或事实性信息如当前天气、新闻、股票价格时使用此工具。输入应为明确的搜索查询词。 ), Tool( nameCalculator, funccalculator, description当需要进行数学运算或单位换算时使用此工具。输入应为一个清晰的数学表达式例如 15 * 2 3 或 98.6 - 32 * 5/9。 ) ]4.2 创建代理并运行我们将使用ReAct框架这是一种让模型进行“推理Reasoning”和“行动Acting”的经典代理模式。# 1. 拉取一个预定义的ReAct提示词模板 prompt hub.pull(hwchase17/react) # 2. 初始化大模型温度可以稍高一点以鼓励思考 llm ChatOpenAI(modelgpt-3.5-turbo, temperature0.1) # 3. 使用工具和提示词创建ReAct代理 agent create_react_agent(llm, tools, prompt) # 4. 创建代理执行器并限制最大迭代次数防止死循环 agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, # 开启详细日志可以看到模型的“思考过程” handle_parsing_errorsTrue, # 优雅处理模型输出解析错误 max_iterations5, # 安全限制最多尝试5次思考-行动循环 early_stopping_methodgenerate # 当模型连续两次输出相同动作时停止 ) # 5. 向代理提问 question 北京现在的气温是25摄氏度上海的气温是华氏80度两地的温差是多少摄氏度 result agent_executor.invoke({input: question}) print(f\n最终答案{result[output]})当你运行这段代码并设置verboseTrue时你会在控制台看到类似以下的精彩输出这正是代理的“思考链” 进入新的代理执行链... 思考用户想知道北京和上海的温差单位是摄氏度。我有北京的温度25°C但上海的温度是华氏度。我需要先将华氏度转换为摄氏度然后计算差值。我应该使用计算器工具。 行动使用工具[Calculator]。 行动输入80 - 32 * 5/9 观察26.666666666666668 思考我已经将80°F转换成了约26.67°C。现在计算温差26.67°C - 25°C。 行动使用工具[Calculator]。 行动输入26.666666666666668 - 25 观察1.6666666666666679 思考我得到了温差约为1.67°C。现在可以给出最终答案了。 最终答案北京25°C和上海约26.67°C的温差大约是1.67摄氏度。这个例子清晰地展示了代理如何将复杂问题分解为“使用计算器转换单位” - “使用计算器计算差值”的步骤并自主调用工具完成任务。5. 常见问题、性能优化与避坑指南在实际使用LangChain开发和生产化过程中你会遇到各种挑战。以下是我从多个项目中总结出的核心问题和解决方案。5.1 检索效果不佳怎么办RAG系统的效果严重依赖于检索质量。如果模型总是回答“找不到答案”或给出无关信息请按以下步骤排查检查文档分割这是最常见的问题根源。用print输出几个分割后的chunk检查它们是否是语义完整的段落。避免一个句子被截断或两个不相关的主题混在一个块里。尝试调整chunk_size和chunk_overlap或者换用MarkdownHeaderTextSplitter如果你的文档结构清晰。评估嵌入模型不同的嵌入模型对中文、专业术语的编码能力差异很大。在中文场景下可以尝试BAAI/bge-large-zh或moka-ai/m3e-base等开源中文嵌入模型通过HuggingFaceEmbeddings加载并与OpenAI的嵌入效果进行对比。优化检索策略调整检索数量ksearch_kwargs{“k”: 4}中的k值需要权衡。太小可能遗漏关键信息太大会引入噪声并增加token消耗。通常从3-5开始测试。使用MMR最大边际相关性除了相似度还可以考虑多样性。Chroma等数据库支持MMR检索在保证相关性的同时避免返回内容高度重复的块。retriever vectorstore.as_retriever( search_typemmr, # 使用MMR检索 search_kwargs{k”: 6, “fetch_k”: 20, “lambda_mult”: 0.5} )增加元数据过滤在分割文档时可以为每个块添加元数据如来源文件、章节标题。检索时可以基于元数据先进行过滤例如只检索某个特定手册的第三章。改进提示词在提示词中明确指令模型“严格基于上下文”并设计当上下文不相关时的回复话术如“资料未提及”。可以加入“如果上下文不相关请直接说明”的指令。5.2 如何处理超长上下文和成本控制大模型的API调用是按Token计费的上下文越长越贵。精细化分割与检索好的检索器能精准找到最相关的几个块是控制成本的第一道关口。选择高效的链类型对于超长文档集优先考虑map_reduce或refine而不是stuff。使用更经济的模型组合对于嵌入步骤使用text-embedding-3-small而非large版本。对于生成步骤在非关键任务上使用gpt-3.5-turbo而非gpt-4。可以利用LangChain的RouterChain让一个更小的模型先判断问题类型和复杂度再决定是否调用大模型。实现缓存层对相同的查询和文档块其嵌入向量和模型回复是可以缓存的。可以使用LangChain的CacheBackedEmbeddings和SQLiteCache来缓存嵌入结果使用BaseCache接口如RedisSemanticCache缓存LLM的响应能极大降低重复请求的成本。5.3 代理Agent不稳定或陷入循环这是代理开发的常态。优化策略包括设计清晰的工具描述工具的描述description是模型理解工具用途的唯一依据。描述必须精确、无歧义并说明输入格式。例如“计算数学表达式”就比“进行计算”好得多。使用结构化工具StructuredTool对于参数复杂的工具使用StructuredTool并为其定义Pydantic参数模型可以强制模型输出结构化的参数极大提高调用准确性。设置严格的停止条件务必设置max_iterations如5-10次。同时利用early_stopping_method当模型连续输出相同的动作或最终答案时自动停止。为代理提供示例Few-Shot在提示词中加入几个完整的“问题-思考-行动-观察-答案”的示例能显著提升代理的推理能力。这可以通过自定义hub.pull下来的提示词模板来实现。考虑使用更强大的模型代理任务对模型的推理能力要求很高。如果gpt-3.5-turbo表现不佳尝试切换到gpt-4或claude-3系列模型效果往往有质的提升。5.4 LangChain vs. LangGraph vs. 其他框架这是最近社区里很热的话题。简单来说LangChain专注于构建链式Sequential和基于代理Agent的应用。它的核心抽象是“链”适合有明确、线性或有限分支的工作流。LangGraph是LangChain团队推出的新库专注于构建有状态、可循环、多参与者的图Graph工作流。它用“图”的节点和边来定义应用流程非常适合需要复杂循环、人工干预、或并发执行的任务比如一个模拟游戏或一个需要多人审批的流程。你可以把LangGraph看作是LangChain在编排超复杂、非线性工作流时的“升级版武器”。如何选择对于大多数RAG、简单工具调用代理LangChain完全足够且更成熟。如果你的应用涉及复杂的状态机、循环审批、或类似自动化的多步骤流程LangGraph提供了更强大和直观的抽象。两者可以结合使用例如在LangGraph的一个节点里运行一个LangChain的RAG链。5.5 部署与生产化考量当你的原型需要走向生产环境时需要注意异步支持LangChain全面支持异步async/await在高并发场景下使用异步调用可以大幅提升吞吐量。确保你的链或代理调用使用ainvoke,abatch等方法。可观测性集成像LangSmith这样的追踪平台至关重要。它能记录每一次链的调用、每一步的输入输出、token消耗和延迟是调试复杂应用、分析成本和性能瓶颈的利器。模块化与测试将你的提示词模板、链、代理逻辑模块化并编写单元测试。特别是测试代理在各种边缘情况下的行为。错误处理与降级网络请求、模型API调用都可能失败。代码中必须有完善的重试、超时和降级机制例如当GPT-4调用失败时自动降级到GPT-3.5。