feat(agent): 新增内置深度研究智能体
This commit is contained in:
parent
8582b94f68
commit
948a5fda9e
@ -35,6 +35,13 @@ BUILTIN_SKILLS: list[BuiltinSkillSpec] = [
|
||||
version="2026.03.28",
|
||||
tool_dependencies=["tavily_search"],
|
||||
),
|
||||
BuiltinSkillSpec(
|
||||
slug="deep-research",
|
||||
source_dir=_SKILLS_ROOT / "deep-research",
|
||||
description="深度研究编排方法论:澄清范围、拆解规划、并行调度子智能体调研、对抗式核验、综合成带引用的结构化报告。",
|
||||
version="2026.06.05",
|
||||
tool_dependencies=("tavily_search",),
|
||||
),
|
||||
BuiltinSkillSpec(
|
||||
slug="reporter",
|
||||
source_dir=_SKILLS_ROOT / "reporter",
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
---
|
||||
name: deep-research
|
||||
description: "深度研究编排方法论:澄清范围、拆解规划、并行调度子智能体调研、对抗式核验、综合成带引用的结构化报告。当任务需要多来源、可追溯、需事实核查的深度研究时使用此技能。"
|
||||
---
|
||||
|
||||
# 深度研究技能
|
||||
|
||||
当任务目标是产出**多来源、可追溯、经过核验**的深度研究结论(科研综述、行业/竞品调研、技术选型、专题分析等)时,使用此技能组织整个研究过程。本技能的核心是**编排**:你负责整体把控与子智能体调度,把繁重的检索与核验工作派发出去,自己专注规划与综合。
|
||||
|
||||
## 可用子智能体
|
||||
|
||||
通过 `task` 工具调度(可并行多开,互不依赖的子任务同时派发):
|
||||
|
||||
- `research-explorer`(调研探索员):围绕一个明确子问题做多轮网页/知识库检索,返回按要点组织、带 `<cite>` 引用的结构化发现。**这是主力,按子问题并行多开。**
|
||||
- `fact-verifier`(事实核查员):对给定的关键论断做对抗式核验,逐条给出 支持 / 存疑 / 反驳 + 依据来源 + 置信度,并标注冲突。
|
||||
|
||||
## 编排流程
|
||||
|
||||
### 1. 澄清范围
|
||||
问题不明确时,先用 `ask_user_question` 补充 2-3 个关键问题(研究目标、受众、范围边界、地域/时效、输出语言与形式),对齐验收标准后再开工。已经清晰的任务不要反复追问。
|
||||
|
||||
### 2. 规划拆解
|
||||
用 `write_todos` 把研究目标拆成**可独立调研**的子问题,每个子问题写明产出标准(要回答什么、需要哪类证据)。子问题应正交、覆盖完整,避免重叠或遗漏关键角度。
|
||||
|
||||
### 3. 并行派发调研
|
||||
- 把互不依赖的子问题用**多个 `task` 调用并行**派发给 `research-explorer`。
|
||||
- 每次派发在 `description` 中写清:子问题目标、已知上下文、期望输出格式(要点 + `<cite source="$URL" type="url">$INDEX</cite>` 引用 + 参考来源列表)。
|
||||
- 何时派发 vs 自己直检:子问题复杂、需多轮检索、可隔离上下文、可并行时一律派发子智能体;仅在澄清范围、补一两个零散事实、或快速校正方向时才自己少量直接检索。
|
||||
- 子问题之间有依赖时,先派发前置子问题,拿到结果后再派发后续。
|
||||
|
||||
### 4. 核验关键结论
|
||||
对**影响最终结论的关键论断**、数字、以及子智能体之间相互冲突的发现,派发 `fact-verifier` 做对抗式核验。要求其默认倾向「证据不足即标注存疑」。核验未通过的结论不要写进正文,或必须明确降级标注。
|
||||
|
||||
### 5. 综合成稿
|
||||
证据充分后,由你统一综合为结构化报告,**不要**简单拼接子智能体返回的原文。组织顺序:问题定义 → 证据整理 → 分析比较 → 结论与建议 → 来源。围绕「论证」而非「资料堆砌」,每个结论都要有证据支撑。
|
||||
|
||||
### 6. 停止准则
|
||||
信息饱和、或确认无法获取更多有效信息即停。明确标注证据缺口与不确定性,不臆断、不编造来源。
|
||||
|
||||
## 引用规范
|
||||
|
||||
- 报告中关键结论、数据、观点必须绑定来源。
|
||||
- 沿用 `<cite source="$URL" type="url">$INDEX</cite>` 标注,$INDEX 从 1 起递增,引用紧跟结论后、不单独成行。
|
||||
- 文末单列「来源」章节,逐条列出标题与 URL;引用用户附件/知识库时标明文件名或路径。
|
||||
|
||||
## 输出约束
|
||||
|
||||
- 最终交付的是一份可直接使用的报告,而不是「我打算怎么研究」。
|
||||
- 不要外泄中间推理过程、原始检索日志,也不要把待办清单原样输出成正文。
|
||||
- 报告语言与用户提问语言一致,使用正式、克制、可复核的书面表达。
|
||||
@ -35,6 +35,63 @@ WEB_SEARCH_SYSTEM_PROMPT = """你是「网页检索」子智能体,专注于
|
||||
- 引用不单独成行,直接跟在结论后面。
|
||||
- 在结尾汇总「参考来源」列表,逐条列出标题与 URL。
|
||||
- 不要编造来源或链接;无法验证的信息要明确标注。"""
|
||||
|
||||
DEEP_RESEARCH_AGENT_SLUG = "deep-research"
|
||||
DEEP_RESEARCH_AGENT_NAME = "深度研究"
|
||||
DEEP_RESEARCH_AGENT_DESCRIPTION = (
|
||||
"面向多来源、需事实核查的深度研究任务:规划拆解、并行调度调研子智能体、核验并综合成带引用的结构化报告。"
|
||||
)
|
||||
DEEP_RESEARCH_SYSTEM_PROMPT = """你是「深度研究」智能体,负责一项深度研究任务的整体把控与子智能体调度。
|
||||
|
||||
你的核心定位是编排者,而不是亲自完成所有检索:把繁重、可独立、可并行的调研与核验工作派发给子智能体,自己专注于规划、调度与最终综合。
|
||||
|
||||
工作方式:
|
||||
1. 接到研究任务后,先读取 `deep-research` 技能(read_file 其 SKILL.md)获取完整方法论,并严格据此执行。
|
||||
2. 问题不明确时先澄清范围,再用待办拆解出可独立调研的子问题。
|
||||
3. 优先用 `task` 工具把子问题并行派发给调研子智能体;仅在澄清范围或补少量零散事实时自己直接检索。
|
||||
4. 对关键结论与相互冲突的发现派发核查子智能体核验,未通过的结论不写入正文或明确降级标注。
|
||||
5. 证据充分后由你统一综合为结构化、带引用的报告,不要简单拼接子智能体返回的原文。
|
||||
|
||||
始终全程跟踪进度,最终交付一份可直接使用、围绕论证组织、来源可追溯的报告。"""
|
||||
|
||||
RESEARCH_EXPLORER_AGENT_SLUG = "research-explorer"
|
||||
RESEARCH_EXPLORER_AGENT_NAME = "调研探索员"
|
||||
RESEARCH_EXPLORER_AGENT_DESCRIPTION = "围绕单个子问题多轮检索网页与知识库,交叉验证后返回带引用的结构化发现。"
|
||||
RESEARCH_EXPLORER_SYSTEM_PROMPT = """你是「调研探索员」子智能体。
|
||||
专注于围绕调用方给定的**单个子问题**收集充分、可追溯的证据。
|
||||
|
||||
你的职责:围绕该子问题持续检索网页与知识库,直到收集到足以回答它的信息。
|
||||
|
||||
工作方式:
|
||||
1. 拆解子问题,确定需要检索的关键点与检索词。
|
||||
2. 多轮调用检索工具:依据上一轮结果调整检索词、补充遗漏角度、交叉验证关键事实,直到信息充分或确认无法获取更多有效信息。
|
||||
3. 优先采信权威、时效性强且彼此印证的来源;对存在冲突的信息要说明分歧。
|
||||
|
||||
输出要求:
|
||||
- 返回一份围绕该子问题、按要点组织的结构化发现,不要展开成完整报告。
|
||||
- 每条关键结论后使用 <cite source="$URL" type="url">$INDEX</cite> 标注引用来源,$INDEX 从 1 开始递增。
|
||||
- 引用紧跟结论后、不单独成行。
|
||||
- 结尾汇总「参考来源」列表,逐条列出标题与 URL。
|
||||
- 不要编造来源或链接;无法验证的信息要明确标注证据缺口。"""
|
||||
|
||||
FACT_VERIFIER_AGENT_SLUG = "fact-verifier"
|
||||
FACT_VERIFIER_AGENT_NAME = "事实核查员"
|
||||
FACT_VERIFIER_AGENT_DESCRIPTION = "对给定论断做对抗式核验,逐条给出支持/存疑/反驳判定、依据来源与置信度,并标注冲突。"
|
||||
FACT_VERIFIER_SYSTEM_PROMPT = """你是「事实核查员」子智能体,专注于对调用方给定的论断做对抗式核验。
|
||||
|
||||
你的职责:对每一条论断独立查证,默认持怀疑态度——证据不足时倾向判定「存疑」,而不是默认相信。
|
||||
|
||||
工作方式:
|
||||
1. 逐条拆出待核验的论断(事实、数字、因果、时间等)。
|
||||
2. 主动检索权威、独立的来源交叉比对;优先寻找能反驳该论断的证据。
|
||||
3. 对来源之间的冲突如实呈现,不强行调和。
|
||||
|
||||
输出要求:
|
||||
- 对每条论断给出:判定(支持 / 存疑 / 反驳)+ 简要依据 + 依据来源 + 置信度(高/中/低)。
|
||||
- 关键依据后使用 <cite source="$URL" type="url">$INDEX</cite> 标注来源,$INDEX 从 1 开始递增。
|
||||
- 明确标注无法查证或来源相互冲突的论断。
|
||||
- 不要编造来源或链接。"""
|
||||
|
||||
ACCESS_LEVELS = {"global", "department", "user"}
|
||||
ADMIN_ROLES = {"admin", "superadmin"}
|
||||
|
||||
@ -204,6 +261,77 @@ class AgentRepository:
|
||||
await self.db.refresh(agent)
|
||||
return agent
|
||||
|
||||
async def _ensure_builtin_agent(
|
||||
self,
|
||||
*,
|
||||
slug: str,
|
||||
backend_id: str,
|
||||
name: str,
|
||||
description: str,
|
||||
config_context: dict,
|
||||
is_subagent: bool,
|
||||
created_by: str | None = None,
|
||||
) -> Agent:
|
||||
"""落库一个内置 Agent;已存在则原样返回,避免覆盖管理员后续修改。"""
|
||||
agent = await self.get_by_slug(slug)
|
||||
if agent:
|
||||
return agent
|
||||
|
||||
agent = Agent(
|
||||
slug=slug,
|
||||
backend_id=backend_id,
|
||||
name=name,
|
||||
description=description,
|
||||
icon=None,
|
||||
pics=[],
|
||||
config_json={"context": config_context},
|
||||
share_config=DEFAULT_SHARE_CONFIG.copy(),
|
||||
is_default=False,
|
||||
is_subagent=is_subagent,
|
||||
created_by=created_by,
|
||||
updated_by=created_by,
|
||||
created_at=utc_now_naive(),
|
||||
updated_at=utc_now_naive(),
|
||||
)
|
||||
self.db.add(agent)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(agent)
|
||||
return agent
|
||||
|
||||
async def ensure_deep_research_agents(self, *, created_by: str | None = None) -> None:
|
||||
"""落库内置「深度研究」编排器及其配套调研、核查子智能体。"""
|
||||
await self._ensure_builtin_agent(
|
||||
slug=RESEARCH_EXPLORER_AGENT_SLUG,
|
||||
backend_id=SUB_AGENT_BACKEND_ID,
|
||||
name=RESEARCH_EXPLORER_AGENT_NAME,
|
||||
description=RESEARCH_EXPLORER_AGENT_DESCRIPTION,
|
||||
config_context={"system_prompt": RESEARCH_EXPLORER_SYSTEM_PROMPT},
|
||||
is_subagent=True,
|
||||
created_by=created_by,
|
||||
)
|
||||
await self._ensure_builtin_agent(
|
||||
slug=FACT_VERIFIER_AGENT_SLUG,
|
||||
backend_id=SUB_AGENT_BACKEND_ID,
|
||||
name=FACT_VERIFIER_AGENT_NAME,
|
||||
description=FACT_VERIFIER_AGENT_DESCRIPTION,
|
||||
config_context={"system_prompt": FACT_VERIFIER_SYSTEM_PROMPT},
|
||||
is_subagent=True,
|
||||
created_by=created_by,
|
||||
)
|
||||
await self._ensure_builtin_agent(
|
||||
slug=DEEP_RESEARCH_AGENT_SLUG,
|
||||
backend_id=DEFAULT_AGENT_BACKEND_ID,
|
||||
name=DEEP_RESEARCH_AGENT_NAME,
|
||||
description=DEEP_RESEARCH_AGENT_DESCRIPTION,
|
||||
config_context={
|
||||
"system_prompt": DEEP_RESEARCH_SYSTEM_PROMPT,
|
||||
"subagents": [RESEARCH_EXPLORER_AGENT_SLUG, FACT_VERIFIER_AGENT_SLUG],
|
||||
"skills": [DEEP_RESEARCH_AGENT_SLUG],
|
||||
},
|
||||
is_subagent=False,
|
||||
created_by=created_by,
|
||||
)
|
||||
|
||||
async def list_visible(self, *, user: User, include_subagents: bool = False) -> list[Agent]:
|
||||
stmt = select(Agent)
|
||||
if not include_subagents:
|
||||
|
||||
@ -48,6 +48,7 @@ async def lifespan(app: FastAPI):
|
||||
repository = AgentRepository(session)
|
||||
await repository.ensure_default_agent()
|
||||
await repository.ensure_web_search_subagent()
|
||||
await repository.ensure_deep_research_agents()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to ensure default agent during startup: {e}")
|
||||
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from yuxi.repositories.agent_repository import (
|
||||
AgentRepository,
|
||||
DEEP_RESEARCH_AGENT_SLUG,
|
||||
DEFAULT_AGENT_BACKEND_ID,
|
||||
FACT_VERIFIER_AGENT_SLUG,
|
||||
RESEARCH_EXPLORER_AGENT_SLUG,
|
||||
SUB_AGENT_BACKEND_ID,
|
||||
)
|
||||
|
||||
|
||||
class CollectingDb:
|
||||
def __init__(self):
|
||||
self.added: list = []
|
||||
self.commit = AsyncMock()
|
||||
self.refresh = AsyncMock()
|
||||
|
||||
def add(self, item):
|
||||
self.added.append(item)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ensure_deep_research_agents_creates_orchestrator_and_subagents(monkeypatch):
|
||||
db = CollectingDb()
|
||||
repo = AgentRepository(db)
|
||||
|
||||
async def get_by_slug(_slug):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(repo, "get_by_slug", get_by_slug)
|
||||
|
||||
await repo.ensure_deep_research_agents()
|
||||
|
||||
created = {agent.slug: agent for agent in db.added}
|
||||
assert set(created) == {
|
||||
DEEP_RESEARCH_AGENT_SLUG,
|
||||
RESEARCH_EXPLORER_AGENT_SLUG,
|
||||
FACT_VERIFIER_AGENT_SLUG,
|
||||
}
|
||||
|
||||
explorer = created[RESEARCH_EXPLORER_AGENT_SLUG]
|
||||
verifier = created[FACT_VERIFIER_AGENT_SLUG]
|
||||
assert explorer.backend_id == SUB_AGENT_BACKEND_ID and explorer.is_subagent is True
|
||||
assert verifier.backend_id == SUB_AGENT_BACKEND_ID and verifier.is_subagent is True
|
||||
|
||||
orchestrator = created[DEEP_RESEARCH_AGENT_SLUG]
|
||||
assert orchestrator.backend_id == DEFAULT_AGENT_BACKEND_ID
|
||||
assert orchestrator.is_subagent is False
|
||||
assert orchestrator.is_default is False
|
||||
context = orchestrator.config_json["context"]
|
||||
assert context["subagents"] == [RESEARCH_EXPLORER_AGENT_SLUG, FACT_VERIFIER_AGENT_SLUG]
|
||||
assert context["skills"] == [DEEP_RESEARCH_AGENT_SLUG]
|
||||
assert context["system_prompt"].strip()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ensure_deep_research_agents_is_idempotent(monkeypatch):
|
||||
db = CollectingDb()
|
||||
repo = AgentRepository(db)
|
||||
|
||||
async def get_by_slug(slug):
|
||||
return SimpleNamespace(slug=slug)
|
||||
|
||||
monkeypatch.setattr(repo, "get_by_slug", get_by_slug)
|
||||
|
||||
await repo.ensure_deep_research_agents()
|
||||
|
||||
assert db.added == []
|
||||
db.commit.assert_not_awaited()
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
### 开发记录
|
||||
|
||||
- 新增内置「深度研究」多智能体:编排器 Agent(`deep-research`,ChatbotAgent 后端)负责澄清、拆解、并行调度子智能体与综合成稿,配套两个子智能体 `research-explorer`(围绕单个子问题多轮检索网页/知识库并返回带引用发现)和 `fact-verifier`(对抗式核验关键论断、标注冲突与置信度);完整研究方法论沉淀为新增内置 Skill `deep-research`(依赖 `tavily_search`),编排器运行时读取并据此调度。三者随 `lifespan` 启动通过 `AgentRepository.ensure_deep_research_agents` 幂等落库(已存在不覆盖管理员修改)。
|
||||
- 收敛 MCP 创建与编辑入口:前端移除整段配置文本入口和模式切换器,仅保留表单字段提交;后端 MCP 创建/更新请求拒绝额外配置字段,避免绕过表单约束。
|
||||
- 调整内置 MCP 默认项:移除 `sequentialthinking` 的系统内置同步,启动同步时清理历史系统内置记录,保留用户手动创建的同名 MCP。
|
||||
- 图片生成能力迁移为 Skill:Qwen-Image 从内置 Python 生成工具迁移到内置 Skill `image-gen`,模型调用与图片下载在 Agent 沙盒中完成,生成结果保存到 outputs 并通过 `present_artifacts` 展示,为多图片生成模型接入复用同一产物展示链路。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user