@tiledesk/tiledesk-server 2.19.0 → 2.19.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@
5
5
  🚀 IN PRODUCTION 🚀
6
6
  (https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
7
7
 
8
+ # 2.19.2
9
+ - Improved vLLM integration in order to support multiple servers
10
+ - Added custom_regex option for .txt and .md file uploaded in a Knowledge Base
11
+ - Improved extension management in order to support markdown files
12
+ - Updated tybot-connector to 2.1.1
13
+
8
14
  # 2.19.0
9
15
  - Added Analytics tracking
10
16
 
@@ -5,6 +5,7 @@ const fs = require('fs');
5
5
  const TEXT_MIME_TYPES = [
6
6
  'text/plain',
7
7
  'text/csv',
8
+ 'text/markdown',
8
9
  'image/svg+xml',
9
10
  'application/xml',
10
11
  'text/xml'
@@ -39,6 +40,9 @@ function areMimeTypesEquivalent(mimeType1, mimeType2) {
39
40
  'image/jpg': ['image/jpeg'],
40
41
  'application/x-zip-compressed': ['application/zip'],
41
42
  'application/zip': ['application/x-zip-compressed'],
43
+ 'text/markdown': ['text/plain', 'text/x-markdown'],
44
+ 'text/plain': ['text/markdown', 'text/x-markdown'],
45
+ 'text/x-markdown': ['text/markdown', 'text/plain'],
42
46
  };
43
47
 
44
48
  // Check if m1 is an alias of m2 or vice versa
@@ -0,0 +1,58 @@
1
+ const winston = require('../config/winston');
2
+ const Integration = require('../models/integrations');
3
+
4
+ function getServerName(url) {
5
+ if (!url) return null;
6
+
7
+ try {
8
+ const hostname = new URL(url).hostname;
9
+
10
+ if (hostname === 'localhost') return 'localhost';
11
+
12
+ const parts = hostname.split('.');
13
+
14
+ if (parts.length >= 2) {
15
+ return parts[parts.length - 2];
16
+ }
17
+
18
+ return hostname;
19
+
20
+ } catch (err) {
21
+ winston.error("Error getting server name from URL: ", err);
22
+ return url;
23
+ }
24
+ }
25
+
26
+ async function up() {
27
+ try {
28
+ let integrations = await Integration.find({ name: 'vllm' });
29
+
30
+ for (const integration of integrations) {
31
+
32
+ if (integration.value?.servers) continue;
33
+
34
+ if (!integration.value?.url) continue;
35
+
36
+ integration.value.servers = [{
37
+ name: getServerName(integration.value.url) || "default",
38
+ url: integration.value.url,
39
+ apikey: integration.value.apikey || "",
40
+ models: integration.value.models || []
41
+ }];
42
+
43
+ delete integration.value.url;
44
+ delete integration.value.apikey;
45
+ delete integration.value.models;
46
+ delete integration.value.token;
47
+
48
+ integration.markModified('value');
49
+
50
+ await integration.save();
51
+ winston.info(`VLLM server ${integration.value.url} migrated to ${integration.value.servers[0].name}`);
52
+ }
53
+ } catch (err) {
54
+ winston.error("VLLM servers migration error: ", err);
55
+ }
56
+ }
57
+
58
+ module.exports = { up };
@@ -203,6 +203,10 @@ var KBSchema = new Schema({
203
203
  type: Boolean,
204
204
  default: false,
205
205
  required: false
206
+ },
207
+ chunk_regex: {
208
+ type: String,
209
+ required: false
206
210
  }
207
211
  }, {
208
212
  timestamps: true
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiledesk/tiledesk-server",
3
3
  "description": "The Tiledesk server module",
4
- "version": "2.19.0",
4
+ "version": "2.19.2",
5
5
  "scripts": {
6
6
  "start": "node ./bin/www",
7
7
  "pretest": "mongodb-runner start",
@@ -49,7 +49,7 @@
49
49
  "@tiledesk/tiledesk-rasa-connector": "^1.0.10",
50
50
  "@tiledesk/tiledesk-sms-connector": "^0.1.13",
51
51
  "@tiledesk/tiledesk-telegram-connector": "^0.1.14",
52
- "@tiledesk/tiledesk-tybot-connector": "^2.1.0",
52
+ "@tiledesk/tiledesk-tybot-connector": "^2.1.1",
53
53
  "@tiledesk/tiledesk-voice-twilio-connector": "^0.3.2",
54
54
  "@tiledesk/tiledesk-vxml-connector": "^0.1.91",
55
55
  "@tiledesk/tiledesk-whatsapp-connector": "1.0.26",
@@ -265,7 +265,7 @@ function listen() {
265
265
  // invited_by string|null
266
266
  // TODO: check
267
267
  authEvent.on("project_user.invite", function (event) {
268
- console.log("project_user.invite", event);
268
+ // console.log("project_user.invite", event);
269
269
  var pu = event.savedProject_userPopulated || event.updatedPuserPopulated;
270
270
  if (!pu) return;
271
271
 
@@ -294,7 +294,7 @@ function listen() {
294
294
  // new_status 'available'|'unavailable'|'busy'
295
295
  // TODO: check
296
296
  authEvent.on("project_user.update.agent", function (event) {
297
- console.log("project_user.update.agent", event);
297
+ // console.log("project_user.update.agent", event);
298
298
  var pu = event.updatedProject_userPopulated;
299
299
  if (!pu) return;
300
300
  if (pu.user_available === undefined) return; // not a status-change update
@@ -326,7 +326,7 @@ function listen() {
326
326
  // assigned_by string|null
327
327
  // routing_type string
328
328
  requestEvent.on("request.department.update", function (requestComplete) {
329
- console.log("request.department.update", requestComplete);
329
+ // console.log("request.department.update", requestComplete);
330
330
  var dept = requestComplete.department;
331
331
 
332
332
  var deptId = departmentId(dept);
package/routes/filesp.js CHANGED
@@ -132,6 +132,9 @@ function areMimeTypesEquivalent(mimeType1, mimeType2) {
132
132
  'image/jpg': ['image/jpeg'],
133
133
  'application/x-zip-compressed': ['application/zip'],
134
134
  'application/zip': ['application/x-zip-compressed'],
135
+ 'text/markdown': ['text/plain', 'text/x-markdown'],
136
+ 'text/plain': ['text/markdown', 'text/x-markdown'],
137
+ 'text/x-markdown': ['text/markdown', 'text/plain'],
135
138
  };
136
139
 
137
140
  // Check if m1 is an alias of m2 or vice versa
package/routes/kb.js CHANGED
@@ -374,7 +374,7 @@ router.post('/qa', async (req, res) => {
374
374
 
375
375
  let model;
376
376
  try {
377
- model = await aiManager.resolveLLMConfig(id_project, data.llm, data.model);
377
+ model = await aiManager.resolveLLMConfig(id_project, data.llm, data.model, data.vllmServer);
378
378
  } catch (err) {
379
379
  let errorCode = err?.code ?? 500;
380
380
  return res.status(errorCode).send({ success: false, error: err.error });
@@ -1583,20 +1583,21 @@ router.get('/', async (req, res) => {
1583
1583
 
1584
1584
  router.get('/:kb_id', async (req, res) => {
1585
1585
 
1586
+ let project_id = req.projectid;
1586
1587
  let kb_id = req.params.kb_id;
1587
1588
 
1588
- KB.findById(kb_id, (err, kb) => {
1589
- if (err) {
1590
- winston.error("Find kb by id error: ", err);
1591
- return res.status(500).send({ success: false, error: err });
1589
+ try {
1590
+ let content = await KB.findOne({ id_project: project_id, _id: kb_id })
1591
+ if (!content) {
1592
+ return res.status(404).send({ success: false, error: "Content not found with id " + kb_id + " for project " + project_id });
1592
1593
  }
1594
+ return res.status(200).send(content);
1593
1595
 
1594
- if (!kb) {
1595
- return res.status(404).send({ success: false, error: "Content not found with id " + kb_id });
1596
- }
1596
+ } catch (err) {
1597
+ winston.error("Find kb by id error: ", err);
1598
+ return res.status(500).send({ success: false, error: "An error occurred retrieving the content" });
1599
+ }
1597
1600
 
1598
- return res.status(200).send(kb);
1599
- })
1600
1601
  })
1601
1602
 
1602
1603
  router.post('/', async (req, res) => {
@@ -1604,7 +1605,7 @@ router.post('/', async (req, res) => {
1604
1605
  const id_project = req.projectid;
1605
1606
  const project = req.project
1606
1607
 
1607
- const { name, type, source, content, refresh_rate, scrape_type, scrape_options, tags, situated_context } = req.body;
1608
+ const { name, type, source, content, refresh_rate, scrape_type, scrape_options, tags, situated_context, chunk_regex } = req.body;
1608
1609
  const namespace_id = req.body?.namespace;
1609
1610
 
1610
1611
  if (!namespace_id) {
@@ -1651,6 +1652,9 @@ router.post('/', async (req, res) => {
1651
1652
  new_kb.scrape_options = aiManager.setDefaultScrapeOptions();
1652
1653
  }
1653
1654
  }
1655
+ if (type === 'regex_custom') {
1656
+ new_kb.chunk_regex = chunk_regex;
1657
+ }
1654
1658
 
1655
1659
  if (tags && Array.isArray(tags) && tags.every(tag => typeof tag === "string")) {
1656
1660
  new_kb.tags = tags;
@@ -1694,6 +1698,7 @@ router.post('/', async (req, res) => {
1694
1698
  ...(saved_kb.scrape_type && { scrape_type: saved_kb.scrape_type }),
1695
1699
  ...(saved_kb.scrape_options && { parameters_scrape_type_4: saved_kb.scrape_options }),
1696
1700
  ...(saved_kb.tags && { tags: saved_kb.tags }),
1701
+ ...(saved_kb.chunk_regex && { chunk_regex: saved_kb.chunk_regex })
1697
1702
  }
1698
1703
 
1699
1704
  winston.debug("json: ", json);
package/routes/llm.js CHANGED
@@ -3,6 +3,7 @@ var router = express.Router();
3
3
  var winston = require('../config/winston');
4
4
  let Integration = require('../models/integrations');
5
5
  const aiService = require('../services/aiService');
6
+ const aiManager = require('../services/aiManager');
6
7
  const multer = require('multer');
7
8
  const fileUtils = require('../utils/fileUtils');
8
9
  const { MODELS_MULTIPLIER } = require('../utils/aiUtils');
@@ -24,36 +25,47 @@ router.post('/preview', async (req, res) => {
24
25
  let body = req.body;
25
26
  let key;
26
27
  let publicKey = false;
28
+ let integration;
29
+ let vllmConfig;
27
30
 
28
31
  if (!body.llm) {
29
32
  return res.status(400).send({ success: false, error: "Missing required parameter 'llm'" });
30
33
  }
31
34
 
32
- let integration = await Integration.findOne({ id_project: id_project, name: body.llm }).catch((err) => {
33
- winston.error("Error finding integration with name: ", body.llm);
34
- return res.status(500).send({ success: false, error: "Error finding integration for " + body.llm});
35
- })
36
-
37
- if (!integration) {
38
- winston.verbose("Integration not found for " + body.llm)
39
- if (body.llm === "openai") {
40
- winston.verbose("Try to retrieve shared OpenAI key")
41
- if (!process.env.GPTKEY) {
42
- winston.error("Shared key for OpenAI not configured.");
43
- return res.status(404).send({ success: false, error: "No key found for " + body.llm });
44
- }
45
- key = process.env.GPTKEY;
46
- publicKey = true;
47
- winston.verbose("Using shared OpenAI key as fallback.");
48
- } else {
49
- winston.verbose("Integration for " + body.llm + " not found.")
50
- return res.status(404).send({ success: false, error: "Integration for " + body.llm + " not found." })
35
+ if (body.llm === 'vllm') {
36
+ try {
37
+ vllmConfig = await aiManager.resolveLLMConfig(id_project, body.llm, body.model, body.vllmServer);
38
+ key = vllmConfig.api_key;
39
+ } catch (err) {
40
+ return res.status(err.code ?? 500).send({ success: false, error: err.error });
51
41
  }
52
42
  } else {
53
- if (!integration?.value?.apikey && body.llm !== "ollama") {
54
- return res.status(422).send({ success: false, error: "The key provided for " + body.llm + " is not valid or undefined." });
43
+ integration = await Integration.findOne({ id_project: id_project, name: body.llm }).catch((err) => {
44
+ winston.error("Error finding integration with name: ", body.llm);
45
+ return res.status(500).send({ success: false, error: "Error finding integration for " + body.llm});
46
+ })
47
+
48
+ if (!integration) {
49
+ winston.verbose("Integration not found for " + body.llm)
50
+ if (body.llm === "openai") {
51
+ winston.verbose("Try to retrieve shared OpenAI key")
52
+ if (!process.env.GPTKEY) {
53
+ winston.error("Shared key for OpenAI not configured.");
54
+ return res.status(404).send({ success: false, error: "No key found for " + body.llm });
55
+ }
56
+ key = process.env.GPTKEY;
57
+ publicKey = true;
58
+ winston.verbose("Using shared OpenAI key as fallback.");
59
+ } else {
60
+ winston.verbose("Integration for " + body.llm + " not found.")
61
+ return res.status(404).send({ success: false, error: "Integration for " + body.llm + " not found." })
62
+ }
63
+ } else {
64
+ if (!integration?.value?.apikey && body.llm !== "ollama") {
65
+ return res.status(422).send({ success: false, error: "The key provided for " + body.llm + " is not valid or undefined." });
66
+ }
67
+ key = integration.value.apikey;
55
68
  }
56
- key = integration.value.apikey;
57
69
  }
58
70
 
59
71
  let obj = { createdAt: new Date() };
@@ -89,6 +101,16 @@ router.post('/preview', async (req, res) => {
89
101
  json.stream = false;
90
102
  }
91
103
 
104
+ if (body.llm === 'vllm') {
105
+ json.llm_key = key || "";
106
+ json.model = {
107
+ name: body.model,
108
+ url: vllmConfig.url,
109
+ provider: 'vllm'
110
+ };
111
+ json.stream = false;
112
+ }
113
+
92
114
  winston.debug("Preview LLM json: ", json);
93
115
 
94
116
  aiService.askllm(json).then((response) => {
@@ -250,41 +250,54 @@ class AiManager {
250
250
  })
251
251
  }
252
252
 
253
- async resolveLLMConfig(id_project, provider = 'openai', model) {
253
+ async resolveLLMConfig(id_project, provider = 'openai', model, vllmServer = undefined) {
254
254
 
255
255
  if (provider === 'ollama' || provider === 'vllm') {
256
- try {
257
- const integration = await integrationService.getIntegration(id_project, provider);
258
-
259
- if (!integration?.value?.url) {
260
- throw { code: 422, error: `Server url for ${provider} is empty or invalid`}
261
- }
256
+ const integration = await integrationService.getIntegration(id_project, provider);
257
+ if (!integration?.value) {
258
+ throw { code: 422, error: `${provider} integration not found` };
259
+ }
260
+
261
+ const value = integration.value;
262
262
 
263
+ if (provider === 'vllm' && Array.isArray(value.servers)) {
264
+ if (!vllmServer) {
265
+ throw { code: 422, error: "vllmServer attribute is undefined" };
266
+ }
267
+ const server = value.servers.find(s => s.name === vllmServer);
268
+ if (!server) {
269
+ throw { code: 422, error: `vllm server '${vllmServer}' not found` };
270
+ }
271
+ if (!server.url) {
272
+ throw { code: 422, error: "Server url for vllm is empty or invalid" };
273
+ }
263
274
  return {
264
275
  provider,
265
276
  name: model,
266
- url: integration.value.url,
267
- api_key: integration.value.apikey || ""
268
- }
269
-
270
- } catch (err) {
271
- throw { code: err.code, error: err.error }
277
+ url: server.url,
278
+ api_key: server.apikey || ""
279
+ };
272
280
  }
273
- }
274
281
 
275
- try {
276
- let key = await integrationService.getKeyFromIntegration(id_project, provider)
282
+ if (!value.url) {
283
+ throw { code: 422, error: `Server url for ${provider} is empty or invalid` };
284
+ }
277
285
 
278
286
  return {
279
287
  provider,
280
288
  name: model,
281
- api_key: key
282
- }
283
-
284
- } catch (err) {
285
- throw { code: err.code, error: err.error }
289
+ url: value.url,
290
+ api_key: value.apikey || ""
291
+ };
286
292
  }
287
293
 
294
+ const key = await integrationService.getKeyFromIntegration(id_project, provider);
295
+
296
+ return {
297
+ provider,
298
+ name: model,
299
+ api_key: key
300
+ };
288
301
  }
289
302
 
290
303
  async checkQuotaAvailability(quoteManager, project, ncontents) {