Skip to main content

Agent Eval Configuration

Agent evals are configured in the same Weiser YAML file as your data quality checks, using three new optional top-level sections: agent_variants, semantic_layers, and eval_suites. All existing sections (datasources, checks, connections, includes, slack_url) work unchanged, and environment variable templating ({{VARIABLE_NAME}}) applies to the new sections too.

version: 1

datasources:
- name: local_db
type: postgresql
uri: duckdb:///examples/eval_example.duckdb

checks:
- name: merchants_not_empty
dataset: merchants
datasource: local_db
type: row_count
condition: gt
threshold: 0

connections:
- name: metricstore
type: metricstore
db_type: duckdb
db_name: eval_example_metricstore.db

semantic_layers:
- name: local_sl
type: generic_sql
datasource: local_db

agent_variants:
- name: baseline
framework: pydantic_ai
entrypoint: examples.eval_agents.build_bi_agent
system_prompt: "You are a careful BI analyst. Always verify column names before querying."
tools: [list_views, describe_view, query, submit_answer]

- name: no_describe_view
framework: pydantic_ai
entrypoint: examples.eval_agents.build_bi_agent
system_prompt: "You are a careful BI analyst. Always verify column names before querying."
tools: [list_views, query, submit_answer]

eval_suites:
- name: lookup_tool_ablation
arms:
- name: baseline
agent_variant: baseline
semantic_layer: local_sl
- name: no_describe_view
agent_variant: no_describe_view
semantic_layer: local_sl
goldens:
- id: merchant_count
input: "How many merchants are there in total?"
split: train
level: easy
reference_values:
- metric: cnt
expected: 3
tolerance_pct: 0
expected_views: [merchants]
metrics:
- type: schema_membership
threshold: 1.0
- type: reference_value_match
- type: llm_judge
name: sql_soundness
threshold: 0.5
params:
evaluation_params: [input, predicted_sqls]
criteria: >
Given the question, is the submitted SQL structurally sound and a
plausible way to answer it? Do not penalize style choices.

semantic_layers

A semantic layer tells the harness which schema the agent may query against. It references an existing datasource by name and reuses its connection details.

ParameterRequiredDescription
nameYesUnique identifier, referenced by eval_suites[].arms[].semantic_layer
typeYescube or generic_sql
datasourceYesName of an existing datasources entry
meta_api_urlNoCube.js only — base URL of the Cube API (e.g. http://localhost:4000)
meta_api_tokenNoCube.js only — API token; use {{CUBE_API_TOKEN}} for secrets

Generic SQL

Introspects tables/views directly from any Weiser SQL datasource via SQLAlchemy. No extra parameters needed:

semantic_layers:
- name: local_sl
type: generic_sql
datasource: local_db

Cube.js

Reads measures/dimensions from the Cube REST meta API; queries still execute through the datasource connection:

semantic_layers:
- name: cube_prod
type: cube
datasource: cube_prod_ds
meta_api_url: "{{CUBE_META_URL}}"
meta_api_token: "{{CUBE_API_TOKEN}}"

agent_variants

An agent variant is a declaratively-configured agent shape. entrypoint is a dotted path to a factory function you write once per agent family; every other field is a knob the harness passes into that factory, so variants of the same agent are pure config.

ParameterRequiredDefaultDescription
nameYesUnique identifier, referenced by eval_suites[].arms[].agent_variant
frameworkYesAgent framework; currently pydantic_ai
entrypointYesDotted path to a factory function, e.g. examples.eval_agents.build_bi_agent
modelNoframework defaultModel string, e.g. anthropic:claude-sonnet-5
system_promptNoInline prompt text or a path to a prompt file (Jinja2-rendered)
toolsNoall toolsSubset of list_views, describe_view, query, submit_answer to expose
model_settingsNoPassed through verbatim to the framework (e.g. temperature)
max_turnsNo40Turn budget per question; hitting it is recorded as a first-class failure category
extraNoFramework-specific escape hatch for anything not modeled above

The entrypoint factory

For pydantic_ai, the entrypoint is a function that takes the variant and the (already-filtered) toolset and returns a built Agent. Write one per agent family and reuse it across every variant:

# examples/eval_agents.py
from pydantic_ai import Agent


def build_bi_agent(variant, tools) -> Agent:
return Agent(
model=variant.model or "anthropic:claude-sonnet-5",
system_prompt=variant.system_prompt or "You are a careful BI analyst.",
tools=tools,
model_settings=variant.model_settings,
)

The tools handed to the factory are already filtered to variant.tools by the harness — that is what makes "compare an agent with/without a tool" a pure-YAML experiment. For a single-arm baseline of "whatever is actually in prod right now", the factory can ignore the knobs and return an already-built agent; extra carries anything framework-specific.

Ablation example

The canonical use case — add a tool, compare with/without. Same factory, same prompt, only the tool set differs:

agent_variants:
- name: baseline
framework: pydantic_ai
entrypoint: myapp.eval_agents.build_bi_agent
model: anthropic:claude-sonnet-5
system_prompt: prompts/bi_system.md
tools: [list_views, describe_view, query, submit_answer]

- name: with_lookup_tool
framework: pydantic_ai
entrypoint: myapp.eval_agents.build_bi_agent
model: anthropic:claude-sonnet-5
system_prompt: prompts/bi_system.md
tools: [list_views, describe_view, query, submit_answer, lookup_customer]

eval_suites

An eval suite pairs one or more arms against a shared set of goldens, scored by a set of metrics.

ParameterRequiredDescription
nameYesUnique suite name, passed to weiser eval --suite
armsYesOne or more EvalArm entries; 2+ arms enables the pairwise comparison report
golden_setNo*Path to an external golden file (.yaml, .yml, or .jsonl)
goldensNo*Inline list of goldens; merged with golden_set if both are given
metricsYesList of metric configurations (see Metrics)
dq_scopeNoExplicit dataset names to use for data-quality attribution; default is auto (derived from the tables the agent's SQL touched)

* At least one of golden_set or goldens must provide goldens — weiser eval-compile fails otherwise.

Arms

An arm is a named pairing of an agent variant with a semantic layer:

arms:
- name: baseline
agent_variant: baseline
semantic_layer: local_sl
- name: no_describe_view
agent_variant: no_describe_view
semantic_layer: local_sl

agent_variant and semantic_layer reference entries by name; weiser eval-compile resolves all references and fails on unknown names.

Goldens

A golden is a single test case. It can be declared inline in the suite or loaded from an external file via golden_set. External YAML files are {goldens: [...]}-shaped and go through the normal Weiser config loader (Jinja2 templating and includes: work); .jsonl files hold one golden dict per line for bulk, synthetic, or production-mined sets.

ParameterRequiredDefaultDescription
idYesUnique identifier within the suite
inputYesThe natural-language question
splitNotraintrain or held_out; use --split to filter a run
levelNoeasyeasy or hard; the scorecard reports accuracy per level
sourceNohand_writtenhand_written, synthetic, or production
reference_valuesNoPinned expected values (see below); enables the reference_value_match metric
reference_answer_textNoA reference natural-language answer; enables applicable_when: reference_answer_text judge criteria
reference_sourceNoProvenance of the reference: human_verified, independent_query, or unverified
expected_viewsNoViews the agent should touch; enables the expected_view_recall metric

reference_values entries:

ParameterRequiredDefaultDescription
metricYesColumn name in the agent's final query results (or row_count)
expectedYesExpected value
tolerance_pctNo0.0Allowed relative deviation, e.g. 0.01 for 1%
widget_indexNoWhich result row to read the metric from

Keep the held-out set as its own versioned, git-committed artifact (golden_set: evals/held_out.yaml) so each revision stays immutable.

Data-quality scope

dq_scope restricts which configured checks: count as data-quality attribution evidence for the suite. By default it is derived automatically: any check whose dataset overlaps a table the agent's SQL touched.

Configuration Includes

The new sections merge identically to existing ones when using includes:, so you can keep evals in their own file:

# main.yaml
version: 1
includes:
- evals/agent_variants.yaml
- evals/suites.yaml

Validate Your Config

weiser eval-compile evals.yaml                      # all suites
weiser eval-compile evals.yaml --suite my_suite # a single suite

This validates the new sections, resolves all agent_variant/semantic_layer/datasource references, checks that the suite has goldens, and runs the multi-variable lint — without executing anything.