Evaluations internals¶
How the Evaluations extension is built. Its source is in extensions/evaluations: its own Compose project (iceberg-evaluations), dependencies, image, version and releases. It uses the platform only through the Extension Bridge v1 (contract 0.1.3 or later) and Conversational BI only through its evaluation API.
| Engine | pydantic-evals (MIT): datasets, the run loop and the LLM judge |
| API | OpenAPI contract |
| Agent skill | skills/agent-evaluations/SKILL.md |
| Decisions | DECISIONS.md, including why no evaluation server is bundled |
Architecture¶
flowchart LR
Person["Person (page)"] --> Eval
Agent["AI agent (MCP)"] --> Eval
subgraph Eval["Evaluations (one service, port 3012)"]
Runner["Runner<br/>pydantic-evals"]
Live["Live check"]
Store["SQLite<br/>question sets, runs, reviews"]
end
subgraph CBI["Conversational BI"]
EvalApi["/api/v1/evaluation/*<br/>ask, query, models, conversations"]
Record["Conversation record<br/>(opted-in teams)"]
end
Bridge["Extension Bridge<br/>team-extensions"]
Polaris["Polaris + RustFS<br/>evaluations.* tables"]
Runner -- "service account, for a team" --> EvalApi
Live --> EvalApi
EvalApi -- "both enabled?" --> Bridge
Runner -- "write token" --> Bridge
Runner -- "PyIceberg append" --> Polaris
- Asking for a team. Evaluations calls Conversational BI's
/api/v1/evaluation/*with its own Keycloak service-account token. Conversational BI accepts it only there, only for callers inBI_AGENT_CALLERS, and only after the Bridge capabilityteam-extensionsconfirms that the team enabled both extensions in that environment. It then reads with its own automation principal: the scope every team member has. - A run.
runner.pybuilds a pydantic-evalsDatasetfrom the question set's version. Its task asks/evaluation/askand computes the expected query with/evaluation/query. The scorers inscorers.pycompare them. Case results go to SQLite; the run then appendsevaluations.runsandevaluations.case_resultsto the target database with a write token of Evaluations' automation principal (lakehouse.py), and publishes the semantic modelevaluation_results. Only then is the rundone. - The control arm.
measure_semantic_modelstarts a pairedcontrolandsemanticrun. The control arm calls/evaluation/ask-schema-only: a second Pydantic AI agent (agent/control.py) with only table and column names and onerun_sqltool, guarded bysemantic/sql_guard.py. Only for this arm does the worker return binder, catalog and parser errors, so the LLM can correct its SQL as the semantic arm reads its error codes.compare_runsacross arms scores on the checks both have and reportsuplift. - Live evaluation.
live.pypages through/evaluation/conversationssince its watermark, scores a deterministic sample and queues failures as reviews. Conversational BI records a chat turn only when the team enabled Evaluations for the environment of the model it used (conversationalbi/conversations.py). - Telemetry. A run is a span
evaluate <set>with a span per case; httpx carries the trace into Conversational BI, so its agent run nests under the case. Scores aregen_ai.evaluation.resultlog events on the case span, sent to OpenTelemetry only.
Install from source¶
From the platform directory, with the platform and Conversational BI running:
uv run python -m scripts.setup --extension evaluations --origin http://localhost:3012 --handshake extensions/evaluations/.env.bridge
docker compose up -d --wait # registers the Evaluations Keycloak clients
docker compose -f compose.users.yaml up -d --wait users # the user portal links to Evaluations
cd extensions/evaluations
uv run python -m scripts.setup # writes .env for this extension
docker compose up -d --build --wait
Development¶
uv sync
uv run pytest && uv run ruff check .
uv run python -m scripts.api_contract # after an API change; review contracts/evaluations-api/v1/openapi.yaml
scripts/e2e_smoke.py checks a live installation. Prepare it as CI does (.github/workflows/evaluations-ci.yml): the flights product with scripts.flights_seed --own demo-demo, demo-admin an administrator of demo-team, and Conversational BI with the scripted model (LLM_MODEL=test:flights, BI_ALLOW_TEST_MODEL=1) and the control arm allowed (BI_EVAL_SCHEMA_ONLY=true; without it the smoke skips the control arm). That model answers every question with the average departure delay by carrier, so exactly 1 of the 25 flights cases passes.