RAG 作为 Tool:让 Agent 自主决定何时检索
作者:程序员马丁
Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。
上一篇给 TinyAgent 装上了 Reflection 机制,让 Agent 不只会执行,还能自我评估、自我纠错。结尾留了一个伏笔:Agent 系列从第 5 篇开始就有一个 searchKnowledge 工具,但它一直是硬编码的——写死了几个关键词匹配分支,返回固定的文本。RAG 系列花了大量篇幅讲的向量检索、重排序、混合检索,在 Agent 这边一个都没用上。
这一篇,咱们把 RAG 真正接入 Agent——让检索变成 Agent 的一个工具,由智能体自主决定什么时候查知识库、查什么、查到的结果怎么用。
本项目中具体代码已上传 GitHub TinyAgent,大家 Clone 项目后,将代码分支切换到 1.14.x,默认主分支是最新代码。运行前复制
.env.example为.env,把自己的 API Key 填进去,默认阿里云百炼平台;.env已加入.gitignore,切分支时不会丢。
Mock 版知识搜索
从第 5 篇开始,TinyAgent 的工具箱里就有一个 SearchKnowledgeTool。回顾一下它的核心实现:
public String invoke(String input) {
String query = ToolUtils.extractField(input, "query");
String lowerQuery = query.toLowerCase();
if (lowerQuery.contains("扫地机") && (lowerQuery.contains("推荐")
|| lowerQuery.contains("老人") || lowerQuery.contains("产品"))) {
return "{\"matched\":\"扫地机选购指南\", \"content\":\"比特 S10 Lite ...\"}";
}
if (lowerQuery.contains("耳机")) {
return "{\"matched\":\"耳机产品列表\", \"content\":\"比特 AirX ...\"}";
}
// ...... 更多 if-else 分支
return "{\"matched\":\"七天无理由退货政策\", \"content\":\"签收次日起 7 天内...\"}";
}
本质上就是一个 if-else 关键词匹配器。之前这么写是因为 Agent 系列的重点在循环控制、记忆、规划、反思这些机制,知识检索用 Mock 不影响主线叙事。但随着 TinyAgent 的能力越来越完整,是时候换成真正的 RAG 检索了——覆盖全量知识、理解语义、支持动态更新,而不是靠 if-else 穷举几个关键词分支。
整体设计:RAG 作为 Tool 的架构
在动手写代码之前,先把架构理清楚。RAG 作为 Agent 的一个 Tool,和之前的 queryOrder、compareProducts 这些工具本质上没区别——都是实现了 Tool 接口,由 Agent 自主决定什么时候调用。区别在于,RAG 工具内部跑的是一条完整的检索链路。

整条链路分三层:
- 工具层:
RagSearchTool实现Tool接口,Agent 通过 Function Calling 调用它,和调queryOrder没有任何区别。 - 检索层:接收用户 query → 调 Embedding API 转向量 → 在 pgvector 里做向量检索 → 返回 Top-K 个最相关的知识片段。
- 存储层:
knowledge_chunk表,存的是预处理好的知识文档片段和对应的向量。
为什么用 pgvector 而不是 Milvus?TinyAgent 在第 13 篇长期记忆里已经用 pgvector 存用户画像和交互记录了——schema.sql 里有 CREATE EXTENSION IF NOT EXISTS vector,memory_entry 表已经跑在 pgvector 上。读者的 Docker 环境里已经有一个跑着 pgvector 的 PostgreSQL 容器,不需要额外装任何东西。比特严选 300-500 个 SKU 的体量,pgvector 的性能绰绰有余。
知识库表设计
在 schema.sql 里新增一张 knowledge_chunk 表:
CREATE TABLE IF NOT EXISTS knowledge_chunk (
id BIGSERIAL PRIMARY KEY,
source VARCHAR(256) NOT NULL,
content TEXT NOT NULL,
embedding vector(1024),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
四个字段,各管各的事:
| 字段 | 类型 | 说明 |
|---|---|---|
id | BIGSERIAL | 自增主键 |
source | VARCHAR(256) | 来源文件名,如 退货政策.txt,方便追溯 |
content | TEXT | 知识片段的原始文本 |
embedding | vector(1024) | 文本对应的向量,1024 维(和 memory_entry 保持一致) |
created_at | TIMESTAMP | 写入时间 |
和 memory_entry 表的区别在于:memory_entry 是用户级的(有 user_id、key、type),存的是某个用户的画像和交互记录;knowledge_chunk 是全局的,存的是平台的知识文档,所有用户共享。