Files
PlantAI-rag-new/sdk/app/services/humanizer.py
T
jerryandClaude Fable 5 bbd5db2315 feat(capabilities): S6 Superpowers+S7 终验(文档/humanizer CJK 修复/验收脚本)
- 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>
2026-09-02 18:27:41 +08:00

102 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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"}