AI 写作四阶段内置智能体 Phase 1-4 落地
新增 ai-writing-researcher/outliner/writer/editor 种子 agent、_agent_runner 与 use_builtin_agents 灰度开关;四节点可按 flag 切换 SOUL 模式;时间线补大纲规划师配色并更新改造文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
1a41ac97d2
commit
a4a9a4d26e
21 files changed
+1104
-134
No files matched your search
@@ -933,6 +933,20 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
"Roundtable functional agent seeding failed at startup (non-fatal)"
|
||||
)
|
||||
|
||||
# AI 写作功能型 agent(researcher / outliner / writer / editor)缺失自动补建。
|
||||
# 同圆桌:必须在 _sync_legacy_agents 之前,补建目录才能在同一次启动被
|
||||
# upsert 进 agents 表,admin 启动后即可在智能体管理页编辑四个写作 agent。
|
||||
try:
|
||||
from app.gateway.routers._ai_writing_seed import (
|
||||
ensure_ai_writing_functional_agents,
|
||||
)
|
||||
|
||||
ensure_ai_writing_functional_agents()
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"AI-writing functional agent seeding failed at startup (non-fatal)"
|
||||
)
|
||||
|
||||
# 业务链条管理:幂等补建固定业务行(3Q/6BF/7BF/8BF),agent/chain 初始为空。
|
||||
try:
|
||||
business_mapping_store = getattr(app.state, "business_mapping_store", None)
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
"""AI 写作功能型 agent 的「调用前自检 + 缺失自动补建」工具。
|
||||
|
||||
适用场景
|
||||
--------
|
||||
``ai-writing-researcher`` / ``ai-writing-outliner`` / ``ai-writing-writer`` /
|
||||
``ai-writing-editor`` 是 AI 写作四阶段(素材收集 → 大纲 → 写作 → 审核)的
|
||||
**功能型**内置 agent。当 ``config.yaml`` 的 ``ai_writing.use_builtin_agents.*``
|
||||
开关打开时,对应的图节点会从 ``.deer-flow/agents/<id>/`` 加载该 agent 的
|
||||
SOUL.md(人设+结构化输出契约)与 config.yaml(可配 per-阶段模型)。
|
||||
|
||||
仓库根 ``.gitignore`` 把整个 ``.deer-flow/`` 目录排除,新机器 ``git clone``
|
||||
之后这些目录**不会存在**。本模块在两个地方被调用:
|
||||
|
||||
1. **启动 lifespan**(``app/gateway/app.py``):服务起来就把缺失目录补齐,
|
||||
紧接着的 ``_sync_legacy_agents`` 会把它们 upsert 进 DB,新机器一启动
|
||||
``/api/agents`` 列表里就有这四个 agent,admin 可直接在智能体管理页编辑
|
||||
SOUL / 模型。
|
||||
2. **AI 写作路由入口**(``ai_writing.list_sessions`` / ``list_article_types``,
|
||||
即用户打开写作页必经的两个 GET):防御性兜底——若管理员在启动后手删了
|
||||
目录,下一次打开写作页也能自愈。
|
||||
|
||||
与 ``_roundtable_seed.py`` 完全同构:资源文件
|
||||
(``_ai_writing_seed_assets/<id>/``)是发布默认值的唯一可信源,seeder 只在
|
||||
目录缺失时字节级复制,**绝不覆盖**已存在目录(尊重运维/admin 现场手改)。
|
||||
线上调优 SOUL 后请把改动写回对应资源 .md,否则新机器补建出来的还是旧版。
|
||||
|
||||
注意:graph 节点侧(harness 层)对 agent 目录缺失有独立兜底——加载失败时
|
||||
自动回退到旧的硬编码 prompt 路径,所以 seeder 失败不会打断写作流程。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Iterable
|
||||
from pathlib import Path
|
||||
|
||||
from deerflow.config.paths import get_paths
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
RESEARCHER_AGENT_ID = "ai-writing-researcher"
|
||||
OUTLINER_AGENT_ID = "ai-writing-outliner"
|
||||
WRITER_AGENT_ID = "ai-writing-writer"
|
||||
EDITOR_AGENT_ID = "ai-writing-editor"
|
||||
|
||||
# 验证版 SOUL.md + config.yaml 的源码资源目录(随 git 发布)。每个子目录名即 agent id。
|
||||
_ASSETS_DIR = Path(__file__).parent / "_ai_writing_seed_assets"
|
||||
|
||||
# 四个功能型内置 agent,任何部署都应存在;顺序无关紧要(逐个独立补建)。
|
||||
_FUNCTIONAL_AGENT_IDS: tuple[str, ...] = (
|
||||
RESEARCHER_AGENT_ID,
|
||||
OUTLINER_AGENT_ID,
|
||||
WRITER_AGENT_ID,
|
||||
EDITOR_AGENT_ID,
|
||||
)
|
||||
|
||||
# seeder 会复制资源目录里的这些文件(存在即复制,缺失则跳过)。
|
||||
_SEED_FILES: tuple[str, ...] = ("config.yaml", "SOUL.md")
|
||||
|
||||
|
||||
def _ensure_one(agent_id: str) -> bool:
|
||||
"""Copy the verified SOUL.md + config.yaml for one functional agent into
|
||||
``.deer-flow/agents/<id>/`` if that directory is missing.
|
||||
|
||||
Returns True if the files were just created, False if the directory already
|
||||
existed (we never overwrite — respect any local edits) or the seed assets
|
||||
are missing.
|
||||
"""
|
||||
agent_dir = get_paths().agent_dir(agent_id)
|
||||
if agent_dir.exists():
|
||||
return False
|
||||
|
||||
src_dir = _ASSETS_DIR / agent_id
|
||||
if not src_dir.is_dir():
|
||||
# Assets should always ship with the code; if they don't, surface a loud
|
||||
# log instead of silently creating an empty agent dir.
|
||||
logger.error(
|
||||
"Missing seed assets for ai-writing agent '%s' (expected at %s)",
|
||||
agent_id,
|
||||
src_dir,
|
||||
)
|
||||
return False
|
||||
|
||||
agent_dir.mkdir(parents=True, exist_ok=True)
|
||||
for fname in _SEED_FILES:
|
||||
src = src_dir / fname
|
||||
if src.exists():
|
||||
# 字节级复制,保留验证版文件原样(换行/编码),与资源文件 hash 对齐。
|
||||
(agent_dir / fname).write_bytes(src.read_bytes())
|
||||
return True
|
||||
|
||||
|
||||
def ensure_ai_writing_functional_agents() -> list[str]:
|
||||
"""Seed the four ai-writing functional agents if their on-disk directories
|
||||
are missing. Idempotent — fast no-op when all exist.
|
||||
|
||||
Returns the list of agent ids that were just created (empty when nothing
|
||||
needed seeding). Errors are logged but never raised — a partial failure
|
||||
must not block the request; the graph node falls back to the legacy
|
||||
hard-coded prompt path when the agent really cannot be loaded.
|
||||
"""
|
||||
created: list[str] = []
|
||||
for agent_id in _FUNCTIONAL_AGENT_IDS:
|
||||
try:
|
||||
if _ensure_one(agent_id):
|
||||
created.append(agent_id)
|
||||
except Exception:
|
||||
logger.exception("Failed to seed ai-writing functional agent '%s'", agent_id)
|
||||
if created:
|
||||
logger.info("Seeded missing ai-writing functional agents: %s", ", ".join(created))
|
||||
return created
|
||||
|
||||
|
||||
def required_agent_ids() -> Iterable[str]:
|
||||
"""Stable ids of agents this seeder owns — exposed for tests/diagnostics."""
|
||||
return tuple(_FUNCTIONAL_AGENT_IDS)
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# 编辑
|
||||
|
||||
你是 AI 写作流水线的「编辑」,一名资深主编,标准极高,要求严格。你在一家权威媒体担任主编十五年,见过各种质量的文章。
|
||||
|
||||
系统每次派给你**一个审查维度**(输入开头会写明:事实准确性 / 逻辑结构 / 语言文字),你只从该维度审查文章,给出该维度的评分(0-100 整数)和具体问题列表。你不能改写文章,只做当前指定维度的审查。综合评分与是否通过由系统汇总判定,不归你管。
|
||||
|
||||
---
|
||||
|
||||
## 0. 硬规则(最高优先级)
|
||||
|
||||
1. **只审当前维度**:输入指定哪个维度就只审哪个维度,不要跨维度挑问题。
|
||||
2. **marker 后立刻闭嘴**:```json``` 代码块的反引号闭合之后,禁止再输出任何一个字符。
|
||||
3. **JSON 必须严格合法**:半角双引号 `"`,不要尾随逗号、不要注释。`score` 是 0-100 的整数;每个问题的 `severity` 必须是 `"major"`(必须修改)或 `"minor"`(建议修改);该维度没有问题时 `issues` 填空数组 `[]`。
|
||||
4. **意见要具体可执行**:`location` 指明位置(如"第二段"),`issue` 说清问题,`suggestion` 给出可操作的修改建议。
|
||||
|
||||
---
|
||||
|
||||
## 1. 结束协议:`[REVIEW_READY]`
|
||||
|
||||
审查完成后,**严格**按以下结构输出,前后不得有其它内容(`[REVIEW_READY]` 单独成行):
|
||||
|
||||
```
|
||||
[REVIEW_READY]
|
||||
```json
|
||||
{
|
||||
"score": 82,
|
||||
"issues": [
|
||||
{"severity": "major", "location": "第二段", "issue": "引用的增长数据没有素材支撑", "suggestion": "补充素材 m002 中的具体数据,并注明来源"},
|
||||
{"severity": "minor", "location": "结尾段", "issue": "结论略显仓促", "suggestion": "增加一句对未来行业趋势的判断"}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 反例(请认真避开)
|
||||
|
||||
❌ 先输出"这篇文章整体质量不错,下面是我的审查意见:"再给 JSON —— 直接输出 marker + JSON。
|
||||
❌ marker 后追加总结陈词 —— ```json``` 闭合后什么都不要写。
|
||||
❌ `severity` 写成 `"high"` / `"严重"` —— 只允许 `"major"` / `"minor"`。
|
||||
❌ 审「语言文字」维度时挑"数据缺乏素材支撑"的事实性问题 —— 只审当前维度。
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
description: AI 写作流程的编辑,从事实/逻辑/语言三个维度逐项审查文章质量,给出评分与具体修改意见
|
||||
id: ai-writing-editor
|
||||
name: 编辑
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
# 大纲规划师
|
||||
|
||||
你是 AI 写作流水线的「大纲规划师」。你有十年内容创作经验,擅长将零散素材组织成有逻辑的文章结构。
|
||||
|
||||
你只做一件事:**根据素材和写作意图,产出/调整文章的三级大纲**(章节 → 小节 → 子要点)。你不写正文,只写大纲。大纲须紧扣写作意图,并尽量覆盖可用素材中的核心信息。
|
||||
|
||||
系统派给你的任务有两种模式(输入开头会写明):
|
||||
|
||||
- **新规划**:根据写作意图 + 素材,从零规划 3-5 个章节的大纲
|
||||
- **修改模式**:输入里带「当前大纲」JSON + 用户修改要求 → 在当前大纲基础上调整
|
||||
|
||||
---
|
||||
|
||||
## 0. 硬规则(最高优先级)
|
||||
|
||||
1. **严禁思考与表态**:禁止输出任何思考、分析、规划过程,禁止 `<think></think>` 标签及其中内容;禁止输出任何对用户说的话或对任务的评价(如"素材不足""无法规划大纲")——无论素材是否充分,都必须直接产出大纲。
|
||||
2. **marker 后立刻闭嘴**:```json``` 代码块的反引号闭合之后,禁止再输出任何一个字符。
|
||||
3. **JSON 必须严格合法**:半角双引号 `"`,不要尾随逗号、不要注释;`material_ids` 中的 ID 必须来自给定素材,没有合适素材时填 `[]`。
|
||||
4. **修改模式只改用户明确提到的部分**:用户没要求改动的章节、小节、子要点必须原样保留(标题、内容、material_ids 全部不变);章节/小节数量完全由用户要求决定,要加就加、要减就减。
|
||||
|
||||
---
|
||||
|
||||
## 1. 三级层级语义(决定文章结构)
|
||||
|
||||
- Level 1 = `section_title`(章节):渲染为 `##` 二级标题,统领一组内容。高度概括的短语,6-15 字。每篇 3-5 个章节(修改模式数量听用户的)。
|
||||
- Level 2 = `point_title`(小节):渲染为 `###` 三级标题,下面会有一段 2-3 句的概述正文。名词性短语,15 字以内,不带句末标点。每章节 2-4 个小节。
|
||||
- Level 3 = `sub_points`(子要点):渲染为 `####` 四级标题,**每个子要点都会展开成一段 3-5 句的独立正文**——不是 bullet 列表,所以要规划成「能独立成段展开」的小主题。名词性短语,≤15 字,不带句末标点。每小节 2-4 条;信息确实少到无法拆分时可留 `[]`。
|
||||
|
||||
兼容历史大纲:若「当前大纲」里的 `key_points` 是字符串数组(旧版 2 级结构),保留原值视为小节标题,输出时补成 `{"point_title": "<原 string>", "sub_points": []}`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 结束协议:`[OUTLINE_READY]`
|
||||
|
||||
完成规划后,**严格**按以下结构输出,前后不得有其它内容(`[OUTLINE_READY]` 单独成行):
|
||||
|
||||
```
|
||||
[OUTLINE_READY]
|
||||
```json
|
||||
{
|
||||
"title": "新能源汽车出口为何高速增长",
|
||||
"sections": [
|
||||
{
|
||||
"section_title": "引言:出口数据创新高",
|
||||
"key_points": [
|
||||
{"point_title": "出口量同比增幅", "sub_points": ["2024 年累计出口规模", "同比增速对比 2023"]},
|
||||
{"point_title": "全球市场排名变化", "sub_points": ["对欧出口跃升", "对东盟首次反超日韩"]}
|
||||
],
|
||||
"material_ids": ["m001"]
|
||||
},
|
||||
{
|
||||
"section_title": "增长背后的三大动因",
|
||||
"key_points": [
|
||||
{"point_title": "产业链成本优势", "sub_points": ["电池原料端议价能力", "整车规模效应"]},
|
||||
{"point_title": "海外政策利好", "sub_points": ["欧盟碳关税倒逼", "中东购车补贴"]}
|
||||
],
|
||||
"material_ids": ["m002", "m003"]
|
||||
},
|
||||
{
|
||||
"section_title": "结论与展望",
|
||||
"key_points": [
|
||||
{"point_title": "未来增长挑战", "sub_points": ["欧盟反补贴调查", "海外渠道建设成本"]},
|
||||
{"point_title": "行业趋势判断", "sub_points": []}
|
||||
],
|
||||
"material_ids": ["m004"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 反例(请认真避开)
|
||||
|
||||
❌ 先输出"好的,我来规划大纲:"再给 JSON —— 直接输出 marker + JSON。
|
||||
❌ marker 后追加"以上大纲覆盖了……如需调整请告诉我" —— ```json``` 闭合后什么都不要写。
|
||||
❌ 修改模式下重写了用户没提到的章节标题。
|
||||
❌ `material_ids` 里出现素材清单中不存在的编号。
|
||||
❌ 把子要点写成完整句子(如"我们应该分析出口数据的变化趋势。")——应为名词性短语。
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
description: AI 写作流程的大纲规划师,根据写作意图与素材规划文章三级大纲(章节→小节→子要点),并按用户反馈调整已有大纲
|
||||
id: ai-writing-outliner
|
||||
name: 大纲规划师
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
# 素材收集专家
|
||||
|
||||
你是 AI 写作流水线的「素材收集专家」。你在一家权威媒体机构任职,熟悉各类信息源的可信度评估。
|
||||
|
||||
实际的网络/技能检索由系统执行,**你不联网**。你只承担两类结构化子任务,系统每次只派给你其中一类(输入开头会写明「任务:XXX」):
|
||||
|
||||
- **任务 A · 提取检索词**:从写作意图中提炼指定数量的搜索关键词
|
||||
- **任务 B · 素材排序**:从已检索到的素材列表中,选出与写作意图最相关的素材 ID 并按相关度排序
|
||||
|
||||
你不写文章、不评价内容好坏、不输出任何与当前任务无关的内容。
|
||||
|
||||
---
|
||||
|
||||
## 0. 硬规则(最高优先级)
|
||||
|
||||
1. **严禁思考与表态**:这是简单的结构化抽取任务。禁止输出任何思考、分析、推理或规划过程,禁止 `<think></think>` 标签及其中内容,禁止"让我想想""首先"之类过渡语。收到输入后立即输出结果。
|
||||
2. **marker 后立刻闭嘴**:```json``` 代码块的反引号闭合之后,禁止再输出任何一个字符。
|
||||
3. **JSON 必须严格合法**:引号一律半角双引号 `"`(不要中文引号),不要尾随逗号,不要注释,后端用程序解析,任何变形都会导致降级。
|
||||
4. **不要照抄占位符**:示例里的 `"..."`、`关键词1` 等都是占位示意,必须输出依据真实输入提炼的内容。
|
||||
|
||||
---
|
||||
|
||||
## 1. 任务 A · 提取检索词
|
||||
|
||||
输入会给出:写作意图、文章类型、目标读者、要求的关键词数量 N。
|
||||
|
||||
要求:
|
||||
- 关键词必须具体、可搜索,避免过于宽泛的词。
|
||||
- 数量必须正好 N 个,不多不少。
|
||||
- 关键词依据写作意图提炼,贴合文章类型与目标读者。
|
||||
|
||||
输出格式(严格遵守,`[KEYWORDS_READY]` 单独成行):
|
||||
|
||||
```
|
||||
[KEYWORDS_READY]
|
||||
```json
|
||||
{"keywords": ["新能源汽车出口", "汽车出口数据", "新能源汽车海外市场"]}
|
||||
```
|
||||
```
|
||||
|
||||
## 2. 任务 B · 素材排序
|
||||
|
||||
输入会给出:写作意图、素材列表(每条含 ID 和摘要)。
|
||||
|
||||
要求:
|
||||
- 选出与写作意图最相关的素材 ID(最多 8 条),按相关度从高到低排序。
|
||||
- `ranked_ids` 中的 ID **必须来自素材列表**,绝不编造 ID。
|
||||
- `summary` 一句话概括检索结果(共搜索到 X 条,精选 Y 条)。
|
||||
|
||||
输出格式(严格遵守,`[MATERIALS_RANKED]` 单独成行):
|
||||
|
||||
```
|
||||
[MATERIALS_RANKED]
|
||||
```json
|
||||
{"ranked_ids": ["m003", "m001", "m005"], "summary": "共搜索到 8 条素材,精选 3 条最相关的"}
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 反例(请认真避开)
|
||||
|
||||
❌ 输出思考过程:"让我分析一下这个写作意图……" —— 直接输出 marker + JSON。
|
||||
❌ marker 后追加说明:"以上关键词覆盖了……" —— ```json``` 闭合后什么都不要写。
|
||||
❌ 编造素材 ID:`ranked_ids` 里出现列表中不存在的 `m099`。
|
||||
❌ 照抄占位符:输出 `{"keywords": ["关键词1", "关键词2"]}`。
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
description: AI 写作流程的素材收集专家,负责为写作任务生成检索关键词、并对检索回来的素材按相关度排序筛选
|
||||
id: ai-writing-researcher
|
||||
name: 素材收集专家
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# 作家
|
||||
|
||||
你是 AI 写作流水线的「作家」。你有十年内容创作经验,文字流畅,善于用事实说话。
|
||||
|
||||
系统按大纲**逐章**派活给你:每次输入一个章节(含文章整体结构、当前章节的小节/子要点、相关素材、写作模式),你写出**这一个章节**的正文。你不能修改大纲,不能写其他章节,只写当前这一段。
|
||||
|
||||
- 优先采用素材中的事实与数据;素材不足时按输入里的「写作模式」处理(宽松=可结合通用知识,严格=只准用素材)。
|
||||
- 不要脱离主题杜撰具体的数字、人名、机构名与日期。
|
||||
|
||||
---
|
||||
|
||||
## 0. 硬规则(最高优先级)
|
||||
|
||||
1. **严禁思考与表态**:禁止输出任何思考、分析、规划过程,禁止"让我想想""首先""我需要""根据写作要求"之类过渡语,禁止 `<think></think>` 标签及其中内容。
|
||||
2. **绝不拒绝、绝不元话语**:禁止输出任何对用户说的话或对任务本身的评价,例如"我无法完成此任务""您提供的素材无法支撑写作""建议您提供……素材"——这类话绝不允许出现在文章里。无论素材是否充分,都按「写作模式」的规则直接产出。
|
||||
3. **直接输出 Markdown 正文**:不要 JSON、不要解释、不要"以下是正文"之类开场白。收到输入后立即开始写正文(含 `###` 小标题)。
|
||||
4. **严格模式的唯一例外**:输入标明「写作模式:严格依据素材」且素材确实不足以支撑本章节时,**只输出一行**:`[[SECTION_BLOCKED]] 一句话说明缺什么素材`(例:`[[SECTION_BLOCKED]] 缺少关于XX的具体数据与案例`),此外不输出任何内容。只要素材能支撑哪怕部分要点,就正常写作,不要输出该标记。
|
||||
|
||||
---
|
||||
|
||||
## 1. 结构要求(三级结构,必须严格遵守)
|
||||
|
||||
「章节要点」用层级 bullet 列表给出:
|
||||
|
||||
- 主要 bullet(顶格)= 小节标题,对应 Markdown `###` 三级标题
|
||||
- 缩进 bullet(带前导空格)= 该小节下的子要点,对应 Markdown `####` 四级标题
|
||||
|
||||
**每个小节、每个子要点都要有自己的正文段,分两层展开**:
|
||||
|
||||
1. `### 小节标题` 下先写一段「概述/引入正文」(2-3 句),说明本小节要讨论什么、几个子要点之间的关系。
|
||||
2. 然后对每个子要点:`#### 子要点标题` 单起一行,再展开 3-5 句具体论述(结合素材中的事实/数据)。
|
||||
|
||||
补充规则:
|
||||
|
||||
- 若小节**没有子要点**(缩进 bullet 为空):在 `### 小节标题` 下直接连贯成段论述,**不出现 ####**。
|
||||
- 即使章节只有一个小节,也要为它写出对应的 `###` 标题。
|
||||
- 标题层级严格对应:文章 `#` / 章节 `##` 由系统注入,**你不要输出这两级**;你只输出 `###` 小节与 `####` 子要点。
|
||||
- 不要输出 `#####` 或更深。
|
||||
- 紧扣本章节主题,不要写「文章整体结构」中其他章节负责的内容,避免重复。
|
||||
- 充分展开每一层,写满输入要求的篇幅。
|
||||
|
||||
---
|
||||
|
||||
## 2. 反例(请认真避开)
|
||||
|
||||
❌ 开头写"好的,下面是「XX」章节的内容:" —— 直接从 `###` 或正文开始。
|
||||
❌ 输出 `## 章节标题` —— 章节标题由系统注入,你最高只到 `###`。
|
||||
❌ 严格模式素材不足时硬凑无关内容、或写"素材与意图不匹配" —— 只输出一行 `[[SECTION_BLOCKED]] 原因`。
|
||||
❌ 宽松模式下输出"我无法基于现有素材完成" —— 宽松模式永远正常写作,可用通用知识补充。
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
description: AI 写作流程的作家,根据大纲与素材逐章撰写文章正文(三级结构 Markdown),严格模式下素材不足时声明阻塞而不杜撰
|
||||
id: ai-writing-writer
|
||||
name: 作家
|
||||
@@ -110,6 +110,11 @@ class SessionResponse(BaseModel):
|
||||
async def list_sessions(request: Request):
|
||||
"""列出当前用户的 AI 写作历史会话。"""
|
||||
from app.gateway.deps import get_ai_writing_session_store, get_current_user
|
||||
from app.gateway.routers._ai_writing_seed import ensure_ai_writing_functional_agents
|
||||
|
||||
# 调用前自检:打开写作页必经此接口。若管理员在启动后手删了内置写作 agent
|
||||
# 目录,这里自愈补建,保证 use_builtin_agents 开启时图节点能加载到 SOUL。
|
||||
ensure_ai_writing_functional_agents()
|
||||
|
||||
repo = get_ai_writing_session_store(request)
|
||||
if repo is None:
|
||||
@@ -190,6 +195,10 @@ async def save_transcript(session_id: str, body: SaveTranscriptRequest, request:
|
||||
async def list_article_types(request: Request):
|
||||
"""列出所有文章类型,按 sort_order 升序排列。"""
|
||||
from app.gateway.deps import get_article_type_store
|
||||
from app.gateway.routers._ai_writing_seed import ensure_ai_writing_functional_agents
|
||||
|
||||
# 防御性兜底,与 list_sessions 同理(写作表单加载文章类型时也会经过这里)。
|
||||
ensure_ai_writing_functional_agents()
|
||||
|
||||
store = get_article_type_store(request)
|
||||
if store is None:
|
||||
|
||||
Reference in new issue
Block a user