@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,192 @@
|
|
|
1
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
2
|
+
import {
|
|
3
|
+
FetchCustomerProfileTool,
|
|
4
|
+
EvaluateEntitlementTool,
|
|
5
|
+
EntitlementNotifySupervisorTool,
|
|
6
|
+
} from './entitlement/index.js';
|
|
7
|
+
import {
|
|
8
|
+
SearchKnowledgeTool,
|
|
9
|
+
SummarizeSnippetTool,
|
|
10
|
+
SuggestFollowupsTool,
|
|
11
|
+
} from './knowledge/index.js';
|
|
12
|
+
import {
|
|
13
|
+
FetchIncidentTool,
|
|
14
|
+
ClassifySeverityTool,
|
|
15
|
+
SelectQueueTool,
|
|
16
|
+
EscalateIncidentTool,
|
|
17
|
+
} from './incidents/index.js';
|
|
18
|
+
import {
|
|
19
|
+
FetchInvoiceTool,
|
|
20
|
+
FetchPurchaseOrderTool,
|
|
21
|
+
CompareLineItemsTool,
|
|
22
|
+
RecordFindingsTool,
|
|
23
|
+
} from './invoices/index.js';
|
|
24
|
+
import {
|
|
25
|
+
AnalyzeTranscriptTool,
|
|
26
|
+
GenerateFeedbackTool,
|
|
27
|
+
LogCoachingNoteTool,
|
|
28
|
+
} from './coaching/index.js';
|
|
29
|
+
import { searchSnippets } from '../data/knowledge.js';
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
FetchCustomerProfileTool,
|
|
33
|
+
EvaluateEntitlementTool,
|
|
34
|
+
EntitlementNotifySupervisorTool,
|
|
35
|
+
SearchKnowledgeTool,
|
|
36
|
+
SummarizeSnippetTool,
|
|
37
|
+
SuggestFollowupsTool,
|
|
38
|
+
FetchIncidentTool,
|
|
39
|
+
ClassifySeverityTool,
|
|
40
|
+
SelectQueueTool,
|
|
41
|
+
EscalateIncidentTool,
|
|
42
|
+
FetchInvoiceTool,
|
|
43
|
+
FetchPurchaseOrderTool,
|
|
44
|
+
CompareLineItemsTool,
|
|
45
|
+
RecordFindingsTool,
|
|
46
|
+
AnalyzeTranscriptTool,
|
|
47
|
+
GenerateFeedbackTool,
|
|
48
|
+
LogCoachingNoteTool,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type ExampleToolInstance = {
|
|
52
|
+
tool: Tool<any, any>;
|
|
53
|
+
category: 'entitlement' | 'knowledge' | 'incident' | 'invoice' | 'coaching';
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export function createExampleTools(): ExampleToolInstance[] {
|
|
57
|
+
return [
|
|
58
|
+
{ tool: new FetchCustomerProfileTool(), category: 'entitlement' },
|
|
59
|
+
{ tool: new EvaluateEntitlementTool(), category: 'entitlement' },
|
|
60
|
+
{ tool: new EntitlementNotifySupervisorTool(), category: 'entitlement' },
|
|
61
|
+
{ tool: new SearchKnowledgeTool(), category: 'knowledge' },
|
|
62
|
+
{ tool: new SummarizeSnippetTool(), category: 'knowledge' },
|
|
63
|
+
{ tool: new SuggestFollowupsTool(), category: 'knowledge' },
|
|
64
|
+
{ tool: new FetchIncidentTool(), category: 'incident' },
|
|
65
|
+
{ tool: new ClassifySeverityTool(), category: 'incident' },
|
|
66
|
+
{ tool: new SelectQueueTool(), category: 'incident' },
|
|
67
|
+
{ tool: new EscalateIncidentTool(), category: 'incident' },
|
|
68
|
+
{ tool: new FetchInvoiceTool(), category: 'invoice' },
|
|
69
|
+
{ tool: new FetchPurchaseOrderTool(), category: 'invoice' },
|
|
70
|
+
{ tool: new CompareLineItemsTool(), category: 'invoice' },
|
|
71
|
+
{ tool: new RecordFindingsTool(), category: 'invoice' },
|
|
72
|
+
{ tool: new AnalyzeTranscriptTool(), category: 'coaching' },
|
|
73
|
+
{ tool: new GenerateFeedbackTool(), category: 'coaching' },
|
|
74
|
+
{ tool: new LogCoachingNoteTool(), category: 'coaching' },
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type SearchToolInput = {
|
|
79
|
+
query: string;
|
|
80
|
+
limit?: number;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type SearchToolOutput = {
|
|
84
|
+
results: Array<{
|
|
85
|
+
id: string;
|
|
86
|
+
title: string;
|
|
87
|
+
score: number;
|
|
88
|
+
type: string;
|
|
89
|
+
summary: string;
|
|
90
|
+
tags: string[];
|
|
91
|
+
}>;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export class SearchTool extends Tool<SearchToolInput, SearchToolOutput> {
|
|
95
|
+
name(): string {
|
|
96
|
+
return 'search';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async call(input: SearchToolInput): Promise<SearchToolOutput> {
|
|
100
|
+
const query = input?.query?.trim();
|
|
101
|
+
if (!query) {
|
|
102
|
+
throw new Error('query is required');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const limit = Math.max(1, Math.min(input.limit ?? 5, 10));
|
|
106
|
+
const snippets = await searchSnippets(query);
|
|
107
|
+
const results = snippets.slice(0, limit).map((snippet, index) => ({
|
|
108
|
+
id: snippet.id,
|
|
109
|
+
title: snippet.title,
|
|
110
|
+
summary: snippet.summary,
|
|
111
|
+
tags: snippet.tags,
|
|
112
|
+
type: 'knowledge',
|
|
113
|
+
score: Math.max(1, snippets.length - index),
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
return { results };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class ExtractEntitiesTool extends Tool<{ text: string }, { entities: string[] }> {
|
|
121
|
+
name(): string {
|
|
122
|
+
return 'extract_entities';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async call(input: { text: string }): Promise<{ entities: string[] }> {
|
|
126
|
+
const text = input?.text ?? '';
|
|
127
|
+
const entities = Array.from(new Set(text.match(/[A-Z]{2,}-\d+/g) ?? []));
|
|
128
|
+
if (!entities.includes('ORDER-REF')) {
|
|
129
|
+
entities.push('ORDER-REF');
|
|
130
|
+
}
|
|
131
|
+
return { entities };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class AssessRiskTool extends Tool<{ context: any }, { riskTier: string; score: number }> {
|
|
136
|
+
name(): string {
|
|
137
|
+
return 'assess_risk';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async call(input: { context: any }): Promise<{ riskTier: string; score: number }> {
|
|
141
|
+
const serialized = JSON.stringify(input?.context ?? {});
|
|
142
|
+
let hash = 0;
|
|
143
|
+
for (let i = 0; i < serialized.length; i++) {
|
|
144
|
+
hash = (hash + serialized.charCodeAt(i) * (i + 1)) % 101;
|
|
145
|
+
}
|
|
146
|
+
const score = hash;
|
|
147
|
+
const riskTier = score > 70 ? 'HIGH' : score > 40 ? 'MEDIUM' : 'LOW';
|
|
148
|
+
return { riskTier, score };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export class CreateRefundTxnTool extends Tool<
|
|
153
|
+
{ orderId: string; amount: number },
|
|
154
|
+
{ transactionId: string; status: string }
|
|
155
|
+
> {
|
|
156
|
+
name(): string {
|
|
157
|
+
return 'create_refund_txn';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async call(input: { orderId: string; amount: number }): Promise<{ transactionId: string; status: string }> {
|
|
161
|
+
if (!input?.orderId || typeof input.amount !== 'number') {
|
|
162
|
+
throw new Error('orderId and amount are required');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
transactionId: `TXN-${Date.now()}`,
|
|
167
|
+
status: input.amount > 0 ? 'COMPLETED' : 'REJECTED',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export class NotifySupervisorToolLegacy extends Tool<
|
|
173
|
+
{ message: string; channel: string },
|
|
174
|
+
{ sent: boolean; messageId: string }
|
|
175
|
+
> {
|
|
176
|
+
name(): string {
|
|
177
|
+
return 'notify_supervisor_legacy';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async call(input: { message: string; channel: string }): Promise<{ sent: boolean; messageId: string }> {
|
|
181
|
+
if (!input?.message) {
|
|
182
|
+
throw new Error('message is required');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
sent: true,
|
|
187
|
+
messageId: `MSG-${Date.now()}`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export { NotifySupervisorToolLegacy as NotifySupervisorTool };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
2
|
+
import {
|
|
3
|
+
getInvoice,
|
|
4
|
+
getPurchaseOrder,
|
|
5
|
+
compareLineItems,
|
|
6
|
+
type InvoiceRecord,
|
|
7
|
+
type PurchaseOrderRecord,
|
|
8
|
+
} from '../../data/invoices.js';
|
|
9
|
+
|
|
10
|
+
export type FetchInvoiceInput = {
|
|
11
|
+
invoiceId: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type FetchInvoiceOutput = {
|
|
15
|
+
invoice: InvoiceRecord;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export class FetchInvoiceTool extends Tool<FetchInvoiceInput, FetchInvoiceOutput> {
|
|
19
|
+
name(): string {
|
|
20
|
+
return 'fetch_invoice';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async call(input: FetchInvoiceInput): Promise<FetchInvoiceOutput> {
|
|
24
|
+
if (!input?.invoiceId) {
|
|
25
|
+
throw new Error('invoiceId is required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const invoice = await getInvoice(input.invoiceId);
|
|
29
|
+
if (!invoice) {
|
|
30
|
+
throw new Error(`Invoice ${input.invoiceId} not found`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { invoice };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type FetchPurchaseOrderInput = {
|
|
38
|
+
purchaseOrderId: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type FetchPurchaseOrderOutput = {
|
|
42
|
+
purchaseOrder: PurchaseOrderRecord;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export class FetchPurchaseOrderTool extends Tool<
|
|
46
|
+
FetchPurchaseOrderInput,
|
|
47
|
+
FetchPurchaseOrderOutput
|
|
48
|
+
> {
|
|
49
|
+
name(): string {
|
|
50
|
+
return 'fetch_purchase_order';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async call(input: FetchPurchaseOrderInput): Promise<FetchPurchaseOrderOutput> {
|
|
54
|
+
if (!input?.purchaseOrderId) {
|
|
55
|
+
throw new Error('purchaseOrderId is required');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const purchaseOrder = await getPurchaseOrder(input.purchaseOrderId);
|
|
59
|
+
if (!purchaseOrder) {
|
|
60
|
+
throw new Error(`Purchase order ${input.purchaseOrderId} not found`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { purchaseOrder };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type CompareLineItemsInput = {
|
|
68
|
+
invoice: InvoiceRecord;
|
|
69
|
+
purchaseOrder: PurchaseOrderRecord;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type Discrepancy = ReturnType<typeof compareLineItems>[number] & {
|
|
73
|
+
varianceAmount: number;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type CompareLineItemsOutput = {
|
|
77
|
+
discrepancies: Discrepancy[];
|
|
78
|
+
variance: number;
|
|
79
|
+
matchedLines: number;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export class CompareLineItemsTool extends Tool<
|
|
83
|
+
CompareLineItemsInput,
|
|
84
|
+
CompareLineItemsOutput
|
|
85
|
+
> {
|
|
86
|
+
name(): string {
|
|
87
|
+
return 'compare_line_items';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async call(input: CompareLineItemsInput): Promise<CompareLineItemsOutput> {
|
|
91
|
+
if (!input?.invoice || !input?.purchaseOrder) {
|
|
92
|
+
throw new Error('invoice and purchaseOrder are required');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const discrepancies = compareLineItems(input.invoice, input.purchaseOrder).map(item => ({
|
|
96
|
+
...item,
|
|
97
|
+
varianceAmount:
|
|
98
|
+
(item.actualQuantity - item.expectedQuantity) * item.expectedPrice ||
|
|
99
|
+
(item.actualPrice - item.expectedPrice) * item.actualQuantity,
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
const matchedLines = input.invoice.lines.length - discrepancies.length;
|
|
103
|
+
const variance = discrepancies.reduce((sum, item) => sum + item.varianceAmount, 0);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
discrepancies,
|
|
107
|
+
variance,
|
|
108
|
+
matchedLines,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type RecordFindingsInput = {
|
|
114
|
+
invoice: InvoiceRecord;
|
|
115
|
+
purchaseOrder: PurchaseOrderRecord;
|
|
116
|
+
discrepancies: Discrepancy[];
|
|
117
|
+
variance: number;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export type RecordFindingsOutput = {
|
|
121
|
+
reportId: string;
|
|
122
|
+
status: 'clean' | 'needs_remediation';
|
|
123
|
+
summary: string;
|
|
124
|
+
nextSteps: string[];
|
|
125
|
+
generatedAt: string;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export class RecordFindingsTool extends Tool<
|
|
129
|
+
RecordFindingsInput,
|
|
130
|
+
RecordFindingsOutput
|
|
131
|
+
> {
|
|
132
|
+
name(): string {
|
|
133
|
+
return 'record_findings';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async call(input: RecordFindingsInput): Promise<RecordFindingsOutput> {
|
|
137
|
+
if (!input?.invoice || !input?.purchaseOrder) {
|
|
138
|
+
throw new Error('invoice and purchaseOrder are required');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const status = input.discrepancies.length === 0 && Math.abs(input.variance) < 1
|
|
142
|
+
? 'clean'
|
|
143
|
+
: 'needs_remediation';
|
|
144
|
+
|
|
145
|
+
const summary = status === 'clean'
|
|
146
|
+
? `Invoice ${input.invoice.id} matches purchase order ${input.purchaseOrder.id}`
|
|
147
|
+
: `Invoice ${input.invoice.id} has ${input.discrepancies.length} discrepancy(s)`;
|
|
148
|
+
|
|
149
|
+
const nextSteps = status === 'clean'
|
|
150
|
+
? ['Archive reconciliation report in AP system']
|
|
151
|
+
: [
|
|
152
|
+
'Open remediation ticket with procurement',
|
|
153
|
+
'Notify accounts payable supervisor',
|
|
154
|
+
'Schedule supplier follow-up call',
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
reportId: `recon-${Date.now()}`,
|
|
159
|
+
status,
|
|
160
|
+
summary,
|
|
161
|
+
nextSteps,
|
|
162
|
+
generatedAt: new Date().toISOString(),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
2
|
+
import {
|
|
3
|
+
searchSnippets,
|
|
4
|
+
loadSnippetContent,
|
|
5
|
+
getSnippetMeta,
|
|
6
|
+
type KnowledgeSnippetMeta,
|
|
7
|
+
} from '../../data/knowledge.js';
|
|
8
|
+
|
|
9
|
+
export type KnowledgeSearchInput = {
|
|
10
|
+
query: string;
|
|
11
|
+
limit?: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type KnowledgeSearchHit = KnowledgeSnippetMeta & {
|
|
15
|
+
score: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type KnowledgeSearchOutput = {
|
|
19
|
+
hits: KnowledgeSearchHit[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class SearchKnowledgeTool extends Tool<KnowledgeSearchInput, KnowledgeSearchOutput> {
|
|
23
|
+
name(): string {
|
|
24
|
+
return 'search_knowledge';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async call(input: KnowledgeSearchInput): Promise<KnowledgeSearchOutput> {
|
|
28
|
+
const query = input?.query?.trim();
|
|
29
|
+
if (!query) {
|
|
30
|
+
throw new Error('query is required');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const limit = Math.max(1, Math.min(input.limit ?? 5, 10));
|
|
34
|
+
const snippets = await searchSnippets(query);
|
|
35
|
+
|
|
36
|
+
const hits: KnowledgeSearchHit[] = snippets.slice(0, limit).map((snippet, index) => ({
|
|
37
|
+
...snippet,
|
|
38
|
+
score: Math.max(1, snippets.length - index),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
return { hits };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type SummarizeSnippetInput = {
|
|
46
|
+
docId: string;
|
|
47
|
+
maxSentences?: number;
|
|
48
|
+
focus?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type SummarizeSnippetOutput = {
|
|
52
|
+
docId: string;
|
|
53
|
+
title: string;
|
|
54
|
+
summary: string;
|
|
55
|
+
highlights: string[];
|
|
56
|
+
followups: string[];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function summarizeContent(content: string, maxSentences: number): string {
|
|
60
|
+
const sentences = content
|
|
61
|
+
.replace(/\s+/g, ' ')
|
|
62
|
+
.split(/(?<=[.!?])\s+(?=[A-Z0-9])/)
|
|
63
|
+
.filter(Boolean);
|
|
64
|
+
|
|
65
|
+
if (sentences.length === 0) {
|
|
66
|
+
return content.trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return sentences.slice(0, maxSentences).join(' ');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function extractHighlights(content: string, maxHighlights = 3): string[] {
|
|
73
|
+
const lines = content.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
74
|
+
const bulletLines = lines.filter(line => /^[-*]/.test(line));
|
|
75
|
+
const highlights: string[] = [];
|
|
76
|
+
|
|
77
|
+
for (const line of bulletLines) {
|
|
78
|
+
highlights.push(line.replace(/^[-*]\s*/, ''));
|
|
79
|
+
if (highlights.length >= maxHighlights) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (highlights.length === 0) {
|
|
85
|
+
highlights.push(...lines.slice(0, maxHighlights));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return highlights.slice(0, maxHighlights);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function deriveFollowups(meta: KnowledgeSnippetMeta, focus?: string): string[] {
|
|
92
|
+
const followups = new Set<string>();
|
|
93
|
+
|
|
94
|
+
for (const tag of meta.tags) {
|
|
95
|
+
if (tag.toLowerCase().includes('escalation')) {
|
|
96
|
+
followups.add('Confirm escalation steps with runbook owner');
|
|
97
|
+
}
|
|
98
|
+
if (tag.toLowerCase().includes('training')) {
|
|
99
|
+
followups.add('Share summary with enablement channel for awareness');
|
|
100
|
+
}
|
|
101
|
+
if (tag.toLowerCase().includes('automation')) {
|
|
102
|
+
followups.add('Evaluate automation opportunity with platform team');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (focus) {
|
|
107
|
+
followups.add(`Identify additional knowledge gaps related to "${focus}"`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (followups.size === 0) {
|
|
111
|
+
followups.add('Log follow-up tasks in support queue');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return Array.from(followups);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class SummarizeSnippetTool extends Tool<
|
|
118
|
+
SummarizeSnippetInput,
|
|
119
|
+
SummarizeSnippetOutput
|
|
120
|
+
> {
|
|
121
|
+
name(): string {
|
|
122
|
+
return 'summarize_snippet';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async call(input: SummarizeSnippetInput): Promise<SummarizeSnippetOutput> {
|
|
126
|
+
if (!input?.docId) {
|
|
127
|
+
throw new Error('docId is required');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const [meta, content] = await Promise.all([
|
|
131
|
+
getSnippetMeta(input.docId),
|
|
132
|
+
loadSnippetContent(input.docId),
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
if (!meta || !content) {
|
|
136
|
+
throw new Error(`Snippet ${input.docId} not found`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const maxSentences = Math.max(1, Math.min(input.maxSentences ?? 3, 6));
|
|
140
|
+
const summary = summarizeContent(content, maxSentences);
|
|
141
|
+
const highlights = extractHighlights(content);
|
|
142
|
+
const followups = deriveFollowups(meta, input.focus);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
docId: input.docId,
|
|
146
|
+
title: meta.title,
|
|
147
|
+
summary,
|
|
148
|
+
highlights,
|
|
149
|
+
followups,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type SuggestFollowupsInput = {
|
|
155
|
+
docId: string;
|
|
156
|
+
context?: {
|
|
157
|
+
channel?: string;
|
|
158
|
+
urgency?: 'low' | 'normal' | 'high';
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export type SuggestFollowupsOutput = {
|
|
163
|
+
docId: string;
|
|
164
|
+
suggestions: Array<{
|
|
165
|
+
action: string;
|
|
166
|
+
owner: string;
|
|
167
|
+
dueInHours: number;
|
|
168
|
+
}>;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export class SuggestFollowupsTool extends Tool<
|
|
172
|
+
SuggestFollowupsInput,
|
|
173
|
+
SuggestFollowupsOutput
|
|
174
|
+
> {
|
|
175
|
+
name(): string {
|
|
176
|
+
return 'suggest_followups';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async call(input: SuggestFollowupsInput): Promise<SuggestFollowupsOutput> {
|
|
180
|
+
if (!input?.docId) {
|
|
181
|
+
throw new Error('docId is required');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const meta = await getSnippetMeta(input.docId);
|
|
185
|
+
if (!meta) {
|
|
186
|
+
throw new Error(`Snippet ${input.docId} not found`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const baseDue = input.context?.urgency === 'high' ? 4 : input.context?.urgency === 'low' ? 24 : 8;
|
|
190
|
+
|
|
191
|
+
const suggestions = deriveFollowups(meta)
|
|
192
|
+
.map((action, index) => ({
|
|
193
|
+
action,
|
|
194
|
+
owner: index === 0 ? 'support.enablement' : 'support.lead',
|
|
195
|
+
dueInHours: baseDue + index * 4,
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
docId: input.docId,
|
|
200
|
+
suggestions,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Tests for BM25 search functionality
|
|
2
|
+
import { BM25Search } from '../src/search/bm25.js';
|
|
3
|
+
|
|
4
|
+
async function testIndexAndSearch() {
|
|
5
|
+
console.log('Testing index and search...');
|
|
6
|
+
|
|
7
|
+
const documents = [
|
|
8
|
+
{ id: 'doc1', content: 'The quick brown fox jumps over the lazy dog' },
|
|
9
|
+
{ id: 'doc2', content: 'A fast brown fox leaps over a sleepy dog' },
|
|
10
|
+
{ id: 'doc3', content: 'The cat sits on the mat' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const search = new BM25Search();
|
|
14
|
+
search.index(documents);
|
|
15
|
+
|
|
16
|
+
const results = search.search('brown fox');
|
|
17
|
+
|
|
18
|
+
if (results.length === 0) {
|
|
19
|
+
throw new Error('Expected results, got none');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!['doc1', 'doc2'].includes(results[0].document.id)) {
|
|
23
|
+
throw new Error(`Expected doc1 or doc2, got ${results[0].document.id}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log('✅ Index and search test passed');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function testEmptyQuery() {
|
|
30
|
+
console.log('Testing empty query...');
|
|
31
|
+
|
|
32
|
+
const documents = [{ id: 'doc1', content: 'Test document' }];
|
|
33
|
+
const search = new BM25Search();
|
|
34
|
+
search.index(documents);
|
|
35
|
+
|
|
36
|
+
const results = search.search('');
|
|
37
|
+
|
|
38
|
+
if (results.length !== 0) {
|
|
39
|
+
throw new Error('Expected empty results for empty query');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log('✅ Empty query test passed');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function testRanking() {
|
|
46
|
+
console.log('Testing ranking...');
|
|
47
|
+
|
|
48
|
+
const documents = [
|
|
49
|
+
{ id: 'doc1', content: 'machine learning algorithms' },
|
|
50
|
+
{ id: 'doc2', content: 'learning machine code' },
|
|
51
|
+
{ id: 'doc3', content: 'deep learning neural networks' },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const search = new BM25Search();
|
|
55
|
+
search.index(documents);
|
|
56
|
+
|
|
57
|
+
const results = search.search('machine learning');
|
|
58
|
+
|
|
59
|
+
if (results.length < 2) {
|
|
60
|
+
throw new Error('Expected at least 2 results');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (results[0].score < results[1].score) {
|
|
64
|
+
throw new Error('Results not properly ranked');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('✅ Ranking test passed');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function testLimitParameter() {
|
|
71
|
+
console.log('Testing limit parameter...');
|
|
72
|
+
|
|
73
|
+
const documents = Array.from({ length: 10 }, (_, i) => ({
|
|
74
|
+
id: `doc${i}`,
|
|
75
|
+
content: `Document ${i} with test content`,
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
const search = new BM25Search();
|
|
79
|
+
search.index(documents);
|
|
80
|
+
|
|
81
|
+
const results = search.search('test', 3);
|
|
82
|
+
|
|
83
|
+
if (results.length > 3) {
|
|
84
|
+
throw new Error(`Expected max 3 results, got ${results.length}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log('✅ Limit parameter test passed');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function runTests() {
|
|
91
|
+
console.log('Running BM25 Search Tests\n');
|
|
92
|
+
console.log('='.repeat(50));
|
|
93
|
+
|
|
94
|
+
const tests = [
|
|
95
|
+
testIndexAndSearch,
|
|
96
|
+
testEmptyQuery,
|
|
97
|
+
testRanking,
|
|
98
|
+
testLimitParameter,
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
let passed = 0;
|
|
102
|
+
let failed = 0;
|
|
103
|
+
|
|
104
|
+
for (const test of tests) {
|
|
105
|
+
try {
|
|
106
|
+
await test();
|
|
107
|
+
passed++;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(`❌ Test failed: ${error}`);
|
|
110
|
+
failed++;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('\n' + '='.repeat(50));
|
|
115
|
+
console.log(`Results: ${passed} passed, ${failed} failed\n`);
|
|
116
|
+
|
|
117
|
+
return failed === 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Run if executed directly
|
|
121
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
122
|
+
runTests().then(success => {
|
|
123
|
+
process.exit(success ? 0 : 1);
|
|
124
|
+
}).catch(error => {
|
|
125
|
+
console.error('Test runner error:', error);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|