用LangChain搭一个RAG知识库Agent:多模型统一API接入实践

最近在做一个企业内部知识库问答系统,技术栈选了LangChain + Chroma向量数据库。过程中踩了不少坑,把完整实现过程记录下来,方便有同样需求的同学参考。

整体架构

系统分三层:文档处理(加载/分块/向量化)、向量检索(Chroma)、模型调用(通过统一API接口对接多个模型)。编排用LangChain 0.3.x,复杂流程用LangGraph做状态管理。

完整实现

文档加载与分块

python 复制代码
from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
import os

# 按文件类型加载
loaders = {
    "*.pdf": PyPDFLoader,
    "*.md": TextLoader,
    "*.txt": TextLoader
}

all_docs = []
for pattern, loader_cls in loaders.items():
    loader = DirectoryLoader("./knowledge_base", glob=pattern, loader_cls=loader_cls)
    all_docs.extend(loader.load())

# 分块策略:技术文档用较小chunk
splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=120,
    separators=["\n\n", "\n", "。", "!", "?", " "]
)
chunks = splitter.split_documents(all_docs)
print(f"共{len(chunks)}个chunk")

向量化与存储

python 复制代码
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# embedding走统一接口
embeddings = OpenAIEmbeddings(
    model="text-embedding-3-small",
    openai_api_key=os.getenv("API_KEY"),
    openai_api_base=os.getenv("API_BASE")  # 统一API地址
)

vectorstore = Chroma.from_documents(
    chunks,
    embeddings,
    persist_directory="./chroma_db",
    collection_metadata={"hnsw:space": "cosine"}  # 用余弦相似度
)

检索增强生成

python 复制代码
from langchain_openai import ChatOpenAI
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 日常问答用成本较低的模型
llm = ChatOpenAI(
    model="deepseek-chat",
    openai_api_key=os.getenv("API_KEY"),
    openai_api_base=os.getenv("API_BASE"),
    temperature=0.1  # 知识库问答要低温
)

system_prompt = """你是企业知识库助手。根据以下检索到的上下文回答问题。
规则:
1. 只基于上下文回答,不编造信息
2. 上下文不足时明确告知"知识库中暂无相关信息"
3. 回答末尾标注引用的文档名称"""

prompt = ChatPromptTemplate.from_messages([
    ("system", system_prompt),
    ("human", "检索上下文:\n{context}\n\n问题:{input}"),
])

combine_chain = create_stuff_documents_chain(llm, prompt)
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 4, "score_threshold": 0.5}
)
rag_chain = create_retrieval_chain(retriever, combine_chain)

Agent工具化

python 复制代码
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.tools import tool

@tool
def search_docs(query: str) -> str:
    """在企业知识库中检索相关信息。输入是搜索关键词。"""
    docs = vectorstore.similarity_search(query, k=3)
    if not docs:
        return "未找到相关文档"
    return "\n---\n".join(f"[{d.metadata.get('source', '未知')}] {d.page_content}" for d in docs)

@tool
def query_database(sql: str) -> str:
    """查询业务数据库获取结构化数据。输入是SQL语句。"""
    # 实际对接内部数据库
    return "查询结果:..."

tools = [search_docs, query_database]
agent_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是企业助手,可以搜索知识库和查询数据库。"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, tools, agent_prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=5)

多模型切换

复杂推理切到更强的模型,只需要改model参数:

python 复制代码
# 简单问答
qa_llm = ChatOpenAI(model="deepseek-chat", openai_api_key=KEY, openai_api_base=BASE)

# 复杂推理
reasoning_llm = ChatOpenAI(model="claude-sonnet-4-20250514", openai_api_key=KEY, openai_api_base=BASE)

# 自动路由(如果平台支持)
auto_llm = ChatOpenAI(model="auto", openai_api_key=KEY, openai_api_base=BASE)

这里用的是统一API接口对接多个模型(比如Mee3 AI这类聚合平台,50多个模型共用一套OpenAI兼容接口),好处是换模型只改model参数,业务代码不动。接口兼容OpenAI格式,LangChain原生支持,接入成本很低。聚合平台还有个好处是故障转移,单个模型服务波动时自动切备用,不用自己写重试逻辑。也可以接入LangChain的模型抽象层,但统一接口的方式更直接。

踩坑经验

坑一:RecursiveCharacterTextSplitter的separators要按语言配置

默认的separators是英文的,处理中文文档时切分效果很差,一个完整的句子被从中间切断。加上中文标点符号"。", "!", "?"后切分自然多了。另外chunk_overlap一定要设,不然跨块的关联信息会丢失。我设的120,大概是一个句子的长度。

坑二:Chroma的persist目录权限问题

在Docker里跑的时候,Chroma的persist_directory没挂载到宿主机,容器重启数据全没了。排查了半天才发现是这个问题。解决方案是Docker compose里挂载volume,或者直接用Chroma的server模式。另外Chroma的collection_metadata里设"hnsw:space": "cosine",余弦相似度比默认的L2更适合文本语义检索。

坑三:检索结果排序和score_threshold不生效

一开始设了score_threshold=0.5,但检索结果里还是有一堆不相关的。查了LangChain源码才发现,Chroma的similarity_search默认不支持score_threshold过滤,需要用similarity_search_with_score手动过滤。改成这样就好了:

python 复制代码
results = vectorstore.similarity_search_with_score(query, k=8)
# 过滤掉相似度太低的结果
filtered = [doc for doc, score in results if score < 0.5]  # Chroma的score是距离,越小越相似

坑四:AgentExecutor的max_iterations太小导致任务中断

复杂问题需要先搜索知识库再查数据库,两轮工具调用就4次迭代了。max_iterations默认值在某些版本里是3,复杂任务直接中断。调到5之后基本够用了,但偶尔还是不够,最终设到了8。

坑五:temperature设太高导致幻觉

知识库问答场景对准确度要求高,temperature一开始用默认值0.7,模型经常"创造性发挥",把知识库里没有的内容编出来。调到0.1之后回答稳定多了。如果是创意写作场景可以调高,但RAG场景尽量低。

部署注意事项

生产环境部署时几个要点:

向量数据库用server模式,别用嵌入式模式,多进程访问会出问题。

文档更新时增量向量化,别每次全量重建。Chroma支持按collection更新,记录已处理的文档hash就行。

加一层缓存,高频问题的回答直接从缓存返回,省token费用。用Redis存query到answer的映射,key用query的embedding向量。

监控检索质量,定期抽样检查检索结果和最终回答的质量。我发现有些文档因为格式问题解析出错,导致检索不到,定期巡检能及时发现。

成本估算

500份文档,约3000个chunk:

  • 一次性向量化:text-embedding-3-small,费用几块钱
  • 日均200次问答,DeepSeek为主:月费用约100-200元
  • 复杂问题切Claude:额外50-100元/月

走统一API接口的好处是多个模型共用一套鉴权和调用方式,省去了分别注册和管理的麻烦。费用方面,聚合平台(如Mee3 AI)比官方价格低15%到30%,核心价值在于接口统一和故障转移。像Mee3 AI接了50多个模型,服务器在国内,不用翻墙,延迟也低。掘金读者如果对成本敏感,可以对比一下各家聚合平台的价格。

小结

RAG知识库Agent的核心不在框架多花哨,而在文档处理质量和检索效果。花时间调分块策略、separators配置、检索参数和prompt,比换更贵的模型有用得多。

欢迎评论区交流,有什么实现上的问题可以一起讨论。


相关推荐
todoitbo1 小时前
用蓝耘元生代做 GitHub 热榜解读:Dify Chatflow 接入和真实项目分析
ai·github·api·dify·蓝耘
武雄(小星Ai)2 天前
大模型API 8月31日迁移潮:Claude涨价50%、GPT-5.4退场、Kimi K2.5退役,一次算清你的账单怎么变
ai·大模型·api
电商API_180079052472 天前
速卖通商品采集API技术文章
java·开发语言·c++·api·跨境电商·商品详情
万邦科技Lafite3 天前
阿里巴巴拍立淘按图搜索商品API返回值实践:提升用户购物满意度的关键措施
开发语言·api·开放api·电商开放平台·京东开放平台
VIP_CQCRE4 天前
用 Ace Data Cloud 快速接入 Suno:把 AI 音乐生成能力集成进你的产品
人工智能·api·suno·ai音乐·acedatacloud
用户7783366132114 天前
用搜索数据 API 做一个关键词联想组件(防抖 + 缓存 + 可复用)
python·api
VIP_CQCRE5 天前
Veo API 实战:用一句镜头语言,批量生成商业级 AI 视频
aigc·api·ai视频·acedatacloud·veo
VIP_CQCRE5 天前
用 Ace Data Cloud 快速接入 MiniMax H3:把 AI 视频生成能力变成可调用的生产力
ai·aigc·api·视频生成·acedatacloud