kotoshu 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/CLAUDE.md +22 -5
- data/Cargo.lock +291 -0
- data/Cargo.toml +15 -0
- data/README.adoc +116 -0
- data/Rakefile +103 -0
- data/TODO.impl/00-vision.md +161 -0
- data/TODO.impl/22-url-pin-single-source-of-truth.md +120 -0
- data/TODO.impl/23-fix-url-defaults.md +57 -0
- data/TODO.impl/24-cli-auto-setup-prompt.md +76 -0
- data/TODO.impl/25-soft-onnxruntime-require.md +75 -0
- data/TODO.impl/26-onnx-vocab-json-generation.md +96 -0
- data/TODO.impl/27-end-to-end-smoke-spec.md +113 -0
- data/TODO.impl/28-readme-quickstart-verification.md +68 -0
- data/TODO.impl/29-status-command.md +84 -0
- data/TODO.impl/30-language-auto-detection.md +74 -0
- data/TODO.impl/31-download-progress.md +58 -0
- data/TODO.impl/32-sarif-output-verification.md +72 -0
- data/TODO.impl/36-cleanup-regressions-0.3.1.md +17 -0
- data/TODO.impl/37-hunspell-correctness-tier2.md +21 -0
- data/TODO.impl/38-onnx-semantic-gating.md +63 -0
- data/TODO.impl/39-tier3-and-beyond.md +36 -0
- data/TODO.impl/65-universal-kotoshu.md +251 -0
- data/TODO.impl/66-kotoshu-core.md +169 -0
- data/TODO.impl/67-kotoshu-rs-and-access-libraries.md +137 -0
- data/TODO.impl/68-sota-adoption.md +152 -0
- data/TODO.impl/69-sota-typo-eval.md +74 -0
- data/TODO.impl/70-sota-confidence-cascade.md +59 -0
- data/TODO.impl/71-sota-nested-model.md +63 -0
- data/TODO.impl/72-model2vec-evaluation.md +49 -0
- data/TODO.impl/73-rl-reranker.md +42 -0
- data/TODO.impl/74-distilled-reranker.md +36 -0
- data/TODO.impl/75-llm-evaluator.md +35 -0
- data/TODO.impl/76-optional-llm-backend.md +37 -0
- data/conformance/vectors.jsonl +2630 -0
- data/data/en/grammar/rules.yaml +109 -0
- data/docs/cache-architecture.md +502 -0
- data/ext/kotoshu_native/Cargo.toml +37 -0
- data/ext/kotoshu_native/extconf.rb +15 -0
- data/ext/kotoshu_native/src/lib.rs +15 -0
- data/lib/kotoshu/algorithms/edit_distance.rb +57 -7
- data/lib/kotoshu/algorithms/lookup/affix_form.rb +132 -0
- data/lib/kotoshu/algorithms/lookup/compound_checks.rb +209 -0
- data/lib/kotoshu/algorithms/lookup/compound_form.rb +50 -0
- data/lib/kotoshu/algorithms/lookup.rb +95 -236
- data/lib/kotoshu/algorithms/suggest.rb +231 -50
- data/lib/kotoshu/cache/base_cache.rb +46 -3
- data/lib/kotoshu/cache/model_cache.rb +449 -7
- data/lib/kotoshu/cache/model_registry.rb +109 -0
- data/lib/kotoshu/cache.rb +1 -0
- data/lib/kotoshu/cli/cache_command.rb +44 -38
- data/lib/kotoshu/cli.rb +34 -4
- data/lib/kotoshu/configuration.rb +61 -7
- data/lib/kotoshu/conformance_exporter.rb +256 -0
- data/lib/kotoshu/conformance_runner.rb +176 -0
- data/lib/kotoshu/core/indexed_dictionary.rb +7 -2
- data/lib/kotoshu/debug_mode.rb +2 -0
- data/lib/kotoshu/dictionary/base.rb +14 -0
- data/lib/kotoshu/dictionary/plain_text.rb +17 -0
- data/lib/kotoshu/embeddings/onnx_runtime_model.rb +11 -1
- data/lib/kotoshu/embeddings/vocabulary.rb +25 -1
- data/lib/kotoshu/metrics_collector.rb +16 -0
- data/lib/kotoshu/metrics_module.rb +11 -0
- data/lib/kotoshu/models/onnx_model.rb +22 -23
- data/lib/kotoshu/native.rb +83 -0
- data/lib/kotoshu/native_backend.rb +131 -0
- data/lib/kotoshu/readers/aff_data.rb +87 -16
- data/lib/kotoshu/readers/aff_reader.rb +5 -4
- data/lib/kotoshu/readers/lookup_builder.rb +1 -15
- data/lib/kotoshu/resource_manager.rb +176 -22
- data/lib/kotoshu/source_registry.rb +21 -4
- data/lib/kotoshu/spellchecker.rb +24 -0
- data/lib/kotoshu/suggestions/frequency_provider.rb +59 -0
- data/lib/kotoshu/suggestions/semantic_cascade.rb +73 -0
- data/lib/kotoshu/suggestions/strategies/base_strategy.rb +10 -0
- data/lib/kotoshu/suggestions/strategies/composite_strategy.rb +62 -3
- data/lib/kotoshu/suggestions/strategies/edit_distance_strategy.rb +70 -199
- data/lib/kotoshu/suggestions/strategies/semantic_strategy.rb +9 -0
- data/lib/kotoshu/suggestions/suggestion_set.rb +9 -2
- data/lib/kotoshu/suggestions.rb +2 -0
- data/lib/kotoshu/version.rb +1 -1
- data/lib/kotoshu.rb +20 -4
- data/rakelib/compile.rake +10 -0
- metadata +61 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48277511d3fd0b5244b98f54a43c1867991fd943092c9b78f657885addfa940e
|
|
4
|
+
data.tar.gz: 0434b01a14db2d334fceb53ced9003ac1378abf75db4386aa5397dfc5d853367
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: adb238648d03837cb4b0fc326c4038d3a40bb5cea968f6ffc49bd2a4bc2ef2614a42a1b79f4cd69e5efc279c0730878aa64885a2384f5ab9cca61c8ade7f096e
|
|
7
|
+
data.tar.gz: 3c1f20fd74572d1ae069767c78faad2fb37caffa3a051d8f6ca2355816f3e6d96fee23de275166b8307cd2622ac80d6982d4aaab35db2617c675e4766d705514
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] — 2026-09-05
|
|
9
|
+
|
|
10
|
+
The universal-kotoshu cut (plan 65's roadmap milestone): tiered
|
|
11
|
+
models, the Rust core, and the native extension.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Model tiers** — `full` (~120 MB) / `fluency` (~15 MB, the new
|
|
16
|
+
default) / `mini` (~3 MB), resolved through the models repo's
|
|
17
|
+
registry (`kotoshu://models/{lang}/{tier}`) with sha256-verified
|
|
18
|
+
primary→mirror→vocab downloads; `KOTOSHU_MODEL_TIER`, `setup
|
|
19
|
+
LANG --model --tier`, and a tier-less legacy-cache bridge.
|
|
20
|
+
- **Native extension** (`ext/kotoshu_native`, optional Rust
|
|
21
|
+
accelerator): `KOTOSHU_BACKEND=ruby|native|auto` (default ruby;
|
|
22
|
+
pure-Ruby installs unchanged), `Kotoshu::Native.available?`, and
|
|
23
|
+
`rake kotoshu:conformance:compare` — 2630 vectors, zero divergences
|
|
24
|
+
between the Ruby and Rust engines.
|
|
25
|
+
- **Conformance export** — `rake kotoshu:conformance:export` freezes
|
|
26
|
+
engine behavior as golden vectors (2630 lines) consumed by
|
|
27
|
+
kotoshu-rs CI.
|
|
28
|
+
- **Confidence cascade** — `semantic_cascade_threshold`
|
|
29
|
+
(`KOTOSHU_SEMANTIC_CASCADE_THRESHOLD`): skip the ONNX rerank when
|
|
30
|
+
the composite strategies are already confident.
|
|
31
|
+
- Hunspell correctness: CHECKCOMPOUNDPATTERN replacement support,
|
|
32
|
+
dot-split casing, INITCAP ngram-root skip, AF alias flag-format
|
|
33
|
+
parsing, and a long tail of fixture fixes (integrational suite
|
|
34
|
+
176 examples, 0 failures, 1 documented pending).
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Model downloads cached 134-byte LFS pointer stubs whose own sha256
|
|
39
|
+
verified forever — the model source now fetches from the media host
|
|
40
|
+
and rejects stubs at download and verify.
|
|
41
|
+
- `Embeddings::Vocabulary` crashed on the shipped wrapped vocab
|
|
42
|
+
format; shared normalizer added.
|
|
43
|
+
- `Models::OnnxModel` used a nonexistent ONNX Runtime API and wrong
|
|
44
|
+
IO names — rewritten against `InferenceSession`.
|
|
45
|
+
- CLI JSON output restored to the documented camelCase contract;
|
|
46
|
+
`DictionaryNotFoundError` mapping restored at the cache boundary.
|
|
47
|
+
|
|
8
48
|
## [0.5.0] — 2026-06-30
|
|
9
49
|
|
|
10
50
|
Tier 2.5 release. Adds the personal-dictionary CLI, completes direct
|
data/CLAUDE.md
CHANGED
|
@@ -9,8 +9,25 @@ dictionary/affix backend (Hunspell-style) with ONNX-converted FastText word
|
|
|
9
9
|
embeddings for context-aware suggestions. The README.adoc is the authoritative
|
|
10
10
|
user-facing description; this file is the contributor-facing map.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Runtime deps (`kotoshu.gemspec`): `thor` (CLI), `rubyzip` (archive extraction during setup), `lutaml-model` (all model (de)serialization — see below). **Soft deps** (NOT in gemspec; `gem install kotoshu` works without them): `onnxruntime` (semantic / ONNX inference) and `suika` (Japanese morphological tokenization; its native ext `dartsclone` would otherwise break slim installs). Ruby 3.1+.
|
|
13
|
+
|
|
14
|
+
## Serialization rule (load-bearing)
|
|
15
|
+
|
|
16
|
+
All model classes use `lutaml-model` for serialization — declare attributes via `attribute :name, :type` and wire names via `mapping do ... end`. **Never hand-roll `to_h` / `from_h` / `to_json` / `from_json` / `serialize` / `deserialize`** on a model. Renames go in `mapping`, not in method bodies. This is a global rule (~/.claude/CLAUDE.md) — existing hand-rolled serialization is debt to migrate, not a pattern to extend.
|
|
17
|
+
|
|
18
|
+
## Workspace: five repos under `kotoshu/`
|
|
19
|
+
|
|
20
|
+
The parent directory `/Users/mulgogi/src/kotoshu/` is a multi-repo workspace — the gem is one piece. The other four are **content repos** fetched on demand by the cache layer, never vendored:
|
|
21
|
+
|
|
22
|
+
| Sibling dir | Repo | Role |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `kotoshu/` (this one) | `kotoshu/kotoshu` | The gem — Ruby API + CLI + analysis pipeline |
|
|
25
|
+
| `dictionaries/` | `kotoshu/dictionaries` | Hunspell `.aff`/`.dic` for ~98 languages (download source for `LanguageCache`) |
|
|
26
|
+
| `frequency-list-kelly/` | `kotoshu/frequency-list-kelly` | Kelly Project frequency tiers (download source for `FrequencyCache`) |
|
|
27
|
+
| `models-fasttext-onnx/` | `kotoshu/models-fasttext-onnx` | FastText `.vec` → ONNX (download source for `ModelCache`) |
|
|
28
|
+
| `kotoshu.github.io/` | `kotoshu/kotoshu.github.io` | Marketing/docs site |
|
|
29
|
+
|
|
30
|
+
Each content repo has its own `TODO.impl/`. The vision and cross-repo plan index live in `TODO.impl/00-vision.md`.
|
|
14
31
|
|
|
15
32
|
## Development Commands
|
|
16
33
|
|
|
@@ -105,7 +122,7 @@ Three caches under `~/.cache/kotoshu/` (see `CACHE_ARCHITECTURE.md` for detail,
|
|
|
105
122
|
|---|---|---|---|
|
|
106
123
|
| `Cache::LanguageCache` | `~/.cache/kotoshu/languages/{code}/spelling/` | `github.com/kotoshu/dictionaries` | 7 days |
|
|
107
124
|
| `Cache::FrequencyCache` | `~/.cache/kotoshu/frequency-lists/{code}/` | `github.com/kotoshu/frequency-list-kelly` | 7 days |
|
|
108
|
-
| `Cache::ModelCache` | `~/.cache/kotoshu/models/{code}/...` | `github.com/kotoshu/models-fasttext-onnx` (FastText `.vec` → ONNX converted upstream) | 30 days |
|
|
125
|
+
| `Cache::ModelCache` | `~/.cache/kotoshu/models/{code}/...` | `github.com/kotoshu/models-fasttext-onnx` (FastText `.vec` → ONNX converted upstream; tiers: full/fluency/mini, registry-resolved) | 30 days |
|
|
109
126
|
|
|
110
127
|
`FrequencyCache` feeds `frequency_bonus` in `Suggestions::Strategies::EditDistanceStrategy` — high-frequency words get a ranking boost. The `kotoshu cache` subcommand exposes list/status/download/info/purge/clean operations.
|
|
111
128
|
|
|
@@ -170,7 +187,7 @@ When implementing features, study these alongside Kotoshu:
|
|
|
170
187
|
|
|
171
188
|
## Other Notes
|
|
172
189
|
|
|
173
|
-
- License is **BSD-2-Clause** (
|
|
190
|
+
- License is **BSD-2-Clause** (per `kotoshu.gemspec` and `LICENSE`).
|
|
174
191
|
- RBS signatures live in `sig/kotoshu.rbs` (the `sig/kotoshu/` subdirectory is empty). Update signatures when changing public APIs.
|
|
175
|
-
- `scripts/` contains one-off utilities (FastText→ONNX conversion in Python, Kelly frequency parsing, diagnostics). `examples/` has numbered walkthrough scripts (`01_*.rb` … `
|
|
192
|
+
- `scripts/` contains one-off utilities (FastText→ONNX conversion in Python, Kelly frequency parsing, diagnostics). `examples/` has numbered walkthrough scripts (`01_*.rb` … `14_*.rb`) plus per-language demo scripts and Spylls-comparison scripts.
|
|
176
193
|
- Design history and superseded planning docs live in `docs/` (`architecture.md`, `cache-architecture.md`, `performance.md`, `plugins.md`, `getting-started.md`, plus integrated planning docs like `KOTOSHU_SOLIDIFICATION_PLAN.md`, `ARCHITECTURE_IMPROVEMENTS.md`, `TDD_ITERATION_STRATEGY.md`). Treat them as historical context, verify against current code before relying on them. `TODO.impl/` is the current source of truth for execution plans.
|
data/Cargo.lock
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "aho-corasick"
|
|
7
|
+
version = "1.1.5"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"memchr",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "bindgen"
|
|
16
|
+
version = "0.72.1"
|
|
17
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
18
|
+
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
|
19
|
+
dependencies = [
|
|
20
|
+
"bitflags",
|
|
21
|
+
"cexpr",
|
|
22
|
+
"clang-sys",
|
|
23
|
+
"itertools",
|
|
24
|
+
"proc-macro2",
|
|
25
|
+
"quote",
|
|
26
|
+
"regex",
|
|
27
|
+
"rustc-hash",
|
|
28
|
+
"shlex",
|
|
29
|
+
"syn",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "bitflags"
|
|
34
|
+
version = "2.13.1"
|
|
35
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
36
|
+
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
|
37
|
+
|
|
38
|
+
[[package]]
|
|
39
|
+
name = "cexpr"
|
|
40
|
+
version = "0.6.0"
|
|
41
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
42
|
+
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
|
43
|
+
dependencies = [
|
|
44
|
+
"nom",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[[package]]
|
|
48
|
+
name = "cfg-if"
|
|
49
|
+
version = "1.0.4"
|
|
50
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
51
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
52
|
+
|
|
53
|
+
[[package]]
|
|
54
|
+
name = "clang-sys"
|
|
55
|
+
version = "1.9.1"
|
|
56
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
57
|
+
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
|
|
58
|
+
dependencies = [
|
|
59
|
+
"glob",
|
|
60
|
+
"libc",
|
|
61
|
+
"libloading",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[[package]]
|
|
65
|
+
name = "either"
|
|
66
|
+
version = "1.18.0"
|
|
67
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
68
|
+
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
|
69
|
+
|
|
70
|
+
[[package]]
|
|
71
|
+
name = "glob"
|
|
72
|
+
version = "0.3.4"
|
|
73
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
74
|
+
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "itertools"
|
|
78
|
+
version = "0.13.0"
|
|
79
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
80
|
+
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
|
81
|
+
dependencies = [
|
|
82
|
+
"either",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[[package]]
|
|
86
|
+
name = "kotoshu"
|
|
87
|
+
version = "0.1.0"
|
|
88
|
+
source = "git+https://github.com/kotoshu/kotoshu-rs?branch=main#9941f1ea1a6157c57659e428f475959508d4d0b3"
|
|
89
|
+
dependencies = [
|
|
90
|
+
"magnus",
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
[[package]]
|
|
94
|
+
name = "kotoshu_native"
|
|
95
|
+
version = "0.0.0"
|
|
96
|
+
dependencies = [
|
|
97
|
+
"kotoshu",
|
|
98
|
+
"magnus",
|
|
99
|
+
"rb-sys",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
[[package]]
|
|
103
|
+
name = "lazy_static"
|
|
104
|
+
version = "1.5.0"
|
|
105
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
106
|
+
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|
107
|
+
|
|
108
|
+
[[package]]
|
|
109
|
+
name = "libc"
|
|
110
|
+
version = "0.2.189"
|
|
111
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
112
|
+
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
113
|
+
|
|
114
|
+
[[package]]
|
|
115
|
+
name = "libloading"
|
|
116
|
+
version = "0.8.9"
|
|
117
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
118
|
+
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
|
119
|
+
dependencies = [
|
|
120
|
+
"cfg-if",
|
|
121
|
+
"windows-link",
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
[[package]]
|
|
125
|
+
name = "magnus"
|
|
126
|
+
version = "0.8.2"
|
|
127
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
128
|
+
checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012"
|
|
129
|
+
dependencies = [
|
|
130
|
+
"magnus-macros",
|
|
131
|
+
"rb-sys",
|
|
132
|
+
"rb-sys-env",
|
|
133
|
+
"seq-macro",
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
[[package]]
|
|
137
|
+
name = "magnus-macros"
|
|
138
|
+
version = "0.8.0"
|
|
139
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
140
|
+
checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
|
|
141
|
+
dependencies = [
|
|
142
|
+
"proc-macro2",
|
|
143
|
+
"quote",
|
|
144
|
+
"syn",
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
[[package]]
|
|
148
|
+
name = "memchr"
|
|
149
|
+
version = "2.8.3"
|
|
150
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
151
|
+
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
152
|
+
|
|
153
|
+
[[package]]
|
|
154
|
+
name = "minimal-lexical"
|
|
155
|
+
version = "0.2.1"
|
|
156
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
157
|
+
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|
158
|
+
|
|
159
|
+
[[package]]
|
|
160
|
+
name = "nom"
|
|
161
|
+
version = "7.1.3"
|
|
162
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
163
|
+
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
|
164
|
+
dependencies = [
|
|
165
|
+
"memchr",
|
|
166
|
+
"minimal-lexical",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
[[package]]
|
|
170
|
+
name = "proc-macro2"
|
|
171
|
+
version = "1.0.107"
|
|
172
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
173
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
174
|
+
dependencies = [
|
|
175
|
+
"unicode-ident",
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
[[package]]
|
|
179
|
+
name = "quote"
|
|
180
|
+
version = "1.0.47"
|
|
181
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
182
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
183
|
+
dependencies = [
|
|
184
|
+
"proc-macro2",
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
[[package]]
|
|
188
|
+
name = "rb-sys"
|
|
189
|
+
version = "0.9.130"
|
|
190
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
191
|
+
checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
|
|
192
|
+
dependencies = [
|
|
193
|
+
"rb-sys-build",
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
[[package]]
|
|
197
|
+
name = "rb-sys-build"
|
|
198
|
+
version = "0.9.130"
|
|
199
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
200
|
+
checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
|
|
201
|
+
dependencies = [
|
|
202
|
+
"bindgen",
|
|
203
|
+
"lazy_static",
|
|
204
|
+
"proc-macro2",
|
|
205
|
+
"quote",
|
|
206
|
+
"regex",
|
|
207
|
+
"shell-words",
|
|
208
|
+
"syn",
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
[[package]]
|
|
212
|
+
name = "rb-sys-env"
|
|
213
|
+
version = "0.2.3"
|
|
214
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
215
|
+
checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
|
|
216
|
+
|
|
217
|
+
[[package]]
|
|
218
|
+
name = "regex"
|
|
219
|
+
version = "1.13.1"
|
|
220
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
221
|
+
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
|
222
|
+
dependencies = [
|
|
223
|
+
"aho-corasick",
|
|
224
|
+
"memchr",
|
|
225
|
+
"regex-automata",
|
|
226
|
+
"regex-syntax",
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
[[package]]
|
|
230
|
+
name = "regex-automata"
|
|
231
|
+
version = "0.4.18"
|
|
232
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
233
|
+
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
|
234
|
+
dependencies = [
|
|
235
|
+
"aho-corasick",
|
|
236
|
+
"memchr",
|
|
237
|
+
"regex-syntax",
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
[[package]]
|
|
241
|
+
name = "regex-syntax"
|
|
242
|
+
version = "0.8.11"
|
|
243
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
244
|
+
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
|
245
|
+
|
|
246
|
+
[[package]]
|
|
247
|
+
name = "rustc-hash"
|
|
248
|
+
version = "2.1.3"
|
|
249
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
250
|
+
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
|
251
|
+
|
|
252
|
+
[[package]]
|
|
253
|
+
name = "seq-macro"
|
|
254
|
+
version = "0.3.6"
|
|
255
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
256
|
+
checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
|
|
257
|
+
|
|
258
|
+
[[package]]
|
|
259
|
+
name = "shell-words"
|
|
260
|
+
version = "1.1.1"
|
|
261
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
262
|
+
checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
|
|
263
|
+
|
|
264
|
+
[[package]]
|
|
265
|
+
name = "shlex"
|
|
266
|
+
version = "1.3.0"
|
|
267
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
268
|
+
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
269
|
+
|
|
270
|
+
[[package]]
|
|
271
|
+
name = "syn"
|
|
272
|
+
version = "2.0.119"
|
|
273
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
274
|
+
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
|
275
|
+
dependencies = [
|
|
276
|
+
"proc-macro2",
|
|
277
|
+
"quote",
|
|
278
|
+
"unicode-ident",
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
[[package]]
|
|
282
|
+
name = "unicode-ident"
|
|
283
|
+
version = "1.0.24"
|
|
284
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
285
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
286
|
+
|
|
287
|
+
[[package]]
|
|
288
|
+
name = "windows-link"
|
|
289
|
+
version = "0.2.1"
|
|
290
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
291
|
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
data/Cargo.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Kotoshu gem Rust workspace (plan 66 / P4b) — mirrors parsanol-ruby's
|
|
2
|
+
# root Cargo.toml: rb_sys's compile task runs `cargo metadata` from the gem
|
|
3
|
+
# root, so the extension crate must be reachable through a root manifest.
|
|
4
|
+
[workspace]
|
|
5
|
+
resolver = "2"
|
|
6
|
+
members = ["ext/kotoshu_native"]
|
|
7
|
+
|
|
8
|
+
# Release profile follows the kotoshu-rs workspace policy (plan 66): fat
|
|
9
|
+
# LTO and single codegen unit so the shipped accelerator is as fast as the
|
|
10
|
+
# core's own optimized builds. Local development builds use the dev profile
|
|
11
|
+
# via RB_SYS_CARGO_PROFILE (see ext/kotoshu_native/extconf.rb).
|
|
12
|
+
[profile.release]
|
|
13
|
+
opt-level = 3
|
|
14
|
+
lto = "fat"
|
|
15
|
+
codegen-units = 1
|
data/README.adoc
CHANGED
|
@@ -171,6 +171,50 @@ analyzer.suggest_corrections("helo", context: "I said helo to the world").map(&:
|
|
|
171
171
|
NOTE: The semantic path requires the optional `onnxruntime` gem. See
|
|
172
172
|
<<_requirements,Requirements>>.
|
|
173
173
|
|
|
174
|
+
[[confidence-cascade]]
|
|
175
|
+
=== Confidence cascade
|
|
176
|
+
|
|
177
|
+
When the traditional (dictionary + edit distance) strategies are already
|
|
178
|
+
confident about a word, the semantic rerank adds latency and model load
|
|
179
|
+
without changing the answer. The confidence cascade formalizes the
|
|
180
|
+
hybrid split: dictionary verdict first, ONNX rerank only for uncertain
|
|
181
|
+
candidates.
|
|
182
|
+
|
|
183
|
+
The `semantic_cascade_threshold` configuration option controls it:
|
|
184
|
+
|
|
185
|
+
[cols="1,3"]
|
|
186
|
+
|===
|
|
187
|
+
|Threshold |Behavior
|
|
188
|
+
|
|
189
|
+
|`1.0` (default)
|
|
190
|
+
|Always rerank — identical to the classic pipeline. This is the sentinel:
|
|
191
|
+
the composite can legitimately score exactly `1.0`, so `1.0` never skips.
|
|
192
|
+
|
|
193
|
+
|`0.9`
|
|
194
|
+
|Rerank only when the top traditional candidate scores below `0.9`;
|
|
195
|
+
otherwise skip the ONNX rerank for that word.
|
|
196
|
+
|
|
197
|
+
|`0.0`
|
|
198
|
+
|Never rerank — the semantic path is bypassed entirely.
|
|
199
|
+
|===
|
|
200
|
+
|
|
201
|
+
[source,bash]
|
|
202
|
+
----
|
|
203
|
+
# Tune via environment variable
|
|
204
|
+
KOTOSHU_SEMANTIC_CASCADE_THRESHOLD=0.9 kotoshu check document.txt
|
|
205
|
+
|
|
206
|
+
# Or via the Ruby API
|
|
207
|
+
Kotoshu.configuration.semantic_cascade_threshold = 0.9
|
|
208
|
+
----
|
|
209
|
+
|
|
210
|
+
Each skip is counted in the metrics registry
|
|
211
|
+
(`Kotoshu::Metrics.stats[:semantic_cascade_skips]`, also exported in
|
|
212
|
+
StatsD and Prometheus formats) and logged under debug/verbose output.
|
|
213
|
+
|
|
214
|
+
Tuning guidance will come from the eval harness once the corpus
|
|
215
|
+
benchmark lands; until then, `0.8`–`0.9` is a reasonable range for
|
|
216
|
+
typo-heavy text.
|
|
217
|
+
|
|
174
218
|
[[multi-language]]
|
|
175
219
|
== Multi-Language Support
|
|
176
220
|
|
|
@@ -276,6 +320,78 @@ https://github.com/kotoshu/models-fasttext-onnx[`kotoshu/models-fasttext-onnx']
|
|
|
276
320
|
repo. The CLI downloads pre-converted artifacts; users do not run
|
|
277
321
|
conversion locally.
|
|
278
322
|
|
|
323
|
+
[[model-tiers]]
|
|
324
|
+
=== Model tiers
|
|
325
|
+
|
|
326
|
+
ONNX models come in three tiers, published by
|
|
327
|
+
https://github.com/kotoshu/models-fasttext-onnx[models-fasttext-onnx]
|
|
328
|
+
in a resource registry under stable ids (`kotoshu://models/{lang}/{tier}`):
|
|
329
|
+
|
|
330
|
+
[cols="1,2,3"]
|
|
331
|
+
|===
|
|
332
|
+
|Tier |Approx. size |What it is
|
|
333
|
+
|
|
334
|
+
|`full`
|
|
335
|
+
|~120 MB
|
|
336
|
+
|Unquantized, ~100k-word vocabulary. The same artifact and cache layout
|
|
337
|
+
as before tiers existed.
|
|
338
|
+
|
|
339
|
+
|`fluency`
|
|
340
|
+
|~15-18 MB
|
|
341
|
+
|int8-quantized, reduced vocabulary (~50k-60k words).
|
|
342
|
+
|
|
343
|
+
|`mini`
|
|
344
|
+
|~3 MB
|
|
345
|
+
|int8-quantized, 10k-word vocabulary.
|
|
346
|
+
|===
|
|
347
|
+
|
|
348
|
+
Tiered downloads are verified: the model bytes are fetched from the
|
|
349
|
+
registry entry's primary URL, falling back to its mirror, with the vocab
|
|
350
|
+
sibling downloaded alongside; the SHA-256 is checked against the
|
|
351
|
+
registry entry before the model is accepted into the cache.
|
|
352
|
+
|
|
353
|
+
.Picking a tier
|
|
354
|
+
[example]
|
|
355
|
+
====
|
|
356
|
+
[source,ruby]
|
|
357
|
+
----
|
|
358
|
+
# One-time setup at a specific tier
|
|
359
|
+
Kotoshu.setup(:en, want: %i[model], tier: :fluency)
|
|
360
|
+
|
|
361
|
+
# Resolve stays cache-only: the requested tier must already be cached,
|
|
362
|
+
# otherwise ResourceNotSetupError is raised (no download, no fallback
|
|
363
|
+
# to another tier)
|
|
364
|
+
Kotoshu.resolve(language: :en, want: %i[spelling model], tier: :fluency)
|
|
365
|
+
|
|
366
|
+
# "Is this tier cached?" (tier: :any asks whether any tier is cached)
|
|
367
|
+
Kotoshu.setup?(:en, resource: :model, tier: :mini)
|
|
368
|
+
----
|
|
369
|
+
|
|
370
|
+
[source,bash]
|
|
371
|
+
----
|
|
372
|
+
kotoshu setup en --model --tier mini
|
|
373
|
+
----
|
|
374
|
+
====
|
|
375
|
+
|
|
376
|
+
`tier: :any` on resolve maps to the *single* cached tier. It resolves
|
|
377
|
+
when exactly one tier is cached and raises otherwise: with no tier
|
|
378
|
+
cached it raises `ResourceNotSetupError`, and with several it raises
|
|
379
|
+
`Kotoshu::ResourceResolutionError` listing them. That listing uses the
|
|
380
|
+
fixed order `mini`, `fluency`, `full` — a reporting order for
|
|
381
|
+
deterministic error messages, not a fallback chain; tiers are never
|
|
382
|
+
silently substituted for one another.
|
|
383
|
+
|
|
384
|
+
The default tier is `fluency` (~15 MB, near-lossless; owner decision
|
|
385
|
+
2026-09-04) — caches written under the old `full` default keep working
|
|
386
|
+
via the tier-less legacy bridge. Override it per call with `tier:`, or set
|
|
387
|
+
it globally via the `model_tier` configuration option (environment
|
|
388
|
+
variable `KOTOSHU_MODEL_TIER`; one of `full`, `fluency`, `mini`):
|
|
389
|
+
|
|
390
|
+
[source,bash]
|
|
391
|
+
----
|
|
392
|
+
KOTOSHU_MODEL_TIER=mini kotoshu setup en --model
|
|
393
|
+
----
|
|
394
|
+
|
|
279
395
|
[[interactive-mode]]
|
|
280
396
|
[[interactive-mode]]
|
|
281
397
|
== Interactive Mode
|
data/Rakefile
CHANGED
|
@@ -10,3 +10,106 @@ require "rubocop/rake_task"
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
12
|
task default: %i[spec rubocop]
|
|
13
|
+
|
|
14
|
+
# Conformance failure reporters (kept as lambdas so nothing leaks into
|
|
15
|
+
# Object from the Rakefile).
|
|
16
|
+
report_failures = lambda do |failures, label = nil|
|
|
17
|
+
failures.first(10).each do |failure|
|
|
18
|
+
prefix = label ? "[#{label}] " : ""
|
|
19
|
+
puts "#{prefix}##{failure.index} #{failure.kind} #{failure.input.inspect} " \
|
|
20
|
+
"on #{failure.dictionary}: expected #{failure.expected.inspect}, got #{failure.actual.inspect}"
|
|
21
|
+
end
|
|
22
|
+
puts "... and #{failures.size - 10} more" if failures.size > 10
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
report_divergences = lambda do |divergences|
|
|
26
|
+
divergences.first(10).each do |divergence|
|
|
27
|
+
puts "[divergence] ##{divergence.index} #{divergence.kind} #{divergence.input.inspect} " \
|
|
28
|
+
"on #{divergence.dictionary}: ruby #{divergence.expected.inspect}, " \
|
|
29
|
+
"native #{divergence.actual.inspect}"
|
|
30
|
+
end
|
|
31
|
+
puts "... and #{divergences.size - 10} more" if divergences.size > 10
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
namespace :kotoshu do
|
|
35
|
+
namespace :conformance do
|
|
36
|
+
desc <<~DESC
|
|
37
|
+
Export conformance vectors for kotoshu-rs (plan 67 M3). Walks the Spylls \
|
|
38
|
+
fixtures (spec/integrational/fixtures) plus the spec fixture corpus, runs \
|
|
39
|
+
the real engine over every fixture word, and writes one JSON object per \
|
|
40
|
+
line. `expected` values are FROZEN engine behavior (byte-reproducible \
|
|
41
|
+
cross-implementation), not claims about linguistic correctness. \
|
|
42
|
+
Output path: ENV["CONFORMANCE_VECTORS_PATH"] or conformance/vectors.jsonl \
|
|
43
|
+
(repo root). The generated file IS committed -- conformance/ is \
|
|
44
|
+
deliberately NOT gitignored -- and kotoshu-rs copies it into \
|
|
45
|
+
tests/conformance/ via its documented sync step so its CI needs no gem.
|
|
46
|
+
DESC
|
|
47
|
+
task :export do
|
|
48
|
+
path = ENV.fetch("CONFORMANCE_VECTORS_PATH", nil) || "conformance/vectors.jsonl"
|
|
49
|
+
require "kotoshu"
|
|
50
|
+
result = Kotoshu::ConformanceExporter.export(path: path)
|
|
51
|
+
puts "Wrote #{result.row_count} vectors from #{result.corpus_count} corpora to #{result.path}"
|
|
52
|
+
unless result.skipped_corpora.empty?
|
|
53
|
+
puts "Skipped corpora:"
|
|
54
|
+
result.skipped_corpora.each { |id, reason| puts " #{id}: #{reason}" }
|
|
55
|
+
end
|
|
56
|
+
result.skipped_words.each { |(id, word), reason| puts " Skipped word #{id} #{word.inspect}: #{reason}" }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
desc <<~DESC
|
|
60
|
+
Replay conformance/vectors.jsonl through the pure-Ruby engine \
|
|
61
|
+
(plan 66 dual-backend suite). Every vector must reproduce its frozen \
|
|
62
|
+
expectation; exits nonzero on any mismatch.
|
|
63
|
+
DESC
|
|
64
|
+
task :ruby do
|
|
65
|
+
require "kotoshu"
|
|
66
|
+
result = Kotoshu::ConformanceRunner.new.run(backend: :ruby)
|
|
67
|
+
puts "ruby backend: #{result.row_count} vectors, #{result.failures.size} failures"
|
|
68
|
+
report_failures.call(result.failures)
|
|
69
|
+
abort "kotoshu:conformance:ruby: #{result.failures.size} of #{result.row_count} vectors diverged" unless result.ok?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc <<~DESC
|
|
73
|
+
Replay conformance/vectors.jsonl through the native (Rust) engine \
|
|
74
|
+
(plan 66 dual-backend suite). Requires the extension to be built \
|
|
75
|
+
(rake compile); exits nonzero on any mismatch.
|
|
76
|
+
DESC
|
|
77
|
+
task :native do
|
|
78
|
+
require "kotoshu"
|
|
79
|
+
unless Kotoshu::Native.available?
|
|
80
|
+
abort "kotoshu:conformance:native requires the native extension -- run `rake compile` first"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
result = Kotoshu::ConformanceRunner.new.run(backend: :native)
|
|
84
|
+
puts "native backend: #{result.row_count} vectors, #{result.failures.size} failures"
|
|
85
|
+
report_failures.call(result.failures)
|
|
86
|
+
abort "kotoshu:conformance:native: #{result.failures.size} of #{result.row_count} vectors diverged" unless result.ok?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
desc <<~DESC
|
|
90
|
+
Run the conformance vectors through BOTH backends and diff every row \
|
|
91
|
+
(plan 66 acceptance: zero diffs). Also fails when either backend \
|
|
92
|
+
diverges from the frozen expectations. Requires the extension \
|
|
93
|
+
(rake compile).
|
|
94
|
+
DESC
|
|
95
|
+
task :compare do
|
|
96
|
+
require "kotoshu"
|
|
97
|
+
unless Kotoshu::Native.available?
|
|
98
|
+
abort "kotoshu:conformance:compare requires the native extension -- run `rake compile` first"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
result = Kotoshu::ConformanceRunner.new.compare
|
|
102
|
+
puts "compare: #{result.ruby.row_count} vectors -- ruby failures: " \
|
|
103
|
+
"#{result.ruby.failures.size}, native failures: #{result.native.failures.size}, " \
|
|
104
|
+
"backend divergences: #{result.divergences.size}"
|
|
105
|
+
report_failures.call(result.ruby.failures, "ruby")
|
|
106
|
+
report_failures.call(result.native.failures, "native")
|
|
107
|
+
report_divergences.call(result.divergences)
|
|
108
|
+
unless result.ok?
|
|
109
|
+
abort "kotoshu:conformance:compare: backends diverged " \
|
|
110
|
+
"(ruby #{result.ruby.failures.size}, native #{result.native.failures.size}, " \
|
|
111
|
+
"cross #{result.divergences.size} of #{result.ruby.row_count})"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|