Cortex Agent Evaluations

This is how "the verdict" is reached. Cortex Agent evaluations let you test, baseline, and hill-climb on an agent's behavior so you know when it is ready to roll out, scoring not just the final answer but each step of its reasoning.

Status: Cortex Agent evaluations reached general availability on 2026-03-13 (answer correctness, logical consistency, custom metrics). The two tool metrics (tool selection and tool execution accuracy) are in public preview as of 2026-06-11. GA Public Preview

Exhibit C is the deep dive behind the walkthrough's Phase 5: The Verdict. Where the walkthrough shows the commands to run, this exhibit explains the framework, the metrics, and how to iterate.

Sources: Cortex Agent evaluations, AI Observability in Snowflake Cortex.

Exhibit C · Section 1 of 5

Why Evaluate

Cortex Agent evaluations let you test, baseline, and hill-climb on an agent's behavior and performance, so you know when it is ready to roll out. You evaluate against both ground-truth and reference-free metrics, and the agent's activity is traced and monitored so you can see each step on the way to its answer.

The Goal-Plan-Action framework

Instead of judging only the final answer, the metrics follow Snowflake's Goal-Plan-Action (GPA) framework: they evaluate the agent at each stage of its reasoning, so you can pinpoint where it went wrong or was inefficient. The four system metrics trace the loop from the user's goal to the agent's answer.

Goalthe user's question Planwhich tools to call Actionstool calls run Answerthe verdict tool selection Public Preview tool execution Public Preview response answer correctness (actions back to goal) logical consistency spans the whole loop, reference-free
The four system metrics trace the GPA loop. Three need ground truth; logical consistency does not.

Access to run an evaluation

Evaluations compute their metrics with the AI_COMPLETE function using LLM-as-a-judge, so the role that runs an evaluation needs, among others:

Where this sits: this is the deep dive behind the walkthrough's Phase 5. The walkthrough runs the commands; this exhibit explains the framework.
Exhibit C · Section 2 of 5

The Metrics

Four system metrics trace the Goal-Plan-Action loop. Three compare against ground truth; logical consistency is reference-free. All are computed by an LLM judge (the AI_COMPLETE function).

System metrics

MetricGPA stageGround truth?What it measuresStatus
answer_correctnessactions → goalYes (ground_truth_output)How closely the agent's streamed reply matches the expected answerGA
logical_consistencywhole loopNo (reference-free)Consistency across the agent's instructions, planning, and tool callsGA
tool_selection_accuracygoal → planYes (ground_truth_invocations)Whether the agent invoked the tools you expected (order-independent)Preview
tool_execution_accuracyplan → actionsYes (ground_truth_invocations)Whether each tool call had appropriate input and returned acceptable outputPreview
Start reference-free, add tools later. Begin with answer_correctness + logical_consistency. Add the tool metrics once the agent is stable and you can describe the tool calls you expect.

Custom metrics (LLM-as-a-judge)

Beyond the system metrics, you can define your own. A custom metric supplies a prompt and a scoring range that are passed to the LLM judge along with the run trace. Custom metrics can be ground-truth-based or reference-free, and their prompts can reference trace data with placeholders like {{input}}, {{output}}, {{ground_truth}}, and {{tool_info}}. Use them for domain-specific checks the streamed reply doesn't expose, for example which tables the agent touched.

metrics:
  - "answer_correctness"
  - "logical_consistency"
  - name: "relevance"
    score_ranges:
      min_score: [1, 3]      # low
      median_score: [4, 6]   # medium
      max_score: [7, 10]     # high
    prompt: |
      Rate 1-10 how relevant the response is to the user's query.
      Compare {{output}} against {{ground_truth}} ...

The judge normalizes each score to the range 0.0 to 1.0. Evaluations currently run the judge on claude-4-sonnet using cross-region inference.

Exhibit C · Section 3 of 5

The Dataset

An evaluation dataset is a table with two columns: the input query, and a ground-truth VARIANT the judges compare against. What you put in the VARIANT depends on which metrics you enable.

ColumnTypeHolds
input_queryVARCHARThe user query to evaluate
ground_truthVARIANTA JSON object describing the expected behavior

Two keys, two tracks

One VARIANT can carry keys for several metrics at once, so a single dataset can drive answer correctness, the tool metrics, and custom metrics in the same run. Start with AC-track rows; add tool-track rows once the agent is stable.

The no-tool guardrail: set "ground_truth_invocations": [] (an empty array) to verify the agent correctly abstains from calling any tool on an out-of-scope question.

Insert a row with PARSE_JSON

CREATE OR REPLACE TABLE agent_evaluation_data (
  input_query VARCHAR,
  ground_truth VARIANT
);

INSERT INTO agent_evaluation_data
  SELECT
    'What was Q1 2025 revenue by product category, and what does our return policy say for electronics?',
    PARSE_JSON('
      {
        "ground_truth_output": "Q1 2025 revenue was ~1.2M Services, 1.4M Hardware, 1.1M Subscriptions USD. Electronics: 30-day returns, 1-year warranty.",
        "ground_truth_invocations": [
          { "tool_name": "finance_analyst",
            "tool_input": "Q1 2025 revenue by product category",
            "tool_output": "SQL aggregating revenue by category for Jan-Mar 2025, ~3 rows near 1.2M/1.4M/1.1M." },
          { "tool_name": "product_docs_search",
            "tool_input": "return policy and warranty for electronics",
            "tool_output": "Policy page mentioning 30-day returns and a 1-year warranty." }
        ]
      }
    ');
Use PARSE_JSON, not OBJECT_CONSTRUCT. OBJECT_CONSTRUCT / ARRAY_CONSTRUCT return OBJECT and ARRAY, not VARIANT. Wrap the JSON in PARSE_JSON (or TO_VARIANT) to guarantee the column type. This mirrors the template's rule to keep PARSE_JSON in eval_dataset.sql.

Writing good ground truth

Because ground_truth_output is fed to an LLM prompt, treat it as a plain-language rubric built from literal, verifiable values:

Exhibit C · Section 4 of 5

Run & Inspect

Drive evaluations from SQL, from a YAML config, or from the Snowsight Evaluations tab. Under the hood it is one function to run and a few functions to read results back.

Run with EXECUTE_AI_EVALUATION

One function handles the lifecycle via a verb: 'START', 'STATUS', 'CANCEL', or 'DELETE'. It takes the run name and a stage path to the config YAML.

-- start a run named run-1 from a config on a stage
CALL EXECUTE_AI_EVALUATION(
  'START',
  OBJECT_CONSTRUCT('run_name', 'run-1'),
  '@eval_db.eval_schema.metrics/agent_evaluation_config.yaml'
);

-- check progress. STATUS / CANCEL / DELETE need the object, and take no stage path.
CALL EXECUTE_AI_EVALUATION('STATUS', OBJECT_CONSTRUCT(
  'run_name',    'run-1',
  'object_name', 'eval_db.eval_schema.evaluated_agent',
  'object_type', 'CORTEX AGENT'));
Schedule it. Because it is a function call, you can wrap EXECUTE_AI_EVALUATION in a Snowflake Task to run or check evaluations on a cadence, exactly as the walkthrough's Ship & Automate step does.

The config YAML

The config has three top-level keys: an optional dataset (to build a dataset from a table), evaluation (which agent + which dataset), and metrics (built-in strings plus any custom definitions).

evaluation:
  agent_params:
    agent_name: "eval_db.eval_schema.evaluated_agent"
    agent_type: "CORTEX AGENT"
  source_metadata:
    type: "dataset"
    dataset_name: "EVALUATION_INPUT"
metrics:
  - "answer_correctness"
  - "logical_consistency"
Required  agent_name must be fully qualified as database.schema.object. A bare name is rejected at start. See Traps.
Repeat-run gotcha: if the YAML keeps a dataset: block, Snowflake tries to create the dataset on every run and can fail with "already exists". For repeated runs on the same dataset, remove the dataset: block and keep only evaluation: + metrics:. This is the same fix the template calls out for its eval config.

Read results back

Function (SNOWFLAKE.LOCAL)Returns
GET_AI_EVALUATION_DATA(db, schema, agent, 'CORTEX AGENT', run)Full per-record evaluation details and scores for a run
GET_AI_RECORD_TRACE(db, schema, agent, 'CORTEX AGENT', record_id)The full trace for a single record, to see where the agent went wrong
GET_AI_OBSERVABILITY_LOGS(db, schema, agent, 'CORTEX AGENT')Warnings and errors from a run (filter by severity + run name)
SELECT * FROM TABLE(SNOWFLAKE.LOCAL.GET_AI_EVALUATION_DATA(
  'eval_db', 'eval_schema', 'evaluated_agent', 'CORTEX AGENT', 'run-1'));

Or use the Snowsight Evaluations tab

On an agent's Evaluations tab you get metric trend cards (current average, change vs previous run, and a trend chart), a runs listing, and the ability to compare up to three runs side by side. Opening a record shows three panes: Evaluation results, Thread details, and Trace details.

Exhibit C · Section 5 of 5

Iterate & Operate

An evaluation is only useful if it drives the next improvement. The loop: inspect the trace, diagnose the root cause, fix the right layer, and re-run.

Interpret and iterate

Snowsight buckets each metric's records into high (80% or more), medium (30% or more), and failed. Read the pattern, then fix the right layer:

Score patternLikely causeFix
Low answer correctnessWeak ground-truth rubric, or wrong tool selectedImprove the rubric wording; check tool descriptions (Exhibit B)
Low logical consistencyAgent reasoning contradicts itself or its instructionsTighten orchestration; reduce instruction length
Low tool accuracyTool descriptions too vagueSharpen "when to use" / "when NOT to use"
High consistency, low correctnessAgent is honest about limits but not finding the answerImprove semantic view coverage; add verified queries (Exhibit A)

Seed and grow the dataset

Cost

An evaluation runs the agent once per query and then runs LLM judges (the AI_COMPLETE function) to score each metric. You are charged for the agent runs, the judge inference, the warehouse time for the managing tasks and metric queries, and storage for datasets and results.

Known limits to plan around

Not supported as evaluated tools: MCP server tools and the code-execution tool. An agent that relies on the code-execution tool fails to run; MCP tools simply are not exercised.
Skills: documented as unsupported, observed to work. The documentation lists skills alongside the limits above. In testing on 2026-08-05, a ten-row evaluation against an agent carrying a stage-based SKILL.md completed normally, with ServerSkillTool_triage spans present in the traces of five rows and all six metrics scoring every row. Note that the skill was not consulted on rows that took the verified-query fast path. Treat skills as usable but verify against the current documentation before depending on it, since this is preview surface and may differ by account or move.
Ship gate: target ≥95% answer correctness before rollout, per the walkthrough's Closing Arguments. Fall short, fix the highest-leverage layer, and re-run.
Exhibit C · Section 6 of 7

Traps That Do Not Error

Five configurations the platform accepts without complaint. Each one returns a completed run and a plausible scorecard, so the only way to know your configuration landed as intended is to check. Each trap below comes with its check.

Why these matter more than failures. A misconfigured evaluation that throws an error costs you ten minutes. One that returns numbers you trust can send you optimizing the wrong layer for as long as you believe it.

1. The four built-in metric names are reserved

answer_correctness, logical_consistency, tool_selection_accuracy, and tool_execution_accuracy are built-in. If you define a custom metric using one of those names, the built-in takes precedence and your prompt is not used. No error is raised, and the resulting scores look entirely reasonable.

Name collides with a built-in

  • Config: a custom metric named tool_selection_accuracy with your own prompt
  • What runs: the built-in, not your prompt
  • What you see: a normal score, no warning
  • Cost: every workflow rule your rubric encoded is unmeasured

Distinct name, both metrics run

  • Config: custom metric named workflow_tool_routing
  • Plus: list "tool_selection_accuracy" separately as a built-in string
  • Result: the built-in does the generic set comparison; yours checks the rules it cannot see
  • Verify: metric_type reads custom

The check, on any eval span:

-- 'custom' = your prompt ran.  'system' = a built-in ran in its place.
RECORD_ATTRIBUTES:"ai.observability.eval.metric_type"::STRING
Check  Confirm metric_type the first time you add any custom metric, before reading its score.

2. agent_name must be fully qualified

A bare object name is rejected at start, with an error that names the required shape:

Object name WORKFLOW_AGENT format should be 'database.schema.object'

Use MY_DB.MY_SCHEMA.WORKFLOW_AGENT in every config, including single-dimension ones.

3. STATUS needs the object, not just the run

START takes the run name and a stage path. STATUS, CANCEL, and DELETE instead need object_name and object_type alongside run_name, and take no stage path.

CALL EXECUTE_AI_EVALUATION('STATUS', OBJECT_CONSTRUCT(
  'run_name',    'run-1',
  'object_name', 'MY_DB.MY_SCHEMA.WORKFLOW_AGENT',  -- fully qualified
  'object_type', 'CORTEX AGENT'));

All of them also need a session context. Without USE DATABASE, USE SCHEMA, and USE WAREHOUSE you get The DB is not set for the current session.

4. One dataset per source table

Section 4 notes that a dataset: block left in the YAML fails on re-run. The reason is worth knowing, because it bounds the workaround: the dataset version name is a fixed constant.

Dataset version SYSTEM_AI_OBS_CORTEX_AGENT_DATASET_VERSION_DO_NOT_DELETE already exists

Because that name never varies, a second dataset built on the same table collides even under a different dataset_name. In practice there is one dataset per source table. To re-snapshot after changing the rows, either delete the existing dataset or point at a different table.

5. Match the status string exactly

COMPLETED is a substring of INVOCATION_PARTIALLY_COMPLETED. A polling loop that tests for a substring will report success on a run that stalled partway through invocation. Compare exactly.

The progression observed across runs:

INVOCATION_IN_PROGRESS → COMPUTATION_IN_PROGRESS → PARTIALLY_COMPLETED → COMPLETED

A run can also park at INVOCATION_PARTIALLY_COMPLETED with rows planned and no errored spans, and not advance. CANCEL and re-run, but check for errored spans first rather than assuming the agent failed.

The pattern behind all five: the evaluation framework validates what it must and accepts the rest. Treat a first run as unverified until you have confirmed the metric types, the qualified names, and the status string.
Exhibit C · Section 7 of 7

Reading the Trace

Section 4 reads results through the GET_AI_* functions. This section is the layer underneath: which spans a Cortex Agent turn actually emits, why one tool can appear as two spans or none at all, and how to pull scores directly for cross-run work.

Span taxonomy

Observed in SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS across a ten-row run.

Span nameWhat it is
AgentRoot span for the turn
GuardrailsSafety check. Present on some turns, not all.
ReasoningAgentStepPlanning-NPlanning step N. Only these carry tool_execution.* attributes.
ReasoningAgentStepResponseGeneration-NFinal answer synthesis
ServerSkillTool_<skill>A skill was read, for example ServerSkillTool_triage
SemanticContextTool_<tool>Cortex Analyst generating SQL
SystemExecuteSQLTool_system_execute_sqlThe platform executing SQL
SqlExecution_SystemSQLThe execution itself, a child of the above
ToolCall-<tool>A custom or generic tool, for example ToolCall-summarize_issue
CortexSearchService_<service>Cortex Search
CortexChartToolImpl-data_to_chartChart generation
CortexAgentGroundTruthOne per evaluation row
Not all of it is your agent. This table is account-wide. CodingAgent.Step-N spans are Cortex Code, a different product writing to the same table. Filter by run name or agent name before reading anything.

Analyst is two spans, not one

SemanticContextTool_Analyst only generates SQL. It never runs it. Execution is always a separate SystemExecuteSQLTool_system_execute_sql plus SqlExecution_SystemSQL pair.

Two consequences for ground truth: one ground_truth_invocations entry for an Analyst tool maps onto a span pair, and the presence of system_execute_sql does not by itself tell you which path ran, because it appears on both.

The verified-query fast path

When a question matches an AI_VERIFIED_QUERIES entry on the semantic view, Analyst does not load the semantic model and emits no Analyst span. It runs the verified SQL directly, and the trace shows only the SQL execution, carrying verified_query_used: true.

Span presence measured across all ten rows of one run:

QuestionAnalystskillexec SQL
How many orders were placed in total?yesyesyes
Tell me about the open defectsyesyesyes
Which product line generated the most revenue?yesyesyes
What is total revenue by region?nonoyes
How did revenue trend by month?nonoyes
How many open issues are there by severity?nonoyes
Summarize issue ISS-004noyesno
What is going on with ISS-006?noyesno
Summarize the issue (no id given)nonono
What was total revenue in 2025? (out of range)nonono

The three rows with no Analyst span are exactly the three verified queries defined on the semantic view. A one-to-one correlation, so the verified query is the cause.

What a span-hunting rubric concludes

  • Told to: find a span named Analyst
  • Finds: system_execute_sql and a chart tool
  • Concludes: "the expected tool was never called"
  • Scores: a correct answer at 0.0. Measured: 0.6 on a routing metric, then 0.0 on an arguments metric.

What the rubric needs to say

  • Treat as an Analyst call: a span named Analyst, or SQL against the semantic view, or any execution with verified_query_used: true
  • Then grade the SQL's measure, grouping, and filter
  • Never deduct for the absence of a literally-named Analyst span
  • Note: the built-in tool metrics handle this already, since they also match on the semantic view name
The skill is skipped too. On all three verified-query rows there was no ServerSkillTool_* span either, so a workflow encoded only in a skill file was never consulted for those questions. The answers were still well formed, but that came from the agent's instructions rather than the skill. A rule that must always hold belongs in instructions.
This is a trade-off, not a defect. Verified queries buy latency and SQL determinism at the cost of the reasoning layer that enforces your workflow. Decide deliberately which questions want which behavior.

Pulling scores directly

GET_AI_EVALUATION_DATA in Section 4 is the right tool for one run. For trend work across many runs, query the events table. One structural detail matters: the score and its metadata sit on different records that share an eval_root_id, so each side has to be collapsed before joining or the explanation comes back NULL.

WITH meta AS (
  SELECT RECORD_ATTRIBUTES:"ai.observability.eval.eval_root_id"::STRING AS rid,
         MAX(RECORD_ATTRIBUTES:"ai.observability.eval.metric_name"::STRING) AS metric,
         MAX(RECORD_ATTRIBUTES:"ai.observability.eval.metric_type"::STRING) AS metric_type,
         MAX(RECORD_ATTRIBUTES:"ai.observability.eval.explanation"::STRING)  AS explanation
  FROM SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS
  WHERE RECORD_ATTRIBUTES:"snow.ai.observability.run.name"::STRING = 'run-1'
    AND RECORD_ATTRIBUTES:"ai.observability.eval.metric_name" IS NOT NULL
  GROUP BY 1),
scores AS (
  SELECT RECORD_ATTRIBUTES:"ai.observability.eval.eval_root_id"::STRING AS rid,
         MAX(RECORD_ATTRIBUTES:"ai.observability.eval_root.score"::FLOAT) AS score
  FROM SNOWFLAKE.LOCAL.AI_OBSERVABILITY_EVENTS
  WHERE RECORD_ATTRIBUTES:"snow.ai.observability.run.name"::STRING = 'run-1'
    AND RECORD_ATTRIBUTES:"ai.observability.eval_root.score" IS NOT NULL
  GROUP BY 1)
SELECT m.metric, m.metric_type, ROUND(AVG(s.score), 3) AS avg_score,
       MIN(s.score) AS worst
FROM meta m JOIN scores s USING (rid)
GROUP BY 1, 2 ORDER BY 2 DESC, 1;
Check  Read the explanation on the lowest-scoring row before acting on any score. Every false negative described in this exhibit was diagnosed from explanation text, not from the numbers.

Judges are not deterministic

Expect small movement between identical runs. answer_correctness moved from 0.967 to 0.934 across two runs with the same dataset, the same agent, and no rubric change. Compare distributions and minimums across runs rather than single scores, and do not tune against noise.
Learn more The template is a dbt project implementing everything in Sections 6 and 7: six metrics, a stage-based skill, and the rubric wording that avoids the verified-query false negative.