用户词库日志存的是调频写下的绝对权重。词库升级换了权重量级后(msime-dictionary 把拼音词频对齐万象,「了 le」从 348 万变成 5200 万),安装时的回放仍把旧词库上 调出来的 650 万写回去,打 l 时「了」排到「来」「里」后面。 回放现在用数据目录里 dictionary-manifest.json 记的 msime.db SHA-256 认出厂词库, 并在日志新增的 dictionary_state 表记下回放到哪份。同一份词库照旧原样写回;换了 词库(或日志还没有记录)时,自动调频的行只在高过新出厂权重时写回,否则保留新 权重并把日志改记成新权重。用户手动加的词和删除不变。 设置页改权重改走新的 record_weight_edit,记在新增的 weight_edited 列里,换词库时 照写不让位;之后的自动调频覆盖同一行时清掉这个标记。
7.4 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 的行上
- 日志存的是绝对权重,只在同一份出厂词库上有意义。安装时的
replay用数据目录里dictionary-manifest.json记的msime.dbSHA-256 认词库,回放到哪份记在日志的dictionary_state表:同一份词库原样写回;换了词库,自动调频的行只在高过新出厂权重时写回,否则保留新权重并把日志改记成新权重。用户手动加的词(user_inserted)和设置页亲手改的权重(weight_edited,由record_weight_edit写)不受影响。设置页改权重要走record_weight_edit,不要用record_upsert,否则换词库时会被当成自动调频让位
改这里之前先读懂现有的护栏注释,它们每一条都对应一个修过的缺陷。
切分与候选
- 平局时偏好更短的首音节(
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 生成标记。