@tiledesk/tiledesk-server 2.18.16 → 2.19.1
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 +6 -1
- package/migrations/1781085026218-vllm-servers-migration.js +58 -0
- package/models/kb_setting.js +4 -0
- package/package.json +2 -2
- package/pubmodules/analytics-publisher/index.js +3 -3
- package/routes/kb.js +16 -11
- package/routes/llm.js +44 -22
- package/services/aiManager.js +34 -21
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
🚀 IN PRODUCTION 🚀
|
|
6
6
|
(https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
|
|
7
7
|
|
|
8
|
-
# 2.
|
|
8
|
+
# 2.19.1
|
|
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
|
+
- Updated tybot-connector to 2.1.1
|
|
12
|
+
|
|
13
|
+
# 2.19.0
|
|
9
14
|
- Added Analytics tracking
|
|
10
15
|
|
|
11
16
|
# 2.18.12
|
|
@@ -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 };
|
package/models/kb_setting.js
CHANGED
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.
|
|
4
|
+
"version": "2.19.1",
|
|
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.
|
|
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/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
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
return res.status(
|
|
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
|
-
|
|
1595
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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) => {
|
package/services/aiManager.js
CHANGED
|
@@ -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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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:
|
|
267
|
-
api_key:
|
|
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
|
-
|
|
276
|
-
|
|
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
|
-
|
|
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) {
|