@ddse/acm-examples 0.5.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/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/acm-demo.ts +495 -0
- package/data/coaching/agents.json +16 -0
- package/data/coaching/transcripts.json +37 -0
- package/data/documents.json +72 -0
- package/data/entitlement/customers.json +38 -0
- package/data/entitlement/policies.json +38 -0
- package/data/incidents/incidents.json +30 -0
- package/data/incidents/routing_rules.json +36 -0
- package/data/invoices/invoices.json +38 -0
- package/data/invoices/purchase-orders.json +38 -0
- package/data/issues.json +99 -0
- package/data/knowledge/docs/kb-001.md +7 -0
- package/data/knowledge/docs/kb-002.md +7 -0
- package/data/knowledge/docs/kb-003.md +9 -0
- package/data/knowledge/index.json +25 -0
- package/data/orders.json +106 -0
- package/dist/bin/acm-demo.d.ts +3 -0
- package/dist/bin/acm-demo.d.ts.map +1 -0
- package/dist/bin/acm-demo.js +392 -0
- package/dist/bin/acm-demo.js.map +1 -0
- package/dist/src/context/directives.d.ts +3 -0
- package/dist/src/context/directives.d.ts.map +1 -0
- package/dist/src/context/directives.js +325 -0
- package/dist/src/context/directives.js.map +1 -0
- package/dist/src/context/index.d.ts +2 -0
- package/dist/src/context/index.d.ts.map +1 -0
- package/dist/src/context/index.js +2 -0
- package/dist/src/context/index.js.map +1 -0
- package/dist/src/data/coaching.d.ts +19 -0
- package/dist/src/data/coaching.d.ts.map +1 -0
- package/dist/src/data/coaching.js +22 -0
- package/dist/src/data/coaching.js.map +1 -0
- package/dist/src/data/entitlement.d.ts +25 -0
- package/dist/src/data/entitlement.d.ts.map +1 -0
- package/dist/src/data/entitlement.js +26 -0
- package/dist/src/data/entitlement.js.map +1 -0
- package/dist/src/data/incidents.d.ts +23 -0
- package/dist/src/data/incidents.d.ts.map +1 -0
- package/dist/src/data/incidents.js +37 -0
- package/dist/src/data/incidents.js.map +1 -0
- package/dist/src/data/invoices.d.ts +34 -0
- package/dist/src/data/invoices.d.ts.map +1 -0
- package/dist/src/data/invoices.js +49 -0
- package/dist/src/data/invoices.js.map +1 -0
- package/dist/src/data/knowledge.d.ts +11 -0
- package/dist/src/data/knowledge.d.ts.map +1 -0
- package/dist/src/data/knowledge.js +57 -0
- package/dist/src/data/knowledge.js.map +1 -0
- package/dist/src/data/loader.d.ts +4 -0
- package/dist/src/data/loader.d.ts.map +1 -0
- package/dist/src/data/loader.js +69 -0
- package/dist/src/data/loader.js.map +1 -0
- package/dist/src/examples/scenarios.d.ts +23 -0
- package/dist/src/examples/scenarios.d.ts.map +1 -0
- package/dist/src/examples/scenarios.js +609 -0
- package/dist/src/examples/scenarios.js.map +1 -0
- package/dist/src/goals/index.d.ts +8 -0
- package/dist/src/goals/index.d.ts.map +1 -0
- package/dist/src/goals/index.js +12 -0
- package/dist/src/goals/index.js.map +1 -0
- package/dist/src/policy.d.ts +5 -0
- package/dist/src/policy.d.ts.map +1 -0
- package/dist/src/policy.js +24 -0
- package/dist/src/policy.js.map +1 -0
- package/dist/src/registries.d.ts +18 -0
- package/dist/src/registries.d.ts.map +1 -0
- package/dist/src/registries.js +38 -0
- package/dist/src/registries.js.map +1 -0
- package/dist/src/renderer.d.ts +9 -0
- package/dist/src/renderer.d.ts.map +1 -0
- package/dist/src/renderer.js +76 -0
- package/dist/src/renderer.js.map +1 -0
- package/dist/src/search/bm25.d.ts +68 -0
- package/dist/src/search/bm25.d.ts.map +1 -0
- package/dist/src/search/bm25.js +131 -0
- package/dist/src/search/bm25.js.map +1 -0
- package/dist/src/search/index.d.ts +2 -0
- package/dist/src/search/index.d.ts.map +1 -0
- package/dist/src/search/index.js +3 -0
- package/dist/src/search/index.js.map +1 -0
- package/dist/src/tasks/coaching.d.ts +30 -0
- package/dist/src/tasks/coaching.d.ts.map +1 -0
- package/dist/src/tasks/coaching.js +143 -0
- package/dist/src/tasks/coaching.js.map +1 -0
- package/dist/src/tasks/entitlement.d.ts +29 -0
- package/dist/src/tasks/entitlement.d.ts.map +1 -0
- package/dist/src/tasks/entitlement.js +135 -0
- package/dist/src/tasks/entitlement.js.map +1 -0
- package/dist/src/tasks/incidents.d.ts +42 -0
- package/dist/src/tasks/incidents.d.ts.map +1 -0
- package/dist/src/tasks/incidents.js +189 -0
- package/dist/src/tasks/incidents.js.map +1 -0
- package/dist/src/tasks/index.d.ts +7 -0
- package/dist/src/tasks/index.d.ts.map +1 -0
- package/dist/src/tasks/index.js +7 -0
- package/dist/src/tasks/index.js.map +1 -0
- package/dist/src/tasks/invoices.d.ts +40 -0
- package/dist/src/tasks/invoices.d.ts.map +1 -0
- package/dist/src/tasks/invoices.js +180 -0
- package/dist/src/tasks/invoices.js.map +1 -0
- package/dist/src/tasks/knowledge.d.ts +23 -0
- package/dist/src/tasks/knowledge.d.ts.map +1 -0
- package/dist/src/tasks/knowledge.js +115 -0
- package/dist/src/tasks/knowledge.js.map +1 -0
- package/dist/src/tasks/legacy.d.ts +50 -0
- package/dist/src/tasks/legacy.d.ts.map +1 -0
- package/dist/src/tasks/legacy.js +85 -0
- package/dist/src/tasks/legacy.js.map +1 -0
- package/dist/src/tools/coaching/index.d.ts +49 -0
- package/dist/src/tools/coaching/index.d.ts.map +1 -0
- package/dist/src/tools/coaching/index.js +119 -0
- package/dist/src/tools/coaching/index.js.map +1 -0
- package/dist/src/tools/entitlement/index.d.ts +52 -0
- package/dist/src/tools/entitlement/index.d.ts.map +1 -0
- package/dist/src/tools/entitlement/index.js +120 -0
- package/dist/src/tools/entitlement/index.js.map +1 -0
- package/dist/src/tools/incidents/index.d.ts +55 -0
- package/dist/src/tools/incidents/index.d.ts.map +1 -0
- package/dist/src/tools/incidents/index.js +109 -0
- package/dist/src/tools/incidents/index.js.map +1 -0
- package/dist/src/tools/index.d.ts +90 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +109 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/invoices/index.d.ts +56 -0
- package/dist/src/tools/invoices/index.d.ts.map +1 -0
- package/dist/src/tools/invoices/index.js +85 -0
- package/dist/src/tools/invoices/index.js.map +1 -0
- package/dist/src/tools/knowledge/index.d.ts +52 -0
- package/dist/src/tools/knowledge/index.d.ts.map +1 -0
- package/dist/src/tools/knowledge/index.js +120 -0
- package/dist/src/tools/knowledge/index.js.map +1 -0
- package/dist/tests/bm25.test.d.ts +2 -0
- package/dist/tests/bm25.test.d.ts.map +1 -0
- package/dist/tests/bm25.test.js +98 -0
- package/dist/tests/bm25.test.js.map +1 -0
- package/dist/tests/integration.test.d.ts +2 -0
- package/dist/tests/integration.test.d.ts.map +1 -0
- package/dist/tests/integration.test.js +126 -0
- package/dist/tests/integration.test.js.map +1 -0
- package/dist/tests/plan-hydration.test.d.ts +2 -0
- package/dist/tests/plan-hydration.test.d.ts.map +1 -0
- package/dist/tests/plan-hydration.test.js +28 -0
- package/dist/tests/plan-hydration.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/examples-architecture.md +144 -0
- package/docs/successrun.md +1022 -0
- package/package.json +33 -0
- package/src/context/directives.ts +366 -0
- package/src/context/index.ts +1 -0
- package/src/data/coaching.ts +50 -0
- package/src/data/entitlement.ts +60 -0
- package/src/data/incidents.ts +78 -0
- package/src/data/invoices.ts +103 -0
- package/src/data/knowledge.ts +77 -0
- package/src/data/loader.ts +80 -0
- package/src/examples/scenarios.ts +724 -0
- package/src/goals/index.ts +18 -0
- package/src/policy.ts +30 -0
- package/src/registries.ts +48 -0
- package/src/renderer.ts +82 -0
- package/src/search/bm25.ts +173 -0
- package/src/search/index.ts +2 -0
- package/src/tasks/coaching.ts +217 -0
- package/src/tasks/entitlement.ts +197 -0
- package/src/tasks/incidents.ts +277 -0
- package/src/tasks/index.ts +6 -0
- package/src/tasks/invoices.ts +269 -0
- package/src/tasks/knowledge.ts +169 -0
- package/src/tasks/legacy.ts +112 -0
- package/src/tools/coaching/index.ts +197 -0
- package/src/tools/entitlement/index.ts +199 -0
- package/src/tools/incidents/index.ts +185 -0
- package/src/tools/index.ts +192 -0
- package/src/tools/invoices/index.ts +165 -0
- package/src/tools/knowledge/index.ts +203 -0
- package/tests/bm25.test.ts +129 -0
- package/tests/integration.test.ts +163 -0
- package/tests/plan-hydration.test.ts +33 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Goal/context index derived from scenario catalog
|
|
2
|
+
import type { Goal, Context } from '@ddse/acm-sdk';
|
|
3
|
+
import { scenarios, type ScenarioKey } from '../examples/scenarios.js';
|
|
4
|
+
|
|
5
|
+
type GoalMap = Record<ScenarioKey, Goal> & Record<string, Goal>;
|
|
6
|
+
type ContextMap = Record<ScenarioKey, Context> & Record<string, Context>;
|
|
7
|
+
|
|
8
|
+
export const goals = Object.keys(scenarios).reduce((acc, key) => {
|
|
9
|
+
const scenario = scenarios[key as ScenarioKey];
|
|
10
|
+
acc[key] = scenario.goal;
|
|
11
|
+
return acc;
|
|
12
|
+
}, {} as GoalMap);
|
|
13
|
+
|
|
14
|
+
export const contexts = Object.keys(scenarios).reduce((acc, key) => {
|
|
15
|
+
const scenario = scenarios[key as ScenarioKey];
|
|
16
|
+
acc[key] = scenario.context;
|
|
17
|
+
return acc;
|
|
18
|
+
}, {} as ContextMap);
|
package/src/policy.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Simple policy engine implementation
|
|
2
|
+
import { PolicyEngine, type PolicyDecision } from '@ddse/acm-sdk';
|
|
3
|
+
|
|
4
|
+
export class SimplePolicyEngine implements PolicyEngine {
|
|
5
|
+
async evaluate(
|
|
6
|
+
action: 'plan.admit' | 'task.pre' | 'task.post',
|
|
7
|
+
payload: any
|
|
8
|
+
): Promise<PolicyDecision> {
|
|
9
|
+
// Simple rule: deny high-risk refunds
|
|
10
|
+
if (action === 'task.pre' && payload.action === 'create_refund') {
|
|
11
|
+
// In a real implementation, we'd check risk from context
|
|
12
|
+
// For demo, randomly allow/deny based on amount
|
|
13
|
+
if (payload.amount && payload.amount > 100) {
|
|
14
|
+
return {
|
|
15
|
+
allow: false,
|
|
16
|
+
reason: 'Refund amount exceeds policy limit',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Default: allow
|
|
22
|
+
return {
|
|
23
|
+
allow: true,
|
|
24
|
+
limits: {
|
|
25
|
+
timeoutMs: 30000,
|
|
26
|
+
retries: 3,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Concrete registry implementations
|
|
2
|
+
import { CapabilityRegistry, ToolRegistry, type Capability, type Task, type Tool } from '@ddse/acm-sdk';
|
|
3
|
+
|
|
4
|
+
export class SimpleCapabilityRegistry extends CapabilityRegistry {
|
|
5
|
+
private tasks = new Map<string, Task>();
|
|
6
|
+
private capabilities = new Map<string, Capability>();
|
|
7
|
+
|
|
8
|
+
register(capability: Capability, task: Task): void {
|
|
9
|
+
this.capabilities.set(capability.name, capability);
|
|
10
|
+
this.tasks.set(capability.name, task);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
list(): Capability[] {
|
|
14
|
+
return Array.from(this.capabilities.values());
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
has(name: string): boolean {
|
|
18
|
+
return this.capabilities.has(name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
resolve(name: string): Task | undefined {
|
|
22
|
+
return this.tasks.get(name);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
inputSchema(name: string): unknown | undefined {
|
|
26
|
+
return this.capabilities.get(name)?.inputSchema;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
outputSchema(name: string): unknown | undefined {
|
|
30
|
+
return this.capabilities.get(name)?.outputSchema;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class SimpleToolRegistry extends ToolRegistry {
|
|
35
|
+
private tools = new Map<string, Tool>();
|
|
36
|
+
|
|
37
|
+
register(tool: Tool): void {
|
|
38
|
+
this.tools.set(tool.name(), tool);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get(name: string): Tool | undefined {
|
|
42
|
+
return this.tools.get(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
list(): string[] {
|
|
46
|
+
return Array.from(this.tools.keys());
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/renderer.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// CLI renderer for streaming output
|
|
2
|
+
export class CLIRenderer {
|
|
3
|
+
private taskStatus = new Map<string, string>();
|
|
4
|
+
|
|
5
|
+
renderPlannerToken(chunk: any): void {
|
|
6
|
+
if (chunk.delta) {
|
|
7
|
+
process.stdout.write(chunk.delta);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const summary: string[] = [];
|
|
11
|
+
if (typeof chunk.plans === 'number') {
|
|
12
|
+
const label = chunk.plans === 1 ? 'plan' : 'plans';
|
|
13
|
+
summary.push(`Generated ${chunk.plans} ${label}.`);
|
|
14
|
+
}
|
|
15
|
+
if (chunk.rationale) {
|
|
16
|
+
summary.push(chunk.rationale);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (summary.length > 0) {
|
|
20
|
+
process.stdout.write(`\n\n${summary.join('\n\n')}\n\n`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (chunk.done && summary.length === 0) {
|
|
24
|
+
process.stdout.write('\n\n');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
renderTaskUpdate(update: any): void {
|
|
29
|
+
const { taskId, status, step, output, error } = update;
|
|
30
|
+
|
|
31
|
+
if (status) {
|
|
32
|
+
this.taskStatus.set(taskId, status);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (status === 'running') {
|
|
36
|
+
console.log(`\n[${taskId}] Task started`);
|
|
37
|
+
} else if (status === 'completed') {
|
|
38
|
+
console.log(`[${taskId}] ✓ Task completed`);
|
|
39
|
+
if (output) {
|
|
40
|
+
console.log(` Output: ${JSON.stringify(output, null, 2)}`);
|
|
41
|
+
}
|
|
42
|
+
} else if (status === 'failed') {
|
|
43
|
+
console.error(`[${taskId}] ✗ Task failed: ${error}`);
|
|
44
|
+
} else if (step) {
|
|
45
|
+
console.log(`[${taskId}] → ${step}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
renderLedgerEntry(entry: any): void {
|
|
50
|
+
const icon = this.getIconForType(entry.type);
|
|
51
|
+
console.log(`${icon} [${entry.type}] ${JSON.stringify(entry.details)}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
renderSummary(result: any): void {
|
|
55
|
+
console.log('\n' + '='.repeat(60));
|
|
56
|
+
console.log('EXECUTION SUMMARY');
|
|
57
|
+
console.log('='.repeat(60));
|
|
58
|
+
const taskEntries = Object.entries(result.outputsByTask ?? {});
|
|
59
|
+
const ledgerEntries = Array.isArray(result.ledger) ? result.ledger.length : 0;
|
|
60
|
+
console.log(`Total tasks: ${taskEntries.length}`);
|
|
61
|
+
console.log(`Ledger entries: ${ledgerEntries}`);
|
|
62
|
+
console.log('\nOutputs:');
|
|
63
|
+
for (const [taskId, record] of taskEntries) {
|
|
64
|
+
const output = record && typeof record === 'object' && 'output' in record ? record.output : record;
|
|
65
|
+
console.log(` ${taskId}:`, JSON.stringify(output, null, 2));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private getIconForType(type: string): string {
|
|
70
|
+
switch (type) {
|
|
71
|
+
case 'PLAN_SELECTED': return '📋';
|
|
72
|
+
case 'GUARD_EVAL': return '🚦';
|
|
73
|
+
case 'TASK_START': return '▶️ ';
|
|
74
|
+
case 'TASK_END': return '✅';
|
|
75
|
+
case 'POLICY_PRE': return '🔒';
|
|
76
|
+
case 'POLICY_POST': return '🔓';
|
|
77
|
+
case 'VERIFICATION': return '✔️ ';
|
|
78
|
+
case 'ERROR': return '❌';
|
|
79
|
+
default: return '•';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Simple BM25 search implementation for ACM examples
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Document interface for BM25 search
|
|
5
|
+
*/
|
|
6
|
+
export interface Document {
|
|
7
|
+
id: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
content: string;
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Search result with score
|
|
15
|
+
*/
|
|
16
|
+
export interface SearchResult {
|
|
17
|
+
document: Document;
|
|
18
|
+
score: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* BM25 parameters
|
|
23
|
+
*/
|
|
24
|
+
export interface BM25Params {
|
|
25
|
+
k1?: number; // Term frequency saturation (default: 1.5)
|
|
26
|
+
b?: number; // Length normalization (default: 0.75)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Simple tokenizer
|
|
31
|
+
*/
|
|
32
|
+
function tokenize(text?: string | null): string[] {
|
|
33
|
+
if (!text) return [];
|
|
34
|
+
|
|
35
|
+
return text
|
|
36
|
+
.toLowerCase()
|
|
37
|
+
.replace(/[^\w\s]/g, ' ')
|
|
38
|
+
.split(/\s+/)
|
|
39
|
+
.filter((token) => token.length > 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* BM25 Search Engine
|
|
44
|
+
*
|
|
45
|
+
* Implements the BM25 ranking function for full-text search.
|
|
46
|
+
* BM25 is a probabilistic ranking function used by search engines
|
|
47
|
+
* to estimate the relevance of documents to a query.
|
|
48
|
+
*/
|
|
49
|
+
export class BM25Search {
|
|
50
|
+
private documents: Document[] = [];
|
|
51
|
+
private documentTokens: Map<string, string[]> = new Map();
|
|
52
|
+
private documentFrequency: Map<string, number> = new Map();
|
|
53
|
+
private averageDocumentLength: number = 0;
|
|
54
|
+
private k1: number;
|
|
55
|
+
private b: number;
|
|
56
|
+
|
|
57
|
+
constructor(params: BM25Params = {}) {
|
|
58
|
+
this.k1 = params.k1 ?? 1.5;
|
|
59
|
+
this.b = params.b ?? 0.75;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Index documents for search
|
|
64
|
+
*/
|
|
65
|
+
index(documents: Document[]): void {
|
|
66
|
+
this.documents = documents;
|
|
67
|
+
this.documentTokens.clear();
|
|
68
|
+
this.documentFrequency.clear();
|
|
69
|
+
|
|
70
|
+
// Tokenize documents
|
|
71
|
+
let totalLength = 0;
|
|
72
|
+
for (const doc of documents) {
|
|
73
|
+
const text = this.extractText(doc);
|
|
74
|
+
const tokens = tokenize(text);
|
|
75
|
+
this.documentTokens.set(doc.id, tokens);
|
|
76
|
+
totalLength += tokens.length;
|
|
77
|
+
|
|
78
|
+
// Update document frequency
|
|
79
|
+
const uniqueTokens = new Set(tokens);
|
|
80
|
+
for (const token of uniqueTokens) {
|
|
81
|
+
this.documentFrequency.set(token, (this.documentFrequency.get(token) || 0) + 1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.averageDocumentLength = totalLength / documents.length;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extract searchable text from document
|
|
90
|
+
*/
|
|
91
|
+
private extractText(doc: Document): string {
|
|
92
|
+
const parts: string[] = [];
|
|
93
|
+
if (doc.title) parts.push(doc.title);
|
|
94
|
+
if (doc.content) parts.push(doc.content);
|
|
95
|
+
return parts.join(' ');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Calculate IDF (Inverse Document Frequency)
|
|
100
|
+
*/
|
|
101
|
+
private idf(term: string): number {
|
|
102
|
+
const df = this.documentFrequency.get(term) || 0;
|
|
103
|
+
if (df === 0) return 0;
|
|
104
|
+
|
|
105
|
+
const n = this.documents.length;
|
|
106
|
+
return Math.log((n - df + 0.5) / (df + 0.5) + 1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Calculate BM25 score for a document
|
|
111
|
+
*/
|
|
112
|
+
private score(docId: string, queryTokens: string[]): number {
|
|
113
|
+
const tokens = this.documentTokens.get(docId);
|
|
114
|
+
if (!tokens) return 0;
|
|
115
|
+
|
|
116
|
+
const docLength = tokens.length;
|
|
117
|
+
let score = 0;
|
|
118
|
+
|
|
119
|
+
for (const queryToken of queryTokens) {
|
|
120
|
+
const termFreq = tokens.filter((t) => t === queryToken).length;
|
|
121
|
+
if (termFreq === 0) continue;
|
|
122
|
+
|
|
123
|
+
const idf = this.idf(queryToken);
|
|
124
|
+
const numerator = termFreq * (this.k1 + 1);
|
|
125
|
+
const denominator = termFreq + this.k1 * (1 - this.b + this.b * (docLength / this.averageDocumentLength));
|
|
126
|
+
|
|
127
|
+
score += idf * (numerator / denominator);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return score;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Search for documents matching the query
|
|
135
|
+
*/
|
|
136
|
+
search(query: string, limit: number = 10): SearchResult[] {
|
|
137
|
+
if (this.documents.length === 0) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const queryTokens = tokenize(query);
|
|
142
|
+
if (queryTokens.length === 0) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Score all documents
|
|
147
|
+
const results: SearchResult[] = [];
|
|
148
|
+
for (const doc of this.documents) {
|
|
149
|
+
const score = this.score(doc.id, queryTokens);
|
|
150
|
+
if (score > 0) {
|
|
151
|
+
results.push({ document: doc, score });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Sort by score (descending) and limit
|
|
156
|
+
results.sort((a, b) => b.score - a.score);
|
|
157
|
+
return results.slice(0, limit);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get all indexed documents
|
|
162
|
+
*/
|
|
163
|
+
getDocuments(): Document[] {
|
|
164
|
+
return this.documents;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get document count
|
|
169
|
+
*/
|
|
170
|
+
getDocumentCount(): number {
|
|
171
|
+
return this.documents.length;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Task, type RunContext } from '@ddse/acm-sdk';
|
|
2
|
+
import {
|
|
3
|
+
type AnalyzeTranscriptInput,
|
|
4
|
+
type AnalyzeTranscriptOutput,
|
|
5
|
+
type GenerateFeedbackInput,
|
|
6
|
+
type GenerateFeedbackOutput,
|
|
7
|
+
type LogCoachingNoteInput,
|
|
8
|
+
type LogCoachingNoteOutput,
|
|
9
|
+
} from '../tools/coaching/index.js';
|
|
10
|
+
|
|
11
|
+
export class AnalyzeTranscriptTask extends Task<
|
|
12
|
+
AnalyzeTranscriptInput,
|
|
13
|
+
AnalyzeTranscriptOutput
|
|
14
|
+
> {
|
|
15
|
+
constructor() {
|
|
16
|
+
super('task-coaching-analyze-transcript', 'coaching.analyze_transcript');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
idemKey(_ctx: RunContext, input: AnalyzeTranscriptInput): string | undefined {
|
|
20
|
+
return input?.transcriptId ? `coaching:transcript:${input.transcriptId}` : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
policyInput(_ctx: RunContext, input: AnalyzeTranscriptInput): Record<string, unknown> {
|
|
24
|
+
return {
|
|
25
|
+
action: 'analyze_transcript',
|
|
26
|
+
transcriptId: input.transcriptId,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
verification(): string[] {
|
|
31
|
+
return ['typeof output.sentimentScore === "number"', 'Array.isArray(output.highlights)'];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async execute(
|
|
35
|
+
ctx: RunContext,
|
|
36
|
+
input: AnalyzeTranscriptInput
|
|
37
|
+
): Promise<AnalyzeTranscriptOutput> {
|
|
38
|
+
const tool = ctx.getTool('analyze_transcript');
|
|
39
|
+
if (!tool) {
|
|
40
|
+
throw new Error('analyze_transcript tool is not registered');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = (await tool.call(input)) as AnalyzeTranscriptOutput;
|
|
44
|
+
ctx.stream?.emit('task', {
|
|
45
|
+
taskId: this.id,
|
|
46
|
+
stage: 'coaching_transcript_analyzed',
|
|
47
|
+
transcriptId: input.transcriptId,
|
|
48
|
+
sentimentScore: result.sentimentScore,
|
|
49
|
+
complianceScore: result.complianceScore,
|
|
50
|
+
});
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface GenerateFeedbackTaskInput {
|
|
56
|
+
transcriptId?: string;
|
|
57
|
+
metrics?: GenerateFeedbackInput['metrics'];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class GenerateFeedbackTask extends Task<
|
|
61
|
+
GenerateFeedbackTaskInput,
|
|
62
|
+
GenerateFeedbackOutput
|
|
63
|
+
> {
|
|
64
|
+
constructor() {
|
|
65
|
+
super('task-coaching-generate-feedback', 'coaching.generate_feedback');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
policyInput(ctx: RunContext, input: GenerateFeedbackTaskInput): Record<string, unknown> {
|
|
69
|
+
const resolved = this.resolveInput(ctx, input);
|
|
70
|
+
return {
|
|
71
|
+
action: 'generate_feedback',
|
|
72
|
+
transcriptId: resolved.transcriptId,
|
|
73
|
+
metrics: resolved.metrics,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
verification(): string[] {
|
|
78
|
+
return ['typeof output.feedbackSummary === "string"', 'Array.isArray(output.actionItems)'];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async execute(
|
|
82
|
+
ctx: RunContext,
|
|
83
|
+
input: GenerateFeedbackTaskInput
|
|
84
|
+
): Promise<GenerateFeedbackOutput> {
|
|
85
|
+
const tool = ctx.getTool('generate_feedback');
|
|
86
|
+
if (!tool) {
|
|
87
|
+
throw new Error('generate_feedback tool is not registered');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const resolved = this.resolveInput(ctx, input);
|
|
91
|
+
const result = (await tool.call(resolved)) as GenerateFeedbackOutput;
|
|
92
|
+
ctx.stream?.emit('task', {
|
|
93
|
+
taskId: this.id,
|
|
94
|
+
stage: 'coaching_feedback_generated',
|
|
95
|
+
transcriptId: resolved.transcriptId,
|
|
96
|
+
escalationRequired: result.escalationRequired,
|
|
97
|
+
});
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private resolveInput(
|
|
102
|
+
ctx: RunContext,
|
|
103
|
+
input: GenerateFeedbackTaskInput
|
|
104
|
+
): GenerateFeedbackInput {
|
|
105
|
+
const analyzeOutput = ctx.outputs?.['task-coaching-analyze-transcript'] as
|
|
106
|
+
| AnalyzeTranscriptOutput
|
|
107
|
+
| undefined;
|
|
108
|
+
|
|
109
|
+
const transcriptId =
|
|
110
|
+
input?.transcriptId ?? analyzeOutput?.transcript?.id;
|
|
111
|
+
if (!transcriptId) {
|
|
112
|
+
throw new Error('transcriptId is required for feedback generation');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const metrics =
|
|
116
|
+
input?.metrics ??
|
|
117
|
+
(analyzeOutput
|
|
118
|
+
? {
|
|
119
|
+
sentimentScore: analyzeOutput.sentimentScore,
|
|
120
|
+
complianceScore: analyzeOutput.complianceScore,
|
|
121
|
+
complianceBreaches: analyzeOutput.complianceBreaches,
|
|
122
|
+
highlights: analyzeOutput.highlights,
|
|
123
|
+
summary: analyzeOutput.summary,
|
|
124
|
+
}
|
|
125
|
+
: undefined);
|
|
126
|
+
|
|
127
|
+
if (!metrics) {
|
|
128
|
+
throw new Error('metrics are required for feedback generation');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
transcriptId,
|
|
133
|
+
metrics,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface LogCoachingNoteTaskInput extends Partial<LogCoachingNoteInput> {}
|
|
139
|
+
|
|
140
|
+
export class LogCoachingNoteTask extends Task<
|
|
141
|
+
LogCoachingNoteTaskInput,
|
|
142
|
+
LogCoachingNoteOutput
|
|
143
|
+
> {
|
|
144
|
+
constructor() {
|
|
145
|
+
super('task-coaching-log-note', 'coaching.log_note');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
policyInput(ctx: RunContext, input: LogCoachingNoteTaskInput): Record<string, unknown> {
|
|
149
|
+
const resolved = this.resolveInput(ctx, input);
|
|
150
|
+
return {
|
|
151
|
+
action: 'log_coaching_note',
|
|
152
|
+
agentId: resolved.agentId,
|
|
153
|
+
escalationRequired: resolved.escalationRequired,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
verification(): string[] {
|
|
158
|
+
return ['output.stored === true', 'typeof output.logId === "string"'];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async execute(
|
|
162
|
+
ctx: RunContext,
|
|
163
|
+
input: LogCoachingNoteTaskInput
|
|
164
|
+
): Promise<LogCoachingNoteOutput> {
|
|
165
|
+
const tool = ctx.getTool('log_coaching_note');
|
|
166
|
+
if (!tool) {
|
|
167
|
+
throw new Error('log_coaching_note tool is not registered');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const resolved = this.resolveInput(ctx, input);
|
|
171
|
+
const result = (await tool.call(resolved)) as LogCoachingNoteOutput;
|
|
172
|
+
ctx.stream?.emit('task', {
|
|
173
|
+
taskId: this.id,
|
|
174
|
+
stage: 'coaching_note_logged',
|
|
175
|
+
agentId: resolved.agentId,
|
|
176
|
+
escalationNotified: result.escalationNotified,
|
|
177
|
+
});
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private resolveInput(
|
|
182
|
+
ctx: RunContext,
|
|
183
|
+
input: LogCoachingNoteTaskInput
|
|
184
|
+
): LogCoachingNoteInput {
|
|
185
|
+
const analyzeOutput = ctx.outputs?.['task-coaching-analyze-transcript'] as
|
|
186
|
+
| AnalyzeTranscriptOutput
|
|
187
|
+
| undefined;
|
|
188
|
+
const feedbackOutput = ctx.outputs?.['task-coaching-generate-feedback'] as
|
|
189
|
+
| GenerateFeedbackOutput
|
|
190
|
+
| undefined;
|
|
191
|
+
|
|
192
|
+
const agentId = input.agentId ?? analyzeOutput?.transcript?.agentId;
|
|
193
|
+
if (!agentId) {
|
|
194
|
+
throw new Error('agentId is required to log coaching note');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const feedbackSummary = input.feedbackSummary ?? feedbackOutput?.feedbackSummary;
|
|
198
|
+
if (!feedbackSummary) {
|
|
199
|
+
throw new Error('feedbackSummary is required to log coaching note');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const actionItems = input.actionItems ?? feedbackOutput?.actionItems;
|
|
203
|
+
if (!actionItems) {
|
|
204
|
+
throw new Error('actionItems are required to log coaching note');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const escalationRequired =
|
|
208
|
+
input.escalationRequired ?? feedbackOutput?.escalationRequired ?? false;
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
agentId,
|
|
212
|
+
feedbackSummary,
|
|
213
|
+
actionItems,
|
|
214
|
+
escalationRequired,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|