@root-signals/scorable-cli 0.8.0 → 0.9.1
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 +104 -0
- package/dist/commands/otel-filter/create.d.ts.map +1 -1
- package/dist/commands/otel-filter/create.js +89 -40
- package/dist/commands/otel-filter/index.d.ts.map +1 -1
- package/dist/commands/otel-filter/index.js +4 -0
- package/dist/commands/otel-filter/update.d.ts +3 -0
- package/dist/commands/otel-filter/update.d.ts.map +1 -0
- package/dist/commands/otel-filter/update.js +53 -0
- package/dist/commands/otel-filter/validate.d.ts +3 -0
- package/dist/commands/otel-filter/validate.d.ts.map +1 -0
- package/dist/commands/otel-filter/validate.js +72 -0
- package/dist/lib/filter-yaml.d.ts +179 -0
- package/dist/lib/filter-yaml.d.ts.map +1 -0
- package/dist/lib/filter-yaml.js +111 -0
- package/examples/otel-filters/claude-code.yaml +31 -0
- package/examples/otel-filters/genai-explicit.yaml +16 -0
- package/examples/otel-filters/openinference-agent.yaml +17 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -302,6 +302,110 @@ scorable otel-filter list
|
|
|
302
302
|
scorable otel-filter delete <filter_id>
|
|
303
303
|
```
|
|
304
304
|
|
|
305
|
+
### Custom extractor rules (when input/output isn't in `gen_ai.*`)
|
|
306
|
+
|
|
307
|
+
For the common case the flags above are everything you need: matching traces are evaluated and their `gen_ai.input.messages` / `gen_ai.output.messages` are fed to the evaluator. If your traces don't follow that shape — Claude Code, OpenInference, custom instrumentations — you tell the evaluator where input/output live by attaching **`extractor_rules`** to the filter. Filters without `extractor_rules` keep the default behavior.
|
|
308
|
+
|
|
309
|
+
Rules are carried in a YAML manifest and applied with `-f`:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
scorable otel-filter create -f filter.yaml
|
|
313
|
+
scorable otel-filter update <id> -f filter.yaml
|
|
314
|
+
scorable otel-filter validate -f filter.yaml # dry-run; exit 2 on schema error
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
CLI flags override values from the file when both are provided.
|
|
318
|
+
|
|
319
|
+
#### Manifest shape
|
|
320
|
+
|
|
321
|
+
```yaml
|
|
322
|
+
name: <string>
|
|
323
|
+
evaluator_id: <uuid> # exactly one of evaluator_id / judge_id
|
|
324
|
+
judge_id: <uuid>
|
|
325
|
+
sampling_rate: <0.0-1.0> # optional, default 1.0
|
|
326
|
+
delay_seconds: <int> # optional, default 10
|
|
327
|
+
is_active: <bool> # optional, default true
|
|
328
|
+
filter_criteria: # which traces to evaluate (same shape as --filter-criteria)
|
|
329
|
+
conditions:
|
|
330
|
+
- column: <string> # span_name, has_error, kind, status, attribute, resource, …
|
|
331
|
+
operator: <string> # =, !=, contains, starts with, any of, none of, …
|
|
332
|
+
value: <string|number|bool>
|
|
333
|
+
key: <string> # required for "attribute" / "resource" sentinel columns
|
|
334
|
+
extractor_rules: # optional; how to extract input/output from matching spans
|
|
335
|
+
- emit: <text|request_response|tool_pair|genai_messages>
|
|
336
|
+
match: # optional; same shape as filter_criteria — empty matches every span
|
|
337
|
+
conditions: [...]
|
|
338
|
+
# … emit-specific fields, see below …
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
#### `extractor_rules` reference
|
|
342
|
+
|
|
343
|
+
Each rule has an `emit` kind, an optional `match` filter (same shape as `filter_criteria.conditions`), and emit-specific fields. Spans are walked in timestamp order; per span, the **first** rule whose `match` passes wins.
|
|
344
|
+
|
|
345
|
+
A rule set must be able to produce both user-side and agent-side content (a `request_response` or `genai_messages` rule alone qualifies; a single `text` `role: user` rule does not). Validation rejects sets that can't.
|
|
346
|
+
|
|
347
|
+
**`text`** — emit one `MessageTurn` per matching span.
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
- emit: text
|
|
351
|
+
match: # optional
|
|
352
|
+
conditions:
|
|
353
|
+
- { column: span_name, operator: "=", value: claude_code.interaction }
|
|
354
|
+
role: user # user | assistant
|
|
355
|
+
locator: # where the value lives
|
|
356
|
+
kind: span_attr # span_attr | event_attr | resource_attr
|
|
357
|
+
key: user_prompt # attribute name
|
|
358
|
+
event_name: <string> # required when kind: event_attr
|
|
359
|
+
value_path: $.foo # optional JSONPath into the located value
|
|
360
|
+
tool_name: <string> # only valid when role: assistant
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**`request_response`** — emit a user turn + an assistant turn from one span. Common when an agent stores prompt and response as flat attributes (OpenInference's `input.value` / `output.value`).
|
|
364
|
+
|
|
365
|
+
```yaml
|
|
366
|
+
- emit: request_response
|
|
367
|
+
match: { ... } # optional
|
|
368
|
+
input_locator: { kind: span_attr, key: input.value } # → user turn
|
|
369
|
+
output_locator: { kind: span_attr, key: output.value } # → assistant turn
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**`tool_pair`** — emit one assistant turn per matching span carrying a tool call (input + output combined into the turn content). Built for tool-execution spans like Claude Code's `claude_code.tool` event.
|
|
373
|
+
|
|
374
|
+
```yaml
|
|
375
|
+
- emit: tool_pair
|
|
376
|
+
match: { ... } # optional
|
|
377
|
+
input_locator: { kind: event_attr, event_name: tool.output, key: bash_command }
|
|
378
|
+
output_locator: { kind: event_attr, event_name: tool.output, key: output }
|
|
379
|
+
tool_name: Bash # static; OR
|
|
380
|
+
tool_name_locator: { kind: span_attr, key: tool_name } # dynamic
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**`genai_messages`** — same parsing as the default extractor, but on attribute keys you choose. Use this if your service emits the standard `gen_ai` JSON-array shape under non-default attribute names.
|
|
384
|
+
|
|
385
|
+
```yaml
|
|
386
|
+
- emit: genai_messages
|
|
387
|
+
match: { ... } # optional
|
|
388
|
+
input_locator: { kind: span_attr, key: my_framework.input.json }
|
|
389
|
+
output_locator: { kind: span_attr, key: my_framework.output.json }
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### Locator details
|
|
393
|
+
|
|
394
|
+
| Field | Meaning |
|
|
395
|
+
| ------------ | ------------------------------------------------------------------------------------------------- |
|
|
396
|
+
| `kind` | `span_attr` (top-level), `event_attr` (inside a named event), or `resource_attr` (resource-level) |
|
|
397
|
+
| `key` | Attribute name to read |
|
|
398
|
+
| `event_name` | Only for `event_attr` — the OTel event whose attributes to read (e.g. `tool.output`) |
|
|
399
|
+
| `value_path` | Optional JSONPath into the located value; used when the attribute is itself JSON |
|
|
400
|
+
|
|
401
|
+
#### Reference manifests
|
|
402
|
+
|
|
403
|
+
`examples/otel-filters/`:
|
|
404
|
+
|
|
405
|
+
- `openinference-agent.yaml` — single-span agent with `input.value` / `output.value` (`request_response`).
|
|
406
|
+
- `claude-code.yaml` — Claude Code's interaction span + tool spans (`text` + `tool_pair`).
|
|
407
|
+
- `genai-explicit.yaml` — gen_ai messages under custom attribute keys.
|
|
408
|
+
|
|
305
409
|
## OTEL Trace Querying
|
|
306
410
|
|
|
307
411
|
### List traces
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/otel-filter/create.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/otel-filter/create.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI,CA6J/D"}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import ora from "ora";
|
|
2
3
|
import { requireApiKey } from "../../auth.js";
|
|
3
4
|
import { apiRequest } from "../../client.js";
|
|
4
5
|
import { printSuccess, printError, printJson, handleSdkError } from "../../output.js";
|
|
6
|
+
import { loadFilterYaml } from "../../lib/filter-yaml.js";
|
|
5
7
|
export function registerCreateCommand(otelFilter) {
|
|
6
8
|
otelFilter
|
|
7
9
|
.command("create")
|
|
8
10
|
.description("Create a new OTEL trace evaluation filter. Wires either an evaluator OR a judge to incoming traces.")
|
|
9
|
-
.
|
|
11
|
+
.option("--name <name>", "Filter name")
|
|
10
12
|
.option("--evaluator-id <id>", "Evaluator UUID to run on matching traces (mutually exclusive with --judge-id)")
|
|
11
13
|
.option("--judge-id <id>", "Judge UUID to run on matching traces (mutually exclusive with --evaluator-id)")
|
|
12
14
|
.option("--filter-criteria <json>", 'Filter conditions as JSON, e.g. \'{"conditions":[{"column":"name","type":"string","key":"name","operator":"contains","value":"agent"}]}\'')
|
|
13
|
-
.option("--sampling-rate <rate>", "Sampling rate between 0.0 and 1.0"
|
|
14
|
-
.option("--delay-seconds <seconds>", "Delay before evaluation (allows late spans)"
|
|
15
|
-
.option("--inactive", "Create the filter as inactive"
|
|
15
|
+
.option("--sampling-rate <rate>", "Sampling rate between 0.0 and 1.0")
|
|
16
|
+
.option("--delay-seconds <seconds>", "Delay before evaluation (allows late spans)")
|
|
17
|
+
.option("--inactive", "Create the filter as inactive")
|
|
18
|
+
.option("-f, --from-file <path>", "Load filter spec from a YAML/JSON file. Required only when the filter needs extractor_rules (custom input/output extraction).")
|
|
16
19
|
.addHelpText("after", `
|
|
17
20
|
Target — exactly one of --evaluator-id or --judge-id is required.
|
|
18
21
|
--evaluator-id Runs a single evaluator (one score, one justification).
|
|
@@ -34,28 +37,39 @@ Examples:
|
|
|
34
37
|
--filter-criteria '{"conditions":[{"column":"resource","type":"string","key":"service.name","operator":"=","value":"construction_assistant_agent"}]}' \\
|
|
35
38
|
--delay-seconds 5
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
--delay-seconds 5
|
|
40
|
+
YAML manifest (only needed for custom extractor_rules):
|
|
41
|
+
When your traces don't follow the OTel GenAI shape (Claude Code, OpenInference,
|
|
42
|
+
custom instrumentations) the eval needs to be told where input/output lives.
|
|
43
|
+
Carry that mapping as extractor_rules inside a YAML file. The flag-based
|
|
44
|
+
invocation above stays unchanged for everything else.
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
$ scorable otel-filter create
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
$ scorable otel-filter create -f ./filter.yaml
|
|
47
|
+
$ scorable otel-filter create -f ./filter.yaml --inactive # override one field
|
|
48
|
+
|
|
49
|
+
See examples/otel-filters/ for reference manifests and the README's
|
|
50
|
+
"OTEL Trace Evaluation Filters" section for the extractor_rules schema.`)
|
|
49
51
|
.action(async (opts) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
let filePayload = {};
|
|
53
|
+
if (opts.fromFile) {
|
|
54
|
+
let source;
|
|
55
|
+
try {
|
|
56
|
+
source = fs.readFileSync(opts.fromFile, "utf8");
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
60
|
+
printError(`Failed to read --from-file: ${msg}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
filePayload = loadFilterYaml(source);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
68
|
+
printError(msg);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
57
71
|
}
|
|
58
|
-
let filterCriteria
|
|
72
|
+
let filterCriteria;
|
|
59
73
|
if (opts.filterCriteria) {
|
|
60
74
|
try {
|
|
61
75
|
filterCriteria = JSON.parse(opts.filterCriteria);
|
|
@@ -65,27 +79,62 @@ Examples:
|
|
|
65
79
|
process.exit(1);
|
|
66
80
|
}
|
|
67
81
|
}
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
82
|
+
let samplingRate;
|
|
83
|
+
if (opts.samplingRate !== undefined) {
|
|
84
|
+
const raw = opts.samplingRate.trim();
|
|
85
|
+
samplingRate = Number(raw);
|
|
86
|
+
if (raw === "" || !Number.isFinite(samplingRate) || samplingRate < 0 || samplingRate > 1) {
|
|
87
|
+
printError("--sampling-rate must be a number between 0.0 and 1.0.");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
let delaySeconds;
|
|
92
|
+
if (opts.delaySeconds !== undefined) {
|
|
93
|
+
const raw = opts.delaySeconds.trim();
|
|
94
|
+
delaySeconds = Number(raw);
|
|
95
|
+
if (raw === "" || !Number.isInteger(delaySeconds) || delaySeconds < 0) {
|
|
96
|
+
printError("--delay-seconds must be a non-negative integer.");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const flagPayload = {};
|
|
101
|
+
if (opts.name !== undefined)
|
|
102
|
+
flagPayload.name = opts.name;
|
|
103
|
+
if (opts.evaluatorId !== undefined)
|
|
104
|
+
flagPayload.evaluator_id = opts.evaluatorId;
|
|
105
|
+
if (opts.judgeId !== undefined)
|
|
106
|
+
flagPayload.judge_id = opts.judgeId;
|
|
107
|
+
if (filterCriteria !== undefined)
|
|
108
|
+
flagPayload.filter_criteria = filterCriteria;
|
|
109
|
+
if (samplingRate !== undefined)
|
|
110
|
+
flagPayload.sampling_rate = samplingRate;
|
|
111
|
+
if (delaySeconds !== undefined)
|
|
112
|
+
flagPayload.delay_seconds = delaySeconds;
|
|
113
|
+
if (opts.inactive !== undefined)
|
|
114
|
+
flagPayload.is_active = !opts.inactive;
|
|
115
|
+
const payload = { ...filePayload, ...flagPayload };
|
|
116
|
+
if (!opts.fromFile) {
|
|
117
|
+
if (payload.sampling_rate === undefined)
|
|
118
|
+
payload.sampling_rate = 1.0;
|
|
119
|
+
if (payload.delay_seconds === undefined)
|
|
120
|
+
payload.delay_seconds = 10;
|
|
121
|
+
if (payload.is_active === undefined)
|
|
122
|
+
payload.is_active = true;
|
|
123
|
+
if (payload.filter_criteria === undefined)
|
|
124
|
+
payload.filter_criteria = {};
|
|
125
|
+
}
|
|
126
|
+
if (typeof payload.name !== "string" || payload.name.length === 0) {
|
|
127
|
+
printError("--name is required (provide via --name or --from-file).");
|
|
71
128
|
process.exit(1);
|
|
72
129
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
130
|
+
if (!payload.evaluator_id && !payload.judge_id) {
|
|
131
|
+
printError("Either --evaluator-id or --judge-id is required.");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
if (payload.evaluator_id && payload.judge_id) {
|
|
135
|
+
printError("--evaluator-id and --judge-id are mutually exclusive.");
|
|
76
136
|
process.exit(1);
|
|
77
137
|
}
|
|
78
|
-
const payload = {
|
|
79
|
-
name: opts.name,
|
|
80
|
-
filter_criteria: filterCriteria,
|
|
81
|
-
sampling_rate: samplingRate,
|
|
82
|
-
delay_seconds: delaySeconds,
|
|
83
|
-
is_active: !opts.inactive,
|
|
84
|
-
};
|
|
85
|
-
if (opts.evaluatorId)
|
|
86
|
-
payload.evaluator_id = opts.evaluatorId;
|
|
87
|
-
if (opts.judgeId)
|
|
88
|
-
payload.judge_id = opts.judgeId;
|
|
89
138
|
const spinner = ora("Creating filter...").start();
|
|
90
139
|
try {
|
|
91
140
|
const apiKey = await requireApiKey();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/otel-filter/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/otel-filter/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAUjE"}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { registerCreateCommand } from "./create.js";
|
|
2
2
|
import { registerListCommand } from "./list.js";
|
|
3
3
|
import { registerDeleteCommand } from "./delete.js";
|
|
4
|
+
import { registerUpdateCommand } from "./update.js";
|
|
5
|
+
import { registerValidateCommand } from "./validate.js";
|
|
4
6
|
export function registerOtelFilterCommands(program) {
|
|
5
7
|
const otelFilter = program
|
|
6
8
|
.command("otel-filter")
|
|
7
9
|
.description("Manage OTEL trace evaluation filters");
|
|
8
10
|
registerCreateCommand(otelFilter);
|
|
9
11
|
registerListCommand(otelFilter);
|
|
12
|
+
registerUpdateCommand(otelFilter);
|
|
10
13
|
registerDeleteCommand(otelFilter);
|
|
14
|
+
registerValidateCommand(otelFilter);
|
|
11
15
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/commands/otel-filter/update.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI,CAoD/D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { requireApiKey } from "../../auth.js";
|
|
4
|
+
import { apiRequest } from "../../client.js";
|
|
5
|
+
import { printSuccess, printError, printJson, handleSdkError } from "../../output.js";
|
|
6
|
+
import { loadFilterYaml } from "../../lib/filter-yaml.js";
|
|
7
|
+
export function registerUpdateCommand(otelFilter) {
|
|
8
|
+
otelFilter
|
|
9
|
+
.command("update <id>")
|
|
10
|
+
.description("Update an OTEL trace evaluation filter from a YAML/JSON manifest")
|
|
11
|
+
.requiredOption("-f, --from-file <path>", "Filter spec path (YAML or JSON)")
|
|
12
|
+
.addHelpText("after", `
|
|
13
|
+
Pairs with the YAML-manifest workflow used by \`create -f\`. Edit the file,
|
|
14
|
+
re-apply with this command.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
$ scorable otel-filter update <filter-id> -f ./filter.yaml`)
|
|
18
|
+
.action(async (id, opts) => {
|
|
19
|
+
let source;
|
|
20
|
+
try {
|
|
21
|
+
source = fs.readFileSync(opts.fromFile, "utf8");
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
25
|
+
printError(`Failed to read --from-file: ${msg}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
let payload;
|
|
29
|
+
try {
|
|
30
|
+
payload = loadFilterYaml(source);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
34
|
+
printError(msg);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const spinner = ora("Updating filter...").start();
|
|
38
|
+
try {
|
|
39
|
+
const apiKey = await requireApiKey();
|
|
40
|
+
const result = await apiRequest("PATCH", `v1/otel/evaluation-filters/${encodeURIComponent(id)}`, { payload, apiKey });
|
|
41
|
+
spinner.stop();
|
|
42
|
+
if (result === null) {
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
printSuccess(`Filter ${id} updated.`);
|
|
46
|
+
printJson(result);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
spinner.stop();
|
|
50
|
+
handleSdkError(e);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/commands/otel-filter/validate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI,CAmEjE"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { requireApiKey } from "../../auth.js";
|
|
4
|
+
import { apiRequestStatus } from "../../client.js";
|
|
5
|
+
import { printError, printSuccess, handleSdkError } from "../../output.js";
|
|
6
|
+
import { loadFilterYaml } from "../../lib/filter-yaml.js";
|
|
7
|
+
import { CliError } from "../../types.js";
|
|
8
|
+
export function registerValidateCommand(otelFilter) {
|
|
9
|
+
otelFilter
|
|
10
|
+
.command("validate")
|
|
11
|
+
.description("Validate an OTEL trace evaluation filter spec without saving")
|
|
12
|
+
.requiredOption("-f, --from-file <path>", "Filter spec path (YAML or JSON)")
|
|
13
|
+
.addHelpText("after", `
|
|
14
|
+
Useful in CI to fail builds on a bad manifest before applying.
|
|
15
|
+
|
|
16
|
+
Exit codes:
|
|
17
|
+
0 schema valid; no warnings
|
|
18
|
+
0 schema valid; warnings written to stderr (e.g. rules don't match any recent span)
|
|
19
|
+
2 schema invalid (local Zod failure or server 400)
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
$ scorable otel-filter validate -f ./filter.yaml`)
|
|
23
|
+
.action(async (opts) => {
|
|
24
|
+
let source;
|
|
25
|
+
try {
|
|
26
|
+
source = fs.readFileSync(opts.fromFile, "utf8");
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
30
|
+
printError(`Failed to read --from-file: ${msg}`);
|
|
31
|
+
throw new CliError(1, msg);
|
|
32
|
+
}
|
|
33
|
+
let payload;
|
|
34
|
+
try {
|
|
35
|
+
payload = loadFilterYaml(source);
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
39
|
+
printError(msg);
|
|
40
|
+
throw new CliError(2, msg);
|
|
41
|
+
}
|
|
42
|
+
const spinner = ora("Validating filter...").start();
|
|
43
|
+
try {
|
|
44
|
+
const apiKey = await requireApiKey();
|
|
45
|
+
const result = await apiRequestStatus("POST", "v1/otel/evaluation-filters/validate", {
|
|
46
|
+
payload,
|
|
47
|
+
apiKey,
|
|
48
|
+
});
|
|
49
|
+
spinner.stop();
|
|
50
|
+
if (!result.ok) {
|
|
51
|
+
if (result.status === 400) {
|
|
52
|
+
throw new CliError(2, "Filter schema validation failed.");
|
|
53
|
+
}
|
|
54
|
+
throw new CliError(1, `Validation request failed (status ${result.status}).`);
|
|
55
|
+
}
|
|
56
|
+
const data = (result.data ?? {});
|
|
57
|
+
const warnings = Array.isArray(data.warnings) ? data.warnings : [];
|
|
58
|
+
if (warnings.length > 0) {
|
|
59
|
+
for (const w of warnings) {
|
|
60
|
+
console.error(`warning: ${w}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
printSuccess("OK");
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
spinner.stop();
|
|
67
|
+
if (e instanceof CliError)
|
|
68
|
+
throw e;
|
|
69
|
+
handleSdkError(e);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const FilterYamlSchema: z.ZodObject<{
|
|
3
|
+
name: z.ZodString;
|
|
4
|
+
evaluator_id: z.ZodOptional<z.ZodString>;
|
|
5
|
+
judge_id: z.ZodOptional<z.ZodString>;
|
|
6
|
+
filter_criteria: z.ZodDefault<z.ZodObject<{
|
|
7
|
+
conditions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
8
|
+
column: z.ZodString;
|
|
9
|
+
operator: z.ZodString;
|
|
10
|
+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
11
|
+
key: z.ZodOptional<z.ZodString>;
|
|
12
|
+
}, z.core.$strict>>>;
|
|
13
|
+
}, z.core.$strict>>;
|
|
14
|
+
sampling_rate: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
delay_seconds: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
is_active: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
+
extractor_rules: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
18
|
+
emit: z.ZodLiteral<"text">;
|
|
19
|
+
match: z.ZodOptional<z.ZodObject<{
|
|
20
|
+
conditions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
21
|
+
column: z.ZodString;
|
|
22
|
+
operator: z.ZodString;
|
|
23
|
+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
24
|
+
key: z.ZodOptional<z.ZodString>;
|
|
25
|
+
}, z.core.$strict>>>;
|
|
26
|
+
}, z.core.$strict>>;
|
|
27
|
+
role: z.ZodEnum<{
|
|
28
|
+
user: "user";
|
|
29
|
+
assistant: "assistant";
|
|
30
|
+
}>;
|
|
31
|
+
locator: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
32
|
+
kind: z.ZodLiteral<"span_attr">;
|
|
33
|
+
key: z.ZodString;
|
|
34
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
35
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
36
|
+
kind: z.ZodLiteral<"resource_attr">;
|
|
37
|
+
key: z.ZodString;
|
|
38
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
39
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
40
|
+
kind: z.ZodLiteral<"event_attr">;
|
|
41
|
+
key: z.ZodString;
|
|
42
|
+
event_name: z.ZodString;
|
|
43
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
44
|
+
}, z.core.$strict>], "kind">;
|
|
45
|
+
tool_name: z.ZodOptional<z.ZodString>;
|
|
46
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
47
|
+
emit: z.ZodLiteral<"request_response">;
|
|
48
|
+
match: z.ZodOptional<z.ZodObject<{
|
|
49
|
+
conditions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
50
|
+
column: z.ZodString;
|
|
51
|
+
operator: z.ZodString;
|
|
52
|
+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
53
|
+
key: z.ZodOptional<z.ZodString>;
|
|
54
|
+
}, z.core.$strict>>>;
|
|
55
|
+
}, z.core.$strict>>;
|
|
56
|
+
input_locator: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
57
|
+
kind: z.ZodLiteral<"span_attr">;
|
|
58
|
+
key: z.ZodString;
|
|
59
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
60
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
61
|
+
kind: z.ZodLiteral<"resource_attr">;
|
|
62
|
+
key: z.ZodString;
|
|
63
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
64
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
65
|
+
kind: z.ZodLiteral<"event_attr">;
|
|
66
|
+
key: z.ZodString;
|
|
67
|
+
event_name: z.ZodString;
|
|
68
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
69
|
+
}, z.core.$strict>], "kind">;
|
|
70
|
+
output_locator: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
71
|
+
kind: z.ZodLiteral<"span_attr">;
|
|
72
|
+
key: z.ZodString;
|
|
73
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
74
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
75
|
+
kind: z.ZodLiteral<"resource_attr">;
|
|
76
|
+
key: z.ZodString;
|
|
77
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
78
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
79
|
+
kind: z.ZodLiteral<"event_attr">;
|
|
80
|
+
key: z.ZodString;
|
|
81
|
+
event_name: z.ZodString;
|
|
82
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
83
|
+
}, z.core.$strict>], "kind">;
|
|
84
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
85
|
+
emit: z.ZodLiteral<"tool_pair">;
|
|
86
|
+
match: z.ZodOptional<z.ZodObject<{
|
|
87
|
+
conditions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
88
|
+
column: z.ZodString;
|
|
89
|
+
operator: z.ZodString;
|
|
90
|
+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
91
|
+
key: z.ZodOptional<z.ZodString>;
|
|
92
|
+
}, z.core.$strict>>>;
|
|
93
|
+
}, z.core.$strict>>;
|
|
94
|
+
input_locator: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
95
|
+
kind: z.ZodLiteral<"span_attr">;
|
|
96
|
+
key: z.ZodString;
|
|
97
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
98
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
99
|
+
kind: z.ZodLiteral<"resource_attr">;
|
|
100
|
+
key: z.ZodString;
|
|
101
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
102
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
103
|
+
kind: z.ZodLiteral<"event_attr">;
|
|
104
|
+
key: z.ZodString;
|
|
105
|
+
event_name: z.ZodString;
|
|
106
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
107
|
+
}, z.core.$strict>], "kind">;
|
|
108
|
+
output_locator: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
109
|
+
kind: z.ZodLiteral<"span_attr">;
|
|
110
|
+
key: z.ZodString;
|
|
111
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
112
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
113
|
+
kind: z.ZodLiteral<"resource_attr">;
|
|
114
|
+
key: z.ZodString;
|
|
115
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
116
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
117
|
+
kind: z.ZodLiteral<"event_attr">;
|
|
118
|
+
key: z.ZodString;
|
|
119
|
+
event_name: z.ZodString;
|
|
120
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
121
|
+
}, z.core.$strict>], "kind">;
|
|
122
|
+
tool_name: z.ZodOptional<z.ZodString>;
|
|
123
|
+
tool_name_locator: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
124
|
+
kind: z.ZodLiteral<"span_attr">;
|
|
125
|
+
key: z.ZodString;
|
|
126
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
127
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
128
|
+
kind: z.ZodLiteral<"resource_attr">;
|
|
129
|
+
key: z.ZodString;
|
|
130
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
131
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
132
|
+
kind: z.ZodLiteral<"event_attr">;
|
|
133
|
+
key: z.ZodString;
|
|
134
|
+
event_name: z.ZodString;
|
|
135
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
136
|
+
}, z.core.$strict>], "kind">>;
|
|
137
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
138
|
+
emit: z.ZodLiteral<"genai_messages">;
|
|
139
|
+
match: z.ZodOptional<z.ZodObject<{
|
|
140
|
+
conditions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
141
|
+
column: z.ZodString;
|
|
142
|
+
operator: z.ZodString;
|
|
143
|
+
value: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
144
|
+
key: z.ZodOptional<z.ZodString>;
|
|
145
|
+
}, z.core.$strict>>>;
|
|
146
|
+
}, z.core.$strict>>;
|
|
147
|
+
input_locator: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
148
|
+
kind: z.ZodLiteral<"span_attr">;
|
|
149
|
+
key: z.ZodString;
|
|
150
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
151
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
152
|
+
kind: z.ZodLiteral<"resource_attr">;
|
|
153
|
+
key: z.ZodString;
|
|
154
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
155
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
156
|
+
kind: z.ZodLiteral<"event_attr">;
|
|
157
|
+
key: z.ZodString;
|
|
158
|
+
event_name: z.ZodString;
|
|
159
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
160
|
+
}, z.core.$strict>], "kind">;
|
|
161
|
+
output_locator: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
162
|
+
kind: z.ZodLiteral<"span_attr">;
|
|
163
|
+
key: z.ZodString;
|
|
164
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
165
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
166
|
+
kind: z.ZodLiteral<"resource_attr">;
|
|
167
|
+
key: z.ZodString;
|
|
168
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
169
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
170
|
+
kind: z.ZodLiteral<"event_attr">;
|
|
171
|
+
key: z.ZodString;
|
|
172
|
+
event_name: z.ZodString;
|
|
173
|
+
value_path: z.ZodOptional<z.ZodString>;
|
|
174
|
+
}, z.core.$strict>], "kind">;
|
|
175
|
+
}, z.core.$strict>], "emit">>>;
|
|
176
|
+
}, z.core.$strict>;
|
|
177
|
+
export type FilterYaml = z.infer<typeof FilterYamlSchema>;
|
|
178
|
+
export declare function loadFilterYaml(source: string): FilterYaml;
|
|
179
|
+
//# sourceMappingURL=filter-yaml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-yaml.d.ts","sourceRoot":"","sources":["../../src/lib/filter-yaml.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAyFxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAczB,CAAC;AAEL,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAczD"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
const MatchCondition = z
|
|
4
|
+
.object({
|
|
5
|
+
column: z.string(),
|
|
6
|
+
operator: z.string(),
|
|
7
|
+
value: z.union([z.string(), z.number(), z.boolean()]).optional(),
|
|
8
|
+
key: z.string().optional(),
|
|
9
|
+
})
|
|
10
|
+
.strict();
|
|
11
|
+
const Match = z
|
|
12
|
+
.object({
|
|
13
|
+
conditions: z.array(MatchCondition).default([]),
|
|
14
|
+
})
|
|
15
|
+
.strict();
|
|
16
|
+
const Locator = z.discriminatedUnion("kind", [
|
|
17
|
+
z
|
|
18
|
+
.object({
|
|
19
|
+
kind: z.literal("span_attr"),
|
|
20
|
+
key: z.string(),
|
|
21
|
+
value_path: z.string().optional(),
|
|
22
|
+
})
|
|
23
|
+
.strict(),
|
|
24
|
+
z
|
|
25
|
+
.object({
|
|
26
|
+
kind: z.literal("resource_attr"),
|
|
27
|
+
key: z.string(),
|
|
28
|
+
value_path: z.string().optional(),
|
|
29
|
+
})
|
|
30
|
+
.strict(),
|
|
31
|
+
z
|
|
32
|
+
.object({
|
|
33
|
+
kind: z.literal("event_attr"),
|
|
34
|
+
key: z.string(),
|
|
35
|
+
event_name: z.string(),
|
|
36
|
+
value_path: z.string().optional(),
|
|
37
|
+
})
|
|
38
|
+
.strict(),
|
|
39
|
+
]);
|
|
40
|
+
const TextRule = z
|
|
41
|
+
.object({
|
|
42
|
+
emit: z.literal("text"),
|
|
43
|
+
match: Match.optional(),
|
|
44
|
+
role: z.enum(["user", "assistant"]),
|
|
45
|
+
locator: Locator,
|
|
46
|
+
tool_name: z.string().optional(),
|
|
47
|
+
})
|
|
48
|
+
.strict();
|
|
49
|
+
const RequestResponseRule = z
|
|
50
|
+
.object({
|
|
51
|
+
emit: z.literal("request_response"),
|
|
52
|
+
match: Match.optional(),
|
|
53
|
+
input_locator: Locator,
|
|
54
|
+
output_locator: Locator,
|
|
55
|
+
})
|
|
56
|
+
.strict();
|
|
57
|
+
const ToolPairRule = z
|
|
58
|
+
.object({
|
|
59
|
+
emit: z.literal("tool_pair"),
|
|
60
|
+
match: Match.optional(),
|
|
61
|
+
input_locator: Locator,
|
|
62
|
+
output_locator: Locator,
|
|
63
|
+
tool_name: z.string().optional(),
|
|
64
|
+
tool_name_locator: Locator.optional(),
|
|
65
|
+
})
|
|
66
|
+
.strict();
|
|
67
|
+
const GenAiMessagesRule = z
|
|
68
|
+
.object({
|
|
69
|
+
emit: z.literal("genai_messages"),
|
|
70
|
+
match: Match.optional(),
|
|
71
|
+
input_locator: Locator,
|
|
72
|
+
output_locator: Locator,
|
|
73
|
+
})
|
|
74
|
+
.strict();
|
|
75
|
+
const ExtractorRule = z.discriminatedUnion("emit", [
|
|
76
|
+
TextRule,
|
|
77
|
+
RequestResponseRule,
|
|
78
|
+
ToolPairRule,
|
|
79
|
+
GenAiMessagesRule,
|
|
80
|
+
]);
|
|
81
|
+
export const FilterYamlSchema = z
|
|
82
|
+
.object({
|
|
83
|
+
name: z.string(),
|
|
84
|
+
evaluator_id: z.string().optional(),
|
|
85
|
+
judge_id: z.string().optional(),
|
|
86
|
+
filter_criteria: Match.default({ conditions: [] }),
|
|
87
|
+
sampling_rate: z.number().min(0).max(1).optional(),
|
|
88
|
+
delay_seconds: z.number().int().min(0).optional(),
|
|
89
|
+
is_active: z.boolean().optional(),
|
|
90
|
+
extractor_rules: z.array(ExtractorRule).default([]),
|
|
91
|
+
})
|
|
92
|
+
.strict()
|
|
93
|
+
.refine((v) => Boolean(v.evaluator_id) !== Boolean(v.judge_id), {
|
|
94
|
+
message: "exactly one of evaluator_id or judge_id is required",
|
|
95
|
+
});
|
|
96
|
+
export function loadFilterYaml(source) {
|
|
97
|
+
let raw;
|
|
98
|
+
try {
|
|
99
|
+
raw = yaml.load(source);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
103
|
+
throw new Error(`Invalid YAML: ${msg}`);
|
|
104
|
+
}
|
|
105
|
+
const result = FilterYamlSchema.safeParse(raw);
|
|
106
|
+
if (!result.success) {
|
|
107
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`);
|
|
108
|
+
throw new Error(`Invalid filter YAML:\n${issues.join("\n")}`);
|
|
109
|
+
}
|
|
110
|
+
return result.data;
|
|
111
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: claude-code-conversation-eval
|
|
2
|
+
judge_id: 0193b6a0-e75d-7a47-9c6f-2f3e3b8f7c91
|
|
3
|
+
sampling_rate: 1.0
|
|
4
|
+
delay_seconds: 60
|
|
5
|
+
is_active: true
|
|
6
|
+
|
|
7
|
+
filter_criteria:
|
|
8
|
+
conditions:
|
|
9
|
+
- column: span_name
|
|
10
|
+
operator: starts_with
|
|
11
|
+
value: claude_code.
|
|
12
|
+
|
|
13
|
+
extractor_rules:
|
|
14
|
+
- emit: text
|
|
15
|
+
match:
|
|
16
|
+
conditions:
|
|
17
|
+
- column: span_name
|
|
18
|
+
operator: "="
|
|
19
|
+
value: claude_code.interaction
|
|
20
|
+
role: user
|
|
21
|
+
locator: { kind: span_attr, key: user_prompt }
|
|
22
|
+
|
|
23
|
+
- emit: tool_pair
|
|
24
|
+
match:
|
|
25
|
+
conditions:
|
|
26
|
+
- column: span_name
|
|
27
|
+
operator: "="
|
|
28
|
+
value: claude_code.tool
|
|
29
|
+
tool_name: Bash
|
|
30
|
+
input_locator: { kind: event_attr, event_name: tool.output, key: bash_command }
|
|
31
|
+
output_locator: { kind: event_attr, event_name: tool.output, key: output }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: custom-genai-keys-eval
|
|
2
|
+
judge_id: 0193b6a0-e75d-7a47-9c6f-2f3e3b8f7c91
|
|
3
|
+
sampling_rate: 1.0
|
|
4
|
+
delay_seconds: 60
|
|
5
|
+
is_active: true
|
|
6
|
+
|
|
7
|
+
filter_criteria:
|
|
8
|
+
conditions:
|
|
9
|
+
- column: span_name
|
|
10
|
+
operator: starts_with
|
|
11
|
+
value: my_framework.
|
|
12
|
+
|
|
13
|
+
extractor_rules:
|
|
14
|
+
- emit: genai_messages
|
|
15
|
+
input_locator: { kind: span_attr, key: my_framework.input.json }
|
|
16
|
+
output_locator: { kind: span_attr, key: my_framework.output.json }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: openinference-agent-eval
|
|
2
|
+
judge_id: 0193b6a0-e75d-7a47-9c6f-2f3e3b8f7c91
|
|
3
|
+
sampling_rate: 1.0
|
|
4
|
+
delay_seconds: 60
|
|
5
|
+
is_active: true
|
|
6
|
+
|
|
7
|
+
filter_criteria:
|
|
8
|
+
conditions:
|
|
9
|
+
- column: attribute
|
|
10
|
+
key: openinference.span.kind
|
|
11
|
+
operator: eq
|
|
12
|
+
value: AGENT
|
|
13
|
+
|
|
14
|
+
extractor_rules:
|
|
15
|
+
- emit: request_response
|
|
16
|
+
input_locator: { kind: span_attr, key: input.value }
|
|
17
|
+
output_locator: { kind: span_attr, key: output.value }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@root-signals/scorable-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "CLI for Scorable",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Scorable",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"dist/**/*.js.map",
|
|
14
14
|
"dist/**/*.d.ts",
|
|
15
15
|
"dist/**/*.d.ts.map",
|
|
16
|
+
"examples/**/*.yaml",
|
|
16
17
|
"README.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
|
@@ -31,12 +32,13 @@
|
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
34
|
"@inquirer/prompts": "^8.3.2",
|
|
34
|
-
"@root-signals/scorable": "^0.6.
|
|
35
|
+
"@root-signals/scorable": "^0.6.1",
|
|
35
36
|
"chalk": "^5.6.2",
|
|
36
37
|
"cli-table3": "^0.6.5",
|
|
37
38
|
"commander": "^14.0.3",
|
|
38
39
|
"js-yaml": "^4.1.1",
|
|
39
|
-
"ora": "^9.3.0"
|
|
40
|
+
"ora": "^9.3.0",
|
|
41
|
+
"zod": "^4.4.3"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@types/js-yaml": "^4.0.9",
|