@contractspec/lib.support-bot 2.4.0 → 2.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/dist/bot/auto-responder.d.ts +2 -0
- package/dist/bot/auto-responder.js +678 -21
- package/dist/bot/feedback-loop.js +652 -2
- package/dist/bot/index.js +681 -23
- package/dist/browser/bot/auto-responder.js +678 -21
- package/dist/browser/bot/feedback-loop.js +652 -2
- package/dist/browser/bot/index.js +681 -23
- package/dist/browser/i18n/catalogs/en.js +191 -0
- package/dist/browser/i18n/catalogs/es.js +191 -0
- package/dist/browser/i18n/catalogs/fr.js +191 -0
- package/dist/browser/i18n/catalogs/index.js +571 -0
- package/dist/browser/i18n/index.js +670 -0
- package/dist/browser/i18n/keys.js +71 -0
- package/dist/browser/i18n/locale.js +13 -0
- package/dist/browser/i18n/messages.js +585 -0
- package/dist/browser/index.js +884 -58
- package/dist/browser/rag/index.js +662 -5
- package/dist/browser/rag/ticket-resolver.js +662 -5
- package/dist/browser/tickets/classifier.js +839 -30
- package/dist/browser/tickets/index.js +839 -30
- package/dist/i18n/catalogs/en.d.ts +8 -0
- package/dist/i18n/catalogs/en.js +192 -0
- package/dist/i18n/catalogs/es.d.ts +6 -0
- package/dist/i18n/catalogs/es.js +192 -0
- package/dist/i18n/catalogs/fr.d.ts +6 -0
- package/dist/i18n/catalogs/fr.js +192 -0
- package/dist/i18n/catalogs/index.d.ts +8 -0
- package/dist/i18n/catalogs/index.js +572 -0
- package/dist/i18n/i18n.test.d.ts +1 -0
- package/dist/i18n/index.d.ts +30 -0
- package/dist/i18n/index.js +671 -0
- package/dist/i18n/keys.d.ts +174 -0
- package/dist/i18n/keys.js +72 -0
- package/dist/i18n/locale.d.ts +8 -0
- package/dist/i18n/locale.js +14 -0
- package/dist/i18n/messages.d.ts +15 -0
- package/dist/i18n/messages.js +586 -0
- package/dist/index.js +884 -58
- package/dist/node/bot/auto-responder.js +678 -21
- package/dist/node/bot/feedback-loop.js +652 -2
- package/dist/node/bot/index.js +681 -23
- package/dist/node/i18n/catalogs/en.js +191 -0
- package/dist/node/i18n/catalogs/es.js +191 -0
- package/dist/node/i18n/catalogs/fr.js +191 -0
- package/dist/node/i18n/catalogs/index.js +571 -0
- package/dist/node/i18n/index.js +670 -0
- package/dist/node/i18n/keys.js +71 -0
- package/dist/node/i18n/locale.js +13 -0
- package/dist/node/i18n/messages.js +585 -0
- package/dist/node/index.js +884 -58
- package/dist/node/rag/index.js +662 -5
- package/dist/node/rag/ticket-resolver.js +662 -5
- package/dist/node/tickets/classifier.js +839 -30
- package/dist/node/tickets/index.js +839 -30
- package/dist/rag/index.js +662 -5
- package/dist/rag/ticket-resolver.d.ts +2 -0
- package/dist/rag/ticket-resolver.js +662 -5
- package/dist/tickets/classifier.d.ts +6 -0
- package/dist/tickets/classifier.js +839 -30
- package/dist/tickets/index.js +839 -30
- package/package.json +148 -8
package/dist/index.js
CHANGED
|
@@ -1,15 +1,667 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// src/i18n/catalogs/en.ts
|
|
3
|
+
import { defineTranslation } from "@contractspec/lib.contracts-spec/translations";
|
|
4
|
+
var enMessages = defineTranslation({
|
|
5
|
+
meta: {
|
|
6
|
+
key: "support-bot.messages",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
domain: "support-bot",
|
|
9
|
+
description: "All user-facing, LLM-facing, and developer-facing strings for the support-bot package",
|
|
10
|
+
owners: ["platform"],
|
|
11
|
+
stability: "experimental"
|
|
12
|
+
},
|
|
13
|
+
locale: "en",
|
|
14
|
+
fallback: "en",
|
|
15
|
+
messages: {
|
|
16
|
+
"prompt.classifier.system": {
|
|
17
|
+
value: "Classify the support ticket.",
|
|
18
|
+
description: "Classifier LLM system prompt"
|
|
19
|
+
},
|
|
20
|
+
"prompt.autoResponder.system": {
|
|
21
|
+
value: "Write empathetic, accurate support replies that cite sources when relevant.",
|
|
22
|
+
description: "Auto-responder LLM system prompt"
|
|
23
|
+
},
|
|
24
|
+
"prompt.autoResponder.user": {
|
|
25
|
+
value: `Ticket #{ticketId} ({category}, {priority}, {sentiment})
|
|
26
|
+
Subject: {subject}
|
|
27
|
+
|
|
28
|
+
{body}
|
|
29
|
+
|
|
30
|
+
Knowledge:
|
|
31
|
+
{knowledge}
|
|
32
|
+
|
|
33
|
+
Respond to the customer.`,
|
|
34
|
+
description: "Auto-responder user prompt template",
|
|
35
|
+
placeholders: [
|
|
36
|
+
{ name: "ticketId", type: "string" },
|
|
37
|
+
{ name: "category", type: "string" },
|
|
38
|
+
{ name: "priority", type: "string" },
|
|
39
|
+
{ name: "sentiment", type: "string" },
|
|
40
|
+
{ name: "subject", type: "string" },
|
|
41
|
+
{ name: "body", type: "string" },
|
|
42
|
+
{ name: "knowledge", type: "string" }
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"responder.closing.friendly": {
|
|
46
|
+
value: "We remain available if you need anything else.",
|
|
47
|
+
description: "Friendly closing line for support responses"
|
|
48
|
+
},
|
|
49
|
+
"responder.closing.formal": {
|
|
50
|
+
value: "Please let us know if you require additional assistance.",
|
|
51
|
+
description: "Formal closing line for support responses"
|
|
52
|
+
},
|
|
53
|
+
"responder.greeting.named": {
|
|
54
|
+
value: "Hi {name},",
|
|
55
|
+
description: "Greeting with customer name",
|
|
56
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
57
|
+
},
|
|
58
|
+
"responder.greeting.anonymous": {
|
|
59
|
+
value: "Hi there,",
|
|
60
|
+
description: "Greeting without customer name"
|
|
61
|
+
},
|
|
62
|
+
"responder.intro.thanks": {
|
|
63
|
+
value: 'Thanks for contacting us about "{subject}".',
|
|
64
|
+
description: "Thank-you intro referencing the ticket subject",
|
|
65
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
66
|
+
},
|
|
67
|
+
"responder.signature": {
|
|
68
|
+
value: "\u2014 ContractSpec Support",
|
|
69
|
+
description: "Email / response signature"
|
|
70
|
+
},
|
|
71
|
+
"responder.references.header": {
|
|
72
|
+
value: "References:",
|
|
73
|
+
description: "Header for the references section"
|
|
74
|
+
},
|
|
75
|
+
"responder.references.sourceLabel": {
|
|
76
|
+
value: "Source {index}",
|
|
77
|
+
description: "Label for a numbered source reference",
|
|
78
|
+
placeholders: [{ name: "index", type: "number" }]
|
|
79
|
+
},
|
|
80
|
+
"responder.category.billing": {
|
|
81
|
+
value: "I understand billing issues can be stressful, so let me clarify the situation.",
|
|
82
|
+
description: "Category intro for billing tickets"
|
|
83
|
+
},
|
|
84
|
+
"responder.category.technical": {
|
|
85
|
+
value: "I see you encountered a technical issue. Here is what happened and how to fix it.",
|
|
86
|
+
description: "Category intro for technical tickets"
|
|
87
|
+
},
|
|
88
|
+
"responder.category.product": {
|
|
89
|
+
value: "Thanks for sharing feedback about the product. Here are the next steps.",
|
|
90
|
+
description: "Category intro for product tickets"
|
|
91
|
+
},
|
|
92
|
+
"responder.category.account": {
|
|
93
|
+
value: "Account access is critical, so let me walk you through the resolution.",
|
|
94
|
+
description: "Category intro for account tickets"
|
|
95
|
+
},
|
|
96
|
+
"responder.category.compliance": {
|
|
97
|
+
value: "Compliance questions require precision. See the policy-aligned answer below.",
|
|
98
|
+
description: "Category intro for compliance tickets"
|
|
99
|
+
},
|
|
100
|
+
"responder.category.other": {
|
|
101
|
+
value: "Here is what we found after reviewing your request.",
|
|
102
|
+
description: "Category intro for uncategorized tickets"
|
|
103
|
+
},
|
|
104
|
+
"responder.subject.replyPrefix": {
|
|
105
|
+
value: "Re: {subject}",
|
|
106
|
+
description: "Reply subject line prefix",
|
|
107
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
108
|
+
},
|
|
109
|
+
"resolver.question.subjectLabel": {
|
|
110
|
+
value: "Subject: {subject}",
|
|
111
|
+
description: "Subject label in resolver question context",
|
|
112
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
113
|
+
},
|
|
114
|
+
"resolver.question.channelLabel": {
|
|
115
|
+
value: "Channel: {channel}",
|
|
116
|
+
description: "Channel label in resolver question context",
|
|
117
|
+
placeholders: [{ name: "channel", type: "string" }]
|
|
118
|
+
},
|
|
119
|
+
"resolver.question.customerLabel": {
|
|
120
|
+
value: "Customer: {name}",
|
|
121
|
+
description: "Customer label in resolver question context",
|
|
122
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
123
|
+
},
|
|
124
|
+
"resolver.action.escalate": {
|
|
125
|
+
value: "Escalate for human review",
|
|
126
|
+
description: "Action label for escalation"
|
|
127
|
+
},
|
|
128
|
+
"resolver.action.respond": {
|
|
129
|
+
value: "Send automated response",
|
|
130
|
+
description: "Action label for automated response"
|
|
131
|
+
},
|
|
132
|
+
"resolver.escalation.insufficientConfidence": {
|
|
133
|
+
value: "Insufficient confidence or missing knowledge references",
|
|
134
|
+
description: "Escalation reason when confidence is too low"
|
|
135
|
+
},
|
|
136
|
+
"tool.classify.title": {
|
|
137
|
+
value: "support_classify_ticket",
|
|
138
|
+
description: "MCP tool title for ticket classification"
|
|
139
|
+
},
|
|
140
|
+
"tool.classify.description": {
|
|
141
|
+
value: "Classify a ticket for priority, sentiment, and category",
|
|
142
|
+
description: "MCP tool description for ticket classification"
|
|
143
|
+
},
|
|
144
|
+
"tool.resolve.title": {
|
|
145
|
+
value: "support_resolve_ticket",
|
|
146
|
+
description: "MCP tool title for ticket resolution"
|
|
147
|
+
},
|
|
148
|
+
"tool.resolve.description": {
|
|
149
|
+
value: "Generate a knowledge-grounded resolution for a ticket",
|
|
150
|
+
description: "MCP tool description for ticket resolution"
|
|
151
|
+
},
|
|
152
|
+
"tool.draft.title": {
|
|
153
|
+
value: "support_draft_response",
|
|
154
|
+
description: "MCP tool title for response drafting"
|
|
155
|
+
},
|
|
156
|
+
"tool.draft.description": {
|
|
157
|
+
value: "Draft a user-facing reply based on resolution + classification",
|
|
158
|
+
description: "MCP tool description for response drafting"
|
|
159
|
+
},
|
|
160
|
+
"error.inputMustIncludeTicket": {
|
|
161
|
+
value: "Input must include ticket",
|
|
162
|
+
description: "Error when input payload is missing the ticket field"
|
|
163
|
+
},
|
|
164
|
+
"error.ticketMissingId": {
|
|
165
|
+
value: "Ticket is missing id",
|
|
166
|
+
description: "Error when ticket object lacks an id"
|
|
167
|
+
},
|
|
168
|
+
"error.resolutionClassificationRequired": {
|
|
169
|
+
value: "resolution and classification are required",
|
|
170
|
+
description: "Error when draft endpoint is called without resolution and classification"
|
|
171
|
+
},
|
|
172
|
+
"feedback.noRecords": {
|
|
173
|
+
value: "No feedback recorded yet.",
|
|
174
|
+
description: "Placeholder when no feedback entries exist"
|
|
175
|
+
},
|
|
176
|
+
"feedback.status.escalated": {
|
|
177
|
+
value: "Escalated",
|
|
178
|
+
description: "Status label for escalated tickets"
|
|
179
|
+
},
|
|
180
|
+
"feedback.status.autoResolved": {
|
|
181
|
+
value: "Auto-resolved",
|
|
182
|
+
description: "Status label for auto-resolved tickets"
|
|
183
|
+
},
|
|
184
|
+
"spec.instructionsAppendix": {
|
|
185
|
+
value: "Always cite support knowledge sources and flag compliance/billing issues for human review when unsure.",
|
|
186
|
+
description: "Instructions appendix appended to agent spec"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// src/i18n/catalogs/fr.ts
|
|
192
|
+
import { defineTranslation as defineTranslation2 } from "@contractspec/lib.contracts-spec/translations";
|
|
193
|
+
var frMessages = defineTranslation2({
|
|
194
|
+
meta: {
|
|
195
|
+
key: "support-bot.messages",
|
|
196
|
+
version: "1.0.0",
|
|
197
|
+
domain: "support-bot",
|
|
198
|
+
description: "French translations for the support-bot package",
|
|
199
|
+
owners: ["platform"],
|
|
200
|
+
stability: "experimental"
|
|
201
|
+
},
|
|
202
|
+
locale: "fr",
|
|
203
|
+
fallback: "en",
|
|
204
|
+
messages: {
|
|
205
|
+
"prompt.classifier.system": {
|
|
206
|
+
value: "Classifiez le ticket de support.",
|
|
207
|
+
description: "Classifier LLM system prompt"
|
|
208
|
+
},
|
|
209
|
+
"prompt.autoResponder.system": {
|
|
210
|
+
value: "R\xE9digez des r\xE9ponses de support empathiques et pr\xE9cises en citant les sources lorsque c\u2019est pertinent.",
|
|
211
|
+
description: "Auto-responder LLM system prompt"
|
|
212
|
+
},
|
|
213
|
+
"prompt.autoResponder.user": {
|
|
214
|
+
value: `Ticket #{ticketId} ({category}, {priority}, {sentiment})
|
|
215
|
+
Sujet\xA0: {subject}
|
|
216
|
+
|
|
217
|
+
{body}
|
|
218
|
+
|
|
219
|
+
Connaissances\xA0:
|
|
220
|
+
{knowledge}
|
|
221
|
+
|
|
222
|
+
R\xE9pondez au client.`,
|
|
223
|
+
description: "Auto-responder user prompt template",
|
|
224
|
+
placeholders: [
|
|
225
|
+
{ name: "ticketId", type: "string" },
|
|
226
|
+
{ name: "category", type: "string" },
|
|
227
|
+
{ name: "priority", type: "string" },
|
|
228
|
+
{ name: "sentiment", type: "string" },
|
|
229
|
+
{ name: "subject", type: "string" },
|
|
230
|
+
{ name: "body", type: "string" },
|
|
231
|
+
{ name: "knowledge", type: "string" }
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
"responder.closing.friendly": {
|
|
235
|
+
value: "Nous restons \xE0 votre disposition si vous avez besoin de quoi que ce soit.",
|
|
236
|
+
description: "Friendly closing line for support responses"
|
|
237
|
+
},
|
|
238
|
+
"responder.closing.formal": {
|
|
239
|
+
value: "Veuillez nous contacter si vous avez besoin d\u2019une assistance suppl\xE9mentaire.",
|
|
240
|
+
description: "Formal closing line for support responses"
|
|
241
|
+
},
|
|
242
|
+
"responder.greeting.named": {
|
|
243
|
+
value: "Bonjour {name},",
|
|
244
|
+
description: "Greeting with customer name",
|
|
245
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
246
|
+
},
|
|
247
|
+
"responder.greeting.anonymous": {
|
|
248
|
+
value: "Bonjour,",
|
|
249
|
+
description: "Greeting without customer name"
|
|
250
|
+
},
|
|
251
|
+
"responder.intro.thanks": {
|
|
252
|
+
value: "Merci de nous avoir contact\xE9s au sujet de \xAB\xA0{subject}\xA0\xBB.",
|
|
253
|
+
description: "Thank-you intro referencing the ticket subject",
|
|
254
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
255
|
+
},
|
|
256
|
+
"responder.signature": {
|
|
257
|
+
value: "\u2014 Support ContractSpec",
|
|
258
|
+
description: "Email / response signature"
|
|
259
|
+
},
|
|
260
|
+
"responder.references.header": {
|
|
261
|
+
value: "R\xE9f\xE9rences\xA0:",
|
|
262
|
+
description: "Header for the references section"
|
|
263
|
+
},
|
|
264
|
+
"responder.references.sourceLabel": {
|
|
265
|
+
value: "Source {index}",
|
|
266
|
+
description: "Label for a numbered source reference",
|
|
267
|
+
placeholders: [{ name: "index", type: "number" }]
|
|
268
|
+
},
|
|
269
|
+
"responder.category.billing": {
|
|
270
|
+
value: "Je comprends que les probl\xE8mes de facturation peuvent \xEAtre stressants, laissez-moi clarifier la situation.",
|
|
271
|
+
description: "Category intro for billing tickets"
|
|
272
|
+
},
|
|
273
|
+
"responder.category.technical": {
|
|
274
|
+
value: "Je vois que vous avez rencontr\xE9 un probl\xE8me technique. Voici ce qui s\u2019est pass\xE9 et comment le r\xE9soudre.",
|
|
275
|
+
description: "Category intro for technical tickets"
|
|
276
|
+
},
|
|
277
|
+
"responder.category.product": {
|
|
278
|
+
value: "Merci d\u2019avoir partag\xE9 vos retours sur le produit. Voici les prochaines \xE9tapes.",
|
|
279
|
+
description: "Category intro for product tickets"
|
|
280
|
+
},
|
|
281
|
+
"responder.category.account": {
|
|
282
|
+
value: "L\u2019acc\xE8s au compte est essentiel, laissez-moi vous guider vers la r\xE9solution.",
|
|
283
|
+
description: "Category intro for account tickets"
|
|
284
|
+
},
|
|
285
|
+
"responder.category.compliance": {
|
|
286
|
+
value: "Les questions de conformit\xE9 exigent de la pr\xE9cision. Consultez la r\xE9ponse conforme aux politiques ci-dessous.",
|
|
287
|
+
description: "Category intro for compliance tickets"
|
|
288
|
+
},
|
|
289
|
+
"responder.category.other": {
|
|
290
|
+
value: "Voici ce que nous avons trouv\xE9 apr\xE8s examen de votre demande.",
|
|
291
|
+
description: "Category intro for uncategorized tickets"
|
|
292
|
+
},
|
|
293
|
+
"responder.subject.replyPrefix": {
|
|
294
|
+
value: "Re\xA0: {subject}",
|
|
295
|
+
description: "Reply subject line prefix",
|
|
296
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
297
|
+
},
|
|
298
|
+
"resolver.question.subjectLabel": {
|
|
299
|
+
value: "Sujet\xA0: {subject}",
|
|
300
|
+
description: "Subject label in resolver question context",
|
|
301
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
302
|
+
},
|
|
303
|
+
"resolver.question.channelLabel": {
|
|
304
|
+
value: "Canal\xA0: {channel}",
|
|
305
|
+
description: "Channel label in resolver question context",
|
|
306
|
+
placeholders: [{ name: "channel", type: "string" }]
|
|
307
|
+
},
|
|
308
|
+
"resolver.question.customerLabel": {
|
|
309
|
+
value: "Client\xA0: {name}",
|
|
310
|
+
description: "Customer label in resolver question context",
|
|
311
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
312
|
+
},
|
|
313
|
+
"resolver.action.escalate": {
|
|
314
|
+
value: "Escalader pour r\xE9vision humaine",
|
|
315
|
+
description: "Action label for escalation"
|
|
316
|
+
},
|
|
317
|
+
"resolver.action.respond": {
|
|
318
|
+
value: "Envoyer une r\xE9ponse automatique",
|
|
319
|
+
description: "Action label for automated response"
|
|
320
|
+
},
|
|
321
|
+
"resolver.escalation.insufficientConfidence": {
|
|
322
|
+
value: "Confiance insuffisante ou r\xE9f\xE9rences de connaissances manquantes",
|
|
323
|
+
description: "Escalation reason when confidence is too low"
|
|
324
|
+
},
|
|
325
|
+
"tool.classify.title": {
|
|
326
|
+
value: "support_classify_ticket",
|
|
327
|
+
description: "MCP tool title for ticket classification"
|
|
328
|
+
},
|
|
329
|
+
"tool.classify.description": {
|
|
330
|
+
value: "Classifier un ticket par priorit\xE9, sentiment et cat\xE9gorie",
|
|
331
|
+
description: "MCP tool description for ticket classification"
|
|
332
|
+
},
|
|
333
|
+
"tool.resolve.title": {
|
|
334
|
+
value: "support_resolve_ticket",
|
|
335
|
+
description: "MCP tool title for ticket resolution"
|
|
336
|
+
},
|
|
337
|
+
"tool.resolve.description": {
|
|
338
|
+
value: "G\xE9n\xE9rer une r\xE9solution fond\xE9e sur la base de connaissances pour un ticket",
|
|
339
|
+
description: "MCP tool description for ticket resolution"
|
|
340
|
+
},
|
|
341
|
+
"tool.draft.title": {
|
|
342
|
+
value: "support_draft_response",
|
|
343
|
+
description: "MCP tool title for response drafting"
|
|
344
|
+
},
|
|
345
|
+
"tool.draft.description": {
|
|
346
|
+
value: "R\xE9diger une r\xE9ponse client bas\xE9e sur la r\xE9solution et la classification",
|
|
347
|
+
description: "MCP tool description for response drafting"
|
|
348
|
+
},
|
|
349
|
+
"error.inputMustIncludeTicket": {
|
|
350
|
+
value: "L\u2019entr\xE9e doit inclure un ticket",
|
|
351
|
+
description: "Error when input payload is missing the ticket field"
|
|
352
|
+
},
|
|
353
|
+
"error.ticketMissingId": {
|
|
354
|
+
value: "Le ticket n\u2019a pas d\u2019identifiant",
|
|
355
|
+
description: "Error when ticket object lacks an id"
|
|
356
|
+
},
|
|
357
|
+
"error.resolutionClassificationRequired": {
|
|
358
|
+
value: "la r\xE9solution et la classification sont requises",
|
|
359
|
+
description: "Error when draft endpoint is called without resolution and classification"
|
|
360
|
+
},
|
|
361
|
+
"feedback.noRecords": {
|
|
362
|
+
value: "Aucun retour enregistr\xE9 pour le moment.",
|
|
363
|
+
description: "Placeholder when no feedback entries exist"
|
|
364
|
+
},
|
|
365
|
+
"feedback.status.escalated": {
|
|
366
|
+
value: "Escalad\xE9",
|
|
367
|
+
description: "Status label for escalated tickets"
|
|
368
|
+
},
|
|
369
|
+
"feedback.status.autoResolved": {
|
|
370
|
+
value: "R\xE9solu automatiquement",
|
|
371
|
+
description: "Status label for auto-resolved tickets"
|
|
372
|
+
},
|
|
373
|
+
"spec.instructionsAppendix": {
|
|
374
|
+
value: "Citez toujours les sources de connaissances du support et signalez les probl\xE8mes de conformit\xE9/facturation pour r\xE9vision humaine en cas de doute.",
|
|
375
|
+
description: "Instructions appendix appended to agent spec"
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// src/i18n/catalogs/es.ts
|
|
381
|
+
import { defineTranslation as defineTranslation3 } from "@contractspec/lib.contracts-spec/translations";
|
|
382
|
+
var esMessages = defineTranslation3({
|
|
383
|
+
meta: {
|
|
384
|
+
key: "support-bot.messages",
|
|
385
|
+
version: "1.0.0",
|
|
386
|
+
domain: "support-bot",
|
|
387
|
+
description: "Spanish translations for the support-bot package",
|
|
388
|
+
owners: ["platform"],
|
|
389
|
+
stability: "experimental"
|
|
390
|
+
},
|
|
391
|
+
locale: "es",
|
|
392
|
+
fallback: "en",
|
|
393
|
+
messages: {
|
|
394
|
+
"prompt.classifier.system": {
|
|
395
|
+
value: "Clasifica el ticket de soporte.",
|
|
396
|
+
description: "Classifier LLM system prompt"
|
|
397
|
+
},
|
|
398
|
+
"prompt.autoResponder.system": {
|
|
399
|
+
value: "Redacta respuestas de soporte emp\xE1ticas y precisas citando fuentes cuando sea relevante.",
|
|
400
|
+
description: "Auto-responder LLM system prompt"
|
|
401
|
+
},
|
|
402
|
+
"prompt.autoResponder.user": {
|
|
403
|
+
value: `Ticket #{ticketId} ({category}, {priority}, {sentiment})
|
|
404
|
+
Asunto: {subject}
|
|
405
|
+
|
|
406
|
+
{body}
|
|
407
|
+
|
|
408
|
+
Conocimientos:
|
|
409
|
+
{knowledge}
|
|
410
|
+
|
|
411
|
+
Responde al cliente.`,
|
|
412
|
+
description: "Auto-responder user prompt template",
|
|
413
|
+
placeholders: [
|
|
414
|
+
{ name: "ticketId", type: "string" },
|
|
415
|
+
{ name: "category", type: "string" },
|
|
416
|
+
{ name: "priority", type: "string" },
|
|
417
|
+
{ name: "sentiment", type: "string" },
|
|
418
|
+
{ name: "subject", type: "string" },
|
|
419
|
+
{ name: "body", type: "string" },
|
|
420
|
+
{ name: "knowledge", type: "string" }
|
|
421
|
+
]
|
|
422
|
+
},
|
|
423
|
+
"responder.closing.friendly": {
|
|
424
|
+
value: "Quedamos a su disposici\xF3n si necesita cualquier otra cosa.",
|
|
425
|
+
description: "Friendly closing line for support responses"
|
|
426
|
+
},
|
|
427
|
+
"responder.closing.formal": {
|
|
428
|
+
value: "No dude en contactarnos si requiere asistencia adicional.",
|
|
429
|
+
description: "Formal closing line for support responses"
|
|
430
|
+
},
|
|
431
|
+
"responder.greeting.named": {
|
|
432
|
+
value: "Hola {name},",
|
|
433
|
+
description: "Greeting with customer name",
|
|
434
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
435
|
+
},
|
|
436
|
+
"responder.greeting.anonymous": {
|
|
437
|
+
value: "Hola,",
|
|
438
|
+
description: "Greeting without customer name"
|
|
439
|
+
},
|
|
440
|
+
"responder.intro.thanks": {
|
|
441
|
+
value: "Gracias por contactarnos sobre \xAB{subject}\xBB.",
|
|
442
|
+
description: "Thank-you intro referencing the ticket subject",
|
|
443
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
444
|
+
},
|
|
445
|
+
"responder.signature": {
|
|
446
|
+
value: "\u2014 Soporte ContractSpec",
|
|
447
|
+
description: "Email / response signature"
|
|
448
|
+
},
|
|
449
|
+
"responder.references.header": {
|
|
450
|
+
value: "Referencias:",
|
|
451
|
+
description: "Header for the references section"
|
|
452
|
+
},
|
|
453
|
+
"responder.references.sourceLabel": {
|
|
454
|
+
value: "Fuente {index}",
|
|
455
|
+
description: "Label for a numbered source reference",
|
|
456
|
+
placeholders: [{ name: "index", type: "number" }]
|
|
457
|
+
},
|
|
458
|
+
"responder.category.billing": {
|
|
459
|
+
value: "Entiendo que los problemas de facturaci\xF3n pueden ser estresantes, perm\xEDtame aclarar la situaci\xF3n.",
|
|
460
|
+
description: "Category intro for billing tickets"
|
|
461
|
+
},
|
|
462
|
+
"responder.category.technical": {
|
|
463
|
+
value: "Veo que encontr\xF3 un problema t\xE9cnico. Esto es lo que sucedi\xF3 y c\xF3mo solucionarlo.",
|
|
464
|
+
description: "Category intro for technical tickets"
|
|
465
|
+
},
|
|
466
|
+
"responder.category.product": {
|
|
467
|
+
value: "Gracias por compartir sus comentarios sobre el producto. Estos son los pr\xF3ximos pasos.",
|
|
468
|
+
description: "Category intro for product tickets"
|
|
469
|
+
},
|
|
470
|
+
"responder.category.account": {
|
|
471
|
+
value: "El acceso a la cuenta es fundamental, perm\xEDtame guiarle hacia la resoluci\xF3n.",
|
|
472
|
+
description: "Category intro for account tickets"
|
|
473
|
+
},
|
|
474
|
+
"responder.category.compliance": {
|
|
475
|
+
value: "Las preguntas de cumplimiento requieren precisi\xF3n. Consulte la respuesta alineada con las pol\xEDticas a continuaci\xF3n.",
|
|
476
|
+
description: "Category intro for compliance tickets"
|
|
477
|
+
},
|
|
478
|
+
"responder.category.other": {
|
|
479
|
+
value: "Esto es lo que encontramos tras revisar su solicitud.",
|
|
480
|
+
description: "Category intro for uncategorized tickets"
|
|
481
|
+
},
|
|
482
|
+
"responder.subject.replyPrefix": {
|
|
483
|
+
value: "Re: {subject}",
|
|
484
|
+
description: "Reply subject line prefix",
|
|
485
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
486
|
+
},
|
|
487
|
+
"resolver.question.subjectLabel": {
|
|
488
|
+
value: "Asunto: {subject}",
|
|
489
|
+
description: "Subject label in resolver question context",
|
|
490
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
491
|
+
},
|
|
492
|
+
"resolver.question.channelLabel": {
|
|
493
|
+
value: "Canal: {channel}",
|
|
494
|
+
description: "Channel label in resolver question context",
|
|
495
|
+
placeholders: [{ name: "channel", type: "string" }]
|
|
496
|
+
},
|
|
497
|
+
"resolver.question.customerLabel": {
|
|
498
|
+
value: "Cliente: {name}",
|
|
499
|
+
description: "Customer label in resolver question context",
|
|
500
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
501
|
+
},
|
|
502
|
+
"resolver.action.escalate": {
|
|
503
|
+
value: "Escalar para revisi\xF3n humana",
|
|
504
|
+
description: "Action label for escalation"
|
|
505
|
+
},
|
|
506
|
+
"resolver.action.respond": {
|
|
507
|
+
value: "Enviar respuesta autom\xE1tica",
|
|
508
|
+
description: "Action label for automated response"
|
|
509
|
+
},
|
|
510
|
+
"resolver.escalation.insufficientConfidence": {
|
|
511
|
+
value: "Confianza insuficiente o referencias de conocimiento faltantes",
|
|
512
|
+
description: "Escalation reason when confidence is too low"
|
|
513
|
+
},
|
|
514
|
+
"tool.classify.title": {
|
|
515
|
+
value: "support_classify_ticket",
|
|
516
|
+
description: "MCP tool title for ticket classification"
|
|
517
|
+
},
|
|
518
|
+
"tool.classify.description": {
|
|
519
|
+
value: "Clasificar un ticket por prioridad, sentimiento y categor\xEDa",
|
|
520
|
+
description: "MCP tool description for ticket classification"
|
|
521
|
+
},
|
|
522
|
+
"tool.resolve.title": {
|
|
523
|
+
value: "support_resolve_ticket",
|
|
524
|
+
description: "MCP tool title for ticket resolution"
|
|
525
|
+
},
|
|
526
|
+
"tool.resolve.description": {
|
|
527
|
+
value: "Generar una resoluci\xF3n basada en conocimientos para un ticket",
|
|
528
|
+
description: "MCP tool description for ticket resolution"
|
|
529
|
+
},
|
|
530
|
+
"tool.draft.title": {
|
|
531
|
+
value: "support_draft_response",
|
|
532
|
+
description: "MCP tool title for response drafting"
|
|
533
|
+
},
|
|
534
|
+
"tool.draft.description": {
|
|
535
|
+
value: "Redactar una respuesta al cliente basada en la resoluci\xF3n y la clasificaci\xF3n",
|
|
536
|
+
description: "MCP tool description for response drafting"
|
|
537
|
+
},
|
|
538
|
+
"error.inputMustIncludeTicket": {
|
|
539
|
+
value: "La entrada debe incluir un ticket",
|
|
540
|
+
description: "Error when input payload is missing the ticket field"
|
|
541
|
+
},
|
|
542
|
+
"error.ticketMissingId": {
|
|
543
|
+
value: "El ticket no tiene identificador",
|
|
544
|
+
description: "Error when ticket object lacks an id"
|
|
545
|
+
},
|
|
546
|
+
"error.resolutionClassificationRequired": {
|
|
547
|
+
value: "la resoluci\xF3n y la clasificaci\xF3n son obligatorias",
|
|
548
|
+
description: "Error when draft endpoint is called without resolution and classification"
|
|
549
|
+
},
|
|
550
|
+
"feedback.noRecords": {
|
|
551
|
+
value: "No hay comentarios registrados a\xFAn.",
|
|
552
|
+
description: "Placeholder when no feedback entries exist"
|
|
553
|
+
},
|
|
554
|
+
"feedback.status.escalated": {
|
|
555
|
+
value: "Escalado",
|
|
556
|
+
description: "Status label for escalated tickets"
|
|
557
|
+
},
|
|
558
|
+
"feedback.status.autoResolved": {
|
|
559
|
+
value: "Resuelto autom\xE1ticamente",
|
|
560
|
+
description: "Status label for auto-resolved tickets"
|
|
561
|
+
},
|
|
562
|
+
"spec.instructionsAppendix": {
|
|
563
|
+
value: "Cite siempre las fuentes de conocimiento de soporte y se\xF1ale los problemas de cumplimiento/facturaci\xF3n para revisi\xF3n humana en caso de duda.",
|
|
564
|
+
description: "Instructions appendix appended to agent spec"
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// src/i18n/messages.ts
|
|
570
|
+
import {
|
|
571
|
+
createI18nFactory
|
|
572
|
+
} from "@contractspec/lib.contracts-spec/translations";
|
|
573
|
+
import { interpolate } from "@contractspec/lib.contracts-spec/translations";
|
|
574
|
+
var factory = createI18nFactory({
|
|
575
|
+
specKey: "support-bot.messages",
|
|
576
|
+
catalogs: [enMessages, frMessages, esMessages]
|
|
577
|
+
});
|
|
578
|
+
var createSupportBotI18n = factory.create;
|
|
579
|
+
var getDefaultI18n = factory.getDefault;
|
|
580
|
+
var resetI18nRegistry = factory.resetRegistry;
|
|
581
|
+
|
|
582
|
+
// src/i18n/locale.ts
|
|
583
|
+
import {
|
|
584
|
+
DEFAULT_LOCALE,
|
|
585
|
+
SUPPORTED_LOCALES,
|
|
586
|
+
resolveLocale,
|
|
587
|
+
isSupportedLocale
|
|
588
|
+
} from "@contractspec/lib.contracts-spec/translations";
|
|
589
|
+
|
|
590
|
+
// src/i18n/keys.ts
|
|
591
|
+
var PROMPT_KEYS = {
|
|
592
|
+
"prompt.classifier.system": "prompt.classifier.system",
|
|
593
|
+
"prompt.autoResponder.system": "prompt.autoResponder.system",
|
|
594
|
+
"prompt.autoResponder.user": "prompt.autoResponder.user"
|
|
595
|
+
};
|
|
596
|
+
var RESPONDER_KEYS = {
|
|
597
|
+
"responder.closing.friendly": "responder.closing.friendly",
|
|
598
|
+
"responder.closing.formal": "responder.closing.formal",
|
|
599
|
+
"responder.greeting.named": "responder.greeting.named",
|
|
600
|
+
"responder.greeting.anonymous": "responder.greeting.anonymous",
|
|
601
|
+
"responder.intro.thanks": "responder.intro.thanks",
|
|
602
|
+
"responder.signature": "responder.signature",
|
|
603
|
+
"responder.references.header": "responder.references.header",
|
|
604
|
+
"responder.references.sourceLabel": "responder.references.sourceLabel",
|
|
605
|
+
"responder.category.billing": "responder.category.billing",
|
|
606
|
+
"responder.category.technical": "responder.category.technical",
|
|
607
|
+
"responder.category.product": "responder.category.product",
|
|
608
|
+
"responder.category.account": "responder.category.account",
|
|
609
|
+
"responder.category.compliance": "responder.category.compliance",
|
|
610
|
+
"responder.category.other": "responder.category.other",
|
|
611
|
+
"responder.subject.replyPrefix": "responder.subject.replyPrefix"
|
|
612
|
+
};
|
|
613
|
+
var RESOLVER_KEYS = {
|
|
614
|
+
"resolver.question.subjectLabel": "resolver.question.subjectLabel",
|
|
615
|
+
"resolver.question.channelLabel": "resolver.question.channelLabel",
|
|
616
|
+
"resolver.question.customerLabel": "resolver.question.customerLabel",
|
|
617
|
+
"resolver.action.escalate": "resolver.action.escalate",
|
|
618
|
+
"resolver.action.respond": "resolver.action.respond",
|
|
619
|
+
"resolver.escalation.insufficientConfidence": "resolver.escalation.insufficientConfidence"
|
|
620
|
+
};
|
|
621
|
+
var TOOL_KEYS = {
|
|
622
|
+
"tool.classify.title": "tool.classify.title",
|
|
623
|
+
"tool.classify.description": "tool.classify.description",
|
|
624
|
+
"tool.resolve.title": "tool.resolve.title",
|
|
625
|
+
"tool.resolve.description": "tool.resolve.description",
|
|
626
|
+
"tool.draft.title": "tool.draft.title",
|
|
627
|
+
"tool.draft.description": "tool.draft.description"
|
|
628
|
+
};
|
|
629
|
+
var ERROR_KEYS = {
|
|
630
|
+
"error.inputMustIncludeTicket": "error.inputMustIncludeTicket",
|
|
631
|
+
"error.ticketMissingId": "error.ticketMissingId",
|
|
632
|
+
"error.resolutionClassificationRequired": "error.resolutionClassificationRequired"
|
|
633
|
+
};
|
|
634
|
+
var FEEDBACK_KEYS = {
|
|
635
|
+
"feedback.noRecords": "feedback.noRecords",
|
|
636
|
+
"feedback.status.escalated": "feedback.status.escalated",
|
|
637
|
+
"feedback.status.autoResolved": "feedback.status.autoResolved"
|
|
638
|
+
};
|
|
639
|
+
var SPEC_KEYS = {
|
|
640
|
+
"spec.instructionsAppendix": "spec.instructionsAppendix"
|
|
641
|
+
};
|
|
642
|
+
var I18N_KEYS = {
|
|
643
|
+
...PROMPT_KEYS,
|
|
644
|
+
...RESPONDER_KEYS,
|
|
645
|
+
...RESOLVER_KEYS,
|
|
646
|
+
...TOOL_KEYS,
|
|
647
|
+
...ERROR_KEYS,
|
|
648
|
+
...FEEDBACK_KEYS,
|
|
649
|
+
...SPEC_KEYS
|
|
650
|
+
};
|
|
2
651
|
// src/bot/auto-responder.ts
|
|
3
652
|
class AutoResponder {
|
|
4
653
|
llm;
|
|
5
654
|
model;
|
|
6
655
|
tone;
|
|
7
656
|
closing;
|
|
657
|
+
i18n;
|
|
8
658
|
constructor(options) {
|
|
9
659
|
this.llm = options?.llm;
|
|
10
660
|
this.model = options?.model;
|
|
11
661
|
this.tone = options?.tone ?? "friendly";
|
|
12
|
-
this.
|
|
662
|
+
this.i18n = createSupportBotI18n(options?.locale);
|
|
663
|
+
const { t } = this.i18n;
|
|
664
|
+
this.closing = options?.closing ?? (this.tone === "friendly" ? t("responder.closing.friendly") : t("responder.closing.formal"));
|
|
13
665
|
}
|
|
14
666
|
async draft(ticket, resolution, classification) {
|
|
15
667
|
if (this.llm) {
|
|
@@ -18,21 +670,23 @@ class AutoResponder {
|
|
|
18
670
|
return this.generateTemplate(ticket, resolution, classification);
|
|
19
671
|
}
|
|
20
672
|
async generateWithLLM(ticket, resolution, classification) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
673
|
+
const { t } = this.i18n;
|
|
674
|
+
const prompt = t("prompt.autoResponder.user", {
|
|
675
|
+
tone: this.tone,
|
|
676
|
+
subject: ticket.subject,
|
|
677
|
+
body: ticket.body,
|
|
678
|
+
category: classification.category,
|
|
679
|
+
priority: classification.priority,
|
|
680
|
+
answer: resolution.answer,
|
|
681
|
+
citations: resolution.citations.map((c) => c.label).join(", ")
|
|
682
|
+
});
|
|
29
683
|
const response = await this.llm.chat([
|
|
30
684
|
{
|
|
31
685
|
role: "system",
|
|
32
686
|
content: [
|
|
33
687
|
{
|
|
34
688
|
type: "text",
|
|
35
|
-
text: "
|
|
689
|
+
text: t("prompt.autoResponder.system")
|
|
36
690
|
}
|
|
37
691
|
]
|
|
38
692
|
},
|
|
@@ -45,17 +699,18 @@ Citations: ${resolution.citations.map((c) => c.label).join(", ")}`;
|
|
|
45
699
|
return this.buildDraft(ticket, resolution, classification, body);
|
|
46
700
|
}
|
|
47
701
|
generateTemplate(ticket, resolution, classification) {
|
|
48
|
-
const
|
|
702
|
+
const { t } = this.i18n;
|
|
703
|
+
const greeting = ticket.customerName ? t("responder.greeting.named", { name: ticket.customerName }) : t("responder.greeting.anonymous");
|
|
49
704
|
const body = `${greeting}
|
|
50
705
|
|
|
51
|
-
|
|
706
|
+
${t("responder.intro.thanks", { subject: ticket.subject })} ${this.renderCategoryIntro(classification)}
|
|
52
707
|
|
|
53
708
|
${resolution.answer}
|
|
54
709
|
|
|
55
710
|
${this.renderCitations(resolution)}
|
|
56
711
|
${this.closing}
|
|
57
712
|
|
|
58
|
-
|
|
713
|
+
${t("responder.signature")}`;
|
|
59
714
|
return this.buildDraft(ticket, resolution, classification, body);
|
|
60
715
|
}
|
|
61
716
|
buildDraft(ticket, resolution, classification, body) {
|
|
@@ -69,30 +724,32 @@ ${this.closing}
|
|
|
69
724
|
};
|
|
70
725
|
}
|
|
71
726
|
renderCategoryIntro(classification) {
|
|
727
|
+
const { t } = this.i18n;
|
|
72
728
|
switch (classification.category) {
|
|
73
729
|
case "billing":
|
|
74
|
-
return "
|
|
730
|
+
return t("responder.category.billing");
|
|
75
731
|
case "technical":
|
|
76
|
-
return "
|
|
732
|
+
return t("responder.category.technical");
|
|
77
733
|
case "product":
|
|
78
|
-
return "
|
|
734
|
+
return t("responder.category.product");
|
|
79
735
|
case "account":
|
|
80
|
-
return "
|
|
736
|
+
return t("responder.category.account");
|
|
81
737
|
case "compliance":
|
|
82
|
-
return "
|
|
738
|
+
return t("responder.category.compliance");
|
|
83
739
|
default:
|
|
84
|
-
return "
|
|
740
|
+
return t("responder.category.other");
|
|
85
741
|
}
|
|
86
742
|
}
|
|
87
743
|
renderCitations(resolution) {
|
|
744
|
+
const { t } = this.i18n;
|
|
88
745
|
if (!resolution.citations.length)
|
|
89
746
|
return "";
|
|
90
747
|
const lines = resolution.citations.map((citation, index) => {
|
|
91
|
-
const label = citation.label ||
|
|
748
|
+
const label = citation.label || t("responder.references.sourceLabel", { index: index + 1 });
|
|
92
749
|
const link = citation.url ? ` (${citation.url})` : "";
|
|
93
750
|
return `- ${label}${link}`;
|
|
94
751
|
});
|
|
95
|
-
return
|
|
752
|
+
return `${t("responder.references.header")}
|
|
96
753
|
${lines.join(`
|
|
97
754
|
`)}`;
|
|
98
755
|
}
|
|
@@ -123,11 +780,12 @@ class SupportFeedbackLoop {
|
|
|
123
780
|
};
|
|
124
781
|
}
|
|
125
782
|
feedbackSummary(limit = 5) {
|
|
783
|
+
const { t } = getDefaultI18n();
|
|
126
784
|
const recent = this.history.slice(-limit);
|
|
127
785
|
if (!recent.length)
|
|
128
|
-
return "
|
|
786
|
+
return t("feedback.noRecords");
|
|
129
787
|
return recent.map((entry) => {
|
|
130
|
-
const status = entry.resolution.actions.some((action) => action.type === "escalate") ? "
|
|
788
|
+
const status = entry.resolution.actions.some((action) => action.type === "escalate") ? t("feedback.status.escalated") : t("feedback.status.autoResolved");
|
|
131
789
|
return `${entry.ticket.subject} \u2013 ${status} (confidence: ${entry.resolution.confidence})`;
|
|
132
790
|
}).join(`
|
|
133
791
|
`);
|
|
@@ -298,10 +956,12 @@ class TicketResolver {
|
|
|
298
956
|
knowledge;
|
|
299
957
|
minConfidence;
|
|
300
958
|
prependPrompt;
|
|
959
|
+
i18n;
|
|
301
960
|
constructor(options) {
|
|
302
961
|
this.knowledge = options.knowledge;
|
|
303
962
|
this.minConfidence = options.minConfidence ?? 0.65;
|
|
304
963
|
this.prependPrompt = options.prependPrompt;
|
|
964
|
+
this.i18n = createSupportBotI18n(options.locale);
|
|
305
965
|
}
|
|
306
966
|
async resolve(ticket) {
|
|
307
967
|
const question = this.buildQuestion(ticket);
|
|
@@ -309,9 +969,14 @@ class TicketResolver {
|
|
|
309
969
|
return this.toResolution(ticket, answer);
|
|
310
970
|
}
|
|
311
971
|
buildQuestion(ticket) {
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
972
|
+
const { t } = this.i18n;
|
|
973
|
+
const header = [
|
|
974
|
+
t("resolver.question.subjectLabel", { subject: ticket.subject }),
|
|
975
|
+
t("resolver.question.channelLabel", { channel: ticket.channel })
|
|
976
|
+
];
|
|
977
|
+
if (ticket.customerName) {
|
|
978
|
+
header.push(t("resolver.question.customerLabel", { name: ticket.customerName }));
|
|
979
|
+
}
|
|
315
980
|
const sections = [
|
|
316
981
|
this.prependPrompt,
|
|
317
982
|
header.join(`
|
|
@@ -332,6 +997,7 @@ class TicketResolver {
|
|
|
332
997
|
score: ref.score
|
|
333
998
|
};
|
|
334
999
|
});
|
|
1000
|
+
const { t } = this.i18n;
|
|
335
1001
|
const confidence = this.deriveConfidence(answer);
|
|
336
1002
|
const escalate = confidence < this.minConfidence || citations.length === 0;
|
|
337
1003
|
return {
|
|
@@ -340,9 +1006,9 @@ class TicketResolver {
|
|
|
340
1006
|
confidence,
|
|
341
1007
|
citations,
|
|
342
1008
|
actions: [
|
|
343
|
-
escalate ? { type: "escalate", label: "
|
|
1009
|
+
escalate ? { type: "escalate", label: t("resolver.action.escalate") } : { type: "respond", label: t("resolver.action.respond") }
|
|
344
1010
|
],
|
|
345
|
-
escalationReason: escalate ? "
|
|
1011
|
+
escalationReason: escalate ? t("resolver.escalation.insufficientConfidence") : undefined,
|
|
346
1012
|
knowledgeUpdates: escalate ? [ticket.body.slice(0, 200)] : undefined
|
|
347
1013
|
};
|
|
348
1014
|
}
|
|
@@ -357,35 +1023,193 @@ class TicketResolver {
|
|
|
357
1023
|
}
|
|
358
1024
|
// src/tickets/classifier.ts
|
|
359
1025
|
var CATEGORY_KEYWORDS = {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
1026
|
+
en: {
|
|
1027
|
+
billing: ["invoice", "payout", "refund", "charge", "billing", "payment"],
|
|
1028
|
+
technical: ["bug", "error", "crash", "issue", "failed", "timeout"],
|
|
1029
|
+
product: ["feature", "roadmap", "idea", "request", "feedback"],
|
|
1030
|
+
account: ["login", "password", "2fa", "account", "profile", "email change"],
|
|
1031
|
+
compliance: ["kyc", "aml", "compliance", "regulation", "gdpr"],
|
|
1032
|
+
other: []
|
|
1033
|
+
},
|
|
1034
|
+
fr: {
|
|
1035
|
+
billing: [
|
|
1036
|
+
"facture",
|
|
1037
|
+
"versement",
|
|
1038
|
+
"remboursement",
|
|
1039
|
+
"frais",
|
|
1040
|
+
"facturation",
|
|
1041
|
+
"paiement"
|
|
1042
|
+
],
|
|
1043
|
+
technical: [
|
|
1044
|
+
"bogue",
|
|
1045
|
+
"erreur",
|
|
1046
|
+
"plantage",
|
|
1047
|
+
"probl\xE8me",
|
|
1048
|
+
"\xE9chou\xE9",
|
|
1049
|
+
"d\xE9lai d\xE9pass\xE9"
|
|
1050
|
+
],
|
|
1051
|
+
product: [
|
|
1052
|
+
"fonctionnalit\xE9",
|
|
1053
|
+
"feuille de route",
|
|
1054
|
+
"id\xE9e",
|
|
1055
|
+
"demande",
|
|
1056
|
+
"retour"
|
|
1057
|
+
],
|
|
1058
|
+
account: [
|
|
1059
|
+
"connexion",
|
|
1060
|
+
"mot de passe",
|
|
1061
|
+
"2fa",
|
|
1062
|
+
"compte",
|
|
1063
|
+
"profil",
|
|
1064
|
+
"changement email"
|
|
1065
|
+
],
|
|
1066
|
+
compliance: ["kyc", "aml", "conformit\xE9", "r\xE9glementation", "rgpd"],
|
|
1067
|
+
other: []
|
|
1068
|
+
},
|
|
1069
|
+
es: {
|
|
1070
|
+
billing: [
|
|
1071
|
+
"factura",
|
|
1072
|
+
"desembolso",
|
|
1073
|
+
"reembolso",
|
|
1074
|
+
"cargo",
|
|
1075
|
+
"facturaci\xF3n",
|
|
1076
|
+
"pago"
|
|
1077
|
+
],
|
|
1078
|
+
technical: [
|
|
1079
|
+
"error",
|
|
1080
|
+
"fallo",
|
|
1081
|
+
"ca\xEDda",
|
|
1082
|
+
"problema",
|
|
1083
|
+
"fallido",
|
|
1084
|
+
"tiempo agotado"
|
|
1085
|
+
],
|
|
1086
|
+
product: [
|
|
1087
|
+
"funcionalidad",
|
|
1088
|
+
"hoja de ruta",
|
|
1089
|
+
"idea",
|
|
1090
|
+
"solicitud",
|
|
1091
|
+
"comentario"
|
|
1092
|
+
],
|
|
1093
|
+
account: [
|
|
1094
|
+
"inicio de sesi\xF3n",
|
|
1095
|
+
"contrase\xF1a",
|
|
1096
|
+
"2fa",
|
|
1097
|
+
"cuenta",
|
|
1098
|
+
"perfil",
|
|
1099
|
+
"cambio de email"
|
|
1100
|
+
],
|
|
1101
|
+
compliance: ["kyc", "aml", "cumplimiento", "regulaci\xF3n", "rgpd"],
|
|
1102
|
+
other: []
|
|
1103
|
+
}
|
|
366
1104
|
};
|
|
367
1105
|
var PRIORITY_HINTS = {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
1106
|
+
en: {
|
|
1107
|
+
urgent: ["urgent", "asap", "immediately", "today", "right away"],
|
|
1108
|
+
high: ["high priority", "blocking", "major", "critical"],
|
|
1109
|
+
medium: ["soon", "next few days"],
|
|
1110
|
+
low: ["nice to have", "when possible", "later"]
|
|
1111
|
+
},
|
|
1112
|
+
fr: {
|
|
1113
|
+
urgent: [
|
|
1114
|
+
"urgent",
|
|
1115
|
+
"d\xE8s que possible",
|
|
1116
|
+
"imm\xE9diatement",
|
|
1117
|
+
"aujourd'hui",
|
|
1118
|
+
"tout de suite"
|
|
1119
|
+
],
|
|
1120
|
+
high: ["haute priorit\xE9", "bloquant", "majeur", "critique"],
|
|
1121
|
+
medium: ["bient\xF4t", "prochains jours"],
|
|
1122
|
+
low: ["ce serait bien", "quand possible", "plus tard"]
|
|
1123
|
+
},
|
|
1124
|
+
es: {
|
|
1125
|
+
urgent: [
|
|
1126
|
+
"urgente",
|
|
1127
|
+
"lo antes posible",
|
|
1128
|
+
"inmediatamente",
|
|
1129
|
+
"hoy",
|
|
1130
|
+
"ahora mismo"
|
|
1131
|
+
],
|
|
1132
|
+
high: ["alta prioridad", "bloqueante", "mayor", "cr\xEDtico"],
|
|
1133
|
+
medium: ["pronto", "pr\xF3ximos d\xEDas"],
|
|
1134
|
+
low: ["ser\xEDa bueno", "cuando sea posible", "m\xE1s tarde"]
|
|
1135
|
+
}
|
|
372
1136
|
};
|
|
373
1137
|
var SENTIMENT_HINTS = {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
1138
|
+
en: {
|
|
1139
|
+
positive: ["love", "great", "awesome", "thank you"],
|
|
1140
|
+
neutral: ["question", "wonder", "curious"],
|
|
1141
|
+
negative: ["unhappy", "bad", "terrible", "awful", "angry"],
|
|
1142
|
+
frustrated: ["furious", "frustrated", "fed up", "ridiculous"]
|
|
1143
|
+
},
|
|
1144
|
+
fr: {
|
|
1145
|
+
positive: ["adore", "super", "formidable", "merci"],
|
|
1146
|
+
neutral: ["question", "demande", "curieux"],
|
|
1147
|
+
negative: ["m\xE9content", "mauvais", "terrible", "affreux", "en col\xE8re"],
|
|
1148
|
+
frustrated: ["furieux", "frustr\xE9", "ras le bol", "ridicule"]
|
|
1149
|
+
},
|
|
1150
|
+
es: {
|
|
1151
|
+
positive: ["encanta", "genial", "incre\xEDble", "gracias"],
|
|
1152
|
+
neutral: ["pregunta", "duda", "curioso"],
|
|
1153
|
+
negative: ["descontento", "malo", "terrible", "horrible", "enojado"],
|
|
1154
|
+
frustrated: ["furioso", "frustrado", "harto", "rid\xEDculo"]
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
var INTENT_KEYWORDS = {
|
|
1158
|
+
en: [
|
|
1159
|
+
{ keyword: "refund", intent: "refund" },
|
|
1160
|
+
{ keyword: "chargeback", intent: "refund" },
|
|
1161
|
+
{ keyword: "payout", intent: "payout" },
|
|
1162
|
+
{ keyword: "login", intent: "login-help" },
|
|
1163
|
+
{ keyword: "feature", intent: "feature-request" },
|
|
1164
|
+
{ keyword: "bug", intent: "bug-report" },
|
|
1165
|
+
{ keyword: "error", intent: "bug-report" }
|
|
1166
|
+
],
|
|
1167
|
+
fr: [
|
|
1168
|
+
{ keyword: "remboursement", intent: "refund" },
|
|
1169
|
+
{ keyword: "r\xE9trofacturation", intent: "refund" },
|
|
1170
|
+
{ keyword: "versement", intent: "payout" },
|
|
1171
|
+
{ keyword: "connexion", intent: "login-help" },
|
|
1172
|
+
{ keyword: "fonctionnalit\xE9", intent: "feature-request" },
|
|
1173
|
+
{ keyword: "bogue", intent: "bug-report" },
|
|
1174
|
+
{ keyword: "erreur", intent: "bug-report" }
|
|
1175
|
+
],
|
|
1176
|
+
es: [
|
|
1177
|
+
{ keyword: "reembolso", intent: "refund" },
|
|
1178
|
+
{ keyword: "contracargo", intent: "refund" },
|
|
1179
|
+
{ keyword: "desembolso", intent: "payout" },
|
|
1180
|
+
{ keyword: "inicio de sesi\xF3n", intent: "login-help" },
|
|
1181
|
+
{ keyword: "funcionalidad", intent: "feature-request" },
|
|
1182
|
+
{ keyword: "error", intent: "bug-report" },
|
|
1183
|
+
{ keyword: "fallo", intent: "bug-report" }
|
|
1184
|
+
]
|
|
378
1185
|
};
|
|
1186
|
+
function resolveBaseLocale(locale) {
|
|
1187
|
+
if (!locale)
|
|
1188
|
+
return "en";
|
|
1189
|
+
const base = locale.split("-")[0]?.toLowerCase() ?? "en";
|
|
1190
|
+
if (base === "en" || base === "fr" || base === "es") {
|
|
1191
|
+
return base;
|
|
1192
|
+
}
|
|
1193
|
+
return "en";
|
|
1194
|
+
}
|
|
379
1195
|
|
|
380
1196
|
class TicketClassifier {
|
|
381
1197
|
keywords;
|
|
1198
|
+
priorityHints;
|
|
1199
|
+
sentimentHints;
|
|
1200
|
+
intentKeywords;
|
|
382
1201
|
llm;
|
|
383
1202
|
llmModel;
|
|
1203
|
+
locale;
|
|
384
1204
|
constructor(options) {
|
|
1205
|
+
this.locale = resolveBaseLocale(options?.locale);
|
|
385
1206
|
this.keywords = {
|
|
386
|
-
...CATEGORY_KEYWORDS,
|
|
1207
|
+
...CATEGORY_KEYWORDS[this.locale],
|
|
387
1208
|
...options?.keywords ?? {}
|
|
388
1209
|
};
|
|
1210
|
+
this.priorityHints = PRIORITY_HINTS[this.locale];
|
|
1211
|
+
this.sentimentHints = SENTIMENT_HINTS[this.locale];
|
|
1212
|
+
this.intentKeywords = INTENT_KEYWORDS[this.locale];
|
|
389
1213
|
this.llm = options?.llm;
|
|
390
1214
|
this.llmModel = options?.llmModel;
|
|
391
1215
|
}
|
|
@@ -397,7 +1221,12 @@ class TicketClassifier {
|
|
|
397
1221
|
const llmResult = await this.llm.chat([
|
|
398
1222
|
{
|
|
399
1223
|
role: "system",
|
|
400
|
-
content: [
|
|
1224
|
+
content: [
|
|
1225
|
+
{
|
|
1226
|
+
type: "text",
|
|
1227
|
+
text: createSupportBotI18n(this.locale).t("prompt.classifier.system")
|
|
1228
|
+
}
|
|
1229
|
+
]
|
|
401
1230
|
},
|
|
402
1231
|
{
|
|
403
1232
|
role: "user",
|
|
@@ -464,7 +1293,8 @@ ${ticket.body}`.toLowerCase();
|
|
|
464
1293
|
"medium",
|
|
465
1294
|
"low"
|
|
466
1295
|
]) {
|
|
467
|
-
|
|
1296
|
+
const hints = this.priorityHints[priority];
|
|
1297
|
+
if (hints?.some((word) => text.includes(word))) {
|
|
468
1298
|
return priority;
|
|
469
1299
|
}
|
|
470
1300
|
}
|
|
@@ -477,25 +1307,21 @@ ${ticket.body}`.toLowerCase();
|
|
|
477
1307
|
"neutral",
|
|
478
1308
|
"positive"
|
|
479
1309
|
]) {
|
|
480
|
-
|
|
1310
|
+
const hints = this.sentimentHints[sentiment];
|
|
1311
|
+
if (hints?.some((word) => text.includes(word))) {
|
|
481
1312
|
return sentiment;
|
|
482
1313
|
}
|
|
483
1314
|
}
|
|
484
1315
|
return "neutral";
|
|
485
1316
|
}
|
|
486
1317
|
extractIntents(text) {
|
|
487
|
-
const intents =
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (text.includes("feature"))
|
|
495
|
-
intents.push("feature-request");
|
|
496
|
-
if (text.includes("bug") || text.includes("error"))
|
|
497
|
-
intents.push("bug-report");
|
|
498
|
-
return intents.length ? intents : ["general"];
|
|
1318
|
+
const intents = new Set;
|
|
1319
|
+
for (const { keyword, intent } of this.intentKeywords) {
|
|
1320
|
+
if (text.includes(keyword)) {
|
|
1321
|
+
intents.add(intent);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return intents.size > 0 ? [...intents] : ["general"];
|
|
499
1325
|
}
|
|
500
1326
|
estimateConfidence(category, priority, sentiment) {
|
|
501
1327
|
let base = 0.6;
|