尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

LangChain框架解析:从RAG到Agent的LLM应用开发实战

LangChain框架解析:从RAG到Agent的LLM应用开发实战 1. 为什么我们需要LangChain从“胶水代码”到“编排框架”的跃迁如果你在过去一年里尝试过基于大语言模型LLM构建应用大概率经历过这样的场景你兴致勃勃地调用了OpenAI的API拿到了一个不错的文本回复。然后你想能不能让模型记住之前的对话于是你开始手动维护一个对话历史列表。接着你又想接入自己的知识库于是你开始研究文本分割、向量化存储和相似度检索。再后来你想让模型能调用搜索引擎或者执行一个简单的计算你开始研究Function Calling写了一大堆解析和调度的逻辑。很快你的代码就变成了一团混杂着API调用、数据处理、业务逻辑和错误处理的“胶水代码”难以维护更别提复用了。这就是LangChain诞生的背景。它不是一个模型而是一个开发框架核心目标是解决LLM应用开发中的编排Orchestration问题。你可以把它想象成软件开发中的Spring或Django它提供了一套标准化的组件和设计模式让你能把LLM、外部工具、数据源和业务逻辑优雅地“组装”起来而不是用“胶水”粘起来。我最初接触LangChain时也犯过嘀咕不就是封装了几个API调用吗我自己写不行吗但在实际构建了几个复杂的Agent智能体和RAG检索增强生成系统后我深刻体会到它的价值远不止于此。它真正提供的是抽象层和最佳实践。例如它定义了Document、VectorStore、Chain、Agent等核心概念让不同模块可以像乐高积木一样组合。这意味着你可以轻松地将OpenAI的模型换成Anthropic的Claude或者将Chroma向量数据库换成Pinecone而无需重写核心业务逻辑。这种可移植性和模块化是手写“胶水代码”时代难以企及的。2. LangChain核心架构拆解理解四大基石要高效使用LangChain不能只停留在调用几个封装好的函数必须理解其核心架构。它主要围绕几个核心抽象构建我将其称为“四大基石”。2.1 基石一模型 I/OModel I/O—— 与大模型对话的统一接口这是最基础的一层负责与各种大模型交互。LangChain在这里做了关键的统一抽象。LLMs 用于文本补全模型输入一段文本输出一段文本。比如OpenAI、Cohere。在代码中你初始化一个ChatOpenAI对象它背后封装了API密钥管理、请求构造、响应解析、错误重试等繁琐细节。Chat Models 专为对话优化的模型消息格式通常是SystemMessage、HumanMessage、AIMessage。这是目前的主流。使用ChatOpenAI时你传入的是一个消息列表而非单一字符串这更符合多轮对话的语义。Embeddings 文本嵌入模型将文本转换为高维向量。这是RAG的基石。LangChain支持数十种嵌入模型从OpenAI的text-embedding-ada-002到开源的BGE、SentenceTransformers切换只需修改一行初始化代码。注意 很多新手会混淆LLMs和Chat Models。简单来说如果你构建的是简单的文本生成如摘要、翻译用LLMs如果是多轮对话、Agent场景务必使用Chat Models因为它对消息角色人/助手/系统有原生支持能获得更稳定、符合预期的表现。2.2 基石二检索Retrieval—— 让模型拥有“长期记忆”这是LangChain解决“模型知识截止日期”和“幻觉”问题的核心模块即RAG技术栈。文档加载器Document Loaders 支持从PDF、Word、HTML、Markdown、数据库、甚至YouTube字幕中加载文本统一成Document对象。文本分割器Text Splitters 这是RAG效果的关键。你不能把整本书扔给模型。常用的RecursiveCharacterTextSplitter会按字符如\n\n\n 递归分割尽量保持段落或句子的完整性。分割时需平衡块大小chunk_size和重叠区chunk_overlap重叠能避免上下文在边界被割裂。向量存储Vector Stores 存储分割后的文本及其嵌入向量。Chroma轻量本地、Pinecone云服务高性能、Weaviate开源功能全是常见选择。选择时需权衡部署复杂度、性能和成本。检索器Retrievers 从向量库中根据问题向量进行相似度搜索返回最相关的文本块。除了基础的相似度检索similarity_search还有MMR最大边际相关性检索在保证相关性的同时增加结果的多样性。2.3 基石三链Chains—— 组合工作流的“管道”Chain是LangChain的灵魂它允许你将多个组件模型、提示词、工具等按特定顺序组合成一个可执行的工作流。最简单的链是LLMChain提示词 模型。但它的威力在于序列化组合。SequentialChain 顺序执行多个子链前一个链的输出作为后一个链的输入。例如你可以用一个链总结文档再用另一个链根据总结回答问题。TransformChain 用于对输入或输出进行自定义转换如格式化、过滤。RouterChain 根据输入内容决定将其传递给哪个下游链处理实现条件分支逻辑。通过链你可以将复杂的多步应用逻辑清晰地表达出来代码可读性和可维护性大大提升。2.4 基石四代理Agents—— 赋予模型“行动力”Agent是LangChain中最强大也最复杂的概念。一个Agent由三部分组成LLM 负责“思考”和决策。工具Tools 定义Agent可以执行的动作如搜索网络、查询数据库、执行代码、调用API。代理执行器Agent Executor 驱动“思考-行动-观察”的循环。其工作流程是用户提出问题 - AgentLLM根据问题和可用工具列表决定下一步该调用哪个工具或直接给出答案- 执行器调用该工具并获取结果 - 将结果作为新的观察反馈给Agent - Agent进行下一轮决策直到它认为可以给出最终答案。实操心得 设计好的工具Tool描述至关重要。描述必须清晰、精确地说明工具的功能、输入格式和输出示例。模糊的描述会导致LLM错误调用或无法调用工具。例如与其写“一个计算器工具”不如写“一个用于执行基础算术运算的工具。输入应为一个包含数字和运算符 - * /的字符串如’22‘。输出为浮点数结果。”3. 从零构建一个RAG问答系统实战踩坑全记录理论说再多不如动手。我们以构建一个基于个人文档库的问答系统为例走通一个完整的LangChain流程并分享其中必然遇到的坑和解决方案。3.1 环境准备与模型选择首先安装核心包pip install langchain langchain-community langchain-openai chromadb。这里我推荐使用langchain-community它集成了大量第三方组件而langchain-openai则专门用于OpenAI集成。模型选择上对于嵌入模型如果追求效果和稳定OpenAI的text-embedding-3-small是性价比之选。对于聊天模型gpt-3.5-turbo足以应对大多数RAG场景成本可控。如果你的文档非常复杂或问题需要深度推理再考虑gpt-4系列。from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_community.vectorstores import Chroma from langchain.text_splitter import RecursiveCharacterTextSplitter llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) # temperature0使输出更确定 embeddings OpenAIEmbeddings(modeltext-embedding-3-small)3.2 文档处理与向量化细节决定成败假设我们有一堆Markdown格式的技术文档。加载和分割是第一个关键点。from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 加载所有.md文件 loader DirectoryLoader(./my_docs/, glob**/*.md, loader_clsTextLoader) documents loader.load() # 文本分割 text_splitter RecursiveCharacterTextSplitter( chunk_size1000, # 每个块约1000字符 chunk_overlap200, # 块间重叠200字符 length_functionlen, separators[\n\n, \n, , ] # 分割优先级 ) all_splits text_splitter.split_documents(documents) print(f原始文档数{len(documents)} 分割后块数{len(all_splits)})踩坑点1分割策略不当导致信息割裂。我曾将一个API文档按固定字符数分割结果一个重要的函数参数说明被硬生生切到了两个块里。当用户问到这个参数时系统检索到的块只有一半信息导致模型生成错误答案。解决方案对于代码、结构化文档可以尝试用MarkdownHeaderTextSplitter先按标题分割再用RecursiveCharacterTextSplitter进行二次分割能更好地保持语义完整性。踩坑点2向量数据库的持久化与复用。每次启动都重新生成向量既耗时又费钱调用嵌入API要花钱。Chroma持久化非常简单# 首次创建并持久化 vectorstore Chroma.from_documents( documentsall_splits, embeddingembeddings, persist_directory./chroma_db # 指定持久化目录 ) vectorstore.persist() # 显式持久化 # 后续直接加载 vectorstore Chroma( persist_directory./chroma_db, embedding_functionembeddings )3.3 构建检索链与优化查询有了向量库我们可以构建一个检索式问答链。LangChain提供了高级的RetrievalQA链它封装了检索、上下文组装和问答生成。from langchain.chains import RetrievalQA qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, # 最常用的类型将所有检索到的文档“塞”进提示词 retrievervectorstore.as_retriever(search_kwargs{k: 4}), # 检索4个最相关块 return_source_documentsTrue, # 返回源文档便于调试 chain_type_kwargs{ prompt: PROMPT # 可以传入自定义提示词模板强烈推荐 } ) result qa_chain.invoke({query: LangChain中Agent的核心组成部分是什么}) print(result[result]) print(来源文档, [doc.metadata.get(source) for doc in result[source_documents]])踩坑点3默认提示词效果不佳。RetrievalQA的默认提示词很简单当检索到的上下文很长或包含无关信息时模型可能无法精准定位答案。解决方案自定义提示词模板。from langchain.prompts import PromptTemplate template 基于以下上下文回答最后的问题。如果你不知道答案就说你不知道不要编造。 尽量使用上下文中的信息保持答案简洁。 上下文 {context} 问题{question} 有帮助的答案 PROMPT PromptTemplate( templatetemplate, input_variables[context, question] )踩坑点4检索结果不相关。有时用户问题很简短或模糊导致检索到的块不相关。可以尝试查询扩展 使用LLM先对原问题进行重写或扩展生成多个相关查询然后合并检索结果。混合搜索 结合关键词搜索如Chroma的max_marginal_relevance_search和向量搜索兼顾相关性和多样性。元数据过滤 在加载文档时为每个Document添加元数据如sourcecategory检索时通过retriever.search_kwargs添加过滤器。3.4 进阶让RAG系统“自我优化”一个基础的RAG系统搭建完成后如何评估和优化我通常会从两个维度入手检索质量评估 手动构造一批“问题-标准答案”对检查系统检索到的文档是否包含了答案所需的信息。可以计算检索命中率。生成质量评估 同样用测试集让系统生成答案人工或使用另一个LLM如GPT-4评估答案的准确性、完整性和相关性。基于评估结果可以迭代优化调整文本分割的chunk_size和chunk_overlap尝试不同的嵌入模型优化提示词模板甚至引入Re-Rank模型如Cohere的Rerank对检索结果进行精排只将最相关的1-2个片段送给LLM这在上下文窗口有限时非常有效。4. 深入Agent实战构建一个多功能AI助手如果说RAG是给模型开了“外挂记忆”那么Agent就是给模型装上了“手脚”。我们构建一个能联网搜索、计算、并查询特定知识库的智能助手。4.1 定义工具集首先我们需要定义Agent可以使用的工具。这里以SerpAPI搜索和llm-math计算为例同时将上一节构建的RAG系统也封装成一个工具。from langchain.agents import Tool, AgentExecutor, create_react_agent from langchain.agents import load_tools from langchain import hub # 工具1 通用网页搜索 tools load_tools([serpapi], llmllm) # 工具2 数学计算 tools load_tools([llm-math], llmllm) # 工具3 自定义知识库问答工具基于之前的RAG链 knowledge_base_tool Tool( nameCompany_Knowledge_Base, funcqa_chain.invoke, # 直接使用qa_chain description当你需要回答关于我们公司产品、政策、内部流程或历史文档的具体问题时使用这个工具。 输入应该是一个清晰、完整的问题。 ) tools.append(knowledge_base_tool)4.2 选择Agent类型与提示词LangChain支持多种Agent类型如zero-shot-react-descriptionconversational-react-description等。ReActReason Act框架是目前最主流和稳定的选择。我们可以从LangChain Hub拉取一个优化过的ReAct提示词。from langchain.agents import AgentExecutor, create_react_agent # 从Hub拉取提示词 prompt hub.pull(hwchase17/react) # 创建Agent agent create_react_agent(llm, tools, prompt) agent_executor AgentExecutor(agentagent, toolstools, verboseTrue, handle_parsing_errorsTrue)4.3 运行与调试现在我们可以向这个Agent提问了。result agent_executor.invoke({ input: 我们公司最新的年假政策是什么另外请帮我计算一下如果我从今天起休假15个工作日我的返工日期是哪天假设周末休息 })在verboseTrue模式下控制台会打印出Agent的完整思考过程Thought: 用户问了两个独立的问题。第一个是关于公司年假政策这应该使用Company_Knowledge_Base工具。第二个是计算日期这应该使用计算器工具。 Action: Company_Knowledge_Base Action Input: 公司最新的年假政策是什么 Observation: 根据公司2024年员工手册正式员工每年享有15天带薪年假... Thought: 我已经回答了第一个问题。现在需要回答第二个问题计算返工日期。我需要知道今天的日期。 Action: Search Action Input: 今天的日期 Observation: 今天是2024年5月27日星期一。 Thought: 现在我需要计算从2024年5月27日开始15个工作日后的日期。我需要使用计算器工具但需要先理解“工作日”意味着跳过周末。 Action: Calculator Action Input: 从2024-05-27开始加上15个工作日排除周六周日后的日期。 Observation: 计算器工具无法直接处理工作日。我需要更精确的指令或者换一种方式思考...踩坑点5工具能力与期望不匹配。如上所示llm-math工具只是一个数学计算器无法处理复杂的日期逻辑。解决方案要么为Agent提供一个更强大的日期计算工具如自定义一个调用datetime库的函数要么在提示词中明确告知Agent该工具的局限性引导它分步计算或换一种提问方式。踩坑点6无限循环或错误解析。Agent有时会陷入“思考-行动”的死循环或者无法正确解析工具的输出的。设置max_iterations参数如max_iterations10可以强制限制循环次数避免无限运行。handle_parsing_errorsTrue可以捕获一些输出格式错误让Agent有机会重试。5. LangChain vs. LangGraph vs. 其他框架如何选择随着生态发展出现了LangGraph、LlamaIndex、Semantic Kernel等框架让人眼花缭乱。我结合自己的使用经验做一个简要对比。5.1 LangChain全面的“瑞士军刀”定位 全功能、模块化的LLM应用开发框架。优势生态最丰富 集成了海量的模型、工具、数据源和内存方案。抽象层次高ChainAgent等抽象非常经典设计思想影响深远。灵活性极强 你可以从底层组件开始自由组装任何复杂的工作流。劣势学习曲线陡峭 概念多API变化在早期较快现在已稳定。“样板代码”较多 构建复杂、有状态的流式应用需要自己管理状态代码可能显得冗长。5.2 LangGraph为复杂、有状态的工作流而生LangGraph是LangChain团队推出的新库它建立在LangChain之上但核心抽象不同。定位 用于构建有状态、多参与者的图工作流。核心概念图Graph和状态State。你将应用定义为一个由节点Node和边Edge组成的图。每个节点执行一个函数并可以修改共享的全局状态。边决定了流程的下一个节点。适用场景复杂的多Agent系统 比如模拟一个软件团队有项目经理Agent、开发Agent、测试Agent它们之间需要有序协作和传递状态。循环审批流程 一个文档需要多人依次审批状态文档、审批意见、当前审批人在图节点间流转。游戏或模拟 多个角色根据规则和状态进行交互。与LangChain Agent的区别 传统的LangChain Agent是单个“思考-行动”循环。LangGraph可以轻松描述多个Agent并行、循环、有条件分支的复杂拓扑结构并且状态管理变得非常直观。如何选择 如果你的应用是简单的线性链或单个AgentLangChain足够。如果你需要构建一个涉及多个实体、有复杂状态流转和循环逻辑的系统LangGraph是更优雅的选择。它们不是替代关系而是互补。你完全可以在LangGraph的节点中使用LangChain的组件。5.3 其他竞争者速览LlamaIndex 最初专注于RAG的数据处理管道在文档加载、索引结构如树索引、关键词表索引方面非常深入。现在也扩展了Agent等功能。如果你项目的核心是复杂文档的深度检索和索引可以优先考虑LlamaIndex。Semantic Kernel (SK) 微软出品与.NET生态结合紧密强调“规划Planner”的概念。如果你主要使用C#开发或深度集成微软系服务如Azure OpenAI Microsoft GraphSK是自然的选择。CrewAI 专注于多Agent协作提供了RoleTaskProcess等高阶抽象让定义“AI团队”和它们的协作流程顺序、分层、轮询变得非常简单。如果你就想快速搭建一个多角色协作的AI系统CrewAI的上手速度可能比直接用LangGraph更快。我的建议从LangChain开始。它是目前事实上的标准社区最活跃遇到问题最容易找到解决方案。深入理解它的核心概念Model I/O Retrieval Chain Agent后你会对LLM应用开发有扎实的认知。当你的项目需要LangGraph或CrewAI所解决的特定复杂性问题时再平滑地扩展过去这时你的学习成本会低很多。6. 生产环境部署与性能调优要点将LangChain应用从Demo推向生产会面临一系列新挑战。6.1 异步化与流式输出Web应用必须支持高并发。LangChain全面支持异步async/await调用。# 同步调用 result qa_chain.invoke({query: ...}) # 异步调用 async_result await qa_chain.ainvoke({query: ...})对于聊天界面流式输出Streaming至关重要它能极大提升用户体验。在初始化LLM时启用流式并在链调用时使用astream或astream_events。from langchain_openai import ChatOpenAI from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler streaming_llm ChatOpenAI( modelgpt-3.5-turbo, streamingTrue, callbacks[StreamingStdOutCallbackHandler()] ) # 在链中使用这个streaming_llm注意 使用astream_events可以获得更细粒度的事件流如“on_chat_model_stream”方便前端实时显示“思考过程”或“引用来源”但处理起来稍复杂。6.2 缓存与成本控制LLM API调用是主要成本。实施缓存可以显著降低开销和延迟。内存缓存InMemoryCache 适合单进程、短期缓存。SQLite缓存SQLiteCache 轻量持久化。Redis缓存RedisCache 适合分布式部署。from langchain.globals import set_llm_cache from langchain.cache import SQLiteCache set_llm_cache(SQLiteCache(database_path.langchain.db))设置缓存后完全相同的请求将直接返回缓存结果。对于RAG系统可以对嵌入向量也进行缓存。6.3 监控、日志与追踪生产系统必须可观测。LangChain与LangSmith深度集成这是一个官方的监控调试平台。设置环境变量LANGCHAIN_TRACING_V2true和LANGCHAIN_API_KEY。运行你的链或Agent所有的步骤、输入、输出、耗时、Token使用量都会被自动记录到LangSmith。在LangSmith界面你可以清晰地可视化整个调用链分析性能瓶颈调试错误甚至进行版本对比。如果没有LangSmith你也应该在自己的应用日志中记录关键信息用户问题、检索到的文档ID、最终答案、耗时和Token消耗。这对于分析用户行为、优化检索效果和成本核算至关重要。6.4 安全性考量提示词注入 永远不要将未经处理的用户输入直接拼接进提示词。使用ChatPromptTemplate等模板化工具进行严格的变量隔离。工具执行权限 Agent的工具非常强大。务必限制工具的能力特别是那些能执行代码PythonREPLTool、访问文件系统或调用外部API的工具。在沙箱环境中运行或进行严格的输入校验和白名单过滤。数据隐私 确保你的向量数据库和日志存储符合数据安全规范。如果使用云服务商的嵌入模型需了解其数据隐私政策。7. 常见问题排查与社区资源即使按照最佳实践开发过程中也难免遇到问题。以下是一些常见问题的排查思路模块导入错误如langchain.llmsnot found LangChain v0.1.0之后进行了模块化重构。很多子模块移到了langchain-community中。确保安装了正确的包并查阅最新官方文档的导入方式。Agent陷入循环或输出无意义内容检查工具描述是否清晰。尝试降低LLM的temperature如设为0减少随机性。在提示词中明确限制max_iterations。使用verboseTrue查看Agent的思考过程定位问题环节。RAG答案质量差检索不到 检查文本分割是否合理尝试不同的chunk_size检查嵌入模型是否适合你的语料中文 vs. 英文。检索到但答错 优化提示词明确指令“基于上下文回答”尝试在上下文中高亮显示答案相关部分通过提示词指令考虑使用Refine或Map-Reduce等更复杂的链类型来处理长上下文。流式输出异常 确保在初始化LLM时设置了streamingTrue并且使用对应的异步流式调用方法如astream。检查网络连接和API密钥权限。学习资源推荐官方文档 始终是最新、最权威的信息源。虽然早期有“面条式代码”的批评但现在的文档结构已清晰很多尤其是概念指南部分。LangChain中文入门教程 对于初学者一些优质的中文教程能帮你快速跨越概念门槛。但切记最终要以官方文档和API Reference为准。GitHub Issues与Discord 遇到具体bug或诡异行为去GitHub仓库的Issues里搜索大概率已经有人遇到过。LangChain的Discord社区非常活跃是提问和获取帮助的好地方。LangSmith 不仅仅是监控工具其提供的模板库和轨迹追踪是学习他人如何构建复杂链和Agent的绝佳范例。从我自己的经验来看学习LangChain最好的方式就是“做中学”。从一个简单的LLMChain开始然后逐步加入检索功能构建RAG再尝试给模型添加一两个工具做成Agent。每走一步都会对框架的设计有更深的理解。它确实引入了一些学习成本但换来的是开发效率、系统可维护性和未来可扩展性的巨大提升。当你需要快速响应业务需求将一个LLM创意落地成可交付的应用时你会庆幸自己站在了LangChain这个“巨人”的肩膀上。
返回列表