@oxy-hq/cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -7
- package/dist/main.mjs +4914 -3760
- package/dist/main.mjs.map +1 -1
- package/dist/{mcp-xxWL3_dz.mjs → mcp-Bdp0rX0l.mjs} +2 -2
- package/dist/{mcp-xxWL3_dz.mjs.map → mcp-Bdp0rX0l.mjs.map} +1 -1
- package/package.json +5 -5
- package/skills/oxy-agents/SKILL.md +318 -0
- package/skills/oxy-agents/references/quick-reference.md +222 -0
- package/skills/oxy-agents/templates/agentic-template.yml +141 -0
- package/skills/oxy-airway-pipelines/SKILL.md +223 -316
- package/skills/oxy-automations/SKILL.md +61 -15
- package/skills/oxy-cli/SKILL.md +32 -5
- package/skills/oxy-custom-apps/SKILL.md +193 -291
- package/skills/oxy-custom-apps/references/minimal-starter.md +63 -0
- package/skills/oxy-custom-apps/references/performance-and-usage.md +105 -0
- package/skills/oxy-custom-apps/references/reading-data.md +106 -0
- package/skills/oxy-custom-apps/references/sandbox-agent-loop.md +267 -0
- package/skills/oxy-custom-apps/references/verifying-in-the-browser.md +65 -0
- package/skills/oxy-data-apps/SKILL.md +362 -0
- package/skills/oxy-data-apps/references/examples.md +263 -0
- package/skills/oxy-data-apps/references/quick-reference.md +171 -0
- package/skills/oxy-data-apps/templates/dashboard-with-controls.app.yml +128 -0
- package/skills/oxy-data-apps/templates/simple-dashboard.app.yml +102 -0
- package/skills/oxy-data-architecture/SKILL.md +137 -152
- package/skills/oxy-evals/SKILL.md +362 -0
- package/skills/oxy-evals/references/diagnosis.md +243 -0
- package/skills/oxy-evals/references/results-json.md +145 -0
- package/skills/oxy-evals/references/writing-expected.md +112 -0
- package/skills/oxy-semantic-modeling/SKILL.md +239 -59
- package/skills/oxy-semantic-modeling/references/warehouse-casts.md +57 -0
- package/skills/oxy-semantic-modeling/templates/topic-template.yml +38 -0
- package/skills/oxy-semantic-modeling/templates/view-template.yml +119 -0
- package/template/.github/workflows/publish.yaml +15 -13
- package/template/.nvmrc +1 -1
- package/template/.oxyc-managed +5 -5
- package/template/README.md +21 -10
- package/template/package.json +3 -2
- package/template/pnpm-workspace.yaml +28 -17
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oxy-evals
|
|
3
|
+
description: >-
|
|
4
|
+
Use when writing, running, or acting on evals for an Oxy agent — `.test.yml` files that run an
|
|
5
|
+
`.agentic.yml` agent and score its answers with an LLM judge — and when the agent's answers are
|
|
6
|
+
wrong. Triggers: "add a test", "write a test case", "write evals", "test this agent", "evaluate
|
|
7
|
+
my agent", "bootstrap an eval suite", "fill in expected answers", "run the tests"; and the
|
|
8
|
+
repair side: "tests are failing", "this test is flaky", "the agent's answer is wrong", "the
|
|
9
|
+
answer should be X but the agent says Y", "debug this eval", "make this test pass", or pasted
|
|
10
|
+
`oxy test --output-json` output or a prompt/response pair to diagnose. Drafts expected answers
|
|
11
|
+
from observed runs, then diagnoses failures and repairs the agent's semantic model, context and
|
|
12
|
+
instructions — never the test.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Oxy evals: draft, run, diagnose, repair
|
|
16
|
+
|
|
17
|
+
An Oxy eval is a `.test.yml` file: a list of prompts, each with the answer a correct agent
|
|
18
|
+
gives. `oxy test` runs the target agent on every prompt several times and asks an LLM judge
|
|
19
|
+
whether each answer carries the expected facts.
|
|
20
|
+
|
|
21
|
+
Drafting the tests and repairing the agent are one loop:
|
|
22
|
+
|
|
23
|
+
1. **Draft** cases and their `expected` answers from evidence.
|
|
24
|
+
2. **Run** them and read the results.
|
|
25
|
+
3. **Diagnose** each failure and **repair the agent** until it passes reliably.
|
|
26
|
+
|
|
27
|
+
**The hard rule: the expected answer is the specification. Never weaken a test to make it
|
|
28
|
+
pass.** Don't loosen `expected` to match what the agent said, delete or skip a failing case, or
|
|
29
|
+
rewrite a prompt to dodge the failure. If a test itself is wrong (a bad expected value, an
|
|
30
|
+
ambiguous prompt), say so, show the evidence, and let the user decide. That's a different
|
|
31
|
+
change from a repair, and you don't make it silently.
|
|
32
|
+
|
|
33
|
+
Reference docs (field tables, CI usage):
|
|
34
|
+
<https://www.oxygen-hq.com/docs/guide/build/test-evaluate/testing>
|
|
35
|
+
|
|
36
|
+
## What can be tested
|
|
37
|
+
|
|
38
|
+
- **Only `.agentic.yml` agents.** `oxy test` rejects any other target, including classic
|
|
39
|
+
`.agent.yml` agents (that runtime was removed). Inline `tests:` blocks inside agent or
|
|
40
|
+
automation files are gone too. A `.test.yml` file is the only entry point.
|
|
41
|
+
- **Only the final answer text is judged.** The judge sees the question, `expected`, and the
|
|
42
|
+
agent's answer. It doesn't see the SQL, the tool calls, or the rows.
|
|
43
|
+
- **`tool:` on a case is accepted but not enforced.** It has no effect on pass/fail. Don't
|
|
44
|
+
rely on it to assert that the agent used a particular path.
|
|
45
|
+
|
|
46
|
+
## The `.test.yml` format
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
name: Sales performance # optional display name
|
|
50
|
+
target: agents/sales.agentic.yml # path from the project root; see "target" below
|
|
51
|
+
|
|
52
|
+
settings:
|
|
53
|
+
runs: 3 # times each case runs (default 3)
|
|
54
|
+
concurrency: 5 # agent runs in flight at once (default 5)
|
|
55
|
+
judge_model: judge # a model `name:` from config.yml (default: first model there)
|
|
56
|
+
|
|
57
|
+
cases:
|
|
58
|
+
- name: total-revenue-2025 # optional, but always add it: it makes --case usable
|
|
59
|
+
prompt: What was total revenue across all stores in 2025?
|
|
60
|
+
expected: Total revenue across all stores in 2025 was approximately $6.7 billion.
|
|
61
|
+
tags: [revenue, critical]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Rules the parser enforces:
|
|
65
|
+
|
|
66
|
+
- **Unknown keys are rejected at every level** (file, `settings`, each case). A typo such as
|
|
67
|
+
`judge-model:` or `expect:` makes the file fail to parse. Run with an explicit file and you
|
|
68
|
+
get `Failed to deserialize test config …`. **Run `oxy test` with no file and a broken file
|
|
69
|
+
is skipped silently.** It just isn't in the run, with no error. After editing a test file,
|
|
70
|
+
run it by path once.
|
|
71
|
+
- **`prompt` and `expected` are required strings.** Everything else is optional.
|
|
72
|
+
- **`target`** is resolved from the project root (the directory holding `config.yml`). If you
|
|
73
|
+
omit it, it's inferred from the filename, in the same directory: `sales.agentic.test.yml`
|
|
74
|
+
targets `sales.agentic.yml` beside it. Tests kept in a separate `tests/` folder must set
|
|
75
|
+
`target` explicitly.
|
|
76
|
+
- **`judge_model` names a model by its `name:` under `models:` in `config.yml`.** It isn't a
|
|
77
|
+
vendor model ID. If you leave it out, the judge is the **first** model listed in
|
|
78
|
+
`config.yml`. An unknown name fails the run before any judging (`LLM model '<name>' could not
|
|
79
|
+
be resolved from project config`). Google/Gemini models can't be used. Prefer a judge from a
|
|
80
|
+
different model family than the agent's, to avoid self-preference bias.
|
|
81
|
+
|
|
82
|
+
Naming and placement: `<agent-stem>.<topic>.test.yml` (for example
|
|
83
|
+
`sales.performance.test.yml`) in the folder the project already uses for tests. Discovery
|
|
84
|
+
(`oxy test` with no file) finds every `*.test.yml` in the project, except under hidden
|
|
85
|
+
(dot-prefixed) directories and `target/`, `node_modules/`, `dist/` or `build/`.
|
|
86
|
+
|
|
87
|
+
## When to write evals
|
|
88
|
+
|
|
89
|
+
- Before changing an agent, a semantic model, or instructions, so there's a baseline to
|
|
90
|
+
compare against.
|
|
91
|
+
- When a user reports a wrong answer. Turn the prompt into a case first, then repair.
|
|
92
|
+
- For the questions the business actually asks, and for the edges: a question the agent must
|
|
93
|
+
decline, a ranking, a trend, a filter on a rare value.
|
|
94
|
+
|
|
95
|
+
## Drafting cases
|
|
96
|
+
|
|
97
|
+
### Prompts
|
|
98
|
+
|
|
99
|
+
- **Use absolute periods.** The agent resolves "last month" or "this week" against *today*
|
|
100
|
+
in its `timezone:` (UTC when unset). The expected answer for a relative prompt goes stale
|
|
101
|
+
the next month. Write "November 2025", not "last month".
|
|
102
|
+
- **Make prompts specific enough to answer without a follow-up question.** Tests run
|
|
103
|
+
headless. If the agent stops to ask the user something, the run ends as an **error**
|
|
104
|
+
(`agentic pipeline asked a clarifying question: …`), which counts as a FAIL. An ambiguous
|
|
105
|
+
prompt shows up as a run error, not as a wrong answer.
|
|
106
|
+
- **Test one thing per case.** "Revenue by region and the top product in each" is two cases.
|
|
107
|
+
- **Name every case** (`name:`), with a short stable slug.
|
|
108
|
+
|
|
109
|
+
### Scaffolding with placeholders
|
|
110
|
+
|
|
111
|
+
When the right answer isn't known yet, scaffold the cases with a placeholder and fill it in
|
|
112
|
+
from evidence:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
expected: "DRAFT: to be synthesized from repeated runs."
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
A placeholder case's verdict means nothing, because the judge has no facts to check. Never
|
|
119
|
+
report a suite as passing while any `DRAFT:` remains in it.
|
|
120
|
+
|
|
121
|
+
### Writing `expected`
|
|
122
|
+
|
|
123
|
+
`expected` is what the judge checks the answer against. The judge's instruction is to PASS an
|
|
124
|
+
answer that contains the **core factual content** of `expected`. It ignores formatting,
|
|
125
|
+
phrasing, and level of detail, and it allows extra correct information. So write the facts
|
|
126
|
+
that must be present, and nothing that may legitimately vary.
|
|
127
|
+
|
|
128
|
+
- State the facts in plain business language, 1–4 sentences. Don't start with "The response
|
|
129
|
+
should…". `expected` is an answer, not a grading instruction.
|
|
130
|
+
- **Approximate numbers** (`~`, "about", "roughly") unless an exact value is reliably stable.
|
|
131
|
+
The judge compares facts, and a cent-level total invites false failures.
|
|
132
|
+
- **Rankings:** name the criterion once, then the top 2–3 entities with anchor values. Don't
|
|
133
|
+
list every row.
|
|
134
|
+
- **Trends:** the direction, the current value, and one or two notable points.
|
|
135
|
+
- **Questions the agent can't answer:** state why, and what a correct answer offers instead.
|
|
136
|
+
- No internal field, view or schema names unless the user would say them.
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
Good: Total revenue across all locations in 2025 is approximately $6.7 billion.
|
|
140
|
+
|
|
141
|
+
Good: The top 5 items by first-week engagements in February 2026: #1 had ~25K engagements
|
|
142
|
+
and #5 had ~10K. Each entry gives the title, channel and engagement count.
|
|
143
|
+
|
|
144
|
+
Good: Regional plan attainment can't be calculated because targets are set at the company
|
|
145
|
+
level only. A correct answer says so and offers company-level attainment instead.
|
|
146
|
+
|
|
147
|
+
Bad: The response should explain total revenue across all stores. (grading instruction)
|
|
148
|
+
Bad: Total revenue is $6,737,218,004.32. (false precision)
|
|
149
|
+
Bad: Sum of store__weekly_sales across all store__store_id values. (schema names)
|
|
150
|
+
Bad: Store A $73.3M, Store B $275M, Store C $402K, … (exhaustive list)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
More worked examples, and the evidence rules for each kind of question:
|
|
154
|
+
[references/writing-expected.md](references/writing-expected.md).
|
|
155
|
+
|
|
156
|
+
## Running
|
|
157
|
+
|
|
158
|
+
Run from the project root (the directory holding `config.yml`). The file argument is resolved
|
|
159
|
+
from your current directory, and `target` from the project root.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
oxy test tests/sales.performance.test.yml # one file
|
|
163
|
+
oxy test tests/sales.performance.test.yml --case total-revenue-2025 --output-json
|
|
164
|
+
oxy test tests/sales.performance.test.yml --case 0 --output-json # by 0-based index
|
|
165
|
+
oxy test tests/sales.performance.test.yml --tag critical --output-json # by tag
|
|
166
|
+
oxy test # every *.test.yml
|
|
167
|
+
oxy test --min-accuracy 0.8 --quiet # CI gate, exit 1 below
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- `--case` takes a 0-based index, a case `name`, or the exact prompt. It requires a file. With
|
|
171
|
+
`--tag` as well, a case must match both. If two cases share a name, the first one runs.
|
|
172
|
+
- `--tag` takes one tag.
|
|
173
|
+
- `--output-json` writes the full results next to the test file, replacing `.yml` with
|
|
174
|
+
`.results.json` (`tests/sales.performance.test.results.json`). With no file argument it
|
|
175
|
+
writes `test-results.json` in the current directory. `--format json` prints the same JSON
|
|
176
|
+
to stdout instead of the pretty report.
|
|
177
|
+
- `-v` adds each failing run's actual output to the pretty report. `-q` shows only the
|
|
178
|
+
summary.
|
|
179
|
+
- `--min-accuracy` compares **per-file** scores. `--threshold-mode average` (the default)
|
|
180
|
+
checks the mean across files, and `all` checks every file. It doesn't check individual
|
|
181
|
+
cases.
|
|
182
|
+
- **Cost:** every case runs `runs` times through the full agent, plus one judge call per run,
|
|
183
|
+
with `concurrency` runs against the warehouse at once. When you iterate, run one case.
|
|
184
|
+
|
|
185
|
+
## Reading results
|
|
186
|
+
|
|
187
|
+
The pretty report gives one line per case:
|
|
188
|
+
|
|
189
|
+
| Status | Meaning |
|
|
190
|
+
| --- | --- |
|
|
191
|
+
| `PASS (3/3)` | every run passed |
|
|
192
|
+
| `FLKY (1/3)` | some runs passed. The answer is unstable, which is a real defect |
|
|
193
|
+
| `FAIL (0/3)` | no run passed |
|
|
194
|
+
|
|
195
|
+
The file's **Correctness** score is the mean over every case × run. Each run scores 1 (PASS)
|
|
196
|
+
or 0 (FAIL). There's no partial credit.
|
|
197
|
+
|
|
198
|
+
In the JSON, each run is a record with `prompt`, `expected`, `actual_output`, `cot` (the
|
|
199
|
+
judge's full response), `choice` (`PASS`/`FAIL`) and `score`. The JSON doesn't include the
|
|
200
|
+
SQL or the tool calls. Three things to check first:
|
|
201
|
+
|
|
202
|
+
- **`actual_output` starting with `[ERROR]`** is a run that never produced an answer: a
|
|
203
|
+
clarifying question, `max iterations exceeded`, a connector failure, a timeout. Its error
|
|
204
|
+
text is also in the top-level `errors` array. Errored runs are appended at the **end** of
|
|
205
|
+
`records`, not next to their case, so group records by `prompt`.
|
|
206
|
+
- **An answer with no numbers, or rows whose metric values are all empty,** is a failed
|
|
207
|
+
attempt, even if the judge passed it.
|
|
208
|
+
- **A `cot` that argues PASS while `choice` is `FAIL`** is usually a judge parse problem, not
|
|
209
|
+
an agent bug. See the judge category in the diagnosis reference.
|
|
210
|
+
|
|
211
|
+
The full schema, `jq` recipes, and how to see what the agent actually queried:
|
|
212
|
+
[references/results-json.md](references/results-json.md).
|
|
213
|
+
|
|
214
|
+
## Filling expected answers from evidence
|
|
215
|
+
|
|
216
|
+
1. Run the case, or the whole file if several placeholders need filling, with
|
|
217
|
+
`--output-json`. Re-run if a run failed on a transient error. Up to three runs in total is
|
|
218
|
+
reasonable.
|
|
219
|
+
2. For each case, compare `actual_output` across every run and classify it:
|
|
220
|
+
|
|
221
|
+
| Class | Evidence | What to write |
|
|
222
|
+
| --- | --- | --- |
|
|
223
|
+
| `stable` | 2+ runs agree on the business facts | a full expected, numbers approximated |
|
|
224
|
+
| `stable_but_partial` | ranking or direction agrees, values vary | the ranking plus 2–3 anchors |
|
|
225
|
+
| `flaky` | runs disagree materially | only the facts that agree. Flag the rest for repair |
|
|
226
|
+
| `ambiguous` | runs answer different readings of the prompt | propose a sharper prompt to the user |
|
|
227
|
+
| `cannot_answer_as_asked` | runs consistently decline, with a valid reason | encode the reason and the alternative offered |
|
|
228
|
+
| `likely_unsupported` | no run finds the data | leave a narrowed `DRAFT:` note. This is a gap to report |
|
|
229
|
+
|
|
230
|
+
3. **Check every number you're about to write down.** An observed answer isn't automatically a
|
|
231
|
+
correct one. If you can, verify it independently (for example, run the agent's SQL or a
|
|
232
|
+
simple query yourself). **Never encode a clearly wrong answer as `expected` just because
|
|
233
|
+
it was stable.** A consistently wrong agent is a repair, not a spec.
|
|
234
|
+
4. Replace the placeholders, turn relative periods into absolute ones, and report:
|
|
235
|
+
|
|
236
|
+
```text
|
|
237
|
+
## Eval drafting summary
|
|
238
|
+
File: tests/sales.performance.test.yml Cases: 9 Drafted: 6 Partial: 2 Flaky: 1
|
|
239
|
+
| Case | Class | Notes |
|
|
240
|
+
| total-revenue-2025 | stable | 3/3 runs agree, ~$6.7B |
|
|
241
|
+
| top-items-feb-2026 | stable_but_partial | ranking stable; values vary |
|
|
242
|
+
Follow-up: <cases to repair, prompts to sharpen, data gaps>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Diagnose and repair
|
|
246
|
+
|
|
247
|
+
When a case fails or is flaky, the agent is the thing under repair. The test stays fixed,
|
|
248
|
+
apart from mechanical edits such as adding a missing `name:`.
|
|
249
|
+
|
|
250
|
+
### 1. Reproduce
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
oxy test <file> --case <name> --output-json
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Read every run of that case: `expected`, `actual_output`, `cot`, `choice`.
|
|
257
|
+
|
|
258
|
+
### 2. See what the agent did
|
|
259
|
+
|
|
260
|
+
The results JSON has only the final answer. To see the path, run the same prompt on its own
|
|
261
|
+
and read the events:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
oxy agentic run <path/to/agent.agentic.yml> -q "<the case's prompt>" --json
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
This streams the run's events as JSON lines and needs `OXY_DATABASE_URL`. Look at
|
|
268
|
+
`query_executed`: its `query`, its `rows`, and its `source`. `source` is `semantic` (compiled
|
|
269
|
+
by the semantic model), `llm` (SQL the model wrote), `verified_sql` (a Verified Query), or
|
|
270
|
+
`vendor`. Also look at `analytics_validation_failed`, the checks that bounced the agent. If
|
|
271
|
+
the project exports traces to Oxy's observability, the run's trace shows the same steps.
|
|
272
|
+
|
|
273
|
+
### 3. Classify the root cause before editing anything
|
|
274
|
+
|
|
275
|
+
| Category | Typical evidence | Fix lives in |
|
|
276
|
+
| --- | --- | --- |
|
|
277
|
+
| A. Semantic model gap: a grouping or dimension is missing | many queries, raw rows, the agent aggregates in prose | a dimension on the view |
|
|
278
|
+
| B. Missing or unfindable measure | `source: llm` where a measure should have answered, or arithmetic done in the answer | a measure, or its name and description |
|
|
279
|
+
| C. Instructions vague or misdirecting | wrong interpretation, varying definitions across runs | `instructions:` or a `.md` context doc |
|
|
280
|
+
| D. Canonical answer exists as SQL | the same question keeps producing different SQL | a Verified Query |
|
|
281
|
+
| E. Validation rejects a correct result | `analytics_validation_failed` loops, `max iterations exceeded` | `validation:` |
|
|
282
|
+
| F. Judge false negative | `cot` agrees with the expected answer, `choice` is FAIL | report it. Don't touch the agent |
|
|
283
|
+
| G. Prompt underspecified | clarifying-question errors, different readings across runs | report it, and propose a sharper prompt |
|
|
284
|
+
| H. Data unavailable | no view or table holds it | report it. Don't fake support |
|
|
285
|
+
|
|
286
|
+
Symptoms, evidence, and a worked fix for each:
|
|
287
|
+
[references/diagnosis.md](references/diagnosis.md).
|
|
288
|
+
|
|
289
|
+
### 4. Repair: levers in an `.agentic.yml` agent, in this order
|
|
290
|
+
|
|
291
|
+
1. **Semantic model** (`.view.yml` / `.topic.yml` matched by the agent's `context:` globs):
|
|
292
|
+
add the missing measure or dimension, or fix an aggregation or an entity. The agent's
|
|
293
|
+
catalog search matches a measure's **name**, its **description** and its **view name**. It
|
|
294
|
+
doesn't read `synonyms:`, so the words users ask with belong in the description. A view
|
|
295
|
+
that no `context:` glob matches doesn't exist as far as the agent is concerned.
|
|
296
|
+
2. **Context** (`context:` globs, relative to the agent file):
|
|
297
|
+
- **`.md` docs** go into the prompts for clarifying the question and writing the answer.
|
|
298
|
+
Use them for business definitions: the fiscal calendar, what "active customer" means,
|
|
299
|
+
which of two similar measures to use.
|
|
300
|
+
- **Verified Queries**: a `.sql` file with a `/* oxy: … */` comment block at the top that
|
|
301
|
+
carries a `description:`. When the agent's search matches its file name or description,
|
|
302
|
+
the SQL runs verbatim, with no SQL generation. A `.sql` file **without** that block is
|
|
303
|
+
only a style example for SQL generation, and is never run as an answer.
|
|
304
|
+
3. **Instructions:** top-level `instructions:` reaches every LLM call. `states.<state>.
|
|
305
|
+
instructions:` reaches one stage: `clarifying`, `specifying`, `solving` or `interpreting`.
|
|
306
|
+
Write general rules ("revenue means net of refunds", "rank by units unless asked
|
|
307
|
+
otherwise"). Never put the expected answer, or a number from it, into instructions.
|
|
308
|
+
4. **Validation:** the `validation:` block **replaces** the 15 default rules rather than
|
|
309
|
+
adjusting them. To switch off or retune one rule, list every rule that should still run.
|
|
310
|
+
An empty `validation: {}` disables them all.
|
|
311
|
+
|
|
312
|
+
Make the smallest fix that generalizes. If the same logical gap exists in several views, fix
|
|
313
|
+
all of them. Preserve the file's existing style. Explain the root cause alongside the edit.
|
|
314
|
+
|
|
315
|
+
### 5. Validate
|
|
316
|
+
|
|
317
|
+
There's no build step. `oxy test` reloads the agent file, its context and its semantic files
|
|
318
|
+
from disk on every run, and `oxy build` (embeddings and integration sync) plays no part. Check
|
|
319
|
+
the files you edited, then re-run:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
oxy validate agents/sales.agentic.yml
|
|
323
|
+
oxy validate semantics/views/orders.view.yml
|
|
324
|
+
oxy test <file> --case <name> --output-json
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**One pass isn't proof.** A case needs `runs`/`runs` on at least two rounds. Then run the
|
|
328
|
+
whole file to catch regressions in other cases. If the repair doesn't hold after three
|
|
329
|
+
diagnose-edit-validate cycles, stop and report what you know.
|
|
330
|
+
|
|
331
|
+
### 6. Report
|
|
332
|
+
|
|
333
|
+
```text
|
|
334
|
+
## Repair summary
|
|
335
|
+
Case: tests/sales.performance.test.yml › top-items-feb-2026
|
|
336
|
+
Root cause: B — the measure existed, but its description never said "engagements"
|
|
337
|
+
Changed: semantics/views/posts.view.yml (description of total_engagements)
|
|
338
|
+
Validation: round 1 3/3 · round 2 3/3 · full file 9/9 cases
|
|
339
|
+
Notes: <judge observations, remaining risks, anything left for the user>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Never
|
|
343
|
+
|
|
344
|
+
- Edit `expected`, delete or skip a case, or reword a prompt to make a failure go away.
|
|
345
|
+
- Encode an observed answer you haven't checked, or a wrong one because it was consistent.
|
|
346
|
+
- Fabricate an expected value. A narrowed `DRAFT:` note is honest, an invented number isn't.
|
|
347
|
+
- Hard-code an answer, or a threshold derived from the test data, into instructions or a view.
|
|
348
|
+
- Treat a judge false negative as an agent bug, or a clarifying-question error as a wrong
|
|
349
|
+
answer.
|
|
350
|
+
- Stop at one lucky pass, or make "while I'm here" edits to unrelated files.
|
|
351
|
+
- Explain a root cause you didn't find. "Unclear, needs investigation" is a valid finding.
|
|
352
|
+
|
|
353
|
+
## Read next
|
|
354
|
+
|
|
355
|
+
- [references/writing-expected.md](references/writing-expected.md): worked `expected` examples
|
|
356
|
+
by question type, and the evidence rules.
|
|
357
|
+
- [references/results-json.md](references/results-json.md): the results schema, `jq`
|
|
358
|
+
recipes, and how the judge's verdict is parsed.
|
|
359
|
+
- [references/diagnosis.md](references/diagnosis.md): the failure taxonomy, with a repair
|
|
360
|
+
pattern for each.
|
|
361
|
+
- The `oxy-semantic-modeling` skill, for view, measure and entity craft when the repair is in
|
|
362
|
+
the semantic model.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Diagnosing and repairing a failing eval
|
|
2
|
+
|
|
3
|
+
Diagnose before editing. For every failing or flaky case, collect:
|
|
4
|
+
|
|
5
|
+
1. Every run's `actual_output`, `cot` and `choice` from the results JSON
|
|
6
|
+
([results-json.md](results-json.md)).
|
|
7
|
+
2. The route the agent took, from `oxy agentic run <agent> -q "<prompt>" --json`. Run it two
|
|
8
|
+
or three times if the case is flaky. Read `query_executed.source` and `query`, and any
|
|
9
|
+
`analytics_validation_failed` events.
|
|
10
|
+
3. The files the agent actually loads: the `context:` globs in its `.agentic.yml`, resolved
|
|
11
|
+
relative to that file's directory. A view, doc or SQL file outside those globs doesn't
|
|
12
|
+
exist for this agent.
|
|
13
|
+
|
|
14
|
+
Then name one category below. If two apply, fix the earlier one first. Most failures are
|
|
15
|
+
**A** or **B**.
|
|
16
|
+
|
|
17
|
+
## A. The semantic model lacks a grouping or dimension
|
|
18
|
+
|
|
19
|
+
**Evidence:** several queries per run, `source: llm` with long hand-written SQL, raw
|
|
20
|
+
row-level results, the answer doing arithmetic or bucketing in prose, and runs that disagree
|
|
21
|
+
because each one improvises the grouping differently.
|
|
22
|
+
|
|
23
|
+
**Fix:** add a dimension that names the grouping, so one semantic query returns a small,
|
|
24
|
+
finished result.
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
dimensions:
|
|
28
|
+
- name: order_size_band
|
|
29
|
+
type: string
|
|
30
|
+
description: >-
|
|
31
|
+
Order size band: Small (under $50), Medium ($50–$200), Large (over $200). Use to compare
|
|
32
|
+
small, medium and large orders or baskets.
|
|
33
|
+
expr: |
|
|
34
|
+
CASE
|
|
35
|
+
WHEN order_total < 50 THEN 'Small'
|
|
36
|
+
WHEN order_total <= 200 THEN 'Medium'
|
|
37
|
+
ELSE 'Large'
|
|
38
|
+
END
|
|
39
|
+
samples: ["Small", "Medium", "Large"]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The thresholds must be **business definitions**, not numbers read off the current data. A
|
|
43
|
+
cut-off derived from today's average drifts as the data moves, and a cut-off derived from the
|
|
44
|
+
test's expected answer is hard-coding the answer. If the same grouping makes sense in several
|
|
45
|
+
views, add it to each of them.
|
|
46
|
+
|
|
47
|
+
## B. A measure is missing, wrong, or can't be found
|
|
48
|
+
|
|
49
|
+
**Missing.** The answer computes a ratio, rate or average itself, and the arithmetic varies
|
|
50
|
+
across runs. Add the measure:
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
measures:
|
|
54
|
+
- name: avg_order_value
|
|
55
|
+
type: number # `number`: the expression already aggregates
|
|
56
|
+
description: >-
|
|
57
|
+
Average order value (AOV): net revenue per order. Also called average basket or
|
|
58
|
+
average ticket.
|
|
59
|
+
expr: "SUM(net_revenue) / NULLIF(COUNT(DISTINCT order_id), 0)"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Wrong.** The runs agree on a number that is consistently wrong. Check the measure's
|
|
63
|
+
aggregation (`sum` where `count_distinct` was meant), a join that fans out and double-counts,
|
|
64
|
+
the wrong date column (record-created rather than order date), and the measure's `filters:`.
|
|
65
|
+
Fix the definition, not the answer.
|
|
66
|
+
|
|
67
|
+
**Can't be found.** The measure exists but the agent never uses it (`source: llm`, or "I
|
|
68
|
+
don't have that data"). The analytics agent's catalog search matches a measure's `name`, its
|
|
69
|
+
`description` and its view's name, and **does not read `synonyms:`**. Put the user's words
|
|
70
|
+
(the ones in the failing prompt) into the measure's `description`.
|
|
71
|
+
|
|
72
|
+
**A dimension that exists but never gets used.** Check three things: its view is matched by
|
|
73
|
+
`context:`; the views are linked by entities (a `primary` entity on one, a `foreign` entity
|
|
74
|
+
with the same key on the other); and its `description` says what it is in the user's terms.
|
|
75
|
+
For view, measure and entity craft, see the `oxy-semantic-modeling` skill.
|
|
76
|
+
|
|
77
|
+
**Two similar members, chosen differently on each run** (for example `created_date` against
|
|
78
|
+
`order_date`). Make the descriptions decisive:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
- name: order_date
|
|
82
|
+
type: date
|
|
83
|
+
description: Date the order was placed. Use for sales, revenue and order trends by date.
|
|
84
|
+
- name: created_date
|
|
85
|
+
type: date
|
|
86
|
+
description: When the database row was written. System metadata, not for business questions.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## C. Instructions are vague, missing, or misdirect
|
|
90
|
+
|
|
91
|
+
**Evidence:** the agent picks a different reasonable definition on each run (gross or net,
|
|
92
|
+
calendar or fiscal), formats or scopes the answer inconsistently, or ignores a business
|
|
93
|
+
convention.
|
|
94
|
+
|
|
95
|
+
**Fix, by where the knowledge belongs:**
|
|
96
|
+
|
|
97
|
+
- **A definition or a business fact** goes in a `.md` doc matched by `context:`. Docs are
|
|
98
|
+
injected into the prompts that clarify the question and write the answer.
|
|
99
|
+
- **A rule for every step** goes in top-level `instructions:`.
|
|
100
|
+
- **A rule for one stage** goes in `states.<state>.instructions`, for one of `clarifying`
|
|
101
|
+
(reading the question), `specifying` (choosing measures and filters), `solving` (writing
|
|
102
|
+
SQL) or `interpreting` (writing the answer).
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
instructions: |
|
|
106
|
+
"Revenue" means net revenue (after refunds and discounts) unless the user says gross.
|
|
107
|
+
states:
|
|
108
|
+
clarifying:
|
|
109
|
+
instructions: |
|
|
110
|
+
Read "last quarter" as the most recent completed fiscal quarter; the fiscal year starts
|
|
111
|
+
on 1 February.
|
|
112
|
+
interpreting:
|
|
113
|
+
instructions: State the period and every filter applied in the answer's first sentence.
|
|
114
|
+
|
|
115
|
+
context:
|
|
116
|
+
- ../semantics/**/*.view.yml
|
|
117
|
+
- ../semantics/**/*.topic.yml
|
|
118
|
+
- ./docs/*.md
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Keep instructions general and reusable. A sentence that only makes sense for one test case
|
|
122
|
+
("the answer to the Q4 question is $1.2M") is hard-coding the test. It's forbidden, and it
|
|
123
|
+
fails the next question anyway.
|
|
124
|
+
|
|
125
|
+
## D. One canonical query should answer the question every time
|
|
126
|
+
|
|
127
|
+
**Evidence:** an important, frequently asked question whose correct SQL is known and fixed,
|
|
128
|
+
but the agent writes different SQL each run.
|
|
129
|
+
|
|
130
|
+
**Fix:** a Verified Query, a `.sql` file matched by `context:` that carries an `oxy:` comment
|
|
131
|
+
block:
|
|
132
|
+
|
|
133
|
+
```sql
|
|
134
|
+
/*
|
|
135
|
+
oxy:
|
|
136
|
+
description: "Current active headcount by department"
|
|
137
|
+
database: warehouse # optional: a database name from config.yml
|
|
138
|
+
*/
|
|
139
|
+
SELECT department, COUNT(*) AS active_headcount
|
|
140
|
+
FROM hr.employees
|
|
141
|
+
WHERE status = 'active'
|
|
142
|
+
GROUP BY department
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
When the agent's search matches the file's name or `description`, the SQL runs **verbatim**.
|
|
146
|
+
Nothing is substituted into it, so it only fits questions whose answer is exactly that query.
|
|
147
|
+
It's no use for "…in a given month". A `.sql` file without the `oxy:` block is only a style
|
|
148
|
+
example for the SQL the model writes.
|
|
149
|
+
|
|
150
|
+
## E. Validation rejects a correct result
|
|
151
|
+
|
|
152
|
+
**Evidence:** `analytics_validation_failed` events repeating on a result that was right, runs
|
|
153
|
+
that end in `max iterations exceeded`, or answers that drift away from the right one after a
|
|
154
|
+
retry. Common causes: `non_empty`, when "none" is a correct answer; `outlier_detection` on
|
|
155
|
+
naturally spiky data; and `freshness_check` on a source that loads late.
|
|
156
|
+
|
|
157
|
+
**Fix:** retune the rule. The `validation:` block **replaces** the default set, so list every
|
|
158
|
+
rule that should still run. A stage you leave out isn't checked at all, and a misspelled rule
|
|
159
|
+
name stops the agent from loading. The 15 defaults:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
validation:
|
|
163
|
+
rules:
|
|
164
|
+
specified:
|
|
165
|
+
- name: metric_resolves
|
|
166
|
+
- name: join_key_exists
|
|
167
|
+
- name: filter_unambiguous
|
|
168
|
+
solvable:
|
|
169
|
+
- name: sql_syntax
|
|
170
|
+
- name: tables_exist_in_catalog
|
|
171
|
+
- name: spec_tables_present
|
|
172
|
+
- name: column_refs_valid
|
|
173
|
+
- name: timeseries_order_by_check
|
|
174
|
+
solved:
|
|
175
|
+
- name: non_empty
|
|
176
|
+
enabled: false # "none" is a valid answer for this agent
|
|
177
|
+
- name: truncation_warning
|
|
178
|
+
- name: no_nan_inf
|
|
179
|
+
- name: outlier_detection
|
|
180
|
+
threshold_sigma: 4.0 # looser than the default for spiky daily data
|
|
181
|
+
min_rows: 12
|
|
182
|
+
- name: null_ratio_check
|
|
183
|
+
- name: duplicate_row_check
|
|
184
|
+
- name: freshness_check
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Loosen only the rule the evidence names. Validation is what stops wrong answers. Don't switch
|
|
188
|
+
it off to make a test pass.
|
|
189
|
+
|
|
190
|
+
## F. Judge false negative (not an agent problem)
|
|
191
|
+
|
|
192
|
+
**Evidence:** the answer is correct when you read it, the `cot` reasoning says so, and
|
|
193
|
+
`choice` is still `FAIL`. Usually the verdict line is missing from the last five lines of the
|
|
194
|
+
judge's response, or it reads `PASS or FAIL`, which parses as FAIL.
|
|
195
|
+
|
|
196
|
+
**Fix:** don't touch the agent. Report it with the `cot` excerpt. Recommend a stronger
|
|
197
|
+
`judge_model`: a model that already exists in `config.yml`, preferably from a different family
|
|
198
|
+
than the agent's. Changing the test's settings is the user's call.
|
|
199
|
+
|
|
200
|
+
A false **positive** (an answer that is wrong but PASSed) is the dangerous one. It usually
|
|
201
|
+
means `expected` is too vague to contradict anything. Report it, and propose a sharper
|
|
202
|
+
`expected` to the user. Don't change it yourself.
|
|
203
|
+
|
|
204
|
+
## G. The prompt is underspecified
|
|
205
|
+
|
|
206
|
+
**Evidence:** runs that end in `agentic pipeline asked a clarifying question: …`, or runs
|
|
207
|
+
that answer different reasonable readings of the prompt.
|
|
208
|
+
|
|
209
|
+
**Fix:** report the readings you saw. The test owner chooses between two remedies:
|
|
210
|
+
|
|
211
|
+
- **Sharpen the prompt.** This is a change to the test, so the user decides.
|
|
212
|
+
- **Teach the agent a default reading**, through instructions or a doc, when the business
|
|
213
|
+
wants one (for example, "when no region is named, report all regions"). That's a legitimate
|
|
214
|
+
agent repair.
|
|
215
|
+
|
|
216
|
+
## H. The data isn't there
|
|
217
|
+
|
|
218
|
+
**Evidence:** no view or table holds the needed facts, and every run declines or answers
|
|
219
|
+
around it.
|
|
220
|
+
|
|
221
|
+
**Fix:** say so plainly, and name the missing source. Don't add semantic or instruction hacks
|
|
222
|
+
that make the agent sound as if it can answer.
|
|
223
|
+
|
|
224
|
+
## Also check
|
|
225
|
+
|
|
226
|
+
- **Dates off by a day, or the wrong month.** The agent resolves "today" and relative dates
|
|
227
|
+
in its `timezone:` (UTC when unset). Set it to the business's zone.
|
|
228
|
+
- **The right number for the wrong period.** The prompt is relative ("last month") and
|
|
229
|
+
`expected` was written for an earlier month. Report it: the test needs an absolute period.
|
|
230
|
+
- **Flaky runs with different `source` values.** Some runs take the semantic model and others
|
|
231
|
+
write SQL. Make the semantic path win: make the measure findable (B), make the dimensions
|
|
232
|
+
reachable (B), or add a Verified Query (D).
|
|
233
|
+
|
|
234
|
+
## Order of repair
|
|
235
|
+
|
|
236
|
+
1. Semantic model: measures, dimensions, entities, descriptions.
|
|
237
|
+
2. Context: `.md` definitions, Verified Queries, and the `context:` globs themselves.
|
|
238
|
+
3. Instructions: global first, then per-state.
|
|
239
|
+
4. Validation tuning.
|
|
240
|
+
|
|
241
|
+
After each change, `oxy validate <edited file>`, then re-run the case at least twice
|
|
242
|
+
(`oxy test <file> --case <name> --output-json`), then the whole file. Nothing needs to be
|
|
243
|
+
rebuilt between edits. Stop after three cycles that don't hold, and report.
|