kotoshu-server 0.1.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fa24971e66e791bd277c8fe878890693c6a624a3de1c488dbb7f5b38f629f0a
4
- data.tar.gz: eabc7a8dd8124cf20a1c05b32257e9fdfdcdebeb72ac6aceae249c2aba692976
3
+ metadata.gz: 4a1120bdda9dcd7ecc468c95d5386127efefa6509e7aa1a03f2fa12a5e28c33f
4
+ data.tar.gz: b8cbe5d0db18715009576fd104be80bb06e35ba1b730cbd2564178755a05e6a1
5
5
  SHA512:
6
- metadata.gz: fac8b861344a723119bf84e7c078c772d491d974cb37a9338b2100acb0e28cf9e17d83e06b4f18aad41ecba9d2ec78bc6249575830b485f03f6f1200ab58daa0
7
- data.tar.gz: bd7c11018743035167858f90f8a7b70aa29efdfb6313496b3f6d06a1e297931985e6259625d3b43b34a5236d03179f58851a33f388f9f5a6e2cbcb0701ab745b
6
+ metadata.gz: 6c001de9ec24f78e48eac1c400b0ecf9038b47fdeed7115ad3ed0635e100adc7b3ae9e241a7474c8956636f1690f3dea39b425c125d122577aa67dfd097af2a4
7
+ data.tar.gz: 8696ca46e3728fbd2b2f32b3e9468be4e544290d83badb6837b2133bb76700fd7241c687446b7926e9d0a485eb95fbae22d44183503da64cb84cada4227080d7
data/README.md CHANGED
@@ -12,9 +12,12 @@ See `TODO.impl/64-http-api-and-sdks.md` for the full plan.
12
12
 
13
13
  ## Install
14
14
 
15
- **Run from source.** The published `kotoshu-server 0.1.0` gem is
16
- empty (a gemspec file-list bug, fixed on main). Use source until the
17
- owner republishes 0.1.1:
15
+ ```bash
16
+ gem install kotoshu-server # >= 0.1.1 (the 0.1.0 gem was empty — a gemspec file-list bug)
17
+ kotoshu-server
18
+ ```
19
+
20
+ Or run from source:
18
21
 
19
22
  ```bash
20
23
  cd kotoshu-server && bundle install && bundle exec exe/kotoshu-server
@@ -71,6 +74,24 @@ the default fluency tier (the full tier is far larger), plus
71
74
  one-time model load on the first model-enabled request. Listing the
72
75
  language in `KOTOSHU_SERVER_MODEL_LANGS` warms it at boot instead.
73
76
 
77
+ ## Language detection
78
+
79
+ `POST /v1/detect` reports which engine answered in the `engine`
80
+ field:
81
+
82
+ - `"lid-176"` — the 176-language lid.176 model served through the
83
+ kotoshu native extension (kotoshu >= 0.10.0). The model is set up
84
+ lazily on the first detect — one download, never at boot, and
85
+ never at all under `KOTOSHU_OFFLINE=1` without a cache (Docker
86
+ defaults to offline).
87
+ - `"heuristic"` — the 7-language character-set heuristic (en, de,
88
+ es, fr, pt, ru, ja), the fallback whenever lid-176 cannot serve:
89
+ kotoshu < 0.10.0, a pure-Ruby install or `KOTOSHU_BACKEND=ruby`,
90
+ or the model missing after a failed setup.
91
+
92
+ Set `KOTOSHU_DETECT=heuristic` to pin the heuristic regardless of
93
+ the installed gem — the 0.1.1 behavior, byte for byte.
94
+
74
95
  ## Endpoints
75
96
 
76
97
  | Method | Path | Body | Returns |
@@ -81,7 +102,7 @@ language in `KOTOSHU_SERVER_MODEL_LANGS` warms it at boot instead.
81
102
  | `GET` | `/v1/languages` | — | `{ cached, supported, model }` |
82
103
  | `POST` | `/v1/check` | `{ text, language?, format?, model? }` | `{ file, word_count, errors: [...] }` |
83
104
  | `POST` | `/v1/suggest` | `{ word, language?, max? }` | `{ word, suggestions: [...] }` |
84
- | `POST` | `/v1/detect` | `{ text }` | `{ language, confidence }` |
105
+ | `POST` | `/v1/detect` | `{ text }` | `{ language, confidence, engine }` |
85
106
 
86
107
  Each `error` and `suggestion` mirrors the lutaml-model serialization
87
108
  (`word`, `distance`, `confidence`, `source`).
@@ -129,6 +150,7 @@ Healthcheck probes `/v1/health` every 30s.
129
150
  | `KOTOSHU_SERVER_LAZY` | `0` | Skip pre-warm; load on first request |
130
151
  | `KOTOSHU_SERVER_DEFAULT_LANG` | `en` | When client omits `language` |
131
152
  | `KOTOSHU_SERVER_LOG_LEVEL` | `info` | `debug`/`info`/`warn`/`error` |
153
+ | `KOTOSHU_DETECT` | `auto` | `heuristic` pins /v1/detect to the heuristic engine |
132
154
  | `KOTOSHU_OFFLINE` | `1` (in Docker) | Never trigger downloads |
133
155
 
134
156
  ## OpenAPI
@@ -12,7 +12,9 @@ module Kotoshu
12
12
  class ModelConfigError < StandardError; end
13
13
 
14
14
  class App < Sinatra::Base
15
- VERSION = "0.1.0".freeze
15
+ # The release version, from lib/kotoshu/server/version.rb —
16
+ # the only source of truth (the release workflow bumps it).
17
+ VERSION = Kotoshu::Server::VERSION
16
18
 
17
19
  # Semantic models (tiers, registry, confidence cascade) ship in
18
20
  # kotoshu 0.7.0. The gemspec keeps its `kotoshu ~> 0.6`
@@ -20,6 +22,14 @@ module Kotoshu
20
22
  # server validates the *installed* gem at boot instead.
21
23
  MODEL_MIN_KOTOSHU = Gem::Version.new("0.7.0")
22
24
 
25
+ # Native language identification (plan 106) ships in kotoshu
26
+ # 0.10.0: Kotoshu.detect_language returning a
27
+ # Language::Detection backed by the lid-176 model through the
28
+ # native extension. Same policy as MODEL_MIN_KOTOSHU — the
29
+ # gemspec floor stays `kotoshu ~> 0.6` (older gems keep the
30
+ # heuristic), so the installed gem is checked per request.
31
+ DETECT_MIN_KOTOSHU = Gem::Version.new("0.10.0")
32
+
23
33
  # Languages to set up at boot, from KOTOSHU_SERVER_LANGUAGES
24
34
  # (space separated). Single source of truth for the env var: the
25
35
  # boot pre-warm and /v1/health both read it here.
@@ -148,6 +158,99 @@ module Kotoshu
148
158
  end
149
159
  end
150
160
 
161
+ # ---- Language detection (/v1/detect) ----
162
+
163
+ @lid_setup_mutex = Mutex.new
164
+ @lid_setup_attempted = false
165
+
166
+ # Whether the installed kotoshu gem carries the lid-176
167
+ # detection surface (0.10.0+): Kotoshu.detect_language returning
168
+ # a Language::Detection, plus setup_lid / LidDetector.available?.
169
+ #
170
+ # @return [Boolean]
171
+ def self.lid_supported?
172
+ Gem::Version.new(Kotoshu::VERSION) >= DETECT_MIN_KOTOSHU
173
+ end
174
+
175
+ # Whether KOTOSHU_DETECT=heuristic pins /v1/detect to the
176
+ # 7-language heuristic, bypassing the gem engine selection (the
177
+ # 0.1.1 behavior, e.g. to reproduce earlier results).
178
+ #
179
+ # @return [Boolean]
180
+ def self.heuristic_detect?
181
+ ENV.fetch("KOTOSHU_DETECT", nil) == "heuristic"
182
+ end
183
+
184
+ # Detect for /v1/detect: the language code, its score, and the
185
+ # engine that served — "lid-176" or "heuristic".
186
+ #
187
+ # Engine selection, in order:
188
+ # 1. KOTOSHU_DETECT=heuristic — the heuristic, pinned.
189
+ # 2. kotoshu < 0.10.0 — the heuristic, as in 0.1.1; the server
190
+ # keeps working on old gems.
191
+ # 3. Otherwise the gem's own selection: lid-176 through the
192
+ # native extension when it is built, the backend is not
193
+ # explicitly ruby, and the artifact pair is cached; the
194
+ # heuristic otherwise (KOTOSHU_BACKEND=ruby, pure-Ruby
195
+ # install, missing model).
196
+ #
197
+ # The lid model is set up lazily on the first detect reaching
198
+ # case 3 — one download, off the boot path, honoring
199
+ # KOTOSHU_OFFLINE through the gem; a failed setup (offline with
200
+ # no cache, no registry entry, checksum mismatch) logs once and
201
+ # detection answers from whatever the gem can serve.
202
+ #
203
+ # @param text [String] the text to analyze
204
+ # @return [Array(String, Float, String)] code (nil when the
205
+ # heuristic is uncertain), score in [0, 1], engine name
206
+ def self.detect_language_with_engine(text)
207
+ return heuristic_detection(text) if heuristic_detect? || !lid_supported?
208
+
209
+ ensure_lid_setup!
210
+ detection = Kotoshu.detect_language(text)
211
+ [detection.code, detection.score, lid_engine]
212
+ end
213
+
214
+ # The heuristic detection (Language::Detector, the same engine
215
+ # /v1/detect served in 0.1.1, unchanged across gem versions).
216
+ #
217
+ # @param text [String]
218
+ # @return [Array(String, Float, String)]
219
+ def self.heuristic_detection(text)
220
+ code, confidence = Kotoshu::Language::Detector.detect_with_confidence(text)
221
+ [code, confidence, "heuristic"]
222
+ end
223
+
224
+ # Which engine the gem serves detect from: lid-176 when the
225
+ # native model is loadable, the heuristic otherwise. Asked
226
+ # after a detect, so it always names the engine behind the
227
+ # returned code.
228
+ #
229
+ # @return [String]
230
+ def self.lid_engine
231
+ Kotoshu::Language::LidDetector.available? ? "lid-176" : "heuristic"
232
+ end
233
+
234
+ # Run Kotoshu.setup_lid once per process, on the first detect.
235
+ # Idempotent in the gem; the once-guard keeps concurrent
236
+ # requests from racing the download. Any failure is logged and
237
+ # swallowed — detect then falls back inside the gem to whatever
238
+ # is cached (typically the heuristic).
239
+ #
240
+ # @return [void]
241
+ def self.ensure_lid_setup!
242
+ @lid_setup_mutex.synchronize do
243
+ return if @lid_setup_attempted
244
+
245
+ @lid_setup_attempted = true
246
+ Kotoshu.setup_lid
247
+ end
248
+ rescue StandardError => e
249
+ Logger.new($stderr).warn(
250
+ "lid model setup failed, falling back to the heuristic: #{e.class}: #{e.message}"
251
+ )
252
+ end
253
+
151
254
  # ---- Semantic analyzers (memoized per language + model file) ----
152
255
 
153
256
  @semantic_analyzers = {}
@@ -265,9 +368,9 @@ module Kotoshu
265
368
  text = body["text"]
266
369
  halt_with_error(400, "missing 'text'") unless text.is_a?(String)
267
370
 
268
- lang, confidence = Kotoshu.detect_language_with_confidence(text)
371
+ language, confidence, engine = self.class.detect_language_with_engine(text)
269
372
  content_type :json
270
- { language: lang, confidence: confidence }.to_json
373
+ { language: language, confidence: confidence, engine: engine }.to_json
271
374
  end
272
375
 
273
376
  # ---- Error handling ----
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Kotoshu
4
4
  module Server
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
data/openapi.yaml CHANGED
@@ -236,6 +236,13 @@ components:
236
236
  properties:
237
237
  language: { type: string, nullable: true }
238
238
  confidence: { type: number, format: float }
239
+ engine:
240
+ type: string
241
+ enum: [lid-176, heuristic]
242
+ description: >
243
+ Which engine served the detection: the 176-language lid-176
244
+ model (kotoshu >= 0.10.0 with the native extension and the
245
+ model set up) or the 7-language heuristic.
239
246
  Error:
240
247
  type: object
241
248
  properties:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kotoshu-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-06 00:00:00.000000000 Z
11
+ date: 2026-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: kotoshu