luojiyin
|
68b791c771
|
perf(neural): add async rerank end-to-end benchmark
|
2026-10-03 10:09:08 +08:00 |
|
Jia Sui
|
0976c3072c
|
engine(ngram): 万象语法模型(octagram .gram)读取端转为产品件
从评测工具的移植版升级为引擎库成员:装载从整文件 ReadFile 换成 Win32 mmap(RSS
只随命中页增长),新增进程内按路径共享的 shared_gram_db(照
ngram::shared_language_model 范式,查询全只读、多会话并发安全)。eval_lm_compare
改用同一份实现,评测与产品不再有两套读取器;darts.h(BSD)随目录入库。
|
2026-10-02 20:15:39 +08:00 |
|
fanlusky
|
31241d22ed
|
fix(engine,server,windows): 句中辅助码支持光标在句中插入,上屏按不带约束的排序调频
光标用箭头或 Ctrl+箭头移回句中后敲反引号,TSF、Server、引擎三处都要求光标在串尾,
反引号被当成标点,光标前的预编辑连同「·」一起上屏。改为只看光标前的部分:光标前是
完整的两键音节就收,落在音节中间或已有段的前面不收。形状规则新增
FanyImeMidSentenceHelpcode::AcceptsMarkerAt,三处共用;引擎的光标插入路径同时接受
句中的第一码和大写第二码。
句中辅助码筛过的列表里,要的词往往已经排第一,选第一个不调频,调了也是在筛过的列表
里排位,下次不敲辅助码时排序不变。现在带句中辅助码上屏词库候选时,改用去掉约束后的
候选作排位参照,选的是第一个也照样调;空格、数字键、鼠标点选都走 ProcessSelectionKey,
引擎自己的上屏路径同样处理。
|
2026-10-02 12:43:16 +08:00 |
|
fanlusky
|
fae80660c1
|
feat(engine,server,windows,ui-html): 双拼句中辅助码
在完整音节后敲反引号,接着的字母筛这个音节上的字:第一码大小写均可,
紧跟的大写字母是第二码,之后可以接着打,一个输入里可以有多段。开关
helpcode.shuangpin_mid_sentence_helpcode 默认关闭。
约束在整句解码时生效,而不是解完再筛:
- Google 解码器在 add_char_qwerty 里送 extend_mtrx_nd 之前按约束过滤词条,
锁住的字带着上下文重新整句解码;约束变化时整段重解,不污染增量搜索。
- kenlm 词格建图时按约束筛边,受约束的跨度不截断地查词库;神经重排只在
词格 n-best 里挑,自动满足约束。
- 普通词条、云和 AI 候选按同一规则收尾校验;满足不了就不出整句。
反引号段在请求里换成手动分隔符,原串留给宿主回读与预编辑显示;选词推进
时剩余音节上的约束随原串保留。输入形状规则放在 contracts/mid_sentence_helpcode.h,
TSF、Server、引擎共用,新增 Worker opcode MidSentenceHelpcodeChanged = 28。
googlepinyinime-rev 已与上游解绑,AGENTS 与 UPSTREAM.md 同步说明。
|
2026-10-02 02:20:37 +08:00 |
|
fanlusky
|
8004bc9250
|
fix(engine): imetest 直接链接正式引擎库,修正与实现不符的重排来源用例
engine/tests 手抄了一份引擎源文件清单,2c3546f8 新增 local_database.cpp
时只登记到 engine/CMakeLists.txt,imetest 自此链接失败;清单还漏了
shuangpin_custom_profile.cpp,也没有正式构建给 googlepinyinime 加的
/FI pinyin_file_io.h 与 /utf-8。改为 add_subdirectory 引擎根目录、链接
MetasequoiaIme::Engine,引擎源只在一处登记;vcpkg 依赖对齐根清单,
删掉无人引用的 includes/main.h。
test_pinyin.cpp 的「重排器同意」用例期望来源记为 NeuralDesktop,但
e7f7addd 引入的实现(word_lattice.h 的契约)是把被认可的三元首选提前、
来源仍为 Generated。以实现为准改测试。imetest 现在完整通过。
|
2026-10-02 00:52:12 +08:00 |
|
fanlusky
|
e502201fd9
|
fix(engine): 全拼别名改写后的 preedit 保留合法切分与手动分隔符
#565 让合法键入在被别名改写后退回无分隔的原始字母,多打一个字母就会丢掉
已显示的全部分隔符(hao'yong -> haoyonga)。现在按键入字母自身的合法读音
重新切分(hao'yong'a、dong'an)。BFS 无法解释时的退路改为原样显示键入,
不再吞掉用户手打的分隔符(haoyonga' 不再显示为 haoyonga)。
测试拆成两组:走简拼守卫的合法拼写,与走退路的输入。dongua、zhonguo 不经
过守卫,归入退路组,此前它们在修复前也能通过,不构成守卫分支的回归用例。
|
2026-10-01 22:57:51 +08:00 |
|
Jia Sui
|
efdea2c14f
|
fix(engine): 全拼合法键入被别名改写后 preedit 不再照搬改写串
|
2026-10-01 15:09:32 +08:00 |
|
fanlusky
|
3f430cd68e
|
fix(server): 双拼辅助码开启时快捷短语奇数长编码不再被拦掉
双拼开着辅助码时,「完整音节 + 一个字母」的编码(如 dia = di + a)会被认成带辅助码,
快捷短语混排见到辅助码就直接退出,这类编码的短语永远进不了候选。整串精确等于用户编码时
按短语处理,Server 的 PlaceQuickPhrases 与引擎的 quick_phrase_code 一并去掉辅助码判断,
并补双拼 dia 的回归用例。
|
2026-10-01 02:35:55 +08:00 |
|
fanlusky
|
c99fb8d585
|
fix(engine): 英文调频不再以中文权重为基准,并按输入记英文槽位
混输列表里英文调频把「全表最高权重 + 1000」写成新权重,中文候选也算在内。
在 zai 下选一次 zaire,它的权重就变成 19,196,987,超过「在」,并且对所有能查出它的输入永久生效。
- adjust_english_candidate_ranking 只用英文候选的权重当基准
- 新增 english_slots 表和 english_slot / set_english_slot /
learn_english_slot_selection / learn_english_slot_bypass:按输入记录首个英文候选在列表中的位置,
选中时按调频名次前移(可以升到首位),越过它时后退一位,最多到首页末位
- 新增 InputSession::reads_as_pinyin:整串能否读成拼音(允许简拼声母和打到一半的音节)
|
2026-09-30 22:49:27 +08:00 |
|
Lu Fan(SonnyCalcr)
|
5a74ee7a17
|
Merge pull request #557 from metasequoiaime/fix/rescore-worker-terminate
fix(engine): 别让重排后台线程的异常把输入法带走
|
2026-09-30 22:41:34 +08:00 |
|
fanlusky
|
0bc2ec1f19
|
test(engine): 重排回调在锁内 notify,避免碰到已析构的条件变量
等待方一拿到锁就可能离开作用域、析构栈上的 mutex 和条件变量,锁外 notify_all 会落在已析构的对象上。三个后台线程用例都改成在锁内 notify。
|
2026-09-30 22:34:05 +08:00 |
|
Jia Sui
|
5d8487a490
|
fix(engine): 别让重排后台线程的异常把输入法带走
Signed-off-by: Jia Sui <jsfaint@gmail.com>
|
2026-09-30 22:08:23 +08:00 |
|
Jia Sui
|
4fde941291
|
fix(engine): 测试的 main 再补一层 catch (...)
只接 std::exception 的话,第三方库或跨 FFI 边界抛出来的东西照样会逃出 main 走 abort,
和上一个 commit 修掉的是同一类故障:ctest 看不到原因,栈也不展开。引擎内现有的抛出确实
都是 std::exception 派生,但这一层缺的是「无论什么异常都到不了 terminate」的保证,补上它
才让 main 的边界真正闭合。
只加 catch 分支,输出通道沿用各文件原来那一个(fprintf / cerr / fmt::println),
不改任何断言。
|
2026-09-30 21:34:12 +08:00 |
|
Jia Sui
|
74e0a16776
|
fix(engine): 让测试的 main 接住逃逸的异常
这些测试用 throw 报告断言失败,但 13 个测试的 main 没有顶层 try,异常一路逃出去就会
走 std::terminate -> abort。abort 不做栈展开,ScopedDataDirectoryCleanup 的析构不执行,
测试建的临时目录和用户词库句柄留在原地;失败原因也不会打到 stderr,ctest 只能看到一段
崩溃,排查时得先猜是哪个断言。
按仓里已有的写法把函数体挪进 run_test(),main 变成一层薄包装,catch 里打 what() 再返回 1。
fuzzy_pinyin 的 create_directories 原本落在 try 之前,一并挪进去。
只是给失败换一条能读到的出口,没有改任何断言或阈值。
|
2026-09-30 21:32:07 +08:00 |
|
fanlusky
|
e365a8d1b5
|
perf(engine): 大模型改为只读映射并按需载入,缩短每键查询路径
- sc.lm 改用 kenlm 的 LAZY 载入:Windows 上默认的 POPULATE_OR_READ 会退成 READ,
把 33 MB 整份拷进私有堆且永不释放;现在是 MapViewOfFile 只读映射,可被系统回收
- dict_japanese.dat 在 Windows 上同样改为只读映射,与 POSIX 分支一致
- 神经整句模型改为开关首次生效时才载入(LazySentenceModel),默认关闭的桌面模型
不再占用约 24 MB
- 新增 reset_sentence_cache():重排结果与上文只影响词格整句,只清 series 一层,
逐前缀查库与切分缓存保持热;set_rescoring_context 也改用它
- CircularBuffer 新增 find():命中时返回指针而不复制整份候选列表,并改为真正的 LRU;
全拼、双拼每键的命中路径改用它
- Google 解码器去掉每次调用前的 im_reset_search,沿用上游的公共前缀增量搜索
- 测试:出货 sc.lm 能映射载入并正常打分;增量搜索与从头搜结果逐步一致
|
2026-09-30 19:58:18 +08:00 |
|
fanlusky
|
42fbb9541c
|
feat(engine): 快捷短语混入全拼 / 双拼候选并参与调频
Shift+K 快捷短语模式保留,仍按编码前缀查询(query_quick_phrases)。另外全拼 / 双拼下
编码精确匹配(query_quick_phrases_by_code)的快捷短语作为一组插在固定位置候选之后,由
LocalModeOptions::quick_phrase_candidates 单独开关;五笔不混。
用户日志新增 quick_phrase_slots 表记录快捷短语组前的普通候选数:选中短语让组前移,
越过组选普通候选让组后移,后移上限为页长减一。quick_phrase_frequency 关掉时组固定在
首位。与普通候选同文本时保留快捷短语那份。
|
2026-09-29 14:31:51 +08:00 |
|
fanlusky
|
d0feee74cc
|
fix(engine): 修正五笔混输的候选方案标记、通配排序与固定位置去重
- 首字母展开与固定位置补入的拼音候选不经过 query(),此前保留默认 Quanpin,
双拼会话里调频/删除会落到全拼引擎;现在按查询方案打标。
- 混输与 z 通配同时开启时,含 z 的编码拼音候选排在通配结果之前,避免通配
整页码行把 zi/zu 这类拼写的正解挤到后几页。
- 固定位置分区合并时,拼音分区 include_missing 补回的词若五笔分区已列出则去掉,
保持同词只留五笔一份。
|
2026-09-29 00:12:46 +08:00 |
|
Jia Sui
|
e9c824266f
|
test(engine): 恢复被拒第五键不改候选的断言
|
2026-09-28 18:31:18 +08:00 |
|
Jia Sui
|
a50232f06c
|
fix(engine): set_wubi_mixed_pinyin 不再复位 z 通配开关
|
2026-09-28 18:28:42 +08:00 |
|
Jia Sui
|
456fdb819c
|
feat(engine,server,ui-html,installer): 五笔拼音混输改为同时混排并拆出独立开关
Signed-off-by: Jia Sui <jsfaint@gmail.com>
|
2026-09-28 17:55:36 +08:00 |
|
fanlusky
|
69ee4fb49b
|
test(engine): 新增逐键耗时基准 bench_input_session
在真实词库上逐字母输入几串典型拼音(单词、短语、整句),每串前清空候选缓存,
按整句联想的几档设置统计每个键在引擎里的耗时(中位数 / p90 / 最大)。只构建、
不注册 ctest,也不选词、不写用户日志;用 METASEQUOIA_IME_DATA_DIR 指向数据目录
的副本运行。
本机一次结果(ms/键,冷缓存,5 轮):
设置 中位数 p90 最大
全关 0.10 0.19 1.04
词格 0.39 2.12 3.97
词格+Google 0.34 1.93 4.43
全开(含两档神经) 0.44 2.72 5.61
引擎查询本身不是打字卡顿的来源,性能工作应放在绘制、IPC 与 UI 线程等待上。
|
2026-09-27 17:02:04 +08:00 |
|
Lu Fan(SonnyCalcr)
|
4375d80414
|
feat: 支持导入自定义辅助码,内置“加加”更名为“拼音加加” (#477)
* feat(engine): 支持 helpcodes/custom 下的自定义辅助码
custom 目录里的每个 .txt 都是一套方案,方案名为 custom/<文件名>。
文件开头的 "# name:" / "# name_en:" 注释给出中英文显示名,缺省用文件名;
兼容 BOM、CRLF 和全角冒号。方案名不能跳出 custom 目录。
* feat(engine): WebView 契约新增 openHelpcodeDirectory
设置页请求打开自定义辅助码目录,仅允许 settings 页发送。同步 ui-html/webview2/shared。
* feat(server): 设置页列出自定义辅助码并可打开其目录
配置快照带上 helpcode.custom_schemas 与 helpcode.custom_directory,每次都会重新扫描;
方案文件被删除或改名后回退到 lantian。两个设置宿主都会处理 openHelpcodeDirectory。
* feat(ui-html): 辅助码设置显示拼音加加与自定义方案
内置方案“加加”改名为“拼音加加”。自定义方案追加在内置方案之后;新增说明区,
提供“打开目录”和“刷新方案”两个按钮,刷新完成后弹出提示。
* feat(installer): 升级与迁移数据目录时保留自定义辅助码
升级清理时只清掉 helpcodes 里的内置表,保留 custom 子目录;更换数据目录时一并迁移;
安装时建出 helpcodes\custom。
|
2026-09-26 20:21:55 +08:00 |
|
fanlusky
|
e7f7addd4f
|
feat(engine): 神经网络整句联想与词格 n-best 重排
- 新增 engine/neural:移植 chinese-ime-lm 的字符级 Transformer 推理,在词格解出的最多 12 条
n-best 里重排挑选整句,desktop / keyboard 两档模型各出一条首选,后台 worker 异步打分
- 词格支持 n-best 输出与重排(quanpin/lattice_rerank),全拼、双拼共用
- 新增 SentenceAssociationOptions,经 QueryRequest 传给各 provider;四个来源默认全关
- 资源契约登记两个 safetensors 与 NOTICE;模型二进制不进仓库,由
scripts/fetch-neural-model.ps1 按 neural-model/lock.json 下载并校验 SHA256
- 新增 neural_sentence 测试
|
2026-09-24 14:03:39 +08:00 |
|
Jia Sui
|
5b0be4e71d
|
Merge branch 'develop' into feat/caret-driven-resegmentation
|
2026-09-24 08:45:54 +08:00 |
|
fanlusky
|
7b7b19f449
|
fix(wubi): 前缀提示不挡混合拼音、候选去重,修复顶字/自动上屏后快打丢字母
- 只有精确码行才算五笔表已回答,前缀提示不再阻止混合拼音回退;拼音无结果时保留提示
- 五笔前缀查询按词去重,保留精确码行
- HideCandidate 抑制改为推送成功记账、Hide 消费(带 1s 时限),不再被下一个按键撤销;
四码唯一自动上屏路径同样记账
|
2026-09-23 15:41:04 +08:00 |
|
Jia Sui
|
1cfba58eba
|
feat(engine): 光标驱动的拼音前缀解码与候选重算
caret 从纯显示状态升级为查询管线输入:set_caret 量化到最后一个完整
音节单元边界(只消费 segment_raw_boundaries,#187 契约),前缀候选
独立解码、preedit 保持全串拼音风格;纯移动光标同样重算候选。
IInputScheme::set_raw_input 提升为接口方法以支持不动现场组合的
独立前缀查询。runtime_isolation 两处按 R3/R4 新语义改写(caret 在
音节中间 floor 到空前缀、候选为空是预期状态);新增 caret 前缀
会话单测(engine/tests + server CI 目标,隔离词库夹具,覆盖
全拼 floor、双拼贪心配对、五笔无单元模型、boundary=0 空候选)。
|
2026-09-22 14:56:22 +08:00 |
|
Jia Sui
|
536d0a95ef
|
feat(windows): 五笔四码后继续输入不丢字(顶字)
四码(不论是否唯一)之后继续敲字母时,先上屏该码的首选候选,再把这个字母
保留为下一次组合的开头。根因是两侧对「组合里有几个字母」认知不一致:TSF 的
本地按键缓冲跟着敲键涨(raw 预编辑样式下 preedit 就是它),服务端的五笔 raw
被引擎裁在四码;空格提交时组合文本被整段替换成候选并清掉本地缓冲,多敲的
字母随之丢失。
- 新增 worker 消息 CommitCandidateAndContinue(27),payload "<consumed>\t<text>":
TSF 按服务端给出的消费数裁**自己**的组合缓冲,而不是套用服务端按旧视图算好
的剩余串——快打时用户已多敲的字母因此不会被覆盖
- TSF 处理序列全部复用既有原语:_InsertTextToComposition + _HandleCompleteCommitFirst
真提交,_StartComposition + AddVirtualKey + _HandleCompositionInputWorker(NO_REQUEST_ID)
重建余下字母的组合;无组合时退化为直写
- 服务端顶字不看自动上屏开关:开关只决定唯一码要不要多敲一键才上屏,
不决定丢不丢输入;自动上屏那条路也改用同一消息,顺带收掉快打竞态
- 引擎新增 wubi_four_code_is_complete(完整四码、码表作答、不要求唯一;
空码不算——没有候选可提交时不触发顶字)
已知限制:光标在组合中间时的改码行为维持现状;空码继续敲字母仍会被忽略。
|
2026-09-22 09:23:25 +08:00 |
|
Jia Sui
|
5d1a9a6cc2
|
feat(server): 五笔四码唯一自动上屏
五笔敲满四码且码表只答一个候选时不再需要空格:第四个字母落下即上屏,下一个
字母直接开新组合。行为无条件生效,不设配置开关:业界默认开启(微软五笔、
搜狗五笔、极点、Rime 主流五笔方案),唯一候选没有第二选择,确认空格是纯冗余;
而一条开关要牵动配置五处同步,换不来可形成预期的行为差异,不值。
- engine 只报告事实(wubi_unique_four_code:完整四码、码表作答、候选恰好一个),
是否上屏由 Server 决定;判定收在 input_key_policy.h 的纯函数里,随 portable
目标进 ASan/UBSan;组词(creating_word)激活时组合保持打开,唯一四码不会把
正在拼的词截断上屏
- 提交复用 ProcessSelectionKey,与空格上屏同一套记账;但只能走 worker 管道推送
CommitCandidate:字母键在默认 raw 预编辑样式下不读请求-回复管道,回一帧 Normal
既不会上屏,还会在 TSF 侧留下永不释放的回复帧,而 Server 已按 Normal 清掉组合
- 上屏前按当前组合同步重建候选页:候选页异步发布,提交路径读的 ui.items /
page_words 否则可能还停在第三码那一拍
- 非唯一四码不做顶字上屏:一个按键只能回一条管道消息,真顶字需要扩 IPC 协议
|
2026-09-22 09:23:24 +08:00 |
|
Jia Sui
|
566ff8b832
|
feat(engine): 新增加加辅助码(拼音加加),第六套辅助码方案
按笔顺拆前两个部件、各取读音首字母(妈 = 女 n + 马 m = nm),部件用加加俗称读音(亻=d、
扌=t、纟=j、忄=s、攵=f、夂=z、刂=l、彳=s);138 个独体/部首字取「本字声母 + 起笔」。码表
7968 条,覆盖《通用规范汉字表》7846 字,比小鹤少 422 个扩展区生僻字——缺码会让含这些字的
词组拿不到辅助码,是已知取舍。
编码对齐拼音加加 5.x 安装包内的 fzm.bin(双拼键位已换算为声母):6487 字取到两码,6349 字
逐字一致,138 字保留本表规则,覆盖外的 800 字按同一规则重建。来源与非官方性质写在 NOTICE.md
与随包的 THIRD_PARTY_NOTICES.txt。
- engine/contracts/assets 注册 helpcode_jiajia,assets.h 重新生成(5 → 6)
- 设置页两个下拉显示名「加加」,两份配置模板注释同步
- 两侧测试补支持列表;README / README.en.md / server/AGENTS.md 改「六套」
默认值不变(双拼 lantian、全拼 ziranma)。
|
2026-09-20 22:00:34 +08:00 |
|
fanlusky
|
1ea01d5e0a
|
fix(quanpin): 备选切分保护位只拉首页外的候选,不覆盖调频名次
保护位原来对任何排在 index 1 之后的备选切分都生效,于是调频刚把 吉安 排到
自然第 5 位,它就因为成了 ji'an 组里权重最高的词、过了 1/100 门槛而被重新
钉回第 2 位。用户选一次就跳到第二,之后再怎么调也只能停在第二——固定槽位把
调频写进权重的结果整个覆盖掉了。
改成只对自然排序已经掉出首页(默认 page_size 6)的备选切分生效。西安 在出货
词库里的自然位置是 16,仍旧提升到第 2 位;调频过的 吉安 留在它自己挣来的位置
上,之后按 promote 一步步往上走。
夹具同步补候选:engine 与 server 的 xian 夹具原来只有三条主候选,备选切分自然
就在首页内,测不到提升这条路径。
|
2026-09-19 22:42:29 +08:00 |
|
fanlusky
|
01c5bca325
|
fix(engine): 选中的整句候选落成用户词组
词格(CandidateSource::Generated)和 Google 解码器(CandidateSource::Fallback)
猜出来的整句在词库里没有对应的行,而调频走的是
update_weight_by_pinyin_and_word——它只改 SQLite 里已经存在的行,找不到行就什么
也不做。于是用户选中这种整句多少次,下次输入同样的拼音,它仍然是猜出来的,仍然
排在词库里那条同音短语后面,看起来就是"永远提不上来"。
选中即落库才是对的处理:存成用户词组之后,下次它以 UserDatabase 候选出现,按
quanpin::generated_sentence_insert_position 的约定排在两条整句之前,之后再选还能
走正常调频。
落库不受 frequency_adjustment_ 约束(那是候选词频策略,不是造词),也不跳过
index == 0——首位整句同样没有落库,下次仍要靠猜。长度上限对标
quanpin::WordLatticeOptions::max_phrase_syllables,再长的整句只是这一次输入的
产物,落库除了撑大用户词库没有别的作用。
|
2026-09-19 13:48:17 +08:00 |
|
fanlusky
|
b4728fdb3b
|
test(engine): 九键删词只要求词库那一行消失
test_nine_key_session 的删词断言从词格整句合并(#430)之后就是红的:删掉
tbl_2_n 里的「你好」之后,重开 session 仍能看到「你好」,但它的 source 是
Generated——整句合成用还活着的单字「你」和「好」拼出来的,同一批候选里还有
「米好」。tbl_2_n 已经空了,删词本身没问题。
删词的契约是词库里那一行没了,不是这几个字从此打不出来(真要那样,删过一次
词就再也输入不了这个词)。断言相应收窄成 Database/UserDatabase 来源的候选。
|
2026-09-19 12:29:09 +08:00 |
|
fanlusky
|
e2a5f5f988
|
fix(engine): 词格整句只用精确读音,并补上多音字读音先验
长句联想此前会把读音对不上的词拿去造句,也分不清多音字的常见读音与生僻读音。
词格的跨度查询改用批量精确键查询。原先走的 query_segments_keyed_flat 带降级:
精确键查不到就退到前缀区间扫描(key >= "gun'qi" AND key < …),gun'qi 因此捞出
gun'qiu 的「滚球」,简拼那两级同理。联想出来的每个词,读音都必须精确对上用户
敲的那段拼音。顺带省掉了白扫的前缀区间,也不再需要 QuerySource——区分全拼与
双拼的只有简拼那几级。
新增读音先验:每一行按「自己的权重 / 该拼音跨度的权重和」算读音占比,占比低于
免罚线的部分按 log10 扣分。语言模型只看汉字,分不出「卷」读 gun、「而」读 neng
这种词库里权重为 0 的多音字读音,于是 gun'qi 出「卷七」、neng'fa'sheng 出
「而发生」。不能整体按 log10 P(词|拼音) 扣,那等于把字频算两遍(语言模型已经
算过一次),「跑得很快」会被「跑的很快」压掉——得/地 各占 de 的 7%~9%,本来就是
常见读音;真正的生僻读音占比是 1e-5 量级,离免罚线还差好几个数量级。
ü 的两种拼法归一化到词库存的那一种。intact_pinyin_list() 两种都认,词库只存
一种(j/q/x/y 后写 u,l/n 后写 v),切出来的 jv、lue 这类音节因此一行都查不到。
改精确键查询之后这会让整张词格断在那里——wo'men'lue'de 解不出任何路径,长句
联想整个消失。lue 和 lve 本来就是同一个读音的两种拼写,归一化之后仍是精确匹配。
新增两条回归用例:生僻读音不得赢下词格;421 个合法音节每个都必须在词库里至少
查得到一行,切分器与词库哪边漂了都先在这里炸。
|
2026-09-19 10:08:39 +08:00 |
|
fanlusky
|
03a5b4fe26
|
feat(engine): 整句候选把词格排到 Google 解码器之前
词格换成 kenlm 三元模型打分之后,它出的整句整体比 Google 解码器那条准,
所以由它占首位。
merge_lattice_candidates 的插入点本来就在 Fallback 之前,word_lattice.h
里也是这么写的不变量;真正把次序翻过来的是词典层在合并之后的一个后置
步骤——它把 Fallback 重新提回表头。全拼和双拼各有一份,这次都删掉。
这一步此前没有任何测试盯着:test_pinyin 里的用例断的是
merge_lattice_candidates 自身,词典层怎么再排它看不见。补一个查询层的
回归用例,断相对位置而不是具体句子——词格出什么取决于 msime.db 和
sc.lm,钉死了会随词库更新而碎;两条来源凑不齐时按既有风格优雅跳过。
顺带修掉 ScopedLocalAppDataOverride 上一条已经过期的注释:dict_pinyin.dat
现在随安装包发布了,不发布的只有 role=user 的 user_dict.dat。
|
2026-09-19 10:08:39 +08:00 |
|
fanlusky
|
80b1fc42a1
|
feat(engine): 词格整句改用 kenlm 三元模型打分
词格原先的整句打分是一份没校准过的启发式 unigram,质量很差。这里内嵌 libime
在用的 kenlm 查询子集,改由它的三元模型 sc.lm 来打路径分。
- engine/ngram 只编 kenlm 的 query / trie-build 路径。不定义 WITH_THREADS,
parallel_read.cc 因此退化成串行实现,整个子集零 Boost 依赖。
KENLM_MAX_ORDER=3 必须与建模阶数一致,改它等于读不懂已发布的模型文件。
- ngram::LanguageModel 是唯一 include kenlm 头文件的地方,对外只给出一个纯
std 的头,_HAS_AUTO_PTR_ETC 和 kenlm 的 include 目录都保持 PRIVATE。
- shared_language_model 按资源路径共享一份 mmap。kenlm 的查询是只读的且把
上下文放在调用方的 State 里,所以并发会话不需要额外加锁。
- 模型缺失时 valid() 为假,词格退回原来的启发式打分:整句候选只是变差,不会
停止工作。测试覆盖了缺模型降级与按路径共享。
- 出货的 ARPA 不带 <s>/</s>,所以解码从 null_state() 起算而非 begin_state()。
kenlm 是上游副本,不重排版、不进 clang-format,其 LGPL-2.1+ 与 BSD 例外清单
已补进 THIRD_PARTY_NOTICES.txt。googlepinyinime 那条 Fallback 整句路径不变。
|
2026-09-19 10:08:39 +08:00 |
|
Jia Sui
|
b1ec3ab1e0
|
feat(server): Ctrl+Backspace 按分词删除前置拼音(#187)
组词中 Ctrl+Backspace 删除光标前的一个分词单元:剩余拼音非空时删
最后一个字的拼音(音节段),剩余拼音为空时删除最后一段已选分词。
engine:
- InputSession::segment_raw_boundaries() 作为按段删除/移动的唯一分段来源:
全拼把纠错显示串的分隔符映回 raw 下标,双拼与 pinyin_segmentation 共用贪心谓词,
2 字辅助码自成一段;非拼音方案与本地模式返回空表示无单位模型
server:
- IsSegmentBackspaceKey(Ctrl-only)、整段丢弃 / 边界截断 / 单字符回退三分支,
截断点残留分隔符清理
- 复用 CompositionRestored 回包(含 caret),仅协商能力位且非 UiLess 时发送;
新出现「只剩已选汉字、无拼音」的中间态
windows:
- 两处 Ctrl 闸门对组词中的 Ctrl+VK_BACK 放行,新增 FUNCTION_BACKSPACE_SEGMENT
与 _HandleCompositionBackspaceSegment(读回包重建,失败回退单字符)
- 空 raw 状态渲染、终态清理 word_for_creating_word,延迟投影失效处理
Closes #187
|
2026-09-17 23:01:31 +08:00 |
|
Jia Sui
|
e64195731e
|
feat(server): Backspace 删空剩余拼音后退选已选分词(#35)
已选分词是造词中间态:删空剩余拼音时不再整条 composition 丢弃,
而是退选最后一段并恢复其原始拼音与候选。
engine:
- SelectionTransition 暴露本次选择消费掉的原始输入(保留大小写与双拼按键)
engine/contracts:
- 新增回复类型 CompositionRestored=14 与能力位 CompositionRestore;
MaxKnown 同步抬高,Server 按协商能力位门控发送,旧 DLL 不会收到未知帧
server:
- 造词中间态压 (consumed_raw, previous_creating_word) 快照,边界 Backspace 弹栈退选
- 任何清空 raw 的路径同时清 creating_word 与快照历史,避免跨 composition 泄漏
windows:
- _HandleCompositionBackspace 提示退选时读回包重建 keystroke buffer / word / 预编辑 / 光标
- NeedToCreateWord 与 CompositionRestored 共用载荷解析;旧 Server 与 UiLess 回退旧行为
Closes #35
|
2026-09-17 18:45:01 +08:00 |
|
Jia Sui
|
809c62995b
|
fix(engine): 全拼模糊音下备选切分抢位
模糊音开启时 QuanpinDictionary::query 对整个候选列表按 pinyin 字符串长度降序
排序(本意是压住模糊/前缀候选)。备选切分的 canonical 键带撇号且音节更多
(xi'e / ji'ang / you'di'an),字符串比主切分键(xie / jiang / you'dian)更长,
于是权重只有 6 的西鄂、23740 的激昂从保护位第 2 跳到第 1,顶掉 xie/jiang 的
首候选;xie 连模糊变体都没有,仅因排序无条件执行就被波及。
- 排序键改为候选覆盖的输入字母数(去掉撇号),与用户实际敲的长度对齐:备选
切分键与主键同层交给词频,模糊/前缀候选仍排在整串匹配之后。
- 备选切分保护位加词频门槛:备选最佳词权重达到主切分首位 1/100 以上才提升。
真歧义保留(xian -> 西安 55K 对 先 1.66M),罕见重码被拒(xie -> 西鄂 6、
jiang -> 激昂 23.7K、youdian -> 尤迪安 7)。
test_fuzzy_pinyin 补齐四个场景回归断言(夹具权重取自出货词库);
test_quanpin_scheme 的保护位用例移到新契约:夹具备选词权重 1 -> 100,
让它留在门槛内继续覆盖「真歧义保留保护位」。
|
2026-09-14 20:14:28 +08:00 |
|
Lu Fan(SonnyCalcr)
|
ac40675ab9
|
Merge pull request #368 from fanlusky/fix/quanpin-autocorrect-jianpin-compose
fix(engine): 全拼纠错支持纠正头部+简拼尾巴的组合 (follow-up #366/#367)
|
2026-09-13 16:40:49 +08:00 |
|
fanlusky
|
49d0bf58b5
|
fix(engine): 全拼纠错支持"纠正头部 + 简拼尾巴"的组合
问题:autocorrect_cut_kbest 只有在整个输入都能切成完整音节时才成功。
所以 hauzh(hau 打错 + zh 简拼)、hauz 这类"需纠正的头部 + 不完整音节
尾巴"整体切分失败,既不纠错也查不出词;而 huazh(hua 本就合法,zh 走
简拼)和 hauzhong(hua+zhong 都是完整音节)都正常。
改动:resolve_series_query 在整串纠错失败时,尝试剥掉结尾的简拼声母
(不完整音节:是合法拼音前缀但本身不是完整音节,如 zh/z/h),对头部
纠错,成功后把纠正结果与尾巴拼成 hua'zh 交给既有的简拼查询路径处理。
优先尝试更短的尾巴,让纠错尽量解释更多输入。复用同一套代价分层逻辑,
更贵的头部读音(如 hau→hai)仍排在便宜的(hau→hua)之后。
头部纠错排除邻键(neighbor)类型:邻键表最大、误报面最广,而本路径已经
放宽了"整串must是合法切分"的约束、去信任一个推测出来的简拼边界,两者
叠加会把漏字形状的输入变成噪声(shng → sun + g → 笋干,而 shng 在关闭
deletion 时本应不纠错)。换位/漏字/多字是结构性错拼,置信度足够,可组合。
效果:hauzh→hua'zh(化妆)、hauz→hua'z、gauzh→gua'zh(挂着)、
hauzhong→hua'zhong(华中)均可纠;huazh/nihaoa 等正常输入不受影响,
关闭纠错时也不组合;shng 关闭 deletion 时不再被组合成 sun'g。
验证:imetest 全绿(新增组合段位断言、关闭开关守卫、shng 不组合守卫);
server 集成测试全绿(QuanpinDictionaryAutocorrectDeletionTypo 等,0 失败);
eval(seed 42,300 样本)mixed 与 deletion 两模型逐档与基线一致;
scripts/format.sh --check 通过。
|
2026-09-13 16:25:32 +08:00 |
|
Jia Sui
|
94abc08e7c
|
feat(engine): ü 系拼写别名归一,非标准拼法候选带轻标记
nue/lue 是词库标准键 nve/lve 的混拼别名,此前按原样查库必然落空。
把别名归一收敛到 resolve_segments 唯一咽喉点(jqxy 系 jv→ju 既有行为
一并收敛),查询、调频、造词键全部落到标准拼法上;别名改写同步进
pinyin_segmentation_,mark_autocorrect_candidates 据此给非标准拼法的
候选写 corrected_from,前端以词尾 * 轻标记提示(对标搜狗纠错星标),
提交、调频、置顶、造词不受影响。
契约翻转:fold_autocorrect_letters 不再把 v 折叠成 u——旧契约
「别名永不标记」按产品决策废弃,别名即标记;nu/nv、lu/lv 是真实
不同音节,归一按段精确匹配 [nl]ue,绝不互串,nu'e 手动分隔符不受
影响。composition 层显示折叠保留(职责是分隔符重建的等价分类),
注释改写说明与字典侧标记契约的分工。
|
2026-09-13 15:56:29 +08:00 |
|
fanlusky
|
c064cc641a
|
fix(engine): 全拼纠错候选按编辑代价分层——便宜读音整组领先
问题:merge_alternative_segmentations 把首选读音与所有备选读音的候选
混在一起、纯按词典词频排序(注释自陈"跨切分词频不可直接比较",实现
却照排)。于是输入 gau 时,高频的 改/该(gai,邻键,权重13)盖过了
低频的 挂(gua,换位,权重10),尽管 gua 的编辑代价更低、更贴近"交换
字母顺序"的用户意图。
改动:
- AutocorrectCut 增加 edge_count/weight,report k-best 的编辑代价;
same_cost_as() 判定同代价。
- resolve_series_query 按代价把备选拆成两层:与首选同代价的进入词频
竞争(alternative_corrected_cuts,保留 uanli→{quan,cuan,...} 那类
等权消歧),严格更贵的落入 costlier_corrected_cuts。
- query_exact 将更贵读音排在整个首选代价层之后追加(仍查询、去重、
标注 corrected_from),词频不再能把更贵的纠错顶到便宜的前面。
权衡(重要):离线 eval(seed 42,300 样本)显示这是"编辑代价优先"
对"词频优先"的取舍——mixed 模型 R@1/R@3 各档下降约 8~17 个点,
deletion 模型小幅下降 1~3 点。原因是注错样本的原词分布跟随词频,
词频优先在整体召回上更强;本改动以召回换取"最便宜纠错优先"的确定性
排序,是产品取向的选择,经维护者确认后合入。
验证:imetest 全绿(新增 gau 代价分层用例、元音尾守卫用例);
scripts/format.sh --check 通过。
|
2026-09-13 15:06:55 +08:00 |
|
fanlusky
|
34c69fbe11
|
fix(engine): 全拼纠错门控——完整音节+单个元音尾不再当简拼,放行纠错
looks_like_syllable_with_jianpin_tail 把「若干完整音节 + 至多 1 个
结尾字母」一律判为简拼意图并压掉纠错。但该守卫只看尾巴长度,未区分
尾字母能否作简拼首字母:结尾是辅音(zheg = zhe + g)确实是简拼,
结尾是元音则不可能是简拼首字母,更可能是换位打错。
结果就是 gau 被判成 ga + 简拼 u 而不纠错,gauzai 因尾部剩 4 字母
逃过守卫却能纠成 gua'zai,行为不一致。
改为:结尾恰好 1 个字母时,仅当它是合理简拼首字母(辅音)才判为
简拼;裸元音结尾放行到纠错。gau -> gua、hau -> hua 现在可纠,
zheg / zher / keneng 等辅音尾仍不纠,多字母尾(sahng/shabg)不受影响。
验证:imetest 全绿(新增 gau/hau 守卫用例与 gau->gua 纠错用例);
eval(seed 42,300 样本)deletion 模型与基线逐档一致,mixed 模型
25%/50%/100% 档 R@1/R@3 各升约 1~1.7 个点、10% 档持平,读音还原不变。
|
2026-09-13 14:31:28 +08:00 |
|
Jia Sui
|
3c2f3ae374
|
feat(engine): 全拼纠错补齐漏字/多字,k-best 切分按词频消解歧义
此前纠错只有交换(sahng -> shang)与 QWERTY 邻键(shabg -> shang)两类,
生成器在离线阶段就把「一个错串对应多个音节」的变体整个丢掉,运行时也只
取代价最低的一条切分。三类输入因此没有出路:漏字的(shng)、多敲一键的
(shangg、sjhang)、漏声母连基础切分都切不出来的(uanli 想打 quan'li)。
本次按专利 CN 101133411 B 的思路重排整条链路:错串可能对应的读音全部
保留到运行时,先由 k-best 切分带出平行读音,再让词典词频决定谁排前面。
- 纠错类型从两类扩到四类:新增漏字(shng -> shang)与多字(shangg 双打、
sjhang 邻键 -> shang)。两个新位跟随既有 autocorrect_transposition /
autocorrect_neighbor 开关联动(任一打开即生效),定义保持独立,留待
将来的独立配置键。查询与 preedit 重建共用同一映射,两侧纠正空间一致。
- 生成器从两表扩到四表并保留歧义变体(ahan -> {shan, zhan});同一条
(错串, 正确) 对被两条规则产出时在两张表各留一份,保证每个类型开关
单独有效。正确音节收敛进 kCorrectSyllables 单表、行内改存 16-bit
下标,一万七千多行不再各自重复音节字符串,--check 拦截过期表头。
- 切分层新增 autocorrect_cut_kbest:按位置自左向右做 top-k 假设传播,
排序键是(纠错边数,边权之和,生成序)——最少纠错优先的契约不变,
权重只在同边数内打破平局,引擎绝不用一条额外纠错换更低权重。边权取
专利 [0052] 允许的估常量(交换 10 / 漏字 11 / 多字 12 / 邻键 13),
标定路径是挖掘用户日志。漏字/多字边不等长,切分段改按 raw 长度推进
位置,preedit 分隔跟着原始字母的区间走。
- 词典层固定 k=9,为离线评测定值:漏声母键 uan 有 10-15 个等权漏字
目标,k=3 按表序截断够不到 quan'li 的正确读音;k=9 提升 R@1/R@3
且 p95 不变。主切分仍是查询键,其余读音交给
merge_alternative_segmentations 与主读法在词频下竞争;备选读音在
缓存命中路径同样发布,corrected_from 标记对全部纠错读音生效;纠错
读音排在音形别名读音之前。结尾单插入(shangg = shang + g)天然是
简拼尾形状,仍被简拼形守卫拦下——多字纠错的可达面在中间/开头插入
与多音节输入。
- 纠错门放宽:!segments.empty() 从门里去掉。BFS 工作在原始字母上,
基础切分为空的丢声母输入恰是最需要纠错的一批,jian -> ian 现在能
走到纠错候选并按词频合并到首位。简拼形守卫保留:合法音节加至多一个
尾字母(zheg)是用户意图,任何纠错表都不得改写。
- preedit 补两道防线:zheg 在显式三位掩码下仍走简拼守卫,不被漏字表
重排为 zheng;只有 BFS 切分的读音与 scheme 别名层完全一致时才按
BFS 位置重画分隔符,否则 sahng 会被切成 sa'hng 而候选实际来自
别名读音 shang。
新增独立评测工具 eval_quanpin_autocorrect:从真实 msime.db 采样高频
词条,按交换/邻键/漏字/多字模型注错,测 R@1/R@3、读音还原率与温态
p50/p95,固定种子可复现。它是评测工具不是测试,永远不进 CI,作为
纠错阈值与门逻辑改动的零回归标尺;注错规则与生成器逐键一致,改任一
侧须同一提交同步另一侧。
表格测试改为按 (错串, 正确) 对断言唯一并加覆盖门(合计 >= 4700 条),
新增歧义保留、漏字/多字变体、k-best 排序与权重定序、preedit 守卫等
用例;engine/tests 的 README 与 AGENTS.md 同步写清 imetest 与评测
工具两个产物的边界。
|
2026-09-13 11:04:34 +08:00 |
|
Jia Sui
|
f28b01faf0
|
test(engine): 修复 imetest 构建树
|
2026-09-13 11:04:34 +08:00 |
|
fanlusky
|
cbd95f74e3
|
fix(engine): 消除 /W4 下的编译警告
一方代码在 /W4 下已零警告。剩余警告全部来自上游副本
(googlepinyinime-rev/、vendor/opencc/ 及其捆绑的 marisa),
按 AGENTS.md 保留上游原格式,有意不改。
- data_path.h、test_pinyin.cpp: _wgetenv 改 _wdupenv_s,返回值
用 optional 包一层,由 unique_ptr 负责 free
- quanpin_dictionary.cpp: 去掉条件表达式内的赋值。外层查询原本
只用其真假,reset_cache_if_database_changed() 可能使其失效,
故保留重取
- build_sql_for_checking_word: 删掉从未使用的 jp 形参
- 未使用的 catch 形参、int/size_type 符号不匹配
- wav_writer.h: 形参 sample_rate 遮蔽同名命名空间常量,改名
requested_sample_rate;默认值改为直接引用该常量,去掉魔数
- pinyin_file_io.h: _wfopen/_wopen 改 _wfsopen/_wsopen_s
最后一项没有用微软推荐的 _wfopen_s: 它打开的文件不可共享,而这是
词库文件,Server、TSF DLL 和设置进程会并发读同一份。_SH_DENYNO
正是被弃用调用原本的共享语义,因此只消除警告,不改变行为。
_wsopen_s 的 pmode 传 0,唯一调用方 userdict.cpp 用的是 O_WRONLY,
不带 _O_CREAT。
|
2026-09-13 02:22:53 +08:00 |
|
fanlusky
|
a7779d2b05
|
refactor(engine): 将 engine 从 submodule 内嵌为仓内一等组件
`vendor/MetasequoiaImeEngine` submodule 改为 `engine/` 目录,导入的是
product-lock.json 原本钉住的提交 c810d201,逐文件核对过 blob,因此这是一次
搬迁而不是引擎升级。引擎自己的嵌套 submodule(googlepinyinime-rev、utfcpp、
miniaudio)按各自 gitlink 一并展开,来源与提交见 engine/UPSTREAM.md。
Windows 专门化裁掉了不属于本产品的部分:whisper.cpp 与本地推理 provider、
macOS 示例宿主、整个 dictionary/ 建库流水线、上游 CI 与 release-please 配置。
词库仍按 product-lock.json 在构建时取发布产物。
引擎不再进 product-lock.json:没有 gitlink 就没有可比对的东西,锁里写一个上游
commit 等于宣称这棵树仍等于上游,而第一个 Windows 专门化改动落地时这句话就假了。
verify-contracts / verify-checkout 随之移除,verify-published 保留。
CI 与工具链:引擎源码并入根 scripts/format.sh(删掉引擎自己的那份,避免两套排除
列表分叉);引擎 voice.yml 里的 silero_vad.onnx 摘要校验移到本仓 lock job;
engine-bump-triage 工作流与其测试删除;gitsubmodule 的 dependabot 频率由 daily
改为 monthly。
README、README.en、AGENTS.md、CONTRIBUTING、docs/product-release.md 及各子目录
文档同步说明新的位置与边界。
|
2026-09-12 03:40:48 +08:00 |
|