#47a: 红头/标题字号颜色从 render_html 产物解析(新增 docgen/html_css.py: 内嵌 style 块 + 调用方传入的模板 css,组合选择器展开、px->pt),解析失败 回退历史常量(FF0000/22pt、h1/h2/h3 22/16/14pt,行为不变)。converter 与 导出路由把 css 传入 DOCX 转换,与 PDF/预览同源;非红头模板(如 report 灰 #333)不误渲染红头。r3 的 doc-body 包裹/表格/图片结构不回归。 #52: intent.py WRITE_VERB 补总结/概括/归纳,新增动词自指保护(唯一命中的 DOC_TYPE 等于命中动词本身时不算写文档意图,如「总结一下植物光合作用的过程」 仍 qa);cases_intent.md 同步规则与用例 31-34(共 34 条)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
104 lines
4.4 KiB
Python
104 lines
4.4 KiB
Python
"""services/docgen/intent.py —— 服务端意图路由(规则词典法)。
|
||
|
||
唯一事实源:docs/team/cases_intent.md(30 条用例表,前后端同表测试)。
|
||
漏判安全原则:不命中一律 qa。
|
||
|
||
判定顺序(按优先级,命中即返回):
|
||
1. export:has_active_artifact 且 EXPORT_WORD + FORMAT_WORD
|
||
2. edit:has_active_artifact 且 EDIT_WORD
|
||
3. docgen:WRITE_VERB + DOC_TYPE
|
||
4. docgen:has_attachments + WRITE_VERB
|
||
5. 其余 qa
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
# ---- 词典(与 cases_intent.md 逐条一致,不得自造)----
|
||
|
||
EXPORT_WORD = ["导出", "转成", "转为", "转换成", "下载成", "输出为", "存为", "转pdf", "转word"]
|
||
FORMAT_WORD = ["pdf", "docx", "doc", "word"] # 英文不区分大小写(contains 判定)
|
||
|
||
EDIT_WORD = ["改", "修改", "调整", "删", "删除", "加一段", "补充", "润色",
|
||
"缩写", "扩写", "重写", "改写", "优化", "加个", "增加一段"]
|
||
|
||
WRITE_VERB = ["写", "撰写", "拟", "起草", "编制", "生成", "编写", "拟定", "草拟", "制定",
|
||
"总结", "概括", "归纳"] # r4 #52(2026-09-11)
|
||
|
||
DOC_TYPE = [
|
||
"论文", "报告", "通知", "通告", "公告", "请示", "批复", "函", "申请书",
|
||
"申请", "申报书", "标书", "合同", "协议", "方案", "计划", "总结", "汇报",
|
||
"讲话稿", "致辞", "证明", "纪要", "简报", "材料", "说明书", "指南", "文档",
|
||
"申报材料", "工作总结", "调研报告", "可行性报告", "项目书", "策划书",
|
||
"演讲稿", "新闻稿", "文案", "简历", "制度", "规定", "办法", "细则", "章程",
|
||
"声明", "邀请函", "感谢信", "慰问信", "介绍信", "推荐信", "倡议书",
|
||
"述职报告", "开题报告", "结题报告", "实验报告", "分析报告", "会议纪要",
|
||
]
|
||
|
||
# 明确排除(防误判,不属于 DOC_TYPE)
|
||
DOC_TYPE_EXCLUDE = ["介绍", "内容", "代码", "诗", "作业", "问题", "建议", "想法"]
|
||
|
||
|
||
def _contains_any(text: str, words: list[str]) -> bool:
|
||
"""text 含 words 任一子串。"""
|
||
return any(w in text for w in words)
|
||
|
||
|
||
def _contains_any_ci(text: str, words: list[str]) -> bool:
|
||
"""不区分大小写匹配(英文词典用)。"""
|
||
lower = text.lower()
|
||
return any(w.lower() in lower for w in words)
|
||
|
||
|
||
def _has_doc_type(text: str) -> bool:
|
||
"""含 DOC_TYPE 任一,且不含 DOC_TYPE_EXCLUDE 任一(排除优先)。"""
|
||
if _contains_any(text, DOC_TYPE_EXCLUDE):
|
||
return False
|
||
return _contains_any(text, DOC_TYPE)
|
||
|
||
|
||
def route(text: str, has_attachments: bool, has_active_artifact: bool) -> str:
|
||
"""意图路由:qa | docgen | edit | export。
|
||
|
||
判定顺序按 cases_intent.md 冻结表,漏判安全(不命中一律 qa)。
|
||
"""
|
||
if not text or not text.strip():
|
||
return "qa"
|
||
|
||
# 1. export(需有活动文档)
|
||
if has_active_artifact:
|
||
if _contains_any(text, EXPORT_WORD) and _contains_any_ci(text, FORMAT_WORD):
|
||
return "export"
|
||
# 2. edit(需有活动文档)
|
||
if _contains_any(text, EDIT_WORD):
|
||
return "edit"
|
||
|
||
# 3. docgen:写类动词 + 文档类型词
|
||
# r4 #52(2026-09-11)动词自指保护:当唯一命中的 DOC_TYPE 恰好等于
|
||
# 命中的动词本身(如「总结一下植物光合作用的过程」——「总结」既是动词
|
||
# 又是文档类型词),不视为写文档意图(继续向下判定)。「写一篇总结」
|
||
# (动词=写,类型=总结)与「总结这篇论文」(动词=总结,类型=论文)
|
||
# 不受影响。规则详见 docs/team/cases_intent.md 用例 31-34。
|
||
_matched_verbs = [w for w in WRITE_VERB if w in text]
|
||
_matched_types = [w for w in DOC_TYPE if w in text]
|
||
if _matched_verbs and _matched_types and _has_doc_type(text):
|
||
if set(_matched_types) != set(_matched_verbs):
|
||
return "docgen"
|
||
|
||
# 4. docgen:附件信号(拖入文件 + 写类动词)
|
||
if has_attachments and _contains_any(text, WRITE_VERB):
|
||
return "docgen"
|
||
|
||
# 5. 漏判安全:其余一律 qa
|
||
return "qa"
|
||
|
||
|
||
async def is_docgen_intent(text: str) -> bool:
|
||
"""dev2 护栏布尔接口(async 契约):route(...) == "docgen"。
|
||
|
||
dev2 的 generate/edit/export 均 await 此函数;路由异常时护栏
|
||
放行(与 dev2 桩行为一致,护栏不阻断)。
|
||
"""
|
||
try:
|
||
return route(text, has_attachments=False, has_active_artifact=False) == "docgen"
|
||
except Exception:
|
||
return True
|