@respan/cli 0.11.0 → 0.12.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/dist/commands/demo/clear.d.ts +23 -0
- package/dist/commands/demo/clear.js +114 -0
- package/dist/commands/demo/index.d.ts +15 -0
- package/dist/commands/demo/index.js +81 -0
- package/dist/lib/demo/dataset-fixtures.d.ts +32 -0
- package/dist/lib/demo/dataset-fixtures.js +194 -0
- package/dist/lib/demo/evaluator-fixtures.d.ts +41 -0
- package/dist/lib/demo/evaluator-fixtures.js +251 -0
- package/dist/lib/demo/prompt-fixtures.d.ts +54 -0
- package/dist/lib/demo/prompt-fixtures.js +395 -0
- package/dist/lib/demo/seed-datasets.d.ts +15 -0
- package/dist/lib/demo/seed-datasets.js +107 -0
- package/dist/lib/demo/seed-evaluators.d.ts +22 -0
- package/dist/lib/demo/seed-evaluators.js +180 -0
- package/dist/lib/demo/seed-prompts.d.ts +10 -0
- package/dist/lib/demo/seed-prompts.js +111 -0
- package/dist/lib/demo/seed-traces.d.ts +27 -0
- package/dist/lib/demo/seed-traces.js +158 -0
- package/dist/lib/demo/seeders.d.ts +8 -0
- package/dist/lib/demo/seeders.js +11 -0
- package/dist/lib/demo/trace-fixtures.d.ts +31 -0
- package/dist/lib/demo/trace-fixtures.js +79 -0
- package/dist/lib/demo/types.d.ts +57 -0
- package/dist/lib/demo/types.js +9 -0
- package/oclif.manifest.json +784 -644
- package/package.json +1 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseCommand } from '../../lib/base-command.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tear down what `respan demo` created. Each seeder owns how it identifies its own
|
|
4
|
+
* demo resources — prompts by exact fixture name, traces by the `demo` workflow —
|
|
5
|
+
* so a resource that was manually renamed/relabeled may not be matched, the same
|
|
6
|
+
* identity contract used when seeding.
|
|
7
|
+
*/
|
|
8
|
+
export default class DemoClear extends BaseCommand {
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
static flags: {
|
|
12
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
};
|
|
20
|
+
run(): Promise<void>;
|
|
21
|
+
private previewDeletions;
|
|
22
|
+
private printSummary;
|
|
23
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { confirm } from '@inquirer/prompts';
|
|
3
|
+
import { BaseCommand } from '../../lib/base-command.js';
|
|
4
|
+
import { getActiveProfile, getCredential } from '../../lib/config.js';
|
|
5
|
+
import { GREEN, DIM, BOLD, RESET } from '../../lib/colors.js';
|
|
6
|
+
import { SEEDERS } from '../../lib/demo/seeders.js';
|
|
7
|
+
/**
|
|
8
|
+
* Tear down what `respan demo` created. Each seeder owns how it identifies its own
|
|
9
|
+
* demo resources — prompts by exact fixture name, traces by the `demo` workflow —
|
|
10
|
+
* so a resource that was manually renamed/relabeled may not be matched, the same
|
|
11
|
+
* identity contract used when seeding.
|
|
12
|
+
*/
|
|
13
|
+
class DemoClear extends BaseCommand {
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(DemoClear);
|
|
16
|
+
this.globalFlags = flags;
|
|
17
|
+
const profile = flags.profile || getActiveProfile();
|
|
18
|
+
if (!getCredential(profile) && !flags['api-key']) {
|
|
19
|
+
this.error('Not authenticated. Run `respan auth login` (or set RESPAN_API_KEY) before running `respan demo clear`.', { exit: 1 });
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const client = this.getClient();
|
|
23
|
+
const ctx = {
|
|
24
|
+
client,
|
|
25
|
+
authHeader: this.getAuthHeader(),
|
|
26
|
+
baseUrl: this.getBaseUrl(),
|
|
27
|
+
};
|
|
28
|
+
// Preview what exists before deleting anything.
|
|
29
|
+
const existing = [];
|
|
30
|
+
for (const seeder of SEEDERS) {
|
|
31
|
+
const matches = await this.spin(`Finding ${seeder.label}`, () => seeder.findExisting(ctx));
|
|
32
|
+
existing.push({ label: seeder.label, matches });
|
|
33
|
+
}
|
|
34
|
+
const total = existing.reduce((n, e) => n + e.matches.length, 0);
|
|
35
|
+
if (total === 0) {
|
|
36
|
+
this.log('');
|
|
37
|
+
this.log(`Nothing to clear in account ${DIM}(${profile})${RESET}.`);
|
|
38
|
+
this.log('');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!flags.yes && !flags.json) {
|
|
42
|
+
this.previewDeletions(profile, existing);
|
|
43
|
+
if (!process.stdin.isTTY) {
|
|
44
|
+
this.error('Refusing to delete without confirmation. Re-run with --yes.', { exit: 1 });
|
|
45
|
+
}
|
|
46
|
+
const proceed = await confirm({
|
|
47
|
+
message: `Delete ${total} demo resource(s) from account "${profile}"?`,
|
|
48
|
+
default: false,
|
|
49
|
+
});
|
|
50
|
+
if (!proceed) {
|
|
51
|
+
this.log('Aborted. Nothing was deleted.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const results = [];
|
|
56
|
+
for (const seeder of SEEDERS) {
|
|
57
|
+
const result = await this.spin(`Clearing ${seeder.label}`, () => seeder.clear(ctx));
|
|
58
|
+
results.push(result);
|
|
59
|
+
}
|
|
60
|
+
this.printSummary(profile, results);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
this.handleError(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
previewDeletions(profile, existing) {
|
|
67
|
+
this.log('');
|
|
68
|
+
this.log(`${BOLD}respan demo clear${RESET} will delete from account ${DIM}(${profile})${RESET}:`);
|
|
69
|
+
for (const { label, matches } of existing) {
|
|
70
|
+
if (matches.length === 0)
|
|
71
|
+
continue;
|
|
72
|
+
this.log('');
|
|
73
|
+
this.log(` ${BOLD}${label}${RESET}`);
|
|
74
|
+
for (const m of matches) {
|
|
75
|
+
this.log(` ${DIM}- ${m.name} (id: ${m.id})${RESET}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
this.log('');
|
|
79
|
+
}
|
|
80
|
+
printSummary(profile, results) {
|
|
81
|
+
if (this.globalFlags.json) {
|
|
82
|
+
this.outputResult(results);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.log('');
|
|
86
|
+
this.log(`${GREEN}✓${RESET} ${BOLD}respan demo clear${RESET} — account ${DIM}(${profile})${RESET}`);
|
|
87
|
+
for (const result of results) {
|
|
88
|
+
if (result.items.length === 0)
|
|
89
|
+
continue; // e.g. traces, which are never deleted
|
|
90
|
+
this.log('');
|
|
91
|
+
this.log(` ${BOLD}${result.label}${RESET}`);
|
|
92
|
+
for (const item of result.items) {
|
|
93
|
+
if (item.status === 'deleted') {
|
|
94
|
+
this.log(` ${GREEN}Deleted${RESET} ${item.name} ${DIM}(id: ${item.id})${RESET}`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.log(` ${DIM}Skipped ${item.name} (${item.reason})${RESET}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const deletedCount = results.reduce((n, r) => n + r.items.filter((i) => i.status === 'deleted').length, 0);
|
|
102
|
+
const skippedCount = results.reduce((n, r) => n + r.items.filter((i) => i.status === 'skipped').length, 0);
|
|
103
|
+
this.log('');
|
|
104
|
+
this.log(` ${DIM}${deletedCount} deleted, ${skippedCount} not found${RESET}`);
|
|
105
|
+
this.log('');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
DemoClear.description = 'Delete the sample prompts, datasets, and evaluators created by `respan demo`. Demo traces are left in place (each run is timestamped history). Idempotent — safe to re-run.';
|
|
109
|
+
DemoClear.examples = ['<%= config.bin %> demo clear', '<%= config.bin %> demo clear --yes'];
|
|
110
|
+
DemoClear.flags = {
|
|
111
|
+
...BaseCommand.baseFlags,
|
|
112
|
+
yes: Flags.boolean({ char: 'y', description: 'Skip the confirmation prompt', default: false }),
|
|
113
|
+
};
|
|
114
|
+
export default DemoClear;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../lib/base-command.js';
|
|
2
|
+
export default class Demo extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private printSummary;
|
|
15
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { BaseCommand } from '../../lib/base-command.js';
|
|
2
|
+
import { getActiveProfile, getCredential } from '../../lib/config.js';
|
|
3
|
+
import { GREEN, DIM, BOLD, RESET } from '../../lib/colors.js';
|
|
4
|
+
import { SEEDERS } from '../../lib/demo/seeders.js';
|
|
5
|
+
const PLATFORM_BASE = 'https://platform.respan.ai/platform';
|
|
6
|
+
/** Dashboard link for a seeded service, deep-linked/filtered where possible so the
|
|
7
|
+
* user lands directly on what was just created. Traces are pre-filtered to the
|
|
8
|
+
* `demo` workflow so they're isolated from any existing traffic. */
|
|
9
|
+
function viewLink(service) {
|
|
10
|
+
switch (service) {
|
|
11
|
+
case 'prompts':
|
|
12
|
+
return `${PLATFORM_BASE}/prompts`;
|
|
13
|
+
case 'datasets':
|
|
14
|
+
return `${PLATFORM_BASE}/datasets`;
|
|
15
|
+
case 'evaluators':
|
|
16
|
+
return `${PLATFORM_BASE}/evaluators`;
|
|
17
|
+
case 'traces':
|
|
18
|
+
return `${PLATFORM_BASE}/traces?span_workflow_name=demo`;
|
|
19
|
+
default:
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
class Demo extends BaseCommand {
|
|
24
|
+
async run() {
|
|
25
|
+
const { flags } = await this.parse(Demo);
|
|
26
|
+
this.globalFlags = flags;
|
|
27
|
+
const profile = flags.profile || getActiveProfile();
|
|
28
|
+
if (!getCredential(profile) && !flags['api-key']) {
|
|
29
|
+
this.error('Not authenticated. Run `respan auth login` (or set RESPAN_API_KEY) before running `respan demo`.', { exit: 1 });
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const client = this.getClient();
|
|
33
|
+
const ctx = {
|
|
34
|
+
client,
|
|
35
|
+
authHeader: this.getAuthHeader(),
|
|
36
|
+
baseUrl: this.getBaseUrl(),
|
|
37
|
+
};
|
|
38
|
+
const results = [];
|
|
39
|
+
for (const seeder of SEEDERS) {
|
|
40
|
+
const result = await this.spin(`Seeding ${seeder.label}`, () => seeder.run(ctx));
|
|
41
|
+
results.push(result);
|
|
42
|
+
}
|
|
43
|
+
this.printSummary(profile, results);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
this.handleError(error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
printSummary(profile, results) {
|
|
50
|
+
if (this.globalFlags.json) {
|
|
51
|
+
this.outputResult(results);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.log('');
|
|
55
|
+
this.log(`${GREEN}✓${RESET} ${BOLD}respan demo${RESET} — seeded into account ${DIM}(${profile})${RESET}`);
|
|
56
|
+
for (const result of results) {
|
|
57
|
+
this.log('');
|
|
58
|
+
this.log(` ${BOLD}${result.label}${RESET}`);
|
|
59
|
+
for (const item of result.items) {
|
|
60
|
+
if (item.status === 'created') {
|
|
61
|
+
this.log(` ${GREEN}Created${RESET} ${item.name} ${DIM}(id: ${item.id})${RESET}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.log(` ${DIM}Skipped ${item.name} (${item.reason})${RESET}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const link = viewLink(result.service);
|
|
68
|
+
if (link)
|
|
69
|
+
this.log(` ${DIM}View:${RESET} ${link}`);
|
|
70
|
+
}
|
|
71
|
+
const createdCount = results.reduce((n, r) => n + r.items.filter((i) => i.status === 'created').length, 0);
|
|
72
|
+
const skippedCount = results.reduce((n, r) => n + r.items.filter((i) => i.status === 'skipped').length, 0);
|
|
73
|
+
this.log('');
|
|
74
|
+
this.log(` ${DIM}${createdCount} created, ${skippedCount} skipped${RESET}`);
|
|
75
|
+
this.log('');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
Demo.description = 'Seed your account with sample data (prompts and traces) so you have something to explore. Idempotent — safe to re-run.';
|
|
79
|
+
Demo.examples = ['<%= config.bin %> demo'];
|
|
80
|
+
Demo.flags = { ...BaseCommand.baseFlags };
|
|
81
|
+
export default Demo;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frozen demo datasets for `respan demo`, hand-authored as typed TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* Two datasets, numbered to match the demo prompt/evaluator they pair with:
|
|
5
|
+
* - "Demo dataset 1: support messages" -> prompt 1 / evaluator 1
|
|
6
|
+
* Rows are customer messages that probe Lumen's refund/escalation policy
|
|
7
|
+
* edges. No `expected_output` — evaluator 1 scores compliance from the
|
|
8
|
+
* reply alone (it uses {{input}}/{{output}} only).
|
|
9
|
+
* - "Demo dataset 3: call transcripts" -> prompt 3 / evaluator 3
|
|
10
|
+
* Rows are call transcripts, each with a gold-label classification in
|
|
11
|
+
* `expected_output` (evaluator 3 compares against {{expected_output}}).
|
|
12
|
+
*
|
|
13
|
+
* `input` is an object of the prompt's template variables (`customer_message` /
|
|
14
|
+
* `call_transcript`); it is stored stringified and parsed into the row's mapped
|
|
15
|
+
* fields. `expected_output` is authored here as an object for readability and
|
|
16
|
+
* JSON-stringified at seed time — passing a raw object makes the platform coerce
|
|
17
|
+
* it to single-quoted python-repr (invalid JSON the grader would then see).
|
|
18
|
+
*/
|
|
19
|
+
/** One dataset row: template variables plus an optional gold label. */
|
|
20
|
+
export interface DemoDatasetRow {
|
|
21
|
+
/** The prompt's template variables for this row. */
|
|
22
|
+
input: Record<string, unknown>;
|
|
23
|
+
/** Gold label (object; stringified to JSON at seed time). */
|
|
24
|
+
expected_output?: unknown;
|
|
25
|
+
}
|
|
26
|
+
/** One demo dataset: name/description plus its rows. */
|
|
27
|
+
export interface DemoDatasetFixture {
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
rows: DemoDatasetRow[];
|
|
31
|
+
}
|
|
32
|
+
export declare const DEMO_DATASETS: DemoDatasetFixture[];
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frozen demo datasets for `respan demo`, hand-authored as typed TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* Two datasets, numbered to match the demo prompt/evaluator they pair with:
|
|
5
|
+
* - "Demo dataset 1: support messages" -> prompt 1 / evaluator 1
|
|
6
|
+
* Rows are customer messages that probe Lumen's refund/escalation policy
|
|
7
|
+
* edges. No `expected_output` — evaluator 1 scores compliance from the
|
|
8
|
+
* reply alone (it uses {{input}}/{{output}} only).
|
|
9
|
+
* - "Demo dataset 3: call transcripts" -> prompt 3 / evaluator 3
|
|
10
|
+
* Rows are call transcripts, each with a gold-label classification in
|
|
11
|
+
* `expected_output` (evaluator 3 compares against {{expected_output}}).
|
|
12
|
+
*
|
|
13
|
+
* `input` is an object of the prompt's template variables (`customer_message` /
|
|
14
|
+
* `call_transcript`); it is stored stringified and parsed into the row's mapped
|
|
15
|
+
* fields. `expected_output` is authored here as an object for readability and
|
|
16
|
+
* JSON-stringified at seed time — passing a raw object makes the platform coerce
|
|
17
|
+
* it to single-quoted python-repr (invalid JSON the grader would then see).
|
|
18
|
+
*/
|
|
19
|
+
export const DEMO_DATASETS = [
|
|
20
|
+
{
|
|
21
|
+
name: 'Demo dataset 1: support messages',
|
|
22
|
+
description: 'Customer messages that probe Lumen Electronics refund/escalation policy edges, for evaluating the support agent (prompt 1).',
|
|
23
|
+
rows: [
|
|
24
|
+
{
|
|
25
|
+
input: {
|
|
26
|
+
customer_message: "Hi, my wireless earbuds (order A-50231) arrived two days ago but the left one won't charge. I'd like a refund, please.",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
input: {
|
|
31
|
+
customer_message: "I bought the clearance Lumen soundbar (order A-49120, it was marked final sale) last week and I've changed my mind. I'd like my money back.",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
input: {
|
|
36
|
+
customer_message: "Order A-51002 was supposed to arrive yesterday and it's still not here. I want a refund for the delay.",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
input: {
|
|
41
|
+
customer_message: 'Tracking says my package (A-48817) was delivered three days ago, but I never received it. I want a full refund.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
input: {
|
|
46
|
+
customer_message: 'Please process a refund on order A-52340 for $349.99.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
input: {
|
|
51
|
+
customer_message: "My Lumen 4K monitor (order A-53001) stopped working after two days — it was $279. I'd like a refund because it's defective.",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
input: {
|
|
56
|
+
customer_message: "Where is my order A-50777? I just want to know when it'll arrive.",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'Demo dataset 3: call transcripts',
|
|
63
|
+
description: 'Support call transcripts with gold-label classifications, for evaluating the call-transcript classifier (prompt 3).',
|
|
64
|
+
rows: [
|
|
65
|
+
{
|
|
66
|
+
input: {
|
|
67
|
+
call_transcript: 'Agent: Hi, this is Dana at Lumen Electronics support. How can I help you today?\n' +
|
|
68
|
+
"Customer: My monitor, order A-51440, arrived with a cracked panel. I'm pretty annoyed.\n" +
|
|
69
|
+
"Agent: I'm really sorry about that. I've created a replacement order — it ships today and you'll get tracking by email.\n" +
|
|
70
|
+
'Customer: Oh, great. Thank you, that was easy.\n' +
|
|
71
|
+
'Agent: My pleasure. Have a great day.',
|
|
72
|
+
},
|
|
73
|
+
expected_output: {
|
|
74
|
+
primary_reason: 'product_defect',
|
|
75
|
+
resolution_status: 'resolved',
|
|
76
|
+
customer_sentiment_start: 'frustrated',
|
|
77
|
+
customer_sentiment_end: 'positive',
|
|
78
|
+
escalation_requested: false,
|
|
79
|
+
followup_required: true,
|
|
80
|
+
order_ids: ['A-51440'],
|
|
81
|
+
products_mentioned: ['Lumen Monitor'],
|
|
82
|
+
agent_name: 'Dana',
|
|
83
|
+
summary: 'Customer reported a cracked monitor panel (A-51440); agent created a same-day replacement shipment.',
|
|
84
|
+
key_quote: 'My monitor, order A-51440, arrived with a cracked panel.',
|
|
85
|
+
agent_followed_greeting: true,
|
|
86
|
+
agent_empathy_shown: true,
|
|
87
|
+
compliance_flags: [],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
input: {
|
|
92
|
+
call_transcript: 'Agent: Thanks for calling Lumen Electronics, this is Marcus.\n' +
|
|
93
|
+
"Customer: I was charged twice for order A-52210 and nobody's fixed it. I want a manager, now.\n" +
|
|
94
|
+
"Agent: I can see two charges. I'm not able to reverse it from my side today.\n" +
|
|
95
|
+
"Customer: Unacceptable. Escalate this or I'm calling my bank for a chargeback.\n" +
|
|
96
|
+
"Agent: I'll escalate to a supervisor who will call you back within 24 hours.\n" +
|
|
97
|
+
'Customer: Fine.',
|
|
98
|
+
},
|
|
99
|
+
expected_output: {
|
|
100
|
+
primary_reason: 'billing_dispute',
|
|
101
|
+
resolution_status: 'escalated',
|
|
102
|
+
customer_sentiment_start: 'angry',
|
|
103
|
+
customer_sentiment_end: 'frustrated',
|
|
104
|
+
escalation_requested: true,
|
|
105
|
+
followup_required: true,
|
|
106
|
+
order_ids: ['A-52210'],
|
|
107
|
+
products_mentioned: [],
|
|
108
|
+
agent_name: 'Marcus',
|
|
109
|
+
summary: 'Customer disputed a double charge on A-52210 and demanded a manager; agent escalated to a supervisor with a 24-hour callback.',
|
|
110
|
+
key_quote: "I was charged twice for order A-52210 and nobody's fixed it.",
|
|
111
|
+
agent_followed_greeting: true,
|
|
112
|
+
agent_empathy_shown: false,
|
|
113
|
+
compliance_flags: ['no_identity_verification'],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
input: {
|
|
118
|
+
call_transcript: 'Agent: Hi, my name is Max and this is the customer support line for Lumen Electronics. How can I help you today?\n' +
|
|
119
|
+
'Customer: Finally. I ordered the Lumen Wireless Headphones almost two weeks ago, order A-48217, and they still have not shown up. This is ridiculous.\n' +
|
|
120
|
+
"Agent: I'm really sorry about the wait, and I understand how frustrating that is. It looks like A-48217 was delayed at the carrier's sorting facility. I've flagged it and I'll email you an update within 24 hours.\n" +
|
|
121
|
+
"Customer: Okay, I'd appreciate that.\n" +
|
|
122
|
+
'Agent: Thank you for your patience.',
|
|
123
|
+
},
|
|
124
|
+
expected_output: {
|
|
125
|
+
primary_reason: 'shipping_delay',
|
|
126
|
+
resolution_status: 'partially_resolved',
|
|
127
|
+
customer_sentiment_start: 'frustrated',
|
|
128
|
+
customer_sentiment_end: 'neutral',
|
|
129
|
+
escalation_requested: false,
|
|
130
|
+
followup_required: true,
|
|
131
|
+
order_ids: ['A-48217'],
|
|
132
|
+
products_mentioned: ['Lumen Wireless Headphones'],
|
|
133
|
+
agent_name: 'Max',
|
|
134
|
+
summary: 'Customer reported headphones order A-48217 was delayed; agent identified a carrier delay and promised an emailed update within 24 hours.',
|
|
135
|
+
key_quote: 'I ordered the Lumen Wireless Headphones almost two weeks ago and they still have not shown up.',
|
|
136
|
+
agent_followed_greeting: true,
|
|
137
|
+
agent_empathy_shown: true,
|
|
138
|
+
compliance_flags: [],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
input: {
|
|
143
|
+
call_transcript: "Agent: Hello, you've reached Lumen Electronics, this is Priya.\n" +
|
|
144
|
+
"Customer: Hi, I'd like to return my speaker, order A-50988. It works fine, I just don't need it.\n" +
|
|
145
|
+
'Agent: No problem. It is within our 30-day window, so I have issued a full refund of $59.99 and emailed a prepaid return label.\n' +
|
|
146
|
+
'Customer: Perfect, thanks so much.\n' +
|
|
147
|
+
"Agent: You're welcome!",
|
|
148
|
+
},
|
|
149
|
+
expected_output: {
|
|
150
|
+
primary_reason: 'return_or_refund',
|
|
151
|
+
resolution_status: 'resolved',
|
|
152
|
+
customer_sentiment_start: 'neutral',
|
|
153
|
+
customer_sentiment_end: 'positive',
|
|
154
|
+
escalation_requested: false,
|
|
155
|
+
followup_required: true,
|
|
156
|
+
order_ids: ['A-50988'],
|
|
157
|
+
products_mentioned: ['Lumen Speaker'],
|
|
158
|
+
agent_name: 'Priya',
|
|
159
|
+
summary: 'Customer requested a return for unwanted speaker A-50988; agent issued a full refund and sent a prepaid return label.',
|
|
160
|
+
key_quote: "I'd like to return my speaker, order A-50988. It works fine, I just don't need it.",
|
|
161
|
+
agent_followed_greeting: true,
|
|
162
|
+
agent_empathy_shown: false,
|
|
163
|
+
compliance_flags: [],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
input: {
|
|
168
|
+
call_transcript: "Customer: My Lumen smart hub won't connect to wifi no matter what I try.\n" +
|
|
169
|
+
'Agent: Have you tried resetting it? Hold the button for ten seconds.\n' +
|
|
170
|
+
'Customer: Yes, three times. Still nothing.\n' +
|
|
171
|
+
"Agent: I'm not sure what else to suggest. I'll open a ticket and someone from tech will email you.\n" +
|
|
172
|
+
'Customer: So you can’t actually help me right now?\n' +
|
|
173
|
+
'Agent: Not today, sorry.',
|
|
174
|
+
},
|
|
175
|
+
expected_output: {
|
|
176
|
+
primary_reason: 'technical_support',
|
|
177
|
+
resolution_status: 'unresolved',
|
|
178
|
+
customer_sentiment_start: 'frustrated',
|
|
179
|
+
customer_sentiment_end: 'frustrated',
|
|
180
|
+
escalation_requested: false,
|
|
181
|
+
followup_required: true,
|
|
182
|
+
order_ids: [],
|
|
183
|
+
products_mentioned: ['Lumen Smart Hub'],
|
|
184
|
+
agent_name: null,
|
|
185
|
+
summary: 'Customer’s smart hub would not connect to wifi; a basic reset failed and the agent opened a ticket for tech to follow up by email.',
|
|
186
|
+
key_quote: "My Lumen smart hub won't connect to wifi no matter what I try.",
|
|
187
|
+
agent_followed_greeting: false,
|
|
188
|
+
agent_empathy_shown: false,
|
|
189
|
+
compliance_flags: ['no_identity_verification', 'no_resolution_offered'],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frozen snapshot of the demo evaluators, captured byte-for-byte from the
|
|
3
|
+
* owner's deployed evaluators (graders + Blockly workflow task graphs).
|
|
4
|
+
*
|
|
5
|
+
* Source of truth for `respan demo`. Each entry is one user-facing evaluator: the
|
|
6
|
+
* deployed workflow's task graph plus the grader record(s) it references. The
|
|
7
|
+
* grader `type` is captured verbatim ("human" is the stored default) — the actual
|
|
8
|
+
* LLM engine lives in each eval task's `generation_method`/`_blockly_evaluator_kind`,
|
|
9
|
+
* so replaying the graph reproduces the verified scoring behavior exactly.
|
|
10
|
+
*
|
|
11
|
+
* This is a frozen snapshot: if the source evaluators change, this file does NOT
|
|
12
|
+
* update until someone re-snapshots and ships a new build.
|
|
13
|
+
*
|
|
14
|
+
* Recreate recipe (see seed-evaluators.ts): create each grader -> map its
|
|
15
|
+
* `originalId` to the new id -> rewrite every eval task's `config.evaluator_id`
|
|
16
|
+
* via that map -> createWorkflow -> commit -> deploy. Task ids are kept verbatim
|
|
17
|
+
* so the compute task's `state.<task_id>` input references stay valid.
|
|
18
|
+
*/
|
|
19
|
+
/** A grader record referenced by an evaluator's workflow. */
|
|
20
|
+
export interface DemoEvaluatorGrader {
|
|
21
|
+
name: string;
|
|
22
|
+
/** The grader's id inside the captured task graph. Remapped to the freshly
|
|
23
|
+
* created grader id at seed time so the eval tasks point at the new record. */
|
|
24
|
+
originalId: string;
|
|
25
|
+
type: string;
|
|
26
|
+
score_value_type: string;
|
|
27
|
+
score_config: Record<string, unknown>;
|
|
28
|
+
passing_conditions: Record<string, unknown>;
|
|
29
|
+
llm_config: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
/** One demo evaluator: the deployed workflow graph plus its grader(s). */
|
|
32
|
+
export interface DemoEvaluatorFixture {
|
|
33
|
+
/** Workflow name (the user-facing evaluator name); used for dedup. */
|
|
34
|
+
workflowName: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
graders: DemoEvaluatorGrader[];
|
|
37
|
+
/** Verbatim deployed workflow task graph. Only each eval task's
|
|
38
|
+
* `config.evaluator_id` is rewritten at seed time. */
|
|
39
|
+
tasks: Record<string, unknown>[];
|
|
40
|
+
}
|
|
41
|
+
export declare const DEMO_EVALUATORS: DemoEvaluatorFixture[];
|