فهرست منبع

feat(query): 目的点动作词前缀外置为配置 + 扩到 59 词(覆盖率 78%→99.8%)

- 新增 product_documents/配置/purpose_point_prefixes.v1.json(59 个动作词;实测 execution 581 的 12240 个目的点覆盖 99.8%、零误剥)。
- search_intent._cleanup_purpose_point 改为运行时从该 JSON 读(config_store 按 mtime 缓存),代码不再写死任何前缀表;读不到/为空则不剥前缀并告警(不静默退回旧表)。
- 以后再漏词只改 JSON 加一行,无需改代码。仅对 point_type=目的点 生效,灵感点不动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sam Lee 3 هفته پیش
والد
کامیت
b1d91d66e9
2فایلهای تغییر یافته به همراه38 افزوده شده و 1 حذف شده
  1. 26 1
      content_agent/business_modules/search_intent.py
  2. 12 0
      product_documents/配置/purpose_point_prefixes.v1.json

+ 26 - 1
content_agent/business_modules/search_intent.py

@@ -1,10 +1,13 @@
 from __future__ import annotations
 
+import logging
 from datetime import datetime, timezone
+from pathlib import Path
 from typing import Any
 
 from content_agent.constants import RUNTIME_RECORD_SCHEMA_VERSION
 from content_agent.errors import ContentAgentError, ErrorCode
+from content_agent.integrations import config_store
 from content_agent.integrations.query_prompt_config import DEFAULT_PROFILE
 from content_agent.interfaces import QueryVariantClient, QueryVariantResult, RuntimeFileStore
 from content_agent.record_payload import with_raw_payload
@@ -304,9 +307,31 @@ def _sample_elements(binding: dict[str, Any]) -> list[dict[str, Any]]:
     return [item for item in values if isinstance(item, dict)] if isinstance(values, list) else []
 
 
+_LOG = logging.getLogger(__name__)
+_PURPOSE_PREFIX_PATH = Path("product_documents/配置/purpose_point_prefixes.v1.json")
+
+
+def _purpose_point_prefixes() -> tuple[str, ...]:
+    """目的点要剥的动作词前缀:**全部来自 JSON 配置**(config_store 按 mtime 缓存),代码不写死任何前缀表。
+    以后加词只改 `product_documents/配置/purpose_point_prefixes.v1.json`。
+    读不到/为空 → 不剥前缀并**告警**(而非静默退回旧表,免得配置没生效却看着像正常)。"""
+    try:
+        data, _ = config_store.load_json(_PURPOSE_PREFIX_PATH)
+        prefixes = data.get("prefixes") if isinstance(data, dict) else None
+        if isinstance(prefixes, list):
+            cleaned = tuple(str(p) for p in prefixes if p)
+            if cleaned:
+                return cleaned
+    except (FileNotFoundError, OSError, ValueError) as exc:
+        _LOG.warning("目的点前缀配置读取失败(%s),本次不剥前缀:%s", _PURPOSE_PREFIX_PATH, exc)
+        return ()
+    _LOG.warning("目的点前缀配置缺 prefixes 或为空(%s),本次不剥前缀", _PURPOSE_PREFIX_PATH)
+    return ()
+
+
 def _cleanup_purpose_point(value: str) -> str:
     text = _normalize_query(value)
-    for prefix in ("分享", "讲述", "表达", "传递", "呼吁", "介绍", "展示", "记录", "呈现"):
+    for prefix in _purpose_point_prefixes():
         if text.startswith(prefix) and len(text) > len(prefix):
             text = text[len(prefix):].strip(" ,,。::")
             break

+ 12 - 0
product_documents/配置/purpose_point_prefixes.v1.json

@@ -0,0 +1,12 @@
+{
+  "schema_version": "purpose_point_prefixes.v1",
+  "note": "目的点开头的动作词:搜索取词时剥掉只留题材(如「分享历史人物事迹」→「历史人物事迹」)。实测 execution 581 的 12240 个目的点,这份覆盖 ~99.7%、零误剥。以后再漏直接往 prefixes 里加词即可,不用改代码(search_intent._cleanup_purpose_point 运行时读本文件,config_store 按 mtime 缓存)。仅对 point_type=目的点 生效;灵感点原样不剥。",
+  "prefixes": [
+    "分享", "讲述", "表达", "传递", "呼吁", "介绍", "展示", "记录", "呈现",
+    "科普", "普及", "讲解", "解读", "解析", "教授", "教学", "分析", "探讨", "报道", "评论", "揭露", "警示",
+    "推广", "推荐", "种草", "引流", "引导", "传播", "宣传", "宣扬",
+    "缅怀", "致敬", "弘扬", "庆祝", "倡导", "激发", "维护", "发送", "互动",
+    "揭秘", "纪念", "解说", "解释", "阐述", "剖析", "批判", "曝光", "提醒", "祝福",
+    "促进", "传授", "演绎", "诱导", "澄清", "发起", "悼念", "探店", "招募", "预告"
+  ]
+}