Evaluate and promote Cortex Agent versions¶
Run Cortex Agent evaluations with pinned metric versions and a custom judge, compare two committed versions, and promote the winner by alias.
| Stage | Evaluate, Operate |
| Topics | Cortex Agents |
| Status | GA |
| Time to complete | 45min |
| Edition | Any edition with Cortex Agents |
| Regions | Cross-region inference enabled for the claude-sonnet-4-6 judge and orchestration model |
| Privileges | ACCOUNTADMIN for sql/01_setup.sql and sql/99_teardown.sql; 02 to 07 run as COOKBOOK_AGENT_EVALUATIONS_ADMIN |
| Related skills | agent-studio (bundled with Cortex Code) |
| Last verified | 2026-10-04 |
| Authors | Bala Krishnamoorthy, Parker Erickson |
| Code | View all files or download agent-evaluations.zip, which includes this page as README.md. Teardown: sql/99_teardown.sql |
This recipe is a reference implementation: review the code and adapt it to your environment before production use.
Score a Cortex Agent against a small ground-truth dataset, change one thing,
score the new version the same way, and promote it to the PRODUCTION alias only
if it wins. Everything runs in SQL: a dataset table, a YAML config on a stage,
EXECUTE_AI_EVALUATION to run it, and GET_AI_EVALUATION_DATA to read the
scores. The judges are pinned so two runs are comparable, and the agent versions
are pinned so you know exactly which configuration each score belongs to.
When to use this¶
- Before an agent change reaches users: new instructions, a new tool, a new model.
- To turn “the new prompt feels better” into a number per question and per metric.
- As the gate in a promotion flow, where only a version that passes is moved to PRODUCTION.
When not to use it:
- Agents whose answers depend on session attributes or row access policies keyed on them. Evaluation runs do not pass session attributes, so the results will not match what users see.
- Agents whose main tools are MCP servers. Evaluations do not call MCP tools.
- Live-traffic monitoring. Evaluations score a fixed dataset; use agent monitoring for production conversations.
How it works¶
Key concepts:
- Dataset. A table with a VARCHAR question column and a VARIANT ground truth
column.
answer_correctnessreads theground_truth_outputkey. Custom metrics see the whole VARIANT through{{ground_truth}}, so you can add your own keys (this recipe addsexpected_toolsandintent). - Config on a stage. The YAML has three top-level keys:
dataset(optional; creates the dataset),evaluation(agent_params,run_params,source_metadata), andmetrics. It is uploaded withPUTto a stage whose file format is CSV withFIELD_DELIMITER = NONE. - Pinned judges. System metrics take a
version. This recipe pinsv3(judgeclaude-sonnet-4-6, oropenai-gpt-5.4where Anthropic models are not allowed). The custom metric names its judge withmodel, because the default custom-metric judge is a legacy model. - Pinned agent versions.
agent_params.agent_versionacceptsVERSION$N, an alias, orLIVE,FIRST,LAST,DEFAULT. The baseline pinsVERSION$1; the candidate uses theCANDIDATEalias, which 06 points at the version it commits. - Promotion by alias. When the candidate passes the gate, 07 moves the
PRODUCTION alias and the DEFAULT pin to it. Applications that call
'...CMO_ASSISTANT!PRODUCTION'get the new version on their next request.
The metrics follow Snowflake’s Goal-Plan-Action framework. There are four system
metrics: answer_correctness and logical_consistency (used here), and
tool_selection_accuracy and tool_execution_accuracy, which are in Public
Preview and read a ground_truth_invocations key. This recipe uses only the
generally available metrics plus a custom metric for tool routing.
Prerequisites¶
| Requirement | Notes |
|---|---|
Cross-region inference, or a region with claude-sonnet-4-6 | Used by the agent and by every judge in this recipe |
ACCOUNTADMIN for 01_setup.sql and 99_teardown.sql | 01 creates the recipe role and grants it SNOWFLAKE.CORTEX_USER and EXECUTE TASK ON ACCOUNT. Teardown deletes the runs as the recipe role, then switches to ACCOUNTADMIN to drop the database, warehouse, and role. Scripts 02 to 07 run as COOKBOOK_AGENT_EVALUATIONS_ADMIN. |
USE AI FUNCTIONS | Granted to PUBLIC by default. If your account revoked it, grant it to COOKBOOK_AGENT_EVALUATIONS_ADMIN. |
Snowflake CLI (snow sql) or another client that supports PUT | 03 uploads the config files from config/. 05 takes the run name through the CLI’s -D option; in another client, edit its SET agenteval_run line instead. |
| Run from the recipe folder | PUT file://config/... paths are relative to it |
Quickstart¶
From agent-evaluations/:
snow sql -f sql/01_setup.sql: role, warehouse, database, 48 rows of campaign data, four strategy documents, the semantic view, and the search service.snow sql -f sql/02_agent.sql: the CMO assistant. VERSION$1 is the baseline; DEFAULT is pinned to it.snow sql -f sql/03_eval_dataset.sql: five evaluation questions, the config stage, andPUTof both config files.snow sql -f sql/04_run_baseline.sql: start the baseline run. It takes a few minutes.snow sql -f sql/05_results.sql: STATUS for the baseline run, then scores per metric, lowest scores with the judge’s explanation, and scores per intent. Until the run isCOMPLETED, 05 stops after STATUS with “The run is still in progress”, so run it again until it gets past that point.snow sql -f sql/06_candidate.sql: add routing and scope rules, commit VERSION$2, point CANDIDATE at it, start the candidate run. Then poll and read it the same way withsnow sql -f sql/05_results.sql -D run_name=candidate.snow sql -f sql/07_compare_and_promote.sql: compare the runs, promote if the gate passes, and call!PRODUCTION.snow sql -f sql/99_teardown.sql.
Walkthrough¶
The dataset¶
Five questions cover the intents the agent must handle: a metric, a comparison, a document lookup, an actual-versus-benchmark question that needs both tools, and an out-of-scope request. Ground truth uses literal values from the data and says what a correct answer must and must not contain.
Use PARSE_JSON for the VARIANT column. OBJECT_CONSTRUCT returns an OBJECT,
not a VARIANT.
The config and the upload¶
config/eval_baseline.yaml includes a dataset
block, so its START creates CMO_EVAL_DATASET.
config/eval_candidate.yaml leaves it out and
references the existing dataset; repeating the block would fail because the
dataset already exists. The metrics block is identical in both:
Run and poll¶
EXECUTE_AI_EVALUATION always takes the action, the run name, and the config
path, including for STATUS, CANCEL, and DELETE:
With SQL the run executes in the session’s current database and schema, so set
the schema first. STATUS moves through states such as
INVOCATION_IN_PROGRESS to COMPLETED.
Every snow sql -f call opens a new session, so a session variable set in one
file is gone in the next. 05 therefore takes the run name from the command
line, with baseline as the default, and only maps the two names this recipe
uses to a config file:
snow sql -f sql/05_results.sql -D run_name=candidate reads the candidate. 05
calls STATUS first and raises an error unless the run is COMPLETED, so the
score queries never read a partial run and the same command serves as the
polling step.
Read the results¶
METRIC_CALLS[0]:explanation holds the judge’s reasoning for each score. Read
it for every low score: it names the fix. In this recipe the baseline declines the
weather question but does not offer anything it can help with, and the
explanation for its 0.33 answer correctness says exactly that.
System metrics score from 0 to 1. The custom metric scores on the 1 to 10 scale its prompt defines, so keep it in its own column.
Improve one thing and compare¶
06 changes only the orchestration instructions (routing rules, a comparison rule, and a scope rule), commits the result, and points CANDIDATE at it. Changing one thing at a time is what lets a score change be attributed to a cause. 07 compares the runs per metric and per question, then applies the gate:
The version name comes from DESCRIBE AGENT (the aliases column) and is
checked against ^VERSION\$[0-9]+$ before it is used in a statement. Callers use
the alias:
See the agent versioning and promotion guide for how aliases, DEFAULT, and rollback behave.
Scheduled runs¶
EXECUTE_AI_EVALUATION can be called from a task, which runs in its own session
under the task owner’s role:
Point a scheduled config at a committed version or at PRODUCTION, and pin the metric versions, so a score change means the agent changed.
Verify¶
STATUSforbaselineandcandidatereturnsCOMPLETED.- 05 returns five rows per metric per run. In two test passes, the baseline
scored 0.87 and 0.93 on
answer_correctness(the weather question scored lowest both times), 1.0 onlogical_consistency, and 9.4 to 9.8 ontool_routing. The candidate scored 1.0 and 1.0 onanswer_correctness. Your numbers can differ, because each run invokes the agent again. - 07 returns
Promoted VERSION$2: answer_correctness <baseline> -> 1(with your scores),DESCRIBE AGENTshowsdefault_version_name=VERSION$2andPRODUCTIONonVERSION$2, and!PRODUCTIONdeclines the weather question and offers marketing help.
Gotchas¶
| Symptom | Cause | Fix |
|---|---|---|
| START fails with “dataset … already exists” | The config still has a dataset block and the dataset was created by an earlier run | Remove the dataset block for repeat runs (as eval_candidate.yaml does), or DROP DATASET first (as 04 does) |
| “Run with name … does not exist” from DELETE or STATUS | The run name was never started, or the START failed | Check the START output; 04 and 06 ignore this error when cleaning up |
| “Failed to create evaluation task … Ensure your role has CREATE TASK ON SCHEMA and EXECUTE TASK ON ACCOUNT” | Evaluation runs are managed by tasks in the current schema | Run as a role with both privileges; 01 grants EXECUTE TASK ON ACCOUNT, and owning the database covers CREATE TASK |
| START fails validating the dataset or agent | The session’s database and schema are not the recipe’s | USE SCHEMA COOKBOOK_AGENT_EVALUATIONS.MARKETING before calling it |
| A custom metric run fails at start | No model key, and the default judge is a legacy model your account never used | Set model explicitly on every custom metric |
| Unpinned system metric runs fail or change scores | Unversioned metrics use the current default judge, which rolls forward on deprecation | Pin version: "v3" (or v2) and keep it the same across runs |
| Scores differ between two runs of the same version | Each run invokes the agent again; orchestration is not deterministic | Run the dataset several times to learn its normal range and gate on that range |
| Custom metric averages look far above 1 | Custom metrics use the scale their prompt defines | Report custom metrics in their own column |
!production returns “Version not found” | Unquoted aliases are stored in uppercase | Call !PRODUCTION |
| 05 fails with “run_name must be baseline or candidate” in a Snowsight worksheet | The <% run_name ... %> placeholder is Snowflake CLI templating, which other clients do not render | Replace the SET agenteval_run value with 'baseline' or 'candidate', or run the file with snow sql -f |
| 05 fails with “Version ‘CANDIDATE’ … does not exist” | 06 has not run yet, so there is no CANDIDATE alias or candidate run | Run sql/06_candidate.sql first |
STATUS shows PARTIALLY_COMPLETED, then COMPLETED a minute later | The docs list PARTIALLY_COMPLETED as a final state, but a run can pass through it while metrics finish | 05 treats it as not done yet; run it again. If it does not change, read STATUS_DETAILS and the warnings query at the end of 05 |
Teardown¶
sql/99_teardown.sql deletes both evaluation runs as the recipe role, then
switches to ACCOUNTADMIN and drops the database (agent, versions, dataset,
stage, tables), the warehouse, and the role. Dropping the role removes its
EXECUTE TASK ON ACCOUNT grant.
Adapt this recipe¶
Keep these, so a customized version keeps the recipe’s security model:
- Least privilege:
ACCOUNTADMINonly for the scripts listed under Privileges; everything else runs as the recipe’s own role. - No grants to
PUBLIC. - Credentials: create keys, tokens, and secrets yourself, in a separate terminal, and read them from environment variables. Never put them in a script or give them to an agent.
- Dynamic SQL, if you add any, uses bind variables or an allowlist of identifiers, never text concatenated from user or model input.
Change these to fit your use case:
- Object names. Rename them in every script, including teardown, then check that teardown still removes everything.
- Roles, personas, and users: map them to your own, each granted no more than the recipe grants.
- Data: point the scripts at your tables instead of the sample data, then run Verify again.
- Models and warehouse sizes.
To have a coding agent adapt it:
Run it with a coding agent¶
Related skills¶
agent-studio(bundled with Cortex Code): its evaluation sub-skills create datasets (including from production monitoring), run evaluations, investigate results, and suggest agent changes. Use it to grow the dataset beyond the five questions here.