From 8edd06fb672140bc1df1bf7c356450bd50b5bc87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Wed, 20 May 2026 14:03:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/AI_WRITING_BACKEND_DEV_ZH.md | 78 +++++++++++++++++-- .../components/ai-writing/AIWritingForm.tsx | 4 +- offline-backend-20260512/backend/config.yaml | 4 + 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/frontend-web/docs/AI_WRITING_BACKEND_DEV_ZH.md b/frontend-web/docs/AI_WRITING_BACKEND_DEV_ZH.md index ab2f9732..e3177f4b 100644 --- a/frontend-web/docs/AI_WRITING_BACKEND_DEV_ZH.md +++ b/frontend-web/docs/AI_WRITING_BACKEND_DEV_ZH.md @@ -460,8 +460,9 @@ checkpointer: type: sqlite # sqlite | postgres | memory connection_string: .deer-flow/data/checkpoints.db -# 清理调度 +# AI 写作子系统 ai_writing: + # ── 清理调度 ───────────────────────────────────────────── cleanup: enabled: true retention_days: 7 @@ -469,13 +470,78 @@ ai_writing: delete_only_finished: false batch_size: 500 - # (可选)素材检索 provider 配置 - search: - provider: deerflow.agents.ai_writing.search.internet_search:WebSearchProvider - ... + # ── 素材收集专家(researcher)专用配置 ────────────────── + # 这两个模型名必须出现在顶层 models[] 段里;留空 / 不配则回退当前写作主模型。 + # 拆出来是为了让「检索词生成 / 素材排序」这种结构化抽取任务可以走更轻量、 + # 不带思考输出的小模型,省钱省时间,也避开写作主模型的思考干扰。 + keyword_model: qwen3-4b-instruct # 检索词生成(EXTRACT_KEYWORDS_*) + rank_model: qwen3-4b-instruct # 素材排序筛选(RANK_MATERIALS_*) + + # 素材总量上限:合并去重后保留多少条素材进入大纲 / 草稿阶段 + # 取值范围 [1, 100],非法值或缺省回退 20。 + # 每个检索词分到的检索条数 = ceil(max_materials / keyword_count), + # 所以这个数越大,每个关键词向 provider 拉的条数越多,整体耗时也越长。 + max_materials: 20 + + # ── 素材检索 provider ─────────────────────────────────── + # internet(默认,DuckDuckGo) / intranet(内网 ES) / tavily / demo + search_provider: internet + + # internet 模式:DuckDuckGo 区域代码(cn-zh / us-en / wt-wt 等) + ddg_region: "cn-zh" + + # intranet 模式:对接内网知识库 ES 检索接口 + # 接口契约(POST): + # payload = {"query": "...", "knowledge_base_name": "", "ts": "yes"} + # response = [{"page_content": "...", "metadata": {m_title, content, recUuid, m_publish, source1}}] + # 实现见 packages/harness/deerflow/agents/ai_writing/search/intranet_stub.py + intranet_search_url: http://10.106.57.180:7863/knowledge_base/search_docs_es + intranet_knowledge_base: samples # 默认 samples,按部署的实际知识库名改 ``` -`_ai_writing_config()` 函数从 `get_app_config().ai_writing` 里读 dict,节点通过 `config.configurable.ai_writing_config` 拿到。 +### 配置项与代码的对应关系 + +| 配置键 | 默认值 | 取值范围 / 说明 | 读取位置 | +|---|---|---|---| +| `ai_writing.keyword_model` | 空(回退写作主模型) | 必须是 `models[]` 段里已声明的模型名 | `nodes/researcher.py` → `llm_json(..., model_name=keyword_model)` | +| `ai_writing.rank_model` | 空(回退写作主模型) | 同上 | `nodes/researcher.py` → 排序分支 `llm_json(..., model_name=rank_model)` | +| `ai_writing.max_materials` | `20` | `[1, 100]`,非法值回退 20;`_resolve_max_materials` 钳制 | `nodes/researcher.py:_DEFAULT_MAX_MATERIALS` | +| `ai_writing.search_provider` | `"internet"` | `internet` / `intranet` / `tavily` / `demo` | `search/__init__.py:get_search_provider` | +| `ai_writing.ddg_region` | `"cn-zh"` | DuckDuckGo 区域代码,仅 `internet` 模式生效 | 同上 | +| `ai_writing.intranet_search_url` | `http://10.106.57.180:7863/knowledge_base/search_docs_es` | 仅 `intranet` 模式生效,POST JSON 接口 | `search/intranet_stub.py:_DEFAULT_ENDPOINT` | +| `ai_writing.intranet_knowledge_base` | `"samples"` | 仅 `intranet` 模式生效,知识库 ID/名称 | 同上 | + +### 配置如何传到节点 + +``` +config.yaml + └─ ai_writing: { ... } + │ + ▼ _ai_writing_config() 取出整个 dict + routers/ai_writing.py:_make_config() + │ 注入到 config.configurable.ai_writing_config + ▼ + nodes/researcher.py + cfg = config.get("configurable", {}) + ai_writing_config = cfg.get("ai_writing_config", {}) + keyword_model = ai_writing_config.get("keyword_model") + rank_model = ai_writing_config.get("rank_model") + max_materials = _resolve_max_materials(ai_writing_config.get("max_materials")) + provider = get_search_provider(ai_writing_config) # ← search_provider 等也从这里读 +``` + +### 切换内网检索的最小步骤 + +1. `config.yaml` 改 `ai_writing.search_provider: intranet` +2. 填 `intranet_search_url` 和 `intranet_knowledge_base`(部署侧给) +3. 内网 ES 接口若返回结构异常 / 网络抖动,researcher 节点会收集 `provider.errors` + `provider.raw_responses`,作为非致命 `notice` 事件推前端时间线(折叠显示原始响应体),**不会中断写作流程** +4. 健康检查:可通过 `IntranetSearchProvider.health_check()` 单测,或直接发一笔 `/start` 看 `research_progress` 是否报错 + +### 备注 + +- `keyword_model` / `rank_model` 只影响素材收集专家的 LLM 调用,**不影响**大纲、草稿、编辑审核三个节点(它们各自走自己的主模型)。 +- `max_materials` 是「合并去重后的总量上限」,不是每个关键词的检索条数;每个词的检索数会按 `ceil(max_materials / keyword_count)` 均分。`keyword_count` 由前端启动写作时传入(1~10,默认 6)。 +- `intranet` 模式不带 fallback;如果内网 ES 全挂,researcher 拿到空结果会直接进入「未检索到素材,可重新搜索或在无素材的情况下继续写作」分支。如果想让内网失败时退回互联网检索,需要在 `get_search_provider` 里手动用 `_FallbackSearchProvider(IntranetSearchProvider(...), DuckDuckGoSearchProvider(...))` 包一层。 --- diff --git a/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx b/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx index db0d3ecd..11b7dd23 100644 --- a/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx +++ b/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx @@ -36,7 +36,7 @@ export function AIWritingForm({ onSubmit, disabled }: AIWritingFormProps) { const [articleType, setArticleType] = useState('analysis'); const [audience, setAudience] = useState('普通读者'); const [wordCountInput, setWordCountInput] = useState('800'); - const [keywordCountInput, setKeywordCountInput] = useState('6'); + const [keywordCountInput, setKeywordCountInput] = useState('1'); const [strictMode, setStrictMode] = useState(false); const [modelName, setModelName] = useState(''); const [models, setModels] = useState([]); @@ -100,7 +100,7 @@ export function AIWritingForm({ onSubmit, disabled }: AIWritingFormProps) { function handleKeywordCountBlur() { const num = parseInt(keywordCountInput, 10); if (isNaN(num)) { - setKeywordCountInput('6'); + setKeywordCountInput('1'); } else { setKeywordCountInput(String(Math.max(1, Math.min(10, num)))); } diff --git a/offline-backend-20260512/backend/config.yaml b/offline-backend-20260512/backend/config.yaml index b7324994..ea59ee68 100644 --- a/offline-backend-20260512/backend/config.yaml +++ b/offline-backend-20260512/backend/config.yaml @@ -185,3 +185,7 @@ memory: block_invisible_unicode: true # streaming_scrubber:预留字段,代码尚未实现 —— 待版本支持后取消注释。 # streaming_scrubber: true # SSE 流前清洗 + +keyword_model: deepseek-chat +rank_model: deepseek-chat +max_materials: 20