feat: 报告结构支持检索方向/来源与智能新增,并修复协助卡停轮与宿主跳转
为深度研究报告结构增加检索方向与技能来源、对话式智能新增页与详情排版;澄清中间件在 after_model 可靠结束本轮,避免弱模型出卡后继续生成;任务按钮补充 host 跳转与相关深链能力。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
a5a9c952e8
commit
51afebb394
67 files changed
+4511
-699
No files matched your search
+76
-28
@@ -11,11 +11,12 @@ from deerflow.agents.middlewares.memory_middleware import MemoryMiddleware
|
||||
from deerflow.agents.middlewares.prompt_prefix_middleware import PromptPrefixMiddleware
|
||||
from deerflow.agents.middlewares.rag_citation_middleware import RagCitationMiddleware
|
||||
from deerflow.agents.middlewares.setup_agent_approval_middleware import SetupAgentApprovalMiddleware
|
||||
from deerflow.agents.middlewares.setup_report_structure_approval_middleware import SetupReportStructureApprovalMiddleware
|
||||
from deerflow.agents.middlewares.setup_writing_approval_middleware import SetupWritingApprovalMiddleware
|
||||
from deerflow.agents.middlewares.subagent_limit_middleware import SubagentLimitMiddleware
|
||||
from deerflow.agents.middlewares.summarization_middleware import BeforeSummarizationHook, DeerFlowSummarizationMiddleware
|
||||
from deerflow.agents.middlewares.task_external_context_middleware import TaskExternalContextMiddleware
|
||||
from deerflow.agents.middlewares.title_middleware import TitleMiddleware
|
||||
from deerflow.agents.middlewares.subagent_limit_middleware import SubagentLimitMiddleware
|
||||
from deerflow.agents.middlewares.summarization_middleware import BeforeSummarizationHook, DeerFlowSummarizationMiddleware
|
||||
from deerflow.agents.middlewares.task_external_context_middleware import TaskExternalContextMiddleware
|
||||
from deerflow.agents.middlewares.title_middleware import TitleMiddleware
|
||||
from deerflow.agents.middlewares.todo_middleware import TodoMiddleware
|
||||
from deerflow.agents.middlewares.token_usage_middleware import TokenUsageMiddleware
|
||||
from deerflow.agents.middlewares.tool_error_handling_middleware import build_lead_runtime_middlewares
|
||||
@@ -31,6 +32,7 @@ logger = logging.getLogger(__name__)
|
||||
# 写作配置 Q&A 现在「先生成大纲并与用户多轮确认 → 再提交配置」,需要更多来回,故上限放宽。
|
||||
# 仍是兜底安全阀:到顶才用 tool_choice 强制提交,正常流程由用户确认大纲后自然提交。
|
||||
WRITING_SETUP_MAX_ROUNDS = 8
|
||||
REPORT_STRUCTURE_SETUP_MAX_ROUNDS = 8
|
||||
|
||||
|
||||
def _get_runtime_config(config: RunnableConfig) -> dict:
|
||||
@@ -292,6 +294,7 @@ def _build_middlewares(
|
||||
is_plan_mode = cfg.get("is_plan_mode", False)
|
||||
is_bootstrap = cfg.get("is_bootstrap", False)
|
||||
is_writing_setup = cfg.get("is_writing_setup", False)
|
||||
is_report_structure_setup = cfg.get("is_report_structure_setup", False)
|
||||
todo_list_middleware = _create_todo_list_middleware(is_plan_mode)
|
||||
if todo_list_middleware is not None:
|
||||
middlewares.append(todo_list_middleware)
|
||||
@@ -319,7 +322,7 @@ def _build_middlewares(
|
||||
|
||||
# Add MemoryMiddleware (after TitleMiddleware) — skip in bootstrap mode (agent creation flow) and ephemeral runs
|
||||
memory_injection_enabled = cfg.get("memory_injection_enabled", True)
|
||||
if not is_bootstrap and not is_writing_setup and not is_ephemeral:
|
||||
if not is_bootstrap and not is_writing_setup and not is_report_structure_setup and not is_ephemeral:
|
||||
middlewares.append(MemoryMiddleware(agent_name=agent_name, memory_config=resolved_app_config.memory, injection_enabled=memory_injection_enabled))
|
||||
|
||||
# KnowledgeRagMiddleware — inject knowledge-base recall (phase 2). Gated
|
||||
@@ -327,16 +330,16 @@ def _build_middlewares(
|
||||
# to the global ``knowledge.rag_enabled`` config. No-op when knowledge is
|
||||
# disabled. Skipped in bootstrap/writing-setup/ephemeral like MemoryMiddleware.
|
||||
knowledge_cfg = getattr(resolved_app_config, "knowledge", None)
|
||||
if not is_bootstrap and not is_writing_setup and not is_ephemeral and knowledge_cfg is not None and knowledge_cfg.enabled:
|
||||
from deerflow.agents.middlewares.knowledge_rag_middleware import KnowledgeRagMiddleware
|
||||
|
||||
middlewares.append(KnowledgeRagMiddleware(limit=knowledge_cfg.rag_limit, default_enabled=knowledge_cfg.rag_enabled))
|
||||
|
||||
# TaskExternalContextMiddleware — inject hidden per-run business context
|
||||
# (for example rwfx's completed 3q report) into the model request only.
|
||||
middlewares.append(TaskExternalContextMiddleware())
|
||||
|
||||
# Add ViewImageMiddleware only for the NATIVE-vision path (main model
|
||||
if not is_bootstrap and not is_writing_setup and not is_report_structure_setup and not is_ephemeral and knowledge_cfg is not None and knowledge_cfg.enabled:
|
||||
from deerflow.agents.middlewares.knowledge_rag_middleware import KnowledgeRagMiddleware
|
||||
|
||||
middlewares.append(KnowledgeRagMiddleware(limit=knowledge_cfg.rag_limit, default_enabled=knowledge_cfg.rag_enabled))
|
||||
|
||||
# TaskExternalContextMiddleware — inject hidden per-run business context
|
||||
# (for example rwfx's completed 3q report) into the model request only.
|
||||
middlewares.append(TaskExternalContextMiddleware())
|
||||
|
||||
# Add ViewImageMiddleware only for the NATIVE-vision path (main model
|
||||
# ingests raw image data). In delegated mode the view_image tool already
|
||||
# returns a text description and stores nothing in `viewed_images`, so the
|
||||
# middleware would otherwise inject a spurious "No images have been viewed."
|
||||
@@ -374,6 +377,7 @@ def _build_middlewares(
|
||||
|
||||
middlewares.append(SetupAgentApprovalMiddleware())
|
||||
middlewares.append(SetupWritingApprovalMiddleware())
|
||||
middlewares.append(SetupReportStructureApprovalMiddleware())
|
||||
|
||||
# SkillStopMiddleware — intercepts skill invocations and halts execution
|
||||
# when a configured skill is about to run. Request-level options
|
||||
@@ -421,7 +425,7 @@ def make_lead_agent(config: RunnableConfig):
|
||||
def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
|
||||
# Lazy import to avoid circular dependency
|
||||
from deerflow.tools import get_available_tools
|
||||
from deerflow.tools.builtins import ask_clarification_tool, setup_agent, setup_writing
|
||||
from deerflow.tools.builtins import ask_clarification_tool, setup_agent, setup_report_structure, setup_writing
|
||||
|
||||
cfg = _get_runtime_config(config)
|
||||
resolved_app_config = app_config
|
||||
@@ -443,8 +447,12 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
|
||||
is_writing_setup = cfg.get("is_writing_setup", False)
|
||||
writing_setup_article_types = cfg.get("valid_article_types") if is_writing_setup else None
|
||||
# 快捷模式:跳过逐项选项澄清,给标题直接出大纲(见 build_writing_setup_prompt)。
|
||||
writing_setup_quick = bool(cfg.get("writing_setup_quick", False)) if is_writing_setup else False
|
||||
writing_sample_context = cfg.get("writing_sample_context") if is_writing_setup else None
|
||||
writing_setup_quick = bool(cfg.get("writing_setup_quick", False)) if is_writing_setup else False
|
||||
writing_sample_context = cfg.get("writing_sample_context") if is_writing_setup else None
|
||||
# 报告结构智能新增:与 is_writing_setup 同构的最小化 bootstrap。
|
||||
is_report_structure_setup = cfg.get("is_report_structure_setup", False)
|
||||
report_structure_setup_skills = cfg.get("valid_retrieval_skills") if is_report_structure_setup else None
|
||||
report_structure_setup_quick = bool(cfg.get("writing_setup_quick", False)) if is_report_structure_setup else False
|
||||
memory_injection_enabled = bool(cfg.get("memory_injection_enabled", True))
|
||||
rag_mode_enabled = bool(cfg.get("rag_mode_enabled", False))
|
||||
writing_mode = bool(cfg.get("writing_mode", False))
|
||||
@@ -455,7 +463,7 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
|
||||
requested_excluded_tools = cfg.get("excluded_tools") or None
|
||||
|
||||
try:
|
||||
agent_config = load_agent_config(agent_id) if not is_bootstrap and not is_writing_setup else None
|
||||
agent_config = load_agent_config(agent_id) if not is_bootstrap and not is_writing_setup and not is_report_structure_setup else None
|
||||
except FileNotFoundError:
|
||||
logger.warning("Agent directory not found for '%s'; running with default config", agent_id)
|
||||
agent_config = None
|
||||
@@ -499,6 +507,13 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
|
||||
# Default lead agent keeps existing behavior (all enabled skills).
|
||||
runtime_available_skills = None
|
||||
|
||||
from deerflow.agents.deep_research.retrieval import merge_collector_skill_allowlist, runtime_extra_skills
|
||||
|
||||
runtime_available_skills = merge_collector_skill_allowlist(
|
||||
agent_id, runtime_available_skills, runtime_extra_skills(cfg)
|
||||
)
|
||||
prompt_available_skills = set(runtime_available_skills) if runtime_available_skills is not None else None
|
||||
|
||||
config["metadata"].update(
|
||||
{
|
||||
"agent_id": agent_id,
|
||||
@@ -533,13 +548,46 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
|
||||
*_build_middlewares(config, model_name=model_name, app_config=resolved_app_config),
|
||||
WritingSetupRoundCapMiddleware(max_rounds=WRITING_SETUP_MAX_ROUNDS),
|
||||
],
|
||||
system_prompt=build_writing_setup_prompt(
|
||||
writing_setup_article_types,
|
||||
quick_mode=writing_setup_quick,
|
||||
sample_context=writing_sample_context,
|
||||
),
|
||||
state_schema=ThreadState,
|
||||
)
|
||||
system_prompt=build_writing_setup_prompt(
|
||||
writing_setup_article_types,
|
||||
quick_mode=writing_setup_quick,
|
||||
sample_context=writing_sample_context,
|
||||
),
|
||||
state_schema=ThreadState,
|
||||
)
|
||||
|
||||
if is_report_structure_setup:
|
||||
from deerflow.agents.lead_agent.prompt import build_report_structure_setup_prompt
|
||||
from deerflow.agents.middlewares.writing_setup_round_cap_middleware import WritingSetupRoundCapMiddleware
|
||||
|
||||
setup_tools = (
|
||||
[setup_report_structure]
|
||||
if report_structure_setup_quick
|
||||
else [setup_report_structure, ask_clarification_tool]
|
||||
)
|
||||
return create_agent(
|
||||
model=create_chat_model(name=model_name, thinking_enabled=thinking_enabled, force_disable_thinking=thinking_force_disabled, app_config=resolved_app_config),
|
||||
tools=setup_tools,
|
||||
middleware=[
|
||||
*_build_middlewares(config, model_name=model_name, app_config=resolved_app_config),
|
||||
WritingSetupRoundCapMiddleware(
|
||||
max_rounds=REPORT_STRUCTURE_SETUP_MAX_ROUNDS,
|
||||
tool_name="setup_report_structure",
|
||||
force_instruction=(
|
||||
f"【系统强制】意图澄清已达 {REPORT_STRUCTURE_SETUP_MAX_ROUNDS} 轮上限。"
|
||||
"现在必须立即调用 setup_report_structure 提交报告结构配置,禁止再向用户提任何问题。"
|
||||
"缺失或不确定的字段一律用合理默认值:"
|
||||
"title 根据对话主题拟一个短标题、name 用一句话描述、structure_mode 默认 adaptive、"
|
||||
"retrieval_directions / retrieval_skills 不确定就留空、content 必须给出一份 Markdown 大纲。"
|
||||
),
|
||||
),
|
||||
],
|
||||
system_prompt=build_report_structure_setup_prompt(
|
||||
report_structure_setup_skills,
|
||||
quick_mode=report_structure_setup_quick,
|
||||
),
|
||||
state_schema=ThreadState,
|
||||
)
|
||||
|
||||
if is_bootstrap:
|
||||
# Special bootstrap agent with minimal prompt for initial custom agent creation flow
|
||||
@@ -600,7 +648,7 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
|
||||
agent_name=agent_display_name,
|
||||
subagent_enabled=subagent_enabled,
|
||||
max_concurrent_subagents=max_concurrent_subagents,
|
||||
available_skills=(set(agent_config.skills or []) if agent_config else None),
|
||||
available_skills=prompt_available_skills,
|
||||
app_config=resolved_app_config,
|
||||
rag_mode_enabled=rag_mode_enabled,
|
||||
writing_mode=writing_mode,
|
||||
@@ -614,7 +662,7 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
|
||||
agent_name=agent_display_name,
|
||||
writing_mode=writing_mode,
|
||||
writing_artifact_path=writing_artifact_path,
|
||||
available_skills=(set(agent_config.skills or []) if agent_config else None),
|
||||
available_skills=prompt_available_skills,
|
||||
app_config=resolved_app_config,
|
||||
memory_injection_enabled=memory_injection_enabled,
|
||||
rag_mode_enabled=rag_mode_enabled,
|
||||
|
||||
Reference in new issue
Block a user