Repository navigation
Add decision model bindings for Cloudflare Clef - #662
Conversation
5b50949 to
5d26884
Compare
|
LGTM! |
Preview:
|
5d26884 to
0782057
Compare
|
LGTM! |
0782057 to
4847fd4
Compare
|
LGTM! |
4847fd4 to
dbc6df6
Compare
|
LGTM! |
pi 1.0.2 adds Cloudflare's Clef and Clef Flash to the Workers AI classifier catalog, and accepts Clef's response: its answers come back directly, where Jev's come wrapped in a run record that the transport used to require. The classifier bindings in the next commit need both. The only other change on Workshop's request paths: Anthropic models whose pi compat enables mid-conversation tool changes now send the inline-tools-2026-09-15 beta in place of mid-conversation-tool-changes-2026-07-01. Workshop declares its tools only in the initial system message, so its requests are otherwise unchanged. pi-agent-core drops its harness, node and search modules, which Workshop doesn't import.
Clef and Clef Flash are Workers AI decision models: given a JSON state
and typed questions, they return a probability for each allowed
answer. They don't chat, so they can't run a chat, a spawned agent or
LanguageModelBinding.run().
A gadget now binds one through the existing AI Model connection and
calls ClassifierModelBinding.classify({state, questions}).
newAiModelGatekeeper() mints a ClassifierModelGatekeeper when the
model is a classifier, i.e. a SUGGESTED_MODELS entry marked
`classifier: true` (see isClassifierModel()), and a
LanguageModelGatekeeper otherwise. The creation spec, sharing rules and
analytics are unchanged. An aiModel blueprint binding records
`classifier: true` so installers pick among classifiers, and the
agent's views of a blueprint name the kind.
listModels() now returns chat models only, and the new
listClassifierModels() returns the classifiers, so chats, spawners and
the new-conversation default never offer one.
Routing follows getModel(): through the platform AI Gateway when one
is configured, otherwise straight to Workers AI with the config's own
credentials. The gateway supplies Workers AI's credential only for
chat requests, so over the WORKERS_AI binding the request goes through
the binding's own run(), which still routes through the gateway. Over
HTTPS, /run wants a Workers AI credential as `Authorization`, and a
request sending the gateway token there is refused even when the token
grants Workers AI. Until AI Gateway authenticates /run as it does
chat, classifiers fail on deployments without the binding transport,
and the docs have those deployments disable them. As with the LLM
binding, there is no quota, BYOK routing or cost accounting.
A classifier session runs the admin check LanguageModelGatekeeper
already ran, now shared as refuseRevokedModel(), so disabling Clef on
the admin Models tab stops the gadgets bound to it.
See plans/classifier-models.md.
The AI Model connection lists chat and classifier models under separate headings, starting on the first chat model. A blueprint's classifier binding picks from, and auto-suggests against, classifier models, and says how to add one when there are none. Spawner pickers keep the chat list. The Providers page lists classifiers beside chat models, without the quick-model action: the quick model writes chat and gadget titles, which a classifier can't. Onboarding's add-model dialog leaves classifiers out, since its model step picks the default chat model. On the admin Models tab a classifier keeps its three modes and gains a Classifier badge, but has no Test or Settings: the test sends a chat request, and the reasoning level and compaction budget only shape chats.
getClassifier() returns from each route instead of reassigning a base URL and options in three branches, and builds the gateway metadata once. The user DO's listModels() now takes the kind, so listClassifierModels() stays only on AuthenticatedApi, which asks for the classifiers. ModelKind names the kind where the lists take it. The gateway cost test's filter for the offered chat models becomes one chatIds() helper.
The Providers page keeps the chat and classifier lists as fetched and combines them during render, rather than storing the combined list beside a set of the classifier IDs. The BlueprintLandingPage and GatekeeperModal tests share their mount and picker-opening steps instead of repeating them per test.
"Decision model" is the industry's term, and the one Cloudflare's
Clef docs use; "classifier" was pi's, which is an implementation
detail. The gadget-facing binding becomes DecisionModelBinding.decide(),
bound as DECISION_MODEL; pi's own Classifier* names stay where they
name pi's exports.
ModelKind ("language" | "decision") moves into the shared API, where
SuggestedModel.kind and BlueprintBinding.modelKind replace the
classifier flags, and modelKind() replaces isClassifierModel(). One
AuthenticatedApi.listModels(kind?) replaces listClassifierModels(),
so a future kind needs no new method.
DecisionRequest.state is any, as JSON.parse() returns, rather than a
JsonObject the d.ts had to define: it only has to be JSON-compatible,
and Workers AI also takes plain text there.
The pickers, the Providers page and the admin model rows follow the
kernel's rename: they ask listModels("decision"), test a blueprint
binding's modelKind, and label the groups Language and Decision
models.
dbc6df6 to
cdd6316
Compare
|
LGTM! |
Eval resultsVerdict: ⚪ Unchanged. No task moved beyond what 10 runs can tell apart from noise.
Failed checks
|
🔬 Eval runs reviewPerformanceAcross 10 runs per task, main → PR pass rates were change-calendar 90% → 70%, chess 80% → 100%, and incident-desk and worker-logs 100% → 90%; ⚪ VERDICT: NO REGRESSION FROM THIS PRThe candidate failures trace to generated gadget mistakes or an unchanged verifier, not the decision-binding additions or blueprint labels, and the pi upgrade has no demonstrated beyond-noise improvement or regression in these runs. TriageFailure modes
Tool errors
What to do
|
|
@kentonv actioned against all your feedback and ready for re-review. |
|
🎉 |
Lets gadgets bind Cloudflare's Clef and Clef Flash. These are decision models: JSON state + typed questions in, a probability per allowed answer out. No chat, tools or text output, so they can't back a chat, a spawned agent or
LanguageModelBinding.run(). Gadgets get a newDecisionModelBindinginstead:ModelKind("language" | "decision") is part of the shared API.SUGGESTED_MODELSentries carrykind, andmodelKind(provider, model)reads it.newAiModelGatekeeper()mints aDecisionModelGatekeeperwhen the model's kind is"decision". Creation spec, sharing and analytics unchanged.listModels(kind = "language")lists one kind, solistModels("decision")returns the decision models and a future kind needs no new method.aiModelbindings recordmodelKind: "decision", so installers pick among decision models.stateisany(JSON-compatible), asJSON.parse()returns; Workers AI also takes plain text there.getModel(): direct with the user's Cloudflare creds, or through the platform AI Gateway. On theWORKERS_AIbinding that'sbinding.run(model, input, {gateway}), since the gateway's passthrough refuses/run.Known gap: doesn't work on deployments that reach AI Gateway over HTTPS (gateway in another account). The gateway supplies Workers AI's credential only for chat, and reusing the gateway token for
/rungets a 401 even when it has Workers AI permissions. Checked live on three accounts. Docs say to disable Clef there; I raised this with the AI Gateway team and am waiting to hear back. Deployments on the default binding route are unaffected.Naming: "decision model", the industry term and the one Cloudflare's Clef docs use. pi calls these "classifiers" (
type: "classifier",classify()), so its ownClassifier*names remain only where the code calls pi.Prompt (against pi): "Please look at the last 10 commits in this repo and classify as either a new feature, a bugfix, docs changes, a refactor, or none of the above"

