12 - 深度研搜:RAGFlow 子智能体与知识库准备


本章课程目标:

  • 理清 RAGFlow 的四层结构:服务、知识库、聊天助手、会话。
  • 掌握课程环境中 RAGFlow 的部署思路和页面准备流程。
  • 完成模型供应商配置、知识库创建、文件上传与解析。
  • 创建 RAGFlow 聊天助手,并通过页面测试知识库问答效果。
  • 理解为什么 RAGFlow 子智能体需要两个工具:先查助手列表,再向指定助手提问。

学习建议: RAGFlow 这章先别急着封装 API。它自己有页面、模型配置、知识库、助手和会话,第一遍先把这些对象之间的关系捋清楚。等你能说清“文件进知识库、助手绑定知识库、会话用助手回答”之后,再看工具封装和子智能体配置,会顺很多。

对应代码分支: 12-deepsearch-ragflow-subagent


1、RAGFlow 解决哪类问题

前两章已经完成了两个子智能体,本章补上第三个子智能体:

子智能体 底层能力 特点
网络搜索助手 Tavily API 直接调用搜索 API
数据库查询助手 MySQL Connector 直接连接数据库执行查询
RAGFlow 助手 RAGFlow 知识库服务 需要先部署和配置外部服务

本章会按四步走:

1
2
3
4
先理解 RAGFlow 适合查什么
-> 再部署服务、准备知识库和聊天助手
-> 然后封装两个 RAGFlow 工具
-> 最后组装成 knowledge_base_agent

RAGFlow 助手和前两个不太一样。Tavily 只要有 API Key 就能查网络,MySQL 只要有账号密码就能查表;RAGFlow 更像一套完整的 RAG 服务,它会负责文档解析、切片、向量化、检索、引用来源和问答生成。


1.1 它查询的是内部非结构化文档

在企业项目里,很多知识不是结构化表格,也不是公开网页,而是内部文档:

  • 公司制度;
  • 产品说明书;
  • 操作手册;
  • 行业白皮书;
  • 金融研报、PDF、Word、Excel;
  • 业务培训材料。

这些资料适合放进 RAGFlow。

用户问:根据 2026 数字人电商直播白皮书,数字人在电商直播里主要解决什么问题?

这种问题不应该查互联网,也不应该查 MySQL 表,而应该交给 RAGFlow 助手。

1.2 为什么不自己手写一套 RAG

如果完全自己写 RAG,需要处理很多环节:

1
2
3
4
5
6
7
8
9
文档读取
-> 文本清洗
-> 文档切片
-> 向量化
-> 存入向量数据库
-> 查询时向量检索
-> 重排序
-> 拼上下文
-> 调模型回答

这当然可以做,而且后续深入 RAG 项目时会继续拆这些细节。但在「深度研搜」项目里,我们先用 RAGFlow 提供一套现成的企业知识库能力。

可以先把 RAGFlow 理解成:一个已经集成好文档解析、向量库、检索和问答能力的 RAG 服务。

1.3 它把多格式资料变成统一知识入口

企业知识库最麻烦的地方,不只是“有没有向量数据库”,而是资料来源很杂。

同一个企业里,可能同时存在:

  • PDF 版制度文件;
  • Word 版操作说明;
  • Excel 版业务清单;
  • PPT 版培训材料;
  • 图片较多的产品说明书;
  • 扫描版或排版复杂的历史文档。

如果全部自己处理,就要为不同文件类型分别写读取、清洗、切分和解析逻辑。文档越多,维护成本越高。

RAGFlow 的价值就在这里:**它把“上传文件、解析文件、建立知识库、绑定助手、页面测试、API 查询”放在同一套服务里。**对 DeepAgents 项目来说,我们不用在子智能体里重复实现文档解析和向量检索,只需要把 RAGFlow 当成一个外部知识库服务,通过 API 向合适的聊天助手提问。


2、理解 RAGFlow 的四层结构

层级 可以怎么理解 在项目中的作用
RAGFlow 服务 一整套运行中的知识库系统 提供页面、API、文档解析和问答能力
知识库 Dataset 存放一类文档的空间 例如电商行业知识库、金融行业知识库
聊天助手 Chat 绑定一个或多个知识库的问答入口 对外表现为“电商行业助手”“金融行业助手”
会话 Session 某一次具体提问或对话 代码里临时创建,用完后删除

它们的关系可以这样看:

1
2
3
4
5
6
7
8
9
10
11
12
flowchart TD
Service[RAGFlow 服务] --> Dataset1[知识库 A]
Service --> Dataset2[知识库 B]
Service --> Dataset3[知识库 C]

Chat1[聊天助手 1] --> Dataset1
Chat1 --> Dataset2
Chat2[聊天助手 2] --> Dataset3

Session1[会话 Session] --> Chat1
Session2[会话 Session] --> Chat1
Session3[会话 Session] --> Chat2

在真实使用时,通常是:

1
2
3
4
5
6
7
8
先创建知识库
-> 上传内部文档
-> 等待文档解析完成
-> 创建聊天助手
-> 给聊天助手绑定知识库
-> 用户提问时创建临时会话
-> 向会话提问
-> 拿到答案后删除会话

这里要记住一句话:代码最终不是直接问知识库,而是问某个聊天助手下的会话。


3、部署 RAGFlow 服务

3.1 机器配置建议

RAGFlow 服务比较重,建议使用云服务器部署。它不是一个只启动 Python 进程的小工具,底层还会拉起 Elasticsearch、MinIO、MySQL、Valkey 等组件。

课程环境建议:

配置项 建议值
CPU 至少 4 核,8 核更稳
内存 至少 16 GB
系统 Ubuntu 24.04
磁盘 至少 50 GB
公网 IP 需要

如果本地虚拟机内存不足,后续解析 PDF、启动 Elasticsearch、MinIO、MySQL 等组件时会比较吃力。

3.2 购买腾讯云服务器

腾讯云竞价实例购买:购买地址

进入腾讯云 CVM 购买页后,可以选择竞价实例或按量计费实例。竞价实例价格低,但有被释放的风险,适合短期课堂练习;如果你要持续使用,建议选择更稳定的计费方式。

腾讯云 CVM 购买页面:选择竞价实例、地域、可用区和基础规格

这里先确认计费方式、服务器规格和系统镜像。课程练习可以优先选便宜的短期实例;如果要长期保存知识库,建议使用更稳定的计费方式。

在测试环境,可以放开所有的安全组规则。但是真实项目不要这样做,公网只开放必要端口,管理后台尽量走内网、VPN 或反向代理。

腾讯云安全组配置页面:课堂测试可临时放开入站规则,真实项目只开放必要端口

截图里的安全组配置只适合课堂临时测试。真实项目里,RAGFlow 页面、数据库、对象存储和向量检索服务都不要直接暴露给公网。

腾讯云实例创建结果页面:记录公网 IP、登录密码和安全组信息

实例创建完成后,先记录公网 IP、登录方式和安全组信息。后面浏览器访问、SSH 登录和 API 地址都会用到这里的公网 IP。

实例创建完成后,在控制台找到公网 IP 和密码,准备远程连接服务器。

3.3 安装 Docker 和 Docker Compose

以下命令适用于 Ubuntu 22.04/24.04。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 更新系统软件包索引
sudo apt-get update

# 安装 Docker 仓库所需的基础工具
sudo apt-get install -y ca-certificates curl git openssl

# 创建 apt keyrings 目录,用于存放 Docker 源的 GPG key
sudo install -m 0755 -d /etc/apt/keyrings

# 使用腾讯云 Docker CE 镜像源的 GPG key,国内服务器下载更稳定
sudo curl -fsSL https://mirrors.cloud.tencent.com/docker-ce/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

# 允许 apt 读取该 GPG key
sudo chmod a+r /etc/apt/keyrings/docker.asc

# 添加 Docker CE 的 apt 软件源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://mirrors.cloud.tencent.com/docker-ce/linux/ubuntu/ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null

# 重新更新软件包索引,让 apt 识别 Docker 软件源
sudo apt-get update

# 安装 Docker Engine、Docker CLI、Buildx 和 Compose 插件
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 设置 Docker 开机自启动,并立即启动 Docker
sudo systemctl enable --now docker

# 查看 Docker 版本
sudo docker --version

# 查看 Docker Compose 版本,注意这里是 docker compose,中间有空格
sudo docker compose version

3.4 调整系统参数

RAGFlow 默认使用 Elasticsearch 作为文档检索和向量存储引擎。Elasticsearch 要求 vm.max_map_count 至少为 262144

1
2
3
4
5
6
7
8
9
10
11
# 查看当前 vm.max_map_count
sysctl vm.max_map_count

# 写入持久化配置,重启后仍然生效
echo 'vm.max_map_count=262144' | sudo tee /etc/sysctl.d/99-ragflow.conf

# 立即加载 sysctl 配置
sudo sysctl --system

# 再次确认结果应为 262144 或更大
sysctl vm.max_map_count

3.5 下载 RAGFlow 源码

课程中使用 RAGFlow 的v0.25.4 版本,建议与课程版本保持一致。

3.5.1 方式一:GitHub 浅克隆

如果 GitHub 可以访问,但速度比较慢,优先使用浅克隆。浅克隆只下载指定版本需要的内容,不下载完整提交历史,通常会快很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 进入 /opt,常用于放置服务器应用
cd /opt

# 只克隆 v0.25.4 这个版本,减少下载体积
sudo git clone --depth 1 --branch v0.25.4 https://github.com/infiniflow/ragflow.git

# 将目录归属给当前用户,避免后续每一步都需要 sudo 编辑文件
sudo chown -R "$USER":"$USER" /opt/ragflow

# 进入 RAGFlow 目录
cd /opt/ragflow

# 进入 Docker Compose 配置目录
cd /opt/ragflow/docker

3.5.2 方式二:使用 Gitee 镜像

如果 GitHub 克隆仍然很慢,可以使用 InfiniFlow 在 Gitee 上的镜像仓库。

1
2
3
4
5
6
7
8
9
10
11
# 进入 /opt,常用于放置服务器应用
cd /opt

# 从 Gitee 镜像克隆指定版本,适合国内服务器
sudo git clone --depth 1 --branch v0.25.4 https://gitee.com/infiniflow/ragflow.git

# 将目录归属给当前用户
sudo chown -R "$USER":"$USER" /opt/ragflow

# 进入 Docker Compose 配置目录
cd /opt/ragflow/docker

3.6 修改 RAGFlow 环境配置

如果你只是短期测试,并且不要求密码安全性,可以跳过本节,直接进入“3.7 启动 RAGFlow”。

先备份 .env

1
2
# 备份原始配置,后续改错可以对照恢复
cp .env .env.bak

生成强密码:

1
2
3
4
5
# 分别生成几个强随机密码,用于替换 .env 里的默认密码
openssl rand -hex 24
openssl rand -hex 24
openssl rand -hex 24
openssl rand -hex 24

编辑 .env

1
2
# 打开 RAGFlow Docker 环境变量配置
nano .env

至少修改以下字段,不要使用默认密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Elasticsearch 密码
ELASTIC_PASSWORD=换成强密码

# MySQL 密码
MYSQL_PASSWORD=换成强密码

# MinIO 密码
MINIO_PASSWORD=换成强密码

# Redis 密码
REDIS_PASSWORD=换成强密码

# 时区,上海服务器建议保持 Asia/Shanghai
TZ=Asia/Shanghai

# 首次部署建议先允许注册,创建完管理员账号后再改成 0
REGISTER_ENABLED=1

保存 nano 文件:

1
2
3
Ctrl + O
Enter
Ctrl + X

3.7 启动 RAGFlow

/opt/ragflow/docker 目录执行:

1
2
3
4
5
6
7
8
9
10
11
# 确认当前目录正确
pwd

# 预先拉取所有镜像。第一次会比较慢
sudo docker compose -f docker-compose.yml pull

# 后台启动 RAGFlow 及其依赖服务
sudo docker compose -f docker-compose.yml up -d

# 查看所有容器状态
sudo docker compose -f docker-compose.yml ps

如果这一步镜像拉取太慢,可以先给 Docker 配置腾讯云镜像加速源。这个配置会影响 mysqlelasticsearchminiovalkey 等所有从 Docker Hub 拉取的基础镜像。

1
2
# 打开 Docker daemon 配置文件
sudo nano /etc/docker/daemon.json

写入以下内容:

1
2
3
{
"registry-mirrors": ["https://mirror.ccs.tencentyun.com"]
}

保存 nano 文件:

1
2
3
Ctrl + O
Enter
Ctrl + X

重启 Docker,让镜像加速配置生效:

1
2
3
4
5
6
7
8
# 重新加载 systemd 配置
sudo systemctl daemon-reload

# 重启 Docker
sudo systemctl restart docker

# 查看 Docker 是否正常运行
sudo systemctl status docker

确认镜像加速源是否生效:

1
2
# 输出中应能看到 Registry Mirrors
sudo docker info

Docker 镜像加速配置验证与 RAGFlow 镜像拉取过程:确认 Registry Mirrors 生效并开始拉取依赖镜像

如果输出里能看到 Registry Mirrors,并且后续 docker compose pull 开始拉取 Elasticsearch、MySQL、Valkey、RAGFlow 等镜像,说明镜像加速配置已经生效。第一次拉镜像体积比较大,耐心等它完成即可。

查看 RAGFlow 主服务日志:

1
2
# 持续查看 RAGFlow 日志
sudo docker compose -f docker-compose.yml logs -f ragflow-cpu

RAGFlow 服务日志:看到服务监听地址后再进入浏览器访问

日志里看到服务已经监听地址,再去浏览器访问会更稳。第一次启动时依赖服务较多,页面没立刻打开不一定是失败,先看日志更准确。

当日志中出现类似内容,说明服务启动成功:

1
Running on all addresses (0.0.0.0)

退出日志查看:

1
Ctrl + C

3.8 浏览器访问

打开浏览器访问:

1
http://你的服务器公网IP

RAGFlow 登录或注册页面:确认公网访问、端口映射和主服务启动正常

能打开登录或注册页面,说明公网访问、端口映射和主服务启动基本正常。首次创建账号后,记得回到 .env 关闭公开注册。

如果安全组和服务都正常,你会看到 RAGFlow 登录或注册页面。

首次注册完成后,建议关闭公网注册:

1
2
3
4
5
# 回到 Docker 配置目录
cd /opt/ragflow/docker

# 编辑 .env
nano .env

修改:

1
REGISTER_ENABLED=0

然后重启服务使配置生效:

1
2
# 重新创建受影响容器,使 .env 配置生效
sudo docker compose -f docker-compose.yml up -d

如果页面打不开,优先检查服务器防火墙、安全组、RAGFlow 容器状态和端口映射。

3.9 安全提示

课程演示中为了快速学习,可能会临时放开较宽的安全组规则。真实项目不要这样处理。

生产环境至少要注意:

  • 只开放必要端口;
  • 后台管理页不要直接暴露在公网;
  • API Key 不要写入公开文档;
  • 数据库、对象存储、向量库服务不要裸露;
  • 尽量通过内网、VPN 或反向代理访问。

4、在页面准备模型、知识库和聊天助手

RAGFlow 服务启动之后,此刻还没有开始写代码。页面里的模型、知识库、文档解析和聊天助手都准备好,后面的 API 调用才有东西可问。

4.1 添加通义千问模型供应商

至少会涉及几类模型:

模型类型 作用
大语言模型 根据检索到的内容生成回答
向量化模型 把文档切片和用户问题转成向量
重排序模型 对召回片段做二次排序,提高相关性
多模态模型 解析图片或 PDF 中的复杂视觉内容

课程里选择通义千问系列,是因为它覆盖模型比较完整。进入 RAGFlow 后,可以在模型供应商里添加 Tongyi-Qianwen,填入自己的百炼 API Key。

RAGFlow 模型供应商配置页面:选择 Tongyi-Qianwen 并填写百炼 API Key

这一步是在 RAGFlow 里配置模型供应商,不是在项目 .env 里配置。RAGFlow 需要自己知道用哪个 LLM、Embedding 或 Rerank 模型。

模型供应商配置完成后,再进入默认模型设置。这里不要一次性把所有模型都配满,先保证 LLMEmbedding 可用即可。

4.2 默认模型怎么选

RAGFlow 的默认模型配置页里会出现 LLMEmbeddingVLMASRRerankTTS 等选项。它们不是都必须配置,而是分别对应不同能力。

配置项 作用 本课程是否必须
LLM 大语言模型,负责对话、总结、问答生成和智能体推理 必须
Embedding 向量模型,把文档切片和用户问题转成向量,用于知识库检索 必须
Rerank 重排序模型,对召回片段做二次排序,提高相关内容排在前面的概率 可选
VLM 视觉语言模型,用于理解图片、截图、图文混排页面等视觉内容 暂时不需要
ASR 语音转文字模型,用于处理录音、语音文件等输入 暂时不需要
TTS 文字转语音模型,用于把回答转换成语音播放 暂时不需要

所以当前课程阶段,只需要先配置两项:

1
2
LLM: qwen-flash
Embedding: text-embedding-v3

这两项刚好对应 RAG 的核心流程:文档解析后要用 Embedding 写入向量库,用户提问时也要用同一个向量模型做检索;检索到相关片段后,再交给 LLM 生成最终回答。

Rerank 可以先不配置。等后面发现“能检索到内容,但排序不够准”时,再补一个重排序模型。VLMASRTTS 属于图片、语音相关能力,本章主要处理文档知识库问答,不需要先配置。


4.3 创建知识库

模型配置完成后,就可以创建知识库。知识库名称和描述要认真写,因为后面创建聊天助手、让 Agent 选择助手时,都依赖这些描述帮助模型判断“这里面有什么资料”。

截图里的重点不是按钮位置,而是名称和描述。后面 Agent 会根据这些文字判断“这个知识库适合回答什么问题”。

本章演示创建两个知识库:

知识库名称 描述建议
电商行业 存放电商行业、AI 应用、数字人直播、直播带货相关研究资料
金融行业 存放货币政策、全球投资展望、投资者情绪等金融行业研究报告

不要写成“测试库”“知识库 1”这种没信息量的名字,否则后续模型很难判断应该使用哪个助手。

RAGFlow 知识库列表页面:确认电商行业和金融行业两个知识库已经创建

4.4 上传文件并解析

知识库创建后,可以上传 PDF、Word、TXT、Excel 等文件。

可以将项目提供的 PDF 文件上传到对应知识库中,文件路径是 deepsearch-agents/docs/knowledge_base/。电商相关 PDF 上传到 电商行业 知识库,金融相关 PDF 上传到 金融行业 知识库,不要混在同一个库里。

RAGFlow 文件上传页面:上传资料后需要等待解析状态完成

上传文件不等于知识库可用了。只有解析完成后,文档才会经过切片、向量化并写入检索引擎,后续聊天助手才能查到内容。

上传后一定要点击解析。解析过程大致会做这些事:

1
2
3
4
5
读取文件
-> 提取文本或多模态内容
-> 文档切片
-> 向量化
-> 写入底层向量数据库

4.5 创建聊天助手并绑定知识库

知识库只是存资料的地方,真正对外提问的是聊天助手。一个聊天助手可以绑定一个或多个知识库。

本章建议先采用“一类知识库对应一个助手”的方式:

聊天助手 绑定知识库
电商行业助手 电商行业
金融行业助手 金融行业

也可以创建一个综合行业研究助手,同时绑定 电商行业金融行业。但在课程演示阶段,先拆成两个助手更清楚,后续 Agent 也更容易根据问题选择正确助手。

RAGFlow 聊天助手列表页面:确认不同业务方向的助手已经创建并准备绑定知识库

聊天助手才是 API 问答入口。知识库存资料,聊天助手负责绑定知识库并对外回答问题。

4.6 助手描述一定要写清楚

创建助手时,助手描述非常重要。

这里建议一开始就把助手名称写得像“业务入口”,例如 电商行业助手金融行业助手。不要只写“助手 A”,后面代码虽然能拿到它,但模型不知道它该负责什么。

后续代码里会通过 API 获取所有助手,并把下面这类信息交给模型:

1
2
助手名称:电商行业助手;功能介绍:提供电商行业、AI 应用、数字人直播、直播带货相关资料查询;关联知识库:电商行业
助手名称:金融行业助手;功能介绍:提供货币政策、全球投资展望、投资者情绪等金融行业资料查询;关联知识库:金融行业

如果助手描述写得太泛,例如“这是一个助手”,模型就无法判断该问谁。

所以建议写成:

1
2
提供电商行业、AI 应用、数字人直播、直播带货相关资料查询,
适合回答电商行业趋势、数字人直播应用、AI 电商场景等问题。

金融行业助手可以写成:

1
2
提供货币政策、全球投资展望、投资者情绪等金融行业资料查询,
适合回答宏观金融、投资展望、政策报告和市场情绪相关问题。

而不是:

1
知识助手

4.7 页面中先做一次问答测试

如果回答中能看到引用来源,说明它不是凭模型常识回答,而是基于知识库检索出来的内容回答。

可以先用这类问题测试:

1
2
全球电商行业 2024-2025 年的市场规模、主要国家排名和增长趋势是什么?
2026 年全球投资展望里提到了哪些主要趋势?

这一步很重要。因为如果页面里都问不出来,后面代码调用 API 也很难成功。

RAGFlow 页面测试结果:确认助手能基于知识库返回答案和引用来源

页面测试是代码接入前的第一道验收。如果页面里已经能返回答案和引用来源,后面排查代码问题时就能把重点放到 API Key、地址和助手名称上。

4.8 生成 API Key

在右上角点击头像,进入个人中心,点击 API,生成 API Key,随后填入项目环境变量中。

RAGFlow 个人中心 API Key 页面:生成后端代码调用服务所需的访问密钥

这里生成的是后端调用 RAGFlow API 的访问密钥。不要把真实 API Key 写进公开文档或提交到仓库里。

项目对应文件路径:deepsearch-agents/.env

后端代码要访问 RAGFlow,需要两个配置:

配置 说明
RAGFLOW_API_URL RAGFlow 服务地址
RAGFLOW_API_KEY RAGFlow 页面中生成的 API Key

5、页面配置和代码调用的分工

RAGFlow 的使用可以分成两部分:

工作 更推荐在哪里做 原因
配模型 页面 配置项多,页面更直观
建知识库 页面或 API 都可以 学习阶段页面更清楚
上传文件和解析 页面更常见 便于观察解析状态和失败原因
创建聊天助手 页面更常见 需要写描述、绑定知识库、测试效果
获取助手列表 代码 Agent 需要动态知道有哪些助手
创建临时会话并提问 代码 用户每次提问都需要自动完成
删除临时会话 代码 防止会话堆积

本项目采用的思路是:

1
2
服务部署、模型、知识库、助手:主要在 RAGFlow 页面准备好
Agent 查询知识库:通过 Python API 创建会话、提问、删除会话

下面就会把这套“代码调用 RAGFlow”的流程封装成两个工具。


6、RAGFlow 子智能体的接入思路

前面已经完成了 RAGFlow 服务侧准备:

1
2
3
4
5
6
7
部署 RAGFlow
-> 配置模型
-> 创建知识库
-> 上传并解析文件
-> 创建聊天助手并绑定知识库
-> 页面测试问答
-> 生成 API Key

接下来要做的是把这些能力接入 DeepAgents 项目。

最终我们会得到第三个子智能体:

子智能体 负责内容
RAGFlow 助手 查询非结构化行业资料,例如白皮书、研报、政策报告、行业报告

它底层会使用两个工具:

工具名 作用
get_assistant_list 获取 RAGFlow 中有哪些聊天助手和关联知识库
create_ask_delete 创建临时会话,向指定助手提问,用完删除会话

这里要抓住一个核心:RAGFlow 子智能体不是直接把问题扔给某个固定知识库,而是先动态发现有哪些助手,再选择合适助手创建临时会话提问。 这样后续知识库和助手增加时,Agent 不需要改代码,也能先查看当前 RAGFlow 里有什么。


6.1 为什么要先查助手列表

RAGFlow 是一个独立服务。今天可能只有“电商行业助手”和“金融行业助手”,明天可能新增“医药行业助手”“企业制度助手”。

如果我们在代码里写死:

1
chat_name = "电商行业助手"

那系统只能问一个固定助手,扩展性很差。

更合理的做法是:

1
2
3
先查询当前有哪些助手
-> 看每个助手的名称、描述、关联知识库
-> 再决定向哪个助手提问

6.2 描述写得准,模型才选得准

get_assistant_list 返回的信息大概长这样:

1
2
助手名称:电商行业助手;功能介绍:提供电商行业、AI 应用、数字人直播、直播带货相关资料查询;关联知识库:电商行业
助手名称:金融行业助手;功能介绍:提供货币政策、全球投资展望、投资者情绪等金融行业资料查询;关联知识库:金融行业

模型会根据这些信息判断应该使用哪个助手。

所以前面反复强调:知识库名称、知识库描述、聊天助手描述,都要认真写。

1
2
用户问数字人直播应用场景 -> 应该找电商行业助手
用户问全球投资展望或货币政策 -> 应该找金融行业助手

7、准备 RAGFlow 工具代码

7.1 读取 RAGFlow 环境变量

项目对应文件路径:deepsearch-agents/app/ragflow/rag_config.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"""
RAGFlow 连接配置加载模块

集中读取 RAGFlow SDK 需要的 API Key 和服务地址,供原始调用示例与
LangChain 工具共用。这样后续如果 .env 字段或读取规则调整,只需要改这一处。
"""

import os
from typing import Optional, Tuple

from dotenv import find_dotenv, load_dotenv


def _load_ragflow_env() -> Tuple[Optional[str], Optional[str]]:
"""
加载 RAGFlow 环境变量

使用 python-dotenv 自动向上查找 .env,保持和项目其他配置加载方式一致。
:return: (api_key, base_url),缺失配置时对应位置返回 None
"""
load_dotenv(find_dotenv())

# RAGFlow SDK 初始化只需要这两个核心字段:认证 API Key 和服务基础地址
api_key = os.getenv("RAGFLOW_API_KEY")
base_url = os.getenv("RAGFLOW_API_URL")
return api_key, base_url

7.2 可选扩展:用代码创建知识库

项目对应文件路径:deepsearch-agents/app/ragflow/knowledge_demo.py

前面主要通过 RAGFlow 页面创建知识库,这是学习阶段最直观的方式。但 RAGFlow SDK 也支持通过代码创建知识库。

这类能力适合后续做自动化后台,例如“用户上传一批企业资料后,系统自动创建知识库并导入文件”。在当前 DeepAgents 子智能体主线里,暂时不把它注册成 Agent 工具,避免模型在不需要的时候乱创建知识库。

核心代码可以这样理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from app.ragflow.rag_config import _load_ragflow_env
from ragflow_sdk import RAGFlow


# RAGFlow SDK 的入口客户端,后续 Dataset、Chat、Session 操作都从这里发起
api_key, base_url = _load_ragflow_env()
ragflow_client = RAGFlow(api_key=api_key, base_url=base_url)


def create_knowledge_base(knowledge_base_name, description):
"""
通过代码创建 RAGFlow 知识库

知识库名称和描述要写准确:后续聊天助手会绑定知识库,
Agent 又会根据助手描述和关联知识库来判断该问哪个助手。
:param knowledge_base_name: 知识库名称
:param description: 知识库描述
"""
# RAGFlow SDK 中知识库通常对应 Dataset;Chat 会再绑定一个或多个 Dataset 对外提供问答
# embedding_model 需要和 RAGFlow 页面中可用的模型供应商配置保持一致
ds = ragflow_client.create_dataset(
name=knowledge_base_name,
description=description,
embedding_model="text-embedding-v3@Tongyi-Qianwen",
)
print(f"创建知识库成功:{ds},{ds.id}")

7.3 可选扩展:用代码上传文档

创建好知识库后,也可以用代码批量上传文档。

精简版流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import os.path


def upload_file_to_knowledge_base(kb_id, file_paths):
"""
向指定知识库上传一个或多个本地文件

注意:此函数只负责把文件送进 Dataset。上传后仍需要在 RAGFlow 页面或任务中完成解析,
否则文档还没有切片、向量化,后续聊天助手可能检索不到内容。
:param kb_id: RAGFlow 知识库 ID,也就是 Dataset ID
:param file_paths: 本地文件路径列表
"""
# 先根据知识库 ID 查询 Dataset 对象,确认文件会上传到目标知识库
datasets = ragflow_client.list_datasets(id=kb_id, page=1, page_size=10)
dataset = datasets[0]

# RAGFlow upload_documents 接收的是文档字典列表:
# display_name/name 用于页面展示,blob 存放文件二进制内容
document_list = []
for file_path in file_paths:
file_name = os.path.basename(file_path)
with open(file_path, "rb") as f:
blob = f.read()
document_list.append(
{"display_name": file_name, "name": file_name, "blob": blob}
)

# 上传完成后,RAGFlow 侧还要执行解析流程,解析成功后才能被 Chat 检索
dataset.upload_documents(document_list)

这个函数只负责“把文件送进 RAGFlow”。上传后仍然要关注解析状态:如果文档没有完成解析,后面的 create_ask_delete 即使能连上助手,也可能检索不到有效内容。

学习阶段建议先用页面上传和解析,因为页面能清楚看到每个文件的状态;等流程跑通后,再考虑把创建知识库、上传文件做成后台管理功能。


8、工具一:get_assistant_list

项目对应文件路径:deepsearch-agents/app/tools/ragflow_tools.py

8.1 工具作用

get_assistant_list 用来获取 RAGFlow 当前所有聊天助手,以及每个助手关联的知识库。

它解决的是“我应该问谁”的问题。

流程如下:

1
2
3
4
5
连接 RAGFlow
-> ragflow_client.list_chats() 查询所有聊天助手
-> 遍历每个 Chat 对象
-> 读取 chat.kb_names
-> 拼接成模型容易理解的字符串

8.2 核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from langchain_core.tools import tool
from ragflow_sdk import RAGFlow

from app.api.monitor import monitor
from app.ragflow.rag_config import _load_ragflow_env

# 模块级复用 RAGFlow 客户端,避免每次工具调用都重新初始化 SDK 对象
api_key, base_url = _load_ragflow_env()
ragflow_client = RAGFlow(api_key=api_key, base_url=base_url)


# @tool 会把函数签名和 docstring 暴露给 DeepAgents,模型据此决定是否调用以及如何填参
@tool
def get_assistant_list() -> str:
"""
查询 RAGFlow 中有哪些聊天助手,以及每个助手关联了哪些知识库

作用:让模型先了解“哪个助手能回答哪类内部文档问题”,再决定后续要向哪个助手提问。
调用 create_ask_delete 之前,应先调用本工具确认助手名称。
:return: 有助手时返回助手名称、功能介绍、关联知识库;无助手或异常时返回中文提示
"""
# 埋点:工具被调用后,前端可以展示当前正在查询 RAGFlow 助手列表
monitor.report_tool(tool_name="ragflow聊天助手列表查询工具:get_assistant_list")

try:
# list_chats 查询的是 RAGFlow 的 Chat 层,不是 Dataset 层
# Chat 负责对外问答,Dataset 只负责承载文档
chat_list = ragflow_client.list_chats()
if not chat_list:
return "没有任何可用助手"

# 把每个助手的名称、描述和绑定知识库拼成模型容易阅读的路由信息
count_chat_info = ""
for chat in chat_list:
# 不同版本 SDK 字段可能为空,这里用 getattr 兼容没有绑定知识库的助手
dataset_names = getattr(chat, "kb_names", []) or []

count_chat_info += f"助手名称:{chat.name};功能介绍:{chat.description}; 关联的知识库:{'、'.join(dataset_names)} \n"
return count_chat_info
except Exception as e:
return f"查询助手信息异常,无可用助手,异常信息:{str(e)}"

8.3 为什么返回字符串

工具返回给 Agent 的内容越清楚越好。

这里返回字符串,而不是复杂对象,是因为模型最容易直接阅读这种格式:

1
助手名称:电商行业助手;功能介绍:提供电商行业、AI 应用、数字人直播、直播带货相关资料查询; 关联的知识库:电商行业

这样模型能直接判断:

1
2
用户问数字人直播应用场景 -> 应该找电商行业助手
用户问全球投资展望或货币政策 -> 应该找金融行业助手

9、工具二:create_ask_delete

项目对应文件路径:deepsearch-agents/app/tools/ragflow_tools.py

9.1 工具作用

create_ask_delete 完成一次临时问答。

它的名字可以拆开看:

1
2
3
create  创建会话
ask 提问
delete 删除会话

为什么要删除会话?

因为代码调用通常只是为了完成当前用户的一次知识库查询,不需要在 RAGFlow 页面里堆积大量临时会话。问完就删,服务会更干净。

9.2 执行流程

1
2
3
4
5
6
7
8
flowchart TD
Start[输入助手名称和问题] --> Connect[连接 RAGFlow]
Connect --> Find[按名称查找聊天助手]
Find --> Create[创建临时 Session]
Create --> Ask[向 Session 提问]
Ask --> Stream[接收流式回答]
Stream --> Delete[删除临时 Session]
Delete --> Return[返回答案]

9.3 核心代码

下面代码沿用前面定义好的 ragflow_clientmonitor@tool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import json


@tool
def create_ask_delete(chat_name, question) -> str:
"""
向某个 RAGFlow 聊天助手创建临时会话并完成一次提问

注意:调用此工具之前,必须先调用 get_assistant_list,明确可用助手名称和助手能力边界。
:param chat_name: 助手名称,必须来自 get_assistant_list 返回结果
:param question: 本次提问的问题
:return: RAGFlow 返回的回答文本;异常时返回中文错误提示
"""
# 埋点:记录目标助手和问题,便于前端展示当前知识库检索动作
monitor.report_tool(
tool_name="ragflow提问助手工具:create_ask_delete",
args={"chat_name": chat_name, "question": question},
)

try:
# 先按名称找到 Chat 对象;真正提问时还需要在 Chat 下创建 Session
chats = ragflow_client.list_chats(name=chat_name)
use_chat = chats[0]

# 每次工具调用只创建一个临时会话,避免多轮上下文污染当前问题
session = use_chat.create_session(name="temp_session_ask")

# SDK 暂未直接封装当前流式接口,这里通过底层 post 调用 Chat completions API
response = ragflow_client.post(
f"/chats/{use_chat.id}/completions",
{
"messages": [{"role": "user", "content": question}],
"stream": True,
"session_id": session.id,
},
stream=True,
)
result = ""
for line in response.iter_lines(decode_unicode=True):
if not line:
continue

# RAGFlow 流式返回遵循 SSE 风格:每行以 data: 开头,[DONE] 表示结束
line = line.removeprefix("data:").strip()
if line == "[DONE]":
break
data = json.loads(line)
chunk_data = data.get("data")
if not isinstance(chunk_data, dict):
continue
answer = chunk_data.get("answer")
if answer:
# 部分流式片段会返回“截至当前的完整答案”,部分会返回增量内容
# 这里兼容两种情况,尽量避免重复拼接
if answer.startswith(result):
result = answer
elif not result.startswith(answer):
result += answer

# 临时会话只用于本次工具调用,查询结束后删除,避免 RAGFlow 页面堆积无用会话
use_chat.delete_sessions(ids=[session.id])
return result
except Exception as e:
return f"提问失败,错误原因:{str(e)}"

9.4 流式结果为什么要做去重兼容

普通流式输出经常是这样:

1
2
3
第 1 段:你
第 2 段:好
第 3 段:啊

这种情况下要用 += 累加。

但 RAGFlow 的流式片段在不同接口或版本下可能有两种形态。一种是“覆盖型完整内容”:

1
2
3
第 1 段:根据
第 2 段:根据文档
第 3 段:根据文档内容

另一种是“增量型片段”:

1
2
3
第 1 段:根据
第 2 段:文档
第 3 段:内容

所以实际代码用了一个兼容写法:

1
2
3
4
if answer.startswith(result):
result = answer
elif not result.startswith(answer):
result += answer

如果新片段已经包含旧结果,就用新片段覆盖;如果新片段是增量内容,就继续拼接。这样可以尽量避免把“根据”“根据文档”“根据文档内容”重复拼成一长串。

9.5 删除会话放在哪里更稳

上面的代码在拿到回答后删除会话。

如果后续做生产级增强,可以把删除逻辑放进 finally,这样即使提问过程中发生异常,也尽量清理临时会话。

课程阶段先保留当前写法,重点是理解这条链路:

1
查助手 -> 建会话 -> 问问题 -> 收回答 -> 删会话

10、完善 RAGFlow 助手提示词

项目对应文件路径:deepsearch-agents/app/prompt/prompts.yml

RAGFlow 助手配置可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sub_agents:
ragflow:
# RAGFlow 负责企业内部非结构化文档:PDF、白皮书、研报、制度文件等
# description 主要给主智能体做路由判断,system_prompt 约束子智能体先查助手再提问
name: "RAGFlow助手"
description: |
负责查询 RAGFlow 内部知识库中的非结构化文档信息,例如 PDF、白皮书、研报、制度文件、产品资料等。
当任务需要查询企业内部私有文档,而不是互联网公开信息或 MySQL 结构化数据时,使用此助手。
system_prompt: |
你是一个专业的 RAGFlow 知识库助手,负责从企业内部非结构化文档中检索信息。

你掌握的工具包括:
1. get_assistant_list: 查询当前可用的 RAGFlow 聊天助手,以及每个助手关联的知识库信息。
2. create_ask_delete: 向指定 RAGFlow 聊天助手创建临时会话并提问。

工作流程:
1. 必须先调用 get_assistant_list,确认有哪些可用助手、助手描述和关联知识库。
2. 根据用户问题选择最匹配的助手;如果没有合适助手,不要强行提问,应说明 RAGFlow 当前没有可用知识库能回答该问题。
3. 向助手提问时,问题必须围绕用户原始需求,不要发散到无关主题。
4. 对复杂、开放性问题,应采用“先宽后窄”的方式提问:先询问总体结论,再追问关键依据、细节、出处或适用场景。
5. 复杂问题至少提问 3 个不同角度的问题;简单事实型问题可以只提问 1 次。
6. 返回时不要做最终综合结论,但需要按提问轮次保留原始回答、使用的助手名称、问题和重要来源信息,交给后续主智能体汇总。

这里的提示词有三个重点。

10.1 先列表,再提问

RAGFlow 助手必须先知道有哪些聊天助手。

所以提示词里明确要求:

1
必须先调用 get_assistant_list

10.2 不要乱问

如果助手描述里写的是“电商行业助手”,就不要拿它问货币政策问题;如果助手描述里写的是“金融行业助手”,就不要拿它问数字人直播问题。

提示词里强调:

1
如果没有合适助手,不要强行提问

这可以减少无效检索。

10.3 不要过早总结

RAGFlow 子智能体查到的是内部资料。它应该按提问轮次保留原始回答、使用的助手名称、问题和重要来源信息,而不是自己先过度总结。

否则可能出现“传话效应”:子智能体总结时丢掉了细节,主智能体后面就拿不到完整依据。


11、组装 knowledge_base_agent

项目对应文件路径:deepsearch-agents/app/agent/subagents/knowledge_base_agent.py

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"""
RAGFlow 知识库子智能体配置模块

将 app/prompt/prompts.yml 中的 ragflow 配置与 RAGFlow 工具组装成
DeepAgents 可识别的字典式子智能体。主智能体后续会根据 description
决定是否把企业内部非结构化文档查询任务分派给它。
"""

from app.agent.prompts import sub_agents_content
from app.tools.ragflow_tools import create_ask_delete, get_assistant_list


# RAGFlow 子智能体处理内部非结构化文档,与网络搜索助手、数据库查询助手形成互补
# 它遵循“先查助手列表 -> 再向指定助手提问”的工作顺序
# tools 列表声明该子智能体可以发现知识库助手,并发起一次性临时会话查询
knowledge_base_agent = {
"name": sub_agents_content["ragflow"]["name"],
"description": sub_agents_content["ragflow"]["description"],
"system_prompt": sub_agents_content["ragflow"]["system_prompt"],
"tools": [get_assistant_list, create_ask_delete],
}

到这里,三个子智能体就都准备好了:

子智能体变量名 对应助手 工具数量
network_search_agent 网络搜索助手 1 个
database_query_agent 数据库查询助手 3 个
knowledge_base_agent RAGFlow 助手 2 个

11.1 本章代码文件关系

本章涉及的代码关系可以这样看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app/prompt/prompts.yml
-> app/agent/prompts.py
-> sub_agents_content

app/ragflow/rag_config.py
-> _load_ragflow_env

app/ragflow/knowledge_demo.py
-> create_knowledge_base
-> upload_file_to_knowledge_base

app/tools/ragflow_tools.py
-> get_assistant_list
-> create_ask_delete

app/agent/subagents/knowledge_base_agent.py
-> name / description / system_prompt
-> tools=[get_assistant_list, create_ask_delete]

prompts.yml 管提示词和描述,rag_config.py 管连接配置,knowledge_demo.py 演示知识库和文件操作,ragflow_tools.py 管真正注册给 Agent 的工具,knowledge_base_agent.py 只负责把配置和工具组装起来。

这也是本项目一直保持的写法:

1
2
3
配置归配置
工具归工具
智能体组装归智能体组装

12、测试与排查

在主智能体接入前,可以先单独测试两个工具。

12.1 测试助手列表

1
2
if __name__ == "__main__":
print(get_assistant_list.invoke({}))

期望看到类似结果:

1
2
助手名称:电商行业助手;功能介绍:提供电商行业、AI 应用、数字人直播、直播带货相关资料查询; 关联的知识库:电商行业
助手名称:金融行业助手;功能介绍:提供货币政策、全球投资展望、投资者情绪等金融行业资料查询; 关联的知识库:金融行业

如果返回“没有任何可用助手”,优先回到 RAGFlow 页面检查:

  • 是否真的创建了聊天助手;
  • 聊天助手是否绑定了知识库;
  • .env 中的 RAGFLOW_API_URLRAGFLOW_API_KEY 是否正确。

12.2 测试临时会话提问

1
2
3
4
5
6
7
8
if __name__ == "__main__":
result = create_ask_delete.invoke(
{
"chat_name": "电商行业助手",
"question": "如果我是一个电商平台运营负责人,应该怎样制定 2026 年 AI 应用路线图?",
}
)
print(result)

如果页面里能回答,但代码里回答失败,重点检查:

  • API Key 是否属于当前 RAGFlow 服务;
  • chat_name 是否和页面里助手名称完全一致;
  • RAGFlow 服务地址是否可以从当前运行代码的机器访问;
  • 文档是否已经解析完成。

12.3 实际运行输出怎么看

运行知识库示例:

1
uv run -m app.ragflow.knowledge_demo

如果创建成功,终端会打印类似下面的内容。实际字段很多,不需要逐个背下来,先看几个关键字段就够了:

1
2
3
4
5
6
7
8
9
创建知识库成功:{
'document_count': 0,
'chunk_count': 0,
'embedding_model': 'text-embedding-v3@Tongyi-Qianwen',
'llm_id': 'qwen-flash@Tongyi-Qianwen',
'id': 'ab8e7870510c11f18369b59a2b1324d3',
'name': '乌萨奇的知识库',
...
},ab8e7870510c11f18369b59a2b1324d3

这段输出重点看四件事:

输出字段 说明
创建知识库成功 说明 API Key、服务地址和 RAGFlow SDK 初始化都已经跑通
id 这是 RAGFlow 知识库的 Dataset ID,后续上传文件、查询知识库时会用到
embedding_model / llm_id 说明本次知识库使用了哪个 Embedding 和 LLM,要和页面模型配置一致
document_count: 0 / chunk_count: 0 说明知识库刚创建,还没有上传和解析文档;这不是报错

再运行 RAGFlow 工具测试:

1
uv run -m app.tools.ragflow_tools

如果终端先出现下面这行,说明工具埋点已经触发:

1
[Monitor:tool_start] 开始执行工具: ragflow提问助手工具:create_ask_delete

随后如果能返回一段带编号、带引用标记的回答,例如:

1
2
3
4
5
6
作为电商平台运营负责人,制定 2026 年 AI 应用路线图需结合平台战略定位、技术成熟度、合规要求及商家生态需求。

1. 明确平台战略定位与开放模式 ... [ID:0]
2. 构建技术驱动的 AI 能力矩阵 ... [ID:3][ID:4]
3. 强化合规管理与风险控制机制 ... [ID:6]
...

就说明这条链路已经跑通:

1
2
3
4
5
6
读取 .env
-> 连接 RAGFlow
-> 找到指定聊天助手
-> 创建临时会话
-> 基于知识库生成回答
-> 返回引用来源

这里最重要的不是答案写得多漂亮,而是看到三个信号:

信号 代表什么
出现 [Monitor:tool_start] monitor.report_tool() 正常执行,后续前端可以展示工具进度
回答里有 [ID:0][ID:3] 等引用 RAGFlow 检索到了知识库片段,不只是模型凭常识回答
回答能围绕“电商平台 AI 应用路线图”展开 chat_name 选对了,电商行业助手和知识库绑定基本正常

如果这一步能成功,说明 RAGFlow 服务、API Key、助手名称、文档解析和流式读取大体都没问题。后续接入主智能体时,主要就看主智能体是否能把内部文档查询任务正确分派给 knowledge_base_agent


13、执行过程与分工回顾

13.1 RAGFlow 子智能体的执行过程

把本章内容串起来,RAGFlow 子智能体的完整执行过程是:

1
2
3
4
5
6
7
8
9
flowchart TD
Start[主智能体分派内部知识查询任务] --> List[调用 get_assistant_list]
List --> Select[根据助手描述选择目标助手]
Select --> Plan[规划提问角度]
Plan --> Ask1[调用 create_ask_delete 提问 1]
Ask1 --> Ask2[必要时继续提问 2]
Ask2 --> Ask3[必要时继续提问 3]
Ask3 --> Raw[保留原始检索信息]
Raw --> End[返回给主智能体]

这里的“至少三个问题”不是硬写在工具里,而是写在子智能体提示词里,让模型根据任务主动多角度查询。


13.2 三个子智能体的分工回顾

到本章为止,项目的三个专家助手已经完整了。

用户需求类型 应该调用的助手 原因
最新公开信息、外部资料 网络搜索助手 需要查互联网
药品库存、销售、结构化数据 数据库查询助手 需要查 MySQL
内部制度、说明书、年报文档 RAGFlow 助手 需要查企业私有知识库

主智能体后续要做的,就是根据用户任务在这三个助手之间调度,并把返回的信息整理成最终答案或文件。


本章小结:

本章把 RAGFlow 的服务侧准备和 DeepAgents 接入放在了一起。

请重点记住四层结构:

1
2
3
4
RAGFlow 服务
-> 知识库 Dataset
-> 聊天助手 Chat
-> 会话 Session

以及一条完整使用链路:

1
2
3
4
5
6
7
配模型
-> 建知识库
-> 上传并解析文件
-> 创建聊天助手并绑定知识库
-> 页面测试
-> 生成 API Key
-> 后端代码调用

代码侧重点记住两个工具:

1
2
3
4
5
get_assistant_list
-> 让模型知道当前 RAGFlow 有哪些助手

create_ask_delete
-> 让模型向指定助手创建临时会话、提问、删除会话

到这里,「深度研搜」项目的三个子智能体都已经具备。后续主智能体会把它们统一注册进去,根据任务自动选择网络搜索、数据库查询或 RAGFlow 知识库查询。