SDK
Score replies, generate challenges, or check an API channel from TypeScript, Rust, Go, and Python programs.
The SDKs put the detector inside your own programs. You can score replies you have already collected, generate random challenges, or send one round of challenges to an API channel and score the replies. All four languages offer the same features:
- Offline scoring of one to three replies, with the same result as
fpd --input FILE --json. - Challenge generation with the generator the website and command line use. Each challenge picks its language and wording at random and asks for 292–332 integers.
- Live detection of one round, with the same sampling rules as command-line detection, over Responses, Chat Completions, or Messages.
The SDKs do not run the tokenizer probe and do not run several rounds; for more rounds, call them in a loop.
Reference data
API
Install
The TypeScript SDK is on npm, the Rust SDK on crates.io, and the Python SDK on PyPI. The Go SDK is a module in the repository:
npm install lmfpd-sdkThe TypeScript SDK runs on Node.js 22+ and Bun 1.4.4+ and has no dependencies once built. Every Rust call is blocking. The Go SDK needs Go 1.23+. The Python SDK needs Python 3.10+ and depends on httpx, zstandard, and numpy.
No package contains reference data. On the first load the SDK downloads about 3.6 MB of compressed data and caches it. Later updates to the reference bank need no SDK upgrade; see Reference data.
Score replies
analyze takes one to three replies. Each has the reply text and expected_count, the number of integers its challenge asked for:
import { loadDetector } from 'lmfpd-sdk'
const detector = await loadDetector()
const analysis = detector.analyze([
{ text: reply1, expected_count: 300 },
{ text: reply2, expected_count: 311 },
{ text: reply3, expected_count: 296 },
])
console.log(analysis.decision, analysis.prediction, analysis.probability)Load once and score as often as you need. Loading parses about 4.5 MB of detector data and takes much longer than one score (see Performance), so do not reload it for every score.
Check an API channel
detect generates challenges, sends them in parallel, checks the replies, and scores them. A failed request does not raise an error; it is recorded in that sample's error and in the result's error:
const result = await detector.detect({
baseUrl: 'https://api.example.com/v1', apiKey: process.env.API_KEY!, model: 'gpt-5.6-sol', api: 'chat_completions',
})
console.log(result.error ?? result.analysis!.prediction)detect raises an error only for an invalid configuration, before it sends any request. When error is empty, analysis always has a ranking. See API for every setting.
Reading results
decision tells you what the result contains:
decision | When | Result |
|---|---|---|
scored | All three replies are valid | A full ranking. When the calibration matches the detector, probability is the confidence of the top candidate |
partial | Only one or two replies are valid, in relaxed mode | A ranking only; probability is null |
unscorable | No reply is valid, or fewer than three in strict mode | No ranking |
not_model | The numbers do not change the way a language model's do | No ranking; see the language-model reply check |
A reply is valid when it contains at least max(80, ceil(expected_count × 0.55)) integers from 1 to 355. Relaxed mode cuts a reply down to expected_count integers; strict mode never cuts and needs three complete replies.
A ranking is not proof of identity
The result is a closed-set ranking within the reference bank. A model outside the bank still gets a closest candidate, and probability compares only the models in the bank. To judge whether a channel is trustworthy, fix the request settings, repeat several rounds, and combine the result with other evidence.
Performance
Measured on an Apple Silicon Mac. Scoring times are the median of 20 runs after a warm-up:
| Language | Load the repository's data/ | Score three replies | Score one reply |
|---|---|---|---|
| Rust | 24 ms | 1.7 ms | 0.5 ms |
| Go | 31 ms | 2.3 ms | 0.7 ms |
| TypeScript (Node or Bun) | 30 ms | 6.5 ms | 2.2 ms |
| Python 3.13 | 37 ms | 1.9 ms | 0.65 ms |
The Python SDK computes with NumPy, which sums in a different order from the other languages; scores differ by about 10⁻¹⁵. Live detection time is dominated by the model's generation and does not depend on the language.
Consistent results
All four implementations are checked against the same test data: number parsing, request construction, 13 kinds of streaming and JSON responses, and 12 scoring cases. Scores must agree within a relative error of 10⁻⁹, and text and states must match exactly. In the repository, bun run sdk:check checks the four languages in turn.
Last updated on