@opensearch-project/agent-health 0.5.2 → 0.7.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 +1 -0
- package/cli/dist/index.js +2325 -961
- package/dist/assets/index-D-Np_l_T.js +246 -0
- package/dist/assets/index-vZt9QZKf.css +1 -0
- package/dist/index.html +2 -2
- package/docs/CLI.md +183 -3
- package/docs/CONFIGURATION.md +1 -1
- package/docs/CONNECTORS.md +1 -1
- package/docs/INSTRUMENT_WITH_OTEL.md +7 -0
- package/docs/SDK.md +178 -5
- package/docs/STORAGE_INDEX_FIELD_LIMITS.md +216 -0
- package/docs/skills/AGENT_HEALTH.md +55 -1
- package/docs/skills/add-connector/SKILL.md +5 -1
- package/examples/eval-files/demo.eval.js +1 -1
- package/examples/eval-files/ops-rca-classification.eval.js +71 -0
- package/examples/eval-files/ops-rca-evaluator.json +15 -0
- package/examples/eval-files/sdk-demo.eval.js +72 -0
- package/examples/eval-files/sdk-describe-demo.eval.js +50 -0
- package/examples/eval-files/sdk-hooks-demo.eval.js +1 -1
- package/lib/dist/lib/agentTrends.d.ts +210 -0
- package/lib/dist/lib/agentTrends.d.ts.map +1 -0
- package/lib/dist/lib/agentTrends.js +360 -0
- package/lib/dist/lib/agentTrends.js.map +1 -0
- package/lib/dist/lib/bedrockCompat.d.ts +27 -0
- package/lib/dist/lib/bedrockCompat.d.ts.map +1 -0
- package/lib/dist/lib/bedrockCompat.js +83 -0
- package/lib/dist/lib/bedrockCompat.js.map +1 -0
- package/lib/dist/lib/benchmarkCaseReview.d.ts +114 -0
- package/lib/dist/lib/benchmarkCaseReview.d.ts.map +1 -0
- package/lib/dist/lib/benchmarkCaseReview.js +177 -0
- package/lib/dist/lib/benchmarkCaseReview.js.map +1 -0
- package/lib/dist/lib/benchmarkImage.d.ts +52 -0
- package/lib/dist/lib/benchmarkImage.d.ts.map +1 -0
- package/lib/dist/lib/benchmarkImage.js +113 -0
- package/lib/dist/lib/benchmarkImage.js.map +1 -0
- package/lib/dist/lib/benchmarkRunsTable.d.ts +109 -0
- package/lib/dist/lib/benchmarkRunsTable.d.ts.map +1 -0
- package/lib/dist/lib/benchmarkRunsTable.js +212 -0
- package/lib/dist/lib/benchmarkRunsTable.js.map +1 -0
- package/lib/dist/lib/benchmarkVersionUtils.d.ts +13 -0
- package/lib/dist/lib/benchmarkVersionUtils.d.ts.map +1 -1
- package/lib/dist/lib/benchmarkVersionUtils.js +21 -0
- package/lib/dist/lib/benchmarkVersionUtils.js.map +1 -1
- package/lib/dist/lib/chunkedFetch.d.ts +18 -0
- package/lib/dist/lib/chunkedFetch.d.ts.map +1 -0
- package/lib/dist/lib/chunkedFetch.js +40 -0
- package/lib/dist/lib/chunkedFetch.js.map +1 -0
- package/lib/dist/lib/comparisonInsights.d.ts +151 -0
- package/lib/dist/lib/comparisonInsights.d.ts.map +1 -0
- package/lib/dist/lib/comparisonInsights.js +270 -0
- package/lib/dist/lib/comparisonInsights.js.map +1 -0
- package/lib/dist/lib/config/loader.d.ts.map +1 -1
- package/lib/dist/lib/config/loader.js +16 -1
- package/lib/dist/lib/config/loader.js.map +1 -1
- package/lib/dist/lib/config/types.d.ts +14 -0
- package/lib/dist/lib/config/types.d.ts.map +1 -1
- package/lib/dist/lib/constants.d.ts +11 -0
- package/lib/dist/lib/constants.d.ts.map +1 -1
- package/lib/dist/lib/constants.js +10 -1
- package/lib/dist/lib/constants.js.map +1 -1
- package/lib/dist/lib/contextFormat.d.ts +26 -0
- package/lib/dist/lib/contextFormat.d.ts.map +1 -0
- package/lib/dist/lib/contextFormat.js +28 -0
- package/lib/dist/lib/contextFormat.js.map +1 -0
- package/lib/dist/lib/dashboardMetrics.d.ts +11 -2
- package/lib/dist/lib/dashboardMetrics.d.ts.map +1 -1
- package/lib/dist/lib/dashboardMetrics.js +38 -3
- package/lib/dist/lib/dashboardMetrics.js.map +1 -1
- package/lib/dist/lib/envCompat.d.ts.map +1 -1
- package/lib/dist/lib/envCompat.js +14 -5
- package/lib/dist/lib/envCompat.js.map +1 -1
- package/lib/dist/lib/evaluationRerun.d.ts +102 -0
- package/lib/dist/lib/evaluationRerun.d.ts.map +1 -0
- package/lib/dist/lib/evaluationRerun.js +134 -0
- package/lib/dist/lib/evaluationRerun.js.map +1 -0
- package/lib/dist/lib/judgeFailureSummary.d.ts +66 -0
- package/lib/dist/lib/judgeFailureSummary.d.ts.map +1 -0
- package/lib/dist/lib/judgeFailureSummary.js +68 -0
- package/lib/dist/lib/judgeFailureSummary.js.map +1 -0
- package/lib/dist/lib/judgeStrategies.d.ts +108 -0
- package/lib/dist/lib/judgeStrategies.d.ts.map +1 -0
- package/lib/dist/lib/judgeStrategies.js +135 -0
- package/lib/dist/lib/judgeStrategies.js.map +1 -0
- package/lib/dist/lib/matchers/expect.d.ts +21 -1
- package/lib/dist/lib/matchers/expect.d.ts.map +1 -1
- package/lib/dist/lib/matchers/expect.js +51 -0
- package/lib/dist/lib/matchers/expect.js.map +1 -1
- package/lib/dist/lib/matchers/judgeAccessor.d.ts +4 -0
- package/lib/dist/lib/matchers/judgeAccessor.d.ts.map +1 -1
- package/lib/dist/lib/matchers/judgeAccessor.js +13 -2
- package/lib/dist/lib/matchers/judgeAccessor.js.map +1 -1
- package/lib/dist/lib/matchers/judgeReasoningParse.d.ts +49 -0
- package/lib/dist/lib/matchers/judgeReasoningParse.d.ts.map +1 -0
- package/lib/dist/lib/matchers/judgeReasoningParse.js +128 -0
- package/lib/dist/lib/matchers/judgeReasoningParse.js.map +1 -0
- package/lib/dist/lib/matchers/traces.d.ts +17 -2
- package/lib/dist/lib/matchers/traces.d.ts.map +1 -1
- package/lib/dist/lib/matchers/traces.js +136 -16
- package/lib/dist/lib/matchers/traces.js.map +1 -1
- package/lib/dist/lib/matchers/tracesPricing.d.ts +38 -0
- package/lib/dist/lib/matchers/tracesPricing.d.ts.map +1 -0
- package/lib/dist/lib/matchers/tracesPricing.js +64 -0
- package/lib/dist/lib/matchers/tracesPricing.js.map +1 -0
- package/lib/dist/lib/matchers/types.d.ts +25 -0
- package/lib/dist/lib/matchers/types.d.ts.map +1 -1
- package/lib/dist/lib/resolveCanonicalRun.d.ts +23 -0
- package/lib/dist/lib/resolveCanonicalRun.d.ts.map +1 -0
- package/lib/dist/lib/resolveCanonicalRun.js +26 -0
- package/lib/dist/lib/resolveCanonicalRun.js.map +1 -0
- package/lib/dist/lib/runActions.d.ts +120 -0
- package/lib/dist/lib/runActions.d.ts.map +1 -0
- package/lib/dist/lib/runActions.js +130 -0
- package/lib/dist/lib/runActions.js.map +1 -0
- package/lib/dist/lib/runInsights.d.ts +86 -0
- package/lib/dist/lib/runInsights.d.ts.map +1 -0
- package/lib/dist/lib/runInsights.js +185 -0
- package/lib/dist/lib/runInsights.js.map +1 -0
- package/lib/dist/lib/runName.d.ts +29 -0
- package/lib/dist/lib/runName.d.ts.map +1 -0
- package/lib/dist/lib/runName.js +38 -0
- package/lib/dist/lib/runName.js.map +1 -0
- package/lib/dist/lib/runReportPath.d.ts +16 -0
- package/lib/dist/lib/runReportPath.d.ts.map +1 -0
- package/lib/dist/lib/runReportPath.js +22 -0
- package/lib/dist/lib/runReportPath.js.map +1 -0
- package/lib/dist/lib/runSort.d.ts +27 -0
- package/lib/dist/lib/runSort.d.ts.map +1 -0
- package/lib/dist/lib/runSort.js +31 -0
- package/lib/dist/lib/runSort.js.map +1 -0
- package/lib/dist/lib/runStats.d.ts +109 -5
- package/lib/dist/lib/runStats.d.ts.map +1 -1
- package/lib/dist/lib/runStats.js +193 -10
- package/lib/dist/lib/runStats.js.map +1 -1
- package/lib/dist/lib/testCases/define.d.ts.map +1 -1
- package/lib/dist/lib/testCases/define.js +93 -46
- package/lib/dist/lib/testCases/define.js.map +1 -1
- package/lib/dist/lib/testCases/judge.d.ts.map +1 -1
- package/lib/dist/lib/testCases/judge.js +22 -4
- package/lib/dist/lib/testCases/judge.js.map +1 -1
- package/lib/dist/lib/testCases/loader.d.ts +35 -1
- package/lib/dist/lib/testCases/loader.d.ts.map +1 -1
- package/lib/dist/lib/testCases/loader.js +278 -31
- package/lib/dist/lib/testCases/loader.js.map +1 -1
- package/lib/dist/lib/trajectoryStepDisplay.d.ts +25 -0
- package/lib/dist/lib/trajectoryStepDisplay.d.ts.map +1 -0
- package/lib/dist/lib/trajectoryStepDisplay.js +42 -0
- package/lib/dist/lib/trajectoryStepDisplay.js.map +1 -0
- package/lib/dist/lib/utils.d.ts +34 -0
- package/lib/dist/lib/utils.d.ts.map +1 -1
- package/lib/dist/lib/utils.js +49 -0
- package/lib/dist/lib/utils.js.map +1 -1
- package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.d.ts +68 -22
- package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.d.ts.map +1 -1
- package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.js +148 -111
- package/lib/dist/services/connectors/claude-code/ClaudeCodeConnector.js.map +1 -1
- package/lib/dist/services/connectors/kiro/KiroConnector.d.ts +21 -13
- package/lib/dist/services/connectors/kiro/KiroConnector.d.ts.map +1 -1
- package/lib/dist/services/connectors/kiro/KiroConnector.js +22 -25
- package/lib/dist/services/connectors/kiro/KiroConnector.js.map +1 -1
- package/lib/dist/services/connectors/pi/PiConnector.d.ts +49 -10
- package/lib/dist/services/connectors/pi/PiConnector.d.ts.map +1 -1
- package/lib/dist/services/connectors/pi/PiConnector.js +102 -86
- package/lib/dist/services/connectors/pi/PiConnector.js.map +1 -1
- package/lib/dist/services/connectors/subprocess/SubprocessConnector.d.ts +59 -14
- package/lib/dist/services/connectors/subprocess/SubprocessConnector.d.ts.map +1 -1
- package/lib/dist/services/connectors/subprocess/SubprocessConnector.js +95 -61
- package/lib/dist/services/connectors/subprocess/SubprocessConnector.js.map +1 -1
- package/lib/dist/services/evaluation/bedrockJudge.d.ts +18 -0
- package/lib/dist/services/evaluation/bedrockJudge.d.ts.map +1 -1
- package/lib/dist/services/evaluation/bedrockJudge.js +4 -0
- package/lib/dist/services/evaluation/bedrockJudge.js.map +1 -1
- package/lib/dist/services/evaluation/index.d.ts +18 -1
- package/lib/dist/services/evaluation/index.d.ts.map +1 -1
- package/lib/dist/services/evaluation/index.js +156 -19
- package/lib/dist/services/evaluation/index.js.map +1 -1
- package/lib/dist/services/metrics.d.ts +55 -0
- package/lib/dist/services/metrics.d.ts.map +1 -0
- package/lib/dist/services/metrics.js +89 -0
- package/lib/dist/services/metrics.js.map +1 -0
- package/lib/dist/services/storage/asyncBenchmarkStorage.d.ts +1 -1
- package/lib/dist/services/storage/asyncBenchmarkStorage.d.ts.map +1 -1
- package/lib/dist/services/storage/asyncBenchmarkStorage.js +30 -3
- package/lib/dist/services/storage/asyncBenchmarkStorage.js.map +1 -1
- package/lib/dist/services/storage/asyncRunStorage.d.ts +20 -1
- package/lib/dist/services/storage/asyncRunStorage.d.ts.map +1 -1
- package/lib/dist/services/storage/asyncRunStorage.js +125 -2
- package/lib/dist/services/storage/asyncRunStorage.js.map +1 -1
- package/lib/dist/services/storage/asyncTestCaseStorage.d.ts +18 -4
- package/lib/dist/services/storage/asyncTestCaseStorage.d.ts.map +1 -1
- package/lib/dist/services/storage/asyncTestCaseStorage.js +21 -4
- package/lib/dist/services/storage/asyncTestCaseStorage.js.map +1 -1
- package/lib/dist/services/storage/opensearchClient.d.ts +47 -12
- package/lib/dist/services/storage/opensearchClient.d.ts.map +1 -1
- package/lib/dist/services/storage/opensearchClient.js +12 -5
- package/lib/dist/services/storage/opensearchClient.js.map +1 -1
- package/lib/dist/services/traces/browserRecovery.d.ts +12 -1
- package/lib/dist/services/traces/browserRecovery.d.ts.map +1 -1
- package/lib/dist/services/traces/browserRecovery.js +35 -5
- package/lib/dist/services/traces/browserRecovery.js.map +1 -1
- package/lib/dist/services/traces/index.d.ts +8 -1
- package/lib/dist/services/traces/index.d.ts.map +1 -1
- package/lib/dist/services/traces/index.js +33 -12
- package/lib/dist/services/traces/index.js.map +1 -1
- package/lib/dist/services/traces/judgeAgentsHints.d.ts +98 -3
- package/lib/dist/services/traces/judgeAgentsHints.d.ts.map +1 -1
- package/lib/dist/services/traces/judgeAgentsHints.js +144 -3
- package/lib/dist/services/traces/judgeAgentsHints.js.map +1 -1
- package/lib/dist/services/traces/messageExtraction.d.ts.map +1 -1
- package/lib/dist/services/traces/messageExtraction.js +95 -33
- package/lib/dist/services/traces/messageExtraction.js.map +1 -1
- package/lib/dist/services/traces/spansToTrajectory.d.ts.map +1 -1
- package/lib/dist/services/traces/spansToTrajectory.js +51 -13
- package/lib/dist/services/traces/spansToTrajectory.js.map +1 -1
- package/lib/dist/services/traces/tracePoller.d.ts +27 -3
- package/lib/dist/services/traces/tracePoller.d.ts.map +1 -1
- package/lib/dist/services/traces/tracePoller.js +196 -33
- package/lib/dist/services/traces/tracePoller.js.map +1 -1
- package/lib/dist/services/traces/trajectoryMerge.d.ts +79 -0
- package/lib/dist/services/traces/trajectoryMerge.d.ts.map +1 -0
- package/lib/dist/services/traces/trajectoryMerge.js +109 -0
- package/lib/dist/services/traces/trajectoryMerge.js.map +1 -0
- package/lib/dist/types/index.d.ts +250 -3
- package/lib/dist/types/index.d.ts.map +1 -1
- package/lib/dist/types/index.js +24 -0
- package/lib/dist/types/index.js.map +1 -1
- package/package.json +8 -5
- package/server/dist/app.js +5490 -1557
- package/server/dist/index.js +5490 -1557
- package/dist/assets/index-CCQRDlO0.js +0 -243
- package/dist/assets/index-CNHQVbcj.css +0 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Storage: OpenSearch index field-limit growth (`evals_runs`)
|
|
2
|
+
|
|
3
|
+
## Incident
|
|
4
|
+
|
|
5
|
+
Owner-hit while running code-QA benchmarks: report/run persistence on the
|
|
6
|
+
shared cluster's `evals_runs` index failed with
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
illegal_argument_exception: Limit of total fields [5000] has been exceeded
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Run **execution** succeeded — only the **write** errored out, i.e. data loss
|
|
13
|
+
(the completed report was never persisted).
|
|
14
|
+
|
|
15
|
+
This is the same *class* of bug PR #418 fixed for `evals_experiments`
|
|
16
|
+
(`EvaluationRun.results` / `testCaseSnapshots`): OpenSearch's default dynamic
|
|
17
|
+
mapping mints a new mapped field for every previously-unseen key under a
|
|
18
|
+
free-form object, and the field-count budget (`index.mapping.total_fields.limit`)
|
|
19
|
+
is **shared across every document in the index**, not per-document. #418 did
|
|
20
|
+
not cover `evals_runs` (the report/`TestCaseRun` index used by the code-SDK
|
|
21
|
+
path) — this fix does.
|
|
22
|
+
|
|
23
|
+
## Root cause: two unprotected growth vectors in `evals_runs`
|
|
24
|
+
|
|
25
|
+
Both are driven by the same source: `EvaluationMetrics`
|
|
26
|
+
(`types/index.ts`) is an open index signature (`[key: string]: number |
|
|
27
|
+
undefined`) by design — custom/system evaluators declare arbitrary metric
|
|
28
|
+
dimension names via `evaluator.scoringConfig.metrics`
|
|
29
|
+
(`server/services/judgeResponseParser.ts`'s `extractMetrics()`,
|
|
30
|
+
`services/storage/asyncRunStorage.ts`'s `storedMetricsToApp()` /
|
|
31
|
+
`toStorageFormat()` — see the comments in both, which explicitly call out
|
|
32
|
+
"preserve every metric the judge emitted, not just the four legacy keys").
|
|
33
|
+
Every *distinct* custom metric name, across every run/matcher ever written,
|
|
34
|
+
used to mint a brand-new mapped field, shared index-wide, forever.
|
|
35
|
+
|
|
36
|
+
| Field (in `evals_runs`) | Shape | Growth vector |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `metrics` (report-level) | `Record<string, number>` | One set of dynamic names per run — one custom evaluator with N metric names adds ≤N new fields **the first time it's seen**, but a code-QA benchmark suite iterating on many custom evaluators over time accumulates without bound. |
|
|
39
|
+
| `matcherResults[].judgeMetrics` | `Record<string, number>`, nested inside a `nested`-typed array | Same growth, but **per SDK `judge()` call** — a single code-QA test case with many `expect`/`judge()` claims × many custom judge dimensions multiplies fast. This is the "code-SDK path" referenced in the incident — `matcherResults` is populated exclusively by the code-based test SDK (`docs/SDK.md`), not the legacy UI-driven runner. |
|
|
40
|
+
|
|
41
|
+
Everything else already flagged in the original bug report — matcher
|
|
42
|
+
`actual`/`expected`, `trajectory`, `logs`, `rawEvents`, `improvementStrategies`,
|
|
43
|
+
`spans` (span attributes) — was **already** `{ type: 'object', enabled: false
|
|
44
|
+
}` in `server/constants/indexMappings.ts` before this change (audited, not
|
|
45
|
+
touched). `llmJudgeResponse` (which itself has an open `extraFields`/
|
|
46
|
+
`parsedMetrics` shape) is **never persisted** to `evals_runs` at all
|
|
47
|
+
(`toStorageFormat()` doesn't include it) — confirmed via `git grep
|
|
48
|
+
llmJudgeResponse services/storage server/adapters`, no hits — so it isn't a
|
|
49
|
+
growth vector for this index either.
|
|
50
|
+
|
|
51
|
+
## Fix (mirrors #418's pattern)
|
|
52
|
+
|
|
53
|
+
`server/constants/indexMappings.ts`, `evals_runs` index:
|
|
54
|
+
|
|
55
|
+
```diff
|
|
56
|
+
metrics: {
|
|
57
|
+
+ dynamic: false,
|
|
58
|
+
properties: {
|
|
59
|
+
accuracy: { type: 'float' },
|
|
60
|
+
faithfulness: { type: 'float' },
|
|
61
|
+
latency_score: { type: 'float' },
|
|
62
|
+
trajectory_alignment_score: { type: 'float' },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
...
|
|
66
|
+
matcherResults: {
|
|
67
|
+
type: 'nested',
|
|
68
|
+
properties: {
|
|
69
|
+
...
|
|
70
|
+
judgeMetrics: {
|
|
71
|
+
+ dynamic: false,
|
|
72
|
+
properties: {
|
|
73
|
+
accuracy: { type: 'float' },
|
|
74
|
+
faithfulness: { type: 'float' },
|
|
75
|
+
latency_score: { type: 'float' },
|
|
76
|
+
trajectory_alignment_score: { type: 'float' },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Unlike #418's `results`/`testCaseSnapshots` (`enabled: false`, fully opaque),
|
|
84
|
+
this uses `dynamic: false` **with explicit typed sub-properties** for the
|
|
85
|
+
four legacy metric names — they stay real, typed, queryable fields (nothing
|
|
86
|
+
queries them today — see the audit below — but it's free to keep them typed),
|
|
87
|
+
while every *other* metric/dimension name is stored in `_source` (readable,
|
|
88
|
+
unaffected) but never added to the mapping. `_source` is unaffected either
|
|
89
|
+
way — the choice between `enabled:false` and `dynamic:false` only changes
|
|
90
|
+
what OpenSearch can filter/sort/aggregate on, never what's persisted or
|
|
91
|
+
returned.
|
|
92
|
+
|
|
93
|
+
## Query audit — nothing queried becomes unsearchable
|
|
94
|
+
|
|
95
|
+
Every OpenSearch-level query/filter/sort/aggregation against `evals_runs`
|
|
96
|
+
(`server/adapters/opensearch/StorageModule.ts`'s `OpenSearchRunOperations`)
|
|
97
|
+
was enumerated. None touch `metrics.*` or `matcherResults[].judgeMetrics.*`
|
|
98
|
+
beyond the four legacy names, which stay mapped:
|
|
99
|
+
|
|
100
|
+
| Consumer | Query | Fields used | Affected by this fix? |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| `OpenSearchRunOperations.search()` | `term` filters | `experimentId`, `experimentRunId`, `testCaseId`, `agentId`, `modelId`, `status`, `passFailStatus` | No — untouched, still explicit `keyword` fields |
|
|
103
|
+
| `OpenSearchRunOperations.search()` | `range` filter | `createdAt` | No — untouched, still `date` |
|
|
104
|
+
| `OpenSearchRunOperations.getAll()` / `.search()` | `sort` | `createdAt` | No |
|
|
105
|
+
| `OpenSearchRunOperations.countsByTestCase()` | `terms` agg | `testCaseId` | No |
|
|
106
|
+
| `asyncRunStorage.ts` `SearchQuery.minAccuracy` | **application-level** `Array.filter()`, not an OpenSearch query (`reports.filter(r => r.metrics.accuracy >= ...)`) | `metrics.accuracy` (read from `_source` in JS) | No — reads the value out of `_source`, which is unaffected by `dynamic: false`. If this were ever converted to a server-side `range` query, it would still work: `accuracy` stays an explicitly mapped, queryable field. |
|
|
107
|
+
| UI (`MatcherResultsPanel.tsx`, `JudgeSection.tsx`, `RunDetailsContent.tsx`) | none — reads `matcherResults`/`judgeMetrics` out of the fetched JSON document, never issues its own OpenSearch query | n/a | No |
|
|
108
|
+
| `services/evaluation/index.ts`, `services/benchmarkRunner.ts`, `services/hookOrchestrator.ts` | none — same, in-process consumption of the already-fetched report | n/a | No |
|
|
109
|
+
|
|
110
|
+
Conclusion: **no consumer anywhere issues an OpenSearch-side query against a
|
|
111
|
+
non-legacy `metrics.*` or `judgeMetrics.*` name.** Both are read back via
|
|
112
|
+
`_source` wherever consumed (search, list, comparison, UI). This mirrors
|
|
113
|
+
exactly the trade-off #418 already made and documented for
|
|
114
|
+
`EvaluationRun.results`.
|
|
115
|
+
|
|
116
|
+
## Migration story — what to run, exactly
|
|
117
|
+
|
|
118
|
+
**Nothing runs automatically against the live cluster from this PR.**
|
|
119
|
+
|
|
120
|
+
### New / fresh indexes
|
|
121
|
+
|
|
122
|
+
No action needed. `ensureIndexes()` (`server/services/indexInitializer.ts`,
|
|
123
|
+
called on every server boot and on "attach new cluster") creates any missing
|
|
124
|
+
index straight from the updated `INDEX_MAPPINGS` — new deployments and any
|
|
125
|
+
environment that doesn't have `evals_runs` yet get the fix immediately.
|
|
126
|
+
|
|
127
|
+
### Existing, NOT-YET-poisoned `evals_runs` (most environments)
|
|
128
|
+
|
|
129
|
+
Also no action needed, but not immediate — `ensureIndexes()` also calls
|
|
130
|
+
`client.indices.putMapping()` on every boot for existing indexes, which is
|
|
131
|
+
how the `dynamic: false` fix reaches an already-existing-but-clean index: it
|
|
132
|
+
succeeds silently and the index is protected from the next write onward.
|
|
133
|
+
|
|
134
|
+
### The shared cluster's `evals_runs`, if already poisoned
|
|
135
|
+
|
|
136
|
+
If any code-QA benchmark run already wrote a custom evaluator metric name to
|
|
137
|
+
the shared cluster before this fix ships, `evals_runs.metrics` (and/or
|
|
138
|
+
`matcherResults.judgeMetrics`) already has real, dynamically-inferred
|
|
139
|
+
sub-properties. OpenSearch's `putMapping` **rejects** an `enabled`/`dynamic`
|
|
140
|
+
change on a field that already has sub-properties
|
|
141
|
+
(`mapper_exception: the [dynamic] parameter can't be updated for the object
|
|
142
|
+
mapping [metrics]`) — `ensureIndexes()` catches this, logs a warning, and
|
|
143
|
+
otherwise no-ops (no crash, no data loss, same as #418's documented
|
|
144
|
+
`mapper_exception` handling for `results`). **The index keeps growing** until
|
|
145
|
+
an explicit reindex is run.
|
|
146
|
+
|
|
147
|
+
**This is not new migration code** — the existing generic reindex mechanism
|
|
148
|
+
(`reindexSingleIndex()`, `server/services/mappingFixer.ts`, already shipped
|
|
149
|
+
and already exposed at `POST /api/storage/reindex`, `server/routes/storage/admin.ts`)
|
|
150
|
+
already recreates any `INDEX_MAPPINGS`-registered index from scratch and
|
|
151
|
+
copies every document across, which sheds a poisoned mapping's dynamically-
|
|
152
|
+
inferred sub-fields while preserving 100% of the underlying `_source` data
|
|
153
|
+
(proven in `tests/integration/services/storage/evalsRunsMappingMigrationRecipe.integration.test.ts`,
|
|
154
|
+
run against a real OpenSearch container with a deliberately-poisoned index).
|
|
155
|
+
|
|
156
|
+
**The owner's exact recipe, when ready to run it against the shared cluster:**
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# 1. Confirm the target index actually needs it (optional sanity check):
|
|
160
|
+
curl -s -X GET "$OPENSEARCH_STORAGE_ENDPOINT/evals_runs/_mapping" \
|
|
161
|
+
-u "$OPENSEARCH_STORAGE_USERNAME:$OPENSEARCH_STORAGE_PASSWORD" \
|
|
162
|
+
| jq '.evals_runs.mappings.properties.metrics'
|
|
163
|
+
# If this prints a `dynamic` key, it's already fixed. If it prints only
|
|
164
|
+
# `properties` with more than the 4 legacy metric names, it's poisoned.
|
|
165
|
+
|
|
166
|
+
# 2. Run the reindex via the running server's admin API (recreates the
|
|
167
|
+
# index from the current INDEX_MAPPINGS and copies every document
|
|
168
|
+
# across; the same doc-count-validated, recovery-safe path
|
|
169
|
+
# reindexSingleIndex() has always used for keyword-type mismatch fixes):
|
|
170
|
+
curl -s -X POST "http://localhost:4001/api/storage/reindex" \
|
|
171
|
+
-H 'Content-Type: application/json' \
|
|
172
|
+
-d '{"index": "evals_runs"}'
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Caveats to read before running this against the shared cluster:
|
|
176
|
+
|
|
177
|
+
- **No write lock during a manual `/api/storage/reindex` call.** The
|
|
178
|
+
auto-fix boot path (`fixIndexMappings()`) acquires a process-local
|
|
179
|
+
migration lock around the reindex; the manual admin route calls
|
|
180
|
+
`reindexSingleIndex()` directly and does **not** (pre-existing gap in
|
|
181
|
+
`server/routes/storage/admin.ts`, not introduced by this PR — flagged here,
|
|
182
|
+
not fixed, since it's out of scope for this change). Run it during a quiet
|
|
183
|
+
window (no in-flight evaluation runs writing reports) to avoid a write
|
|
184
|
+
racing the index delete/recreate step.
|
|
185
|
+
- It touches only `evals_runs`. The already-known-poisoned `evals_experiments`
|
|
186
|
+
(800+ stale `results.*` fields, per the incident notes) uses the identical
|
|
187
|
+
recipe (`{"index": "evals_experiments"}`) — that cleanup is separately
|
|
188
|
+
planned by ops; this PR does not touch or schedule it.
|
|
189
|
+
- Document count is validated before the temporary index is deleted; if the
|
|
190
|
+
copy-back count doesn't match, the error message names the surviving temp
|
|
191
|
+
index (`evals_runs_reindex_temp`) for manual recovery — nothing is deleted
|
|
192
|
+
until the counts are confirmed equal.
|
|
193
|
+
|
|
194
|
+
## Tests
|
|
195
|
+
|
|
196
|
+
- Unit (`tests/unit/server/constants/indexMappings.test.ts`): mapping-shape
|
|
197
|
+
assertions — `dynamic: false` + typed legacy properties on both `metrics`
|
|
198
|
+
and `matcherResults.judgeMetrics`; pre-existing `enabled:false` fields stay
|
|
199
|
+
disabled; every field `OpenSearchRunOperations.search()` queries stays
|
|
200
|
+
explicitly mapped.
|
|
201
|
+
- Integration, real OpenSearch
|
|
202
|
+
(`tests/integration/services/storage/testCaseRunMetricsMappingGrowth.integration.test.ts`):
|
|
203
|
+
writes one report with 1000+ distinct custom `metrics`/`judgeMetrics` names
|
|
204
|
+
(500 report-level + 125 `judge()` calls × 4 dimensions), asserts it
|
|
205
|
+
round-trips correctly and the index's total mapped-field count does not
|
|
206
|
+
grow; asserts the query-audit fields stay queryable.
|
|
207
|
+
- Integration, real OpenSearch, migration recipe
|
|
208
|
+
(`tests/integration/services/storage/evalsRunsMappingMigrationRecipe.integration.test.ts`):
|
|
209
|
+
deliberately poisons a throwaway index the old way, runs the *existing*
|
|
210
|
+
`reindexSingleIndex()`, asserts the mapping resets to `dynamic: false` and
|
|
211
|
+
all document data survives byte-for-byte.
|
|
212
|
+
|
|
213
|
+
Both integration suites skip gracefully (with a console warning) if no
|
|
214
|
+
OpenSearch cluster is reachable at `TEST_OPENSEARCH_ENDPOINT` (default
|
|
215
|
+
`http://localhost:9200`) — the unit suite covers the mapping-shape assertions
|
|
216
|
+
unconditionally.
|
|
@@ -167,7 +167,7 @@ run a JSON file:
|
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
169
|
# `-f` accepts BOTH JSON test-case files and code SDK (.eval.js / .eval.ts) files
|
|
170
|
-
npx @opensearch-project/agent-health benchmark -f ./
|
|
170
|
+
npx @opensearch-project/agent-health benchmark -f ./examples/eval-files/demo.eval.js -a my-agent
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
They produce **per-matcher results** (`matcherResults[]`) instead of a single
|
|
@@ -303,6 +303,60 @@ Repeat until all high-priority issues are resolved.
|
|
|
303
303
|
| Server / config issues | `npx agent-health doctor` (checks config + connectivity) |
|
|
304
304
|
| "OpenSearch storage not configured" | Fine for local use — file-based storage is the default. Set `OPENSEARCH_STORAGE_*` only for shared / production persistence. |
|
|
305
305
|
|
|
306
|
+
> **Note (benchmark execution vs. reads):** file-based storage covers reads,
|
|
307
|
+
> single `run`s, and sample data, but **executing a multi-case `benchmark`
|
|
308
|
+
> currently requires an OpenSearch storage client** — without one the execute
|
|
309
|
+
> path returns `Cannot execute in sample-only mode`. Point `OPENSEARCH_STORAGE_*`
|
|
310
|
+
> at a cluster (a local security-disabled Docker OpenSearch on plain HTTP with
|
|
311
|
+
> `authType=none` is enough) before running `benchmark`.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Benchmarking a local (subprocess) agent — gotchas
|
|
316
|
+
|
|
317
|
+
When you wrap a local CLI agent (Kiro, Claude Code, Pi, or your own script) as a
|
|
318
|
+
**subprocess** agent and run a `benchmark`, these are the traps that bite first
|
|
319
|
+
— check them before blaming the agent:
|
|
320
|
+
|
|
321
|
+
1. **Pin a real judge model — don't leave it on `demo`.** The `demo` provider is
|
|
322
|
+
a mock judge that returns high pass rates without calling an LLM, so a run
|
|
323
|
+
can look like "100% pass" while nothing was actually judged. Set the run's
|
|
324
|
+
judge model to a real Bedrock model, e.g.
|
|
325
|
+
`us.anthropic.claude-sonnet-4-5-20250929-v1:0`, and confirm it's invocable in
|
|
326
|
+
your account (`aws bedrock ... ` / `doctor`). If your pass rate looks too
|
|
327
|
+
good, check the judge provider first.
|
|
328
|
+
2. **Raise the subprocess timeout for slow agents.** The subprocess connector
|
|
329
|
+
defaults to a 5-minute (`300000` ms) timeout. A real ops/RCA agent can run
|
|
330
|
+
~10 min. Set it in your agent's `connectorConfig`:
|
|
331
|
+
```ts
|
|
332
|
+
{ key: 'my-ops-agent', connectorType: 'subprocess',
|
|
333
|
+
connectorConfig: { timeout: 1200000 /* 20 min */, /* ... */ } }
|
|
334
|
+
```
|
|
335
|
+
3. **Fail loud on a wrong agent name.** If your wrapper points at an agent key
|
|
336
|
+
that doesn't exist, the underlying CLI may silently fall back to a default
|
|
337
|
+
agent — so you benchmark the wrong thing. Echo the resolved agent name in
|
|
338
|
+
your wrapper and eyeball the first trajectory.
|
|
339
|
+
4. **Long runs + CLI SSE disconnects.** On runs longer than a few minutes the
|
|
340
|
+
CLI's streaming connection can drop and report `0/0` / `fetch failed` **while
|
|
341
|
+
the server keeps going**. The results are still persisted — read them back
|
|
342
|
+
from storage (`list runs` / the UI run inspector / `--export`) rather than
|
|
343
|
+
trusting the CLI summary.
|
|
344
|
+
5. **Watch for a stale server on the port.** If you patch/upgrade the package
|
|
345
|
+
but a previously-started `npx` server is still bound to port 4001, your runs
|
|
346
|
+
are served by the old code. Confirm which process owns the port
|
|
347
|
+
(`lsof -i :4001` / a `/proc` sweep) and restart the one you actually patched
|
|
348
|
+
(the server loads `server/dist/app.js`, not `index.js`).
|
|
349
|
+
6. **Judge `CredentialsProviderError` after a session rotates.** If your AWS
|
|
350
|
+
sandbox session rotates, `AWS_SHARED_CREDENTIALS_FILE` can point at a stale/
|
|
351
|
+
empty file and the judge fails. Re-vend credentials and restart the server
|
|
352
|
+
with the AWS env explicit (`aws sts get-caller-identity` to confirm first).
|
|
353
|
+
7. **Local OpenSearch dying (exit 255) = memory pressure.** Give the container
|
|
354
|
+
enough heap and run it with `--restart=unless-stopped`.
|
|
355
|
+
8. **Case schema is strict.** Test cases need `expectedOutcomes` (a
|
|
356
|
+
**string[]**, not a singular `expectedOutcome` string) and a capitalized
|
|
357
|
+
`difficulty` (`Easy` | `Medium` | `Hard`). Validate your converter output
|
|
358
|
+
against a known-good case before importing 16 of them.
|
|
359
|
+
|
|
306
360
|
---
|
|
307
361
|
|
|
308
362
|
## Server API Reference
|
|
@@ -62,7 +62,11 @@ connectorRegistry.register(new CustomConnector());
|
|
|
62
62
|
- `propagateHeader: true` → inject a `traceparent` HTTP header into HTTP/SSE agents (`injectTraceparentHeaders()`).
|
|
63
63
|
- `serviceName: '<otel-service-name>'` → service-name + time-window fallback. Defaults: `claude-code-agent`, `kiro-agent`, `pi-agent`, `observio-sample-agent`. See the "Trace correlation conventions" section in `AGENTS.md`.
|
|
64
64
|
- **Subprocess connectors** (`SubprocessConnector` subclasses) can override
|
|
65
|
-
`parseStderrChunk(chunk)` to turn stderr markers into trajectory steps (how
|
|
65
|
+
`parseStderrChunk(chunk, trajectory, onProgress, state)` to turn stderr markers into trajectory steps (how
|
|
66
66
|
`kiro` surfaces `[tool] Running:` / `[tool] status:` as `action` +
|
|
67
67
|
`tool_result` steps). The base class persists `stderr` to `rawOutput` and
|
|
68
68
|
honors per-request `connectorConfig` overrides (`args` / `inputMode` / `timeout`).
|
|
69
|
+
Keep ALL streaming state (partial-line buffers, pending tool names, captured
|
|
70
|
+
ids) on the per-invocation `state` object (extend `SubprocessExecutionState`
|
|
71
|
+
via `createExecutionState()`), never on `this` — the registry shares one
|
|
72
|
+
connector instance across concurrent runs.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* -H 'Content-Type: application/json' \
|
|
19
19
|
* -d '{
|
|
20
20
|
* "name":"SDK Demo",
|
|
21
|
-
* "sources":[{"type":"code-import","filenames":["
|
|
21
|
+
* "sources":[{"type":"code-import","filenames":["examples/eval-files/demo.eval.js"],"testCaseIds":[]}],
|
|
22
22
|
* "agentKey":"observio",
|
|
23
23
|
* "modelId":"claude-sonnet"
|
|
24
24
|
* }'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright OpenSearch Contributors
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Worked example — evaluating an ops / RCA agent that triages a ticket.
|
|
8
|
+
*
|
|
9
|
+
* The agent under test reads a ticket and emits a structured classification:
|
|
10
|
+
* - ticketType: "latency" | "fault" (deterministic — exact match)
|
|
11
|
+
* - rootCause: one of a fixed category set (deterministic — exact match)
|
|
12
|
+
* - sop: a recommended runbook (non-deterministic — LLM judge)
|
|
13
|
+
*
|
|
14
|
+
* This shows the two check styles side by side:
|
|
15
|
+
* • deterministic → expect(...).to.equal(...) on the parsed output
|
|
16
|
+
* • non-deterministic → judge(result, '<natural-language claim>')
|
|
17
|
+
*
|
|
18
|
+
* WEIGHTED SCORING (root-cause 60%, SOP, metrics, latency 10%):
|
|
19
|
+
* per-matcher pass/fail lives here, but the *single weighted aggregate score*
|
|
20
|
+
* across criteria is defined once in a custom EVALUATOR, not in the test body.
|
|
21
|
+
* Attach it at run time with `evaluatorId` — see ops-rca-evaluator.json next to
|
|
22
|
+
* this file. Run:
|
|
23
|
+
*
|
|
24
|
+
* npx @opensearch-project/agent-health benchmark \
|
|
25
|
+
* -f ./examples/eval-files/ops-rca-classification.eval.js -a my-ops-agent
|
|
26
|
+
*
|
|
27
|
+
* Docs: ../../docs/SDK.md Instrumentation: ../../docs/INSTRUMENT_WITH_OTEL.md
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const { test, expect } = require('@opensearch-project/agent-health');
|
|
31
|
+
|
|
32
|
+
// The agent's allowed root-cause categories — deterministic ground truth.
|
|
33
|
+
const ROOT_CAUSE_CATEGORIES = [
|
|
34
|
+
'dependency_outage',
|
|
35
|
+
'resource_exhaustion',
|
|
36
|
+
'config_error',
|
|
37
|
+
'code_regression',
|
|
38
|
+
'network',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
test('ticket-123-db-outage', {
|
|
42
|
+
prompt: 'Triage ticket TICKET-123 and return your classification table.',
|
|
43
|
+
description: 'DB dependency outage — must classify as fault + dependency_outage',
|
|
44
|
+
context: [
|
|
45
|
+
{
|
|
46
|
+
description: 'Ticket body',
|
|
47
|
+
value:
|
|
48
|
+
'TICKET-123: payment-service returning 500s since 10:30. ' +
|
|
49
|
+
'Logs: "Connection refused to database-primary:5432". p99 latency normal until errors began.',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
labels: ['category:RCA', 'difficulty:Medium', 'agent:ops', 'type:fault'],
|
|
53
|
+
}, async function ({ agent, judge }) {
|
|
54
|
+
const result = await agent.run();
|
|
55
|
+
|
|
56
|
+
// ── Deterministic checks — exact classification, no LLM, $0 ──────────────
|
|
57
|
+
const out = result.parsedOutput() || {}; // agent emits JSON classification
|
|
58
|
+
expect(out.ticketType).to.equal('fault');
|
|
59
|
+
expect(ROOT_CAUSE_CATEGORIES).to.include(out.rootCause);
|
|
60
|
+
expect(out.rootCause).to.equal('dependency_outage');
|
|
61
|
+
|
|
62
|
+
// Prove it actually investigated rather than guessing.
|
|
63
|
+
expect(result.trajectory).to.haveStepsOfType('action');
|
|
64
|
+
|
|
65
|
+
// ── Non-deterministic checks — LLM judge on the free-text SOP ────────────
|
|
66
|
+
await judge(result, 'Recommends a runbook appropriate for a database dependency outage');
|
|
67
|
+
await judge(result, 'Explains that payment-service cannot reach database-primary as the root cause');
|
|
68
|
+
|
|
69
|
+
// ── Budget guard (feeds the "latency 10%" weight via the evaluator) ──────
|
|
70
|
+
expect(result).to.haveCompletedWithin(120_000);
|
|
71
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Ops RCA (weighted)",
|
|
3
|
+
"description": "Weighted scoring for a ticket-triage / RCA ops agent. Root-cause accuracy dominates; latency is a small tie-breaker. Attach to a run via evaluatorId.",
|
|
4
|
+
"systemPrompt": "You are evaluating an ops agent that triages a support ticket. The agent must (1) classify the ticket type (latency vs fault), (2) identify the root cause from a fixed category set, (3) recommend an appropriate SOP/runbook, and (4) check the relevant metrics. Score each metric from 0 to 100.\n\nCRITICAL CRITERIA:\n- root_cause_accuracy is the PRIMARY metric. A wrong root cause is a failed triage regardless of everything else.\n- sop_selection: did the agent recommend a runbook appropriate for the identified root cause?\n- relevant_metrics: did the agent inspect the metrics/logs that actually matter for this failure mode?\n- latency: score higher when the agent reaches a correct answer in fewer steps / less wall-clock time.\n\nReturn pass_fail_status, reasoning, a metrics object with the four numeric scores, and improvement_strategies.",
|
|
5
|
+
"scoringConfig": {
|
|
6
|
+
"metrics": [
|
|
7
|
+
{ "name": "root_cause_accuracy", "weight": 0.6, "scale": 100 },
|
|
8
|
+
{ "name": "sop_selection", "weight": 0.2, "scale": 100 },
|
|
9
|
+
{ "name": "relevant_metrics", "weight": 0.1, "scale": 100 },
|
|
10
|
+
{ "name": "latency", "weight": 0.1, "scale": 100 }
|
|
11
|
+
],
|
|
12
|
+
"passThreshold": 80,
|
|
13
|
+
"scale": 100
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright OpenSearch Contributors
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SDK demo eval — three test cases that show the spectrum of evaluation
|
|
8
|
+
* methods the code-based SDK supports:
|
|
9
|
+
*
|
|
10
|
+
* 1. mock-says-hello (deterministic) — agent invoked, only chai matchers
|
|
11
|
+
* 2. mock-rca-judged (agentic) — agent invoked + LLM judge matcher
|
|
12
|
+
* 3. data-only-no-prompt (deterministic) — no agent call at all
|
|
13
|
+
*
|
|
14
|
+
* Run with:
|
|
15
|
+
* AH_PORT=4002 npx @opensearch-project/agent-health benchmark \
|
|
16
|
+
* -f examples/eval-files/sdk-demo.eval.js -a demo
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { test, expect } = require('@opensearch-project/agent-health');
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// 1. Deterministic — agent runs, all assertions are local chai matchers
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
test('mock-says-hello', {
|
|
26
|
+
prompt: 'Say hello in one short sentence.',
|
|
27
|
+
description: 'Mock agent must produce a non-empty response within 30s',
|
|
28
|
+
labels: ['category:Smoke', 'difficulty:Easy', 'method:deterministic'],
|
|
29
|
+
}, async function ({ agent }) {
|
|
30
|
+
const result = await agent.run();
|
|
31
|
+
expect(result.trajectory).to.have.length.greaterThan(0);
|
|
32
|
+
expect(result.agentOutput.trim()).to.have.length.greaterThan(0);
|
|
33
|
+
expect(result).to.haveCompletedWithin(30_000);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
// 2. Agentic (hybrid) — deterministic preflight + LLM judge for semantic claim
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
test('mock-rca-judged', {
|
|
41
|
+
prompt: 'Diagnose why the payment service is failing and explain the root cause.',
|
|
42
|
+
description: 'Hybrid: structural checks first, then LLM judge for semantic correctness',
|
|
43
|
+
context: [
|
|
44
|
+
{
|
|
45
|
+
description: 'Error log',
|
|
46
|
+
value: 'ERROR 2026-05-20 10:31:22 [payment-service] Connection refused to db-primary:5432',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
labels: ['category:RCA', 'difficulty:Medium', 'method:agentic'],
|
|
50
|
+
}, async function ({ agent, judge }) {
|
|
51
|
+
const result = await agent.run();
|
|
52
|
+
|
|
53
|
+
// Cheap deterministic preflight — fail fast before spending $ on the judge
|
|
54
|
+
expect(result.trajectory).to.have.length.greaterThan(0);
|
|
55
|
+
expect(result).to.haveCompletedWithin(60_000);
|
|
56
|
+
|
|
57
|
+
// LLM judge — produces a structured matcher verdict with score + reasoning
|
|
58
|
+
await judge(result, 'Mentions the payment service or its database connection failure');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
62
|
+
// 3. Deterministic, no prompt — agent never invoked, $0 / 0ms agent step
|
|
63
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
test('data-only-no-prompt', {
|
|
66
|
+
description: 'Pure data check; agent invocation skipped entirely',
|
|
67
|
+
labels: ['category:Data Quality', 'difficulty:Easy', 'method:deterministic'],
|
|
68
|
+
}, function ({ result }) {
|
|
69
|
+
expect(result.durationMs).to.equal(0);
|
|
70
|
+
expect(result.trajectory).to.have.length(0);
|
|
71
|
+
expect(2 + 2).to.equal(4);
|
|
72
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright OpenSearch Contributors
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SDK demo with describe() grouping. Each describe() block declares a
|
|
8
|
+
* benchmark; CLI auto-creates one benchmark per group and runs them.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { describe, test, expect } = require('@opensearch-project/agent-health');
|
|
12
|
+
|
|
13
|
+
describe('RCA Suite', () => {
|
|
14
|
+
test('payment service is the root cause', {
|
|
15
|
+
prompt: 'Diagnose why the payment service is failing.',
|
|
16
|
+
context: [{ description: 'Error log', value: 'ERROR [payment-service] Connection refused to db-primary:5432' }],
|
|
17
|
+
labels: ['category:RCA', 'difficulty:Medium'],
|
|
18
|
+
}, async function ({ agent, judge }) {
|
|
19
|
+
const result = await agent.run();
|
|
20
|
+
expect(result.trajectory).to.have.length.greaterThan(0);
|
|
21
|
+
expect(result).to.haveCompletedWithin(60_000);
|
|
22
|
+
await judge(result, 'Mentions the payment service or its database connection failure');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('budget cap', {
|
|
26
|
+
prompt: 'Summarize the system in one paragraph.',
|
|
27
|
+
labels: ['category:Budget', 'difficulty:Easy'],
|
|
28
|
+
}, async function ({ agent, traces }) {
|
|
29
|
+
const result = await agent.run();
|
|
30
|
+
expect(result.agentOutput.length).to.be.greaterThan(0);
|
|
31
|
+
expect(traces.totalTokens).to.be.lessThan(50_000);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('Smoke', () => {
|
|
36
|
+
test('says hello', {
|
|
37
|
+
prompt: 'Say hello in one short sentence.',
|
|
38
|
+
labels: ['category:Smoke', 'difficulty:Easy'],
|
|
39
|
+
}, async function ({ agent }) {
|
|
40
|
+
const result = await agent.run();
|
|
41
|
+
expect(result.trajectory).to.have.length.greaterThan(0);
|
|
42
|
+
expect(result.agentOutput.trim()).to.have.length.greaterThan(0);
|
|
43
|
+
expect(result).to.haveCompletedWithin(30_000);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Tests outside any describe() — go to the file-default benchmark
|
|
48
|
+
test('orphan-deterministic', function () {
|
|
49
|
+
expect(2 + 2).to.equal(4);
|
|
50
|
+
});
|