CNSH v2.0:可嵌入中文纠错规则库的设计与实践
做中文文本处理的人大概都遇到过这种尴尬编辑器里一片波浪线点开仔细看全是英文拼写建议中文错别字和病句却纹丝不动甚至还会把你原本写对的句子标红。CNSH中文编辑器这套纠错规则库就是专门把这一块补上的。我把它定位成一套可嵌入、可扩展的中文校对规则集配合编辑器或命令行工具使用能对常见错别字、形近字误用、语法搭配、标点符号、数字单位等问题做批量检查和修正。v2.0是我在一版规则库跑了一年多之后重写的版本重点解决了误报率偏高、上下文判断不足、规则可维护性差三个老毛病。如果你正在做中文编辑工具、RAG文本清洗、公众号排版校对或者想给自己搭一套文本质检流程这篇内容应该能提供不少可以直接抄走的方案。1. 我为什么重写这套规则库1.1 初版规则库踩过的坑最早一版CNSH规则库其实做得挺粗糙的。当时我把市面上能找到的错别字表、常见语病清单、标点规范全部整理到一起用简单的字符串匹配做扫描遇到字面完全一致的内容就触发规则。听起来很美好真正跑起来之后问题一个接一个往外冒。第一个问题是规则之间互相打架。比如“的/地/得”的规则我一开始简单粗暴地认为动词后面跟“的”就是错的结果“跑得快”“跳得高”这类补语结构全部被误判。更麻烦的是不同来源的规则对同一个词给出的修正建议不一致有的说“按装”应该改成“安装”有的又认为“按装”属于可接受的口语写法导致同一篇文档在不同规则分组下出现完全相反的检查结果。第二个问题是完全没有上下文概念。“度过”和“渡过”的区分如果只看词本身任何一个规则都无法判断对错。初版规则库只能做到“发现这个词就提示用户确认”本质上就是把判断责任全部推给了用户使用体验非常差。还有一个很致命的问题规则数量一旦超过一千条调试和扩展就变得极其困难。想加一条新规则改完之后不知道影响了哪些旧场景每次发布都像拆炸弹。1.2 v2.0的目标与设计原则经过一年多的实际使用和用户反馈我总结出重写必须满足的几条设计原则。第一规则必须分层。不能再像原来那样把所有规则堆在一个平面上而是要把规则拆成词法层、语法层、语义层三个维度每层有独立的处理逻辑和置信度评估方式。第二上下文感知优先。对于有歧义的词不能只给“疑似错误”的提示而要结合前后文、词性、句式结构给出更精确的判断。至少要做到“在什么条件下触发在什么条件下放行”。第三规则可测试、可追溯。每一条规则必须配有对应的正面例句和负面例句新增规则时自动跑一遍全量测试集保证不会破坏已有场景。第四性能要有底线。规则库不能只停留在学术示范阶段而是要能扛住长文档、批量文件夹扫描这类真实场景。v2.0相比v1.0除了规则数量从一千多条扩展到三千多条更关键的是规则引擎的整体重写。原来是“遍历规则列表、逐条匹配文本”现在是“先分词、再分层、最后按置信度汇总”复杂度从O(规则数×文本长度)降到了接近O(文本长度)。我把两个版本的对比整理成了一张表维度v1.0v2.0规则数量约1200条约3200条上下文支持无纯字符串匹配支持前后文Token、词性、句式判断误报率实测约25%实测约6%规则格式散落的文本列表结构化JSON/YAML支持字段扩展性能1万字约8秒1万字约1.2秒扩展方式人工改代码配置热加载无需重启2. 规则库的整体架构与分类体系2.1 规则分层词法、语法、语义v2.0把纠错规则拆成了三层分别对应文本处理的不同粒度。词法层解决的是“字词本身写错”的问题包括错别字、形近字误用、音近字误用、专有名词写法不一致等。这一层最直接也最容易实现基本靠词典加规则就能覆盖大部分场景。比如“按装”提示改为“安装”“既使”提示改为“即使”“针炙”提示改为“针灸”。词法层的规则最依赖词典质量所以我花了大量时间整理高频错别字词对同时把每个词对的使用场景描述清楚避免无脑替换。语法层解决的是“词语搭配和句子结构”的问题。比如“提高”可以和“水平”“效率”“质量”搭配但很少说“提高程度”“改善”可以和“生活”“环境”“关系”搭配但说“改善水平”就很怪异。语法层规则不能简单看词面而是要结合词性标注、依存关系、常见搭配库来综合判断。我实际实现的时候是把常见搭配表做成“中心词修饰词”的索引结构扫描时先找到中心词再去检查它的修饰成分是否在合法搭配范围内。语义层解决的是“语义一致性和表达规范”的问题。这一层最难也是v2.0重点加强的部分。语义层规则通常是规则引擎和语言模型混合处理的规则引擎负责确定性的检查语言模型负责模糊判断。比如“产品深受欢迎”和“产品深受喜爱”都可以说但“产品深受满意”就少见这种判断靠纯规则很难覆盖全需要用一个轻量级的语言模型做二次确认。不过为了控制性能开销语义层规则只对词法、语法层筛出来的疑似错误做验证不会全量跑模型。2.2 规则目录结构与配置格式v2.0的规则目录按照“分类/子分类/规则文件”的方式组织每个规则文件是一个JSON或YAML格式的配置文件。这种设计的好处是规则和引擎完全解耦运营人员可以单独维护错别字词库开发人员可以单独调整语法规则互不影响。规则目录的典型结构如下cbsh-rules/ ├── lexical/ # 词法层规则 │ ├── typo.json # 错别字规则 │ ├── similar-char.json # 形近字规则 │ ├── pinyin.json # 音近字规则 │ └── proper-noun.json # 专有名词规范 ├── syntax/ # 语法层规则 │ ├── collocation.json # 搭配规则 │ ├── word-order.json # 语序规则 │ └── redundant.json # 成分冗余规则 ├── semantic/ # 语义层规则 │ ├── ambiguity.json # 歧义词判断 │ └── consistency.json # 术语一致性 ├── style/ # 风格与排版 │ ├── punctuation.json # 标点符号 │ ├── number-unit.json # 数字与单位 │ └── banned-words.json # 禁用词与风险词 └── test-cases/ # 测试用例集 ├── lexical-test.json ├── syntax-test.json └── semantic-test.json每一条规则项的字段设计也做了规范化。核心字段包括id唯一规则ID格式为“分类-序号”比如typo-0012type规则类型replace表示替换warn表示提示suggest表示建议trigger触发条件支持字符串、正则表达式、分词后的Token序列context上下文条件可以指定前文词、后文词、词性标签suggestion修正建议可以是固定文本也可以是候选词列表weight置信度权重取值范围0到1scope适用范围比如“全部”“技术文档”“新闻稿件”“营销文案”test规则自带的测试用例包含正面和负面样例拿一条具体的错别字规则举例{ id: typo-0102, type: replace, trigger: 按装, context: {}, suggestion: 安装, weight: 0.98, scope: 全部, test: { positive: [请按装完成后重启系统], negative: [安装程序正在运行] } }这里positive表示应该触发纠错的例句negative表示不该触发的反例。每次改规则或加规则的时候测试用例都会全量跑一遍确保不会引入新的误报。2.3 上下文判断与误报控制v2.0在引擎层面增加了一个“上下文判定模块”专门处理那些模棱两可的词。设计思路是当扫描到某个候选词时先不要急着报错而是把它前后若干个Token取出来做一次多维度判断。以“度过”和“渡过”这对经典案例为例。单看词两个词都正确必须看后面的宾语才能判断。“度过”通常接时间、假期、日子比如“度过了一个愉快的周末”“渡过”通常接河流、难关、危机比如“渡过了最困难的一段时期”。所以规则可以写成{ id: typo-0218, type: suggest, trigger: 渡过, context: { next_words: [假期, 周末, 时光, 岁月], action: suggest_replace }, suggestion: 度过, weight: 0.85, scope: 全部 }再比如“的/地/得”的处理。v2.0的规则不是简单看前一个词是不是动词而是要看整个句法结构。“他高兴地跳了起来”中的“地”是正确的但如果写成“他高兴的跳了起来”这里“的”就可能是“地”的误用。判断逻辑是如果“的”后面紧跟动词或动词短语并且“的”前面是形容词或修饰成分那就提示改为“地”。这个规则用分词和词性标注就能实现准确率做到了九成以上。上下文判定模块最核心的价值在于把原来那种“逢词必报”的做法改成了“有条件才报”。一条规则如果没有命中上下文条件即使候选词出现了也不会抛提示这直接让误报率降到了可接受的范围。3. v2.0核心规则模块拆解3.1 错别字与形近字规则错别字规则是CNSH的立身之本v2.0这部分做了大幅扩充。除了传统的“错字→正确字”映射还加入了“词对场景”的控制能力。我整理错别字词对时优先覆盖两类内容一是在工作中反复遇到的真实高频错误二是网上公开语料中统计出来的高频错别字。第一类比如把“部署”写成“布署”、把“配置”写成“配制”、把“账号”写成“帐号”第二类比如“迫不及待”写成“迫不急待”、“一筹莫展”写成“一愁莫展”。形近字规则稍有不同它解决的是“字长得像、含义完全不同”的误用问题。比如“度假”和“渡假”、“寒暄”和“寒喧”、“竣工”和“峻工”。这类错误通过纯视觉相似度很难判断所以我直接在库里面维护了一个“易混淆字词对照表”把常见的形近字词对全部列出来并标注推荐用法。实际使用中我不建议所有的错别字规则都设置成“强制替换”。比如“帐号”和“账号”目前其实处于混用状态不同输入法、不同平台都有各自的偏好直接改成强制替换会惹恼一部分用户。这类规则我通常设置成suggest级别只提示、不强制把最终决定权交给用户。3.2 常见语法搭配纠错语法搭配规则是v2.0新增的重头戏也是工作量最大的一块。它解决的场景是每个词单独看都没问题但放在一起就是别扭。典型的例子包括冗余表达、动宾搭配不当、量词误用等。冗余表达是最常见的语法问题。比如“凯旋归来”中的“凯旋”本身就有“胜利归来”的意思后面再跟“归来”就重复了“亲眼目睹”中的“目睹”本身就含有“亲眼”的意思再加“亲眼”属于画蛇添足“免费赠送”中的“赠送”天然包含“免费”“免费”两个字其实可以去掉。这些规则不需要太高深的算法靠短语黑名单加推荐替换就能覆盖。动宾搭配不当则需要借助搭配库。我的做法是筛选出高频动词和名词构建一张“主谓宾搭配表”记录哪些组合是常见的、哪些是罕见或错误的。比如“提高”这个词合法宾语包括“水平”“效率”“质量”“能力”“收入”等非法或罕见宾语包括“程度”“范围”“效果”。当句子中出现“提高程度”这种组合时引擎会给出提示并建议改为“提高水平”或“提高幅度”。量词误用的规则相对简单但也很实用。比如“一支笔”不能说成“一根笔”“一辆车”不能说成“一台车”。量词规则需要结合前面的名词来判断核心逻辑是建立“名词→可搭配量词集合”的映射。3.3 标点符号与排版规范很多人会忽略标点符号的纠错但在我实际做排版校对的时候标点错误反而是出现频率最高的。v2.0专门用了一个独立的style目录来管理这些规则。中文标点最常见的错误包括该用中文逗号的地方用了英文逗号、该用中文引号的地方用了英文引号、破折号使用不规范、引号嵌套层级混乱、连续多个逗号导致句子成分不清。其中英文标点混入中文段落的问题最普遍规则也最简单——只要在中文上下文中扫描到英文标点并且前后文都是中文字符就提示替换为中文标点。引号嵌套是另一个高频错误。中文规范中引号嵌套时外双内单也就是最外层用双引号内部再用引号时用单引号。很多文章中会出现双层双引号直接嵌套的情况看起来特别不专业。v2.0会通过扫描连续引号对来判断嵌套层级是否正确。还有一个容易被忽略的点是全角空格。中文排版中汉字之间一般不会出现空格但很多用户从网页复制内容时会带上全角空格导致排版错乱。我在规则库中单独加了一条“连续全角空格”的检查规则触发后建议删除。3.4 数字、单位与日期规范数字和单位的规范问题在技术文档、行业报告、产品说明中非常突出。v2.0把这类规则单独拆出来做成一个“数字规范”模块。数字规范规则包括中文数字和阿拉伯数字的使用场景、日期格式统一、数字范围中连接号的规范、百分比的写法、单位符号的大小写等。比如“3月5号”和“3月5日”都见过但正式文档通常建议统一为“3月5日”“12-15人”中的短横线应该使用规范的范围连接号而不是连字符。单位符号的大小写问题也很容易踩坑。“kg”和“Kg”、“MB”和“Mb”在正式文档中都有严格要求前者正确、后者错误。不过这类规则需要注意应用范围比如在代码注释或技术文档中单位写法可能已经被行业习惯固化了。所以我给这类规则设置了一个scope字段默认只在“正式文档”模式下启用避免干扰代码场景。日期规则也值得一提。我遇到过大量“2024年1月1日”写成了“2024年01月01日”的情况这在正式公文中并没有明确规定但约定俗成的中文排版建议省略前导零。v2.0默认对这种格式给出提示但权重设得较低避免过度打扰。3.5 禁用词与风险表达检查这个模块可能是我和团队被用户问得最多的模块也是最需要结合业务场景定制的模块。v2.0内置了一套基础的风险表达规则覆盖广告法常见违禁词、夸大宣传词、绝对化用语等。广告法违禁词是这类规则的典型代表比如“最”“第一”“顶级”“极致”“国家级”“100%治愈”等。企业在发布营销文案前用这套规则扫描一遍可以大大降低合规风险。不过必须强调这类规则库不能盲目通用不同行业的禁用词差异很大平台方、出版社、医疗行业都有各自的审核标准。我的建议是把内置风险词表当作底库业务方再根据自己行业的标准做二次扩展和调整扩展词表放到独立配置文件中方便随时更新。v2.0在处理这类规则时特别加了一个“边界预处理”逻辑。比如“最好的我们”是一部剧名这时候把“最好”标成违禁词就不合适法务、律师这些词在某些语境下不能一刀切。所以风险词规则的触发条件通常要带上下文约束只有出现在广告宣传场景特征明显的句式中比如“全网最好”“行业第一”时才会触发提示。4. 实操接入与批量纠错流程4.1 在VS Code中配置CNSH我把CNSH设计成了可嵌入的规则引擎所以在VS Code中使用非常方便只需要安装一个小的扩展壳子再把规则库路径配置到settings.json里就可以。{ cnsh.enable: true, cnsh.rulesPath: ./cnsh-rules, cnsh.lintOnSave: true, cnsh.checkScope: all, cnsh.maxReportCount: 200, cnsh.disabledRules: [style/banned-words.json] }配置完之后打开一个中文文档所有检查结果会以诊断信息的形式出现在编辑器底部面板。点击错误项可以直接跳转到对应位置并且看到修改建议。有些规则如果设置了suggest级别会显示为灰色提示不影响正常编辑。我建议第一次接入时先不要开启lintOnSave而是先用命令面板手动执行一次全量检查看看误报情况把不想启用的规则在配置里禁用掉。等规则库稳定了再开启保存即检查体验会顺滑很多。4.2 命令行批量纠错工具编辑器适合单篇文档校对但真正大规模使用场景是批量扫描整个目录的文本文件。为此我写了一个命令行工具核心逻辑很简单读取文件、调用规则引擎、输出错误清单。cnsh check ./docs --format json --threshold 0.6输出是一个JSON数组每条记录包含文件名、位置、规则ID、错误类型、建议修改、置信度等信息。这样不管是人工处理还是接到CI流程里做自动质检都非常方便。[ { file: docs/readme.md, line: 12, column: 8, rule: typo-0102, type: replace, original: 按装, suggestion: 安装, confidence: 0.98 } ]批量扫描时我还加了缓存机制已经检查过且文件没有变动的结果会直接复用避免重复计算。对于动辄几百篇文档的目录这个优化非常关键实测能把整体耗时缩短百分之七八十。4.3 自定义规则与热更新CNSH的规则库不是定死的实际使用中几乎每个人都会根据自己的业务场景新增规则。v2.0的自定义规则体验比初版好得多只需要在对应的分类目录下新增一个配置文件然后在主配置里注册一下就可以。新增一条词法规则的流程很简单在lexical/目录下新建一个JSON文件比如custom-typo.json按照标准格式写入规则项在cnsh-rules/main.json中引入该文件运行一次规则检测确认不破坏既有用例热更新的实现原理是引擎启动时会扫描所有规则文件的修改时间配置变更后自动重新加载不需要重启编辑器或命令行进程。这一点在做长时间批量任务时特别有用改了规则不用从头跑一遍。{ rules: [ { id: custom-0001, type: replace, trigger: 脚本之家, context: {}, suggestion: 脚本家, weight: 0.99, scope: 技术文档 } ] }这里只是举个例子。实际应用中自定义规则最常见的需求是“公司内部产品名称写法统一”“行业术语限定”等用上面这种方式不到五分钟就能加好一条规则。5. 常见问题与调参实录5.1 误报太多怎么办这是所有规则库都会遇到的头号问题也是用户吐槽最多的点。我的经验是遇到误报不要急着删规则先找到误报的根因再处理。如果误报集中在某一条规则上比如“的/地/得”规则频繁把正确的句子标出来那说明规则的上下文条件写得太宽松了。解决方法是给这条规则加上更严格的上下文限制比如要求前后词的词性必须满足特定条件才触发。如果误报分布在很多条规则上但每条规则触发频率都不高那说明整个规则库的置信度阈值设置得太低。可以把全局阈值从0.7调到0.8只保留高置信度规则。v2.0的规则权重体系在这里就发挥作用了不需要逐条改只需要在配置里调整一次默认阈值即可。还有一种常见情况某条规则在大多数场景下都正确但在某一类特殊文本中频繁误报。这时候可以为该规则配置一个exclude条件例如在代码块、引用块、标题中跳过检查。我在规则引擎里增加了“区域屏蔽”能力可以按Markdown语法、HTML标签、代码片段等粒度控制检查范围。5.2 长文本性能优化规则库在短文本和长文本上的表现差异很大。短文本基本没有性能压力但一旦扫描几十万字的小说、报告性能问题就会凸显。v2.0做性能优化主要靠三板斧。第一板斧是分词缓存。同一篇文档中很多词会反复出现分词结果如果没有变化就直接复用不重复计算。第二板斧是并行扫描。词法层、语法层、语义层的规则互相独立可以先分词、再分线程并行处理最后合并结果。第三板斧是优先级贪心算法。优先跑权重高、成本低的规则如果高置信度规则已经命中就跳过那些低权重规则的重复检查。实测下来处理一篇十万字左右的中文报告v2.0大概需要6到8秒对于离线扫描场景完全够用。如果接实时输入场景可以通过限制单次扫描文本长度来控制延迟建议单次不超过两千字分段处理。5.3 与AI模型配合使用的经验这个问题我要多说几句。很多人以为有了大语言模型传统规则库就没有存在价值了。我实际对比下来的结论是规则库和AI模型不是替代关系而是互补关系。规则库的优势是稳定、可控、可解释。同样的错误规则库今天判断是错明天判断还是错不会像大模型一样偶尔给个不一致的答案。AI模型在处理需要理解语义的复杂错误上更有优势比如“由于……因此……”搭配不当、“因为……所以……”的句式冗长等但这些场景规则库很难穷举。我的实践经验是先让CNSH跑一遍规则检查把确定性的错误全部处理掉再把处理后的文本交给大模型做二次润色。这样做的好处是大模型不需要把精力浪费在“是不是写错了”的判断上可以集中精力做语义优化而且规则库处理后的文本错别字更少大模型的输出质量也会更高。如果你把CNSH接入到AI写作链路中还可以做一个有趣的小功能把规则库命中的错误作为结构化提示词传给大模型让模型“专门针对这些疑似错误进行重点审查”实测能显著减少AI改写时的误改比例。5.4 常见问题速查表为了方便查阅我把实际使用中遇到的高频问题整理成了一个表格问题可能原因解决方式检查结果太多几乎每段都有提示置信度阈值太低调高全局阈值至0.8以上某条规则总是误报上下文条件太宽松增加词性/前后文约束规则库更新后不生效缓存未刷新检查配置文件修改时间重启引擎长文本扫描特别慢未启用并行或缓存确认开启缓存适当增加并行线程数Markdown代码块中的代码被误报未配置区域屏蔽在配置中添加代码块屏蔽规则公司产品名被当成错别字缺少自定义白名单在自定义规则中加入产品名词表AI润色后把原文改错模型缺少纠错上下文把规则结果作为提示词传给模型这张表对应的都是真实场景大家遇到类似问题可以直接按索引查。写在最后的经验我自己在项目里已经用CNSH v2.0跑了大半年最深的体会是规则库最值钱的部分不是规则数量而是规则的“克制”。写一条规则很容易难的是让它在自己该触发的地方触发、不该触发的地方闭嘴。再分享一个小技巧。每次新增规则前先准备至少五个正面例句和五个负面例句正面例句确保该报的能报出来负面例句确保不该报的不会误报。规则写完后跑一遍全量测试集只有所有用例通过才允许合并。v2.0能保持较低的误报率根本原因就在于这个不起眼的流程被严格执行了。另外一个比较有意思的经验是我后来把CNSH接到了数据清洗流程中用来处理从网上抓取的语料。原本要花大量人工清理的错别字和噪声文本规则库扫一遍能自动解决大部分问题效率提升非常明显。如果你也有类似的需求建议从词法层规则开始用逐步叠加语法和语义层这样能比较平滑地找到一个适合自己项目的调参点。提示规则库的配置文件建议纳入Git管理每次改动都提交一次commit并附上测试用例的变化。这样即使某条新规则引入了问题也能快速回滚避免影响线上任务。