LangChain实战:从Chain到RAG与Agent的完整工程落地指南
2026/9/1 4:55:33 网站建设 项目流程

简介:这是一份面向初学者的LangChain入门与实践项目代码包。LangChain是当前流行的基于Python的开源框架,通过模块化设计将大语言模型与外部数据、工具及记忆能力连接,能有效解决信息滞后、无法执行外部操作、记忆有限等痛点,并支持OpenAI、Hugging Face等多种主流模型。这份代码包聚焦其核心模块,涵盖提示词模板、链式调用、记忆功能与工具集成等关键内容,附有智能运维、问答系统、对话机器人等场景的示范代码,可帮助开发者直观理解提示词工程与链式应用的组织方式。资源共3个文件,以HTML说明文档为主体,配合代码工程配置与版本管理文件,整体仅5KB,轻量易读,适合在本地快速浏览并对照核心概念。已有103人学习下载,对于希望低成本了解LangChain整体架构、快速上手构建LLM应用的开发者而言,是一份简洁高效的入门素材。 前阵子有个做后端的朋友问我:LangChain是不是已经被吹过头了?我反问他,最近你写AI应用,是不是还得自己封装多轮对话、管理上下文、处理工具调用的结构化输出?他不说话了。LangChain的定位就是这样——它不是最优雅的框架,却是把LLM应用的公共组件沉淀得最全的一套抽象体系。这篇内容我结合自己从入门到上生产环境的完整项目代码,把整个技术链路捋一遍,从最基础的Chain到RAG再到Agent,最后是工程化落地的坑,希望能给正在入门LangChain的朋友一份能直接抄作业的参考。

1. 先搞清楚LangChain到底解决了什么问题

1.1 它不只是"封装API的SDK"

很多人第一次接触LangChain,以为它就是帮你调OpenAI接口的封装库,然后对比一下直接requests.post(),觉得"多此一举"。这个判断只对了一半。LangChain真正解决的是多组件协作时的胶水问题:一个完整的AI应用,通常需要提示词管理、模型切换、记忆保持、文档检索、工具调用、输出结构化,这些组件单独写都不难,难的是把它们拼在一起还能灵活替换。

我自己的项目演进最能说明这一点。最早我用原生OpenAI SDK写了一个客服问答脚本,prompt写死在代码里,换模型要改六七处,加个知识库检索直接重构。后来切到LangChain,prompt变成了可配置模板,模型可以按环境切换,检索、记忆、工具全部变成可插拔的组件,一次重构省下后面大半年的维护成本。

1.2 最小可运行示例跑通一遍

理解LangChain最快的方式,是写一个最小链路。下面这段代码,就是LangChain最经典的"三段式":模板、模型、解析器,用管道符串联。

from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个资深的{domain}领域专家,请用通俗易懂的语言回答用户的问题。"), ("human", "{question}") ]) parser = StrOutputParser() chain = prompt | llm | parser result = chain.invoke({"domain": "法律", "question": "合同里违约金一般怎么约定?"}) print(result)

prompt | llm | parser这一行是整个LangChain的精华:管道符把提示词模板、模型调用、输出解析三个环节串联成一个Runnable对象。chain.invoke()接收一个字典,里面的key对应模板里的{domain}{question}。这种设计的好处是每个环节都可以独立替换,比如把StrOutputParser换成JsonOutputParser,接口完全不变。

1.3 生态全景:一张地图看清LangChain各模块

LangChain的生态分成几块:langchain-core是核心抽象(Runnable、Message、Tool),langchain-community是社区集成(各种文档加载器、向量库、第三方模型),langchain本身是Chain、Agent等高层逻辑,还有langgraph负责有状态的工作流编排,langsmith做链路追踪。市面上说的"LangChain"通常指这整个体系。

入门时最容易犯的错,是不知道接口该从哪个包import。比如ChatOpenAIlangchain_openai里,PyPDFLoaderlangchain_community.document_loaders里,FAISSlangchain_community.vectorstores里。记住一个粗略原则:模型相关看langchain_*的独立包,通用工具和集成类看langchain_community,新项目里langchain主包主要负责组装。

2. 核心抽象逐个拆:Prompt、Chain、Memory、Tool

2.1 Prompt模板:把提示词当成代码来维护

提示词的工程化,是LangChain给我带来最大收益的部分。用原生代码写AI应用,prompt经常是字符串拼接,等需求变了几轮之后,整个文件又脏又乱。用ChatPromptTemplate之后,提示词从业务代码里剥离出来,变成了带变量的模板,甚至可以做成配置文件。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder prompt = ChatPromptTemplate.from_messages([ ("system", "你是{bot_name},性格{personality}。"), MessagesPlaceholder(variable_name="history"), ("human", "{input}") ])

注意MessagesPlaceholder这个组件,它在模板里占了一个位置,运行时填入多轮对话历史。这是做聊天应用的关键,也是新手最容易漏的地方——没有历史占位符,模型永远记不住上下文。

2.2 Chain组合:管道式编程的边界在哪里

LangChain 0.2之后的写法,基本统一成了Runnable管道。除了最基础的prompt | llm | parser,还可以用RunnableParallel做并行,用RunnableLambda包任意Python函数。

from langchain_core.runnables import RunnableParallel, RunnableLambda def format_result(data): return f"答案:{data['answer']}\n参考资料数:{data['source_count']}" parallel = RunnableParallel( answer=chain, source_count=llm2 ) full_chain = parallel | RunnableLambda(format_result)

管道式编程的优点是用声明式方式描述数据流,一眼就能看清整个处理链路。但它的边界也很明显:没有内置的循环和条件分支,一旦业务逻辑需要"根据上一步结果决定下一步走哪条路",就不是Chain能优雅解决的了,那正是LangGraph的地盘。所以别迷信Chain,它适合"固定管线",不适合"动态决策"。

2.3 Memory记忆:看着简单,坑不少

给聊天机器人加记忆,是LangChain里最容易被低估的模块。早期版本里大家习惯用ConversationBufferMemory,它会直接把所有历史消息塞给模型,很快就把上下文窗口挤爆。

from langchain.memory import ConversationBufferWindowMemory memory = ConversationBufferWindowMemory(k=5, return_messages=True)

k=5代表只保留最近5轮对话。这只是初级方案,实际项目中更好的做法是:把"长期记忆"落库(比如用户偏好),把"短期记忆"用窗口控制,而复杂业务状态直接交给LangGraph去管,不要在prompt层硬凑。我在复盘项目时发现,记忆出问题通常会先看两件事——历史消息有没有悄悄把system消息挤掉,以及token计算和实际用量是否一致。

2.4 Tool自定义:扩展大模型能力的最短路径

大模型本身只会生成文本,想让模型查数据库、算价格、发请求,就得给它工具。LangChain里定义一个工具只需两步:写普通Python函数,再装饰一下。

from langchain_core.tools import tool @tool def get_weather(city: str) -> str: """查询指定城市的实时天气。""" import requests # 这里替换成真实天气API return f"{city} 当前晴,气温 24℃"

函数名、docstring、参数类型注解,这三样东西会被LangChain自动解析成模型的工具描述。所以docstring要写清楚工具是干什么的、什么时候用,模型看到描述才会在合适的场景调用它。另一个常见坑:工具里不要做太重的事(比如长时间同步请求),模型调工具是有超时等待的,生产环境里工具最好做成轻量封装,内部再异步处理。

3. RAG实战:用项目代码跑通一个知识库问答

3.1 完整链路:加载、切分、向量化、检索、生成

RAG(检索增强生成)是LangChain目前落地场景最广的能力,先用项目代码把完整链路串起来。

from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import FAISS # 1. 加载 loader = PyPDFLoader("docs/产品手册.pdf") documents = loader.load() # 2. 切分 splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", " ", ""] ) chunks = splitter.split_documents(documents) # 3. 向量化并入库 embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = FAISS.from_documents(chunks, embeddings) vectorstore.save_local("data/vectorstore") # 4. 检索 retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) # 5. 生成 from langchain_core.prompts import ChatPromptTemplate rag_prompt = ChatPromptTemplate.from_messages([ ("system", "你是客服助手。仅根据以下资料回答问题,若资料中没有答案,请直接说不知道。"), ("human", "资料:{context}\n\n问题:{question}") ]) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": lambda x: x["question"]} | rag_prompt | llm | parser ) print(rag_chain.invoke({"question": "产品支持哪些连接方式?"}))

这段代码里的retriever | format_docs是LangChain里很巧妙的嵌套管道:检索器的输出是文档列表,经过format_docs转成纯文本,再作为context变量传给prompt。这样整个RAG流程依然保持管道式结构,可读性很强。

3.2 切分策略和检索质量,直接决定体验

RAG项目中,模型本身反而不是瓶颈,检索质量才是。我自己踩过的坑有两个最典型。

第一个是切分粒度和格式。默认的RecursiveCharacterTextSplitter按字符数硬切,如果文档是表格密集型的,很容易把语义完整的表格从中间切开,检索出来的片段残缺不全。后来我改成优先按段落、再按句子切,同时控制chunk_overlap在80到150之间,让相邻块之间保留一部分重复内容,这才解决了"答案断半截"的问题。

第二个是embedding模型的选择。中文场景直接用OpenAI的text-embedding-3-small,质量可以但中文颗粒度一般;对私域文档,我后来换成BGE或M3E这类中文embedding模型,配合Ollama本地部署,效果提升明显。选embedding之前,建议拿你自己的文档跑一组对比测试,找一个"检索命中率"最高的组合。

3.3 模型来源切换:OpenAI、Ollama、vLLM统一接口

LangChain最方便的一点,是模型层抽象得非常干净。ChatOpenAI不只是接OpenAI,只要接口兼容就能统一接入。

from langchain_openai import ChatOpenAI # 方式一:直连OpenAI llm = ChatOpenAI(model="gpt-4o-mini", api_key="sk-xxx") # 方式二:本地Ollama llm = ChatOpenAI( model="qwen2.5:7b", base_url="http://localhost:11434/v1", api_key="ollama" ) # 方式三:vLLM部署的私有模型 llm = ChatOpenAI( model="qwen2.5-14b-instruct", base_url="http://192.168.1.10:8000/v1", api_key="vllm" )

关键在base_url:Ollama和vLLM都提供了OpenAI兼容接口,所以在LangChain里都当ChatOpenAI用。这就意味着,你可以先用OpenAI跑通逻辑,再无缝切到本地模型做私有化部署,业务代码一行都不用改。生产环境中我建议把模型配置全部放进环境变量或配置中心,连model名都别写死在代码里。

4. Agent实战:从Chain升级到LangGraph

4.1 让模型自己决定下一步:ReAct模式怎么跑

Chain和RAG都是"固定管线",但真实的业务需求往往是"需要根据问题动态决定调哪个工具、查哪份资料"。这就是Agent的用武之地。最经典的实现是ReAct模式:模型在"思考"和"行动"之间循环,直到得出最终答案。

from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.tools import tool @tool def query_stock(code: str) -> str: """查询股票代码的实时行情,输入为6位股票代码。""" # 替换为真实行情接口 return f"{code} 最新价 12.50 元,涨跌幅 +1.2%" @tool def calculator(expression: str) -> str: """计算数学表达式,输入如 (12.5*1000)/3。""" return str(eval(expression)) tools = [query_stock, calculator] prompt = ChatPromptTemplate.from_messages([ ("system", "你是股票助手。使用工具回答用户问题,所有数字结果保留两位小数。"), MessagesPlaceholder(variable_name="chat_history"), ("human", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), ]) agent = create_tool_calling_agent(llm, tools, prompt) executor = AgentExecutor(agent=agent, tools=tools, verbose=True) result = executor.invoke({ "input": "我买了1000股000001,帮我算一下现在市值多少?", "chat_history": [] }) print(result["output"])

注意prompt里那个agent_scratchpad占位符,它专门用来放Agent的中间推理过程,是工具调用型Agent的标配。create_tool_calling_agent是0.2之后的新写法,走的模型原生Function Calling能力,效率和稳定性都比老版本的ReAct Agent好,新项目建议直接用这个。

4.2 LangChain和LangGraph到底怎么选

这个问题的答案,我在项目进入第二个版本时才彻底想明白。LangChain的Agent是一个"黑盒"调度器,你给它工具和提示词,它在内部循环调用模型,但你不能精细控制中间的每一步;LangGraph是把Agent拆成一张显式的状态图,节点(Node)就是一步操作,边(Edge)就是状态转移条件,整个Agent的路径完全透明可控。

from langgraph.graph import StateGraph, START, END from typing import TypedDict class AgentState(TypedDict): question: str intermediate_steps: list def call_model(state): # 第一步:让模型决定调用哪个工具 return {"intermediate_steps": ["thinking..."]} def call_tool(state): # 第二步:执行工具 return {"intermediate_steps": ["tool result..."]} graph = StateGraph(AgentState) graph.add_node("model", call_model) graph.add_node("tool", call_tool) graph.add_edge(START, "model") graph.add_conditional_edges("model", router, {"continue": "tool", "finish": END})

一句话总结:简单的工具调用用LangChain Agent就够了,涉及多步流程、人工审批、需要断点恢复、想精细控制每一步的业务,直接上LangGraph。我的教训是从Chain直接跳到LangGraph,中间在Agent上硬扛了两个星期,最后发现该用的还是图编排。

4.3 Agent跑偏的排查记录

Agent上线后最常见的故障是"模型不按预期调用工具"。我遇到过三类情况,排查链路值得分享:

第一类,工具描述写得像说明书,模型看不懂。排查方法是用LangSmith或直接打印Agent的中间步骤,看模型每一步在"想"什么。后面我习惯先给每个工具说一句人话总结,再给使用场景示例,调用准确率立刻上来。

第二类,模型反复调用同一个工具停不下来。这通常是工具返回结果不明确导致的,比如查询成功但返回空串。后来我在工具返回里统一加状态前缀,比如"查询成功:..."、"未找到:...",模型收到明确反馈后就能正常终止。

第三类,上下文越跑越长,Agent中途失效。Agent的中间推理会不停累积token,进入死循环后上下文直接爆炸。解决办法是限制最大迭代次数,并定期清理agent_scratchpad里的中间过程,或者直接改用LangGraph做更可控的循环。

5. 生产级项目代码怎么组织

5.1 目录结构:别把所有代码堆在main.py里

LangChain项目随随便便就会膨胀,如果不从一开始就做好代码组织,后面改一个prompt都要在几百行里找。这是我目前比较顺手的结构:

llm-app/ ├── app/ │ ├── main.py # 入口,FastAPI服务 │ ├── config.py # 配置加载 │ ├── chains/ # 各种RAG、基础问答链 │ │ ├── chat_chain.py │ │ └── rag_chain.py │ ├── agents/ # Agent定义 │ ├── tools/ # 自定义工具 │ ├── rag/ │ │ ├── loader.py # 文档加载 │ │ └── splitter.py # 切分策略 │ └── memory/ # 记忆管理 ├── config/ │ └── settings.yaml ├── data/ # 原始文档、向量库 ├── tests/ └── requirements.txt

核心原则就一条:每个组件一个文件,文件之间只通过明确的接口通信。比如tools/里的函数只负责做一件事,chains/里的chain只负责组装,不要把检索逻辑、prompt、业务判断混在一起。

5.2 配置和密钥管理:从写死到外置

密钥和模型配置是项目最容易出事故的位置。我在代码评审时见过不少把api_key写死在代码里提交到仓库的,这非常危险。正确的做法是统一走环境变量或配置文件:

# .env 示例 OPENAI_API_KEY=sk-xxxx OPENAI_BASE_URL=https://api.openai.com/v1 LLM_MODEL=gpt-4o-mini EMBEDDING_MODEL=text-embedding-3-small FAISS_INDEX_PATH=data/vectorstore

代码里加一个config.py统一读取:

import os from dotenv import load_dotenv load_dotenv() OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL") LLM_MODEL = os.getenv("LLM_MODEL")

.env文件加进.gitignore,仓库里只保留.env.example模板。项目代码如果能推到公司私库,记得先检查历史提交里有没有泄露出密钥,必要时用工具清理历史记录。

5.3 调试技巧与常见报错

最后分享几个实测很管用的调试技巧。第一个是开verboseAgentExecutor(verbose=True)或者chain.invoke()时打印中间步骤,能看到每个环节的输入输出,排查问题快得多。第二个是用好LangSmith,它能把链条上每一步的token、耗时、prompt内容都记录下来,我把它当成LangChain的"日志系统"。

常见报错也列几个:

报错信息原因处理方式
ModuleNotFoundError: langchain_community缺少集成包pip install langchain-community
OpenAIError: connection errorbase_url配置错误或网络不通检查base_url是否指向正确的vLLM/Ollama端口
OutputParserException模型输出无法被解析器解析换更强的模型,或改用JsonOutputParser并给例
ValueError: too many values to unpack老版本Agent与新版API混用统一升级到0.2+版本,用create_tool_calling_agent

最后一个建议:LangChain版本更新很快,不同大版本之间的API破坏性改动不少,项目里最好锁定版本号(比如langchain==0.2.x),升版本前先看变更日志。我自己吃过的亏,就是langchain整体从0.1升到0.2时,大量import路径变了,几十个文件逐个改才缓过来。

如果你刚开始上手LangChain,建议先照着本文第一段最小示例跑通,再逐步把RAG和Agent加进去。这个框架的抽象方式已经成为AI应用开发的事实标准,掌握它之后,无论底层模型怎么换、业务怎么复杂化,你手上的技术栈都能接得住。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询