Skip to main content

Crate cratefield_adapter_workers_ai

Crate cratefield_adapter_workers_ai 

Source
Expand description

cratefield-adapter-workers-ai: the Classifier port over the Cloudflare Workers AI binding (issue #456). The model is reached through env.AI.run(...) — a platform binding, no API key at all — so this adapter exists only on the Workers runtime, and it bills through the venture’s Cloudflare account instead of to a vendor key.

It is a convenience for ventures already on Workers, explicitly NOT the primary way in: bring-your-own-key via cratefield-adapter-typesafe is the default Classifier. But it is the same purpose-trained model, so profile() reports Calibration::Classifier too — the same family of numbers, and thresholds tuned on either hold on both (unlike the adapter-classifier-llm family; see CLASSIFIER.md).

Two shapes are load-bearing here. First, the JS binding: worker::Ai is a wasm-bindgen handle held inside a Send + Sync trait object, which is sound without a line of unsafe here because wasm-bindgen itself declares JsValue Send + Sync off the atomics target (the same shape runtime-cloudflare’s D1 wrapper uses); only the returned future needs worker’s into_send. Second, the binding cannot be constructed off wasm, while ask must still be testable natively — so the call goes through the narrow AiRunner seam and tests inject a fake runner.

There is no key in this adapter, so there is nothing to leak. A Workers AI binding error can still echo platform identifiers (account ids, binding names), so error text only ever travels inside ClassifierError — whose Display scrubs — and this crate’s log lines carry outcome labels, never raw binding text. The same binding also carries the Speech port (issue #861) over its own SpeechRunner seam — see the speech module.

§cratefield-adapter-workers-ai

Classifier port over the Cloudflare Workers AI binding for the Cratefield harness (issue #456). The model is reached through env.AI.run(...) — a platform binding, no API key at all — so this adapter only exists on the Workers runtime, and it bills through the venture’s Cloudflare account rather than to a vendor key.

This is a convenience for ventures already on Workers, explicitly not the primary way in: bring-your-own-key via cratefield-adapter-typesafe is the default Classifier, because it runs on every runtime through the HttpClient port. This adapter’s profile() still reports Calibration::Classifier — it is the same purpose-trained model (typesafe/jev) behind a different transport, so its numbers are the same family as the direct adapter’s, and a threshold tuned on either holds on both (unlike the adapter-classifier-llm family; see CLASSIFIER.md).

§Usage

ⓘ
use cratefield_adapter_workers_ai::WorkersAi;

// `AI` is the conventional binding name; a missing or wrong-typed binding
// yields `None`, meaning the runtime simply does not provide the port.
// Never a panic.
if let Some(classifier) = WorkersAi::from_env(&env, "AI") {
    runtime = runtime.classifier(classifier);
}

The model id defaults to typesafe/jev; override it (and the state ceiling) with WorkersAi::from_env_with_model or WorkersAi::with_runner.

§The context window

Workers AI documents a 32,000-token context window for this model. At the port’s conservative ~4 chars/token that is about 128,000 chars, but the questions share the window too, so the default state ceiling is core’s DEFAULT_MAX_STATE_CHARS (96,000 chars, roughly 24,000 tokens), leaving roughly a quarter of the window for the question set. The ceiling is overridable per constructor through a ClassifierProfile.

§The wire format

The request and response documents are this crate’s assumption, not something this repository’s vendors document: the same model as the direct adapter-typesafe adapter, so the same documents travel, one level down in the binding instead of in an HTTP body.

  • Input: {"state": "...", "questions": [{"id", "kind", "instructions", "criteria" (choice), "levels" (score)}]} — all questions of the call in one run().
  • Output: {"answers": [{"id", "value", "probabilities"}]} where value is a criterion label, a number on the scale, or true/false.

§What is refused, what is retryable

The adapter never invents an answer: a missing question id, a label the question never offered, or a distribution that is not normalisable is ClassifierError::Rejected. Probabilities that do not sum to one are normalised (the model’s softmax is not obliged to land exactly on one); non-finite or non-positive totals are refused.

There is no key here, so there is nothing to leak — but a Workers AI binding error can echo platform identifiers (account ids, binding names), so error text only ever travels inside ClassifierError (whose Display scrubs) and this crate’s log lines carry outcome labels, never raw binding text.

Structs§

AiBinding
The real runner: the env.AI binding, obtained from worker::Env::ai(..).
SpeechBinding
The real runner: the env.AI binding. No unsafe is needed to hold the JS handle in a Send + Sync struct — wasm-bindgen declares JsValue Send + Sync; only the call futures are !Send, which worker’s send module fixes at the call site. Off wasm there is no Env — hence the seam.
WorkersAi
Classifier over the Cloudflare Workers AI binding.
WorkersAiSpeech
Speech over the Cloudflare Workers AI binding. The runner is a type parameter so tests can substitute the binding; venture code writes WorkersAiSpeech::from_env(..) and never names the parameter.

Enums§

SpeechModel
The text-to-speech model a WorkersAiSpeech voices with: Deepgram’s Aura, English-only on both generations.
TranscriptionModel
The speech-to-text model a WorkersAiSpeech transcribes with. Nova3 is Deepgram’s multilingual Nova-3 (words with confidence, no clip length); the Whisper models are OpenAI’s (Whisper without confidence, WhisperLargeV3Turbo with language and duration).

Constants§

DEFAULT_MODEL_ID
The model this adapter runs by default: the same purpose-trained classifier the direct adapter-typesafe adapter calls over HTTP, one transport further away. Override it per constructor.
WORKERS_AI_CONTEXT_TOKENS
The context window Workers AI documents for the model, in tokens. At the port’s conservative ~4 chars/token the default state ceiling (DEFAULT_MAX_STATE_CHARS, 96,000 chars) spends roughly 24,000 tokens and leaves roughly a quarter of the window for the question set.
WORKERS_AI_MAX_AUDIO_BYTES
The most audio one transcription may carry when no WorkersAiSpeech::with_limits override says otherwise: 1 MiB, well under the port’s 25 MiB default. The binding takes the clip as a JSON number array — roughly 32 serde_json::Value bytes per audio byte before serde_wasm_bindgen copies it into a JavaScript array again — so a 25 MiB clip would ask a 128 MB Workers isolate for ~800 MB twice over and be killed mid-request instead of refused with a typed SpeechError::LimitExceeded. The other limits stay at the port defaults.

Traits§

AiRunner
The runner seam: one call to the binding, all questions at once.
SpeechRunner
The speech seam, the sibling of AiRunner: one call to the binding, a JSON document in and a JSON document or an audio byte stream out. The error type is SpeechError itself, so a fake can exercise every mapping.