fix: writing mode uses chat search then pipeline write only

Keep deep_research_report as a write-only deliverable from conversation materials, block post-success rewrite loops with a summary nudge, fill thin/empty pools from model knowledge, and open the sandbox only on real report.md content.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
董宏凯andCursor committed 2026-08-30 12:41:04 +08:00
1 parent 9afb9750e5
commit 49c01cb72b
20 files changed
+1449 -381

No files matched your search

@@ -1,5 +1,5 @@
import hashlib
import logging
import hashlib
import logging
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware
@@ -38,45 +38,45 @@ logger = logging.getLogger(__name__)
# 写作配置 Q&A 现在「先生成大纲并与用户多轮确认 → 再提交配置」,需要更多来回,故上限放宽。
# 仍是兜底安全阀:到顶才用 tool_choice 强制提交,正常流程由用户确认大纲后自然提交。
WRITING_SETUP_MAX_ROUNDS = 8
REPORT_STRUCTURE_SETUP_MAX_ROUNDS = 8
_ES_QUERY_ROUTING_OPEN_TAG = '<skill_routing skill="es_query">'
def _log_es_query_routing_registration(
*,
system_prompt: str,
app_config: AppConfig,
agent_id: str | None,
available_skills: set[str] | None,
) -> None:
"""Log whether the exact configured ES routing block reached this Q&A prompt."""
routing = app_config.skills.es_query_routing
configured_prompt = (routing.prompt or "").strip()
expected_block = (
f"{_ES_QUERY_ROUTING_OPEN_TAG}\n{configured_prompt}\n</skill_routing>"
if configured_prompt
else ""
)
eligible = available_skills is None or "es_query" in available_skills
registered = bool(expected_block) and expected_block in system_prompt
fingerprint = (
hashlib.sha256(configured_prompt.encode("utf-8")).hexdigest()[:12]
if configured_prompt
else "-"
)
log = logger.info if registered or not routing.enabled or not eligible else logger.warning
log(
"ES query routing prompt registration: agent_id=%s enabled=%s eligible=%s "
"registered=%s prompt_chars=%d prompt_sha256=%s",
agent_id or "default",
routing.enabled,
eligible,
registered,
len(configured_prompt),
fingerprint,
)
REPORT_STRUCTURE_SETUP_MAX_ROUNDS = 8
_ES_QUERY_ROUTING_OPEN_TAG = '<skill_routing skill="es_query">'
def _log_es_query_routing_registration(
*,
system_prompt: str,
app_config: AppConfig,
agent_id: str | None,
available_skills: set[str] | None,
) -> None:
"""Log whether the exact configured ES routing block reached this Q&A prompt."""
routing = app_config.skills.es_query_routing
configured_prompt = (routing.prompt or "").strip()
expected_block = (
f"{_ES_QUERY_ROUTING_OPEN_TAG}\n{configured_prompt}\n</skill_routing>"
if configured_prompt
else ""
)
eligible = available_skills is None or "es_query" in available_skills
registered = bool(expected_block) and expected_block in system_prompt
fingerprint = (
hashlib.sha256(configured_prompt.encode("utf-8")).hexdigest()[:12]
if configured_prompt
else "-"
)
log = logger.info if registered or not routing.enabled or not eligible else logger.warning
log(
"ES query routing prompt registration: agent_id=%s enabled=%s eligible=%s "
"registered=%s prompt_chars=%d prompt_sha256=%s",
agent_id or "default",
routing.enabled,
eligible,
registered,
len(configured_prompt),
fingerprint,
)
def _get_runtime_config(config: RunnableConfig) -> dict:
@@ -426,6 +426,18 @@ def _build_middlewares(
if agent_name == FORCED_RESEARCH_AGENT_ID:
middlewares.append(ForcedResearchMiddleware())
# 写作模式首轮:允许先聊/检索;只有模型开始写 md 时才改走 deep_research_report。
# 不设 tool_choice——thinking 模式的兼容接口会 400。
writing_mode = bool(cfg.get("writing_mode", False))
writing_artifact_path = cfg.get("writing_artifact_path") if writing_mode else None
if writing_mode:
from deerflow.tools.builtins.deep_research_report_tool import is_writing_mode_report_path
if not is_writing_mode_report_path(writing_artifact_path if isinstance(writing_artifact_path, str) else None):
from deerflow.agents.middlewares.writing_mode_report_middleware import WritingModeReportMiddleware
middlewares.append(WritingModeReportMiddleware())
# LoopDetectionMiddleware — detect and break repetitive tool call loops
middlewares.append(LoopDetectionMiddleware())
@@ -517,6 +529,12 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
writing_artifact_path = cfg.get("writing_artifact_path") if writing_mode else None
if not isinstance(writing_artifact_path, str) or not writing_artifact_path.strip():
writing_artifact_path = None
# 只有写作模式产物 report.md 才走续写;其它 leftover .md 不得跳过管线。
if writing_artifact_path is not None:
from deerflow.tools.builtins.deep_research_report_tool import is_writing_mode_report_path
if not is_writing_mode_report_path(writing_artifact_path):
writing_artifact_path = None
agent_id = validate_agent_id(cfg.get("agent_id") or cfg.get("agent_name"))
requested_excluded_tools = cfg.get("excluded_tools") or None
@@ -691,10 +709,10 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
from deerflow.tools.builtins.notebook_search_tool import notebook_search_tool
default_tools = [notebook_search_tool] + default_tools
# 写作模式首轮(尚无 md 产物):挂深度研究报告工具,报告由管线生成而非
# 模型手工 write_file。续写轮(已有 writing_artifact_path)不挂,保持
# 原有「直接编辑该文件」行为。不受 tool_groups/excluded_tools 过滤——
# 它是写作模式合约的核心交付手段。
# 写作模式首轮(尚无 report.md):挂深度研究报告工具,报告由管线生成而非
# 模型手工 write_file。续写轮(已有 report.md)不挂,保持原有「直接编辑
# 该文件」行为。不受 tool_groups/excluded_tools 过滤——它是写作模式合约
# 的核心交付手段。
if writing_mode and not writing_artifact_path:
from deerflow.tools.builtins.deep_research_report_tool import deep_research_report_tool
@@ -726,8 +744,8 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
writing_mode=writing_mode,
writing_artifact_path=writing_artifact_path,
)
else:
system_prompt = apply_prompt_template(
else:
system_prompt = apply_prompt_template(
subagent_enabled=subagent_enabled,
max_concurrent_subagents=max_concurrent_subagents,
agent_id=agent_id,
@@ -737,20 +755,20 @@ def _make_lead_agent(config: RunnableConfig, *, app_config: AppConfig):
available_skills=prompt_available_skills,
app_config=resolved_app_config,
memory_injection_enabled=memory_injection_enabled,
rag_mode_enabled=rag_mode_enabled,
is_notebook_mode=is_notebook_mode,
notebook_source_ids=notebook_source_ids,
is_scheduled_run=bool(cfg.get("is_scheduled_run", False)),
)
_log_es_query_routing_registration(
system_prompt=system_prompt,
app_config=resolved_app_config,
agent_id=agent_id,
available_skills=prompt_available_skills,
)
return create_agent(
rag_mode_enabled=rag_mode_enabled,
is_notebook_mode=is_notebook_mode,
notebook_source_ids=notebook_source_ids,
is_scheduled_run=bool(cfg.get("is_scheduled_run", False)),
)
_log_es_query_routing_registration(
system_prompt=system_prompt,
app_config=resolved_app_config,
agent_id=agent_id,
available_skills=prompt_available_skills,
)
return create_agent(
model=create_chat_model(name=model_name, thinking_enabled=thinking_enabled, reasoning_effort=reasoning_effort, force_disable_thinking=thinking_force_disabled, app_config=resolved_app_config),
tools=default_tools,
middleware=_build_middlewares(config, model_name=model_name, agent_name=agent_id, app_config=resolved_app_config),