Repository navigation
Conversation
A System One model (TypeSafe's Jev) evaluates a state against typed questions and returns one typed answer per question. It generates no text, has no messages, no tools and no streaming, so it does not fit sdk.Provider. Add it as a capability of its own, alongside embedding, speech, transcription, image and video. Questions are built with Noul, Choice and Score, and a single Criteria field mirrors the wire shape. Answers come back keyed by question ID, with typed accessors that reject a kind mismatch instead of returning a zero value. Confidence is a pointer because a Noul answer carries none, so a confidence gate cannot silently read it as a hard zero. Add sdk.JSONObject to solve the ordering problem this API exposes: encoding/json sorts map keys, but a System One model reads the object as content, so the order of Choice options and of a structured state is part of what it is shown. ChoiceOptions marshals in slice order, NoulCriteria is a struct so true stays before false, and JSONObject covers objects built at runtime, preserving order at every level on the way back in too. No provider yet; this is the SDK surface only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
Evaluate, a new SDK capability for System One models — models that evaluate a state against typed questions and return structured answers with calibrated probabilities, instead of generating text. The first one is TypeSafe's Jev (POST /v1/systemone).This is the SDK surface only. No provider yet — see Follow-ups.
Why a new capability instead of a
ProviderA System One model has no messages, no tools, no streaming and no text output. Routing it through
DoGeneratewould mean a provider that rejects almost everyGenerateParamsfield and returns a text result nobody wants. So it gets its own interface, exactly likeEmbeddingProvider,SpeechProviderandVideoProvider:Prior art agrees on the diagnosis.
maruel/genaiopens its TypeSafe notes with "TypeSafe is not a chat provider... it does not fit the rest of the provider interfaces directly" — but then maps it onto its chat entry point anyway, and documents the cost: its scoreboard has to be hand-authored because the generic smoke tests drive providers with text prompts and cannot exercise a provider that only answers questions. twilight has the same genericTest/TestModel/ListModelssurface and would pay the same price, so this PR keeps the capability separate.The interface is deliberately vendor-neutral (
EvaluationProvider, notJevProvider): a second System One vendor, or an LLM-backed implementation built on twilight's existing providers, can satisfy it without a change here.The JSON ordering problem
encoding/jsonmarshals Go maps with their keys sorted. For most APIs that is invisible. Here it is not: a System One model reads the object as content, so the order of Choice options — and of the fields of a structured state — is part of what it is shown. Amap[string]stringof options silently reorders the question on its way to the wire, which makes results harder to reproduce and is invisible at the call site.Both Go projects that already integrate this API use
map[string]...for choice criteria and lose the order. This PR fixes it in three places:criteriaChoiceOptionsis a slice with aMarshalJSONthat emits a JSON object in slice order. Duplicate option names are an error rather than a silently collapsed object.criteriaNoulCriteriais a struct, sotruestays beforefalse. A map would sort them the other way.state/instructionssdk.JSONObject, an ordered JSON object for values built at runtime. Order is preserved at every nesting level on the way back in too, and numbers decode asjson.Numberso a document round-trips byte-identically.A Go struct already marshals in field-declaration order, so a struct state needs no special handling — that is documented on
WithState, andJSONObjectis there for the case where a map would otherwise be the obvious choice. Score levels are a slice already, and their index is the level number, so they must never be reordered.Design notes
Criteria anymirrors the wire. The API has exactly onecriteriafield whose shape followstype, soQuestiondoes too:*NoulCriteria,ChoiceOptions, orScoreLevels.Validatetype-switches onKindand reports the mismatch. This keepsQuestionsmall enough to pass by value and lets a provider marshalq.Criteriastraight through.Confidenceis a*float64. The API documents that Noul answers carry no confidence. A plainfloat64would read as0.0, and the confidence-gating pattern the API is designed around (if confidence < 0.5 { route_to_human() }) would silently treat every Noul as maximally uncertain.result.Noul("department")on a Choice answer returnsanswer for "department" is a choice, not a noul, not a zero value.MaxChoiceOptions = 255,MinScoreLevels = 2,MaxScoreLevels = 10), so a malformed questionnaire fails before the round trip instead of as an opaque 422.WithQuestionsappends, so questions can be assembled across call sites. Asking them together is the point: the state is read once and every question is evaluated against it in parallel, which is what makes batching dramatically cheaper than one call per question.Tests
29 new unit tests, no API key or network required:
JSONObject,NoulCriteriafield order, struct members. The ordering tests assert against the sorted-map output too, so they fail loudly if they ever stop proving anything.JSONObjectround trip including nested objects, arrays and a large integer; non-object input rejected; duplicate keys rejected;nilmarshals tonull.EvaluationProvidercovering parameter pass-through, error propagation, typed accessors, kind-mismatch errors, and Noul's absent confidence.go build,go vet,golangci-lint run(0 issues) andgo test -short -raceall pass.Follow-ups
Not in this PR, kept separate to keep it reviewable:
provider/typesafe/systemone— the actual client. Needs 429/529 Overloadedbackoff honouringRetry-After, whichinternal/utils/fetch.godoes not have yet. Notesdk.ClassifyProbeStatusdoes not recognise 529 either.QuestionsFrom(&struct{ Urgent sdk.Noul; Team sdk.Choice }{...}), so a question and its answer live in one field. Good ergonomics for fixed question sets; the slice API in this PR stays underneath for the runtime-generated ones (ranking 255 options or 182 tools is a primary use case and cannot be expressed as struct fields).docs/evaluation.md, plus the capability table indocs/providers.mdand the README feature list.EvaluationProviderbuilt on the existing OpenAI/Anthropic/Google providers, so the same questions can run against either a real System One model or an LLM for cost/quality comparison. TypeSafe ships exactly this for Python; twilight's multi-provider reach makes it a natural fit.🤖 Generated with Claude Code