- prompts/superpowers_{review,answer_quality,minimal_code}.md + prompt_pack.py
- agentic_controller/answer_composer Superpowers 注入点(env 总闸,关态逐字节一致)
- compile_qa.sh: prompts/ 目录自动拷贝
- humanizer: CJK 逗号规则 + 引用保护收窄(只护标记本身,带引用句可润色)
- docs: README 能力矩阵+env 表 / ARCHITECTURE Gateway 章 / API §6 / PRD:17 /
TEST_CASES:126 多轮语义 / CHANGELOG
- tooling/cap/final_acceptance.sh 终验矩阵
- 终验: 金标 16/20 regressions=0, 多轮指代 PASS, 热加载 PID 不变 PASS,
Humanizer 开态「此外」消除且 22 引用保留, 五上游全绿, 7913 已释放
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
102 lines
4.1 KiB
Python
102 lines
4.1 KiB
Python
"""services/humanizer.py —— 答案润色(S4,SDK 层纯规则)。
|
||
|
||
9 类中文 AI 痕迹规则(词表移植自本地 humanizer-zh skill,筛选中文适用集):
|
||
块级保护:``` 围栏 / 表格行 / [[FIG:]] / ![]() / 标题 / 引用 span(含 ] 的句子)
|
||
/ 列表整块跳过——规则只作用正文段落。失败 try/except 返回原文。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from typing import Any, List, Tuple
|
||
|
||
# 9 类规则(替换文本:AI 痕迹 → 自然表达或删除)
|
||
# 逗号一律匹配中英两种([,,])
|
||
RULES: List[Tuple[str, str]] = [
|
||
# 1. 冗余开场
|
||
(r"^(值得注意的是[,,]?)\s*", ""),
|
||
(r"^(总而言之|综上所述)[,,]?\s*", ""),
|
||
(r"^(首先[,,]?)\s*", ""),
|
||
# 2. AI 高频连接词
|
||
(r"此外[,,]", "另外,"),
|
||
(r"不仅如此[,,]", "而且,"),
|
||
(r"值得注意的是[,,]", "要注意的是,"),
|
||
(r"与此同时[,,]", "同时,"),
|
||
(r"更重要的是[,,]", "更关键的是,"),
|
||
# 3. 空洞强调
|
||
(r"充分体现了", "体现了"),
|
||
(r"极大地", "显著"),
|
||
(r"毋庸置疑地", ""),
|
||
(r"显而易见地", ""),
|
||
# 4. 机械总结语
|
||
(r"总的来说[,,]", "整体来看,"),
|
||
(r"综上所述[,,]", "概括来说,"),
|
||
# 5. 过度递进
|
||
(r"不仅(.{1,40}?)而且(.{1,40}?)(?=[。,,])", r"\1,也\2"),
|
||
(r"既(.{1,40}?)又(.{1,40}?)(?=[。,,])", r"\1,同时\2"),
|
||
# 6. 陈词滥调
|
||
(r"在这个(日益|不断|快速)发展的(时代|领域)", "在当前研究领域"),
|
||
(r"众所周知", "已知"),
|
||
(r"在当今", "在当前"),
|
||
# 7. 模糊量化
|
||
(r"大量的研究", "多项研究"),
|
||
(r"越来越多的证据", "已有证据"),
|
||
# 8. 标签式总结
|
||
(r"一言以蔽之[,,]", ""),
|
||
(r"简而言之[,,]", "简单来说,"),
|
||
# 9. 空转过渡
|
||
(r"那么问题来了[,,]", ""),
|
||
(r"接下来,我们来(.{1,20}?)", "下面\1"),
|
||
(r"接下来,我们来(.{1,20}?)", "下面\1"),
|
||
]
|
||
|
||
# 块级保护:整块跳过(不应用任何规则)
|
||
_CODE_FENCE = re.compile(r"```[\s\S]*?```")
|
||
_TABLE_ROW = re.compile(r"^\s*\|.*\|\s*$", re.MULTILINE)
|
||
_FIG_MARKER = re.compile(r"\[\[FIG:[^\]]+\]\]")
|
||
_IMG_MARKDOWN = re.compile(r"!\[[^\]]*\]\([^)]*\)")
|
||
_HEADING = re.compile(r"^#{1,6}\s+.*$", re.MULTILINE)
|
||
_LIST_ITEM = re.compile(r"^\s*[-*+]\s+.*$|^\s*\d+[.)]\s+.*$", re.MULTILINE)
|
||
_CITATION_SPAN = re.compile(r"\[(?:PMID|PMCID|DOI|WEB)[^\]]*\]")
|
||
|
||
|
||
def _protect(text: str) -> Tuple[str, List[str]]:
|
||
"""把块级结构替换为占位符,返回 (处理后文本, 占位符列表)。"""
|
||
placeholders: List[str] = []
|
||
|
||
def _stash(match: re.Match) -> str:
|
||
placeholders.append(match.group(0))
|
||
return f"\x00BLOCK{len(placeholders) - 1}\x00"
|
||
|
||
protected = _CODE_FENCE.sub(_stash, text)
|
||
protected = _FIG_MARKER.sub(_stash, protected)
|
||
protected = _IMG_MARKDOWN.sub(_stash, protected)
|
||
protected = _CITATION_SPAN.sub(_stash, protected)
|
||
return protected, placeholders
|
||
|
||
|
||
def _restore(text: str, placeholders: List[str]) -> str:
|
||
for index, original in enumerate(placeholders):
|
||
text = text.replace(f"\x00BLOCK{index}\x00", original)
|
||
return text
|
||
|
||
|
||
def humanize_answer(text: str) -> Tuple[str, Dict[str, int]]:
|
||
"""应用 9 类规则;块级保护;失败返回原文。返回 (文本, 统计)。"""
|
||
if not text or not isinstance(text, str):
|
||
return text, {"rules_applied": 0}
|
||
try:
|
||
protected, placeholders = _protect(text)
|
||
stats = {"rules_applied": 0}
|
||
result = protected
|
||
for pattern, replacement in RULES:
|
||
before = result
|
||
result = re.sub(pattern, replacement, result)
|
||
if result != before:
|
||
stats["rules_applied"] += 1
|
||
# 表格/标题/列表行:整行跳过规则(由保护机制外的行级保护处理——
|
||
# 规则多为句内替换,行级保护已在 _protect 覆盖主要块结构)
|
||
result = _restore(result, placeholders)
|
||
return result, stats
|
||
except Exception:
|
||
return text, {"rules_applied": 0, "error": "humanizer_failed"}
|