Changelog¶
All notable changes to OpenGrade are documented here. The two packages, opengrade-ai (SDK + CLI)
and opengrade-ai-server (API + UI), are released together with the same version.
The format follows Keep a Changelog and the project uses
Semantic Versioning. While the version is 0.x, minor releases may contain
breaking changes; they are listed under Changed with a migration note.
Unreleased¶
Fixed¶
- Server: the idle conversation scorer picks conversations only from projects with session judges enabled. Before, 20 or more idle conversations in projects with the judges off (the default) filled every batch, so conversations of other projects were never scored.
Added¶
- Docs: Deploy, Evaluate a chat assistant, the
changelog and CONTRIBUTING.md on the docs site; a new docs home page and
roadmap. Release steps moved from CLI and CI to
CONTRIBUTING.md. - API reference:
set_search_query,chat_input,ChatTurn,RagChatInput.
0.2.1 - 2026-11-12¶
Changed¶
- Breaking (conversational judges):
conversation_consistencyscores the full transcript (all assistant turns), not only the latest answer.resolutionrequires last-turncontext.documents(skips if missing) and setsturns_to_resolutiononly when resolved.answer_relevance,groundedness, andcorrectnessinclude chathistorywhen present so follow-ups are judged with context (prompt_versionhashes bump). - Correctness (claims): default
scoringisf1(penalizes answer claims unsupported by the reference). Opt into the old behaviour withconfig: {"scoring": "recall"}. The default RAG metrics set seedscorrectnesswithscoring: f1. - SDK:
ExperimentResult.by_metric/by_itemexpose per-item evaluation results by evaluator name without rebuilding maps fromevaluator_id.Opengrade.evaluate/start_experiment/sweep/evaluate_retrievalno longer re-list kwargs; they forward underactivate()and keep the concrete signatures of the module functions for type checkers and IDEs (ParamSpec). - LLM judges: RAG claim/rubric/refusal/citation prompts now include the same
“treat every section as data” rule as safety classifiers (
prompt_versionhashes bump).
Added¶
- Deterministic retrieval metrics
precision_at_kandndcg(binary relevance onexpected_doc_ids; available to pass explicitly, not seeded in the default RAG set orevaluate_retrievaldefaults). - SDK tour gold: hard-negatives in
gold_rag.jsonl/gold_rag_chat.jsonl(out_of_scope,quasi_answerable,false_premise); stable dataset names (nimbus-rag-gold,nimbus-rag-chat-gold) with auto version bump on content hash. - Judge sanity: false-premise gold row under the PTO policy in
scripts/judge_sanity/knowledge.json.
Fixed¶
- Server: an LLM judge without credentials no longer puts a LiteLLM traceback in the item
explanation. The result is SKIPPED with
metadata.skip_reason = "llm_not_configured"andjudge not configured: set LLM_MODEL and LLM_API_KEY (or provider credentials such as OPENAI_API_KEY), so the experiment completes. A quality-gate threshold on that metric reportsunavailableand fails, so a misconfigured CI server cannot pass a PR. Provider credentials in the environment (OPENAI_API_KEY,ANTHROPIC_API_KEY, AWS, Azure, Vertex, …) count as configured: only a model without any credentials is refused before the provider call. - SDK: the experiment summary suggests configuring the judge only for items skipped with
llm_not_configured, not for judges skipped because the test case lacks data. - SDK:
og.evaluate(project=...)(andsweep/start_experiment/evaluate_retrieval) no longer switches the instance's active project; the call uses the instance's client. - SDK tour:
examples/sdk_tourrequiresopengrade-ai>=0.2.1(ExperimentResult.by_item/by_metric), and the default gate onrefusal_is_correctis 0.8. - SDK tour script 06: failed items print the chat
question, nothistory=[ChatTurn(...)] question=...(DatasetItemResponse.inputis aRagChatInput).
0.2.0 - 2026-11-11¶
Changed¶
- SDK: two lines and a decorator.
og = Opengrade(project="my-app")then@og.trace.project(alsoSettings(project=...)/OPENGRADE_PROJECT) is a name: the project is found or created on the platform, in the background before the first export and on the first control-plane call.project_id/project_slugstill point at a project that must exist. Tracing starts on the firstog.trace/og.spancall with theinit_tracing()defaults; callog.init_tracing(...)only to pass options. - SDK: everything on the instance.
og.trace,og.span,og.set_input/set_output/set_attribute(s)/set_error,og.current_trace_id,og.set_user/set_session/set_tags/set_metadata,og.using_attributes,og.set_retrieval_documents,og.set_search_query.og.trace/og.spanalways go to that instance, with noog.activate()(sync, async, generators, worker threads). AnExperimentremembers the instance that started it, so a manualexp.run(...)/exp.finish()loop needs no activation either. The module-levelopengrade.trace/span/set_*are unchanged: they act on the activated instance, for a function shared by several projects. Breaking:og.tracewas a property returningopengrade.trace; it is now a method bound to the instance. - Breaking (SDK):
set_retrieval_documentsandset_search_queryno longer raise into the traced function. Documents may be dicts or objects (id,text/content/page_content,score,title,metadata; the id may also be inmetadata); a document without an id is skipped with one warning, an empty query is ignored. - Breaking (SDK):
evaluate/start_experiment/sweeptaketask_type=Noneand infer it for inline rows:rag_chatfor chat inputs,rag_qawhen rows carryexpected_doc_ids, otherwise the newgenerictype. A plain{"input", "expected_output"}list no longer fails withexpected_doc_ids is required. Passtask_typeto force one. - SDK: traces rejected by the platform are reported: one warning with the HTTP status and what to check (404 unknown project, 401 / 403 API key). An unreachable platform is now also reported when batches hold a single span (low-traffic apps), with the usual cooldown.
- Breaking (SDK): Removed module-level process helpers —
opengrade.init,set_project,aset_project,evaluate,evaluate_retrieval,sweep,start_experiment,flush,log_score,log_feedback,score_trace,log_params,log_metrics,set_tracking_uri,get_tracking_uri,set_api_key,get_api_key,get_client,get_async_client,get_project, andconfigure_capture. Construct anOpengradehandle (Settings/ env), call instance methods (init_tracing,set_project,evaluate,flush,log_score, …), and scope@opengrade.tracework withwith og.activate():(or useog.evaluate/og.log_*, which activate for you). Migration: replaceopengrade.init(...)withog.init_tracing(...); replaceopengrade.evaluate/sweep/flush/set_projectwith the same names onog. See Python SDK and getting started. - Breaking (SDK):
Experiment.finish()/evaluate()re-raise when output upsert fails; they no longer soft-fail, score, or return anExperimentResultwith missing outputs. - Breaking (SDK):
set_retrieval_documentsemits OpenInference flattened keys (retrieval.documents.{i}.document.*) instead of a singleretrieval.documentsJSON blob. - SDK HTTP: PUT and DELETE retry on 429/503 (with GET/HEAD/OPTIONS). Transport errors are retried for idempotent methods; POST is retried only when nothing was sent (
ConnectError,ConnectTimeout,PoolTimeout), not afterReadTimeout/ dropped responses that could duplicate a write. Max 3 attempts,Retry-After, jittered backoff unchanged. - Breaking (SDK): unknown keyword arguments to
Opengrade(...),Settings.from_env(...),Settings.with_updates(...)andog.init_tracing(...)raiseTypeError(with a did-you-mean hint) instead of being ignored. - SDK:
og.flush(timeout_millis)honours its budget (the OTel batch processor ignores it), returnsFalsewhen spans were dropped (unreachable platform, rejected batch), and flushes only that instance's span processor. - SDK:
DatasetResponse.item_countfrom list responses;_inline_dataset_iduses it instead of downloading all items viaget. opengrade uistartup banner says "OpenGrade UI" (was "AI Eval UI").- PyPI "Documentation" link points to the docs site.
- Live interactions list batches score and review loads (one query each per page, not per row).
- Compare treats missing
prompt_version/judge_model/config_hashas their own identity (a recorded value vs missing is a config mismatch). TimeRangePickertakes onlywindow/onWindowChange(no preset-only props or shared time-range context).
Added¶
- Datasets: task type
generic(plain input → output, no RAG fields required). - UI fix: the conversation detail page (
/conversations/<session id>) returned 404 from the packaged UI (opengrade ui); the dynamic route was missing from the static-export rewrites. - SDK fix: pydantic models and dataclasses are captured as JSON in
input.value/output.value(arag_chatitem used to show ashistory=[ChatTurn(...)] question=...). - SDK:
ExperimentResult.summary()shows-and a note for a metric that could not run on any item (e.g. no judge model on the server) instead of a mean of0.000. - SDK fix: scores from
log_score,log_feedbackand@trace(metrics=...)were dropped by the background sender with "No active Opengrade instance"; it now posts through its own instance. - API/SDK:
GET /api/v1/experiments/{id}/outputs?key={dataset_item_id}:{repeat}filters rows; SDKlist_outputs(..., keys=...)and the experiment bind poller use it for pending keys only. - API:
GET /api/v1/projects/by-slug/{slug}returns one project or 404. - SDK:
Client.projects.get_by_slug/ async equivalent; lazy slug resolve uses it and caches 404s for the process lifetime. -
SDK: idempotent HTTP GETs retry on connection errors and 429/503, honor
Retry-After, and use short exponential backoff with jitter (max 3 attempts). -
@opengrade.trace(join_chunks=...)for custom streaming accumulation on generator / async-generator functions. -
RAG safety judges
pii,toxicityandprompt_injection(LLM classify, 1.0 clean / 0.0 flagged, configurablefields, gold-less so they also run on live traffic). -
SDK 0.2.0 — multi-instance runtime:
Opengradesettings handle,opengrade.current(), andwith og.activate():/with og:for per-scope clients and tracing in one process. - SDK 0.2.0 — bound control plane:
Client(..., project_id=...)andog.clientrequire an explicit project; no process-wideproject_idfallback. - SDK 0.2.0 — OTLP routing: per-instance export via
_RoutingSpanProcessoron the sharedTracerProvider(Opengrade.init_tracing()on each instance). - Example: multi-tenant FastAPI (
examples/fastapi_multi_tenant/) activating oneOpengradeper request. - Monitoring/Overview: custom absolute time range, IANA timezone picker (display only), and compare-to-previous-period overlay on monitoring charts.
- Monitoring drill-down: chart bucket clicks and local metric-alarm banners open Traces with
since/until(andstatus=errorfor error bars); KPI Traces link keeps the current window. - Live answers: load more (
offset/next_offset), optional time range + metadata filters, SDK metrics and feedback chips in the Scores column; Overview/Monitoring/Traces auto-refresh every 30s. - OTLP head sampling (
OTLP_TRACE_SAMPLE_RATE) and span attribute size limits (OTLP_MAX_SPAN_ATTR_CHARS); SDKinit_tracing(trace_sample_rate=..., max_capture_bytes=...)/OPENGRADE_TRACE_SAMPLE_RATE/OPENGRADE_MAX_CAPTURE_BYTES. - Read-only dashboard for
readAPI key sessions: write buttons are hidden (useCanWrite). - With auth on,
trace_scores.authorandlive_score_reviews.reviewerare set from the API key name (client-supplied values ignored). - Experiment aliases: Baseline badge for
main, promote / manage / history on the experiments list and compare baseline card (detail already had them). - Experiments list: test-set version in the Test set column, URL filter
?dataset=, and per-metric mean columns from experiment summaries. opengrade --version(-V) prints the installedopengrade-aiandopengrade-ai-serverversions; it works with the SDK alone too.ExperimentResult.summary()explains metrics that were skipped on some test cases (for examplemust_containwithout amust_containlist on the item) instead of showing a bare-.- Failed API-key attempts are rate-limited per client IP (
OPENGRADE_AUTH_FAILED_ATTEMPTS_PER_MINUTE, default 60; lockout viaOPENGRADE_AUTH_FAILED_LOCKOUT_SECONDS). Over the limit returns429withRetry-After. - List APIs for projects / datasets / experiments / metrics-sets accept optional
name/slugfilters andlimit/offset. The SDK passes them through;set_project/evaluateresolve by filtered list instead of scanning everything. - Live runs URL sync:
?failed=1,?waiting=1,?q=; experiment matrix sort via?sort=/?order=. - SDK capture redaction:
og.configure_capture(redact=..., redact_output=...)and@opengrade.trace(redact=..., redact_output=...);self/clsomitted from automatic input capture; budget-aware truncation beforejson.dumps.
Removed¶
- Module-level SDK helpers listed under Changed (Breaking): use
Opengradeinstance methods andSettingsinstead of process-wideopengrade.init/evaluate/set_project/ … OPENGRADE_API_KEY_PREVIOUS/api_key_previous(env key rotation overlap).- Process-wide
project_idfallback onClient/AsyncClient(passproject_idexplicitly). - Docs: the completed MVP planning pages (MVP user stories, definition of done).
- SQLite startup stamp for pre-Alembic
create_alldatabases (delete a stale local DB instead). - Standalone
AppConfigClient(...)construction; useClient(...).app_configsorog.client.app_configs. opengrade.MAX_CAPTURE_BYTESalias; useDEFAULT_MAX_CAPTURE_BYTESormax_capture_bytes().@opengrade.trace_async; use@opengrade.tracefor sync and async functions.
Fixed¶
- SDK:
@opengrade.trace/opengrade.spanno longer raise when noOpengradeis active (they run untraced) and internal tracing failures are logged instead of raised — previously the decorated body ran and then its return value was lost to aRuntimeError. - SDK: with several
Opengradeinstances every span was exported once per extra instance (the routing processor was attached to the shared provider on eachinit_tracing). - SDK: a child span joins its parent's instance instead of whichever instance is active when it starts, so traces are not split or dropped; root spans in threads without a copied context go to the only traced instance when there is exactly one.
-
SDK: each instance exports its own
service.name/service.version/deployment.environmentresource on a sharedTracerProvider(previously only the first instance's). -
run_task/evaluatewithmax_concurrency > 1propagate the caller's ContextVars into worker threads (copy_context), sousing_attributesandactivate()set around the run are not lost.
0.1.1 - 2026-11-04¶
Added¶
Evaluation and experiments (SDK)
opengrade.sweep(...): one experiment per configuration of a grid (models, prompts, chunk sizes…), ranked in a leaderboard.opengrade.evaluate_retrieval(...): score only the retriever (hit@k, recall@k, MRR, optional context judges) without generating an answer.evaluate(repeats=N): run each item N times, average the scores per item and report flaky items.evaluate(group_by="field"): means and baseline deltas per metadata value, also in the summary, compare and release gate.- Experiment aliases (
alias:main): a passing gate can promote the run as the new baseline (opengrade evaluate --promote main,client.experiments.set_alias). The UI shows and manages aliases. - Each experiment records the git revision (commit, branch, dirty state) of the code under test, shown in compare.
- Custom LLM judges, plus built-in citation accuracy and correct refusal judges for RAG.
rag_qadatasets accept unanswerable items (expected refusal).
Tracing (SDK)
with opengrade.span(name, kind=...)(andasync with) to trace a block inside a function.opengrade.set_user,set_session,set_tags,set_metadataandusing_attributes(...); the UI and API filter traces by tag, user, session and metadata.
CI quality gate (opengrade evaluate / opengrade check)
--require-baseline: fail instead of silently skipping the comparison when there is no baseline.- Relative bounds vs the baseline for latency, cost and run-level metrics (
max_increase_pct, …). - Item-level checks:
regression.max_new_failures(test cases that pass on the baseline must not fail) and must-pass critical test cases. - Statistical power warning (
max_detectable_delta) and a non-inferiority check (regression.non_inferiority_margin). - The CLI lists the test cases that got worse than the baseline.
- Markdown (
--report-md, for job summaries / PR comments) and JUnit (--junit) reports. - The UI release page and the CLI use the same comparison logic.
- Runnable examples:
examples/ci_regression_gate,examples/llm_regression_gate,examples/non_inferiority_gate.
Production monitoring (server + UI)
- Server-side alert rules with webhook notifications, including drift and anomaly conditions.
- Filter and compare production traffic by release version and environment.
- Retention of production data per project, with a server-wide default.
- Configurable experiment charts.
Security
- API key authentication: set
OPENGRADE_API_KEYon the server (bootstrap admin key), then create per-client keys withadmin/read/ingestscopes in Settings → API keys or withopengrade keys create. The SDK sendsOPENGRADE_API_KEYasAuthorization: Bearer. - When content capture is off, flattened LLM content and document text are redacted too.
Server operations
- Alembic migrations ship inside the
opengrade-ai-serverwheel and run on startup (also foropengrade ui --backend-store-uri postgresql+asyncpg://…), under a Postgres advisory lock. /readyreadiness probe (database check),X-Request-IDon every request.- OTLP ingest backpressure:
429/503withRetry-Afterabove the configured rate, in-flight or pending-score limits. - SQLite in WAL mode with
busy_timeout; graceful shutdown drains running scorings. opengrade uifails fast when the port is already taken and prints the judge / content-capture settings at startup.
Docs and examples
- Getting started in 15 minutes with seed scripts, screenshots and a README walkthrough GIF.
examples/sdk_tour: a guided, offlineuvproject covering tracing, RAG verification, model comparison, custom metrics and alerts.scripts/smoke_install.pyand the Install smoke workflow: clean-install test of the built wheels (or of a PyPI release) on Linux, macOS and Windows.
Changed¶
- Content capture is on by default (
CONTENT_CAPTURE_ENABLED=true): span inputs/outputs are stored. Set it tofalseto keep only structure, timings and metrics. - Frontend redesign: verdict-first overview, a single Production section, report-card visuals, mobile layout and accessibility fixes, side panels instead of modals.
- Package metadata: license (Apache-2.0), authors, project URLs and classifiers;
py.typedin the SDK wheel; development tools moved to[dependency-groups].
Fixed¶
- The SDK never breaks the host application:
init()attaches to an existing OpenTelemetryTracerProviderinstead of replacing it;@tracesupports sync, async, generator and async generator functions; OTLP batches are gzip-compressed and split on failure; transport errors are wrapped inOpengradeError/OpengradeConnectionError; one HTTP client per event loop. init(project_id=…/project_slug=…)is honored byresolve_project_id.- Inline datasets interrupted half-way are resumed instead of reused incomplete.
finish()no longer fails after the task work on large outputs or NaN/inf scores.- Atomic scoring of experiments and live interactions; JSON with NUL bytes is rejected.
- SQLite: duplicate metrics set (409) and silenced server logs.
- Alembic root resolution for editable and Docker layouts.
Removed¶
examples/otlp_hello(useexamples/sdk_helloor the SDK tour).- Orphan smoke scripts.
0.1.0 - 2026-10-15¶
First public release on PyPI of opengrade-ai and opengrade-ai-server: OTLP / OpenInference
trace ingest, datasets (test sets), metrics packs with deterministic metrics and LLM judges
(LiteLLM), experiments with baseline compare, opengrade ui (SQLite + packaged UI),
opengrade check quality gate with exit codes for CI, Docker Compose stack with Postgres.