最少音节切分平局时固定取较短首音节,切分又在查词之前定死,yibananzhao 只会切成 yi'ba'nan'zhao 出「一把难找」,「一般按照」「这个方案很好」这类 n/ng 边界歧义的整句没有机会。 - arbitrate_literal_segmentations:合法输入的等音节数字面切分各解一遍词格,路径分最高的领衔, 同分仍取原切分;输方分差在 3.0 以内时它的整句占第 2 位,更远不出。任一切分在词库里有整词时 不介入(单词级仍按词频),整句来源全关时与原来一致,用户调过频的读音不让出领衔 - 合法输入纠错的原读法改为领衔的字面切分:纠错胜出时第 2 位是得分最高的字面读法 - 首选是另一种等音节数字面切分时预编辑跟着它分(yi'ban'an'zhao、fang'an) - 胜出切分的前缀词替换原切分前缀区间扫出的同名行,选「一般」按 yi'ban 消耗 - 词格跨度查询与最佳路径加缓存,随 reset_cache 清空;普通输入每键 p90 也随之下降 - 新增 eval_quanpin_sentence 与句子级评测集 tests/data/quanpin_sentence_cases.tsv
6.7 KiB
AGENTS.md — engine/
本目录是本仓库的输入引擎组件,不再是子模块。来源提交和专门化时裁掉的内容见 UPSTREAM.md;仓库级约定以根 AGENTS.md 为准,本文件补充引擎自身的实现、数据和验证规则。
这里是一等代码,不是 vendored 第三方。 需要为 Windows 改引擎就直接改,和改 server/ 一样评审和测试,不要为了"保持和上游一致"而绕路。唯一例外是 ngram/kenlm/、ngram/octagram/darts.h、utfcpp/ 和 voice/third_party/:那几个确实是上游副本,保留其格式与许可,别去重排版;其中 darts.h 是按精确路径单独排除的,同目录的 octagram_gram.{h,cpp} 是我们的代码,仍受 format.sh 管。googlepinyinime-rev/ 已与上游解绑,需要时直接改(句中辅助码的字约束就在 matrixsearch.cpp 里),但沿用它原有的排版、不进 format.sh,许可不变,本地改动登记在 UPSTREAM.md。
helpcode/ 负责辅助码。voice/ 是可选语音模块,规则见 voice/AGENTS.md;导入的 Windows 独立程序位于 voice/platforms/windows/,默认不构建,也不进入公共库目标。词库不在本目录内,由 product-lock.json 钉住的发布产物在构建时下载。
构建与测试
产品构建通过 server/CMakeLists.txt 的 add_subdirectory 引入本目录,所以改了引擎最省事的验证是跑 server 的构建与测试,那也是 CI 实际走的路径。
要单独构建(迭代引擎本身时快得多):
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failure --timeout 20
googlepinyinime-rev/src/share/、ngram/kenlm/ 和 utfcpp/source 现在都在树里,普通检出即可,不需要 git submodule update。
改了引擎就要跑测试。 引擎的缺陷会以"某个输入模式偶发不出候选"的形式出现在前端,编译通过说明不了什么。
tests/ 是一个 CI 不构建的独立工程
tests/CMakeLists.txt 是独立 CMake 工程,产出 imetest(注册源只有 tests/src/test_pinyin.cpp)和全拼纠错离线评测工具 eval_quanpin_autocorrect,详见 tests/README.md。它不被根 CMakeLists.txt 引入——根目录只把 tests/src/*.cpp 当自己测试目标的源文件,不 add_subdirectory(tests)。所以:
imetest里的用例在 CI 中不会执行;往test_pinyin.cpp加用例,PR 的绿勾只代表引擎仍能编译,不代表测试跑过- 它反过来
add_subdirectory本目录、链接正式的MetasequoiaIme::Engine,引擎新增源文件只登记在根CMakeLists.txt一处;tests/vcpkg.json的依赖要和根vcpkg.json保持一致 - 它是 MSVC 专有配置(Windows.h、
/Zc:__cplusplus)
新增测试请加到根 CMakeLists.txt 已登记的目标里(tests/src/test_*_input_session.cpp 那一批),那些才会被跑到。
全拼分表命名(跨仓硬约定)
msime.db 按音节数加首音节首字母分表。权威定义是 contracts/dictionary/format.json,C++ 生成头供查询/回放使用,Python API(仓库根的 scripts/dictionary_product.py 是同一份的副本)供校验发布的词库使用;设置页通过公共 quanpin::build_table_name 写入。修改后运行生成检查及七/八/九音节创建、查询、回放回归。
| 音节数 | 表名 | 示例 |
|---|---|---|
| 1–7 | tbl_{N}_{首字母} |
ni'hao → tbl_2_n |
| ≥ 8 | tbl_others_{首字母} |
shui'shan'shu'ru'fa'hai'ke'yi → tbl_others_s |
首字母取第一个音节的第一个小写拉丁字母。禁止对 ≥8 音节拼出 tbl_8_*:建库脚本不会创建这些表,写入和回放都会失败,安装升级时的回放失败会导致整批回滚并中止安装。改规则必须更新共享格式契约,禁止在消费端重新拼接表名。
用户词库权重
user_dictionary/user_dictionary_journal.cpp 的 adjust_candidate_ranking 改起来比看上去危险,几条已经踩过的坑:
- 权重要留在出货词典的量级内(
kManagedWeightCeiling)。曾经有过一次局部 rebalance 写出 10^12 级数值,以及拿权重为 1 的生僻词当阶梯基准写出负权重到邻近 key 上 - 一个上下文里可能混着多个
entry_key(单字母简拼y会同时出现yi的「一」和you的「有」)。按entry_key过滤候选列表会让选中项 rank 恒为 0,早退分支直接返回,一个权重都不写 - 但写入必须按各候选自己的 key 走,否则会把权重写到别的 key 的行上
改这里之前先读懂现有的护栏注释,它们每一条都对应一个修过的缺陷。
切分与候选
- 平局时偏好更短的首音节(
quanpin/quanpin_utils.cpp的cut_one_piece_min_segments)。这个判据是对的,不要翻成前向最长匹配——在 48068 条带词典切分的多音节词上量过,翻向会从 99.605% 掉到 99.393%,因为拼音的n/ng歧义让前向最长匹配在中文上不成立 - 歧义切分交给词典裁决,走
quanpin_dictionary.cpp的备选切分路径,受kMaxSyllablesForMultipleSegmentations限制 - 上一条是单词级(有整词就按词频比)。没有任何切分能命中整词的整句(
yibananzhao的一般按照 / 一把难找),由arbitrate_literal_segmentations对等音节数的字面切分各解一遍词格、按路径分裁决,只在整句来源开着时介入;预编辑跟着首选的切分走。上面的「平局取短首音节」在这里只是同分时的判据 query()在每次按键上都会跑。往里加无条件的枚举或查询前先量耗时,tests/src/test_pinyin.cpp里那个 timings 用例只打印不断言,拦不住回归。改切分或整句打分时,用eval_quanpin_sentence(句子级评测集在tests/data/quanpin_sentence_cases.tsv)对着真实词库的副本跑前后对比,它同时报每键耗时
数据
msime.db、english.db、others.db、dict_japanese.dat 都来自 product-lock.json 钉住的词库 Release,构建时下载;建库流水线不在本仓库内。查询侧用的格式契约是 contracts/dictionary/format.json,改它等于改一个已经发布的词库的读法,务必确认锁住的那一版词库仍然符合新规则。
候选窗的中英翻译除 english.db 的大表外还有一层人工覆盖:english.db 同目录的 custom_translations.txt,格式 源<Tab>释义,优先级高于大表(english/english_dictionary.cpp)。要纠正个别释义改这里,不要动 ECDICT 生成的大表。
提交
提交信息用 type(scope): 摘要。不要添加 Co-Authored-By、Generated with 或其他 AI 生成标记。