Domain Model¶
Project¶
Top-level organization/isolation boundary for platform data.
In v0.1 this is not a multi-tenant SaaS tenancy or auth realm. It groups traces, datasets, experiments and policies for a team or application running a self-hosted instance.
Fields: - id - name - slug - created_at
Trace¶
Represents one execution of an AI application.
Fields: - id - project_id - trace_id - name - status - start_time - end_time - input - output - metadata - environment - user_id (optional) - session_id (optional)
Span¶
A timed child operation.
Fields: - id - trace_id - parent_span_id - span_id - name - kind - start_time - end_time - status - attributes - events
OpenInference span kinds should be supported where available, including LLM, EMBEDDING and CHAIN.
Dataset¶
Reusable evaluation collection.
Prefer one dataset per evaluation task (for example People Ops RAG vs classification). Optional soft typing guides the UI with field hints and recommended evaluators; it does not validate item schemas.
Fields:
- id
- project_id
- name
- version
- description
- task_type (optional; allowlist: rag_qa, rag_chat, classification, agent_tools, generic)
- created_at
DatasetItem¶
A single evaluation example.
Fields: - id - dataset_id - input - expected_output - context - metadata - source_trace_id (optional) - source_span_id (optional)
rag_qa test set contract¶
When task_type=rag_qa, each item used in the RAG vertical must include:
- question — stored as
input(string or structured JSON; portfolio/import use plain string) - expected_answer — stored as
expected_output - expected_doc_ids — non-empty list in
metadata.expected_doc_ids(CSV import uses pipe-separated ids)
actual_output and retrieved document text belong to a run (experiment item output or bound OTLP trace), not the test set.
rag_chat test set contract¶
When task_type=rag_chat, each item is one conversational state plus the last user turn:
- input —
{"history": [{"role": "user"|"assistant"|"system", "content": "..."}], "question": "..."} - expected_output — reference answer for the last turn only
- expected_doc_ids / must_contain / answerable — same semantics as
rag_qa, for the last turn
Last-turn judges (answer_relevance, groundedness, correctness, …) read
input.question and, when history is non-empty, also receive that history to
disambiguate the latest turn. Conversation judges:
query_rewrite_faithfulness (history + search query), conversation_consistency
(full transcript including the latest assistant answer), and resolution
(history + latest Q/A + last-turn context.documents; skips without documents;
turns_to_resolution only when resolved).
Metrics set (project-scoped)¶
Versioned grading configuration for RAG (and custom) evaluators on a project. One set per project may be marked is_project_default (seed name Default v1).
Fields: - id, project_id, name, version, description - is_project_default - entries (ordered) - created_at, updated_at
Each MetricsSetEntry has: kind, enabled, threshold, config, evaluator_id, is_default (seed rows on the default set), created_at.
Default seed entries (is_default=true, not deletable on the default set while referenced):
| kind | role |
|---|---|
hit_at_k |
expected doc id in top-k retrieved ids (binary) |
recall_at_k |
fraction of expected doc ids in top-k |
mrr |
reciprocal rank of first expected doc in top-k |
context_precision |
LLM judge: RAGAS average precision of retrieved chunks vs reference answer |
must_contain |
required phrases in the answer |
groundedness |
LLM judge on answer vs retrieved chunks |
correctness |
LLM judge vs expected answer |
latency |
per-item latency vs config.max_ms |
Custom sets are created with POST .../metrics-sets; versioning copies entries with is_default=false. Experiments may pin a set via metrics_set_id.
Metrics pack (API alias)¶
HTTP /metrics-pack reads and writes the project default metrics set. Prefer /metrics-sets for named/custom sets.
Evaluator¶
A named implementation/configuration.
Fields: - id - project_id - name - type - config - version
Types: - deterministic - llm_judge - custom
EvaluationRun¶
Execution of an evaluator against a dataset/experiment.
Fields: - id - experiment_id - evaluator_id - status - started_at - finished_at
EvaluationResult¶
Per-item score.
Fields: - id - run_id - dataset_item_id - score - label - explanation - metadata - duration_ms
AppConfig¶
Versioned registry entry for prompt, model, and retrieval settings used when running evaluations.
Fields:
- id
- project_id
- name
- version (monotonic per name within a project)
- description (optional)
- prompt (JSON object)
- model (JSON object)
- retrieval (JSON object)
- content_hash (SHA-256 of canonical prompt/model/retrieval)
- created_at
Creating a config with the same name allocates the next version. Listing supports name filter and latest=true (one row per config family).
AppConfigAlias¶
Named pointer to a specific AppConfig version within a project (for example baseline, candidate).
Fields: - project_id - name - app_config_id - updated_at
Aliases are unique per (project_id, name). PUT creates or moves an alias; DELETE removes it.
Experiment¶
A versioned evaluation execution.
Fields:
- id
- project_id
- name
- dataset_id
- model_config (free-form JSON, or snapshot from an app config when bound)
- app_config_id (optional; set when created from app_config_id or resolved app_config_alias)
- metrics_set_id (optional pin for pack scoring; overridable per evaluate-pack request)
- version
- baseline_experiment_id
- status
- created_at
On create, supply at most one of app_config_id or app_config_alias. When either is set, the API copies the referenced config into model_config (including app_config_id, content_hash, prompt/model/retrieval) and ignores a client-supplied model_config. Without app config fields, model_config remains free-form.
Versioning¶
An experiment is an immutable record of what was evaluated against what. Every input it depends on is pinned to a version, so two experiments can be compared and an old result can be explained later:
| Input | How it is pinned |
|---|---|
| Test set | dataset_id points at one dataset version. Once a version has a scored run it is frozen: adding, importing or promoting items forks the next version (version + 1) instead of changing it. |
| App under test | model_config is a snapshot. With an app config it carries app_config_id and content_hash; app configs themselves are versioned per name (monotonic version). |
| Parameters | Logged params are locked once the experiment has evaluation runs (or when they come from an app config snapshot). |
| Grading | metrics_set_id pins a versioned metrics set. Each EvaluationRun is kept; LLM-judge results record prompt_version and model in their metadata. |
| Label | version is an optional free-form label (git SHA, release tag) set by the caller (default: the short commit SHA, -dirty suffix with uncommitted changes), e.g. og.evaluate(..., version="v1.4.0"). |
| Code | git is {commit, branch, dirty, remote_url}, detected by the SDK from the local checkout (or CI env vars such as GITHUB_SHA / CI_COMMIT_SHA when there is no .git). Compare shows both commits and links the code diff for GitHub / GitLab remotes. Credentials are stripped from the remote URL. Opt out with git=False or OPENGRADE_DISABLE_GIT=1. |
Re-scoring an experiment adds a new EvaluationRun; it never overwrites earlier results. Compare and release checks read the latest terminal run with results.
ReleasePolicy¶
Machine-readable quality gate. Phase 4 evaluates it statelessly (YAML/file or inline API body); the reserved keys are regression, latency, cost and experiment_metrics; every other key binds to an evaluator entity name.
Semantics:
- {name}.min — pass if latest-run mean_score >= min
- latency.p95_max_ms — pass if p95 of result metadata.latency_ms (nearest-rank) <= threshold. Rows without latency_ms (e.g. SKIPPED) are excluded from the sample. Any result with metadata.missing_output: true and no usable latency_ms fails the check (do not green-light incomplete runs on a latency subset).
- cost.max_per_request_usd — pass if mean of result metadata.cost_usd <= threshold
- latency.max_increase_pct / cost.max_increase_pct — relative bounds vs the baseline experiment: pass if the candidate p95 latency (resp. mean cost per request) is at most that many percent above the baseline's ((candidate − baseline) / baseline × 100). Checks latency.p95.max_increase_pct and cost.mean_per_request_usd.max_increase_pct; actual is the change in percent. Missing data on either side → unavailable; any increase from a baseline of 0 fails. Each block needs its absolute bound, its relative bound, or both.
- regression — for each shared evaluator, uses the same bootstrap status as experiment compare (GET …/compare/{baseline}): fail when status is regression (paired 95% CI entirely below 0). Optional max_delta is a mean-score safety floor: also fail when status is unchanged/improved but mean_score delta (candidate − baseline) < max_delta. Compare status insufficient_n (n < 5) yields check unavailable → overall failed. Opt-in regression.allow_insufficient_n: true restores soft-pass (skip those evaluators) for small demo sets. A bare regression: {} gates on bootstrap only.
- experiment_metrics — {key: {min?, max?}} gates experiment-level scalars logged via PUT /experiments/{id}/metrics; each bound becomes a check experiment_metrics.<key>.min|max. A key that was never logged is unavailable and fails the gate. Relative bounds vs the baseline run: max_increase / max_decrease (absolute change) and max_increase_pct / max_decrease_pct (percent of the baseline value), checks experiment_metrics.<key>.<bound>. actual is the change in the guarded direction (positive = moved that way; improvements are negative) and passes when <= the bound. Use max_increase for lower-is-better metrics (opengrade.error_rate, opengrade.latency_p95_ms) and max_decrease for higher-is-better ones.
- Rules that compare with a baseline (regression, groups.max_delta, any relative bound) require a baseline experiment.
- Missing run/metadata/score → check unavailable → overall failed
Example (RAG portfolio uses pack metric names):
hit_at_k:
min: 0.8
groundedness:
min: 0.7
correctness:
min: 0.7
latency:
p95_max_ms: 30000
regression:
max_delta: -0.05 # optional mean floor; omit for bootstrap-only
# allow_insufficient_n: true # optional; demos with n < 5 only
# Relative bounds vs the baseline (optional):
# latency: {p95_max_ms: 30000, max_increase_pct: 25}
# cost: {max_increase_pct: 10}
# experiment_metrics:
# opengrade.error_rate: {max: 0.05, max_increase: 0.01}
Custom evaluator names (for example quality with a contains evaluator) are valid when those evaluators exist on the project.
LiveInteraction (Live runs)¶
Prod-like turn submitted for gold-less review (not a DatasetItem / Experiment).
Fields:
- id, project_id
- question, answer
- documents (normalized retrieval docs)
- metadata, external_id (optional; unique per project)
- judge_status: pending | running | scored | error
- metrics_set_id, score_warning, error_message
- created_at, scored_at
Related:
- LiveInteractionScore — per gold-less metric (groundedness, answer_relevance) outcome
- LiveScoreReview — per-score human verdict agree | disagree with the judge label, optional corrected_explanation / note (source for judge calibration)
Promote creates a DatasetItem on a chosen dataset (does not delete the live interaction).
LiveScoreReview¶
Fields: id, live_interaction_score_id (unique), verdict, corrected_explanation, note, reviewer, created_at. Deleted when the score is replaced on rescore.
Review¶
Human judgement attached to a trace/span/evaluation result (generic Phase 5 model; Live Run MVP uses LiveScoreReview).
Fields: - id - target_type - target_id - label - score - comment - reviewer - created_at