API
Functions in the four languages side by side, detection settings, sampling rules, result fields, and error handling.
The functions match one to one across the four languages, and so do the result fields. TypeScript, Rust, and Python use the same snake_case field names as the JSON. Go struct fields follow Go naming and marshal to the same JSON as the other languages.
Functions
loadDetector(options?: { source?: string | readonly string[]; cache?: string; ttl?: number }): Promise<Detector>
detector.builtAt: string
detector.analyze(outputs: Output[], options?: { strict?: boolean }): Analysis
detector.detect(config: DetectConfig, options?: { signal?: AbortSignal; onSample?: (index: number, sample: Sample) => void }): Promise<DetectResult>
generateChallenges(count = 3): Challenge[]
parseNumbers(text: string): number[]
buildRequest(config: DetectConfig, prompt: string): { url, headers, body } | { error: string }
requestSample(config: DetectConfig, challenge: Challenge): Promise<Sample>
DEFAULT_DATA_URLS: readonly string[]- Loading:
sourceis a list of data URLs or one local folder,cacheis the cache file, andttlsets how often the cache is checked for updates; see Reference data.built_atis the build time of the reference bank; use it to tell whether the data has changed. - Scoring:
analyzetakes one to three{ text, expected_count }replies.expected_countis the number of integers the challenge asked for and must be a positive integer. - Generating challenges:
countis 1–3. Each challenge has anid, aprompt, and anexpected_count; the counts in one round all differ. - Parsing numbers: returns the longest run of integers from 1 to 355 in a reply. Letters split runs; punctuation and whitespace do not.
- Building a request: returns the URL, headers, and JSON body that
detectwould send, without sending anything. Use it to check settings. - Sending one challenge: sends one request and checks the reply without scoring it. A failure is recorded in the returned sample's
error.
Detection settings
TypeScript uses camelCase (baseUrl), Go uses struct fields (BaseURL), and Rust and Python use snake_case (base_url).
| Setting | Description | Default |
|---|---|---|
base_url | Base URL or full endpoint. A bare host gets /v1; a URL with a path keeps the path and adds the selected API's endpoint | Required |
api_key | API key. It appears only in request headers, and error messages show [REDACTED] in its place | Required |
model | Requested model name | Required |
api | responses, chat_completions, or messages. Rust uses the Api enum and Go the APIResponses constants | responses |
effort | Reasoning effort, such as low or high. With Messages, none turns thinking off | Empty, not sent |
service_tier | flex, default, fast, or ultrafast. Sent as speed with Messages and as service_tier with the other APIs | default, not sent |
stream | Use SSE streaming; turn it off for plain JSON responses. Go turns it off with the NoStream field | On |
strict | Strict mode: no truncation, and all three replies must be complete and valid. Needs count 3 | Off |
count | Challenges per round, 1–3 | 3 |
parallel | Concurrent requests, 1–3, at most count | 3 |
timeout | Time limit in seconds (a time.Duration in Go). Set to 0 (a negative duration in Go) to disable. With SSE it limits only the wait for the first response body data; there is no limit once data flows. With JSON it covers the whole response. Rust and Python cannot cancel a request, so with the timeout off, a server that accepts the connection but never sends data blocks detect forever | 120 |
In Go a zero field means the default; for example, Count: 0 means three challenges. In Rust, DetectConfig::new(base_url, api_key, model) returns the defaults, and you change fields from there. Python's detect takes keyword arguments; build_request and request_sample take a dict with the same keys.
An invalid configuration makes detect and build_request fail before any request with one of these messages:
base_url, api_key, and model are required.
api must be responses, chat_completions, or messages.
service_tier must be flex, default, fast, or ultrafast.
count must be an integer from 1 to 3.
parallel must be an integer from 1 to 3.
timeout must be 0 (no timeout) or between 0.001 and 2147483.647 seconds.
strict requires count 3.
Base URL must be a valid HTTP or HTTPS URL.
Base URL must use HTTP or HTTPS without credentials, a query, or a fragment.
The endpoint in --baseurl does not match --api.Progress
detect can take a callback that runs each time a sample changes. It receives the sample's index in challenges and samples, and the sample at that moment.
const result = await detector.detect(config, {
onSample: (index, sample) => console.log(index, sample.state, `${sample.count}/${sample.expected_count}`),
})Each sample goes through these states:
queued: reported once for every sample, in index order, before any request.waiting: the request has been sent.streaming: the first SSE body data arrived andfirst_byte_msis set. Each later piece of text is reported again with a growingcount. JSON responses skip this step and staywaitinguntil the whole response is read.- A final state:
complete,truncated,failed, orcancelled. It is reported once and equals the sample in the result.
The same state can be reported several times in a row. A streamed reply calls back for every piece of text, so throttle redraws in your UI if needed.
Calls never overlap, so the callback needs no lock. In Rust, Go, and Python it runs on SDK worker threads, and a slow callback slows sampling. A failing callback never fails a sample: in TypeScript a thrown error is reported as an uncaught exception and detection continues; a panic in Rust or Go and an exception in Python propagate out of detect.
Sampling rules
detect samples like command-line detection:
- In relaxed mode a streamed reply stops being read once it has
expected_countintegers, and the sample state istruncated. A JSON reply is cut down toexpected_countintegers. - A reply must end normally, with a finish reason of
stop,end_turn, orcompleted. Refusals, content filtering, and unfinished replies fail unless relaxed mode has already truncated them. - A valid reply has at least
max(80, ceil(expected_count × 0.55))integers. - Redirects are not followed and requests are not retried. Failed samples stay in the result.
Results
Analysis
Returned by analyze, and as analysis in a detect result:
| Field | Description |
|---|---|
decision | scored, partial, unscorable, or not_model; see Reading results |
prediction, prediction_name | ID and display name of the top candidate. The ID is empty without a ranking |
family_prediction, family_prediction_name | Model family of the top candidate |
results | All candidates from the highest ranking score down: model, display_name, family, family_name, score, probability |
ranking_score | Ranking score of the top candidate, or null without a ranking |
probability | Confidence of the top candidate, a closed-set probability within the reference bank. Set only for scored with a valid calibration |
probability_status | reference_calibrated or unavailable |
probability_scope, calibration | Scope of the confidence and the calibration used, or null without a confidence |
used_outputs | Number of valid replies |
diagnostics | Parsing of each reply: parsed_numbers, minimum_numbers, accepted, and raw_out_of_range_count, the count of numbers outside 1–355 |
sequence | Measures of the language-model reply check: autocorrelation, small_step_share, model_like. null without valid replies |
method, model_version | Scoring method and detector version |
evidence | A label for the result and an English explanation in reason |
Sample
Each sample in a detect result, and the return value of request_sample:
| Field | Description |
|---|---|
state | complete, truncated, or failed; cancelled after cancellation in TypeScript and Go. Progress callbacks also see queued, waiting, and streaming |
text | Text used for scoring. A truncated sample keeps only the first expected_count integers |
raw_text | Raw text received |
count, expected_count | Number of integers parsed and number requested |
first_byte_ms | Milliseconds from sending the request to the first SSE body data, or to reading and parsing the whole JSON response. null until then |
response_model, response_id | Model name and response ID returned by the API, or null |
error | Why the sample failed, or null |
DetectResult
detect returns four fields: challenges (this round's challenges), samples, analysis, and error. When error is null, analysis always has a ranking. error is set in these cases:
error | Cause |
|---|---|
No valid samples. This round cannot be scored. | Every sample failed |
Strict mode requires 3/3 successful samples; received N/3. | A sample failed in strict mode |
The text of analysis.evidence.reason | analysis is present but has no ranking, for example not_model |
Cancelled. This round was not scored. | Detection was cancelled (TypeScript and Go) |
Scoring failed. Check that the reference bank and detector files are valid. | A data file is damaged |
Errors
| Language | How errors are reported |
|---|---|
| TypeScript | Throws Error, except buildRequest, which returns { error } |
| Rust | Returns Result<T, lmfpd::Error> |
| Go | Returns an error |
| Python | Raises lmfpd.LmfpdError |
analyze fails when its input is not one to three replies with a positive integer expected_count: Each input round must contain one to three outputs with text and a positive integer expected_count. request_sample never fails; configuration errors and request failures go into the sample's error.
Cancellation
TypeScript's detect cancels through options.signal, and Go's Detect and RequestSample follow ctx. Cancelling stops requests in flight at once and sends no further challenges. Samples become cancelled, and the round is not scored. Rust's and Python's detect cannot be cancelled midway; only timeout ends a stuck request. With timeout set to 0 in these two languages, a request that connects but never receives data blocks detect and request_sample forever.
Last updated on