这次我们来看一套 LangChain + RAG + AI Agent 的完整实战路径:知识库问答、工具调用、状态化工作流、接口服务化,一条主线串到底。
现在网上讲 LangChain 的教程非常多,但真正动手时会发现几个问题:版本更新太快、示例代码抄下来就跑不通、检索结果和预期差很远、一到 Agent 多轮调用就报错。所以这篇文章不做概念堆砌,直接给出一条可运行、可验证、可扩展的技术主线。文章会覆盖 RAG 知识库的完整构建流程、Agent 工具调用实战、LangGraph 状态化工作流、效果评估指标、接口服务化与批量任务设计,以及最常见的 8 个排查场景。
不管你是想给公司内部资料做知识库问答,还是想把大模型接进现有业务工具链,这套流程的通用思路都可以直接复用。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 技术栈 | LangChain、LangGraph、Chroma、FastAPI,可选 Ollama 本地模型 |
| 核心功能 | 文档加载、文本切分、向量化、检索生成、Agent 工具调用、工作流管理 |
| 模型接入 | OpenAI 风格 API / 本地 Ollama 双通道,可切换 |
| 接口能力 | FastAPI 暴露 HTTP 接口,支持单条问答和批量任务 |
| 硬件门槛 | 云端 API 模式普通开发机即可;本地模型按参数量匹配合适内存或显存 |
| 学习成本 | 有 Python 基础,一天内可跑通主链路 |
| 适合场景 | 知识库问答、制度文档检索、数据分析助手、业务工具编排 |
| 注意边界 | 需按实际情况验证模型授权、数据合规与内容准确性 |
整体判断:这套技术栈的入门门槛不算高,真正容易出问题的是版本兼容、检索质量和工具调用编排。后面每一节都会围绕这几个痛点展开。
2. LangChain、RAG、Agent 与 LangGraph 的关系
很多初学者会把 LangChain、RAG、Agent 混为一谈,其实它们是四个不同层次的东西。
2.1 LangChain 是编排框架
LangChain 本身不提供大模型,也不负责训练模型。它是一个应用开发框架,帮开发者把大模型、提示词、文档、向量库、外部工具串联起来。你可以把它理解为一条流水线:模板定义好之后,数据从一端进入,经过处理,从另一端输出。
框架的价值在于规范化了常用组件:
- 模型封装:ChatOpenAI、Ollama 等统一的聊天模型接口
- 提示词管理:ChatPromptTemplate、FewShotPromptTemplate
- 文档处理:各种 DocumentLoader、TextSplitter
- 记忆管理:对话历史、窗口记忆、摘要记忆
- 工具调用:Tool、Agent、AgentExecutor
组件之间用标准接口连接,所以你可以随时替换某个环节。比如今天用 OpenAI,明天换成 Ollama 的 Qwen,代码改动很小。
2.2 RAG 是解决“模型不知道”的路径
RAG,全称 Retrieval-Augmented Generation,检索增强生成。核心思路是:模型回答之前,先从知识库或文档库中检索相关内容,再把检索结果作为上下文送给生成模型。
为什么要这么做?因为大模型的知识截止时间有限,也不掌握你的私有业务资料。让它直接回答公司制度问题,它只会瞎编。RAG 的做法是先用检索把答案的“候选材料”找出来,模型只需要做阅读理解,幻觉概率会明显下降。
RAG 的典型链路:
文档加载 -> 文本切分 -> 向量化 -> 向量库存储 用户提问 -> 向量检索 -> 拼接上下文 -> 模型生成中间有一个关键点:检索质量直接决定生成质量。检索不到正确答案,模型怎么生成都是错的。
2.3 Agent 是“让模型自己决定下一步”
Agent 可以理解为一个智能体。它不仅仅是回答问题,而是能根据任务目标编排步骤、调用工具、查看结果,再决定下一步动作。
例如用户提问“查询最近三天的订单金额,并生成汇总报告”,这串任务不能靠一次模型调用解决。Agent 需要先调用订单查询工具,拿到原始数据,再调用计算工具或报表工具,最后整理成报告。
LangChain 的 Agent 体系里,关键组件包括:
- Tool:一个可以执行具体功能的函数,比如查询数据库、调用接口
- Prompt:告诉模型有哪些工具、什么情况下用哪个
- Agent:根据用户输入和工具列表,规划下一步动作
- AgentExecutor:负责循环执行“思考→调用工具→观察结果→再规划”的过程
2.4 LangGraph 是状态化工作流
LangGraph 是 LangChain 团队推出的低层编排框架,用来构建状态化的 Agent 应用。它和 LangChain 的关系不是替代,而是向下延伸。
LangChain 的 AgentExecutor 适合简单的循环任务。一旦业务流程复杂,比如需要条件分支、人工审核节点、多 Agent 协作,就需要更精确的控制。LangGraph 用图的方式定义工作流,节点就是处理逻辑,边就是流转条件,每个节点都能读写共享状态。
简单对比:
| 对比项 | LangChain AgentExecutor | LangGraph |
|---|---|---|
| 定位 | 高层封装,开箱即用 | 底层编排,灵活可控 |
| 状态管理 | 简单,适合单轮循环 | 显式状态,支持复杂分支 |
| 使用场景 | 快速验证、轻量 Agent | 生产级工作流、多 Agent 协作 |
| 学习成本 | 低 | 中等 |
我的建议是:先跑通 LangChain 的 AgentExecutor,理解工具调用逻辑,再迁移到 LangGraph。
3. 环境准备与前置条件
第 3 节开始进入实操。先准备一套干净的基础环境。
3.1 Python 与虚拟环境
建议使用 Python 3.10 或 3.11,兼容性更稳定。正式项目务必使用虚拟环境,避免把依赖装进系统环境。
python -m venv .venv source .venv/bin/activate # Windows 用户执行 .venv\Scripts\activate安装核心依赖:
pip install langchain langchain-openai langchain-community langchain-chroma chromadb pypdf fastapi uvicorn python-dotenv如果后面要测试本地模型,再补装:
pip install ollama注意:LangChain 的包拆分比较细,老教程里的from langchain.llms import OpenAI在 0.3 之后已经变更。新版本统一从langchain_openai导入模型类,这一点很关键。
3.2 模型接入:云端 API 与本地模型
模型接入是第一个分叉口。如果你有 OpenAI 兼容的 API Key,直接配置环境变量即可。在项目根目录创建.env文件:
OPENAI_API_KEY=你的API_KEY OPENAI_BASE_URL=https://api.openai.com/v1使用国内可直连的大模型服务时,把OPENAI_BASE_URL换成对应的兼容地址即可,代码不用改。
如果想在本地跑模型,可以先安装 Ollama,然后拉取一个支持工具调用的模型,比如 Qwen 系列:
ollama pull qwen2.5:7b ollama serve本地模型的好处是数据不出内网,坏处是效果和速度取决于硬件。具体拉取哪个 tag,以 Ollama 官方仓库当前支持的模型列表为准。
4. 从 0 构建 RAG 知识库
这一节用一个真实可运行的示例,打通 RAG 全流程。示例默认使用云端 API,本地模型接入方式在同一节末尾说明。
4.1 文档加载
先看文档加载。LangChain 社区提供了多种加载器,常见的有:
- TextLoader:加载纯文本文件
- PyPDFLoader:加载 PDF
- CSVLoader:加载 CSV
- DirectoryLoader:批量加载目录下的文档
from langchain_community.document_loaders import TextLoader loader = TextLoader("./data/kb.txt", encoding="utf-8") docs = loader.load() print(docs[0].page_content[:500])加载完成后,文档变成Document对象,包含page_content和metadata。如果后续要做多文档来源追踪,可以在加载时给metadata添加来源字段。
4.2 文本切分
文档加载完成后,不能直接整篇向量化。模型对输入长度有限制,而且整篇文档向量化之后检索粒度太粗。常见做法是使用RecursiveCharacterTextSplitter。
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=64, separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""] ) chunks = splitter.split_documents(docs) print(f"切分后文档块数量: {len(chunks)}")参数解释:
chunk_size:每块最大字符数,中文场景建议 300 到 800 之间chunk_overlap:相邻块之间的重叠字符数,用来缓解切分截断导致的语义断裂separators:优先在段落、句号、分号处切分,最后才按空格或字符切
切分策略是 RAG 调优的第一步。块太大,检索定位不准;块太小,上下文信息不完整。后面评估章节会专门说。
4.3 向量化与向量库入库
文本切分之后,调用 Embedding 模型把每块文本变成向量。这里用 OpenAI 的text-embedding-3-small做演示。
from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./db/chroma" )persist_directory指定向量库的持久化目录,第一次运行后,向量数据会写入本地磁盘。下次启动时,不需要重新加载文档,直接加载向量库即可:
vectorstore = Chroma( persist_directory="./db/chroma", embedding_function=embeddings )Chroma 是一个轻量级开源向量数据库,适合本地开发。生产环境如果需要更高并发,可以迁移到 Elasticsearch、Milvus 或者 Qdrant,接口设计理念类似。
4.4 检索与生成
向量库准备好之后,把检索器和生成链拼起来。
from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) prompt = ChatPromptTemplate.from_messages([ ("system", "你是知识库问答助手。请严格基于以下资料回答问题,资料中没有的信息不要编造:\n\n{context}"), ("human", "{question}") ]) llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) def ask(question: str) -> str: docs = retriever.invoke(question) context = "\n\n".join([doc.page_content for doc in docs]) chain = prompt | llm | StrOutputParser() return chain.invoke({"context": context, "question": question}) if __name__ == "__main__": answer = ask("这篇知识库里提到了哪些关键概念?") print(answer)流程拆开看:
retriever.invoke(question)返回 TopK 相关文档- 所有文档拼接成一个
context - 提示词要求模型只基于
context回答 chain.invoke完成生成
这是最基础的 RAG 链路。跑通之后,再考虑重排序、混合检索、记忆等增强能力。
如果使用 Ollama 本地模型,只需要替换两处:
from langchain_ollama import ChatOllama, OllamaEmbeddings embeddings = OllamaEmbeddings(model="qwen2.5:7b") llm = ChatOllama(model="qwen2.5:7b", temperature=0)代码结构不用改。
5. Agent 实战:让模型学会调用工具
RAG 解决的是“知识来源”问题,Agent 解决的是“执行动作”问题。这一节用一个带两个工具的 Agent 示例说明原理。
先定义两个工具:一个查询当前时间,一个做乘法计算。
from datetime import datetime from langchain.tools import tool from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI @tool def get_current_time() -> str: """返回当前日期时间。""" return datetime.now().isoformat() @tool def multiply(a: int, b: int) -> int: """计算两个整数的乘积。""" return a * b tools = [get_current_time, multiply]初始化 Agent 时,提示词里需要包含input和agent_scratchpad两个变量。agent_scratchpad用来记录模型已经思考过什么、调用过哪些工具,是循环执行的关键。
prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个智能助手,可以在需要时调用工具解决问题。"), ("human", "{input}"), ("placeholder", "{agent_scratchpad}"), ]) llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) agent = create_tool_calling_agent(llm, tools, prompt) executor = AgentExecutor(agent=agent, tools=tools, verbose=True) if __name__ == "__main__": result = executor.invoke({ "input": "现在北京时间是多少?顺便计算 23 乘以 17。" }) print(result["output"])执行时打开verbose=True,可以看到完整的思考轨迹:
- 模型决定先调用
get_current_time - 工具返回时间结果
- 模型接着调用
multiply - 工具返回 391
- 模型整理最终答案
这里的关键认知是:工具只是普通函数,@tool装饰器负责把函数包装成模型可识别的工具描述。工具名、参数说明、函数 docstring 都会传给模型,作为模型选择工具的依据。所以工具说明必须写清楚,否则模型可能不会调用。
需要注意,工具调用能力是模型侧支持的。OpenAI 的 GPT 系列原生支持,Ollama 本地模型需要看模型是否支持 Function Calling。跑之前确认模型版本。
6. RAG 效果评估与调优
RAG 链路跑通后,下一步是评估效果。很多初学者只关注“能不能生成答案”,忽略“检索质量”这个真正的瓶颈。知识库问答的失败案例,大部分问题出在检索环节。
6.1 核心评估指标
| 指标 | 观察环节 | 说明 | 评估方式 |
|---|---|---|---|
| 命中率 Hit Rate | 检索 | 正确答案所需的文档是否出现在 TopK 结果中 | 人工标注或按标准答案片段判断 |
| MRR | 检索排序 | 第一个正确答案排得越靠前越好 | 自动化计算 |
| 上下文相关性 | 检索+生成 | 检索出的内容是否与问题主题相关 | LLM 辅助评分 |
| 忠实度 Faithfulness | 生成 | 答案是否忠于检索上下文,不编造信息 | LLM 辅助对比答案与上下文 |
| 答案相关性 | 生成 | 答案是否直接回答用户问题,而非答非所问 | LLM 辅助评分 |
工程上最常用的两个指标是 Hit Rate 和 MRR。它们只考察检索结果,不涉及生成,方便快速迭代。
可以写一个简易命中率评估脚本:
def hit_rate(questions, golden_docs, retriever): hits = 0 for question, gold in zip(questions, golden_docs): docs = retriever.invoke(question) context = " ".join([doc.page_content for doc in docs]) if gold in context: hits += 1 return hits / len(questions)更完整的评估可以借助 RAGAS 这类开源框架做 LLM 辅助评分,也可以自己写一个“LLM 裁判”脚本。核心是先把问题集和标准答案准备好,再跑指标,避免凭感觉判断效果。
6.2 重排序与混合检索
基础向量检索有两个常见问题:
- 语义相近但关键词不匹配的文本,召回不稳定
- TopK 结果里混入无关片段
重排序(Rerank)可以在向量检索之后,用 Cross-Encoder 模型对候选文档逐条打分,把最相关的内容排到前面。
# 伪代码:先向量检索得到候选,再用重排序模型精排 candidates = retriever.invoke(question, k=10) reranked = reranker.rerank(question, candidates) final_docs = reranked[:4]混合检索则是“向量检索 + 关键词检索”并行,再把结果合并去重。Elasticsearch 同时支持 BM25 和向量检索,生产场景常用它做统一检索层。如果你们系统已经在用 Elasticsearch,接入 RAG 时优先考虑它,而不是另起一套向量库。
6.3 切分与提示词调优
RAG 调优的大方向,按优先级排列:
- 切分策略:调整
chunk_size、chunk_overlap,实测 300 到 800 之间最常用 - 检索召回数:
k太小可能漏答案,太大可能引入噪声,常见取值 4 到 10 - 重排序:候选集扩到 10 到 20,精排后取前 4 到 5
- 提示词:明确要求“只基于资料回答”“资料不足时直接说明”
- 查询改写:用户问题太口语化时,先让模型改写为检索表达
调优时一次只改一个变量,跑完评估指标再改下一个。
7. 接口服务化与批量任务设计
纯脚本演示只能验证逻辑,真正接入业务需要把 RAG 和 Agent 封装成接口服务。
7.1 FastAPI 包装 RAG
用 FastAPI 包装一个/rag/query接口:
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class QueryBody(BaseModel): question: str k: int = 4 temperature: float = 0.0 @app.post("/rag/query") def rag_query(body: QueryBody): docs = retriever.invoke(body.question) context = "\n\n".join([doc.page_content for doc in docs]) chain = prompt | llm | StrOutputParser() answer = chain.invoke({"context": context, "question": body.question}) return { "answer": answer, "source_count": len(docs), "sources": [doc.metadata.get("source", "") for doc in docs] }启动服务:
uvicorn main:app --host 127.0.0.1 --port 8000用 curl 验证:
curl -X POST http://127.0.0.1:8000/rag/query \ -H "Content-Type: application/json" \ -d '{"question": "什么是RAG?", "k": 4}'返回结果里带上sources,方便调用方核对答案来源。这个信息在调试阶段非常有用。
7.2 批量任务设计
接口服务适合在线问答。如果业务有批量需求,比如一次性处理上百个问题,不应该在上百个请求里直接并发调用接口,更稳妥的做法是任务队列模式。
简单实现可以用concurrent.futures控制并发:
import time from concurrent.futures import ThreadPoolExecutor, as_completed questions = ["问题1", "问题2", "问题3"] def process(question): return ask(question) with ThreadPoolExecutor(max_workers=4) as pool: futures = {pool.submit(process, q): q for q in questions} for future in as_completed(futures): question = futures[future] try: answer = future.result() print(f"{question}: {answer}") except Exception as e: print(f"{question}: FAILED - {e}")生产环境建议使用 Celery 或消息队列做异步任务,任务状态、失败重试、结果落库都更完善。无论哪种方案,都要注意:
- 记录每个任务的状态和日志
- 失败任务要有重试机制,建议设置重试上限
- 控制并发数,避免打爆模型服务或向量库
- 接口服务要加访问限制,避免内部接口被外部调用
7.3 并发与重试策略
大模型接口的延迟通常以秒计,在线接口超时设置建议 60 秒以上。批量任务重试时要注意幂等性:同一个问题重复处理,不应该产生两份不一致的结果。简单做法是任务表里记录处理状态,处理成功后标记完成。
8. 资源占用与性能观察
如果你的开发机性能一般,需要关心整个 RAG 链路的资源占用。
8.1 各环节资源消耗特征
| 环节 | 资源消耗类型 | 说明 |
|---|---|---|
| 文档加载与切分 | CPU、内存 | 一次性操作,PDF 解析较慢 |
| 向量化嵌入 | CPU/GPU、内存 | 批量文本越多,耗时越长 |
| 向量库检索 | 内存、磁盘 | 文本块数量越大,索引占用越高 |
| LLM 生成 | 内存/显存或云端 API | 本地模型时资源占用最明显 |
| 重排序 | CPU/GPU | 推理耗时比向量检索高,通常只对候选集执行 |
本地嵌入模型的参数量通常在几百 MB 到几 GB 之间,CPU 可以推理,只是大批量嵌入时速度慢。本地 7B 量级模型通过 Ollama 运行,通常需要 4GB 以上内存或显存,具体取决于模型量化精度。实际占用需以本机测试为准。
8.2 显存与内存观察方法
Linux 下观察显存使用:
nvidia-smi观察内存使用:
free -hAPI 模式下,本地资源压力主要来自向量库和文档预处理,模型推理在云端。如果感觉响应变慢,优先检查向量库磁盘 IO 和 API 调用频率。
8.3 降低资源占用的通用手段
- 文本块数量较大时,先做嵌入缓存,重复文本不重复向量化
- 向量库索引大小影响检索耗时,按业务范围拆分多个集合
- 本地模型中,优先选择 4bit 量化版本,减少内存占用
- 批量任务控制并发数,避免内存暴涨
- 定时清理日志和临时文件
还有一个常见问题:服务启动后端口被占用。启动前先检查端口:
lsof -i :8000如果有进程残留,杀掉旧进程再启动新服务。
9. 常见问题与排查方法
实战中报错不可怕,关键要知道往哪个方向查。把最常见的问题整理成一张表:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| pip 安装依赖失败 | Python 版本过低或依赖冲突 | 查看报错日志,确认 Python 版本 | 使用 Python 3.10/3.11,创建新虚拟环境 |
找不到langchain.llms模块 | 使用了旧版导入路径 | 检查 LangChain 版本 | 改用langchain_openai等新包 |
| 模型返回空内容或报错 | API Key 未配置或 Base URL 不对 | 检查.env文件和日志 | 确认环境变量已加载 |
| Chroma 向量库打开失败 | 持久化目录损坏或版本不一致 | 查看启动日志 | 备份后删除./db/chroma重建 |
| 检索结果不相关 | 切分策略不合理或 embedding 不适配 | 打印检索到的文档内容 | 调整 chunk_size、k 值,加重排序 |
| Agent 不调用工具 | 工具描述不清晰或模型不支持工具调用 | 打开 verbose 查看规划过程 | 改写工具 docstring,换支持工具调用的模型 |
| 接口超时 | 模型推理耗时较长或并发过高 | 查看 API 日志和耗时记录 | 加长超时时间,降低并发数 |
| 批量任务卡住 | 某个任务出现异常未捕获 | 检查任务日志和异常处理 | 单任务 try/except,设置重试上限 |
实际排查时,先看报错信息,再缩小到具体环节。RAG 链路按“文档加载→切分→向量化→检索→生成”分段打日志,很快能定位问题。
有一个非常有用的调试技巧:在检索之后打印检索到的文档内容。如果文档内容本身就不对,那问题一定在检索前面的环节,而不是生成模型的问题。
10. 最佳实践与学习路径建议
最后聊几条工程落地建议,都是容易被忽视但很影响结果的事情。
10.1 先小参数跑通,再扩大规模
第一次搭建时,不要准备几百 MB 的文档,先拿 3 到 5 篇文章跑通全链路。确认检索和生成都正常后,再逐步扩充知识库。这样可以快速区分“代码问题”和“数据问题”。
10.2 建立一套最小可运行配置
把环境依赖、.env模板、启动命令记录成文档,或者在项目里保留一个README.md和requirements.txt。团队协作时,新成员十几分钟就能把环境跑起来,而不是反复踩安装坑。
10.3 评估优先于调参
没有评估指标的 RAG 调优都是凭感觉。先准备一份至少覆盖常见问题的测试集,再用命中率和忠实度指标做基线,每次改动跑一遍结果对比。比直接调整参数更有效。
10.4 合规与边界意识
如果知识库涉及公司内部资料或用户隐私,需要特别注意:
- 确认文档来源合法,不放入未授权的版权内容
- 系统内部接口要限制访问范围,避免数据泄露
- 涉及人脸、声音等敏感数据时,必须确保有明确授权
- 重要场景生成结果要人工复核,不能直接对外发布
10.5 下一步学习方向
跑通基础链路后,可以按这几个方向继续深入:
- 把 Agent 接进业务系统,接入数据库查询、日志分析、工单处理等真实工具
- 学习 LangGraph,把多步骤流程做成带状态管理的工作流
- 研究多路召回策略,结合关键词检索、向量检索和知识图谱
- 针对垂直领域数据做切分策略和提示词优化
- 用 Elasticsearch 等企业级检索组件替换单机向量库,支撑更高并发
这套 LangChain、RAG、AI Agent 的组合在任何大模型应用项目里基本都是基础设施。把这一条主线跑通,后面再学多 Agent 协作、记忆管理、工具调用增强,都有清晰的地图可以参照。建议先照着文中的示例代码把 RAG 链路跑通,再尝试加上一个简单工具,Ag ent 和 LangGraph 的部分很快就能上手。