deerflow-code/frontend-web/docs/ai-writing-内置智能体改造.md
2026-09-07 18:24:55 +08:00

21 KiB
Raw Blame History

AI 写作「四阶段内置智能体化」改造方案

目标:把 AI 写作现有的 素材收集专家 / 写作大纲 / 作家写作 / 编辑审核 四个功能, 从「硬编码的 LangGraph 节点 + 硬编码提示词」改造为由内置功能型智能体驱动—— 每个阶段对应一个可配置的内置 agent(独立人设 SOUL、可配模型、可挂技能/工具)。

本文给出现状梳理、目标设计、以及分步可独立交付的实现计划。


0. 一句话结论

不推倒重来。保留现有图的编排骨架、4 个用户干预点、流式与状态机,只把每个阶段节点的「大脑」从「硬编码 prompt 直调 LLM」替换为「加载对应内置 agent 的 SOUL+配置、在子线程上跑该 agent、按结构化契约解析其输出」。 这样前端几乎不动,复杂逻辑(严格模式、素材不足求助、逐章流式、技能检索步骤)全部保留。


1. 现状梳理(精确到文件)

AI 写作是一条 LangGraph 图:packages/harness/deerflow/agents/ai_writing/graph.py

intent_parser → researcher → (pause 素材确认) → writer_outline → (pause 大纲确认)
   → writer_draft → (pause 草稿确认 / 素材不足求助) → editor → (pause 编辑打回) → done

四个待改造阶段(均为硬编码节点,直接调 LLM、不走 agent 运行时):

阶段(用户语言) 节点文件 提示词 产出(state.py)
素材收集专家 nodes/researcher.py prompts/researcher_prompts.py material_package(keywords + materials)
写作大纲 nodes/writer_outline.py prompts/writer_prompts.py current_outline(Outline JSON)
作家写作 nodes/writer_draft.py prompts/writer_prompts.py current_draft(Markdown,逐章流式)
编辑审核 nodes/editor.py prompts/editor_prompts.py review_result(评分 + 问题列表 JSON)

关键事实:

  • LLM 调用方式:节点通过 nodes/_utils.py 的 get_model() + llm_json() 直接调 LLM,提示词写死在 prompts/*.py。没有走 agent 人设 / 技能 / 工具体系。
  • 模型:全程单一 state.model_name(用户在写作表单里选的主模型),各阶段无法分别配。
  • 干预点 / 流式 / 严格模式 / 素材不足求助:都在 graph.py 的 pause 节点和 writer_draft 里,逻辑较重,需原样保留。
  • 前端:frontend-web/src/open-canvas/contexts/AIWritingContext.tsx 是状态机(researching/writing_outline/writing_draft/reviewing/...),时间线 AIWritingTimeline.tsx 按 progress_events(已带 agent_name 字段)渲染。

2. 可复用的现成机制(不用造轮子)

代码库里已经有一套成熟的「内置功能型智能体」范式——圆桌的 roundtable-intent / roundtable-recommender / roundtable-coordinator:

  1. 资源即代码:app/gateway/routers/_roundtable_seed_assets/<agent-id>/
    • config.yaml(id / name / description,可扩展 model / skills / tool_groups)
    • SOUL.md(人设 + 职责 + 工作流 + 结构化输出契约)
  2. 种子器:app/gateway/routers/_roundtable_seed.py::ensure_roundtable_functional_agents
    • 启动时 + 相关路由入口自愈:目录不存在才从资源字节级落盘到 .deer-flow/agents/<id>/ + DB(已存在则永不覆盖,尊重本地编辑)。
  3. 运行:在独立 thread 上跑该内置 agent(见 app/gateway/routers/intent.py,INTENT_AGENT_ID = "roundtable-intent"),通过 lead agent 运行时把 SOUL.md 作系统提示,流式经 LangGraph custom/messages 通道透传。
  4. 结构化输出约定:SOUL 规定模型末尾输出固定 marker + json(如 [INTENT_READY]\n```json{...}```),后端用正则解析(intent.py::_INTENT_READY_PATTERN`)。

我们要做的,本质就是照搬这套范式,给 AI 写作做 4 个功能型 agent,并让对应的图节点改成「跑这个 agent + 解析其结构化输出」。

另有 agents 通用体系可复用:agents 表、/api/agents CRUD、智能体管理页(配模型/技能/工具)——4 个写作 agent 落盘后天然出现在这里,可被 admin 编辑。


3. 目标设计

3.1 四个内置写作智能体(建议 id / 职责)

Agent id 名称 取代节点 结构化输出 marker 产出 schema
ai-writing-researcher 素材收集专家 researcher [MATERIALS_READY] MaterialPackage
ai-writing-outliner 大纲规划师 writer_outline [OUTLINE_READY] Outline
ai-writing-writer 作家 writer_draft [SECTION_READY](逐章) 章节 Markdown
ai-writing-editor 编辑 editor [REVIEW_READY] ReviewResult

每个 agent 自带:

  • config.yaml:id / name / description +(扩展)model(该阶段专用模型,可空=回退用户主模型)、skills(如 researcher 挂检索技能)、tool_groups。
  • SOUL.md:由现有 prompts/*.py 内容迁移改写,追加结构化输出契约(严格 marker + JSON 格式 + 反例,仿 roundtable-intent SOUL 的「硬规则」写法,压制弱模型乱输出)。

3.2 节点改造方式(核心)

每个阶段节点保留输入/输出契约不变(仍读写 state 的同一批字段),内部从:

# 旧:硬编码 prompt 直调
model = get_model(config, state["model_name"])
result = llm_json(model, SYSTEM_PROMPT, USER_PROMPT.format(...))

改为:

# 新:跑对应内置 agent,按 marker 解析
result = await run_writing_agent(
    agent_id="ai-writing-outliner",
    state=state, config=config,
    user_payload=<本阶段输入>,        # 意图/素材/大纲/草稿等拼成的一段输入
    marker="[OUTLINE_READY]",         # 结构化输出契约
)

新增统一助手 nodes/_agent_runner.py::run_writing_agent(...):在子 thread 上跑指定内置 agent(复用 intent.py 的 run/stream 机制)、把流式 chunk 透传到 custom 通道(前端时间线不回归)、用复用的 _utils.parse_json 多级容错解析 marker 后的 JSON。

3.3 为什么选这个方案(A)而非全多智能体重写(B)

  • 方案 A(推荐):保留图骨架,逐节点替换大脑。前端基本不动;干预点/流式/严格模式/素材求助原样保留;可按阶段灰度 + feature flag 回退,风险可控。
  • 方案 B(不推荐):仿圆桌重做成多 agent 编排,需重写编排、状态、流式、4 个干预点对接,工作量数倍、回归面巨大,收益(更"纯"的多 agent)对本场景并不必要。

4. 配置与可管理性

  • 落盘:仿 roundtable 新增 _ai_writing_seed_assets/ + 一个 ensure_ai_writing_functional_agents() 种子器,在 Gateway 启动与 ai_writing 会话入口自愈。
  • 管理界面:4 个 agent 落盘后即出现在智能体管理页,admin 可编辑 SOUL(人设)/ 模型 / 挂载技能。可选:在「文章类型配置页」旁加一个「写作智能体配置」入口,或给这 4 个打一个 tag 便于筛选。
  • 模型优先级:节点跑 agent 时 该 agent config.model > 用户写作表单选的主模型 model_name(保留现有"用户选模型"语义,新增 per-阶段覆盖能力)。
  • 技能:researcher 现有的技能检索能力(search/skill_search.py)改为 ai-writing-researcher 的挂载技能,admin 可增减;检索步骤事件(SkillStep)形态保留给前端展示。

5. 分步实现计划(每步可独立交付 / 验收)

Phase 0 · 契约冻结

  • 定 4 个 agent 的 id / name、每个的 marker + JSON schema(与 state.py 的 MaterialPackage/Outline/DraftArticle/ReviewResult 严格对齐)。
  • 验收:本文 §3.1 / §6 的契约评审通过、字段与现有 TypedDict 一一对应。

Phase 1 · 内置 agent 种子化(后端,纯新增,不接管流程)

  • 新建 app/gateway/routers/_ai_writing_seed_assets/<4 个 agent>/{config.yaml, SOUL.md};SOUL 由现有 prompts/*.py 迁移改写 + 追加结构化输出契约。
  • 仿 _roundtable_seed.py 写 ensure_ai_writing_functional_agents(),挂到启动 lifespan + ai_writing 入口。
  • 验收:启动后 4 个 agent 出现在 .deer-flow/agents/、DB、/api/agents,能在智能体管理页查看/编辑;此阶段不改图,写作流程行为不变。

Phase 2 · 节点接入「跑内置 agent」(后端,逐阶段灰度)

  • 新增 nodes/_agent_runner.py::run_writing_agent(...)(子线程跑 agent + 流式透传 + marker 解析)。
  • 加 feature flag:config.yaml → ai_writing.use_builtin_agents.{editor,outliner,researcher,writer}(默认 false=走旧节点)。
  • 接入顺序(由易到难):
    1. editor(最简单:小 JSON、无检索、无逐章流式)→ flag 打开端到端验证。
    2. outliner(输出 Outline JSON)。
    3. researcher(保留技能检索步骤事件、素材确认暂停)。
    4. writer(最难:保留逐章流式 + 严格模式素材不足求助 blocked_sections;建议仍由节点控制"逐章循环",每章调一次 ai-writing-writer,section_help 暂停逻辑不动)。
  • 每接一个,旧 prompt 与旧逻辑保留,节点按 flag 二选一。
  • 验收:每个阶段 flag 打开后端到端走通,产出结构与旧版一致,干预点 / 流式 / 严格模式 / 求助暂停均不回归。

Phase 3 · 每阶段 agent 配置化

  • per-agent 模型覆盖生效(agent.config.model > model_name)。
  • researcher 的检索能力改为挂载技能,admin 可增减。
  • 智能体管理页可编辑这 4 个的 SOUL / 模型 / 技能;config 热加载,新会话即生效、不重启前端。
  • 验收:admin 改某阶段 agent 的模型 / SOUL / 技能后,新会话立即按新配置运行。

Phase 4 · 前端适配(小改)

  • 时间线 / 状态文案:阶段名读对应 agent 的 name(progress_events.agent_name 已存在,基本沿用,去掉写死文案)。
  • 可选:写作表单 / 设置加「查看·配置写作智能体」入口,跳智能体管理页。
  • 验收:前端展示各阶段对应的智能体名;配置入口可达。

Phase 5 · 清理收口

  • 四阶段稳定后,默认打开全部 flag、移除旧 prompt 直调路径与 flag(prompts 历史留在 git)。
  • 更新 offline-backend-20260512/backend/CLAUDE.md 架构描述。
  • 验收:回归测试通过、冗余删除、文档同步。

6. 结构化输出契约(与现有 state 对齐)

各 agent SOUL 末尾必须输出 marker + ```json``` 块,schema 直接对齐 state.py`:

  • ai-writing-researcher → [MATERIALS_READY]:{ "keywords": string[], "materials": Material[] }(Material 见 state.py,含 id/title/content/source/relevance_score/source_type/...)。
  • ai-writing-outliner → [OUTLINE_READY]:Outline = { "title": string, "sections": OutlineSection[] }(OutlineSection = { section_title, key_points: KeyPoint[], material_ids })。
  • ai-writing-writer → 逐章 [SECTION_READY]:{ "section_title": string, "markdown": string, "needs_more_material"?: { "reason": string } }(needs_more_material 触发严格模式求助 blocked_sections)。
  • ai-writing-editor → [REVIEW_READY]:ReviewResult = { "verdict": "pass"|"reject", "fact_score", "logic_score", "language_score", "overall_score", "pass_threshold", "revision_notes": ReviewIssue[] }。

解析复用 nodes/_utils.py::parse_json(多级容错),marker 正则仿 intent.py::_INTENT_READY_PATTERN。


7. 风险与注意

  1. 弱模型结构化输出不稳:SOUL 写严格格式 + 反例 + 「输出 marker 后立刻闭嘴」硬规则(仿 roundtable-intent SOUL §0);解析层复用 parse_json 多级降级。
  2. writer 的逐章流式 + 严格模式求助最复杂:建议循环控制权留在节点(每章调一次 agent),不要让 agent 一次吐全文,否则 blocked_sections / section_help / 逐章流式都要重做。
  3. researcher 技能检索步骤事件(SkillStep)前端有专门卡片:迁移时保留这些 progress_events 形态。
  4. 种子器 never-overwrite:迭代 SOUL 后老部署不会自动更新(与圆桌同一问题)。运维更新办法:删 .deer-flow/agents/<id>/ 重新种子,或后续给种子器加「内置功能 agent 按版本/hash 覆盖」开关。
  5. 子线程隔离:跑写作 agent 的子 thread 与主 ai_writing thread 状态隔离,注意 checkpointer 与清理(沿用 ai_writing 既有的会话清理)。
  6. 离线内网:researcher 的检索一律走技能(不联网),与现有「离线部署禁 web_search、走 skill」策略一致。

8. 涉及文件清单(落地时对照)

后端(主要改动)

  • 新增 app/gateway/routers/_ai_writing_seed_assets/{ai-writing-researcher,ai-writing-outliner,ai-writing-writer,ai-writing-editor}/{config.yaml,SOUL.md}
  • 新增 app/gateway/routers/_ai_writing_seed.py(种子器,仿 _roundtable_seed.py)
  • 新增 packages/harness/deerflow/agents/ai_writing/nodes/_agent_runner.py(run_writing_agent)
  • 改 nodes/{researcher,writer_outline,writer_draft,editor}.py(按 flag 二选一)
  • 改 config.yaml(新增 ai_writing.use_builtin_agents.* 开关;可选 per-agent 默认模型)
  • 保留 prompts/*.py(回退用,Phase 5 删)

前端(小改)

  • 改 src/open-canvas/components/ai-writing/AIWritingTimeline.tsx(阶段名读 agent name)
  • 可选:写作表单/设置加「配置写作智能体」入口(跳 /page/... 智能体管理页)

文档

  • 本文件
  • Phase 5 收口时更新 offline-backend-20260512/backend/CLAUDE.md

9. 实施记录(2026-06-11,Phase 1-4 已落地)

已交付

  • Phase 1:_ai_writing_seed_assets/{ai-writing-researcher,ai-writing-outliner,ai-writing-writer,ai-writing-editor}/{config.yaml,SOUL.md} + _ai_writing_seed.py::ensure_ai_writing_functional_agents(),挂启动 lifespan(_sync_legacy_agents 之前)+ ai_writing.list_sessions / list_article_types 入口自愈。
  • Phase 2:nodes/_agent_runner.py(stream_writing_agent / run_writing_agent / parse_marker_json / resolve_agent_model_name / use_builtin_agent);四节点按 config.yaml → ai_writing.use_builtin_agents.{researcher,outliner,writer,editor} 二选一(默认全 false,config 按 mtime 热加载,改完即对新会话生效)。agent 目录缺失时节点静默回退旧 prompt 路径,写作流程永不因 agent 被删而中断。
  • Phase 3(部分):per-agent 模型覆盖已生效(优先级 agent config.model > 节点显式回退模型(keyword_model/rank_model)> 表单主模型 model_name);SOUL/模型在智能体管理页可编辑、按 mtime 热加载。researcher「检索能力改挂载技能」未做(仍走节点内 SkillPicker 编排)。
  • Phase 4:时间线 agent 名由后端 progress_events.agent_name 动态下发(flag 开启时读 agent config 的 name);TimelineStep.tsx 补「大纲规划师」配色(未命中名字回退灰色,admin 改名不报错)。
  • 测试:tests/test_ai_writing_seed.py(资源完整性 / 契约 marker / never-overwrite)+ tests/test_ai_writing_agent_runner.py(marker 多级解析 / 模型优先级 / flag / 流式端到端 / SOUL 缺失降级),共 31 例。

与 §6 契约的修订(以实现为准)

为保住「逐章流式打字机」「review_chunk/review_item_done 分维度卡片」「素材确认/求助暂停」等前端形态,编排控制权全部留在节点,agent 只承担单次结构化产出:

Agent 实际契约
ai-writing-researcher 两个子任务:[KEYWORDS_READY] + {"keywords": []};[MATERIALS_RANKED] + {"ranked_ids": [], "summary": ""}。素材本体来自节点的检索编排(agent 不联网),MaterialPackage 仍由节点组装
ai-writing-outliner [OUTLINE_READY] + 完整 Outline JSON(新规划 / 修改模式共用,payload 开头标明任务模式)
ai-writing-writer 无 JSON marker:每章直接输出 Markdown 正文(保流式);严格模式素材不足只输出一行 [[SECTION_BLOCKED]] 原因(沿用原哨兵)
ai-writing-editor 每个维度一次调用:[REVIEW_READY] + {"score": int, "issues": []};综合分/verdict/pass_threshold 仍由节点按 config 计算(不信任 LLM 自判通过)

另一处架构修订:不在子 thread 上经 lead agent 运行时跑(§3.2 原设想复用 intent.py 机制,但其 loopback 在 app 层,harness 图节点不可 import;且单轮结构化调用无需 thread/checkpointer/中间件成本)。run_writing_agent 直接「SOUL 作 system prompt + 单条输入」调模型,规避了 §7.5 的子线程隔离/清理风险。researcher 挂技能(Phase 3 剩余项)如需完整工具体系再评估接入运行时。

已知存量测试失败(与本次无关)

test_ai_writing_graph.py(mock 已删除的 intent_parser.llm_json)、test_ai_writing_word_count.py(mock 已删除的 writer_draft.get_model)、test_ai_writing_draft_headings.py(断言三级结构升级前的 ####→### 行为)、test_ai_writing_outline_revise.py(断言旧 prompt 文案),改造前即失败,待另行清理。

10. Phase 3 收尾:检索类型两项化 + 配置技能驱动检索(2026-06-13 已实施)

本节落地了 §9 遗留的「researcher 检索改挂载技能」,并按新需求重新设计了检索类型与 PAUSE-1 交互。

10.1 检索类型(material_source)收敛为两项

  • 写作表单「素材来源」改名「检索类型」,只剩 general(通用检索,默认)/ notebook(我的空间);旧值 knowledge_base / skill 由 researcher 节点按 general 兼容(老会话 resume 不受影响)。
  • 前端:AIWritingForm.tsx(选项/标签)、WritingFormContext.tsx(默认值/类型/applyProposal 旧值映射)、types/ai-writing.ts、message-list.tsx(审批卡标签兼容旧值)。

10.2 通用检索 = 素材收集专家配置的技能编排

  • ai-writing-researcher agent 的 config.yaml → skills 是权威配置(智能体管理页可增删/替换);种子默认 [knowledge-base-search]。
  • 内置技能 knowledge-base-search(知识库检索):把原 web/intranet 关键词并发检索包装成技能。researcher 对它特殊处理——不拼 description,直接对 Step1 生成的检索词做原生并发检索(行为与旧 knowledge_base 模式一致)。其它配置技能按 意图 + description 拼定向 query 检索。按配置顺序执行、累积达 max_materials 早停(stop_early)。
  • 技能配置为空/全部失效 → 内存兜底回退知识库检索(researcher._load_configured_skills),检索永不瘫痪。
  • 种子:_ai_writing_seed_assets/skills/knowledge-base-search/SKILL.md → skills/public/(never-overwrite);旧部署的 researcher config.yaml 缺 skills 键时就地补默认(显式 skills: [] 不动)。_ai_writing_seed.py::ensure_ai_writing_functional_agents() 一并处理。

10.3 PAUSE-1 输入框 → 匹配技能检索并追加

  • 用户在素材确认卡输入自然语言(如「检索 A 的数据」)→ re_search + user_query:配置技能 >1 个时 SkillPicker(candidates=配置技能) 让模型按输入匹配排序(流式打字机保留),单技能直接用;用用户原话(非知识库技能附 description)检索。
  • 追加语义:保留原素材列表与顺序,新素材按 URL 去重后追加在末尾(ID 续号),跳过 LLM 重排与 max_materials 截断(仅 100 条硬上限),summary 显示「追加检索到 X 条新素材,当前共 Y 条」。原 20 条 + 新 20 条 = 40 条。
  • 「技能检索」独立按钮已删除(通用检索本来就走技能编排);后端继续兼容旧 enable_skill_search action(按 user_query 追加处理)。无输入的「重新搜索」仍为覆盖式重检。
  • 前端:InterventionCard.tsx(按钮/文案,hasQuery 时按钮文案「检索并追加素材」)、MaterialsToolbar.tsx(提示语)、useAIWritingStream.ts(素材卡回到 researching 一律置 skillSearchPending,让 SkillStepsCard 占位即时挂载)。

10.4 测试

  • tests/test_ai_writing_material_source.py 重写(9 例):缺省/旧值→通用检索、自定义技能 description query、user_query 追加+URL 去重+多技能 picker 匹配(candidates 断言)、无输入重检覆盖、空配置回退。
  • tests/test_ai_writing_seed.py 扩展(+5 例):技能资产存在、researcher 种子含默认 skills、技能种子 never-overwrite、缺 skills 键就地补丁、显式 [] 不动。