主要技术:FastAPI、DeepAgents、LangGraph、MySQL、RAGFlow、WebSocket
项目目录
项目简介
深度研搜是一套基于 Python、DeepAgents 和 FastAPI 的多智能体研究助手。它可以按任务需要搜索公开资料、查询 MySQL 数据、检索 RAGFlow 知识库和读取上传附件,将结果整理成回答或可下载的 Markdown、PDF 报告。
用户可以提出一个行业资料调研任务,也可以查询教学库中的药品库存,筛选库存大于 100 的药品并生成报告。主智能体负责分派任务与汇总,网络、数据库和知识库助手分别获取信息;页面展示执行过程、任务状态和生成文件。仓库提供前后端代码、图文教程与对应章节分支。
项目亮点
- 主智能体与专家助手分工: 基于
DeepAgents组装主智能体与网络、数据库、知识库三个助手,各助手使用对应工具完成子任务,再将结果交回主智能体汇总,避免把所有来源的中间过程混在一起。 - 从附件分析到文件交付: 主智能体直接使用文件工具,读取文本文档、文本型
PDF、Word正文和Excel预览,将整理结果写入Markdown,并按需转换为PDF。页面通过文件接口列出和下载实际生成的产物。 - 会话上下文与目录归档: 通过
thread_id区分同一进程中的会话上下文,按会话组织附件与报告目录;使用ContextVar向深层工具传递当前目录和任务标识,执行结束后恢复上下文。 - 后台执行与实时进度:
FastAPI在后台运行研究任务,monitor统一上报工具调用、助手调用、结果与异常事件,经WebSocket推送到对应会话;页面支持请求取消执行中的任务。
技术架构
项目分为研究执行和应用交互两条主线:
研究执行: DeepAgents 负责主从任务调度,Tavily、MySQL 和 RAGFlow 分别提供网络、结构化数据和知识库信息。附件读取、Markdown 写入与 PDF 转换由主智能体直接调用。
应用交互: React 页面提交任务和附件,FastAPI 管理后台任务及上传、文件列表、下载接口,WebSocket 将执行事件与最终结果推送到当前页面。
| 代码位置 | 主要职责 |
|---|---|
app/agent/ |
组装主智能体与三个专家助手,配置模型、提示词和会话上下文 |
app/tools/ |
封装搜索、数据库、知识库、附件读取与报告生成工具 |
app/api/ |
提供任务接口,通过 context 与 monitor 传递上下文并推送事件 |
frontend/ |
展示对话、助手调用、工具事件和输出文件 |
上传附件和生成报告按会话保存在本地目录。examples/ 另外提供人工审批、Skills 和不同存储后端的独立示例,可配合章节教程阅读。
如何写进简历
研报通 · 制造业产品情报研究助手 | 制造业场景示例
项目描述:面向制造企业产品研究人员的情报研究助手,汇总网页、业务数据和内部资料,结合上传附件比较产品参数、整理交付动态,生成可下载的研究报告。
技术栈:Python、FastAPI、DeepAgents、LangGraph、MySQL、RAGFlow、WebSocket
主要工作:
- DeepAgents 多助手任务编排:基于
DeepAgents将网页搜索、业务查询和内部文档检索封装为子智能体,通过task委派型号与参数研究;主智能体保留研究目标并汇总子任务结果,分开承载检索细节和报告主线的上下文。 - 会话状态与工具上下文传递:以
thread_id关联InMemorySaver的进程内状态,按会话组织附件与报告目录;通过ContextVar向深层文件工具传递当前目录和会话标识,并在任务结束、异常或取消时恢复绑定,避免上下文残留。 - 异步执行与跨线程事件推送:基于
FastAPI与asyncio在后台执行研究,由monitor汇总助手调用、工具与结果事件;同步工具产生的事件通过线程安全调度回到连接所属事件循环,再以WebSocket定向返回当前会话。 - 产品参数归一与报告交付:将不同来源的型号别名、参数单位和测试条件整理为对照记录,同一参数冲突时保留版本及来源;结合
RAGFlow检索与附件内容生成Markdown,再转换为PDF交付研究结果。
药商参谋 · 医药经营数据研究助手 | 医药流通场景示例
项目描述:面向医药流通企业的经营分析助手,围绕药品库存、批次效期及业务台账开展查询分析,生成经营报告。
技术栈:Python、FastAPI、DeepAgents、LangGraph、MySQL、RAGFlow、pandas、WebSocket
主要工作:
- 数据库子智能体与聚合查询:将库表发现、字段预览和
SQL执行封装到数据库子智能体,由主智能体通过task下发药品、批次与效期目标;子智能体基于真实结构组织聚合查询,主控接收结果后继续研究与报告生成。 - 异构附件解析与台账计算:以
pypdf、python-docx读取文本型附件,利用pandas返回表格结构与摘要,控制明细进入模型的规模;针对库存台账扩展全量计算工具,按药品、批次和仓库完成归并,将差异摘要与明细文件分开交付。 - RAGFlow 工具封装与会话收敛:封装
RAGFlow SDK的助手发现和临时会话调用,将业务问题路由到绑定知识库的助手;兼容流式接口的增量与累计答案,查询结束后清理临时会话,减少外部问答历史对本次研究的干扰。 - 会话文件与报告生成衔接:按
session_id将上传文件复制到当前任务目录,通过ContextVar让附件读取、Markdown写入与PDF转换共享工作路径;结合文件列表及下载接口返回产物,保留查询结果与台账文件的关联。
面试题与回答
3 题你的项目采用单 Agent 还是多 Agent,分别承担什么职责?
展开收起
核心回答
下面以一个整合公开资料、企业数据库和内部知识库的研搜助手为参考示例。
项目采用主从式多 Agent:一个主智能体统一面对用户,配置三个自定义业务专家子智能体,按信息来源分工。后端使用
Python和FastAPI,DeepAgents负责主从调度,底层由LangGraph承接执行状态;大模型负责理解问题、选择调用和整理结果,Tavily、MySQL、RAGFlow分别提供真实信息。- 主智能体:理解目标、拆分任务、选择专家,拿到结果后判断是否还缺信息,最后汇总回答。读取上传文件、生成
Markdown和转换PDF由它调用文件工具完成。 - 网络搜索子智能体:通过
Tavily搜索公开网页,补充最新公开信息。 - 数据库查询子智能体:先确认表和字段,再生成、执行
SQL,获取结构化业务数据。 RAGFlow子智能体:选择合适的知识库助手,查询企业内部的非结构化文档。
例如新能源团队研究某类电池的技术路线与量产进展,主智能体把政策和竞品公告查询交给网络助手,把相关业务统计交给数据库助手,把内部产品资料查询交给
RAGFlow助手;结果回来后统一解释,再按要求生成文件。拆开的主要原因是三类数据源的工具、查询步骤和上下文不同,能把各领域的细节留在子任务里;代价是多了调度、信息交接和模型调用开销。若需求只有单一知识库问答,一个 Agent 配检索工具通常就足够。展开说明
选型的依据是任务边界,而不是模型数量
这个例子按信息来源拆分,因为“查公开网页”“查业务表”“查内部文档”的处理过程不同。数据库助手需要表结构和查询结果,网络助手需要网页与搜索记录,把这些原始材料都放进主智能体会挤占上下文,也容易让它混淆工具用途。
子智能体在自己的上下文中选择工具、读取反馈并完成局部任务,主智能体主要接收结果。它和“一个 Agent 挂很多工具”的区别,在于存在独立的任务执行边界;给几次模型调用换上角色名称,并不能体现这种分工。
多 Agent 也不要求多种模型。这个参考实现通过统一配置创建模型,三个业务子智能体没有覆盖
model,因此沿用主智能体的模型。职责差异来自提示词、工具集合和任务上下文。需要进一步控制成本时,可以评估给不同角色配置不同模型,但要比较工具调用、结果质量、延迟和费用,而不是默认小模型一定适合 Worker。一次调用如何完成交接
主智能体根据子智能体的能力描述,利用框架的
task工具下发子目标和必要背景。例如给数据库助手的任务应包含统计对象、时间范围和指标口径;给知识库助手的任务应说明要核实哪项产品或制度信息。子智能体执行后把结果交回主智能体,由主智能体决定补查还是汇总,子智能体不分别给用户输出三份最终答案。数据库查询与文档查询只有在输入已经齐备、互不依赖时才适合并行。如果要先查出产品型号,再检索对应内部资料,就必须先完成前一步。接口采用异步执行、流式推送进度,也不代表每个子任务都会自动并行。
角色分工与权限控制要分别落实
配置不同工具集合可以缩小角色的业务范围,但实际权限仍由工具和数据源执行。例如数据库场景应使用只读账号、查询校验、行数和执行时间限制,不能把“只查询”的提示词当作数据库权限。该参考代码的自定义
SQL工具直接执行传入语句,进一步加固属于后续工程工作。示例使用
thread_id关联会话,用InMemorySaver保存进程内状态。它有助于同一会话持续执行;若要在服务重启后恢复任务,还需要持久化检查点和产物记录。WebSocket用于推送进度,不承担这些状态的持久化。常见追问
为什么不用一个 Agent,把三个数据源都做成工具?
完全可以作为简单版本。若请求主要落在同一领域,工具选择稳定,单 Agent 的成本和排障负担更小。拆分的理由应是领域上下文、工具选择或独立执行已有明确需求;用相同任务比较成功率、来源准确性、延迟和 Token 消耗,才能判断拆分是否值得。
主智能体和子智能体用相同模型,为什么还算多 Agent?
模型是能力底座,Agent 是围绕目标、指令、上下文和工具组织起来的执行单元。同一个模型可以驱动多个独立子任务;反过来,先后调用两个不同模型,也不自动构成多 Agent 协作。
子智能体返回的结果互相矛盾怎么办?
先比较数据来源、时间范围和统计口径,判断是否确实在回答同一件事。例如数据库返回实际销售记录,内部文档描述计划销量,两者不能直接互相覆盖。主智能体应保留各自依据,补查能验证分歧的证据,仍无法确认时在最终结果中说明缺口。
对应教程
- 深度研搜:项目总览与工程初始化:查看主智能体、三个业务专家和信息来源的对应关系,以及从查询到文件交付的完整流程。
- 深度研搜:主智能体搭建与异步执行:查看文件工具为何由主智能体持有,以及主从组装、会话标识和流式执行如何连接。
- 深度研搜配套源码:主智能体组装:对照
tools、subagents和checkpointer的实际配置,区分业务角色与框架提供的运行能力。
参考资料
- 国内:JavaGuide:多 Agent 协作系统设计:借鉴文中对独立执行边界的划分,解释正文中多 Agent 与多阶段 Prompt Chain 的区别及拆分的代价。
- 国内:zero2Agent:多智能体协作:角色分工、通信机制与冲突仲裁:参考角色分工与结构化交付的设计,说明正文中子 Agent 如何接收必要背景、交付结果,以及单 Agent 与多 Agent 的选型条件。
- 国内:Datawhale:第六章 框架开发实践:借鉴软件开发团队与图式编排案例,区分正文中的角色职责设计和执行顺序安排。
- 国内:zero2Agent:记忆与上下文:参考运行状态、模型上下文和记忆的分工,解释正文中会话标识、检查点与文件产物为什么需要分别管理。
- 国外:LangChain:Deep Agents Subagents:依据子智能体委派、上下文隔离及模型配置约定,解释正文中不同角色为何可以使用同一模型。
- 国外:Anthropic:How we built our multi-agent research system:借鉴主研究者拆分方向、子代理独立取证后汇总的实践,说明正文中并行研究的适用条件与协调成本。
- 主智能体:理解目标、拆分任务、选择专家,拿到结果后判断是否还缺信息,最后汇总回答。读取上传文件、生成
多 Agent 如何隔离上下文和状态?
展开收起
核心回答
深度研搜把子任务的模型消息、会话检查点、运行上下文和文件目录分别管理。
DeepAgents通过task工具委派网络、数据库或知识库子任务,子智能体独立完成工具交互,再把结果交回主智能体。应用以session_id作为thread_id定位会话状态,同时为该会话建立附件与报告目录,用ContextVar把当前目录和会话标识传给深层工具及事件推送。这样,共用一套 Agent 定义也能让不同会话使用各自的历史、附件和产物。同一会话的上下文可以继续使用;其中
InMemorySaver保存的是进程内检查点,磁盘文件保存的是附件和报告,两者不能互相替代。主子智能体怎样交换上下文
主智能体根据专家的
description选择委派对象,把子目标和必要背景传给task。例如数据库助手需要统计对象、时间范围与口径,知识库助手需要待核实的问题;网络检索的完整过程不必因此广播给数据库助手。在本项目使用的
DeepAgents 0.5.7中,委派会用任务描述建立子智能体的新消息输入,完成后将其最终消息作为ToolMessage交回主智能体。因此主智能体可以汇总结果,而不必接收每轮工具对话。不过,独立消息不等于所有状态都独立:框架仍会传递或合并部分非消息字段,共享内容要按具体字段管理。该版本的委派实现可以看到输入过滤和结果回写。会话标识怎样串起历史、附件和报告
run_deep_agent()为一次会话完成四项关联:- 定位历史。 把
session_id放入configurable.thread_id,交给配置了InMemorySaver的主智能体。同一标识用于延续该会话,不是操作系统线程编号。 - 整理文件。 创建
app/output/session_{session_id},把app/updated/session_{session_id}中的上传文件复制进去,并把文件名和工作目录加入本轮输入。模型仍需调用read_file_content才能读取附件内容。 - 传递运行上下文。 将目录与会话标识写入两个
ContextVar。文件工具读取目录,monitor读取会话标识,将执行事件发送到对应前端连接;模型不需要在每个工具参数里重新填写这两个值。 - 恢复绑定。 流式执行结束、取消或异常后,在
finally中用设置时取得的 token 恢复上下文,结束本轮绑定的作用域。
主执行入口负责这些关联,上下文模块负责设置、读取与恢复绑定。
例如两个会话都上传
材料.pdf并生成报告.md,各自的目录提供文件归属,thread_id提供历史归属,当前运行上下文让文件工具找到对应目录。只修改报告文件名,解决不了读取错会话历史或把进度发给另一连接的问题。这些机制各自解决到哪一层
ContextVar避免用一个可变全局变量保存“当前会话”,但它不保存长期记忆,也不会自动深拷贝其中的可变对象。会话目录便于组织附件和交付物,也便于继续读取文件;服务重启后能否恢复 Agent 状态,还取决于检查点是否持久化。文件访问权限是另一层。本项目的
resolve_path()会处理相对路径和重复目录,但仍允许目录外的真实绝对路径,因此当前工作目录不能当作文件沙箱。若用于多用户服务,需要在资源入口核验身份和归属,并在路径解析后检查允许范围;这些约束由程序执行。路径解析源码展示了现有行为。对应教程
- 深度研搜:主智能体搭建与异步执行:回看主从组装、上传文件复制、会话检查点和运行上下文的设置与恢复。
参考资料
- 国内:JavaGuide:多 Agent 协作系统设计:采用独立任务、显式交接和共享结果的分工,解释主智能体为何不需要广播全部工具历史。
- 国内:zero2Agent:多智能体协作:借鉴公共约束、子任务过程与交付结果的分层,说明委派时应保留必要条件。
- 国内:WeThinkIn:记忆与上下文工程:参考记忆主体、读取作用域和派生副本,区分文件存在、状态已保存与模型实际取得的内容。
- 国内:zero2Agent:记忆与上下文:参考异步任务污染及会话文件命名空间,解释运行上下文、检查点和磁盘产物各自的职责。
- 国外:LangChain:Deep Agents Subagents:采用委派任务、独立工具过程和精简结果交付的说明;本例具体的状态传递行为以
0.5.7实现为准。 - 国外:Python 3.12:contextvars:依据异步上下文变量、token 恢复与浅复制机制,解释当前会话绑定如何传递,以及它与持久化和资源权限的区别。
- 定位历史。 把
向页面推送任务进度时,后端选择 WebSocket 还是 SSE,为什么?
展开收起
核心回答
深度研搜当前采用
HTTP提交和取消任务,WebSocket推送执行事件。研究过程可能调用网络搜索、数据库和知识库,页面需要看到正在使用哪个助手、哪个工具,以及最终结果。后端用FastAPI承接请求,借助asyncio后台任务执行研搜,再由统一的monitor把事件定向发给当前会话。这里选择
WebSocket,实现上是把会话内的进度和结果集中到一条长连接。当前业务消息主要从服务端发往页面,接收端循环只处理心跳,没有通过这条连接提交或取消任务,因此SSE也足以承载这类单向通知。 不能把已有选型解释成双向业务交互的刚性要求。一次进度怎样到达页面
- 先建立会话连接。 页面连接
/ws/{thread_id},ConnectionManager保存thread_id到连接对象的映射。随后提交POST /api/task,带上同一会话标识和研究问题。 - 把长任务交给后台。 接口通过
asyncio.create_task()调度run_deep_agent(),返回started和thread_id,不等待最终报告。任务运行时把会话标识和输出目录写入ContextVar,让深层工具能读取本次执行的上下文。 - 统一上报业务事件。 工具调用、子智能体调用和结果分别经过
report_tool()、report_assistant()、report_task_result()。monitor组装包含event、message、data和时间戳的monitor_event,读取当前thread_id,再找到对应连接发送。 - 回到连接所属的事件循环发送。 如果上报发生在连接所在的循环,使用
create_task();否则通过asyncio.run_coroutine_threadsafe()投递回该循环。页面按事件类型更新执行记录、最终回答或错误状态。
这套分工让工具只负责上报“发生了什么”,不需要自己保存或操作页面连接。任务接口和事件发送分别负责调度和通知。
推送了哪些内容
当前事件包括
session_created、tool_start、assistant_call、task_result、task_cancelled和error。例如调用网络搜索助手时展示助手名称,调用文件工具时展示工具名称,收到task_result后展示返回的完整结果。虽然主智能体使用
astream()执行,现有代码解析的是节点状态及其中的消息,再生成这些阶段事件,没有把模型 token 增量直接转发给页面。具体分支见主智能体执行。心跳、重连和取消分别解决什么
前端定时发送文本
ping,服务端返回pong;断开后,前端会用同一thread_id重新连接。连接管理器删除旧连接前还会比较对象身份,避免旧连接稍后触发断开,把已经登记的新连接一起删除。但当前连接表和后台任务表都在进程内,没有持久事件日志,也没有按事件游标补发的接口。断线期间已产生的通知不会因为重连自动恢复,丢失最终结果事件时,页面也不能仅凭连接恢复就判断任务已完成。若要补齐,应增加任务状态查询或历史事件补发,而不是只缩短重连间隔。
取消通过
POST /api/task/{thread_id}/cancel向后台任务发出协作式取消请求,关闭页面连接不会自动执行该接口。另一个同会话请求会先对旧任务调用cancel(),随后创建新任务,未等待旧任务结束,因此不能据此保证两次任务严格互斥。这些行为独立于WebSocket或SSE的选择。- 先建立会话连接。 页面连接
