Appearance
RAG
检索增强生成(Retrieval-Augmented Generation,RAG)解决的是"模型不知道的外部知识"问题。AgentScope 2.x 把 RAG 做成了 agentscope.rag 模块 + RAGMiddleware 中间件 的两层结构:前者负责文档解析、 切块、向量化与存储,后者负责把检索能力接入 Agent 的 ReAct 循环。这套架构在 2.0.3 回归——1.x 的 agentscope.rag 旧 API 已被移除,2.x 是全新实现,类名、方法签名、模块路径均不兼容。
版本基线:本文以 Python
agentscope 2.0.4(PyPI,2026-07-07)为准,要求 Python>=3.11。 RAG 模块在 2.0.3 回归(源:2.0.4 release notes),1.x 的agentscope.rag旧 API 已移除, 不可作为 2.x 代码使用。导入路径为from agentscope.rag import KnowledgeBase, TextParser, ...与from agentscope.middleware import RAGMiddleware(源:src/agentscope/rag/__init__.py、src/agentscope/middleware/_rag.py)。2.x 文档站存在版本化陷阱:非版本化的/en/URL 会 404,本页所有官方链接均使用versions/2.0.4/en/形式。
为什么需要 RAG
大模型的参数化知识有两个硬约束:训练有截止日期,且无法访问你的私有文档。RAG 的思路是——在推理前 把"与当前问题相关的文档片段"检索出来,注入到上下文里,让模型基于检索结果作答。AgentScope 2.x 把这件事拆成两条独立的流:
- 索引流(离线 / 写入路径):把原始文档解析成
Section、切块成Chunk、用 embedding 模型 向量化、写入向量库。这一步由KnowledgeBase负责,与 Agent 无关。 - 检索流(在线 / 读取路径):Agent 在推理时检索知识库,把命中的
Chunk注入上下文或作为工具 结果回灌。这一步由RAGMiddleware编排。
两条流通过 KnowledgeBase 实例解耦——索引时你拿到一个 KnowledgeBase 句柄,检索时把同一个句柄 交给 RAGMiddleware。多个 KnowledgeBase(哪怕用不同 embedding 模型)可以挂到同一个 RAGMiddleware 上,Agent 一次检索就能跨库合并结果。
前端类比
如果你来自前端,RAG 就是全文搜索 + 结果注入的组合:
- 把索引流类比为 Algolia / MeiliSearch 的建库过程:原始文档(PDF、PPT、纯文本)经过分词 (parser)、切片(chunker)、向量化(embedding)后写入倒排索引(向量库)。区别在于 Algolia 用 词频向量,RAG 用语义向量。
- 把检索流类比为搜索框 + 自动补全:用户输入查询 -> 搜索引擎返回 top_k 条命中 -> 把命中片段 拼进 prompt(类似把搜索结果渲染到页面顶部)-> 模型基于这些片段生成回答。
RAGMiddleware的两种模式对应两种前端交互模式:"agentic"像用户主动点击搜索按钮(模型 自行决定何时检索),"static"像页面加载时自动搜索(每次用户提问都先检索再回答)。
AgentScope 原生语义:RAG 不是把检索结果塞进 system prompt 这么简单。KnowledgeBase 是一个 运行时句柄,绑定了一个 embedding 模型和一个向量库 collection,提供 search / insert_document / delete_document / list_documents 四个操作。RAGMiddleware 不拥有这些资源,只编排检索—— "agentic" 模式通过 list_tools() 向 Agent 暴露一个 search_knowledge 工具,由模型在 ReAct 循环 中决定何时调用;"static" 模式在每轮 reply 的首次推理(cur_iter == 0)时自动检索,把结果作为 HintBlock 注入上下文。这和"前端调一次搜索 API 拼字符串"不同:检索是 Agent 执行链的一环,受 中间件洋葱模型包裹,可与 Tracing、Budget 等中间件组合。
索引与检索数据流
下面这张图把两条流画在一起。左半是索引流:文档经过 parser 产出 Section,chunker 切成 Chunk, embedding 模型向量化后写入向量库的 collection。右半是检索流:用户查询进入 RAGMiddleware,在 向量库中检索出 Chunk,agentic 模式作为工具结果回灌 Agent,static 模式作为 HintBlock 注入 上下文。
图后解释:
KnowledgeBase是两条流的交汇点——索引时你调用kb.insert_document(chunks),检索时RAGMiddleware调用kb.search(queries)。同一个kb实例被两边共享。- parser 与 chunker 职责分离:parser 只按文档自然边界(PDF 一页、PPT 一张幻灯片)产出
Section,不切分长文本;chunker 才把长文本按近似 token 数切成Chunk。两者都是async。 - embedding 模型在索引和检索时必须一致——
KnowledgeBase把同一个embedding_model同时 用于插入时的文档向量和检索时的查询向量,向量维度和语义空间必须匹配,否则检索无意义。 - 向量库是 async context manager——
KnowledgeBase要求 store 在使用前已进入(__aenter__已调用),因此索引和检索代码都要用async with store:包裹。
组件表
agentscope.rag 与 agentscope.middleware 中与 RAG 相关的组件如下(源:rag/__init__.py、 middleware/_rag.py):
| 组件 | 作用 |
|---|---|
KnowledgeBase | 运行时句柄,绑定 embedding 模型 + 向量库 collection,提供检索 / 插入 / 删除 |
TextParser | 解析纯文本 / Markdown 文件为 Section(str 既可是路径也可是内联文本) |
PDFParser | 解析 PDF,每页一个 Section |
PPTParser | 解析 PPTX,每张幻灯片一个 Section |
WordParser | 解析 Word(.docx)文档 |
ExcelParser | 解析 Excel 表格 |
ImageParser | 解析图片(配合多模态 embedding 模型) |
ApproxTokenChunker | 按近似 token 数切块(len(text.encode("utf-8")) // 4),支持 overlap |
MilvusLiteStore | Milvus Lite 向量库后端,本地 .db 文件,需 pip install agentscope[milvuslite] |
QdrantStore | Qdrant 向量库后端 |
MongoDBStore | MongoDB 向量库后端 |
RAGMiddleware | 把 KnowledgeBase 检索能力接入 Agent 的中间件,agentic / static 双模式 |
此外,embedding 模型从 agentscope.embedding 导入(源:embedding/__init__.py):
| 组件 | 作用 |
|---|---|
DashScopeEmbeddingModel | DashScope embedding 模型,支持文本与多模态 |
OpenAIEmbeddingModel | OpenAI embedding 模型 |
GeminiEmbeddingModel | Gemini embedding 模型 |
OllamaEmbeddingModel | Ollama 本地 embedding 模型 |
EmbeddingModelBase | embedding 模型抽象基类,自定义后端时继承 |
索引示例:解析、切块、写入向量库
索引流的完整步骤:选 parser 解析文件 -> 选 chunker 切块 -> 创建 embedding 模型与向量库 -> KnowledgeBase.insert_document(chunks)。注意向量库必须用 async with 进入。
python
import asyncio
import os
from agentscope.credential import DashScopeCredential
from agentscope.embedding import DashScopeEmbeddingModel
from agentscope.rag import (
ApproxTokenChunker,
KnowledgeBase,
MilvusLiteStore,
TextParser,
)
async def build_knowledge_base() -> KnowledgeBase:
# 1. 解析文档:TextParser 接受文件路径或内联文本
parser = TextParser()
sections = await parser.parse(
file="docs/handbook.md",
filename="handbook.md",
)
# 2. 切块:默认 chunk_size=512, overlap=50(近似 token)
chunker = ApproxTokenChunker(chunk_size=512, overlap=50)
chunks = await chunker.chunk(sections)
# 3. embedding 模型:dimensions 是必填项,索引与检索必须用同一个模型
embedding_model = DashScopeEmbeddingModel(
credential=DashScopeCredential(
api_key=os.environ["DASHSCOPE_API_KEY"],
),
model="text-embedding-v4",
dimensions=1024,
)
# 4. 向量库:MilvusLiteStore 是 async context manager
store = MilvusLiteStore(uri="./rag_demo.db")
# 5. KnowledgeBase 句柄:description 必填,agentic 模式下会暴露给模型
kb = KnowledgeBase(
name="company-handbook",
description="公司内部规章制度与入职文档。",
embedding_model=embedding_model,
vector_store=store,
collection="handbook",
)
# 6. 向量库必须先进入,再插入
async with store:
document_id = await kb.insert_document(chunks)
print(f"已索引文档,document_id={document_id}")
return kb
asyncio.run(build_knowledge_base())要点:
parser.parse(file, filename)是 async——返回list[Section]。TextParser的file参数 如果指向磁盘上的现存文件就当路径读,否则当内联文本处理;PDFParser/PPTParser等二进制 parser 的file只接受文件路径(str)或bytes。chunker.chunk(sections)是 async——返回list[Chunk]。ApproxTokenChunker用len(text.encode("utf-8")) // 4估算 token 数,不依赖任何 tokenizer 库;携带DataBlock(图片、视频)的 Section 原样透传,不切片。DashScopeEmbeddingModel的dimensions是必填——文本模型text-embedding-v4支持 64 / 128 / 256 / 512 / 768 / 1024 / 1536 / 2048 等维度,索引和检索必须用同一维度。KnowledgeBase构造函数的description是必填——name和description都会暴露给模型:name用于工具描述与前端渲染,description告诉模型"这个知识库里有什么、何时该检索"。async with store:不可省略——KnowledgeBase要求 store 的__aenter__已调用,否则 向量库客户端未初始化。
检索接入 Agent:RAGMiddleware + Toolkit
检索流的核心是 RAGMiddleware。"agentic" 模式(默认)下,中间件通过 list_tools() 暴露一个 search_knowledge 工具,模型在 ReAct 循环中自行决定何时检索;Toolkit 把这个工具注册进 Agent。
python
import asyncio
import os
from agentscope.agent import Agent
from agentscope.credential import DashScopeCredential
from agentscope.embedding import DashScopeEmbeddingModel
from agentscope.message import UserMsg
from agentscope.middleware import RAGMiddleware
from agentscope.model import DashScopeChatModel
from agentscope.rag import KnowledgeBase, MilvusLiteStore
from agentscope.tool import Toolkit
async def main() -> None:
# 复用索引阶段创建的 KnowledgeBase(此处省略索引代码)
embedding_model = DashScopeEmbeddingModel(
credential=DashScopeCredential(
api_key=os.environ["DASHSCOPE_API_KEY"],
),
model="text-embedding-v4",
dimensions=1024,
)
store = MilvusLiteStore(uri="./rag_demo.db")
kb = KnowledgeBase(
name="company-handbook",
description="公司内部规章制度与入职文档。",
embedding_model=embedding_model,
vector_store=store,
collection="handbook",
)
# RAGMiddleware:agentic 模式让模型自行决定何时检索
rag_middleware = RAGMiddleware(
knowledge_bases=[kb],
parameters=RAGMiddleware.Parameters(
mode="agentic",
top_k=3,
),
)
# Toolkit 注册中间件提供的 search_knowledge 工具
toolkit = Toolkit(tools=await rag_middleware.list_tools())
agent = Agent(
name="hr_assistant",
system_prompt="你是公司 HR 助手。回答员工规章问题时,先检索知识库再作答。",
model=DashScopeChatModel(
credential=DashScopeCredential(
api_key=os.environ["DASHSCOPE_API_KEY"],
),
model="qwen-plus",
),
middlewares=[rag_middleware],
toolkit=toolkit,
)
async with store:
result = await agent.reply(
UserMsg(name="user", content="年假天数怎么计算?"),
)
print(result.get_text_content())
asyncio.run(main())要点:
RAGMiddleware(knowledge_bases=[...], parameters=...)——构造参数名是knowledge_bases(复数),不是knowledges。parameters接受RAGMiddleware.Parameters实例,省略时用默认值 (mode="agentic",top_k=5)。Toolkit(tools=await mw.list_tools())——list_tools()是 async,在 agentic 模式返回[search_knowledge 工具],在 static 模式返回[]。Toolkit 把工具注册给 Agent,模型才能在 ReAct 循环中调用。Agent(middlewares=[rag_middleware], toolkit=toolkit)——中间件和工具包都要传入。中间件 挂载后,模型的search_knowledge工具调用会被RAGMiddleware内部处理,检索结果作为ToolChunk回灌进上下文。- 向量库
async with store:仍要包裹整个检索过程——因为kb.search()会访问向量库客户端。
agentic vs static 模式
RAGMiddleware.Parameters.mode 只有两个取值(源:middleware/_rag.py,Literal["static", "agentic"]):
| 维度 | "agentic"(默认) | "static" |
|---|---|---|
| 触发方式 | 模型在 ReAct 循环中自行决定是否调用工具 | 每轮 reply 的首次推理(cur_iter == 0)自动检索 |
| 工具暴露 | list_tools() 返回 [search_knowledge] | list_tools() 返回 [](不暴露工具) |
| 结果注入 | 作为 ToolChunk 工具结果回灌 | 作为 HintBlock 注入 agent.state.context |
| 检索时机 | 模型判断需要时 | 用户提问后立即检索 |
| 前端事件 | 走工具调用事件流 | 可选 HintBlockEvent(emit_hint_event=True 时) |
| 适用场景 | 知识库大、只有部分问题需要检索、节省 token | 每个问题都大概率需要知识库、希望模型总能看到相关上下文 |
关键细节:
- agentic 模式不保证一定检索——如果模型判断当前问题不需要知识库(如闲聊),它不会调用
search_knowledge,也就不产生检索开销。这是"灵活"与"可控"的权衡。 - static 模式只检索一次——在
cur_iter == 0(首次推理)时检索,后续工具调用轮次不重复检索, 避免每轮都重新 embedding 和注入。persist_hint=False(默认)时,注入的HintBlock在参与完 当次推理后自动从上下文移除,避免历史污染。 - static 模式的
hint_template——默认模板是"The following content is retrieved from the knowledge base(s)...{context}",必须包含且仅包含一个{context}占位符,否则校验报错。 - 两种模式可以混用多个 KnowledgeBase——
knowledge_bases=[kb1, kb2]时,检索会并发查询所有 库,按分数降序合并后截断到top_k。注意:不同 embedding 模型的分数不可严格比较,混合部署时 跨库排序仅供参考。
与长期记忆的区别
RAG 和长期记忆(Long-Term Memory)都给 Agent 注入"模型不知道的信息",但职责完全不同:
| 维度 | RAG | 长期记忆(LTM) |
|---|---|---|
| 知识来源 | 外部文档(PDF、PPT、手册) | 跨会话的对话历史与用户偏好 |
| 写入方式 | 离线索引(parser + chunker + insert) | 运行时自动提取与存储 |
| 检索触发 | 按查询语义检索相关片段 | 按当前对话上下文检索相关记忆 |
| 数据形态 | 文档切块(Chunk) | 结构化记忆条目(事实、偏好) |
| 中间件 | RAGMiddleware | AgenticMemoryMiddleware / Mem0Middleware / ReMeMiddleware |
简单说:RAG 是"给 Agent 一本可查的参考书",LTM 是"让 Agent 记住和你聊过什么"。两者的中间件可以 同时挂载到同一个 Agent 上。LTM 的细节见 长期记忆。
常见错误
KnowledgeBase漏传description:构造函数签名是KnowledgeBase(name, description, embedding_model, vector_store, collection),description是必填的位置参数。漏传会TypeError。description在 agentic 模式下会被写进search_knowledge工具描述,模型靠它判断"何时该检索 这个库"。- embedding 模型未配或索引 / 检索不一致:
DashScopeEmbeddingModel的dimensions是必填项, 省略会报错。索引时用的 embedding 模型(模型名 + 维度)必须和检索时完全一致——KnowledgeBase用同一个embedding_model同时处理查询和文档向量,维度不匹配会导致向量库报错或检索无意义。 - 向量库未装 extra:
MilvusLiteStore需要pip install agentscope[milvuslite](extra 名是milvuslite),否则import pymilvus失败。QdrantStore、MongoDBStore同理需要各自的 extra。安装时看清报错是不是ModuleNotFoundError: No module named 'pymilvus'之类。 - 向量库未
async with进入:KnowledgeBase要求 store 的__aenter__已调用。如果你直接store = MilvusLiteStore(...)后就调用kb.insert_document(),向量库客户端未初始化会报错。 必须用async with store:包裹索引和检索操作。 kb.search()传单个字符串:search的签名是search(queries: list[str | TextBlock | DataBlock], top_k=5, ...),接受的是查询列表,不是单个str。传kb.search("问题")会 把字符串当可迭代对象逐字符拆分,产生无意义检索。正确写法是kb.search(queries=["问题"])。- mode 取值拼错:
RAGMiddleware.Parameters.mode只有"static"和"agentic"两个取值 (Literal["static", "agentic"])。写成"agent"/"auto"/"reactive"会被 pydantic 校验拒绝。 RAGMiddleware构造参数名写错:参数名是knowledge_bases(复数),不是knowledges或knowledge_base。拼错会被当作未知关键字参数。- static 模式期望工具调用:static 模式下
list_tools()返回[],不暴露search_knowledge工具。如果你在 static 模式下还把await mw.list_tools()注册进 Toolkit,得到的是空工具列表, 模型无法主动检索——检索由中间件在on_reasoning自动完成。
先修
下一步
- 长期记忆 - 跨会话记忆与 RAG 的职责区分