Fingerpoint Detector
SDK

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: source is a list of data URLs or one local folder, cache is the cache file, and ttl sets how often the cache is checked for updates; see Reference data. built_at is the build time of the reference bank; use it to tell whether the data has changed.
  • Scoring: analyze takes one to three { text, expected_count } replies. expected_count is the number of integers the challenge asked for and must be a positive integer.
  • Generating challenges: count is 1–3. Each challenge has an id, a prompt, and an expected_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 detect would 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).

SettingDescriptionDefault
base_urlBase URL or full endpoint. A bare host gets /v1; a URL with a path keeps the path and adds the selected API's endpointRequired
api_keyAPI key. It appears only in request headers, and error messages show [REDACTED] in its placeRequired
modelRequested model nameRequired
apiresponses, chat_completions, or messages. Rust uses the Api enum and Go the APIResponses constantsresponses
effortReasoning effort, such as low or high. With Messages, none turns thinking offEmpty, not sent
service_tierflex, default, fast, or ultrafast. Sent as speed with Messages and as service_tier with the other APIsdefault, not sent
streamUse SSE streaming; turn it off for plain JSON responses. Go turns it off with the NoStream fieldOn
strictStrict mode: no truncation, and all three replies must be complete and valid. Needs count 3Off
countChallenges per round, 1–33
parallelConcurrent requests, 1–3, at most count3
timeoutTime 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 forever120

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:

  1. queued: reported once for every sample, in index order, before any request.
  2. waiting: the request has been sent.
  3. streaming: the first SSE body data arrived and first_byte_ms is set. Each later piece of text is reported again with a growing count. JSON responses skip this step and stay waiting until the whole response is read.
  4. A final state: complete, truncated, failed, or cancelled. 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_count integers, and the sample state is truncated. A JSON reply is cut down to expected_count integers.
  • A reply must end normally, with a finish reason of stop, end_turn, or completed. 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:

FieldDescription
decisionscored, partial, unscorable, or not_model; see Reading results
prediction, prediction_nameID and display name of the top candidate. The ID is empty without a ranking
family_prediction, family_prediction_nameModel family of the top candidate
resultsAll candidates from the highest ranking score down: model, display_name, family, family_name, score, probability
ranking_scoreRanking score of the top candidate, or null without a ranking
probabilityConfidence of the top candidate, a closed-set probability within the reference bank. Set only for scored with a valid calibration
probability_statusreference_calibrated or unavailable
probability_scope, calibrationScope of the confidence and the calibration used, or null without a confidence
used_outputsNumber of valid replies
diagnosticsParsing of each reply: parsed_numbers, minimum_numbers, accepted, and raw_out_of_range_count, the count of numbers outside 1–355
sequenceMeasures of the language-model reply check: autocorrelation, small_step_share, model_like. null without valid replies
method, model_versionScoring method and detector version
evidenceA label for the result and an English explanation in reason

Sample

Each sample in a detect result, and the return value of request_sample:

FieldDescription
statecomplete, truncated, or failed; cancelled after cancellation in TypeScript and Go. Progress callbacks also see queued, waiting, and streaming
textText used for scoring. A truncated sample keeps only the first expected_count integers
raw_textRaw text received
count, expected_countNumber of integers parsed and number requested
first_byte_msMilliseconds 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_idModel name and response ID returned by the API, or null
errorWhy 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:

errorCause
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.reasonanalysis 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

LanguageHow errors are reported
TypeScriptThrows Error, except buildRequest, which returns { error }
RustReturns Result<T, lmfpd::Error>
GoReturns an error
PythonRaises 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

On this page