@directive-run/claude-plugin 1.16.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/.claude-plugin/plugin.json +6 -0
- package/LICENSE +26 -0
- package/README.md +113 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/skills/README.md +9 -0
- package/skills/building-ai-agents/SKILL.md +414 -0
- package/skills/building-ai-agents/ai-adapters.md +257 -0
- package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
- package/skills/building-ai-agents/ai-communication.md +304 -0
- package/skills/building-ai-agents/api-skeleton.md +5 -0
- package/skills/building-ai-agents/examples.md +849 -0
- package/skills/building-ai-orchestrators/SKILL.md +401 -0
- package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
- package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
- package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
- package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
- package/skills/building-ai-orchestrators/examples.md +1609 -0
- package/skills/building-directive-systems/SKILL.md +499 -0
- package/skills/building-directive-systems/api-skeleton.md +5 -0
- package/skills/building-directive-systems/examples.md +4149 -0
- package/skills/building-directive-systems/multi-module.md +327 -0
- package/skills/building-directive-systems/plugins.md +361 -0
- package/skills/building-directive-systems/react-adapter.md +376 -0
- package/skills/building-directive-systems/system-api.md +463 -0
- package/skills/getting-started-with-directive/SKILL.md +198 -0
- package/skills/getting-started-with-directive/api-skeleton.md +5 -0
- package/skills/getting-started-with-directive/core-patterns.md +240 -0
- package/skills/getting-started-with-directive/examples.md +87 -0
- package/skills/getting-started-with-directive/sitemap.md +202 -0
- package/skills/hardening-ai-systems/SKILL.md +377 -0
- package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
- package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
- package/skills/hardening-ai-systems/ai-security.md +400 -0
- package/skills/hardening-ai-systems/api-skeleton.md +5 -0
- package/skills/hardening-ai-systems/examples.md +628 -0
- package/skills/migrating-to-directive/SKILL.md +249 -0
- package/skills/migrating-to-directive/anti-patterns.md +382 -0
- package/skills/migrating-to-directive/api-skeleton.md +5 -0
- package/skills/migrating-to-directive/core-patterns.md +240 -0
- package/skills/migrating-to-directive/examples.md +511 -0
- package/skills/migrating-to-directive/schema-types.md +273 -0
- package/skills/reviewing-directive-code/SKILL.md +232 -0
- package/skills/reviewing-directive-code/anti-patterns.md +382 -0
- package/skills/reviewing-directive-code/api-skeleton.md +5 -0
- package/skills/reviewing-directive-code/core-patterns.md +240 -0
- package/skills/reviewing-directive-code/examples.md +463 -0
- package/skills/reviewing-directive-code/naming.md +321 -0
- package/skills/scaffolding-directive-modules/SKILL.md +218 -0
- package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
- package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
- package/skills/scaffolding-directive-modules/examples.md +1386 -0
- package/skills/scaffolding-directive-modules/naming.md +321 -0
- package/skills/scaffolding-directive-modules/schema-types.md +273 -0
- package/skills/testing-ai-systems/SKILL.md +398 -0
- package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
- package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
- package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
- package/skills/testing-ai-systems/api-skeleton.md +5 -0
- package/skills/testing-ai-systems/examples.md +1261 -0
- package/skills/testing-directive-code/SKILL.md +479 -0
- package/skills/testing-directive-code/api-skeleton.md +5 -0
- package/skills/testing-directive-code/examples.md +156 -0
- package/skills/testing-directive-code/history.md +351 -0
- package/skills/testing-directive-code/testing.md +412 -0
- package/skills/writing-directive-constraints/SKILL.md +486 -0
- package/skills/writing-directive-constraints/api-skeleton.md +5 -0
- package/skills/writing-directive-constraints/constraints.md +274 -0
- package/skills/writing-directive-constraints/error-boundaries.md +330 -0
- package/skills/writing-directive-constraints/examples.md +1361 -0
- package/skills/writing-directive-constraints/resolvers.md +367 -0
- package/skills/writing-directive-modules/SKILL.md +342 -0
- package/skills/writing-directive-modules/anti-patterns.md +382 -0
- package/skills/writing-directive-modules/api-skeleton.md +5 -0
- package/skills/writing-directive-modules/core-patterns.md +240 -0
- package/skills/writing-directive-modules/examples.md +1639 -0
- package/skills/writing-directive-modules/naming.md +321 -0
- package/skills/writing-directive-modules/schema-types.md +273 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
# AI testing and evaluations
|
|
2
|
+
|
|
3
|
+
> Covers `@directive-run/ai/testing` and `@directive-run/ai/evals` — `createMockAgentRunner`, `createTestOrchestrator`, simulators, `createEvalSuite` + `eval*` criteria.
|
|
4
|
+
|
|
5
|
+
Mock runners, test orchestrators, snapshot helpers, simulators, and a dataset-driven evaluation framework. Import all testing utilities from `@directive-run/ai/testing`; eval criteria factories come from `@directive-run/ai/evals`.
|
|
6
|
+
|
|
7
|
+
## Decision tree
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
What are you testing?
|
|
11
|
+
├── Single-agent runs (unit/integration) → createTestOrchestrator
|
|
12
|
+
├── Multi-agent patterns → createTestMultiAgentOrchestrator
|
|
13
|
+
├── Just the runner contract → createMockAgentRunner
|
|
14
|
+
├── Approval workflow → createApprovalSimulator (built into createTestOrchestrator)
|
|
15
|
+
├── Breakpoint pauses → createBreakpointSimulator
|
|
16
|
+
├── DAG topology → createTestDag + assertDagExecution
|
|
17
|
+
├── Failure modes (timeouts, errors) → createFailingRunner
|
|
18
|
+
├── Snapshot constraint behavior → createConstraintRecorder
|
|
19
|
+
└── Time-dependent logic → createTimeController
|
|
20
|
+
|
|
21
|
+
What are you evaluating?
|
|
22
|
+
├── Output quality → createEvalSuite + eval* criteria
|
|
23
|
+
├── Token / latency budgets → evalCost + evalLatency
|
|
24
|
+
├── Output shape / size → evalOutputLength + evalStructure
|
|
25
|
+
├── Safety / PII → evalSafety
|
|
26
|
+
├── Reference match → evalMatch
|
|
27
|
+
├── Faithfulness / relevance / coherence → evalFaithfulness / evalRelevance / evalCoherence
|
|
28
|
+
├── LLM-as-judge → evalJudge
|
|
29
|
+
└── Custom assertion → evalAssert
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Mock runner
|
|
33
|
+
|
|
34
|
+
`createMockAgentRunner` returns `{ run, getCalls, getCallsFor, clearCalls, setResponse, setDefaultResponse }`. The factory takes a SINGLE options object — responses are keyed by **agent name**, NOT input pattern. (For input-pattern matching, use `generate` inside a response.) **The function is named `createMockAgentRunner`, not `createMockRunner`**.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { createMockAgentRunner } from "@directive-run/ai/testing";
|
|
38
|
+
|
|
39
|
+
const mock = createMockAgentRunner({
|
|
40
|
+
responses: {
|
|
41
|
+
analyst: { output: "Analysis: positive trend", totalTokens: 100 },
|
|
42
|
+
writer: { output: "Draft article…", totalTokens: 200, delay: 50 },
|
|
43
|
+
failing: { output: "n/a", error: new Error("simulated 503") },
|
|
44
|
+
},
|
|
45
|
+
defaultResponse: { output: "mock response", totalTokens: 10 },
|
|
46
|
+
recordCalls: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const orchestrator = createAgentOrchestrator({ runner: mock.run });
|
|
50
|
+
|
|
51
|
+
await orchestrator.run({ name: "analyst", instructions: "…", model: "x" }, "Analyze this");
|
|
52
|
+
|
|
53
|
+
mock.getCalls(); // RecordedCall[] — every recorded run
|
|
54
|
+
mock.getCallsFor("analyst"); // RecordedCall[] — only this agent
|
|
55
|
+
mock.setResponse("analyst", { output: "different reply", totalTokens: 80 });
|
|
56
|
+
mock.clearCalls();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For dynamic responses keyed on input, use the `generate` callback inside the config:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const mock = createMockAgentRunner({
|
|
63
|
+
responses: {
|
|
64
|
+
classifier: {
|
|
65
|
+
output: "fallback",
|
|
66
|
+
totalTokens: 10,
|
|
67
|
+
generate: (input) => /summarize/i.test(input)
|
|
68
|
+
? { output: "Summary: …", totalTokens: 50 }
|
|
69
|
+
: { output: "Default reply", totalTokens: 10 },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Test orchestrator
|
|
76
|
+
|
|
77
|
+
`createTestOrchestrator` extends `AgentOrchestrator` with mock-runner wiring, an approval simulator, and call recording. Pass `mockResponses` and Directive constraints/resolvers exactly as you would to the production factory.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createTestOrchestrator } from "@directive-run/ai/testing";
|
|
81
|
+
import { t } from "@directive-run/core";
|
|
82
|
+
|
|
83
|
+
const test = createTestOrchestrator({
|
|
84
|
+
mockResponses: {
|
|
85
|
+
analyst: { output: "Analysis: positive trend", totalTokens: 100 },
|
|
86
|
+
},
|
|
87
|
+
factsSchema: {
|
|
88
|
+
result: t.string(),
|
|
89
|
+
confidence: t.number(),
|
|
90
|
+
},
|
|
91
|
+
init: (facts) => {
|
|
92
|
+
facts.result = "";
|
|
93
|
+
facts.confidence = 0;
|
|
94
|
+
},
|
|
95
|
+
constraints: {
|
|
96
|
+
lowConfidence: {
|
|
97
|
+
when: (facts) => facts.confidence < 0.5,
|
|
98
|
+
require: { type: "RE_ANALYZE" },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
resolvers: {
|
|
102
|
+
reAnalyze: {
|
|
103
|
+
requirement: (r): r is { type: "RE_ANALYZE" } => r.type === "RE_ANALYZE",
|
|
104
|
+
resolve: async (req, context) => { context.facts.confidence = 0.8; },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const result = await test.run({ name: "analyst", instructions: "…", model: "x" }, "Analyze");
|
|
110
|
+
|
|
111
|
+
test.getCalls(); // RecordedCall[]
|
|
112
|
+
test.getApprovalRequests(); // ApprovalRequest[] from the built-in simulator
|
|
113
|
+
test.mockRunner; // the underlying MockAgentRunner
|
|
114
|
+
test.approvalSimulator; // the built-in ApprovalSimulator
|
|
115
|
+
test.resetAll(); // reset orchestrator + clear calls + clear approvals
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Test multi-agent orchestrator
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import {
|
|
122
|
+
createTestMultiAgentOrchestrator,
|
|
123
|
+
createMockAgentRunner,
|
|
124
|
+
assertMultiAgentState,
|
|
125
|
+
} from "@directive-run/ai/testing";
|
|
126
|
+
import { sequential } from "@directive-run/ai/multi-agent";
|
|
127
|
+
|
|
128
|
+
const mock = createMockAgentRunner({
|
|
129
|
+
responses: {
|
|
130
|
+
researcher: { output: "research notes", totalTokens: 150 },
|
|
131
|
+
writer: { output: "draft article", totalTokens: 200 },
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const orch = createTestMultiAgentOrchestrator({
|
|
136
|
+
agents: {
|
|
137
|
+
researcher: { name: "researcher", instructions: "research", model: "x" },
|
|
138
|
+
writer: { name: "writer", instructions: "write", model: "x" },
|
|
139
|
+
},
|
|
140
|
+
patterns: {
|
|
141
|
+
pipeline: sequential(["researcher", "writer"]),
|
|
142
|
+
},
|
|
143
|
+
runner: mock.run,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
orch.start();
|
|
147
|
+
await orch.runPattern("pipeline", "Write about AI");
|
|
148
|
+
|
|
149
|
+
assertMultiAgentState(orch, {
|
|
150
|
+
status: "completed",
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Snapshot / introspection helpers
|
|
155
|
+
|
|
156
|
+
| Helper | Purpose |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `createConstraintRecorder()` | Records every constraint evaluation as a `ConstraintSnapshot`; pair with `assertOrchestratorState`. |
|
|
159
|
+
| `assertOrchestratorState(orch, expected)` | Diffs facts, status, and constraint state against an expected shape. |
|
|
160
|
+
| `assertMultiAgentState(orch, expected)` | Same shape, for multi-agent. |
|
|
161
|
+
| `assertScratchpadState(orch, expected)` | Checks the multi-agent scratchpad contents. |
|
|
162
|
+
| `assertDerivedValues(orch, expected)` | Diffs derivation values. |
|
|
163
|
+
| `assertTimelineEvents(timeline, expected)` | Asserts a sequence of timeline event types/payloads. |
|
|
164
|
+
| `assertDagExecution(events, expected)` | Asserts DAG node execution order. |
|
|
165
|
+
| `assertBreakpointHit(events, expected)` | Asserts a breakpoint was hit with given metadata. |
|
|
166
|
+
| `assertRerouted(events, expected)` | Asserts a self-healing reroute fired (when configured). |
|
|
167
|
+
| `assertAgentHealth(monitor, agentId, expected)` | Asserts a health score range / passing failed counts. |
|
|
168
|
+
| `assertCheckpoint(cp, expected)` | Asserts checkpoint shape (state version, conversation length, etc.). |
|
|
169
|
+
| `assertMultiplexedStream(stream, expected)` | Asserts a multiplexed stream produced expected per-agent chunks. |
|
|
170
|
+
|
|
171
|
+
There is no `assertAgentCalled` / `assertAgentNotCalled` / `assertTokensUsed` / `assertGuardrailPassed` / `assertGuardrailBlocked` — those don't exist. Use `mock.getCalls()` + `expect()` from your test framework, and inspect `result.tokenUsage` and `result.guardrailEvents` directly.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { expect } from "vitest";
|
|
175
|
+
|
|
176
|
+
const calls = mock.getCallsFor("analyst");
|
|
177
|
+
expect(calls).toHaveLength(1);
|
|
178
|
+
expect(calls[0].input).toMatch(/analyze/i);
|
|
179
|
+
|
|
180
|
+
expect(result.tokenUsage).toBeLessThan(500);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Simulators
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import {
|
|
187
|
+
createFailingRunner,
|
|
188
|
+
createApprovalSimulator,
|
|
189
|
+
createBreakpointSimulator,
|
|
190
|
+
createTimeController,
|
|
191
|
+
createTestCheckpointStore,
|
|
192
|
+
createTestReflectionEvaluator,
|
|
193
|
+
createTestEmbedder,
|
|
194
|
+
} from "@directive-run/ai/testing";
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### `createFailingRunner(error?, options?)`
|
|
198
|
+
|
|
199
|
+
Returns an `AgentRunner` that throws (or fails after N successes). Replaces the imagined `createErrorSimulator` / `createLatencySimulator`.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const failing = createFailingRunner(new Error("rate_limit"), {
|
|
203
|
+
delay: 100,
|
|
204
|
+
failAfter: 2, // first 2 calls succeed, then it starts throwing
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const orchestrator = createAgentOrchestrator({
|
|
208
|
+
runner: failing,
|
|
209
|
+
selfHealing: { fallbackRunners: [backupRunner] },
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
For latency-only simulation, set `delay` on a `MockAgentConfig`:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const slow = createMockAgentRunner({
|
|
217
|
+
responses: { writer: { output: "slow reply", totalTokens: 50, delay: 800 } },
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### `createApprovalSimulator(options?)`
|
|
222
|
+
|
|
223
|
+
Drives the approval workflow deterministically — auto-approve, auto-reject, or queue.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const sim = createApprovalSimulator({ defaultAction: "approve" });
|
|
227
|
+
|
|
228
|
+
const orchestrator = createAgentOrchestrator({
|
|
229
|
+
runner,
|
|
230
|
+
autoApproveToolCalls: false,
|
|
231
|
+
onApprovalRequest: (req) => sim.handle(req),
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const requests = sim.getRequests();
|
|
235
|
+
sim.clearRequests();
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### `createBreakpointSimulator(options?)`
|
|
239
|
+
|
|
240
|
+
Resolves breakpoints automatically, with optional modifications. Pair with the orchestrator's `breakpoints:` config.
|
|
241
|
+
|
|
242
|
+
### `createTimeController(startTime?)`
|
|
243
|
+
|
|
244
|
+
Drives `Date.now()` and `setTimeout` deterministically — required when testing budget windows, retry backoffs, or memory expiry.
|
|
245
|
+
|
|
246
|
+
### `createTestCheckpointStore()`
|
|
247
|
+
|
|
248
|
+
In-memory checkpoint store for round-tripping `checkpoint()` + `restore()` without disk IO.
|
|
249
|
+
|
|
250
|
+
### `createTestReflectionEvaluator(options?)`
|
|
251
|
+
|
|
252
|
+
Deterministic evaluator for `reflect()` patterns — scores can be hard-coded per iteration.
|
|
253
|
+
|
|
254
|
+
### `createTestEmbedder()`
|
|
255
|
+
|
|
256
|
+
Deterministic embedder (no network calls) for `createSemanticCache` testing.
|
|
257
|
+
|
|
258
|
+
## Evaluation framework
|
|
259
|
+
|
|
260
|
+
`createEvalSuite` runs a dataset of test cases through one or more agents, scores each output with one or more `EvalCriterion`, and returns aggregate results. Criteria are NOT a `criteria.*()` namespace — they're top-level `eval*` factories.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import {
|
|
264
|
+
createEvalSuite,
|
|
265
|
+
evalCost,
|
|
266
|
+
evalLatency,
|
|
267
|
+
evalOutputLength,
|
|
268
|
+
evalSafety,
|
|
269
|
+
evalStructure,
|
|
270
|
+
evalMatch,
|
|
271
|
+
evalJudge,
|
|
272
|
+
evalFaithfulness,
|
|
273
|
+
evalRelevance,
|
|
274
|
+
evalCoherence,
|
|
275
|
+
evalAssert,
|
|
276
|
+
} from "@directive-run/ai/evals";
|
|
277
|
+
|
|
278
|
+
const suite = createEvalSuite({
|
|
279
|
+
agents: [analystAgent, writerAgent],
|
|
280
|
+
runner,
|
|
281
|
+
dataset: [
|
|
282
|
+
{ id: "ts-basics", input: "What is TypeScript?", expected: "TypeScript is…", tags: ["basics"] },
|
|
283
|
+
{ id: "monads", input: "Explain monads", expected: "A monad is…", tags: ["advanced"] },
|
|
284
|
+
{ id: "ssr-tradeoffs", input: "Server vs client rendering", expected: "Tradeoffs…", tags: ["advanced"] },
|
|
285
|
+
],
|
|
286
|
+
criteria: {
|
|
287
|
+
cost: evalCost({ maxTokensPerRun: 500 }),
|
|
288
|
+
latency: evalLatency({ maxMs: 5000 }),
|
|
289
|
+
safety: evalSafety(),
|
|
290
|
+
relevance: evalRelevance({ embedder: embedderFn, minSimilarity: 0.7 }),
|
|
291
|
+
judge: evalJudge({ runner, judge: judgeAgent }),
|
|
292
|
+
},
|
|
293
|
+
concurrency: 4,
|
|
294
|
+
onCaseComplete: (caseResult) => console.log(`✓ ${caseResult.caseId} — ${caseResult.score.toFixed(2)}`),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const results = await suite.run();
|
|
298
|
+
|
|
299
|
+
console.log(results.summary.averageScore); // 0.82
|
|
300
|
+
console.log(results.summary.passRate); // 0.90
|
|
301
|
+
console.log(results.cases); // EvalCaseResult[]
|
|
302
|
+
console.log(results.agentSummaries); // EvalAgentSummary[] (per-agent breakdown)
|
|
303
|
+
console.log(results.totalTokens, results.durationMs);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Custom criterion
|
|
307
|
+
|
|
308
|
+
A criterion is `{ name, fn, threshold?, weight? }` where `fn` returns `{ score, passed?, reason?, durationMs? }`. **Scorers must be pure** — no side effects.
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
const codeQuality = {
|
|
312
|
+
name: "code-quality",
|
|
313
|
+
threshold: 0.6,
|
|
314
|
+
weight: 1.0,
|
|
315
|
+
fn: (ctx) => {
|
|
316
|
+
const start = Date.now();
|
|
317
|
+
const output = String(ctx.result.output);
|
|
318
|
+
const hasCode = /\b(function|const|class)\b/.test(output);
|
|
319
|
+
const hasExplanation = output.length > 100;
|
|
320
|
+
|
|
321
|
+
const score = hasCode && hasExplanation ? 1.0 : hasCode ? 0.7 : 0.2;
|
|
322
|
+
return {
|
|
323
|
+
score,
|
|
324
|
+
passed: score >= 0.6,
|
|
325
|
+
reason: hasCode && hasExplanation ? "code with explanation"
|
|
326
|
+
: hasCode ? "code only"
|
|
327
|
+
: "no code",
|
|
328
|
+
durationMs: Date.now() - start,
|
|
329
|
+
};
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const suite = createEvalSuite({
|
|
334
|
+
agents: [coderAgent],
|
|
335
|
+
runner,
|
|
336
|
+
dataset,
|
|
337
|
+
criteria: { codeQuality },
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### LLM-as-judge
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { evalJudge } from "@directive-run/ai/evals";
|
|
345
|
+
|
|
346
|
+
const judge = evalJudge({
|
|
347
|
+
runner, // any AgentRunner — typically a smaller/cheaper model
|
|
348
|
+
judge: judgeAgent, // the AgentLike to invoke
|
|
349
|
+
promptTemplate: `…`, // optional — defaults to a JSON-output rubric
|
|
350
|
+
timeoutMs: 30_000,
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
The judge returns `{ score: number, reason?: string }` parsed from JSON. The default prompt enforces "Respond with ONLY a JSON object: …" — override `promptTemplate` if you need a different rubric.
|
|
355
|
+
|
|
356
|
+
### CI quality gates
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
const results = await suite.run();
|
|
360
|
+
|
|
361
|
+
if (results.summary.averageScore < 0.75) {
|
|
362
|
+
console.error(`Quality gate failed: ${results.summary.averageScore} < 0.75`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for (const [criterionName, summary] of Object.entries(results.summary.byCriterion)) {
|
|
367
|
+
if (summary.averageScore < 0.6) {
|
|
368
|
+
console.error(`${criterionName} regressed: ${summary.averageScore}`);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Anti-patterns
|
|
375
|
+
|
|
376
|
+
### Calling `createMockRunner` (singular, with input-pattern array)
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// WRONG — createMockRunner does not exist; the array-pattern form does not exist
|
|
380
|
+
createMockRunner([{ input: /analyze/, output: "…" }])
|
|
381
|
+
|
|
382
|
+
// CORRECT — keyed by agent name
|
|
383
|
+
createMockAgentRunner({
|
|
384
|
+
responses: { analyst: { output: "…", totalTokens: 100 } },
|
|
385
|
+
})
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Importing hallucinated assertions
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// WRONG — none of these exist
|
|
392
|
+
import {
|
|
393
|
+
assertAgentCalled,
|
|
394
|
+
assertAgentNotCalled,
|
|
395
|
+
assertTokensUsed,
|
|
396
|
+
assertGuardrailPassed,
|
|
397
|
+
assertGuardrailBlocked,
|
|
398
|
+
} from "@directive-run/ai/testing";
|
|
399
|
+
|
|
400
|
+
// CORRECT — use the mock's recorder + your test framework's assertions
|
|
401
|
+
const calls = mock.getCallsFor("analyst");
|
|
402
|
+
expect(calls).toHaveLength(1);
|
|
403
|
+
expect(result.tokenUsage).toBeLessThan(200);
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Importing `createEvaluator`, `criteria.*()`, `createLLMJudge`, `createEvaluationSuite`
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// WRONG — these are all hallucinated names
|
|
410
|
+
import { createEvaluator, criteria, createLLMJudge, createEvaluationSuite } from "@directive-run/ai/testing";
|
|
411
|
+
const evaluator = createEvaluator({ criteria: [criteria.relevance(), criteria.coherence()] });
|
|
412
|
+
const judge = createLLMJudge({ runner, model: "…", criteria: ["accuracy"], rubric: "…" });
|
|
413
|
+
|
|
414
|
+
// CORRECT — top-level eval factories + createEvalSuite
|
|
415
|
+
import { createEvalSuite, evalRelevance, evalCoherence, evalJudge } from "@directive-run/ai/evals";
|
|
416
|
+
const suite = createEvalSuite({
|
|
417
|
+
agents: [agent],
|
|
418
|
+
runner,
|
|
419
|
+
dataset,
|
|
420
|
+
criteria: {
|
|
421
|
+
relevance: evalRelevance({ embedder, minSimilarity: 0.7 }),
|
|
422
|
+
coherence: evalCoherence({ embedder, minSimilarity: 0.5 }),
|
|
423
|
+
judge: evalJudge({ runner, judge: judgeAgent }),
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Importing `createErrorSimulator` / `createLatencySimulator`
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
// WRONG — neither exists
|
|
432
|
+
import { createErrorSimulator, createLatencySimulator } from "@directive-run/ai/testing";
|
|
433
|
+
|
|
434
|
+
// CORRECT — createFailingRunner for errors; delay in MockAgentConfig for latency
|
|
435
|
+
import { createFailingRunner, createMockAgentRunner } from "@directive-run/ai/testing";
|
|
436
|
+
const failing = createFailingRunner(new Error("503"), { failAfter: 2, delay: 100 });
|
|
437
|
+
const slow = createMockAgentRunner({ responses: { writer: { output: "…", delay: 800 } } });
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Side-effecting scorers
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
// WRONG — scorers run inside the eval suite and must be pure
|
|
444
|
+
{
|
|
445
|
+
name: "quality",
|
|
446
|
+
fn: (ctx) => {
|
|
447
|
+
fs.writeFileSync("eval.log", String(ctx.result.output)); // ← side effect
|
|
448
|
+
metrics.increment("evals"); // ← side effect
|
|
449
|
+
return { score: 0.8 };
|
|
450
|
+
},
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// CORRECT — log/metrics in onCaseComplete or after suite.run()
|
|
454
|
+
const suite = createEvalSuite({
|
|
455
|
+
…,
|
|
456
|
+
onCaseComplete: (caseResult) => {
|
|
457
|
+
metrics.increment("evals");
|
|
458
|
+
fs.appendFileSync("eval.log", JSON.stringify(caseResult) + "\n");
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## Quick reference
|
|
464
|
+
|
|
465
|
+
| API | Import path | Purpose |
|
|
466
|
+
|---|---|---|
|
|
467
|
+
| `createMockAgentRunner` | `@directive-run/ai/testing` | Deterministic mock runner with call recording |
|
|
468
|
+
| `createTestOrchestrator` | `@directive-run/ai/testing` | Single-agent orchestrator with mock + approval simulator |
|
|
469
|
+
| `createTestMultiAgentOrchestrator` | `@directive-run/ai/testing` | Multi-agent orchestrator wired to a mock runner |
|
|
470
|
+
| `createFailingRunner` | `@directive-run/ai/testing` | Runner that throws (or fails after N successes) |
|
|
471
|
+
| `createApprovalSimulator` | `@directive-run/ai/testing` | Deterministic approve/reject driver |
|
|
472
|
+
| `createBreakpointSimulator` | `@directive-run/ai/testing` | Deterministic breakpoint resolver |
|
|
473
|
+
| `createTimeController` | `@directive-run/ai/testing` | Virtual clock for budgets / backoffs / TTLs |
|
|
474
|
+
| `createTestCheckpointStore` | `@directive-run/ai/testing` | In-memory checkpoint store |
|
|
475
|
+
| `createTestReflectionEvaluator` | `@directive-run/ai/testing` | Deterministic evaluator for `reflect()` |
|
|
476
|
+
| `createTestEmbedder` | `@directive-run/ai/testing` | Deterministic embedder for cache tests |
|
|
477
|
+
| `assertOrchestratorState` / `assertMultiAgentState` / `assertScratchpadState` / `assertDerivedValues` / `assertTimelineEvents` / `assertDagExecution` / `assertBreakpointHit` / `assertRerouted` / `assertAgentHealth` / `assertCheckpoint` / `assertMultiplexedStream` | `@directive-run/ai/testing` | State/topology/event assertions |
|
|
478
|
+
| `createEvalSuite` | `@directive-run/ai/evals` | Dataset-driven multi-criterion eval runner |
|
|
479
|
+
| `evalCost` / `evalLatency` / `evalOutputLength` / `evalSafety` / `evalStructure` / `evalMatch` / `evalJudge` / `evalFaithfulness` / `evalRelevance` / `evalCoherence` / `evalAssert` | `@directive-run/ai/evals` | Built-in criterion factories |
|
|
480
|
+
|
|
481
|
+
## See also
|
|
482
|
+
|
|
483
|
+
- [`ai-orchestrator.md`](./ai-orchestrator.md) — `createTestOrchestrator` wraps this orchestrator; the prod surface is what your tests are exercising
|
|
484
|
+
- [`ai-multi-agent.md`](./ai-multi-agent.md) — `createTestMultiAgentOrchestrator` for testing pattern execution
|
|
485
|
+
- [`ai-debug-observability.md`](./ai-debug-observability.md) — recorded debug events feed `assertTimelineEvents` / `assertDagExecution` / `assertBreakpointHit`
|