@tiledesk/tiledesk-tybot-connector 2.0.29-rc5 → 2.0.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiledesk/tiledesk-tybot-connector",
3
- "version": "2.0.29-rc5",
3
+ "version": "2.0.30",
4
4
  "description": "Tiledesk Tybot connector",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -15,7 +15,7 @@
15
15
  "@tiledesk/tiledesk-chatbot-client": "^0.5.30",
16
16
  "@tiledesk/tiledesk-chatbot-util": "^0.8.39",
17
17
  "@tiledesk/tiledesk-client": "^0.10.13",
18
- "@tiledesk/tiledesk-multi-worker": "^0.3.3",
18
+ "@tiledesk/tiledesk-multi-worker": "^0.3.2",
19
19
  "accept-language-parser": "^1.5.0",
20
20
  "app-root-path": "^3.1.0",
21
21
  "axios": "^1.7.7",
@@ -2,7 +2,7 @@ const httpUtils = require('../utils/HttpUtils');
2
2
  const winston = require('../utils/winston');
3
3
  const API_ENDPOINT = process.env.API_ENDPOINT;
4
4
 
5
- class LLMService {
5
+ class KBService {
6
6
 
7
7
  constructor() { }
8
8
 
@@ -101,5 +101,5 @@ class LLMService {
101
101
  }
102
102
  }
103
103
 
104
- const llmService = new LLMService();
105
- module.exports = llmService;
104
+ const kbService = new KBService();
105
+ module.exports = kbService;
@@ -54,14 +54,14 @@ const { DirMoveToUnassigned } = require('./directives/DirMoveToUnassigned');
54
54
  const { DirAddTags } = require('./directives/DirAddTags');
55
55
  const { DirSendWhatsapp } = require('./directives/DirSendWhatsapp');
56
56
  const { DirReplaceBotV3 } = require('./directives/DirReplaceBotV3');
57
- const { DirAiPrompt } = require('./directives/DirAiPrompt');
57
+ const { DirAiTask, DirAiPrompt } = require('./directives/DirAiPrompt');
58
58
  const { DirWebResponse } = require('./directives/DirWebResponse');
59
59
  const { DirConnectBlock } = require('./directives/DirConnectBlock');
60
60
  const { DirAiCondition } = require('./directives/DirAiCondition');
61
- const { DirAddKbContent } = require('./directives/DirAddKbContent');
62
- const { DirFlowLog } = require('./directives/DirFlowLog');
63
61
 
64
62
  const winston = require('../utils/winston');
63
+ const { DirFlowLog } = require('./directives/DirFlowLog');
64
+ const { DirAddKbContent } = require('./directives/DirAddKbContent');
65
65
 
66
66
  class DirectivesChatbotPlug {
67
67
 
@@ -211,7 +211,7 @@ class DirectivesChatbotPlug {
211
211
  }
212
212
 
213
213
  async process(directive) {
214
-
214
+ const t3 = Date.now();
215
215
  let context = this.context;
216
216
  if (directive) {
217
217
  winston.verbose("(DirectivesChatbotPlug) directive['name']: " + directive["name"]);
@@ -221,13 +221,14 @@ class DirectivesChatbotPlug {
221
221
  directive_name = directive.name.toLowerCase();
222
222
  }
223
223
  if (directive && directive.action) {
224
- const action_id = directive.action["_tdActionId"];
225
- const locked_action_id = await this.chatbot.currentLockedAction(this.supportRequest.request_id);
226
- if ( locked_action_id && (locked_action_id !== action_id) ) {
227
- let next_dir = await this.nextDirective(this.directives);
228
- this.process(next_dir);
229
- return;
230
- }
224
+ const action_id = directive.action["_tdActionId"];
225
+ const locked_action_id = await this.chatbot.currentLockedAction(this.supportRequest.request_id);
226
+ if ( locked_action_id && (locked_action_id !== action_id) ) {
227
+ let next_dir = await this.nextDirective(this.directives);
228
+ this.process(next_dir);
229
+ return;
230
+ }
231
+
231
232
  }
232
233
  if (directive == null || (directive !== null && directive["name"] === undefined)) {
233
234
  winston.debug("(DirectivesChatbotPlug) stop process(). directive is (null?): ", directive);
@@ -452,7 +453,6 @@ class DirectivesChatbotPlug {
452
453
  else if (directive_name === Directives.WAIT) {
453
454
  new DirWait(context).execute(directive, async () => {
454
455
  let next_dir = await this.nextDirective(this.directives);
455
-
456
456
  this.process(next_dir);
457
457
  });
458
458
  }
@@ -718,18 +718,6 @@ class DirectivesChatbotPlug {
718
718
  }
719
719
  });
720
720
  }
721
- else if (directive_name === Directives.WEBHOOK) {
722
- new DirIntent(context).execute(directive, async (stop) => {
723
- if (stop) {
724
- if (context.log) { console.log("Stopping Actions on:", JSON.stringify(directive));}
725
- this.theend();
726
- }
727
- else {
728
- let next_dir = await this.nextDirective(this.directives);
729
- this.process(next_dir);
730
- }
731
- });
732
- }
733
721
  else if (directive_name === Directives.WEB_RESPONSE) {
734
722
  new DirWebResponse(context).execute(directive, async () => {
735
723
  let next_dir = await this.nextDirective(this.directives);
@@ -742,12 +730,6 @@ class DirectivesChatbotPlug {
742
730
  this.process(next_dir);
743
731
  })
744
732
  }
745
- else if (directive_name === Directives.ADD_KB_CONTENT) {
746
- new DirAddKbContent(context).execute(directive, async () => {
747
- let next_dir = await this.nextDirective(this.directives);
748
- this.process(next_dir);
749
- });
750
- }
751
733
  else {
752
734
  let next_dir = await this.nextDirective(this.directives);
753
735
  this.process(next_dir);
@@ -67,14 +67,14 @@ class DirAiCondition {
67
67
  // "conditionIntentId": "#9b1c29c1671847dba6db561f771a142e"
68
68
  // }
69
69
  // ]
70
- let falllbackIntent = action.falllbackIntent; // non condition met block
71
- let falseIntent = action.falllbackIntent; // On error block
70
+ let fallbackIntent = action.fallbackIntent; // non condition met block
71
+ let errorIntent = action.errorIntent; // On error block
72
72
  await this.checkMandatoryParameters(action).catch( async (missing_param) => {
73
73
  const error = "AiPrompt Error: '" + missing_param + "' attribute is undefined"
74
74
  this.logger.error(error);
75
75
  await this.chatbot.addParameter("flowError", error);
76
- if (falseIntent) {
77
- await this.#executeCondition(false, trueIntent, trueIntentAttributes, falseIntent, falseIntentAttributes);
76
+ if (errorIntent) {
77
+ await this.#executeIntent(errorIntent);
78
78
  callback(true);
79
79
  return Promise.reject();
80
80
  }
@@ -97,6 +97,7 @@ class DirAiCondition {
97
97
  });
98
98
 
99
99
  let instructions = filler.fill(action.instructions, requestVariables);
100
+ let prompt_header = "Reply with the label satisfying the corresponding condition or with “fallback” if all conditions are false.\nIf more than one condition is true, answer with the first label corresponding to the true condition, following the order from top to bottom."
100
101
  let condition_prompt = TiledeskChatbotUtil.AiConditionPromptBuilder(prompt_header, intents, instructions)
101
102
 
102
103
  // let raw_condition_prompt = `Reply with the label satisfying the corresponding condition or with “fallback” if all conditions are false.
@@ -116,7 +117,9 @@ class DirAiCondition {
116
117
  'Content-Type': 'application/json'
117
118
  }
118
119
 
120
+ let answer = "";
119
121
  let key;
122
+ let publicKey = false;
120
123
  let ollama_integration;
121
124
 
122
125
  if (action.llm === 'ollama') {
@@ -124,8 +127,8 @@ class DirAiCondition {
124
127
  this.logger.error("[AI Condition] Error getting ollama integration.")
125
128
  winston.error("DirAiPrompt Error getting ollama integration: ", err);
126
129
  await this.chatbot.addParameter("flowError", "Ollama integration not found");
127
- if (falseIntent) {
128
- await this.#executeCondition(false, trueIntent, trueIntentAttributes, falseIntent, falseIntentAttributes);
130
+ if (errorIntent) {
131
+ await this.#executeIntent(errorIntent);
129
132
  callback(true);
130
133
  return;
131
134
  }
@@ -136,12 +139,18 @@ class DirAiCondition {
136
139
  } else {
137
140
  key = await integrationService.getKeyFromIntegrations(this.projectId, action.llm, this.token);
138
141
 
142
+ if (!key && action.llm === "openai") {
143
+ this.logger.native("[AI Condition] OpenAI key not found in Integration. Retrieve shared OpenAI key.")
144
+ key = process.env.GPTKEY;
145
+ publicKey = true;
146
+ }
147
+
139
148
  if (!key) {
140
- this.logger.error("[AI Condition] llm key not found in integrations");
141
- winston.error("Error: DirAiPrompt llm key not found in integrations");
149
+ this.logger.error("[AI Condition] llm key not found");
150
+ winston.error("Error: DirAiPrompt llm key not found");
142
151
  await this.chatbot.addParameter("flowError", "AiPrompt Error: missing key for llm " + action.llm);
143
- if (falseIntent) {
144
- await this.#executeCondition(false, trueIntent, trueIntentAttributes, falseIntent, falseIntentAttributes);
152
+ if (errorIntent) {
153
+ await this.#executeIntent(errorIntent);
145
154
  callback(true);
146
155
  return;
147
156
  }
@@ -150,6 +159,33 @@ class DirAiCondition {
150
159
  }
151
160
  }
152
161
 
162
+ if (publicKey === true) {
163
+ try {
164
+ let keep_going = await this.checkQuoteAvailability(this.projectId, this.token)
165
+ if (keep_going === false) {
166
+ this.logger.warn("[AI Condition] OpenAI tokens quota exceeded");
167
+ await this.chatbot.addParameter("flowError", "GPT Error: tokens quota exceeded");
168
+ if (errorIntent) {
169
+ await this.#executeIntent(errorIntent);
170
+ callback();
171
+ return;
172
+ }
173
+ callback();
174
+ return;
175
+ }
176
+ } catch (err) {
177
+ this.logger.error("An error occured on checking token quota availability");
178
+ await this.chatbot.addParameter("flowError", "An error occured on checking token quota availability");
179
+ if (errorIntent) {
180
+ await this.#executeIntent(errorIntent);
181
+ callback();
182
+ return;
183
+ }
184
+ callback();
185
+ return;
186
+ }
187
+ }
188
+
153
189
  let json = {
154
190
  question: condition_prompt,
155
191
  llm: action.llm,
@@ -201,9 +237,9 @@ class DirAiCondition {
201
237
  error = JSON.stringify(err.response.data);
202
238
  }
203
239
  this.logger.error("[AI Condition] error executing action: ", error);
204
- if (falseIntent) {
205
- await this.chatbot.addParameter("flowError", "AiPrompt Error: " + error);
206
- await this.#executeCondition(false, trueIntent, trueIntentAttributes, falseIntent, falseIntentAttributes);
240
+ if (errorIntent) {
241
+ await this.chatbot.addParameter("flowError", "[AI Condition] error executing action: condition label not found in intents list");
242
+ await this.#executeIntent(errorIntent);
207
243
  callback(true);
208
244
  return;
209
245
  }
@@ -214,38 +250,54 @@ class DirAiCondition {
214
250
  winston.debug("DirAiPrompt resbody: ", resbody);
215
251
  answer = resbody.answer;
216
252
  this.logger.native("[AI Condition] answer: ", answer);
253
+
254
+ // if (publicKey === true) {
255
+ // let tokens_usage = {
256
+ // tokens: resbody.usage.total_token,
257
+ // model: json.model
258
+ // }
259
+ // quotasService.updateQuote(this.projectId, this.token, tokens_usage);
260
+ // }
217
261
 
218
262
  await this.#assignAttributes(action, answer);
219
263
 
220
264
  if (answer === "fallback") {
221
- if (falllbackIntent) {
222
- this.#executeIntent(falllbackIntent, () => {
223
- if (callback) {
224
- callback(true);
225
- return;
226
- }
227
- });
265
+ if (fallbackIntent) {
266
+ await this.#executeIntent(fallbackIntent)
267
+ if (callback) {
268
+ callback(true);
269
+ return;
270
+ }
228
271
  }
229
272
  }
230
273
  else {
274
+ let answer_found = null;
231
275
  intents.forEach( i => {
232
276
  if (i.label === answer) {
233
- this.#executeIntent(i.conditionIntentId, () => {
234
- if (callback) {
235
- callback(true);
236
- return;
237
- }
238
- });
277
+ answer_found = i;
239
278
  }
240
279
  });
280
+ if (answer_found) {
281
+ await this.#executeIntent(answer_found.conditionIntentId)
282
+ if (callback) {
283
+ callback(true);
284
+ return;
285
+ }
286
+ }
287
+ else { // if (answer === "fallback") {
288
+ if (fallbackIntent) {
289
+ await this.#executeIntent(fallbackIntent)
290
+ if (callback) {
291
+ callback(true);
292
+ return;
293
+ }
294
+ }
295
+ else {
296
+ this.logger.error("[AI Condition] Fallback connector not found");
297
+ }
298
+ }
241
299
  }
242
300
  this.logger.error("[AI Condition] error executing action: condition label not found in intents list");
243
- if (falseIntent) {
244
- await this.chatbot.addParameter("flowError", "[AI Condition] error executing action: condition label not found in intents list");
245
- await this.#executeCondition(false, trueIntent, trueIntentAttributes, falseIntent, falseIntentAttributes);
246
- callback(true);
247
- return;
248
- }
249
301
  callback();
250
302
  return;
251
303
  }
@@ -255,7 +307,7 @@ class DirAiCondition {
255
307
 
256
308
  async checkMandatoryParameters(action) {
257
309
  return new Promise((resolve, reject) => {
258
- let params = ['question', 'llm', 'model']; // mandatory params
310
+ let params = ['llm', 'model']; // mandatory params
259
311
  params.forEach((p) => {
260
312
  if (!action[p]) {
261
313
  reject(p)
@@ -250,13 +250,13 @@ class DirAiPrompt {
250
250
  answer = resbody.answer;
251
251
  this.logger.native("[AI Prompt] answer: ", answer);
252
252
 
253
- // if (publicKey === true) {
254
- // let tokens_usage = {
255
- // tokens: resbody.usage.total_token,
256
- // model: json.model
257
- // }
258
- // quotasService.updateQuote(this.projectId, this.token, tokens_usage);
259
- // }
253
+ if (publicKey === true) {
254
+ let tokens_usage = {
255
+ tokens: resbody.usage.total_token,
256
+ model: json.model
257
+ }
258
+ quotasService.updateQuote(this.projectId, this.token, tokens_usage);
259
+ }
260
260
 
261
261
  await this.#assignAttributes(action, answer);
262
262
 
@@ -11,10 +11,8 @@ const winston = require('../../utils/winston');
11
11
  const httpUtils = require("../../utils/HttpUtils");
12
12
  const integrationService = require("../../services/IntegrationService");
13
13
  const { Logger } = require("../../Logger");
14
+ const kbService = require("../../services/KbService");
14
15
  const quotasService = require("../../services/QuotasService");
15
- const llmService = require("../../services/LLMService");
16
-
17
-
18
16
 
19
17
  class DirAskGPTV2 {
20
18
 
@@ -99,7 +97,10 @@ class DirAskGPTV2 {
99
97
  "gpt-4o-mini": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, return <NOANS>\n\n==Retrieved context start==\n{context}\n==Retrieved context end==",
100
98
  "gpt-4.1": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, append <NOANS> at the end of the answer\n\n==Retrieved context start==\n{context}\n==Retrieved context end==",
101
99
  "gpt-4.1-mini": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, append <NOANS> at the end of the answer\n\n==Retrieved context start==\n{context}\n==Retrieved context end==",
102
- "gpt-4.1-nano": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, append <NOANS> at the end of the answer\n\n==Retrieved context start==\n{context}\n==Retrieved context end=="
100
+ "gpt-4.1-nano": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, append <NOANS> at the end of the answer\n\n==Retrieved context start==\n{context}\n==Retrieved context end==",
101
+ "gpt-5": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, append <NOANS> at the end of the answer\n\n==Retrieved context start==\n{context}\n==Retrieved context end==",
102
+ "gpt-5-mini": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, append <NOANS> at the end of the answer\n\n==Retrieved context start==\n{context}\n==Retrieved context end==",
103
+ "gpt-5-nano": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, append <NOANS> at the end of the answer\n\n==Retrieved context start==\n{context}\n==Retrieved context end=="
103
104
  }
104
105
 
105
106
  let source = null;
@@ -178,7 +179,7 @@ class DirAskGPTV2 {
178
179
  if (!key) {
179
180
  this.logger.native("[Ask Knowledge Base] OpenAI key not found in Integration. Using shared OpenAI key");
180
181
  winston.verbose("DirAskGPTV2 - Key not found in Integrations. Searching in kb settings...");
181
- key = await llmService.getKeyFromKbSettings(this.projectId, this.token);
182
+ key = await kbService.getKeyFromKbSettings(this.projectId, this.token);
182
183
  }
183
184
 
184
185
  if (!key) {
@@ -375,7 +376,7 @@ class DirAskGPTV2 {
375
376
  }
376
377
  } else {
377
378
  await this.#assignAttributes(action, answer, source);
378
- llmService.addUnansweredQuestion(this.projectId, json.namespace, json.question, this.token).catch((err) => {
379
+ kbService.addUnansweredQuestion(this.projectId, json.namespace, json.question, this.token).catch((err) => {
379
380
  winston.error("DirAskGPTV2 - Error adding unanswered question: ", err);
380
381
  this.logger.warn("[Ask Knowledge Base] Unable to add unanswered question", json.question, "to namespacae", json.namespace);
381
382
  })
@@ -118,7 +118,7 @@ class DirWebRequestV2 {
118
118
  this.logger.native("[Web Request] resbody: ", resbody);
119
119
 
120
120
  if (err) {
121
- this.logger.error("[Web Request] error: ", err);
121
+ this.logger.error("WebRequest error: ", err);
122
122
  winston.log("webRequest error: ", err);
123
123
  if (callback) {
124
124
  if (falseIntent) {