From 294d06f22b69794a4b189a1eecfdec19a52bed09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Tue, 19 May 2026 13:00:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/docs/素材专家检索.md | 14 ++++++++ .../src/open-canvas/api/ai-writing.ts | 1 + .../components/ai-writing/AIWritingForm.tsx | 32 +++++++++++++++++++ .../components/ai-writing/AIWritingPanel.tsx | 3 ++ .../src/open-canvas/types/ai-writing.ts | 2 ++ .../backend/app/gateway/routers/ai_writing.py | 4 +++ .../agents/ai_writing/nodes/researcher.py | 23 ++++++++++++- .../ai_writing/prompts/researcher_prompts.py | 5 +-- .../deerflow/agents/ai_writing/state.py | 2 ++ 9 files changed, 83 insertions(+), 3 deletions(-) diff --git a/frontend-web/docs/素材专家检索.md b/frontend-web/docs/素材专家检索.md index d4ddb29b..2d66b605 100644 --- a/frontend-web/docs/素材专家检索.md +++ b/frontend-web/docs/素材专家检索.md @@ -32,6 +32,20 @@ ai_writing: --- +## 1.5 检索关键词数量 + +素材专家的检索词数量由**用户在写作表单中指定**(「检索关键词数量」输入框, +范围 `1~10`,默认 `6`)。数量越多素材越全面,但检索与排序更耗时。 + +- 该参数随 `POST /api/ai-writing/start` 的 `keyword_count` 字段传入,写入 `AIWritingState.keyword_count`。 +- `researcher_node` 用它决定让 LLM 生成多少个检索词、并发检索多少个方向 + (`EXTRACT_KEYWORDS_USER` prompt 按该数量格式化,`search_keywords = keywords[:keyword_count]`)。 +- 非法值(缺省 / 越界 / 非数字)由前后端双重兜底:后端 `_resolve_keyword_count()` + 规整到 `[1, 10]`,越界回退默认 `6`。 +- 补充搜索(`re_search`)时用户额外添加的关键词不会被该上限截断。 + +--- + ## 2. 内网接口规格 - **地址**:`http://10.106.57.180:7863/knowledge_base/search_docs_es` diff --git a/frontend-web/src/open-canvas/api/ai-writing.ts b/frontend-web/src/open-canvas/api/ai-writing.ts index 0305eecb..a0295ea9 100644 --- a/frontend-web/src/open-canvas/api/ai-writing.ts +++ b/frontend-web/src/open-canvas/api/ai-writing.ts @@ -21,6 +21,7 @@ export async function startWritingSession(req: AIWritingRequest): Promise([]); @@ -94,15 +95,27 @@ export function AIWritingForm({ onSubmit, disabled }: AIWritingFormProps) { } } + // 检索关键词数量限定 1~10,非法值回退默认 6 + function handleKeywordCountBlur() { + const num = parseInt(keywordCountInput, 10); + if (isNaN(num)) { + setKeywordCountInput('6'); + } else { + setKeywordCountInput(String(Math.max(1, Math.min(10, num)))); + } + } + function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!intent.trim()) return; const wordCount = Math.max(100, parseInt(wordCountInput, 10) || 800); + const keywordCount = Math.max(1, Math.min(10, parseInt(keywordCountInput, 10) || 6)); onSubmit({ userIntent: intent.trim(), articleType, targetAudience: audience.trim() || '普通读者', wordCountTarget: wordCount, + keywordCount, modelName: modelName || undefined, }); } @@ -158,6 +171,25 @@ export function AIWritingForm({ onSubmit, disabled }: AIWritingFormProps) { +
+ + setKeywordCountInput(e.target.value)} + onBlur={handleKeywordCountBlur} + disabled={disabled} + /> +

+ 素材收集专家用几个关键词检索,越多素材越全面但耗时更长。 +

+
+
{state.request.wordCountTarget} 字 + + {state.request.keywordCount} 个 + {state.request.modelName && ( {state.request.modelName} diff --git a/frontend-web/src/open-canvas/types/ai-writing.ts b/frontend-web/src/open-canvas/types/ai-writing.ts index 2e936da3..ddf5c294 100644 --- a/frontend-web/src/open-canvas/types/ai-writing.ts +++ b/frontend-web/src/open-canvas/types/ai-writing.ts @@ -135,6 +135,8 @@ export interface AIWritingRequest { targetAudience: string; wordCountTarget: number; modelName?: string; + /** 素材检索关键词数量(1~10,默认 6) */ + keywordCount: number; } // ── 流式审核条目 ────────────────────────────────────────────────────────────── diff --git a/offline-backend-20260512/backend/app/gateway/routers/ai_writing.py b/offline-backend-20260512/backend/app/gateway/routers/ai_writing.py index 6e923f41..9a592ab8 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/ai_writing.py +++ b/offline-backend-20260512/backend/app/gateway/routers/ai_writing.py @@ -40,6 +40,8 @@ class StartWritingRequest(BaseModel): target_audience: str = "普通读者" word_count_target: int = 800 model_name: str = "" + # 素材检索关键词数量:用户指定用几个关键词检索(1~10,默认 6) + keyword_count: int = 6 class StartWritingResponse(BaseModel): @@ -230,6 +232,8 @@ async def start_writing(body: StartWritingRequest, request: Request): "article_type": body.article_type, "target_audience": body.target_audience, "word_count_target": body.word_count_target, + # 规整到 [1, 10],非法值回退默认 6 + "keyword_count": max(1, min(10, body.keyword_count)), "revision_count": 0, "max_revisions": 3, "status": "researching", diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/researcher.py b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/researcher.py index d037e6a3..bc34cec2 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/researcher.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/researcher.py @@ -20,6 +20,19 @@ logger = logging.getLogger(__name__) _MAX_MATERIALS = 12 +# 素材检索关键词数量边界:用户可指定 1~10 个,非法值回退默认 6 +_DEFAULT_KEYWORD_COUNT = 6 +_MAX_KEYWORD_COUNT = 10 + + +def _resolve_keyword_count(raw: object) -> int: + """把用户传入的检索关键词数量规整到 [1, _MAX_KEYWORD_COUNT],非法值回退默认 6。""" + try: + n = int(raw) # type: ignore[arg-type] + except (TypeError, ValueError): + return _DEFAULT_KEYWORD_COUNT + return max(1, min(_MAX_KEYWORD_COUNT, n)) + def _notice(title: str, detail: str, raw: str = "") -> dict: """构造一个非致命告警事件,前端在时间线显示「标题 + 真实原因」,不打断写作流程。 @@ -41,6 +54,9 @@ async def researcher_node(state: AIWritingState, config: RunnableConfig) -> dict intervention = state.get("last_user_intervention") or {} extra_keywords: list[str] = intervention.get("_extra_keywords") or intervention.get("extra_keywords") or [] + # 用户指定的检索关键词数量(1~10),决定生成多少个检索词、检索多少个方向 + keyword_count = _resolve_keyword_count(state.get("keyword_count")) + # SSE 实时进度推送(与 outline/draft 节点共用 sse_queue 机制) cfg = config.get("configurable", {}) sse_queue = cfg.get("sse_queue") @@ -68,6 +84,7 @@ async def researcher_node(state: AIWritingState, config: RunnableConfig) -> dict user_intent=intent, article_type=article_type, target_audience=target_audience, + keyword_count=keyword_count, ), fallback={"keywords": [intent]}, config=config, @@ -80,7 +97,11 @@ async def researcher_node(state: AIWritingState, config: RunnableConfig) -> dict # Step 2: 并发搜索 provider = get_search_provider(ai_writing_config) - search_keywords = keywords[:6] + # 按用户指定的关键词数量截断;补充搜索时不截断用户额外添加的关键词 + search_limit = keyword_count + if extra_keywords: + search_limit = max(search_limit, len(keywords)) + search_keywords = keywords[:search_limit] total = len(search_keywords) await _emit(f"检索词已生成,开始检索 {total} 个方向…") diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/prompts/researcher_prompts.py b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/prompts/researcher_prompts.py index b8f7e576..b82863d2 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/prompts/researcher_prompts.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/prompts/researcher_prompts.py @@ -3,7 +3,7 @@ EXTRACT_KEYWORDS_SYSTEM = """你是一名专业情报研究员(素材收集专 你的工作背景:你在一家权威媒体机构任职,熟悉各类信息源的可信度评估。 你只能做以下事情: -1. 从写作意图中提取 3-6 个搜索关键词 +1. 从写作意图中按要求的数量提取搜索关键词 你不能写文章,不能评价内容好坏,只做关键词提取。 【严禁思考】 @@ -25,7 +25,8 @@ EXTRACT_KEYWORDS_USER = """写作意图:{user_intent} 文章类型:{article_type} 目标读者:{target_audience} -请提取 3-6 个搜索关键词,用于搜集相关素材。只输出 JSON: +请提取 {keyword_count} 个搜索关键词,用于搜集相关素材。 +关键词数量必须为 {keyword_count} 个,不多不少。只输出 JSON: {{"keywords": ["关键词1", "关键词2"]}}""" RANK_MATERIALS_SYSTEM = """你是一名专业情报研究员(素材收集专家)。 diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/state.py b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/state.py index 8d93f1f3..346b0e82 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/state.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/state.py @@ -84,6 +84,8 @@ class AIWritingState(TypedDict): article_type: str target_audience: str word_count_target: int + # 素材检索关键词数量(用户指定,1~10,默认 6) + keyword_count: int # Agent A 输出 material_package: Optional[MaterialPackage]