@indemn/cli 1.5.0 → 1.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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +9 -4
- package/.github/workflows/ci.yml +10 -0
- package/.github/workflows/publish.yml +2 -2
- package/.mcp.json +1 -4
- package/CONTRIBUTING.md +3 -1
- package/README.md +69 -176
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +226 -18
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/analytics.d.ts +3 -0
- package/dist/cli/analytics.d.ts.map +1 -0
- package/dist/cli/analytics.js +133 -0
- package/dist/cli/analytics.js.map +1 -0
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +62 -17
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/billing.d.ts +3 -0
- package/dist/cli/billing.d.ts.map +1 -0
- package/dist/cli/billing.js +23 -0
- package/dist/cli/billing.js.map +1 -0
- package/dist/cli/campaigns.d.ts +3 -0
- package/dist/cli/campaigns.d.ts.map +1 -0
- package/dist/cli/campaigns.js +34 -0
- package/dist/cli/campaigns.js.map +1 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +69 -102
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +71 -27
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/conversations.d.ts +3 -0
- package/dist/cli/conversations.d.ts.map +1 -0
- package/dist/cli/conversations.js +129 -0
- package/dist/cli/conversations.js.map +1 -0
- package/dist/cli/distribution.d.ts +3 -0
- package/dist/cli/distribution.d.ts.map +1 -0
- package/dist/cli/distribution.js +122 -0
- package/dist/cli/distribution.js.map +1 -0
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/eval.js +315 -71
- package/dist/cli/eval.js.map +1 -1
- package/dist/cli/functions.d.ts.map +1 -1
- package/dist/cli/functions.js +125 -35
- package/dist/cli/functions.js.map +1 -1
- package/dist/cli/index.js +63 -54
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +27 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +152 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/kb.d.ts.map +1 -1
- package/dist/cli/kb.js +141 -15
- package/dist/cli/kb.js.map +1 -1
- package/dist/cli/link.d.ts +21 -0
- package/dist/cli/link.d.ts.map +1 -0
- package/dist/cli/link.js +112 -0
- package/dist/cli/link.js.map +1 -0
- package/dist/cli/org.d.ts.map +1 -1
- package/dist/cli/org.js +79 -67
- package/dist/cli/org.js.map +1 -1
- package/dist/cli/platform.d.ts +3 -0
- package/dist/cli/platform.d.ts.map +1 -0
- package/dist/cli/platform.js +133 -0
- package/dist/cli/platform.js.map +1 -0
- package/dist/cli/tickets.d.ts +3 -0
- package/dist/cli/tickets.d.ts.map +1 -0
- package/dist/cli/tickets.js +125 -0
- package/dist/cli/tickets.js.map +1 -0
- package/dist/cli/utils/output.d.ts +15 -9
- package/dist/cli/utils/output.d.ts.map +1 -1
- package/dist/cli/utils/output.js +53 -46
- package/dist/cli/utils/output.js.map +1 -1
- package/dist/cli/voice.d.ts +3 -0
- package/dist/cli/voice.d.ts.map +1 -0
- package/dist/cli/voice.js +206 -0
- package/dist/cli/voice.js.map +1 -0
- package/dist/core/diff.d.ts +16 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +57 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/org-resolver.d.ts +37 -0
- package/dist/core/org-resolver.d.ts.map +1 -0
- package/dist/core/org-resolver.js +73 -0
- package/dist/core/org-resolver.js.map +1 -0
- package/dist/core/runtime.d.ts +36 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +62 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/scope.d.ts +33 -0
- package/dist/core/scope.d.ts.map +1 -0
- package/dist/core/scope.js +37 -0
- package/dist/core/scope.js.map +1 -0
- package/dist/mcp/registry.d.ts +67 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +195 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +87 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-meta.d.ts +21 -0
- package/dist/mcp/tool-meta.d.ts.map +1 -0
- package/dist/mcp/tool-meta.js +203 -0
- package/dist/mcp/tool-meta.js.map +1 -0
- package/dist/mcp/tools.d.ts +2 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1462 -215
- package/dist/mcp/tools.js.map +1 -1
- package/dist/sdk/agent-bundle.d.ts +99 -0
- package/dist/sdk/agent-bundle.d.ts.map +1 -0
- package/dist/sdk/agent-bundle.js +211 -0
- package/dist/sdk/agent-bundle.js.map +1 -0
- package/dist/sdk/agents.d.ts +54 -1
- package/dist/sdk/agents.d.ts.map +1 -1
- package/dist/sdk/agents.js +59 -5
- package/dist/sdk/agents.js.map +1 -1
- package/dist/sdk/analytics.d.ts +158 -0
- package/dist/sdk/analytics.d.ts.map +1 -0
- package/dist/sdk/analytics.js +185 -0
- package/dist/sdk/analytics.js.map +1 -0
- package/dist/sdk/auth.d.ts +34 -12
- package/dist/sdk/auth.d.ts.map +1 -1
- package/dist/sdk/auth.js +60 -77
- package/dist/sdk/auth.js.map +1 -1
- package/dist/sdk/billing.d.ts +25 -0
- package/dist/sdk/billing.d.ts.map +1 -0
- package/dist/sdk/billing.js +61 -0
- package/dist/sdk/billing.js.map +1 -0
- package/dist/sdk/campaigns.d.ts +35 -0
- package/dist/sdk/campaigns.d.ts.map +1 -0
- package/dist/sdk/campaigns.js +59 -0
- package/dist/sdk/campaigns.js.map +1 -0
- package/dist/sdk/chat.d.ts +25 -2
- package/dist/sdk/chat.d.ts.map +1 -1
- package/dist/sdk/chat.js +37 -10
- package/dist/sdk/chat.js.map +1 -1
- package/dist/sdk/client.d.ts +93 -5
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +362 -100
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/config-change.d.ts +74 -0
- package/dist/sdk/config-change.d.ts.map +1 -0
- package/dist/sdk/config-change.js +117 -0
- package/dist/sdk/config-change.js.map +1 -0
- package/dist/sdk/config.d.ts.map +1 -1
- package/dist/sdk/config.js +2 -1
- package/dist/sdk/config.js.map +1 -1
- package/dist/sdk/conversations.d.ts +101 -0
- package/dist/sdk/conversations.d.ts.map +1 -0
- package/dist/sdk/conversations.js +155 -0
- package/dist/sdk/conversations.js.map +1 -0
- package/dist/sdk/credentials.d.ts +25 -0
- package/dist/sdk/credentials.d.ts.map +1 -0
- package/dist/sdk/credentials.js +44 -0
- package/dist/sdk/credentials.js.map +1 -0
- package/dist/sdk/distribution.d.ts +94 -0
- package/dist/sdk/distribution.d.ts.map +1 -0
- package/dist/sdk/distribution.js +100 -0
- package/dist/sdk/distribution.js.map +1 -0
- package/dist/sdk/eval.d.ts +81 -0
- package/dist/sdk/eval.d.ts.map +1 -1
- package/dist/sdk/eval.js +117 -0
- package/dist/sdk/eval.js.map +1 -1
- package/dist/sdk/functions.d.ts +14 -1
- package/dist/sdk/functions.d.ts.map +1 -1
- package/dist/sdk/functions.js +48 -5
- package/dist/sdk/functions.js.map +1 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/jarvis-chat.d.ts +28 -0
- package/dist/sdk/jarvis-chat.d.ts.map +1 -0
- package/dist/sdk/jarvis-chat.js +117 -0
- package/dist/sdk/jarvis-chat.js.map +1 -0
- package/dist/sdk/kb.d.ts +69 -1
- package/dist/sdk/kb.d.ts.map +1 -1
- package/dist/sdk/kb.js +111 -24
- package/dist/sdk/kb.js.map +1 -1
- package/dist/sdk/orgs.d.ts +13 -0
- package/dist/sdk/orgs.d.ts.map +1 -0
- package/dist/sdk/orgs.js +59 -0
- package/dist/sdk/orgs.js.map +1 -0
- package/dist/sdk/platform.d.ts +62 -0
- package/dist/sdk/platform.d.ts.map +1 -0
- package/dist/sdk/platform.js +95 -0
- package/dist/sdk/platform.js.map +1 -0
- package/dist/sdk/session.d.ts +25 -0
- package/dist/sdk/session.d.ts.map +1 -0
- package/dist/sdk/session.js +69 -0
- package/dist/sdk/session.js.map +1 -0
- package/dist/sdk/skills.d.ts +46 -0
- package/dist/sdk/skills.d.ts.map +1 -0
- package/dist/sdk/skills.js +203 -0
- package/dist/sdk/skills.js.map +1 -0
- package/dist/sdk/test-chat.d.ts +52 -0
- package/dist/sdk/test-chat.d.ts.map +1 -0
- package/dist/sdk/test-chat.js +134 -0
- package/dist/sdk/test-chat.js.map +1 -0
- package/dist/sdk/tickets.d.ts +98 -0
- package/dist/sdk/tickets.d.ts.map +1 -0
- package/dist/sdk/tickets.js +91 -0
- package/dist/sdk/tickets.js.map +1 -0
- package/dist/sdk/types.d.ts +91 -6
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/voice.d.ts +147 -0
- package/dist/sdk/voice.d.ts.map +1 -0
- package/dist/sdk/voice.js +236 -0
- package/dist/sdk/voice.js.map +1 -0
- package/package.json +22 -3
- package/scripts/check-skills.mjs +25 -0
- package/skills/agent-builder/SKILL.md +89 -252
- package/skills/evaluations/SKILL.md +23 -234
- package/skills/evaluations/references/analysis.md +95 -0
- package/skills/evaluations/references/cli-reference.md +28 -0
- package/skills/evaluations/references/json-formats.md +100 -0
- package/src/cli/agents.ts +202 -22
- package/src/cli/analytics.ts +138 -0
- package/src/cli/auth.ts +56 -18
- package/src/cli/billing.ts +25 -0
- package/src/cli/campaigns.ts +33 -0
- package/src/cli/chat.ts +66 -115
- package/src/cli/config.ts +65 -31
- package/src/cli/conversations.ts +133 -0
- package/src/cli/distribution.ts +116 -0
- package/src/cli/eval.ts +254 -70
- package/src/cli/functions.ts +122 -36
- package/src/cli/index.ts +58 -55
- package/src/cli/init.ts +154 -0
- package/src/cli/kb.ts +120 -17
- package/src/cli/link.ts +117 -0
- package/src/cli/org.ts +81 -75
- package/src/cli/platform.ts +128 -0
- package/src/cli/tickets.ts +130 -0
- package/src/cli/utils/output.ts +51 -42
- package/src/cli/voice.ts +204 -0
- package/src/core/diff.ts +52 -0
- package/src/core/org-resolver.ts +113 -0
- package/src/core/runtime.ts +73 -0
- package/src/core/scope.ts +64 -0
- package/src/mcp/registry.ts +236 -0
- package/src/mcp/server.ts +84 -2
- package/src/mcp/tool-meta.ts +241 -0
- package/src/mcp/tools.ts +1529 -265
- package/src/sdk/agent-bundle.ts +245 -0
- package/src/sdk/agents.ts +74 -5
- package/src/sdk/analytics.ts +220 -0
- package/src/sdk/auth.ts +87 -107
- package/src/sdk/billing.ts +59 -0
- package/src/sdk/campaigns.ts +60 -0
- package/src/sdk/chat.ts +44 -10
- package/src/sdk/client.ts +373 -102
- package/src/sdk/config-change.ts +148 -0
- package/src/sdk/config.ts +2 -1
- package/src/sdk/conversations.ts +199 -0
- package/src/sdk/credentials.ts +53 -0
- package/src/sdk/distribution.ts +132 -0
- package/src/sdk/eval.ts +140 -0
- package/src/sdk/functions.ts +52 -6
- package/src/sdk/index.ts +2 -0
- package/src/sdk/jarvis-chat.ts +112 -0
- package/src/sdk/kb.ts +116 -29
- package/src/sdk/orgs.ts +62 -0
- package/src/sdk/platform.ts +115 -0
- package/src/sdk/session.ts +90 -0
- package/src/sdk/skills.ts +188 -0
- package/src/sdk/test-chat.ts +151 -0
- package/src/sdk/tickets.ts +120 -0
- package/src/sdk/types.ts +88 -6
- package/src/sdk/voice.ts +262 -0
- package/test/cli/entry.test.ts +34 -0
- package/test/cli/init.test.ts +66 -0
- package/test/cli/link.test.ts +57 -0
- package/test/core/org-resolver.test.ts +92 -0
- package/test/core/runtime.test.ts +30 -0
- package/test/core/scope.test.ts +93 -0
- package/test/harness.test.ts +24 -0
- package/test/helpers/config-home.ts +22 -0
- package/test/helpers/fake-middleware.ts +56 -0
- package/test/helpers/fetch-recorder.ts +75 -0
- package/test/helpers/jwt.ts +9 -0
- package/test/helpers/mcp-pair.ts +38 -0
- package/test/mcp/__snapshots__/tools-snapshot.test.ts.snap +145 -0
- package/test/mcp/askers.test.ts +60 -0
- package/test/mcp/elicit-timeout.test.ts +29 -0
- package/test/mcp/registry.test.ts +169 -0
- package/test/mcp/shaping.test.ts +41 -0
- package/test/mcp/tools-snapshot.test.ts +42 -0
- package/test/sdk/agents.test.ts +232 -0
- package/test/sdk/analytics.test.ts +89 -0
- package/test/sdk/auth.test.ts +70 -0
- package/test/sdk/billing.test.ts +47 -0
- package/test/sdk/campaigns.test.ts +43 -0
- package/test/sdk/client.test.ts +80 -0
- package/test/sdk/config-change.test.ts +142 -0
- package/test/sdk/conversations.test.ts +80 -0
- package/test/sdk/credentials.test.ts +33 -0
- package/test/sdk/distribution.test.ts +55 -0
- package/test/sdk/eval.test.ts +155 -0
- package/test/sdk/kb-upload.test.ts +50 -0
- package/test/sdk/kb.test.ts +95 -0
- package/test/sdk/orgs.test.ts +48 -0
- package/test/sdk/platform.test.ts +89 -0
- package/test/sdk/renewal.test.ts +115 -0
- package/test/sdk/session.test.ts +60 -0
- package/test/sdk/skills.test.ts +235 -0
- package/test/sdk/test-chat.test.ts +112 -0
- package/test/sdk/tickets.test.ts +42 -0
- package/test/sdk/voice.test.ts +147 -0
- package/vitest.config.ts +11 -0
package/src/cli/eval.ts
CHANGED
|
@@ -3,7 +3,10 @@ import * as fs from 'fs';
|
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { IndemnClient } from '../sdk/client.js';
|
|
6
|
-
import { EvalSDK } from '../sdk/eval.js';
|
|
6
|
+
import { EvalSDK, gateVerdict, type GateOptions } from '../sdk/eval.js';
|
|
7
|
+
import type { EvaluationTriggerInput } from '../sdk/types.js';
|
|
8
|
+
import { CliError, EXIT } from '../core/runtime.js';
|
|
9
|
+
import { confirmOrExit } from './link.js';
|
|
7
10
|
import { RubricsSDK } from '../sdk/rubrics.js';
|
|
8
11
|
import { TestSetsSDK } from '../sdk/test-sets.js';
|
|
9
12
|
import { exportEvalMarkdown, exportEvalReport } from '../report/export.js';
|
|
@@ -15,63 +18,105 @@ export function registerEvalCommands(program: Command): void {
|
|
|
15
18
|
eval_
|
|
16
19
|
.command('run')
|
|
17
20
|
.argument('<agent-id>', 'Agent ID')
|
|
18
|
-
.description('Run an evaluation')
|
|
19
|
-
.requiredOption('--test-set <id>', 'Test set ID')
|
|
21
|
+
.description('Run an evaluation (costs model calls; asks first). With a threshold it becomes a CI gate')
|
|
22
|
+
.requiredOption('--test-set <id>', 'Test set ID (must belong to this agent)')
|
|
20
23
|
.option('--rubric <id>', 'Rubric ID')
|
|
21
24
|
.option('--concurrency <n>', 'Concurrency level')
|
|
22
25
|
.option('--wait', 'Wait for evaluation to complete')
|
|
26
|
+
.option('--timeout <seconds>', 'With --wait: give up after this long', '1800')
|
|
23
27
|
.option('--eval-model <model>', 'Model for evaluation judging')
|
|
24
|
-
.option('--keep', 'Keep evaluation data after completion')
|
|
25
28
|
.option('--limit <n>', 'Limit number of items to evaluate')
|
|
26
29
|
.option('--component-scope <scope>', 'Component scope filter')
|
|
27
30
|
.option('--component-ids <ids>', 'Comma-separated component IDs')
|
|
28
|
-
.option('--conversation-ids <ids>', 'Comma-separated conversation IDs')
|
|
29
|
-
.option('--
|
|
31
|
+
.option('--conversation-ids <ids>', 'Comma-separated conversation IDs (transcript mode)')
|
|
32
|
+
.option('--date-from <date>', 'Grade real conversations from this date (transcript mode)')
|
|
33
|
+
.option('--date-to <date>', 'Up to this date, exclusive (needs --date-from)')
|
|
34
|
+
.option('--mode <mode>', 'Evaluation mode: "transcript" grades real conversations')
|
|
35
|
+
.option('--webhook-url <url>', 'Called when the run finishes')
|
|
36
|
+
.option('--min-pass-rate <rate>', 'CI gate: exit 1 when passed/total is below this (0..1). Implies --wait')
|
|
37
|
+
.option('--max-p95-ms <ms>', 'CI gate: exit 1 when p95 latency is above this. Implies --wait')
|
|
38
|
+
.option('--error-policy <policy>', 'CI gate: block (a failed run fails the gate) or warn', 'block')
|
|
39
|
+
.option('--fail-on-error', 'CI gate: also exit 1 when any case had an infrastructure error. Implies --wait')
|
|
40
|
+
.option('--allow-other-agent', 'Run a test set written for another agent')
|
|
41
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
42
|
+
.option('--json', 'Print the final run as JSON')
|
|
30
43
|
.action(async (agentId: string, options) => {
|
|
31
44
|
try {
|
|
32
45
|
const client = new IndemnClient();
|
|
33
46
|
const sdk = new EvalSDK(client);
|
|
34
|
-
|
|
47
|
+
await sdk.assertAgentInOrg(agentId);
|
|
35
48
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
const testSet = await new TestSetsSDK(client).getTestSet(options.testSet);
|
|
50
|
+
const owner = (testSet as { agent_id?: string }).agent_id;
|
|
51
|
+
if (owner && owner !== agentId && !options.allowOtherAgent) {
|
|
52
|
+
throw new CliError(`Test set ${options.testSet} was written for agent ${owner}, not ${agentId}. Pass --allow-other-agent to run it anyway.`, EXIT.NEEDS_INPUT);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const input: EvaluationTriggerInput = { bot_id: agentId, test_set_id: options.testSet };
|
|
40
56
|
if (options.rubric) input.rubric_id = options.rubric;
|
|
41
57
|
if (options.concurrency) input.concurrency = parseInt(options.concurrency, 10);
|
|
58
|
+
// Voice simulations share call rooms; the dashboard runs them one at a time.
|
|
59
|
+
if (((testSet as { items?: Array<{ type?: string }> }).items ?? []).some((i) => i.type === 'voice_simulation')) input.concurrency = 1;
|
|
42
60
|
if (options.evalModel) input.eval_model = options.evalModel;
|
|
43
|
-
if (options.keep !== undefined) input.keep = options.keep;
|
|
44
61
|
if (options.limit) input.limit = parseInt(options.limit, 10);
|
|
45
62
|
if (options.componentScope) input.component_scope = options.componentScope;
|
|
46
63
|
if (options.componentIds) input.component_ids = options.componentIds.split(',');
|
|
47
64
|
if (options.conversationIds) input.conversation_ids = options.conversationIds.split(',');
|
|
48
|
-
if (options.
|
|
65
|
+
if (options.webhookUrl) input.webhook_url = options.webhookUrl;
|
|
66
|
+
if (Boolean(options.dateFrom) !== Boolean(options.dateTo)) {
|
|
67
|
+
throw new CliError('--date-from and --date-to go together.', EXIT.NEEDS_INPUT);
|
|
68
|
+
}
|
|
69
|
+
if (options.dateFrom) {
|
|
70
|
+
input.date_from = options.dateFrom;
|
|
71
|
+
input.date_to = options.dateTo;
|
|
72
|
+
if (options.mode && options.mode !== 'transcript') throw new CliError('A date range only works with --mode transcript.', EXIT.NEEDS_INPUT);
|
|
73
|
+
// The service ignores dates outside transcript mode and would run the test set instead.
|
|
74
|
+
input.mode = 'transcript';
|
|
75
|
+
} else if (options.mode) {
|
|
76
|
+
input.mode = options.mode;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const items = (testSet as { items?: unknown[] }).items?.length ?? 0;
|
|
80
|
+
const count = input.mode === 'transcript' ? 'real conversations' : `${input.limit ? Math.min(input.limit, items) : items} item(s)`;
|
|
81
|
+
await confirmOrExit(`Run an evaluation of agent ${agentId} on ${count} with concurrency ${input.concurrency ?? 1}? It uses paid model calls and cannot be cancelled.`, Boolean(options.yes));
|
|
82
|
+
|
|
83
|
+
const gate: GateOptions = {
|
|
84
|
+
minPassRate: options.minPassRate !== undefined ? Number(options.minPassRate) : undefined,
|
|
85
|
+
maxP95Ms: options.maxP95Ms !== undefined ? Number(options.maxP95Ms) : undefined,
|
|
86
|
+
errorPolicy: options.errorPolicy === 'warn' ? 'warn' : 'block',
|
|
87
|
+
failOnError: Boolean(options.failOnError),
|
|
88
|
+
};
|
|
89
|
+
const gating = gate.minPassRate !== undefined || gate.maxP95Ms !== undefined || gate.failOnError;
|
|
49
90
|
|
|
50
|
-
const
|
|
91
|
+
const spinner = ora('Triggering evaluation...').start();
|
|
92
|
+
const result = await sdk.triggerEvaluation(input);
|
|
51
93
|
spinner.stop();
|
|
94
|
+
if (!options.json) {
|
|
95
|
+
printSuccess(`Evaluation started: ${result.run_id}`);
|
|
96
|
+
if (result.total) console.log(` Total items: ${result.total}`);
|
|
97
|
+
}
|
|
98
|
+
if (!options.wait && !gating) return;
|
|
52
99
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
pollSpinner.fail('Evaluation did not complete in time');
|
|
72
|
-
throw err;
|
|
73
|
-
}
|
|
100
|
+
const pollSpinner = options.json ? null : ora('Waiting for evaluation to complete...').start();
|
|
101
|
+
const run = await sdk.pollRunUntilComplete(result.run_id, 10_000, Number(options.timeout) * 1000).catch((err) => {
|
|
102
|
+
pollSpinner?.fail('Evaluation did not complete in time');
|
|
103
|
+
throw err;
|
|
104
|
+
});
|
|
105
|
+
pollSpinner?.stop();
|
|
106
|
+
const verdict = gateVerdict(run, gate);
|
|
107
|
+
if (options.json) {
|
|
108
|
+
printJson({ run, gate: gating ? verdict : undefined });
|
|
109
|
+
} else {
|
|
110
|
+
printSuccess(`Evaluation ${run.status}: ${run.passed ?? 0}/${run.total ?? 0} passed (${(verdict.passRate * 100).toFixed(1)}%)`);
|
|
111
|
+
if (run.errors) console.log(chalk.yellow(` Infrastructure errors: ${run.errors}`));
|
|
112
|
+
if (run.latency?.p95_ms !== undefined) console.log(` p95 latency: ${run.latency.p95_ms} ms`);
|
|
113
|
+
if (run.criteria_passed !== undefined && run.criteria_total !== undefined) console.log(` Criteria: ${run.criteria_passed}/${run.criteria_total}`);
|
|
114
|
+
}
|
|
115
|
+
if (gating && !verdict.pass) {
|
|
116
|
+
console.error(chalk.red(`Gate failed: ${verdict.reasons.join('; ')}`));
|
|
117
|
+
process.exit(1);
|
|
74
118
|
}
|
|
119
|
+
if (gating && !options.json) printSuccess('Gate passed.');
|
|
75
120
|
} catch (err) {
|
|
76
121
|
printError(err);
|
|
77
122
|
process.exit(1);
|
|
@@ -80,8 +125,8 @@ export function registerEvalCommands(program: Command): void {
|
|
|
80
125
|
|
|
81
126
|
eval_
|
|
82
127
|
.command('list')
|
|
83
|
-
.description('List evaluation runs')
|
|
84
|
-
.
|
|
128
|
+
.description('List evaluation runs of an agent')
|
|
129
|
+
.requiredOption('--agent-id <id>', 'Agent ID (runs of other orgs are never listed)')
|
|
85
130
|
.option('--json', 'Output as JSON')
|
|
86
131
|
.option('--limit <n>', 'Limit number of results')
|
|
87
132
|
.option('--page <n>', 'Page number (use with --limit)')
|
|
@@ -91,6 +136,7 @@ export function registerEvalCommands(program: Command): void {
|
|
|
91
136
|
const sdk = new EvalSDK(client);
|
|
92
137
|
const spinner = ora('Fetching evaluation runs...').start();
|
|
93
138
|
|
|
139
|
+
await sdk.assertAgentInOrg(options.agentId);
|
|
94
140
|
let runs = await sdk.listRuns(options.agentId);
|
|
95
141
|
spinner.stop();
|
|
96
142
|
|
|
@@ -139,7 +185,7 @@ export function registerEvalCommands(program: Command): void {
|
|
|
139
185
|
const sdk = new EvalSDK(client);
|
|
140
186
|
const spinner = ora('Fetching run status...').start();
|
|
141
187
|
|
|
142
|
-
const run = await sdk.
|
|
188
|
+
const run = await sdk.getRunInOrg(runId);
|
|
143
189
|
spinner.stop();
|
|
144
190
|
|
|
145
191
|
if (options.json) {
|
|
@@ -179,6 +225,7 @@ export function registerEvalCommands(program: Command): void {
|
|
|
179
225
|
const sdk = new EvalSDK(client);
|
|
180
226
|
const spinner = ora('Fetching results...').start();
|
|
181
227
|
|
|
228
|
+
await sdk.getRunInOrg(runId);
|
|
182
229
|
const results = await sdk.getRunResults(runId);
|
|
183
230
|
spinner.stop();
|
|
184
231
|
|
|
@@ -211,6 +258,7 @@ export function registerEvalCommands(program: Command): void {
|
|
|
211
258
|
const sdk = new EvalSDK(client);
|
|
212
259
|
const spinner = ora('Fetching bot context...').start();
|
|
213
260
|
|
|
261
|
+
await sdk.assertAgentInOrg(agentId);
|
|
214
262
|
const context = await sdk.getBotContext(agentId);
|
|
215
263
|
spinner.stop();
|
|
216
264
|
|
|
@@ -253,6 +301,7 @@ export function registerEvalCommands(program: Command): void {
|
|
|
253
301
|
const client = new IndemnClient();
|
|
254
302
|
const spinner = ora('Fetching evaluation data...').start();
|
|
255
303
|
|
|
304
|
+
await new EvalSDK(client).getRunInOrg(runId);
|
|
256
305
|
const result = await exportEvalMarkdown(runId, options.output, client);
|
|
257
306
|
|
|
258
307
|
spinner.stop();
|
|
@@ -266,16 +315,32 @@ export function registerEvalCommands(program: Command): void {
|
|
|
266
315
|
eval_
|
|
267
316
|
.command('report')
|
|
268
317
|
.argument('<run-id>', 'Evaluation run ID')
|
|
269
|
-
.description('
|
|
318
|
+
.description('Branded evaluation report PDF (made here, or by the evaluations service with --server)')
|
|
270
319
|
.option('--output <path>', 'Output file path')
|
|
320
|
+
.option('--server', 'Ask the evaluations service to render it (completed runs only)')
|
|
321
|
+
.option('--force', 'With --server: render again instead of reusing the last report')
|
|
271
322
|
.action(async (runId: string, options) => {
|
|
272
323
|
try {
|
|
273
324
|
const client = new IndemnClient();
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
325
|
+
const sdk = new EvalSDK(client);
|
|
326
|
+
await sdk.getRunInOrg(runId);
|
|
327
|
+
if (options.server) {
|
|
328
|
+
const spinner = ora('Rendering on the evaluations service...').start();
|
|
329
|
+
const { download_url, report_id } = await sdk.exportReport(runId, Boolean(options.force));
|
|
330
|
+
spinner.stop();
|
|
331
|
+
if (options.output) {
|
|
332
|
+
const res = await fetch(download_url);
|
|
333
|
+
if (!res.ok) throw new Error(`Download failed (${res.status}).`);
|
|
334
|
+
fs.writeFileSync(options.output, Buffer.from(await res.arrayBuffer()));
|
|
335
|
+
printSuccess(`Report saved to ${options.output}`);
|
|
336
|
+
} else {
|
|
337
|
+
console.log(download_url);
|
|
338
|
+
console.log(chalk.dim(`Link valid for 15 minutes. New link later: indemn eval report-link ${report_id}`));
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const spinner = ora('Generating PDF...').start();
|
|
277
343
|
const result = await exportEvalReport(runId, options.output, client);
|
|
278
|
-
|
|
279
344
|
spinner.stop();
|
|
280
345
|
printSuccess(`Report saved to ${result.file_path}`);
|
|
281
346
|
} catch (err) {
|
|
@@ -284,13 +349,146 @@ export function registerEvalCommands(program: Command): void {
|
|
|
284
349
|
}
|
|
285
350
|
});
|
|
286
351
|
|
|
352
|
+
eval_
|
|
353
|
+
.command('report-link')
|
|
354
|
+
.argument('<report-id>', 'Report id printed by `eval report --server`')
|
|
355
|
+
.description('A fresh download link for a server-rendered report')
|
|
356
|
+
.action(async (reportId: string) => {
|
|
357
|
+
try { console.log((await new EvalSDK(new IndemnClient()).downloadReport(reportId)).download_url); } catch (err) { printError(err); process.exit(1); }
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
eval_
|
|
361
|
+
.command('trends')
|
|
362
|
+
.requiredOption('--agent <id>', 'Agent ID')
|
|
363
|
+
.option('--test-set <id>', 'Only runs of this test set')
|
|
364
|
+
.option('--limit <n>', 'Runs to include', '50')
|
|
365
|
+
.option('--json', 'Output as JSON')
|
|
366
|
+
.description('Pass rate and latency over past runs, with a drift warning when the pass rate dropped')
|
|
367
|
+
.action(async (options) => {
|
|
368
|
+
try {
|
|
369
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
370
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
371
|
+
const res = await sdk.trends(options.agent, options.testSet, Number(options.limit)) as { series: Array<Record<string, unknown>>; drift: Record<string, unknown> | null };
|
|
372
|
+
if (options.json) { printJson(res); return; }
|
|
373
|
+
console.log(formatTable(['Run', 'When', 'Pass rate', 'Total', 'p95 ms'], res.series.map((r) => [
|
|
374
|
+
String(r.run_id), String(r.created_at ?? ''), `${(Number(r.pass_rate ?? 0) * 100).toFixed(1)}%`, String(r.total ?? ''), String(r.latency_p95_ms ?? ''),
|
|
375
|
+
])));
|
|
376
|
+
if (res.drift) console.log(chalk.yellow(`Drift: pass rate fell from ${res.drift.from} to ${res.drift.to} in run ${res.drift.run_id}.`));
|
|
377
|
+
} catch (err) { printError(err); process.exit(1); }
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
eval_
|
|
381
|
+
.command('compare')
|
|
382
|
+
.argument('<run-a>', 'First run')
|
|
383
|
+
.argument('<run-b>', 'Second run')
|
|
384
|
+
.description('Pass rate and p95 latency difference between two runs')
|
|
385
|
+
.action(async (a: string, b: string) => {
|
|
386
|
+
try {
|
|
387
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
388
|
+
await sdk.getRunInOrg(a);
|
|
389
|
+
await sdk.getRunInOrg(b);
|
|
390
|
+
printJson(await sdk.compareRuns(a, b));
|
|
391
|
+
} catch (err) { printError(err); process.exit(1); }
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
eval_
|
|
395
|
+
.command('metrics')
|
|
396
|
+
.description('Voice quality metrics a test item may require (names for expected.voice.required_metrics)')
|
|
397
|
+
.action(async () => {
|
|
398
|
+
try { printJson(await new EvalSDK(new IndemnClient()).qualityMetrics()); } catch (err) { printError(err); process.exit(1); }
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
eval_
|
|
402
|
+
.command('transcript')
|
|
403
|
+
.requiredOption('--agent <id>', 'Agent ID (needs a stored evaluator config)')
|
|
404
|
+
.requiredOption('--baseline <file>', 'JSON list of { sender: "user"|"bot", text }')
|
|
405
|
+
.requiredOption('--replay <file>', 'JSON list of { sender: "user"|"bot", text }')
|
|
406
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
407
|
+
.description('Grade a replayed conversation against a baseline one')
|
|
408
|
+
.action(async (options) => {
|
|
409
|
+
try {
|
|
410
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
411
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
412
|
+
await confirmOrExit('Grade the transcript? It uses paid model calls.', Boolean(options.yes));
|
|
413
|
+
printJson(await sdk.evaluateTranscript({
|
|
414
|
+
agent_id: options.agent,
|
|
415
|
+
baseline_transcript: JSON.parse(fs.readFileSync(options.baseline, 'utf8')),
|
|
416
|
+
replay_transcript: JSON.parse(fs.readFileSync(options.replay, 'utf8')),
|
|
417
|
+
}));
|
|
418
|
+
} catch (err) { printError(err); process.exit(1); }
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const prod = eval_.command('prod-calls').description('Ingested production voice calls, and turning them into regression tests');
|
|
422
|
+
prod.command('list')
|
|
423
|
+
.requiredOption('--agent <id>', 'Agent ID')
|
|
424
|
+
.option('--search <text>', 'Only calls whose transcript contains this')
|
|
425
|
+
.option('--limit <n>', 'At most this many', '50')
|
|
426
|
+
.action(async (options) => {
|
|
427
|
+
try {
|
|
428
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
429
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
430
|
+
printJson(await sdk.prodCalls(options.agent, { search: options.search, limit: Number(options.limit) }));
|
|
431
|
+
} catch (err) { printError(err); process.exit(1); }
|
|
432
|
+
});
|
|
433
|
+
prod.command('promote')
|
|
434
|
+
.argument('<session-id>', 'Call session id')
|
|
435
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
436
|
+
.description('Add the call as a test item to the agent\'s "Regressions (review)" test set')
|
|
437
|
+
.action(async (sessionId: string, options) => {
|
|
438
|
+
try {
|
|
439
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
440
|
+
const call = await sdk.prodCall(sessionId);
|
|
441
|
+
if (call.bot_id) await sdk.assertAgentInOrg(String(call.bot_id));
|
|
442
|
+
await confirmOrExit(`Add call ${sessionId} to the "Regressions (review)" test set?`, Boolean(options.yes));
|
|
443
|
+
printJson(await sdk.promoteProdCall(sessionId));
|
|
444
|
+
} catch (err) { printError(err); process.exit(1); }
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const datasets = eval_.command('datasets').description('Low-level datasets the evaluation trigger creates');
|
|
448
|
+
datasets.command('list').requiredOption('--agent <id>', 'Agent ID').action(async (options) => {
|
|
449
|
+
try {
|
|
450
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
451
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
452
|
+
printJson(await sdk.listDatasets(options.agent));
|
|
453
|
+
} catch (err) { printError(err); process.exit(1); }
|
|
454
|
+
});
|
|
455
|
+
datasets.command('cases').argument('<dataset-id>').description('Test cases of a dataset').action(async (id: string) => {
|
|
456
|
+
try {
|
|
457
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
458
|
+
const ds = await sdk.getDataset(id);
|
|
459
|
+
if (ds.agent_id) await sdk.assertAgentInOrg(String(ds.agent_id));
|
|
460
|
+
printJson(await sdk.listTestCases(id));
|
|
461
|
+
} catch (err) { printError(err); process.exit(1); }
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const evaluators = eval_.command('evaluators').description('Evaluator types and the stored evaluator configs of an agent');
|
|
465
|
+
evaluators.command('types').action(async () => {
|
|
466
|
+
try { printJson(await new EvalSDK(new IndemnClient()).evaluatorTypes()); } catch (err) { printError(err); process.exit(1); }
|
|
467
|
+
});
|
|
468
|
+
evaluators.command('list').requiredOption('--agent <id>', 'Agent ID').action(async (options) => {
|
|
469
|
+
try {
|
|
470
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
471
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
472
|
+
printJson(await sdk.listEvaluatorConfigs(options.agent));
|
|
473
|
+
} catch (err) { printError(err); process.exit(1); }
|
|
474
|
+
});
|
|
475
|
+
evaluators.command('delete').argument('<config-id>').option('--yes', 'Do not ask for confirmation').action(async (id: string, options) => {
|
|
476
|
+
try {
|
|
477
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
478
|
+
const cfg = await sdk.getEvaluatorConfig(id);
|
|
479
|
+
if (cfg.agent_id) await sdk.assertAgentInOrg(String(cfg.agent_id));
|
|
480
|
+
await confirmOrExit(`Delete evaluator config ${id}?`, Boolean(options.yes));
|
|
481
|
+
printJson(await sdk.deleteEvaluatorConfig(id));
|
|
482
|
+
} catch (err) { printError(err); process.exit(1); }
|
|
483
|
+
});
|
|
484
|
+
|
|
287
485
|
// Rubric commands — registered on program, not nested under eval
|
|
288
486
|
const rubric = program.command('rubric').description('Manage evaluation rubrics');
|
|
289
487
|
|
|
290
488
|
rubric
|
|
291
489
|
.command('list')
|
|
292
|
-
.description('List rubrics')
|
|
293
|
-
.
|
|
490
|
+
.description('List rubrics of an agent')
|
|
491
|
+
.requiredOption('--agent-id <id>', 'Agent ID (rubrics of other orgs are never listed)')
|
|
294
492
|
.option('--json', 'Output as JSON')
|
|
295
493
|
.option('--limit <n>', 'Limit number of results')
|
|
296
494
|
.option('--page <n>', 'Page number (use with --limit)')
|
|
@@ -300,6 +498,7 @@ export function registerEvalCommands(program: Command): void {
|
|
|
300
498
|
const sdk = new RubricsSDK(client);
|
|
301
499
|
const spinner = ora('Fetching rubrics...').start();
|
|
302
500
|
|
|
501
|
+
await new EvalSDK(client).assertAgentInOrg(options.agentId);
|
|
303
502
|
let rubrics = await sdk.listRubrics(options.agentId);
|
|
304
503
|
spinner.stop();
|
|
305
504
|
|
|
@@ -473,13 +672,10 @@ export function registerEvalCommands(program: Command): void {
|
|
|
473
672
|
.command('delete')
|
|
474
673
|
.argument('<id>', 'Rubric ID')
|
|
475
674
|
.description('Delete a rubric')
|
|
476
|
-
.
|
|
675
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
676
|
+
.action(async (id: string, options) => {
|
|
477
677
|
try {
|
|
478
|
-
|
|
479
|
-
if (!confirmed) {
|
|
480
|
-
console.log('Cancelled.');
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
678
|
+
await confirmOrExit(`Delete rubric ${id}? This cannot be undone.`, Boolean(options.yes));
|
|
483
679
|
|
|
484
680
|
const client = new IndemnClient();
|
|
485
681
|
const sdk = new RubricsSDK(client);
|
|
@@ -537,8 +733,8 @@ export function registerEvalCommands(program: Command): void {
|
|
|
537
733
|
|
|
538
734
|
testset
|
|
539
735
|
.command('list')
|
|
540
|
-
.description('List test sets')
|
|
541
|
-
.
|
|
736
|
+
.description('List test sets of an agent')
|
|
737
|
+
.requiredOption('--agent-id <id>', 'Agent ID (test sets of other orgs are never listed)')
|
|
542
738
|
.option('--json', 'Output as JSON')
|
|
543
739
|
.option('--limit <n>', 'Limit number of results')
|
|
544
740
|
.option('--page <n>', 'Page number (use with --limit)')
|
|
@@ -548,17 +744,8 @@ export function registerEvalCommands(program: Command): void {
|
|
|
548
744
|
const sdk = new TestSetsSDK(client);
|
|
549
745
|
const spinner = ora('Fetching test sets...').start();
|
|
550
746
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
sets = await sdk.listTestSets(options.agentId);
|
|
554
|
-
} catch (err: any) {
|
|
555
|
-
spinner.stop();
|
|
556
|
-
if (!options.agentId && err?.message?.includes('500')) {
|
|
557
|
-
printError('The server returned an error listing all test sets. Try filtering by agent: indemn testset list --agent-id <id>');
|
|
558
|
-
process.exit(1);
|
|
559
|
-
}
|
|
560
|
-
throw err;
|
|
561
|
-
}
|
|
747
|
+
await new EvalSDK(client).assertAgentInOrg(options.agentId);
|
|
748
|
+
let sets: any[] = await sdk.listTestSets(options.agentId);
|
|
562
749
|
spinner.stop();
|
|
563
750
|
|
|
564
751
|
if (options.page && options.limit) {
|
|
@@ -731,13 +918,10 @@ export function registerEvalCommands(program: Command): void {
|
|
|
731
918
|
.command('delete')
|
|
732
919
|
.argument('<id>', 'Test set ID')
|
|
733
920
|
.description('Delete a test set')
|
|
734
|
-
.
|
|
921
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
922
|
+
.action(async (id: string, options) => {
|
|
735
923
|
try {
|
|
736
|
-
|
|
737
|
-
if (!confirmed) {
|
|
738
|
-
console.log('Cancelled.');
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
924
|
+
await confirmOrExit(`Delete test set ${id}? This cannot be undone.`, Boolean(options.yes));
|
|
741
925
|
|
|
742
926
|
const client = new IndemnClient();
|
|
743
927
|
const sdk = new TestSetsSDK(client);
|