oh-my-knowledge 0.18.0 → 0.19.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 +399 -326
- package/README.zh.md +724 -0
- package/dist/src/cli.js +21 -13
- package/dist/src/cli.js.map +1 -1
- package/dist/src/eval-core/evaluation-job.d.ts +3 -1
- package/dist/src/eval-core/evaluation-job.d.ts.map +1 -1
- package/dist/src/eval-core/evaluation-job.js +3 -1
- package/dist/src/eval-core/evaluation-job.js.map +1 -1
- package/dist/src/eval-workflows/each-evaluation-workflow.d.ts +7 -3
- package/dist/src/eval-workflows/each-evaluation-workflow.d.ts.map +1 -1
- package/dist/src/eval-workflows/each-evaluation-workflow.js +7 -2
- package/dist/src/eval-workflows/each-evaluation-workflow.js.map +1 -1
- package/dist/src/eval-workflows/evaluation-pipeline.d.ts +5 -1
- package/dist/src/eval-workflows/evaluation-pipeline.d.ts.map +1 -1
- package/dist/src/eval-workflows/evaluation-pipeline.js +6 -2
- package/dist/src/eval-workflows/evaluation-pipeline.js.map +1 -1
- package/dist/src/eval-workflows/run-evaluation.d.ts +8 -2
- package/dist/src/eval-workflows/run-evaluation.d.ts.map +1 -1
- package/dist/src/eval-workflows/run-evaluation.js +11 -3
- package/dist/src/eval-workflows/run-evaluation.js.map +1 -1
- package/dist/src/observability/{production-analyzer.d.ts → skill-health-analyzer.d.ts} +24 -2
- package/dist/src/observability/skill-health-analyzer.d.ts.map +1 -0
- package/dist/src/observability/{production-analyzer.js → skill-health-analyzer.js} +61 -6
- package/dist/src/observability/skill-health-analyzer.js.map +1 -0
- package/dist/src/observability/trace-adapter.d.ts.map +1 -1
- package/dist/src/observability/trace-adapter.js +27 -1
- package/dist/src/observability/trace-adapter.js.map +1 -1
- package/dist/src/renderer/html-renderer.d.ts.map +1 -1
- package/dist/src/renderer/html-renderer.js +25 -3
- package/dist/src/renderer/html-renderer.js.map +1 -1
- package/dist/src/renderer/layout.d.ts.map +1 -1
- package/dist/src/renderer/layout.js +106 -0
- package/dist/src/renderer/layout.js.map +1 -1
- package/dist/src/renderer/skill-health-renderer.d.ts +2 -2
- package/dist/src/renderer/skill-health-renderer.d.ts.map +1 -1
- package/dist/src/renderer/skill-health-renderer.js +39 -4
- package/dist/src/renderer/skill-health-renderer.js.map +1 -1
- package/dist/src/renderer/summary.js +1 -1
- package/dist/src/server/report-server.d.ts +2 -1
- package/dist/src/server/report-server.d.ts.map +1 -1
- package/dist/src/server/report-server.js +397 -2
- package/dist/src/server/report-server.js.map +1 -1
- package/dist/src/types.d.ts +6 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +10 -3
- package/dist/src/observability/production-analyzer.d.ts.map +0 -1
- package/dist/src/observability/production-analyzer.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,103 +1,118 @@
|
|
|
1
1
|
# oh-my-knowledge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/oh-my-knowledge)
|
|
4
|
+
[](https://github.com/lizhiyao/oh-my-knowledge/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
**English** | [简体中文](./README.zh.md)
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
Knowledge-artifact evaluation toolkit — measure your artifact's quality with objective data.
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
**Fix the model, vary the knowledge artifact, let the data speak.**
|
|
10
13
|
|
|
11
|
-
##
|
|
14
|
+
## Why this tool
|
|
15
|
+
|
|
16
|
+
Teams doing knowledge engineering produce lots of knowledge artifacts (skills today, but also prompts, agents, workflows…). When someone asks "why is v2 better than v1", you need objective data instead of gut feeling. `oh-my-knowledge` solves this with controlled experiments: **same model, same test samples, only the knowledge artifact changes.**
|
|
17
|
+
|
|
18
|
+
## Key features
|
|
19
|
+
|
|
20
|
+
- **Controlled-variable offline bench** — fix the model and samples, vary only the artifact; works with Claude Code skills, CLAUDE.md prompts, RAG knowledge bases, or any markdown-based instruction
|
|
21
|
+
- **Six-dimension scoring** — separate signals for Fact / Behavior / LLM-judge / Cost / Efficiency / Stability, so a regression in one axis isn't hidden by gains in another
|
|
22
|
+
- **Production session observability** — parse Claude Code session JSONL traces, measure per-skill failure rate, latency, token cost, and knowledge-gap signals on real user sessions
|
|
23
|
+
- **Knowledge-gap detection** — severity-weighted signals (explicit markers / failed searches / hedging language / repeated failures) quantify risk exposure instead of claiming completeness
|
|
24
|
+
- **Pre-merge CI gate** — `omk bench ci` enforces three-layer all-pass (fact + behavior + llm-judge) semantics, catching single-layer regressions a composite score would hide
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
12
27
|
|
|
13
28
|
```bash
|
|
14
|
-
#
|
|
29
|
+
# install
|
|
15
30
|
npm i oh-my-knowledge -g
|
|
16
31
|
|
|
17
|
-
#
|
|
32
|
+
# scaffold an eval project
|
|
18
33
|
omk bench init my-eval
|
|
19
34
|
cd my-eval
|
|
20
35
|
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
36
|
+
# drop the artifacts you want to compare into skills/
|
|
37
|
+
# option 1: plain .md files (skills/v1.md, skills/v2.md)
|
|
38
|
+
# option 2: full artifact dirs (skills/my-skill-v1/SKILL.md, ...)
|
|
39
|
+
# a single artifact also works — baseline is auto-added as control
|
|
25
40
|
|
|
26
|
-
#
|
|
41
|
+
# preview the plan
|
|
27
42
|
omk bench run --dry-run
|
|
28
43
|
|
|
29
|
-
#
|
|
44
|
+
# run the evaluation (auto-discovers everything under skills/)
|
|
30
45
|
omk bench run
|
|
31
46
|
```
|
|
32
47
|
|
|
33
|
-
##
|
|
48
|
+
## Use inside Claude Code
|
|
34
49
|
|
|
35
|
-
|
|
50
|
+
After installing omk, talk to it in natural language from Claude Code:
|
|
36
51
|
|
|
37
52
|
```
|
|
38
|
-
/omk eval #
|
|
39
|
-
/omk evolve #
|
|
40
|
-
/omk gen-samples #
|
|
53
|
+
/omk eval # evaluate the artifact(s) in the current project
|
|
54
|
+
/omk evolve # auto-iterate to improve an artifact
|
|
55
|
+
/omk gen-samples # generate test cases
|
|
41
56
|
```
|
|
42
57
|
|
|
43
|
-
|
|
58
|
+
You can also just say "compare v1 vs v2 for me" or "improve this artifact" — omk picks the right command.
|
|
44
59
|
|
|
45
|
-
##
|
|
60
|
+
## Features
|
|
46
61
|
|
|
47
|
-
|
|
|
48
|
-
|
|
49
|
-
| **18
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
| **MCP URL
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
62
|
+
| Feature | What it does |
|
|
63
|
+
|---|---|
|
|
64
|
+
| **18 assertion types** | substring, regex, JSON Schema, semantic similarity, custom JS function, and more |
|
|
65
|
+
| **Six-dim evaluation** | Fact / Behavior / LLM-judge / Cost / Efficiency / Stability shown independently |
|
|
66
|
+
| **Multi-executor** | Claude CLI / Claude SDK / OpenAI / Gemini / any custom command |
|
|
67
|
+
| **MCP URL fetching** | pull content from private-doc URLs via an MCP server (SSO-protected knowledge bases, etc.) |
|
|
68
|
+
| **Blind A/B** | `--blind` hides variant names; HTML report has a reveal button |
|
|
69
|
+
| **Parallel execution** | `--concurrency N` runs N tasks at once |
|
|
70
|
+
| **Multi-run variance** | `--repeat N` repeats the eval and computes mean / SD / CI / t-test |
|
|
71
|
+
| **Auto analysis** | detects low-discrimination assertions, flat scores, all-pass / all-fail, expensive samples |
|
|
72
|
+
| **Traceability** | reports carry CLI version, Node version, artifact version fingerprint |
|
|
73
|
+
| **EN / ZH switch** | one-click language toggle in the HTML report |
|
|
59
74
|
|
|
60
|
-
##
|
|
75
|
+
## How it works
|
|
61
76
|
|
|
62
|
-
|
|
77
|
+
Core idea: **fix the model and the samples, vary only the artifact and runtime context**, use interleaved scheduling to cancel time drift, score via assertions + LLM judge (dual channel), then layer on knowledge-gap signals to quantify risk exposure.
|
|
63
78
|
|
|
64
79
|
```mermaid
|
|
65
80
|
flowchart TD
|
|
66
|
-
subgraph Input["①
|
|
81
|
+
subgraph Input["① Input"]
|
|
67
82
|
S["eval-samples<br/>(JSON / YAML)"]
|
|
68
83
|
A["artifacts<br/>skills/*.md · SKILL.md<br/>baseline · git:name · @cwd"]
|
|
69
84
|
end
|
|
70
85
|
|
|
71
|
-
subgraph Prep["②
|
|
72
|
-
V["
|
|
73
|
-
U["URL
|
|
86
|
+
subgraph Prep["② Preprocess (resolve & fetch)"]
|
|
87
|
+
V["variant resolution<br/>variant → artifact + runtime context<br/>(cwd / project CLAUDE.md / local skills)"]
|
|
88
|
+
U["URL fetching<br/>URLs in prompt / context<br/>MCP Server(private docs) → HTTP"]
|
|
74
89
|
end
|
|
75
90
|
|
|
76
|
-
subgraph Schedule["③
|
|
91
|
+
subgraph Schedule["③ Interleaved + concurrent scheduling"]
|
|
77
92
|
Q["s1-v1 → s1-v2 → s2-v1 → s2-v2 …<br/>--concurrency N · --repeat N"]
|
|
78
93
|
end
|
|
79
94
|
|
|
80
|
-
subgraph Exec["④
|
|
81
|
-
E["claude / claude-sdk / openai / gemini<br/>anthropic-api / openai-api /
|
|
82
|
-
T["claude-sdk
|
|
95
|
+
subgraph Exec["④ Executor (fixed model)"]
|
|
96
|
+
E["claude / claude-sdk / openai / gemini<br/>anthropic-api / openai-api / custom"]
|
|
97
|
+
T["claude-sdk extracts<br/>turns / toolCalls trace"]
|
|
83
98
|
E -.-> T
|
|
84
99
|
end
|
|
85
100
|
|
|
86
|
-
subgraph Score["⑤
|
|
87
|
-
AS["
|
|
88
|
-
LS["LLM
|
|
89
|
-
CS["
|
|
101
|
+
subgraph Score["⑤ Dual-channel scoring"]
|
|
102
|
+
AS["assertions (18 types)<br/>content / structure / cost / latency<br/>agent: tools_called · turns_min …"]
|
|
103
|
+
LS["LLM judge<br/>rubric · dimensions (independent per-dim scores)"]
|
|
104
|
+
CS["composite score<br/>mean of assertion & LLM when both present"]
|
|
90
105
|
AS --> CS
|
|
91
106
|
LS --> CS
|
|
92
107
|
end
|
|
93
108
|
|
|
94
|
-
subgraph Analyze["⑥
|
|
95
|
-
D["
|
|
96
|
-
G["
|
|
109
|
+
subgraph Analyze["⑥ Auto analysis + knowledge gaps"]
|
|
110
|
+
D["low-discrimination / flat scores / all-pass or all-fail<br/>expensive samples · variance · t-test"]
|
|
111
|
+
G["knowledge-gap signals<br/>(quantify risk exposure, not completeness proof)"]
|
|
97
112
|
end
|
|
98
113
|
|
|
99
|
-
subgraph Report["⑦
|
|
100
|
-
R["
|
|
114
|
+
subgraph Report["⑦ Report"]
|
|
115
|
+
R["Six dims: Fact / Behavior / LLM-judge / Cost / Efficiency / Stability<br/>JSON + HTML · blind reveal<br/>CLI/Node/version fingerprint traceable"]
|
|
101
116
|
end
|
|
102
117
|
|
|
103
118
|
S --> U
|
|
@@ -114,72 +129,72 @@ flowchart TD
|
|
|
114
129
|
G --> R
|
|
115
130
|
```
|
|
116
131
|
|
|
117
|
-
|
|
132
|
+
**Key design choices:**
|
|
118
133
|
|
|
119
|
-
-
|
|
120
|
-
- **variant = artifact + runtime context
|
|
121
|
-
-
|
|
122
|
-
-
|
|
134
|
+
- **Interleaved scheduling** removes time drift: different variants of the same sample are dispatched alternately rather than "all of v1 then all of v2", so model load / network jitter can't be mis-attributed to the artifact.
|
|
135
|
+
- **variant = artifact + runtime context**: `name@cwd` lets control groups explicitly declare the "project directory" input, separating "project-level accumulated knowledge" from "explicit artifact injection".
|
|
136
|
+
- **Dual-channel scoring is complementary**: assertions catch deterministic defects (must call tool X, must contain field Y); the LLM judge catches subjective quality (readability, completeness). Mean is taken when both are present.
|
|
137
|
+
- **Knowledge-gap signals** are not part of the score — they are an independent tracking channel that tells you "how much risk exposure this evaluation covered", for convergence tracking, not as a completeness proof.
|
|
123
138
|
|
|
124
|
-
##
|
|
139
|
+
## Eval sample format
|
|
125
140
|
|
|
126
|
-
|
|
141
|
+
Supports JSON and YAML (`eval-samples.json`, `eval-samples.yaml`, `eval-samples.yml`).
|
|
127
142
|
|
|
128
143
|
```json
|
|
129
144
|
[
|
|
130
145
|
{
|
|
131
146
|
"sample_id": "s001",
|
|
132
|
-
"prompt": "
|
|
147
|
+
"prompt": "Review this code for security issues",
|
|
133
148
|
"context": "function auth(u, p) { db.query('SELECT * FROM users WHERE name=' + u); }",
|
|
134
|
-
"rubric": "
|
|
149
|
+
"rubric": "Should identify SQL injection risk and recommend parameterized queries",
|
|
135
150
|
"assertions": [
|
|
136
|
-
{ "type": "contains", "value": "SQL
|
|
137
|
-
{ "type": "contains", "value": "
|
|
138
|
-
{ "type": "not_contains", "value": "
|
|
151
|
+
{ "type": "contains", "value": "SQL injection", "weight": 1 },
|
|
152
|
+
{ "type": "contains", "value": "parameterized", "weight": 1 },
|
|
153
|
+
{ "type": "not_contains", "value": "looks fine", "weight": 0.5 }
|
|
139
154
|
],
|
|
140
155
|
"dimensions": {
|
|
141
|
-
"security": "
|
|
142
|
-
"actionability": "
|
|
156
|
+
"security": "did it identify the injection vulnerability?",
|
|
157
|
+
"actionability": "did it give directly usable fix code?"
|
|
143
158
|
}
|
|
144
159
|
}
|
|
145
160
|
]
|
|
146
161
|
```
|
|
147
162
|
|
|
148
|
-
###
|
|
163
|
+
### Fields
|
|
149
164
|
|
|
150
|
-
|
|
|
151
|
-
|
|
152
|
-
| `sample_id` | `string` |
|
|
153
|
-
| `prompt` | `string` |
|
|
154
|
-
| `context` | `string` |
|
|
155
|
-
| `rubric` | `string` |
|
|
156
|
-
| `assertions` | `array` |
|
|
157
|
-
| `assertions[].type` | `string` |
|
|
158
|
-
| `assertions[].value` | `string\|number` |
|
|
159
|
-
| `assertions[].values` | `array` |
|
|
160
|
-
| `assertions[].pattern` | `string` |
|
|
161
|
-
| `assertions[].flags` | `string` |
|
|
162
|
-
| `assertions[].schema` | `object` |
|
|
163
|
-
| `assertions[].reference` | `string` |
|
|
164
|
-
| `assertions[].threshold` | `number` |
|
|
165
|
-
| `assertions[].fn` | `string` |
|
|
166
|
-
| `assertions[].weight` | `number` |
|
|
167
|
-
| `dimensions` | `object` |
|
|
165
|
+
| Field | Type | Required | Description |
|
|
166
|
+
|---|---|---|---|
|
|
167
|
+
| `sample_id` | `string` | **yes** | Unique sample ID |
|
|
168
|
+
| `prompt` | `string` | **yes** | User prompt sent to the model |
|
|
169
|
+
| `context` | `string` | no | Extra context (e.g. code). Wrapped in a code block and appended to the prompt. URLs are auto-fetched at runtime. |
|
|
170
|
+
| `rubric` | `string` | no | Scoring guideline for the LLM judge (1-5 scale) |
|
|
171
|
+
| `assertions` | `array` | no | Assertion checks; see [assertion types](#assertion-types) |
|
|
172
|
+
| `assertions[].type` | `string` | **yes** | Assertion type |
|
|
173
|
+
| `assertions[].value` | `string\|number` | depends | Check value (required for `contains`, `min_length`, `cost_max`, etc.) |
|
|
174
|
+
| `assertions[].values` | `array` | depends | String array (required for `contains_all`, `contains_any`) |
|
|
175
|
+
| `assertions[].pattern` | `string` | depends | Regex pattern (required for `regex`) |
|
|
176
|
+
| `assertions[].flags` | `string` | no | Regex flags (default `"i"`) |
|
|
177
|
+
| `assertions[].schema` | `object` | depends | JSON Schema object (required for `json_schema`, via [ajv](https://ajv.js.org/)) |
|
|
178
|
+
| `assertions[].reference` | `string` | depends | Reference text (required for `semantic_similarity`) |
|
|
179
|
+
| `assertions[].threshold` | `number` | no | Pass threshold for semantic similarity (default 3) |
|
|
180
|
+
| `assertions[].fn` | `string` | depends | Path to a custom assertion JS file (required for `custom`) |
|
|
181
|
+
| `assertions[].weight` | `number` | no | Weight (default 1) |
|
|
182
|
+
| `dimensions` | `object` | no | Multi-dimension scoring; key = dimension name, value = scoring guideline |
|
|
168
183
|
|
|
169
|
-
### URL
|
|
184
|
+
### URL auto-fetching
|
|
170
185
|
|
|
171
|
-
`prompt`
|
|
186
|
+
URLs in `prompt` and `context` are auto-fetched before evaluation and inlined into the text. Useful when referencing online docs, API references, etc.:
|
|
172
187
|
|
|
173
188
|
```json
|
|
174
189
|
{
|
|
175
190
|
"sample_id": "s001",
|
|
176
|
-
"prompt": "
|
|
191
|
+
"prompt": "Generate test cases from this PRD: https://wiki.example.com/prd/feature-x"
|
|
177
192
|
}
|
|
178
193
|
```
|
|
179
194
|
|
|
180
|
-
|
|
195
|
+
At runtime, URLs are replaced with the actual content. Fetch order: MCP Server first for matching URLs (e.g. SSO-protected private docs), then plain HTTP for the rest. URLs already resolved by MCP are not re-fetched via HTTP.
|
|
181
196
|
|
|
182
|
-
|
|
197
|
+
**Private-doc URLs**: drop a `.mcp.json` config file into the project dir, or pass `--mcp-config <path>`:
|
|
183
198
|
|
|
184
199
|
```json
|
|
185
200
|
{
|
|
@@ -202,452 +217,510 @@ flowchart TD
|
|
|
202
217
|
}
|
|
203
218
|
```
|
|
204
219
|
|
|
205
|
-
|
|
220
|
+
**Public URLs**: fetched via plain HTTP. If they require auth, make sure the shell already has network access configured (VPN, proxy, etc.).
|
|
206
221
|
|
|
207
|
-
###
|
|
222
|
+
### Scoring strategy
|
|
208
223
|
|
|
209
|
-
#### 1.
|
|
224
|
+
#### 1. Assertion score
|
|
210
225
|
|
|
211
|
-
|
|
226
|
+
Rule-based local checks; each assertion yields pass/fail.
|
|
212
227
|
|
|
213
|
-
|
|
228
|
+
**Formula:**
|
|
214
229
|
|
|
215
|
-
-
|
|
216
|
-
-
|
|
217
|
-
-
|
|
230
|
+
- Pass rate = sum of passed assertion weights / total weight (0–1)
|
|
231
|
+
- Score = 1 + pass_rate × 4 (mapped to 1–5)
|
|
232
|
+
- Example: 3 assertions (weight 1 each), 2 pass → pass rate 2/3 → score = 1 + 0.67 × 4 = **3.67**
|
|
218
233
|
|
|
219
|
-
#### 2. Rubric / Dimensions
|
|
234
|
+
#### 2. Rubric / Dimensions score
|
|
220
235
|
|
|
221
|
-
|
|
236
|
+
The judge model (default `haiku`) scores 1–5 against the rubric. In `dimensions` mode, each dimension is scored independently and then averaged.
|
|
222
237
|
|
|
223
|
-
#### 3.
|
|
238
|
+
#### 3. Composite score
|
|
224
239
|
|
|
225
|
-
|
|
|
226
|
-
|
|
227
|
-
|
|
|
228
|
-
|
|
|
229
|
-
|
|
|
230
|
-
|
|
|
240
|
+
| Condition | Formula |
|
|
241
|
+
|---|---|
|
|
242
|
+
| Only assertions | `assertionScore` |
|
|
243
|
+
| Only LLM judge | `llmScore` |
|
|
244
|
+
| Both present | `(assertionScore + llmScore) / 2` |
|
|
245
|
+
| Neither | `0` |
|
|
231
246
|
|
|
232
|
-
###
|
|
247
|
+
### Assertion types
|
|
233
248
|
|
|
234
|
-
|
|
249
|
+
**Deterministic assertions (18 total):**
|
|
235
250
|
|
|
236
|
-
|
|
|
237
|
-
|
|
238
|
-
| `contains` / `not_contains` |
|
|
239
|
-
| `regex` |
|
|
240
|
-
| `min_length` / `max_length` |
|
|
241
|
-
| `json_valid` / `json_schema` | JSON
|
|
242
|
-
| `starts_with` / `ends_with` |
|
|
243
|
-
| `equals` / `not_equals` |
|
|
244
|
-
| `word_count_min` / `word_count_max` |
|
|
245
|
-
| `contains_all` / `contains_any` |
|
|
246
|
-
| `cost_max` / `latency_max` |
|
|
247
|
-
| `semantic_similarity` | LLM
|
|
248
|
-
| `custom` |
|
|
251
|
+
| Type | Description |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `contains` / `not_contains` | substring must / must-not appear |
|
|
254
|
+
| `regex` | regex match |
|
|
255
|
+
| `min_length` / `max_length` | length bounds |
|
|
256
|
+
| `json_valid` / `json_schema` | JSON validation |
|
|
257
|
+
| `starts_with` / `ends_with` | prefix / suffix |
|
|
258
|
+
| `equals` / `not_equals` | exact match |
|
|
259
|
+
| `word_count_min` / `word_count_max` | word-count bounds |
|
|
260
|
+
| `contains_all` / `contains_any` | multi-value match |
|
|
261
|
+
| `cost_max` / `latency_max` | cost / latency caps |
|
|
262
|
+
| `semantic_similarity` | LLM-based semantic similarity |
|
|
263
|
+
| `custom` | custom JS function (30 s timeout) |
|
|
249
264
|
|
|
250
|
-
###
|
|
265
|
+
### Custom assertion
|
|
251
266
|
|
|
252
267
|
```js
|
|
253
268
|
// my-assertion.mjs
|
|
254
269
|
export default function(output, { sample, assertion }) {
|
|
255
|
-
return { pass: output.includes('SQL'), message: '
|
|
270
|
+
return { pass: output.includes('SQL'), message: 'checked for SQL keyword' };
|
|
256
271
|
}
|
|
257
272
|
```
|
|
258
273
|
|
|
259
|
-
##
|
|
274
|
+
## Six-dim evaluation
|
|
260
275
|
|
|
261
|
-
|
|
276
|
+
Reports display results across six independent dimensions. The three scoring layers — Fact / Behavior / LLM-judge — are shown separately so you see **which layer regressed** instead of a single composite number:
|
|
262
277
|
|
|
263
|
-
|
|
|
264
|
-
|
|
265
|
-
|
|
|
266
|
-
|
|
|
267
|
-
|
|
|
268
|
-
|
|
|
278
|
+
| Dimension | Metric | Description |
|
|
279
|
+
|---|---|---|
|
|
280
|
+
| 📋 **Fact** | fact-assertion pass rate | rule-verifiable assertions like `contains` / `json_schema` / `fact_check`, mapped to 1-5 |
|
|
281
|
+
| 🛠️ **Behavior** | behavior-assertion pass rate | execution-compliance assertions like `tools_called` / `tool_output_contains` / `turns_max` |
|
|
282
|
+
| 💬 **LLM-judge** | rubric score | 1-5 scored by the judge model against a predefined rubric; subjective, catches what rules miss |
|
|
283
|
+
| 💰 **Cost** | total cost, input/output tokens | API cost based on token usage and model pricing |
|
|
284
|
+
| ⚡ **Efficiency** | average latency (ms) | end-to-end latency from request to full response |
|
|
285
|
+
| 🛡️ **Stability** | CV (coefficient of variation) | score consistency across repeated runs (`--repeat ≥ 2`); single-run shows `—`, **honestly acknowledging what can't be measured** |
|
|
269
286
|
|
|
270
|
-
## CLI
|
|
287
|
+
## CLI reference
|
|
271
288
|
|
|
272
289
|
### `omk bench run`
|
|
273
290
|
|
|
274
291
|
```bash
|
|
275
|
-
omk bench run [
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
--samples
|
|
279
|
-
--skill-dir
|
|
280
|
-
--
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
--
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
--
|
|
290
|
-
--
|
|
291
|
-
--
|
|
292
|
-
--
|
|
293
|
-
--
|
|
294
|
-
--
|
|
295
|
-
--
|
|
296
|
-
--
|
|
297
|
-
|
|
298
|
-
--
|
|
299
|
-
--
|
|
300
|
-
--
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
292
|
+
omk bench run [options]
|
|
293
|
+
|
|
294
|
+
options:
|
|
295
|
+
--samples <path> sample file (default: eval-samples.json, also detects .yaml/.yml)
|
|
296
|
+
--skill-dir <path> artifact dir (default: skills)
|
|
297
|
+
--control <expr> control-group variant expression (experiment role = control)
|
|
298
|
+
--treatment <v1,v2> treatment-group variant expressions, comma-separated
|
|
299
|
+
at least one of --control / --treatment is required
|
|
300
|
+
(unless you use --config or --each)
|
|
301
|
+
special values: baseline (empty artifact), git:name (git HEAD),
|
|
302
|
+
git:ref:name (specific commit), path with "/" (read file directly)
|
|
303
|
+
--config <path> YAML/JSON config file (evaluation-as-code); declares
|
|
304
|
+
samples + variants + model + executor in one file; CLI
|
|
305
|
+
flags override config fields when both are provided
|
|
306
|
+
--model <name> model under test (default: sonnet)
|
|
307
|
+
--judge-model <name> judge model (default: haiku)
|
|
308
|
+
--output-dir <path> output dir (default: ~/.oh-my-knowledge/reports/)
|
|
309
|
+
--no-judge skip the LLM judge
|
|
310
|
+
--no-cache disable result cache (on by default; identical inputs reuse)
|
|
311
|
+
--dry-run preview only
|
|
312
|
+
--blind blind mode
|
|
313
|
+
--concurrency <n> parallel tasks (default: 1)
|
|
314
|
+
--timeout <sec> per-task executor timeout (default: 120)
|
|
315
|
+
--repeat <n> repeat N times for variance analysis (default: 1)
|
|
316
|
+
--executor <name> executor (default: claude); supports custom commands
|
|
317
|
+
--skip-preflight skip pre-evaluation model reachability check
|
|
318
|
+
--mcp-config <path> MCP config for fetching private-doc URLs via MCP Server
|
|
319
|
+
(default: .mcp.json in cwd)
|
|
320
|
+
--no-serve don't auto-start the report server after the run
|
|
321
|
+
--verbose print per-sample details (duration, tokens, output preview)
|
|
322
|
+
--each batch mode: evaluate each artifact independently vs baseline
|
|
323
|
+
requires {name}.eval-samples.json paired with each artifact
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `omk bench run --each` (batch mode)
|
|
327
|
+
|
|
328
|
+
When `skills/` contains several **independent** artifacts, use `--each` to evaluate each one against baseline and produce a merged report.
|
|
307
329
|
|
|
308
330
|
```
|
|
309
331
|
skills/
|
|
310
|
-
├── asset.md ← artifact
|
|
311
|
-
├── asset.eval-samples.json ←
|
|
332
|
+
├── asset.md ← artifact file
|
|
333
|
+
├── asset.eval-samples.json ← paired samples
|
|
312
334
|
├── home.md
|
|
313
335
|
├── home.eval-samples.json
|
|
314
|
-
└── product/ ←
|
|
336
|
+
└── product/ ← directory format also supported
|
|
315
337
|
├── SKILL.md
|
|
316
338
|
└── eval-samples.json
|
|
317
339
|
```
|
|
318
340
|
|
|
319
|
-
|
|
341
|
+
Pairing rules:
|
|
320
342
|
|
|
321
|
-
- `{name}.md` →
|
|
322
|
-
- `{name}/SKILL.md` →
|
|
323
|
-
-
|
|
343
|
+
- `{name}.md` → looks for `{name}.eval-samples.json` in the same dir
|
|
344
|
+
- `{name}/SKILL.md` → looks for `{name}/eval-samples.json`
|
|
345
|
+
- artifacts without paired samples are skipped with a warning
|
|
324
346
|
|
|
325
347
|
```bash
|
|
326
348
|
omk bench run --each
|
|
327
349
|
omk bench run --each --dry-run
|
|
328
350
|
```
|
|
329
351
|
|
|
330
|
-
### `omk bench gen-samples
|
|
352
|
+
### `omk bench gen-samples` (generate test cases)
|
|
331
353
|
|
|
332
|
-
|
|
354
|
+
Reads an artifact's content and uses an LLM to auto-generate eval-samples. Review and edit them before running eval.
|
|
333
355
|
|
|
334
356
|
```bash
|
|
335
|
-
#
|
|
357
|
+
# generate for a specific artifact (writes eval-samples.json)
|
|
336
358
|
omk bench gen-samples skills/my-skill.md
|
|
337
359
|
|
|
338
|
-
#
|
|
360
|
+
# batch-generate for every artifact under skills/ that lacks samples
|
|
339
361
|
omk bench gen-samples --each
|
|
340
362
|
|
|
341
|
-
#
|
|
363
|
+
# specify sample count
|
|
342
364
|
omk bench gen-samples skills/my-skill.md --count 10
|
|
343
365
|
```
|
|
344
366
|
|
|
345
|
-
|
|
367
|
+
Options:
|
|
346
368
|
|
|
347
369
|
```
|
|
348
|
-
--each
|
|
349
|
-
--count <n>
|
|
350
|
-
--model
|
|
351
|
-
--skill-dir
|
|
370
|
+
--each batch-generate for every artifact missing samples
|
|
371
|
+
--count <n> samples per artifact (default: 5)
|
|
372
|
+
--model <name> model used for generation (default: sonnet)
|
|
373
|
+
--skill-dir <path> artifact dir (default: skills), used with --each
|
|
352
374
|
```
|
|
353
375
|
|
|
354
|
-
### `omk bench evolve
|
|
376
|
+
### `omk bench evolve` (self-iterating improvement)
|
|
355
377
|
|
|
356
|
-
|
|
378
|
+
Lets the AI iterate an artifact automatically: evaluate → analyze weak spots → LLM rewrites → evaluate again → keep if the score went up, drop otherwise → repeat.
|
|
357
379
|
|
|
358
380
|
```bash
|
|
359
|
-
#
|
|
381
|
+
# basic: iterate 5 rounds
|
|
360
382
|
omk bench evolve skills/my-skill.md
|
|
361
383
|
|
|
362
|
-
#
|
|
384
|
+
# set rounds and target score
|
|
363
385
|
omk bench evolve skills/my-skill.md --rounds 10 --target 4.5
|
|
364
386
|
```
|
|
365
387
|
|
|
366
|
-
|
|
388
|
+
Options:
|
|
367
389
|
|
|
368
390
|
```
|
|
369
|
-
--rounds <n>
|
|
370
|
-
--target
|
|
371
|
-
--samples
|
|
372
|
-
--improve-model
|
|
391
|
+
--rounds <n> max iteration rounds (default: 5)
|
|
392
|
+
--target <score> stop early when the score reaches this threshold
|
|
393
|
+
--samples <path> sample file (default: eval-samples.json)
|
|
394
|
+
--improve-model <name> model used for rewrites (default: sonnet)
|
|
373
395
|
```
|
|
374
396
|
|
|
375
|
-
|
|
397
|
+
Each round's output is saved under `skills/evolve/` (`my-skill.r0.md`, `my-skill.r1.md`…), so you can `diff` to see what the AI changed. The best round is written back to the original file.
|
|
376
398
|
|
|
377
399
|
### `omk bench ci`
|
|
378
400
|
|
|
379
|
-
|
|
401
|
+
Run the evaluation inside CI. Exit code 0 on pass, 1 on fail — can be wired into gates directly.
|
|
402
|
+
|
|
403
|
+
The gate is **three-layer all-pass**: `avgFactScore >= threshold AND avgBehaviorScore >= threshold AND avgJudgeScore >= threshold`. Any layer below threshold is FAIL, and the output shows which layer broke. This stops cases like `fact 4.5→2.5 but judge 3→5` from passing via composite averaging — if one layer regresses, the gate catches it.
|
|
380
404
|
|
|
381
405
|
```bash
|
|
382
|
-
omk bench ci [
|
|
383
|
-
--threshold
|
|
406
|
+
omk bench ci [options]
|
|
407
|
+
--threshold <number> per-layer minimum score (default: 3.5); applied
|
|
408
|
+
independently to fact / behavior / judge
|
|
384
409
|
```
|
|
385
410
|
|
|
386
411
|
### `omk bench report`
|
|
387
412
|
|
|
388
|
-
|
|
413
|
+
Start the report server to browse historical reports, submit feedback, and delete reports.
|
|
389
414
|
|
|
390
415
|
```bash
|
|
391
|
-
omk bench report [
|
|
392
|
-
--port
|
|
416
|
+
omk bench report [options]
|
|
417
|
+
--port <number> server port (default: 7799)
|
|
393
418
|
```
|
|
394
419
|
|
|
395
420
|
### `omk bench init`
|
|
396
421
|
|
|
397
422
|
```bash
|
|
398
|
-
omk bench init [
|
|
423
|
+
omk bench init [dir] # scaffold an eval project
|
|
399
424
|
```
|
|
400
425
|
|
|
401
|
-
##
|
|
426
|
+
## `omk analyze` — production observability
|
|
427
|
+
|
|
428
|
+
`omk bench run` is **offline evaluation** (fixed controls, repeatable, scored). Production is different — no control group, no ground truth, no repetition, so scoring isn't valid there. `omk analyze` turns existing Claude Code session traces into **skill-health reports** (coverage, gap signals, execution stability, tokens/latency per skill). It gives you clues about **which skill is worth re-evaluating offline**, not a production score.
|
|
402
429
|
|
|
403
|
-
|
|
430
|
+
```bash
|
|
431
|
+
# analyze all cc sessions of the current project (auto-infers kb from the trace)
|
|
432
|
+
omk analyze ~/.claude/projects/-Users-you-Documents-my-project
|
|
433
|
+
|
|
434
|
+
# restrict to the last 7 days / 24 hours / 30 minutes
|
|
435
|
+
omk analyze ~/.claude/projects/my-project --last 7d
|
|
436
|
+
|
|
437
|
+
# absolute time window
|
|
438
|
+
omk analyze ~/.claude/projects/my-project --from 2026-04-01T00:00:00Z --to 2026-04-15T23:59:59Z
|
|
439
|
+
|
|
440
|
+
# whitelist specific skills
|
|
441
|
+
omk analyze ~/.claude/projects/my-project --skills audit,polish
|
|
442
|
+
|
|
443
|
+
# override the inferred knowledge-base root
|
|
444
|
+
omk analyze ~/.claude/projects/my-project --kb /path/to/project
|
|
445
|
+
```
|
|
404
446
|
|
|
405
|
-
|
|
406
|
-
|--------|----------|------|
|
|
407
|
-
| `claude` | 默认 | 通过 `claude -p` 调用 Claude CLI |
|
|
408
|
-
| `claude-sdk` | 结构化输出 | 通过 Claude Agent SDK 调用,无 stdout 解析,避免 buffer 截断 |
|
|
409
|
-
| `openai` | 跨厂商对比 | 通过 `openai api` CLI 调用 |
|
|
410
|
-
| `gemini` | 跨厂商对比 | 通过 `gemini` CLI 调用 |
|
|
411
|
-
| `anthropic-api` | 无需 CLI | 直接调用 Anthropic HTTP API(需 `ANTHROPIC_API_KEY`) |
|
|
412
|
-
| `openai-api` | 无需 CLI | 直接调用 OpenAI HTTP API(需 `OPENAI_API_KEY`) |
|
|
447
|
+
The command writes `~/.oh-my-knowledge/analyses/<timestamp>-skill-health.json`. Browse results alongside bench reports with `omk bench report` — the homepage has a "📊 Skill Health Reports" link, and each skill card also has a "trend →" link to its time-series view. For two reports side-by-side, use the compare selector on `/analyses`.
|
|
413
448
|
|
|
414
|
-
|
|
449
|
+
**What you get per skill:**
|
|
415
450
|
|
|
416
|
-
|
|
451
|
+
- **Knowledge usage** — which KB files this skill actually read (coverage %)
|
|
452
|
+
- **Knowledge gaps** — four weighted signals (failed search / model-flagged gap / hedging / repeated miss); hedging goes through an LLM-assisted classifier to filter out business-possibility hedging vs genuine knowledge uncertainty
|
|
453
|
+
- **Execution stability** — tool-failure rate; a skill with > 20% failures gets a warning that its gap signals may be environmental noise rather than real knowledge gaps
|
|
454
|
+
- **Usage cost** — billable tokens (input+output) separate from cached tokens, total duration
|
|
417
455
|
|
|
418
|
-
|
|
456
|
+
**What this is NOT:**
|
|
457
|
+
|
|
458
|
+
- Not a general APM (request/response/latency tracing is Langfuse / Datadog territory)
|
|
459
|
+
- Not streaming / alerting (batch only — run on a cron if you want periodic snapshots)
|
|
460
|
+
- Not a production score (no control group, no ground truth — use `omk bench run` for scoring)
|
|
461
|
+
|
|
462
|
+
## Executors
|
|
463
|
+
|
|
464
|
+
### Built-in executors
|
|
465
|
+
|
|
466
|
+
| Executor | When to use | Description |
|
|
467
|
+
|---|---|---|
|
|
468
|
+
| `claude` | default | invokes `claude -p` via Claude CLI |
|
|
469
|
+
| `claude-sdk` | structured output | uses Claude Agent SDK — no stdout parsing, avoids buffer truncation |
|
|
470
|
+
| `openai` | cross-vendor comparison | invokes `openai api` CLI |
|
|
471
|
+
| `gemini` | cross-vendor comparison | invokes `gemini` CLI |
|
|
472
|
+
| `anthropic-api` | no CLI needed | calls Anthropic HTTP API directly (needs `ANTHROPIC_API_KEY`) |
|
|
473
|
+
| `openai-api` | no CLI needed | calls OpenAI HTTP API directly (needs `OPENAI_API_KEY`) |
|
|
474
|
+
|
|
475
|
+
API-direct executors support custom base URLs via env: `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`.
|
|
476
|
+
|
|
477
|
+
### Custom executor
|
|
478
|
+
|
|
479
|
+
Any shell command can serve as an executor, communicating via stdin/stdout JSON:
|
|
419
480
|
|
|
420
481
|
```bash
|
|
421
482
|
omk bench run --executor "python my_provider.py"
|
|
422
483
|
omk bench run --executor "./my-executor.sh"
|
|
423
484
|
```
|
|
424
485
|
|
|
425
|
-
|
|
486
|
+
**Protocol:**
|
|
426
487
|
|
|
427
|
-
-
|
|
428
|
-
-
|
|
429
|
-
- stdout
|
|
430
|
-
-
|
|
488
|
+
- **input** (stdin): JSON `{"model":"...","system":"...","prompt":"..."}`
|
|
489
|
+
- **output** (stdout): JSON `{"output":"model reply","inputTokens":0,"outputTokens":0,"costUSD":0}`
|
|
490
|
+
- stdout only needs to return the fields you care about; others default to 0. Plain-text output (no tokens/cost parsing) is also fine.
|
|
491
|
+
- non-zero exit code counts as failure
|
|
431
492
|
|
|
432
|
-
### Artifact
|
|
493
|
+
### Artifact directory layout
|
|
433
494
|
|
|
434
|
-
|
|
495
|
+
The built-in executors (claude / openai / gemini) support two artifact layouts, mixable in the same run:
|
|
435
496
|
|
|
436
497
|
```
|
|
437
498
|
skills/
|
|
438
|
-
├── v1.md #
|
|
439
|
-
└── my-skill/ #
|
|
440
|
-
├── SKILL.md #
|
|
441
|
-
├── config.json #
|
|
499
|
+
├── v1.md # option 1: plain .md file
|
|
500
|
+
└── my-skill/ # option 2: full artifact dir
|
|
501
|
+
├── SKILL.md # this file is auto-loaded as system prompt
|
|
502
|
+
├── config.json # other files don't participate in eval, kept for completeness
|
|
442
503
|
└── scripts/
|
|
443
504
|
```
|
|
444
505
|
|
|
445
|
-
**Variant
|
|
506
|
+
**Variant resolution rules:**
|
|
446
507
|
|
|
447
|
-
`variant`
|
|
508
|
+
`variant` is the experiment-group expression. After resolution, OMK produces an `artifact` plus an optional `runtime context` (currently mainly `cwd`).
|
|
448
509
|
|
|
449
|
-
|
|
|
450
|
-
|
|
451
|
-
| `name` |
|
|
452
|
-
| `baseline` |
|
|
453
|
-
| `project-env@/path/to/project` |
|
|
454
|
-
| `git:name` |
|
|
455
|
-
| `git:ref:name` |
|
|
456
|
-
| `./path/to/file.md` |
|
|
457
|
-
| `variant@/path/to/project` |
|
|
510
|
+
| Format | Meaning |
|
|
511
|
+
|---|---|
|
|
512
|
+
| `name` | looks up `name.md` or `name/SKILL.md` in the artifact dir, resolves to one artifact |
|
|
513
|
+
| `baseline` | empty artifact, no system prompt — think "nothing at all" |
|
|
514
|
+
| `project-env@/path/to/project` | empty artifact, but run in the specified project dir — observe project-level runtime context alone |
|
|
515
|
+
| `git:name` | reads the last-committed version of an artifact from git HEAD |
|
|
516
|
+
| `git:ref:name` | reads an artifact from a specific commit |
|
|
517
|
+
| `./path/to/file.md` | path with `/`: read the file directly as an artifact |
|
|
518
|
+
| `variant@/path/to/project` | attach a run dir to any variant; supports `name@cwd`, `git:name@cwd`, `/file.md@cwd` |
|
|
458
519
|
|
|
459
|
-
|
|
520
|
+
When both `--control` and `--treatment` are omitted, use `--config eval.yaml` or `--each`. With `--each`, `baseline` is auto-added as control and every discovered artifact becomes a treatment.
|
|
460
521
|
|
|
461
522
|
```bash
|
|
462
|
-
#
|
|
463
|
-
omk bench run
|
|
523
|
+
# explicit: one control, one or more treatments
|
|
524
|
+
omk bench run --control v1 --treatment v2
|
|
525
|
+
omk bench run --control baseline --treatment v1,v2,v3
|
|
464
526
|
|
|
465
|
-
#
|
|
466
|
-
omk bench run --
|
|
527
|
+
# compare empty artifact vs explicit artifact
|
|
528
|
+
omk bench run --control baseline --treatment my-skill
|
|
467
529
|
|
|
468
|
-
#
|
|
469
|
-
omk bench run --
|
|
530
|
+
# observe project-level runtime context in isolation (use a self-describing label)
|
|
531
|
+
omk bench run --control baseline --treatment project-env@/path/to/target-project
|
|
470
532
|
|
|
471
|
-
#
|
|
472
|
-
omk bench run
|
|
533
|
+
# compare "project-level runtime context" vs "explicit artifact injection"
|
|
534
|
+
omk bench run \
|
|
535
|
+
--control project-env@/path/to/target-project \
|
|
536
|
+
--treatment /path/to/target-project/.claude/skills/prd/SKILL.md@/path/to/target-project
|
|
473
537
|
|
|
474
|
-
#
|
|
475
|
-
omk bench run --
|
|
538
|
+
# before vs after (old version read from git history)
|
|
539
|
+
omk bench run --control git:my-skill --treatment my-skill
|
|
476
540
|
|
|
477
|
-
#
|
|
478
|
-
omk bench run --
|
|
541
|
+
# direct file paths
|
|
542
|
+
omk bench run --control ./old-skill.md --treatment ./new-skill.md
|
|
479
543
|
|
|
480
|
-
#
|
|
481
|
-
omk bench run --
|
|
544
|
+
# config-file driven (evaluation-as-code)
|
|
545
|
+
omk bench run --config eval.yaml
|
|
482
546
|
```
|
|
483
547
|
|
|
484
|
-
|
|
548
|
+
**Prerequisites:**
|
|
485
549
|
|
|
486
|
-
- **claude
|
|
487
|
-
- **claude-sdk
|
|
488
|
-
- **anthropic-api
|
|
489
|
-
- **openai
|
|
490
|
-
- **openai-api
|
|
491
|
-
- **gemini
|
|
550
|
+
- **claude**: install [Claude Code](https://claude.ai/code) and authenticate
|
|
551
|
+
- **claude-sdk**: install [Claude Code](https://claude.ai/code) and authenticate (uses Agent SDK, no CLI stdout parsing)
|
|
552
|
+
- **anthropic-api**: set the `ANTHROPIC_API_KEY` env var
|
|
553
|
+
- **openai**: `pip install openai` and set `OPENAI_API_KEY`
|
|
554
|
+
- **openai-api**: set the `OPENAI_API_KEY` env var
|
|
555
|
+
- **gemini**: `npm i -g @google/gemini-cli` and authenticate
|
|
492
556
|
|
|
493
|
-
### Agent
|
|
557
|
+
### Agent evaluation and project-level runtime context
|
|
494
558
|
|
|
495
|
-
|
|
559
|
+
When the executor is `claude-sdk`, OMK supports a first pass of agent-aware evaluation.
|
|
496
560
|
|
|
497
|
-
|
|
561
|
+
A few concepts worth keeping separate:
|
|
498
562
|
|
|
499
|
-
- `artifact
|
|
500
|
-
- `variant
|
|
501
|
-
- `runtime context
|
|
563
|
+
- `artifact`: the thing being evaluated — baseline, skill, prompt, agent
|
|
564
|
+
- `variant`: the CLI expression for an experiment group
|
|
565
|
+
- `runtime context`: the runtime environment; currently mainly `cwd`. In project-type agent scenarios it includes the project dir, its `CLAUDE.md`, local skills, and any other environmental factors that affect behavior
|
|
502
566
|
|
|
503
|
-
|
|
567
|
+
In OMK, `agent` is not a catch-all term and neither is `skill`. A cleaner phrasing: **you are comparing how different artifacts behave under different runtime contexts.**
|
|
504
568
|
|
|
505
|
-
-
|
|
506
|
-
-
|
|
507
|
-
-
|
|
569
|
+
- auto-extracts turns / toolCalls traces
|
|
570
|
+
- supports assertions on tool-call behavior
|
|
571
|
+
- supports running under a specified `cwd`, so Claude Code auto-loads the project's `CLAUDE.md`, skills, and local runtime context
|
|
508
572
|
|
|
509
|
-
####
|
|
573
|
+
#### Recommended executor
|
|
510
574
|
|
|
511
575
|
```bash
|
|
512
576
|
omk bench run --executor claude-sdk
|
|
513
577
|
```
|
|
514
578
|
|
|
515
|
-
####
|
|
579
|
+
#### Agent-related assertions
|
|
516
580
|
|
|
517
|
-
|
|
|
518
|
-
|
|
519
|
-
| `tools_called` |
|
|
520
|
-
| `tools_not_called` |
|
|
521
|
-
| `tools_count_min` / `tools_count_max` |
|
|
522
|
-
| `tool_output_contains` |
|
|
523
|
-
| `turns_min` / `turns_max` |
|
|
581
|
+
| Assertion | Meaning |
|
|
582
|
+
|---|---|
|
|
583
|
+
| `tools_called` | must call the specified tool(s) |
|
|
584
|
+
| `tools_not_called` | must not call the specified tool(s) |
|
|
585
|
+
| `tools_count_min` / `tools_count_max` | tool-call-count bounds |
|
|
586
|
+
| `tool_output_contains` | output of a specific tool must contain given content |
|
|
587
|
+
| `turns_min` / `turns_max` | turn-count bounds |
|
|
524
588
|
|
|
525
|
-
####
|
|
589
|
+
#### Three common control setups
|
|
526
590
|
|
|
527
|
-
**1.
|
|
591
|
+
**1. Bare-model baseline**
|
|
528
592
|
|
|
529
|
-
|
|
593
|
+
No system prompt and no knowledge-carrying project dir. Requires at least one treatment to compare against:
|
|
530
594
|
|
|
531
595
|
```bash
|
|
532
596
|
omk bench run \
|
|
533
597
|
--executor claude-sdk \
|
|
534
|
-
--
|
|
598
|
+
--control baseline \
|
|
599
|
+
--treatment my-skill
|
|
535
600
|
```
|
|
536
601
|
|
|
537
|
-
**2.
|
|
602
|
+
**2. Empty artifact + project-level runtime context**
|
|
538
603
|
|
|
539
|
-
|
|
604
|
+
No system prompt, but runs inside a project dir. This is **not** a strict "bare baseline" — it is "empty artifact + project-level runtime context".
|
|
540
605
|
|
|
541
606
|
```bash
|
|
542
607
|
omk bench run \
|
|
543
608
|
--executor claude-sdk \
|
|
544
|
-
--
|
|
609
|
+
--control baseline \
|
|
610
|
+
--treatment project-env@/path/to/target-project
|
|
545
611
|
```
|
|
546
612
|
|
|
547
|
-
**3.
|
|
613
|
+
**3. Explicit artifact injection**
|
|
548
614
|
|
|
549
|
-
|
|
615
|
+
Inject an external `SKILL.md` as the artifact while also keeping the project dir. Good for contrasting "project-level runtime context" vs "explicit single-artifact injection".
|
|
550
616
|
|
|
551
617
|
```bash
|
|
552
618
|
omk bench run \
|
|
553
619
|
--executor claude-sdk \
|
|
554
|
-
--
|
|
620
|
+
--control project-env@/path/to/target-project \
|
|
621
|
+
--treatment /path/to/target-project/.claude/skills/prd/SKILL.md@/path/to/target-project
|
|
555
622
|
```
|
|
556
623
|
|
|
557
|
-
####
|
|
624
|
+
#### Recommended first-round design
|
|
558
625
|
|
|
559
|
-
|
|
626
|
+
For PRD / complex business-knowledge scenarios, start with:
|
|
560
627
|
|
|
561
628
|
```bash
|
|
562
629
|
omk bench run \
|
|
563
630
|
--executor claude-sdk \
|
|
564
631
|
--samples skills/evaluate-review/eval-samples.yaml \
|
|
565
|
-
--
|
|
632
|
+
--control baseline \
|
|
633
|
+
--treatment /path/to/target-project/.claude/skills/prd/SKILL.md@/path/to/target-project
|
|
566
634
|
```
|
|
567
635
|
|
|
568
|
-
|
|
636
|
+
If you want to prove whether "the knowledge sitting inside the project directory" is effective on its own, add a second treatment:
|
|
569
637
|
|
|
570
638
|
```bash
|
|
571
639
|
omk bench run \
|
|
572
640
|
--executor claude-sdk \
|
|
573
641
|
--samples skills/evaluate-review/eval-samples.yaml \
|
|
574
|
-
--
|
|
642
|
+
--control baseline \
|
|
643
|
+
--treatment project-env@/path/to/target-project,/path/to/target-project/.claude/skills/prd/SKILL.md@/path/to/target-project
|
|
575
644
|
```
|
|
576
645
|
|
|
577
|
-
####
|
|
646
|
+
#### Design tips
|
|
578
647
|
|
|
579
|
-
-
|
|
580
|
-
-
|
|
581
|
-
-
|
|
648
|
+
- **Always start with `--dry-run`** to confirm samples, variants, and `cwd` are parsed correctly
|
|
649
|
+
- **Project-level controls must differ in `cwd`**: the same prompt under different project dirs hits different runtime contexts
|
|
650
|
+
- **Try PRD scenarios first**: compared to pure coding, they make it easier to validate knowledge completeness, impact-area detection, and business correctness
|
|
582
651
|
|
|
583
|
-
###
|
|
652
|
+
### Common model configurations
|
|
584
653
|
|
|
585
|
-
|
|
654
|
+
**Don't have Claude?** Most Chinese LLMs (GLM, Qwen, Moonshot, DeepSeek, etc.) are OpenAI-API compatible — use the `openai-api` executor directly:
|
|
586
655
|
|
|
587
656
|
```bash
|
|
588
|
-
# GLM
|
|
589
|
-
export OPENAI_API_KEY="
|
|
657
|
+
# GLM (Zhipu)
|
|
658
|
+
export OPENAI_API_KEY="your Zhipu API key"
|
|
590
659
|
export OPENAI_BASE_URL="https://open.bigmodel.cn/api/paas/v4"
|
|
591
660
|
omk bench run --executor openai-api --model glm-4-plus \
|
|
592
661
|
--judge-model glm-4-plus --no-cache
|
|
593
662
|
|
|
594
|
-
#
|
|
595
|
-
export OPENAI_API_KEY="
|
|
663
|
+
# Qwen (Alibaba)
|
|
664
|
+
export OPENAI_API_KEY="your Qwen API key"
|
|
596
665
|
export OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
597
666
|
omk bench run --executor openai-api --model qwen-plus \
|
|
598
667
|
--judge-model qwen-plus
|
|
599
668
|
|
|
600
669
|
# DeepSeek
|
|
601
|
-
export OPENAI_API_KEY="
|
|
670
|
+
export OPENAI_API_KEY="your DeepSeek API key"
|
|
602
671
|
export OPENAI_BASE_URL="https://api.deepseek.com"
|
|
603
672
|
omk bench run --executor openai-api --model deepseek-chat \
|
|
604
673
|
--judge-model deepseek-chat
|
|
605
674
|
|
|
606
|
-
# Moonshot
|
|
607
|
-
export OPENAI_API_KEY="
|
|
675
|
+
# Moonshot (Kimi)
|
|
676
|
+
export OPENAI_API_KEY="your Moonshot API key"
|
|
608
677
|
export OPENAI_BASE_URL="https://api.moonshot.cn/v1"
|
|
609
678
|
omk bench run --executor openai-api --model moonshot-v1-8k \
|
|
610
679
|
--judge-model moonshot-v1-8k
|
|
611
680
|
```
|
|
612
681
|
|
|
613
|
-
**Ollama
|
|
682
|
+
**Ollama local model:**
|
|
614
683
|
|
|
615
684
|
```bash
|
|
616
685
|
omk bench run --executor "python examples/custom-executor/ollama-executor.py" \
|
|
617
686
|
--model llama3 --no-judge
|
|
618
687
|
```
|
|
619
688
|
|
|
620
|
-
|
|
689
|
+
**About the judge model:**
|
|
621
690
|
|
|
622
|
-
- `--judge-model`
|
|
623
|
-
- `--judge-executor`
|
|
624
|
-
-
|
|
625
|
-
-
|
|
691
|
+
- `--judge-model` picks the model used by the LLM judge (default `haiku`)
|
|
692
|
+
- `--judge-executor` picks the executor the judge uses (defaults to `--executor`)
|
|
693
|
+
- If you don't have Claude, point `--judge-executor` and `--judge-model` at whatever model you have
|
|
694
|
+
- Add `--no-judge` to skip the LLM judge and rely on assertions alone
|
|
626
695
|
|
|
627
|
-
##
|
|
696
|
+
## Environment variables
|
|
628
697
|
|
|
629
|
-
|
|
|
630
|
-
|
|
631
|
-
| `CCV_PROXY_URL` |
|
|
632
|
-
| `OMK_BENCH_PORT` |
|
|
698
|
+
| Variable | Description |
|
|
699
|
+
|---|---|
|
|
700
|
+
| `CCV_PROXY_URL` | proxy requests through cc-viewer for live eval-traffic visualization |
|
|
701
|
+
| `OMK_BENCH_PORT` | report server port (default: 7799) |
|
|
633
702
|
|
|
634
|
-
##
|
|
703
|
+
## Requirements
|
|
635
704
|
|
|
636
705
|
- Node.js >= 20
|
|
637
|
-
- `claude` CLI
|
|
638
|
-
-
|
|
706
|
+
- `claude` CLI (for the default executor and LLM judge; see [Claude Code](https://claude.ai/code))
|
|
707
|
+
- not needed if you use other executors (openai / gemini) with `--no-judge`
|
|
708
|
+
|
|
709
|
+
## Security notice
|
|
710
|
+
|
|
711
|
+
This tool is designed for **local trusted environments** (dev machines, CI pipelines). The following features execute local code — make sure inputs come from a trusted source:
|
|
639
712
|
|
|
640
|
-
|
|
713
|
+
| Feature | Risk | Scope |
|
|
714
|
+
|---|---|---|
|
|
715
|
+
| **Custom assertions** (`custom`) | dynamically loads and executes user-specified `.mjs` files | only use assertion files you authored or reviewed |
|
|
716
|
+
| **eval-samples.json** | assertion configs can reference external file paths | don't use sample files from untrusted sources |
|
|
641
717
|
|
|
642
|
-
|
|
718
|
+
**Recommendations:**
|
|
643
719
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
| **eval-samples.json** | 断言配置中可引用外部文件路径 | 不要使用不可信来源的样本文件 |
|
|
720
|
+
- Do not expose `omk bench report` on the public internet (no auth)
|
|
721
|
+
- Don't use third-party eval-samples you haven't vetted
|
|
722
|
+
- Custom assertions have a 30-second timeout but no sandbox isolation
|
|
648
723
|
|
|
649
|
-
|
|
724
|
+
---
|
|
650
725
|
|
|
651
|
-
|
|
652
|
-
- 不要用不可信的第三方 eval-samples 文件
|
|
653
|
-
- 自定义断言有 30 秒执行超时,但无沙箱隔离
|
|
726
|
+
See [CHANGELOG](./CHANGELOG.md) for release notes. Contributions welcome — see [CONTRIBUTING](./CONTRIBUTING.md).
|