jevlar

package module
v0.0.0-...-486db9e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 22, 2026 License: MIT Imports: 12 Imported by: 0

README

jevlar

CI Go Reference

Typed Jev decisions for Go. The name is Jev + kevlar: the types are there to stop the invalid request or the misread answer before it goes anywhere.

Jev answers three kinds of question about a piece of content: a noul (a yes/no statement, answered with a probability), a choice (pick one of N labels, with a probability for each), and a score (rate against an ordered rubric, answered with an expected level). All three can be mixed in one request. jevlar makes each of those shapes a distinct Go type, so a choice can't be read as a score, a label you never asked for can't come back as an answer, and the selected alternative arrives as your domain type rather than a string you then have to switch on and hope you spelled right.

The library is stdlib only. It has one HTTP client with no retries, no credential discovery, and no logging; the pure request/response layer is exported for anyone who'd rather bring their own transport.

Install

go get github.com/roasbeef/jevlar

Requires Go 1.27 or newer: Client.Evaluate and Batch.Map are generic methods, which landed in 1.27.

A mixed batch, end to end

This is the same program that's compiled as Example in example_test.go, minus the fixture server. Set JEV_API_KEY and run examples/triage to see it hit the real service.

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/roasbeef/jevlar"
)

// Queue is the application's own routing decision. Jev only ever sees the
// labels; the library hands back one of these values.
type Queue int

const (
	Billing Queue = iota
	Support
	Sales
)

// Triage is what we want to know about an inbound message.
type Triage struct {
	Route   jevlar.ChoiceAnswer[Queue]
	Urgency jevlar.ScoreAnswer
	Spam    jevlar.Probability
}

func main() {
	client := jevlar.NewClient(os.Getenv("JEV_API_KEY"))

	// Building a choice binds each label to a Go value. The alternative
	// count and label uniqueness are checked here, before any request
	// exists.
	route, err := jevlar.Choice(
		jevlar.Text("Which team should handle this?"),
		jevlar.Alt("billing", Billing).Describe(
			jevlar.Text("Charges, refunds, and invoices"),
		),
		jevlar.Alt("support", Support),
		jevlar.Alt("sales", Sales),
	)
	if err != nil {
		panic(err)
	}

	urgency, err := jevlar.Score(jevlar.Text("How urgent is this?"),
		jevlar.Text("Can wait"),
		jevlar.Text("Needs attention this week"),
		jevlar.Text("Needs attention today"),
	)
	if err != nil {
		panic(err)
	}

	// Three independent questions become one typed answer. The names are
	// the keys Jev uses in its reply; they never leak past this call.
	batch := jevlar.Map3(
		jevlar.Named("route", route),
		jevlar.Named("urgency", urgency),
		jevlar.Named("spam", jevlar.Noul(jevlar.Text("Is this spam?"))),
		func(r jevlar.ChoiceAnswer[Queue], u jevlar.ScoreAnswer,
			s jevlar.Probability) Triage {

			return Triage{Route: r, Urgency: u, Spam: s}
		},
	)

	eval, err := client.Evaluate(context.Background(),
		jevlar.Text("I was charged twice for last month. Please help."),
		batch,
	)
	if err != nil {
		panic(err)
	}

	// The switch is over our own type, so the compiler sees every case.
	switch eval.Answers.Route.Selected {
	case Billing:
		fmt.Println("route: billing")
	case Support:
		fmt.Println("route: support")
	case Sales:
		fmt.Println("route: sales")
	}
	fmt.Println("urgency level:", eval.Answers.Urgency.Nearest())
	fmt.Println("spam:", eval.Answers.Spam)
}

The three question types

Every constructor returns a Question[A], where A is the answer type. The question carries the only decoder that can produce that answer, so the compiler tracks which kind of answer you'll get back from each one.

Noul is a yes/no statement. jevlar.Noul(instructions) returns a Question[Probability]: the probability that the statement holds. There's no confidence field and no threshold; the library never turns a probability into a bool on your behalf, since you're the one who knows what a false positive costs. NoulWithCriteria(instructions, yes, no) lets you spell out what counts as each side (either may be nil).

Choice picks one of N labelled alternatives. jevlar.Choice(instructions, alts...) returns a Question[ChoiceAnswer[T]], with T being whatever type you put in the alternatives. An Alternative[T] is a label, your value, and an optional description; Alt(label, value) and .Describe(content) build one. The answer's Selected is your T, Label is the wire label (handy for logs), Confidence is Jev's own confidence, and Probabilities lists every alternative in the order you gave them, each paired with its probability. The constructor rejects zero alternatives, more than 255, an empty label, or a duplicate label, since any of those would be silently mangled by the JSON object the API expects.

Score rates content against an ordered rubric. jevlar.Score(instructions, levels...) returns a Question[ScoreAnswer]. Levels go from lowest to highest, and a level's position is its score, starting at zero. The answer's Value is the probability-weighted expected level (so 1.7 is perfectly normal), Nearest() rounds it to an integer level, and Legend and Probabilities are slices indexed by level, always exactly one entry per level you asked for. Rubrics must have two to ten levels.

Instructions, state, descriptions, and rubric levels are all Content. Only Text, Object, and Array implement it, which is exactly the set the API accepts at the top level; there's no way to send a bare number or null. jevlar.Marshal(v) turns a struct into Content (and refuses scalars), for when the state is structured rather than free text. On the answer side the same trick applies: Question[A] is constrained to the closed Answer interface (Probability, ChoiceAnswer[T], ScoreAnswer), so there's no Question[string] to accidentally build.

Batches

A Batch[A] is a set of named, independent questions whose answers combine into one value of type A. Named(name, q) lifts a single question into a batch. From there:

// Two to four heterogeneous questions into a struct.
jevlar.Map2(a, b, func(A, B) C) Batch[C]
jevlar.Map3(a, b, c, func(A, B, C) D) Batch[D]
jevlar.Map4(a, b, c, d, func(A, B, C, D) E) Batch[E]

// Any number of same-typed questions into a slice, in input order.
jevlar.All(batches...) Batch[[]A]

// Post-process an answer without touching the questions.
b.Map(func(A) B) Batch[B]

All is the one to reach for when the number of questions isn't known at compile time, e.g. scoring every passage in a document:

passages := make([]jevlar.Batch[jevlar.ScoreAnswer], len(docs))
for i, doc := range docs {
	passages[i] = jevlar.Named(fmt.Sprintf("p%d", i), relevance(doc))
}
eval, err := client.Evaluate(ctx, jevlar.Text(query),
	jevlar.All(passages...))
// eval.Answers is a []jevlar.ScoreAnswer, one per doc, in order.

Questions in a batch are independent: nothing in one question can see another's answer. A decision that depends on an earlier answer is a second request. Duplicate or empty names are caught when the request is built, and the response is checked to contain exactly the names that were asked for, nothing more and nothing less.

Client and transport

NewClient(apiKey, opts...) takes WithHTTPClient, WithBaseURL, and WithModel. The default model is jevlar.Latest (jev-latest), which is a moving alias; EvaluateWith pins a versioned name for a single call, and client.Models(ctx) lists what your account can use.

Every call is exactly one HTTP attempt. A non-200 status comes back as an *HTTPError with the status, headers, and raw body (422s carry a JSON list of what the service didn't like). HTTPError.Retryable() mirrors the official SDKs' policy (408, 429, 5xx) and RetryAfter() reads retry-after-ms or Retry-After for you, but the retry loop, the backoff, and the deadline are yours. A 200 that doesn't match the request's contract (a missing answer, a label that wasn't offered, a probability of 1.2) is an error wrapping ErrInvalidResponse.

If you'd rather not use the built-in client, EvaluationRequest(model, state, batch) and ModelsRequest() hand you a *Request[A] with Method(), Path(), and Body(). Do the exchange however you like, attach the bearer token yourself, and pass the 200 body to req.Decode. The decoder is bound to the request, so a response can't be decoded against the wrong batch. client.Do(ctx, req) is the same path with the built-in transport.

What's checked where

Whatever the type system can rule out, it does: answer type per question, domain type per choice, content shape, probability range. What it can't (alternative and level counts, label and name uniqueness, a blank model, a nil state) is checked once when the question or request is built, and never reaches the wire. On the way back, every answer's type tag, every label, every probability key and value, the score bounds, the legend, and the token counts are validated against the request that produced them. The distribution sum is allowed a tolerance of 0.01 per entry, since each value may be rounded independently. The library validates structure and bounds, not the truth of the model's judgement; a typed answer can still be wrong.

docs/API.md records the upstream contract this is built against and where the upstream documents disagree with each other.

Development

go test -race ./...
gofmt -l .
go vet ./...

The test suite covers wire encoding for all three question types, request building errors, malformed and inconsistent responses, HTTP failures and retry hints, and property-based round trips of generated choice and score questions via rapid. No API key is needed to run it.

License

MIT.

Documentation

Overview

Package jevlar is a typed, correct-by-construction client for Jev, the decision API served at api.typesafe.ai.

Jev answers three kinds of question about a piece of content. A noul question returns the probability that a yes/no statement holds. A choice question selects one of several labelled alternatives and reports the probability of each. A score question rates the content against an ordered rubric and returns a probability-weighted expected level.

The package encodes each of those shapes in the type system so that the compiler, rather than a runtime check, rules out the common mistakes:

  • A Question[A] carries the only decoder able to produce its answer, so a noul question cannot be read as a score and a choice cannot yield a label that was never requested. A is constrained to the closed Answer set, so nothing else can pose as a question's result.
  • A choice question maps each label to a caller-owned Go value. The selected alternative therefore arrives as the caller's own type, ready for an exhaustive switch.
  • A Batch[A] combines independent named questions into a single typed answer, either a struct assembled with Map2 through Map4 or a slice assembled with All. Answer names cannot drift from the questions that produced them because both live in the same value. Batch.Map and Client.Evaluate are generic methods, so the answer type flows through ordinary method calls; this is why the module requires Go 1.27.
  • Content is an interface implemented only by Text, Object, and Array, so a bare number, boolean, or null can never be sent where the API rejects it.
  • Probability is an opaque value in the closed interval [0, 1]. The library never turns a probability into a boolean on the caller's behalf.

The remaining constraints that cannot be expressed as types, such as the permitted number of alternatives or rubric levels, are checked once when the question is built, before any network traffic.

A Client sends prepared requests over HTTP. The pure request layer, EvaluationRequest and ModelsRequest together with Request.Decode, is exported for callers that own their own transport.

Example

Example shows a mixed batch evaluated end to end. The fixture server stands in for api.typesafe.ai so the example runs offline; drop the WithBaseURL option and supply a real API key to talk to the service.

package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"

	"github.com/roasbeef/jevlar"
)

// Queue is the application's own routing decision. Jev only ever sees the
// labels; the library hands back one of these values.
type Queue int

const (
	Billing Queue = iota
	Support
	Sales
)

// Triage is what the application wants to know about an inbound message.
type Triage struct {
	Route   jevlar.ChoiceAnswer[Queue]
	Urgency jevlar.ScoreAnswer
	Spam    jevlar.Probability
}

// Example shows a mixed batch evaluated end to end. The fixture server stands
// in for api.typesafe.ai so the example runs offline; drop the WithBaseURL
// option and supply a real API key to talk to the service.
func main() {
	srv := httptest.NewServer(http.HandlerFunc(fixtureHandler))
	defer srv.Close()

	client := jevlar.NewClient("your-api-key", jevlar.WithBaseURL(srv.URL))

	// Building a choice binds each label to a Go value. The count and
	// label uniqueness are checked here, before any request exists.
	route, err := jevlar.Choice(
		jevlar.Text("Which team should handle this?"),
		jevlar.Alt("billing", Billing).Describe(
			jevlar.Text("Charges, refunds, and invoices"),
		),
		jevlar.Alt("support", Support),
		jevlar.Alt("sales", Sales),
	)
	if err != nil {
		panic(err)
	}

	urgency, err := jevlar.Score(jevlar.Text("How urgent is this?"),
		jevlar.Text("Can wait"),
		jevlar.Text("Needs attention this week"),
		jevlar.Text("Needs attention today"),
	)
	if err != nil {
		panic(err)
	}

	// Three independent questions become one typed answer. The names are
	// the keys Jev uses in its reply; they never leak past this call.
	batch := jevlar.Map3(
		jevlar.Named("route", route),
		jevlar.Named("urgency", urgency),
		jevlar.Named("spam", jevlar.Noul(jevlar.Text("Is this spam?"))),
		func(r jevlar.ChoiceAnswer[Queue], u jevlar.ScoreAnswer,
			s jevlar.Probability) Triage {

			return Triage{Route: r, Urgency: u, Spam: s}
		},
	)

	eval, err := client.Evaluate(context.Background(),
		jevlar.Text("I was charged twice for last month. Please help."),
		batch,
	)
	if err != nil {
		panic(err)
	}

	// The switch is over the application's type, so the compiler can see
	// every case.
	switch eval.Answers.Route.Selected {
	case Billing:
		fmt.Println("route: billing")
	case Support:
		fmt.Println("route: support")
	case Sales:
		fmt.Println("route: sales")
	}
	fmt.Println("urgency level:", eval.Answers.Urgency.Nearest())
	fmt.Println("spam:", eval.Answers.Spam)
	fmt.Println("model:", eval.Model)

}

// fixtureHandler replies with a canned answer shaped like the real service.
func fixtureHandler(w http.ResponseWriter, r *http.Request) {
	_, _ = io.WriteString(w, `{
		"model": "jev-1.13.0",
		"answers": {
			"route": {
				"type": "choice", "choice": "billing",
				"confidence": 0.9,
				"probabilities": {
					"billing": 0.9, "support": 0.08,
					"sales": 0.02
				}
			},
			"urgency": {
				"type": "score", "score": 1.8,
				"confidence": 0.8,
				"legend": {
					"0": "Can wait",
					"1": "Needs attention this week",
					"2": "Needs attention today"
				},
				"probabilities": {
					"0": 0.05, "1": 0.1, "2": 0.85
				}
			},
			"spam": {"type": "noul", "noul": 0.02}
		},
		"usage": {"input_tokens": 120, "output_tokens": 12}
	}`)
}
Output:
route: billing
urgency level: 2
spam: 0.02
model: jev-1.13.0

Index

Examples

Constants

View Source
const (
	// DefaultBaseURL is the official API origin.
	DefaultBaseURL = "https://api.typesafe.ai"

	// MaxResponseBytes bounds how much of a response body the Client will
	// read. A well-formed answer is a few kilobytes at most.
	MaxResponseBytes = 4 << 20
)
View Source
const (
	// MaxAlternatives is the largest number of alternatives a choice
	// question may offer.
	MaxAlternatives = 255

	// MinScoreLevels is the smallest usable rubric. A single level cannot
	// discriminate anything.
	MinScoreLevels = 2

	// MaxScoreLevels is the largest rubric the API documents.
	MaxScoreLevels = 10
)

Variables

View Source
var (
	// ErrNilState is returned when an evaluation is prepared without any
	// content to evaluate.
	ErrNilState = errors.New("jevlar: state content is required")

	// ErrEmptyModel is returned when the model name is empty or blank.
	ErrEmptyModel = errors.New("jevlar: model name is empty")

	// ErrEmptyBatch is returned when a batch holds no questions. The API
	// requires at least one.
	ErrEmptyBatch = errors.New("jevlar: batch has no questions")

	// ErrEmptyName is returned when a question is named with the empty
	// string.
	ErrEmptyName = errors.New("jevlar: question name is empty")

	// ErrDuplicateName is returned when two questions in a batch share a
	// name. A JSON object cannot carry both.
	ErrDuplicateName = errors.New("jevlar: duplicate question name")

	// ErrChoiceCount is returned when a choice question has fewer than one
	// or more than MaxAlternatives alternatives.
	ErrChoiceCount = errors.New(
		"jevlar: choice requires between 1 and 255 alternatives",
	)

	// ErrEmptyLabel is returned when a choice alternative has an empty
	// label.
	ErrEmptyLabel = errors.New("jevlar: choice label is empty")

	// ErrDuplicateLabel is returned when two alternatives in a choice share
	// a label.
	ErrDuplicateLabel = errors.New("jevlar: duplicate choice label")

	// ErrScoreCount is returned when a score question has fewer than
	// MinScoreLevels or more than MaxScoreLevels rubric levels.
	ErrScoreCount = errors.New(
		"jevlar: score requires between 2 and 10 levels",
	)

	// ErrProbabilityRange is returned when a number outside [0, 1] is used
	// where a probability is required.
	ErrProbabilityRange = errors.New(
		"jevlar: probability must be within [0, 1]",
	)

	// ErrInvalidContent is returned when Marshal is given a value whose
	// JSON form is not a string, object, or array.
	ErrInvalidContent = errors.New(
		"jevlar: content must encode to a string, object, or array",
	)

	// ErrInvalidResponse wraps every failure to decode a 200 response into
	// the answer the request contracted for.
	ErrInvalidResponse = errors.New("jevlar: invalid response")

	// ErrMissingAPIKey is returned by the Client when no API key was
	// supplied.
	ErrMissingAPIKey = errors.New("jevlar: API key is required")

	// ErrResponseTooLarge is returned by the Client when a response body
	// exceeds MaxResponseBytes.
	ErrResponseTooLarge = errors.New("jevlar: response body too large")
)

Functions

func IsRetryable

func IsRetryable(err error) bool

IsRetryable reports whether err is an *HTTPError whose status the service suggests retrying.

Types

type Alternative

type Alternative[T any] struct {
	Label       string
	Value       T
	Description Content
}

Alternative is one labelled option of a choice question. Label is the string Jev evaluates and returns. Value is the caller's own representation of that outcome, which the decoded answer carries in place of the label. Description is optional; nil tells Jev to interpret the label alone.

func Alt

func Alt[T any](label string, value T) Alternative[T]

Alt is a shorthand constructor for an undescribed Alternative.

func (Alternative[T]) Describe

func (a Alternative[T]) Describe(description Content) Alternative[T]

Describe returns a copy of the alternative with a description attached.

type Answer

type Answer interface {
	// contains filtered or unexported methods
}

Answer is the closed set of answer types Jev can return: a Probability for a noul, a ChoiceAnswer for a choice, and a ScoreAnswer for a score. It constrains Question so that no other type can pose as an answer.

type Array

type Array []any

Array is an ordered list of JSON values.

type Batch

type Batch[A any] struct {
	// contains filtered or unexported fields
}

Batch is a set of independently named questions whose answers combine into a single value of type A. Every question in a batch is answered by one request, and A is fixed when the batch is built, so the shape of the answer is known before any I/O happens.

Start with Named to give one question a name, then combine batches with Map2, Map3, or Map4 to build a struct, or with All to build a slice. The Map method transforms an answer without changing the questions.

Questions in a batch are independent: no question can observe another's answer. A decision that depends on an earlier answer needs a second request.

func All

func All[A any](batches ...Batch[A]) Batch[[]A]

All combines any number of batches with the same answer type into one whose answer is a slice in input order. An empty input yields an empty batch, which EvaluationRequest rejects with ErrEmptyBatch.

func Map2

func Map2[A, B, C any](ba Batch[A], bb Batch[B], f func(A, B) C) Batch[C]

Map2 combines two batches into one whose answer is f applied to both.

func Map3

func Map3[A, B, C, D any](ba Batch[A], bb Batch[B], bc Batch[C],
	f func(A, B, C) D) Batch[D]

Map3 combines three batches into one whose answer is f applied to all.

func Map4

func Map4[A, B, C, D, E any](ba Batch[A], bb Batch[B], bc Batch[C],
	bd Batch[D], f func(A, B, C, D) E) Batch[E]

Map4 combines four batches into one whose answer is f applied to all.

func Named

func Named[A Answer](name string, q Question[A]) Batch[A]

Named places a single question in a batch under the given name. The API keys answers by name, so the name must be unique within the final request; EvaluationRequest reports ErrDuplicateName or ErrEmptyName otherwise.

func (Batch[A]) Map

func (b Batch[A]) Map[B any](f func(A) B) Batch[B]

Map transforms the answer of a batch without changing its questions.

type ChoiceAnswer

type ChoiceAnswer[T any] struct {
	Selected      T
	Label         string
	Confidence    Probability
	Probabilities []Outcome[T]
}

ChoiceAnswer is the decoded answer to a choice question. Selected is the value of the alternative the service selected, already converted to the caller's type. The library does not re-derive the winner from Probabilities. Label is the wire label of that alternative, kept for logging. Probabilities lists every requested alternative in request order.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client sends prepared requests to Jev over HTTP. It performs exactly one attempt per call, adds the bearer credential, and bounds the response size. Retries, if wanted, are the caller's policy; HTTPError.Retryable and HTTPError.RetryAfter expose the service's guidance.

A Client is safe for concurrent use.

func NewClient

func NewClient(apiKey string, opts ...Option) *Client

NewClient creates a client that authenticates with apiKey. The key is only ever written to the Authorization header of outgoing requests.

func (*Client) Do

func (c *Client) Do[A any](ctx context.Context, req *Request[A]) (A, error)

Do executes a prepared request and decodes its response. A non-200 status is returned as an *HTTPError carrying the status, headers, and raw body. A 200 body that violates the request's contract is returned as an error wrapping ErrInvalidResponse.

func (*Client) Evaluate

func (c *Client) Evaluate[A any](ctx context.Context, state Content,
	batch Batch[A]) (*Evaluation[A], error)

Evaluate asks every question in the batch about state using the client's default model. The returned Evaluation carries the batch's answer type.

func (*Client) EvaluateWith

func (c *Client) EvaluateWith[A any](ctx context.Context, model Model,
	state Content, batch Batch[A]) (*Evaluation[A], error)

EvaluateWith is Evaluate with an explicit model, for pinning a version on a single call.

func (*Client) Model

func (c *Client) Model() Model

Model returns the model Evaluate will request.

func (*Client) Models

func (c *Client) Models(ctx context.Context) ([]ModelInfo, error)

Models lists the models and aliases available to the account.

type Content

type Content interface {
	// contains filtered or unexported methods
}

Content is a JSON value that Jev accepts as state, instructions, a choice description, or a rubric level. Only Text, Object, and Array implement it. The API rejects a top-level number, boolean, or null, so those shapes cannot be expressed at all. Nested values inside an Object or Array are ordinary JSON and may hold any type.

func Marshal

func Marshal(v any) (Content, error)

Marshal converts any JSON-encodable Go value, typically a struct, into Content. It fails with ErrInvalidContent if the value encodes to a number, boolean, or null, since Jev cannot accept those at the top level.

type Evaluation

type Evaluation[A any] struct {
	// Model is the model that answered, which may differ from the alias
	// that was requested.
	Model Model

	// Answers is the combined answer of the batch.
	Answers A

	// Usage is the provider-reported token accounting.
	Usage Usage
}

Evaluation is the decoded result of one evaluation request. Answers has the batch's statically known type.

type HTTPError

type HTTPError struct {
	StatusCode int
	Header     http.Header
	Body       []byte
}

HTTPError is a non-200 response from the service. The body is kept verbatim because 422 responses carry a JSON list of validation problems, while proxy errors may be HTML or plain text. The body can echo request content, so callers decide whether to log it.

func (*HTTPError) Error

func (e *HTTPError) Error() string

Error summarises the failure with the status and a bounded excerpt of the body.

func (*HTTPError) RetryAfter

func (e *HTTPError) RetryAfter() (time.Duration, bool)

RetryAfter returns the delay the service asked for, read from the retry-after-ms header or a Retry-After header holding either a delay in seconds or an HTTP date. The second result is false when neither header is usable.

func (*HTTPError) Retryable

func (e *HTTPError) Retryable() bool

Retryable reports whether the official SDKs would retry this status: 408, 429, and every 5xx. It is advice only; the caller owns the retry budget.

type Model

type Model string

Model names a Jev model or alias accepted by the evaluation endpoint.

const Latest Model = "jev-latest"

Latest is the provider's moving alias for the current stable model. Pin a versioned name for evaluations that must stay reproducible.

type ModelInfo

type ModelInfo struct {
	// Name is accepted as the Model of an evaluation.
	Name Model `json:"name"`

	// Description is the provider's summary of the model.
	Description string `json:"description"`

	// ReleaseDate is the provider's date string, usually YYYY-MM-DD.
	ReleaseDate string `json:"release_date"`
}

ModelInfo describes one model returned by the model catalogue.

type Object

type Object map[string]any

Object is structured content with named fields. Keys are emitted in sorted order by encoding/json.

type Option

type Option func(*Client)

Option configures a Client.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL points the client at a different origin, such as a test server or a proxy. A trailing slash is removed.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient replaces the underlying HTTP client, for custom timeouts, proxies, or transports.

func WithModel

func WithModel(model Model) Option

WithModel sets the model used by Evaluate. It defaults to Latest.

type Outcome

type Outcome[T any] struct {
	Value       T
	Probability Probability
}

Outcome pairs one alternative's value with the probability Jev assigned to it.

type Probability

type Probability struct {
	// contains filtered or unexported fields
}

Probability is a number in the closed interval [0, 1]. It is opaque so that a value outside that range cannot be constructed. Jev reports probabilities and confidences with this type; converting one into a yes/no decision is left to the caller, who knows the cost of each kind of mistake.

func NewProbability

func NewProbability(v float64) (Probability, error)

NewProbability validates v and wraps it. It fails with ErrProbabilityRange for NaN and for any value outside [0, 1].

func (Probability) Float64

func (p Probability) Float64() float64

Float64 returns the underlying value for ranking, thresholding, and arithmetic.

func (Probability) String

func (p Probability) String() string

String formats the probability with the shortest representation that round trips.

type Question

type Question[A Answer] struct {
	// contains filtered or unexported fields
}

Question is a question about some content together with the only decoder able to read its answer. The type parameter A is the answer type, so the compiler tracks which kind of answer each question produces. Build one with Noul, NoulWithCriteria, Choice, or Score, then name it with Named to place it in a Batch.

func Choice

func Choice[T any](instructions Content,
	alternatives ...Alternative[T]) (Question[ChoiceAnswer[T]], error)

Choice builds a question that selects one of the supplied alternatives. The answer's Selected field has the alternatives' value type T. It fails with ErrChoiceCount, ErrEmptyLabel, or ErrDuplicateLabel when the alternatives cannot be expressed as a JSON object of distinct labels.

func Noul

func Noul(instructions Content) Question[Probability]

Noul builds a yes/no question. The answer is the probability that the statement in instructions holds for the content. A nil instructions value omits the field, which the API permits when the question is clear from context.

func NoulWithCriteria

func NoulWithCriteria(instructions, yes, no Content) Question[Probability]

NoulWithCriteria builds a yes/no question with optional evidence for each side. Either criterion may be nil, in which case it is omitted.

func Score

func Score(instructions Content,
	levels ...Content) (Question[ScoreAnswer], error)

Score builds a question that rates the content against an ordered rubric. Levels are described from lowest to highest; the level's position is its score, starting at zero. It fails with ErrScoreCount when the rubric is outside [MinScoreLevels, MaxScoreLevels].

type Request

type Request[A any] struct {
	// contains filtered or unexported fields
}

Request is a prepared HTTP request together with the only decoder able to read its 200 response. The Client executes it; callers with their own transport can read Method, Path, and Body, perform the exchange, and hand the 200 body to Decode.

func EvaluationRequest

func EvaluationRequest[A any](model Model, state Content,
	batch Batch[A]) (*Request[Evaluation[A]], error)

EvaluationRequest prepares a POST to /v1/systemone that asks every question in the batch about state. It reports ErrNilState, ErrEmptyModel, ErrEmptyBatch, ErrEmptyName, or ErrDuplicateName before building a body, and any Content that fails to encode.

func ModelsRequest

func ModelsRequest() *Request[[]ModelInfo]

ModelsRequest prepares a GET of /v1/models, which lists the models and aliases available to the authenticated account.

func (*Request[A]) Body

func (r *Request[A]) Body() []byte

Body returns the JSON body, or nil for a GET.

func (*Request[A]) Decode

func (r *Request[A]) Decode(body []byte) (A, error)

Decode validates a 200 response body against the request's contract. Any failure wraps ErrInvalidResponse. Callers must handle non-200 statuses before calling Decode; the Client does so by returning an *HTTPError.

func (*Request[A]) Method

func (r *Request[A]) Method() string

Method returns the HTTP method.

func (*Request[A]) Path

func (r *Request[A]) Path() string

Path returns the request path relative to the API base URL.

type ScoreAnswer

type ScoreAnswer struct {
	Value         float64
	Confidence    Probability
	Legend        []Content
	Probabilities []Probability
}

ScoreAnswer is the decoded answer to a score question. Value is the probability-weighted expected level and may fall between two integer levels. Legend and Probabilities are indexed by level, in the order the rubric was supplied, and always have exactly one entry per level.

func (ScoreAnswer) Nearest

func (s ScoreAnswer) Nearest() int

Nearest returns the integer level closest to Value. Ties round up.

type Text

type Text string

Text is natural-language content.

type Usage

type Usage struct {
	InputTokens  int `json:"input_tokens"`
	OutputTokens int `json:"output_tokens"`
}

Usage is the token accounting reported with every evaluation.

Directories

Path Synopsis
examples
triage command
Command triage evaluates one support message against Jev and prints the typed answers.
Command triage evaluates one support message against Jev and prints the typed answers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL