From 4f8b0f1c67f4434c01b67003122ec2b9fc7cf69a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Wed, 27 May 2026 14:19:11 +0800 Subject: [PATCH] =?UTF-8?q?ai=E5=86=99=E4=BD=9C=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agents/ai_writing/nodes/intent_parser.py | 84 ++++++++++++++----- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/intent_parser.py b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/intent_parser.py index e3845ce8..d5008461 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/intent_parser.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/agents/ai_writing/nodes/intent_parser.py @@ -83,11 +83,16 @@ _SYSTEM_TEMPLATE = """你是一名写作助手。 _USER_TEMPLATE = """用户写作意图:{user_intent} -请分析并输出以下 JSON(如果意图中未明确某字段,给出合理默认值): +用户在表单中已指定写作配置(以下三项必须原样输出,不要自行修改): +- 文章类型(article_type):{preconfigured_article_type} +- 目标读者(target_audience):{preconfigured_audience} +- 目标字数(word_count_target):{preconfigured_word_count} + +请输出以下 JSON,article_type / target_audience / word_count_target 必须与上述一致,只需根据用户意图精炼 refined_intent: {{{{ - "article_type": "{type_options} 之一", - "target_audience": "目标读者描述", - "word_count_target": 800, + "article_type": "{preconfigured_article_type}", + "target_audience": "{preconfigured_audience}", + "word_count_target": {preconfigured_word_count}, "refined_intent": "精炼后的写作意图(一句话)" }}}}""" @@ -124,6 +129,44 @@ def _coerce_word_count(raw) -> int | None: return value if value > 0 else None +def _resolve_intent_fields( + state: AIWritingState, + result: dict, + fallback_result: dict, + valid_keys: list[str], + default_key: str, +) -> tuple[str, str, int, str]: + """合并 LLM 解析结果与用户表单预填字段;预填字段优先(见 AI_WRITING_BACKEND_DEV_ZH §intent_parser)。""" + fallback_type = fallback_result["article_type"] + if fallback_type not in valid_keys: + fallback_type = default_key + + raw_type = result.get("article_type", "") + llm_type = raw_type if raw_type in valid_keys else fallback_type + + preconfigured_type = (state.get("article_type") or "").strip() + if preconfigured_type in valid_keys: + final_article_type = preconfigured_type + else: + final_article_type = llm_type + + preconfigured_audience = (state.get("target_audience") or "").strip() + if preconfigured_audience: + final_target_audience = preconfigured_audience + else: + final_target_audience = result.get("target_audience") or fallback_result["target_audience"] + + preconfigured_wc = state.get("word_count_target") + if isinstance(preconfigured_wc, int) and preconfigured_wc > 0: + final_word_count = preconfigured_wc + else: + word_count = _coerce_word_count(result.get("word_count_target")) + final_word_count = word_count if word_count is not None else fallback_result["word_count_target"] + + final_refined_intent = result.get("refined_intent") or state["user_intent"] + return final_article_type, final_target_audience, final_word_count, final_refined_intent + + async def intent_parser_node(state: AIWritingState, config: RunnableConfig) -> dict: # 从 configurable 读取动态类型列表,未配置时退回默认值 configurable = (config or {}).get("configurable", {}) @@ -137,10 +180,6 @@ async def intent_parser_node(state: AIWritingState, config: RunnableConfig) -> d type_options = _build_type_options(valid_article_types) system = _SYSTEM_TEMPLATE.format(type_options=type_options) - user = _USER_TEMPLATE.format( - user_intent=state["user_intent"], - type_options=" | ".join(valid_keys), - ) fallback_type = state.get("article_type", default_key) if fallback_type not in valid_keys: @@ -153,6 +192,17 @@ async def intent_parser_node(state: AIWritingState, config: RunnableConfig) -> d "refined_intent": state["user_intent"], } + preconfigured_wc = fallback_result["word_count_target"] + if not isinstance(preconfigured_wc, int) or preconfigured_wc <= 0: + preconfigured_wc = 800 + + user = _USER_TEMPLATE.format( + user_intent=state["user_intent"], + preconfigured_article_type=fallback_type, + preconfigured_audience=fallback_result["target_audience"], + preconfigured_word_count=preconfigured_wc, + ) + # 流式调用:与 writer_outline / writer_draft 同套机制 —— LLM 逐字吐出 JSON, # 通过 ``intent_chunk`` 推给前端打字机;完成后 ``parse_json`` 解析结构化结果, # 再通过 ``intent_ready`` 推一份结构化字段给前端结果卡片。 @@ -193,13 +243,9 @@ async def intent_parser_node(state: AIWritingState, config: RunnableConfig) -> d "detail": f"意图解析失败,已按默认配置兜底。原因:{llm_error}", }) - # 校验 LLM 返回的 article_type 是否在有效范围内,不合法时回退到默认值 - raw_type = result.get("article_type", "") - final_article_type = raw_type if raw_type in valid_keys else fallback_type - final_target_audience = result.get("target_audience") or fallback_result["target_audience"] - word_count = _coerce_word_count(result.get("word_count_target")) - final_word_count = word_count if word_count is not None else fallback_result["word_count_target"] - final_refined_intent = result.get("refined_intent") or state["user_intent"] + final_article_type, final_target_audience, final_word_count, final_refined_intent = _resolve_intent_fields( + state, result, fallback_result, valid_keys, default_key + ) # 推送结构化结果给前端 —— 结果卡片显示文章类型 / 目标读者 / 字数 / 精炼意图 writer({ @@ -214,11 +260,9 @@ async def intent_parser_node(state: AIWritingState, config: RunnableConfig) -> d updates: dict = {"status": "researching", "progress_events": progress_events} updates["article_type"] = final_article_type - if result.get("target_audience"): - updates["target_audience"] = final_target_audience - if word_count is not None: - updates["word_count_target"] = final_word_count - if result.get("refined_intent"): + updates["target_audience"] = final_target_audience + updates["word_count_target"] = final_word_count + if final_refined_intent != state["user_intent"]: updates["user_intent"] = final_refined_intent return updates