Documentation
¶
Overview ¶
Package eval mounts the Hanzo Cloud /v1/evals/* surface: a NATIVE, org-scoped evaluation system that replaces the retired 3.x observability-console fork (the crash-looping console proxy this file used to be). Nothing proxies to console anymore.
Storage split (CTO directive), two orthogonal stores this package composes:
- metastore (store.go) — Hanzo Base/SQLite, per-org config/metadata: datasets, dataset-items, evaluators, score-configs, dataset-run defs.
- telemetry (telemetry.go) — datastore/ClickHouse, append-only event stream: traces + scores-as-events (all AI observability). Optional at Mount; when no datastore is wired the run still scores, but trace/score persistence is honestly skipped (logged), never faked.
The run orchestrator drives a REAL evaluation: for each ACTIVE dataset item it (1) calls the model-under-test through the runner, (2) records a trace, (3) calls the LLM-as-judge through the runner, (4) validates + records the score, (5) updates the durable run record. The runner is PLUGGABLE (EvalRunner): today an in-process gateway runner (the caller's bearer → loopback /v1/chat/completions); a DigitalOcean-backed runner can drop in behind the same interface without touching the store/API/FE.
Tenant isolation is enforced SERVER-SIDE on every request: the org is c.Org() — the value SanitizeIdentity minted from the VALIDATED bearer owner (HIP-0026) — and NEVER a client-supplied X-Org-Id/X-Project-Id header. Every metastore query filters WHERE org=?; every telemetry read binds org as a named parameter.
Surface (all org-scoped; /v1 only):
POST /v1/evals/datasets create/upsert a dataset -> Dataset
GET /v1/evals/datasets list the org's datasets -> {data:[…]}
GET /v1/evals/datasets/:name dataset detail + item count -> Dataset
DELETE /v1/evals/datasets/:name delete a dataset (+ its items)
POST /v1/evals/dataset-items create/upsert an item -> DatasetItem
GET /v1/evals/dataset-items list items (datasetName, limit) -> {data:[…]}
POST /v1/evals/evaluators create/upsert an evaluator -> Evaluator
GET /v1/evals/evaluators list the org's evaluators -> {data:[…]}
POST /v1/evals/score-configs create/upsert a score config -> ScoreConfig
GET /v1/evals/score-configs list the org's score configs -> {data:[…]}
POST /v1/evals/scores record a score event -> ScoreView
GET /v1/evals/scores list score events (filters+limit) -> {data:[…]}
GET /v1/evals/traces list traces (filters+limit) -> {data:[…]}
POST /v1/evals/runs run a dataset through model+judge -> runSummary
GET /v1/evals/runs list run records (datasetName) -> {data:[…]}
Order 145: binds /v1/evals/* BEFORE the AI subsystem's /v1/* catch-all (150), the same slot productsvc uses. serve.go auto-registers GET /v1/evals/health.
Index ¶
- func Mount(app *zip.App, deps cloud.Deps) error
- func Shutdown() error
- type Dataset
- type DatasetItem
- type DatasetRun
- type EvalRunner
- type Evaluator
- type Observation
- type ObservationFilter
- type ScoreConfig
- type ScoreEvent
- type ScoreFilter
- type Store
- func (s *Store) Close() error
- func (s *Store) CountItems(ctx context.Context, org, dataset string) (int, error)
- func (s *Store) DeleteDataset(ctx context.Context, org, name string) (bool, error)
- func (s *Store) GetDataset(ctx context.Context, org, name string) (Dataset, error)
- func (s *Store) GetEvaluator(ctx context.Context, org, name string) (Evaluator, error)
- func (s *Store) GetItem(ctx context.Context, org, id string) (DatasetItem, error)
- func (s *Store) GetScoreConfig(ctx context.Context, org, name string) (ScoreConfig, error)
- func (s *Store) ListDatasets(ctx context.Context, org string, limit int) ([]Dataset, error)
- func (s *Store) ListEvaluators(ctx context.Context, org string, limit int) ([]Evaluator, error)
- func (s *Store) ListItems(ctx context.Context, org, dataset string, activeOnly bool, limit int) ([]DatasetItem, error)
- func (s *Store) ListRuns(ctx context.Context, org, dataset string, limit int) ([]DatasetRun, error)
- func (s *Store) ListScoreConfigs(ctx context.Context, org string, limit int) ([]ScoreConfig, error)
- func (s *Store) PutItem(ctx context.Context, it DatasetItem) (DatasetItem, error)
- func (s *Store) UpsertDataset(ctx context.Context, d Dataset) (Dataset, error)
- func (s *Store) UpsertEvaluator(ctx context.Context, e Evaluator) (Evaluator, error)
- func (s *Store) UpsertRun(ctx context.Context, r DatasetRun) (DatasetRun, error)
- func (s *Store) UpsertScoreConfig(ctx context.Context, c ScoreConfig) (ScoreConfig, error)
- type Telemetry
- type Trace
- type TraceFilter
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Dataset ¶ added in v1.786.12
type Dataset struct {
ID string
Org string
Name string
Description string
Metadata string // opaque JSON object, stored verbatim (bounded at the edge)
CreatedAt int64
UpdatedAt int64
}
Dataset is an org-scoped named collection of eval items. (org,name) is unique.
type DatasetItem ¶ added in v1.786.12
type DatasetItem struct {
ID string
Org string
Dataset string
Input string // opaque JSON, verbatim
Expected string // opaque JSON, verbatim ("expectedOutput" on the wire)
Metadata string // opaque JSON, verbatim
Status string // ACTIVE | ARCHIVED
CreatedAt int64
UpdatedAt int64
}
DatasetItem is one input/expected pair inside a dataset. Items are addressed by their own id; (org,dataset,id) scopes every lookup. Status ACTIVE|ARCHIVED mirrors the observation item lifecycle (a run consumes only ACTIVE items).
type DatasetRun ¶ added in v1.786.12
type DatasetRun struct {
ID string
Org string
Dataset string
Name string
Model string
JudgeModel string
Items int
Scored int
AvgScore float64
CreatedAt int64
UpdatedAt int64
}
DatasetRun is the DEFINITION of a run (its metadata): which dataset+model, the run name, and rollup counters. The per-item scores/traces are telemetry (ClickHouse); this row is the durable, listable run record. (org,dataset,name) is unique so a run name is stable per dataset.
type EvalRunner ¶ added in v1.786.12
type EvalRunner interface {
// Complete runs one item's input through the model-under-test and returns its
// raw text output.
Complete(ctx context.Context, authz, model string, input any) (string, error)
// Judge scores one item's output against the rubric using the judge model,
// returning a numeric score in [0,1] and a one-line reasoning. It never
// invents a score: an unparseable judge reply is an error the caller records
// as the item's failure, not a fabricated 0 or 1.
Judge(ctx context.Context, authz string, judge judgeSpec, input, expected any, output string) (float64, string, error)
}
EvalRunner is the PLUGGABLE execution seam (P3): the two independent steps of an evaluation, kept orthogonal so a DigitalOcean-backed runner and the in-process gateway runner both satisfy the same contract. The store, API and FE stay native and runner-agnostic regardless of which runner is wired.
The two steps are deliberately separate — Complete (produce the model-under-test output) and Judge (score that output against a rubric) — because DO's Agent Evaluations fuse run+judge into one async job with a FIXED (OpenAI) judge, whereas the gateway path needs them independent and needs the judge to be ANY model the caller can reach. A future DO adapter implements the same two methods (internally mapping a batch → evaluation_datasets upload → test_cases → evaluation_runs → poll → per-item results); nothing else changes.
Both methods take the caller's own Authorization bearer (authz): the run executes with the CALLER's identity and model entitlements — no privilege escalation, no service identity, the gateway authenticates it exactly as a direct call would.
type Evaluator ¶ added in v1.786.12
type Evaluator struct {
ID string
Org string
Name string
Model string
Criteria string
ScoreName string // the score name this evaluator emits (defaults to Name)
CreatedAt int64
UpdatedAt int64
}
Evaluator is an org-scoped judge definition: a model + rubric that scores a run's items. (org,name) is unique. It carries NO secret — the model key is the caller's own bearer at run time, never stored here.
type Observation ¶ added in v1.786.50
type Observation struct {
ID string
TraceID string
Org string
UserID string
Model string
Provider string
PromptTokens int64
CompletionTokens int64
TotalTokens int64
CostCents int64
Status string
ErrorMsg string
Timestamp time.Time
}
Telemetry is the append-only event store for eval traces + scores. Every method takes an authoritative org (via the value's Org field or the filter); no method can read across orgs. Observation is ONE production LLM generation — the read projection of the proven `hanzo.cloud_usage` ledger (written on every AI call by ai/object's zapWriteUsage, the same funnel that emits the OTel GenAI span). It is the v3 "observation of type GENERATION" shape the console's Observe surface renders: model/provider, in/out/total tokens, cost, status, attribution. This is a READ over an ai/object-owned table — eval never writes it (its own tables stay eval_traces/eval_scores) — so there is exactly ONE emission path (recordTrace), fanned to o11y (span), the observability ingest (span), and this ledger; no parallel write.
type ObservationFilter ¶ added in v1.786.50
ObservationFilter scopes a generations read. Org is authoritative (bound, never interpolated); Model/UserID are optional narrowers; Limit is always bounded.
type ScoreConfig ¶ added in v1.786.12
type ScoreConfig struct {
ID string
Org string
Name string
DataType string // NUMERIC | CATEGORICAL | BOOLEAN
MinValue *float64 // NUMERIC lower bound (nil = unbounded)
MaxValue *float64 // NUMERIC upper bound (nil = unbounded)
Categories []string // CATEGORICAL allowed labels
CreatedAt int64
UpdatedAt int64
}
ScoreConfig is an org-scoped definition of a score's shape: NUMERIC (with optional min/max), CATEGORICAL (with an allowed value set), or BOOLEAN. Scores written for this name are validated against it (Red: score integrity).
type ScoreEvent ¶ added in v1.786.12
type ScoreEvent struct {
ID string
Org string
Name string
TraceID string
RunName string
Dataset string
ItemID string
DataType string
Value float64
StringValue string
Comment string
Timestamp time.Time
}
ScoreEvent is one recorded score (append-only). Value is the numeric score; StringValue is the categorical/boolean label (empty for pure numeric). DataType is NUMERIC|CATEGORICAL|BOOLEAN. TraceID links it to its trace/run.
type ScoreFilter ¶ added in v1.786.12
ScoreFilter bounds a scores read. Org is MANDATORY (the caller passes the authoritative org); the others narrow within the org. Limit is always applied.
type Store ¶ added in v1.786.12
type Store struct {
// contains filtered or unexported fields
}
Store is the eval metastore over one SQLite file ({DataDir}/evals.db). Tenancy is the org column; MaxOpenConns(1) serializes writes against the file lock (same discipline as prompts/projectsvc).
func (*Store) CountItems ¶ added in v1.786.12
CountItems returns the number of items in (org,dataset) via a COUNT(*), so the dataset-detail view never has to load item bodies just to size the collection (Red LOW: loading up to maxListLimit full rows to len() them was a ~96MB amplification on a large dataset).
func (*Store) DeleteDataset ¶ added in v1.786.12
DeleteDataset removes a dataset and its items in one transaction. Reports whether the dataset existed.
func (*Store) GetDataset ¶ added in v1.786.12
func (*Store) GetEvaluator ¶ added in v1.786.12
func (*Store) GetScoreConfig ¶ added in v1.786.12
func (*Store) ListDatasets ¶ added in v1.786.12
func (*Store) ListEvaluators ¶ added in v1.786.12
func (*Store) ListItems ¶ added in v1.786.12
func (s *Store) ListItems(ctx context.Context, org, dataset string, activeOnly bool, limit int) ([]DatasetItem, error)
ListItems returns items for (org,dataset), newest first, bounded by limit. When activeOnly is set, ARCHIVED items are excluded (the run path uses this).
func (*Store) ListScoreConfigs ¶ added in v1.786.12
func (*Store) PutItem ¶ added in v1.786.12
func (s *Store) PutItem(ctx context.Context, it DatasetItem) (DatasetItem, error)
PutItem inserts a new item or updates an existing one by (org,id). The dataset MUST already exist for this org (enforced by the caller via GetDataset); the item is bound to that dataset. Cross-org item ids can never collide because the WHERE always carries org.
func (*Store) UpsertDataset ¶ added in v1.786.12
UpsertDataset creates a dataset, or updates description/metadata when (org,name) already exists. Idempotent create keeps the FE's "ensure dataset" call safe. Returns the resulting row.
func (*Store) UpsertEvaluator ¶ added in v1.786.12
func (*Store) UpsertRun ¶ added in v1.786.12
func (s *Store) UpsertRun(ctx context.Context, r DatasetRun) (DatasetRun, error)
UpsertRun records or updates a run's metadata by (org,dataset,name). The run row is the durable, listable record; its per-item scores live in telemetry.
func (*Store) UpsertScoreConfig ¶ added in v1.786.12
func (s *Store) UpsertScoreConfig(ctx context.Context, c ScoreConfig) (ScoreConfig, error)
type Telemetry ¶ added in v1.786.12
type Telemetry interface {
RecordTrace(ctx context.Context, t Trace) error
RecordScore(ctx context.Context, sc ScoreEvent) error
ListScores(ctx context.Context, f ScoreFilter) ([]ScoreEvent, error)
ListTraces(ctx context.Context, f TraceFilter) ([]Trace, error)
ListObservations(ctx context.Context, f ObservationFilter) ([]Observation, error)
Close() error
}
type Trace ¶ added in v1.786.12
type Trace struct {
ID string
Org string
Name string
Dataset string
ItemID string
RunName string
Model string
Input string
Output string
Timestamp time.Time
}
Trace is one model-under-test invocation recorded during a run. Input/Output are opaque JSON/text; metadata carries the run/dataset/item linkage.