Metadata-Version: 2.4
Name: text-rewrite
Version: 0.2.3
Summary: A high-performance text rewriting and fuzzy phonetic matching engine
Author-email: Magic Jack <majic31@163.com>
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: flashtext>=2.7
Requires-Dist: numba>=0.59.0
Requires-Dist: numpy>=1.21.0
Requires-Dist: pypinyin>=0.49.0
Requires-Dist: jieba>=0.42.1
Provides-Extra: test
Requires-Dist: pytest>=7.0.0; extra == "test"

# text-rewrite 🚀

[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**text-rewrite** 是一个专为工业级 ASR（语音识别）后处理设计的**高性能文本纠错与过滤引擎**。其理念通过非侵入式方式修改热词，所有的ASR引擎都可将转写内容通过该框架调用，实现中英文热词纠错效果。本项目经过精密的设计，性能好、对相似读音的识别准、阈值约束合理，一定程度上解决了传统 ASR 热词匹配中常见的“跨词误杀”、“谐音错认”、“英文纠错”，并且性能极高(万级别热词下短句（30字）热词替换仅不到3ms)。

## ✨ 核心特性

- **极致性能 ($O(N)$ 线性扩展)**：针对万级甚至十万级热词进行专项优化。处理近 2000 字的长文本只需不足 300 毫秒（常规短句 <3ms），不会拖垮流式并发服务器。
- **Entity-Aware 实体约束引擎**：针对容易误杀的极短词（如人名“叶开” vs “也开心”），创新性引入基于 Jieba 词性标注的轻量级 NER 引擎。**内建极速音素预检机制与无 HMM 模式**，将千字长文本的 NER 解析时间从 400ms 暴砍至 5ms，万级别热词下短句（30字）热词替换仅不到3ms。
- **双音节倒排索引 (Bigram Syllable Index)**：创新性地构建了基于“首位双音节”自适应哈希倒排池。将 10000 个热词在 2000 字长文本上的 DP 候选空间极致压缩了 98.8%，打破长文本的魔咒。
- **Numba Batch DP 加速**：底层基于 Numba JIT 编译的“批量化动态规划（DP）”核心引擎，彻底消除跨语言调度开销，支持纯发音级别的纠错（完美包容平翘舌、前后鼻音、形近音误差）。
- **中英混合 & 多音字感知**：热词与输入文本使用同一套音素化流程，英文热词（如 `Claude`、`GitHub`、`iPhone15`）不区分大小写即可命中；中文按词组上下文注音（如“重庆”读 `chong2`），轻声处理保持一致。
- **微秒级 FlashText 精确匹配**：对于全局安全大词表，底层自动退化为 Aho-Corasick 自动机，做到微秒级无感替换。
- **Pipeline 乐高式组装**：提供高度可扩展的链式过滤器架构，正则清洗、精准替换、模糊纠错一气呵成。

## 📦 安装依赖

该项目核心依赖于高效计算与轻量级 NLP 组件：

```bash
pip install -r requirements.txt
```
> **要求**: `numba>=0.59.0`, `numpy>=1.21.0`, `pypinyin>=0.49.0`, `jieba>=0.42.1`, `flashtext>=2.7`

> **无 Numba 环境（如 PyArmor 加密部署）**：设置环境变量 `DISABLE_NUMBA=1`，或在未安装 numba 的环境中运行时，引擎会自动降级为纯 Python 执行**同一份 DP 算法**，纠错结果与 JIT 版本完全一致（有回归测试保障），但速度会显著变慢，启动时会打印一条 warning 提示。仅建议作为兜底方案。

## 🧩 核心过滤器 (Filters) 概览与最佳实践

在真实的工业级落地场景中，最常用的“黄金三剑客”是以下三个过滤器的组合：

1. **`RegexFilter` (打头阵：规则清洗)**
   - **作用**：干脏活累活。负责前置格式规整。
   - **场景**：将全角符号转半角、去除多余空格、清理语气词（“呃”、“啊”、“那个”），以及执行如 `四S -> 4S` 这种高度规律性的文本格式化。

2. **`HotwordFilter` (明确的热词替换)**
   - **作用**：基于 FlashText 的极速精确替换，零误杀，快、准、狠。
   - **场景**：用于承载几万到几十万量级的“黑白名单 / 品牌库 / 敏感词库”（如“蔚来”、“极氪”）。用它拦截掉绝大部分必须 100% 准确的词，避免增加下游模糊匹配的开销和误杀率。

3. **`EntityAwareFuzzyFilter` (音素热词纠错)**
   - **作用**：收拾残局。
   - **场景**：经过前面精确词表的拦截，剩下的“长尾错别字”（如用户口音导致的“魏来”、“及克”）由它出马，通过发音的编辑距离计算把错别字捞回来。
   - **语法**：`[标签]原词:权重 | 原词2:替换词:权重`
     - 强行模糊拦截：`张三:0.9` (权重 0.9 较高，即使发音偏差较大如“展伞”也会被强行纠正为“张三”)
     - 严格约束拦截：`李四:0.6` (权重 0.6 极低，只有在拼音和声调100%完美吻合时才允许纠正，防止误杀)
     - 短实体约束：`[nr]叶开:0.8` (`nr`为人名，只有 Jieba 认为是人名时才启动音素级检索，且权重适中)
   - **权重 (Weight) 机制**：底层的发音相似度基础门槛为 `0.6`，实际生效门槛 = `0.6 / 权重`。
     - **数值越大**：越容易被强行修改（容忍口音和 ASR 识别偏差）。
     - **数值越小**：要求发音越精确，设置为 `0.6` 时代表需要 `1.0`（即 100% 完全 match，包括声调）才能触发修改。
     - **💡 推荐设置区间**：通常建议设置在 **`0.6` ~ `1.0`** 之间（具体还需要尝试）。
       - `0.6`：极度保守（完全同音同调）。
       - `0.7 ~ 0.8`：常规推荐值（适度包容平翘舌、前后鼻音、声调识别错误）。
       - `0.9 ~ 1.0`：较激进（容忍较大的口音偏差）。
       - `> 1.0`：非常危险（比如 100 的话，及格线变成 0.006，几乎只要字数一样、发音稍微沾一点边就会被强行替换，极易造成“张冠李戴”的误杀）。

> **注**：除上述三者外，内部模块如 `FuzzyPhonemeFilter`（底层的 Numba DP 发音匹配引擎）和 `JiebaNERFilter`（底层 NER 引擎）主要作为组件被 `EntityAwareFuzzyFilter` 自动编排调用，在常规业务中通常无需直接操作。

### 🛠 权重诊断、排错工具(Diagnostic Tool)
在实际业务落地时，如果你发现某个错别字**没有按预期被纠正**，或者某个正常词**被意外误杀**，可以使用内置的诊断脚本来查看底层的真实打分情况，从而精准调优权重：

```bash
python tests/check_weight_score.py # 检查权重工具
```

该工具会直接输出底层 Numba 引擎的真实打分逻辑，帮助你快速理解权重的运作方式：
- **场景 A（权重 0.6，极其严格）**：试图把“里死(li3 si3)”纠正为“李四(li3 si4)”。因为声调不同，底层真实得分为 `0.9167`。但权重 `0.6` 对应的及格线是 `1.0000` (要求 100% 完美匹配)。因此 `0.9167 < 1.0000`，引擎判定差距过大，**拒绝修改**。
- **场景 B（权重 0.7，适当放宽）**：同样测试“里死”纠正为“李四”。权重提高到 `0.7` 后，及格线降为 `0.8571`。此时 `0.9167 >= 0.8571`，引擎判定符合容错范围，**成功触发修改**。

你可以随时修改该脚本中的 `analyze_match(target_word="你的词", test_text="测试文本", weight=0.7)` 参数，针对你的业务专有名词进行沙盒测试和精细化调参！

### 🛠 测试脚本说明：
```bash
pytest                              # 单元/回归测试：英文热词、多音字、长文本多次命中、无 Numba 降级一致性（tests/test_fuzzy_regression.py）
python tests/demo_full_pipeline.py # demo，常规用法说明，包括分词情况也可以看到（get_phoneme_info函数）
python tests/bench_entity_fuzzy.py # 性能测试
python tests/demo_dynamic_static.py # 工程落地：全局热词与租户热词示例。另外也有一种用法，就是按照hotword作为key，pipeline作为value，存放到cache（或者lru_cache）中使用
python tests/profile_entity_fuzzy.py # 内部开发者专用：微观耗时剖析与倒排索引压缩率监测
```

### 💡 进阶：底层近似音 (Similar Phonemes) 打分逻辑
为什么有些错别字的打分特别高？引擎底层在进行动态规划（DP）时，内置了一套**近似音损耗系数（Loss Coefficient）**。
- **完全相同的音素**（如 `a` 和 `a`）：扣 0 分。
- **易混淆的近似音**（如 `z/zh`，`an/ang`，`l/n` 等被定义在 `SIMILAR_PHONEMES` 中的音）：**只扣 0.5 分**。
- **完全不同的音素**（如 `a` 和 `i`）：扣 1.0 分。

**计算实例**：把“安康(an1 kang1)”和“安刊(an1 kan1)”进行对比。
两者的声母、声调完全一致，唯一的区别在于韵母 `ang` 和 `an`。因为它们属于近似音（前后鼻音），引擎只扣了 0.5 分。
最终相似度得分 = `1.0 - (0.5 / 5个总音素) = 0.9000`。
*   如果设置权重 `0.7` (及格线 0.857)，由于 `0.9 > 0.857`，系统判定发音足够相似，**成功纠错**。
*   如果设置权重 `0.6` (及格线 1.0)，由于 `0.9 < 1.0`，系统因为这极其微小的损耗拒绝了替换，达到了**防误杀的物理隔离**。

## 🚀 快速开始 (Quick Start)

下面是一个将“正则清洗 -> 精确大词表拦截 -> 高危实体模糊纠错”串联起来的完整 Demo：

```python
from text_rewrite.pipeline import Pipeline
from text_rewrite.filters.regex import RegexFilter
from text_rewrite.filters.hotword import HotwordFilter
from text_rewrite.filters.entity_fuzzy import EntityAwareFuzzyFilter

# 1. 配置正则过滤器（清洗语气词）
regex_rules = {
    r"\b(嗯|啊|哦|那个)\b": ""
}
regex_filter = RegexFilter(rules=regex_rules)

# 2. 配置精确热词过滤器（处理 10万级 安全大词表）
exact_filter = HotwordFilter(hotwords={"确定性长词": "替换词"})

# 3. 配置实体感知模糊过滤器 (EntityAwareFuzzyFilter)
# 语法: [标签]原词:权重 | 原词2:替换词:权重
fuzzy_rules = [
    "张三:0.9",             # 无标签：全局极简配置（较高权重，发音类似张三的词，如展伞，都会被纠正为张三）
    "[nr]叶开:1.0",         # 有标签：严格约束（必须是人名词性，且发音相似才纠正，适中权重）
    "李四:0.7"              # 全局极简配置（里死 -> 李四，常规权重）
]

# 工业界防呆技巧：引擎默认关闭了 Jieba 的 HMM（新词发现）以追求 5ms 极限性能。
# 如果热词极短（如2个字）且想避免误杀，建议保持 HMM 关闭，并将 ASR 常误识别的词强行喂入词典：
import jieba
jieba.add_word('夜凯', tag='nr')

# 注：如果您不需要追求极限性能，也可以直接开启 HMM 让它自己猜：
# entity_filter = EntityAwareFuzzyFilter(rules=fuzzy_rules, use_hmm=True)
entity_filter = EntityAwareFuzzyFilter(rules=fuzzy_rules)

# 4. 组装 Pipeline 引擎（顺序即执行顺序）
pipeline = Pipeline()
pipeline.add_filter(regex_filter)
pipeline.add_filter(exact_filter)
pipeline.add_filter(entity_filter)

# 5. 真实流式调用
text = "那个，昨天通知了一下展伞和里死，但是他也开心了，最后通知了夜凯。"
result = pipeline.process(text)

print(result) 
# 输出: "，昨天通知了一下张三和李四，但是他也开心了，最后通知了叶开。"
```

> **注意**：由于初始化 Numba 引擎以及 JIT 预热需要少量时间，建议在服务启动时**单例初始化** `Pipeline` 实例，在后续流式请求中复用该实例调用 `.process(text)`。

## 📊 性能压测 (Benchmark)

您可以运行自带的压测脚本，体验在注入 **10000** 条工业级比例热词配置下的强悍性能：
```bash
python tests/bench_entity_fuzzy.py
```
*   **短句 (约 30 字)**: 端到端平均耗时 **~ 1 ms**
*   **长文 (约 2000 字)**: 端到端平均耗时 **~ 150 ms** (呈现完美的 $O(N)$ 线性扩展，不受一万级词表拖累)

## 📝 更新日志 (Changelog)

### v0.2.2
**正确性修复（建议所有用户升级）**
- **英文热词生效**：此前热词侧与输入侧使用了不同的音素化函数（热词整词作为 1 个音素、输入按字母拆分），导致 `Claude`、`GitHub` 等英文热词无法命中。现已统一。
- **多音字**：热词改为按词组上下文注音，与输入侧一致（如“重庆”不再被注为 `zhong4`），“冲庆 → 重庆”可正常纠正。
- **轻声**：热词侧与输入侧轻声编码统一，含轻声字（的/吗/子…）的热词不再被无故扣分。
- **长文本漏纠**：此前同一热词在单次调用中最多只替换 32 处，其余被静默丢弃。现已取消该上限。
- **无 Numba 降级**：此前 `DISABLE_NUMBA=1` 时模糊纠错完全失效（直接返回原文）。现降级为纯 Python 执行同一份算法，结果一致。

**测试**
- 新增 `tests/test_fuzzy_regression.py`，覆盖以上全部场景。

**性能**：与 v0.2.1 A/B 对比，短句与长文耗时均无变化（差异在测量波动范围内）。

> [!WARNING]
> **升级注意**：由于英文热词从“完全不生效”变为“生效”，如果您的规则中已配置了英文热词，升级后它们会开始触发替换。建议先用 `python tests/check_weight_score.py` 确认权重是否合适。英文按字母计算编辑距离，如 `klaude → Claude` 只差 1 个字母。

### v0.2.1
- 新增 `use_hmm` 参数，可在极致性能（关闭 HMM）与高召回（开启 HMM）之间切换。

---
*Built with ❤️ for High-Performance NLP Engineering.*
