@exulu/backend 1.16.0 → 1.17.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/CHANGELOG.md +2 -35
- package/dist/index.cjs +316 -245
- package/dist/index.d.cts +24 -15
- package/dist/index.d.ts +24 -15
- package/dist/index.js +312 -242
- package/documentation/logging.md +122 -0
- package/documentation/otel.md +145 -0
- package/package.json +15 -4
- package/lms.md +0 -3
package/dist/index.js
CHANGED
|
@@ -175,87 +175,48 @@ import pgvector2 from "pgvector/knex";
|
|
|
175
175
|
import "bullmq";
|
|
176
176
|
import { v4 as uuidv4 } from "uuid";
|
|
177
177
|
var bullmqDecorator = async ({
|
|
178
|
+
queue,
|
|
178
179
|
label,
|
|
179
|
-
type,
|
|
180
|
-
workflow,
|
|
181
180
|
embedder,
|
|
182
181
|
inputs,
|
|
183
|
-
queue,
|
|
184
182
|
user,
|
|
185
|
-
|
|
186
|
-
session,
|
|
187
|
-
configuration,
|
|
188
|
-
updater,
|
|
189
|
-
context,
|
|
190
|
-
steps,
|
|
191
|
-
source,
|
|
192
|
-
documents,
|
|
183
|
+
role,
|
|
193
184
|
trigger,
|
|
194
|
-
|
|
185
|
+
workflow,
|
|
186
|
+
item,
|
|
187
|
+
context
|
|
195
188
|
}) => {
|
|
189
|
+
if (embedder && workflow) {
|
|
190
|
+
throw new Error("Cannot have both embedder and workflow in the same job.");
|
|
191
|
+
}
|
|
192
|
+
if (workflow && item) {
|
|
193
|
+
throw new Error("Cannot have both workflow and item in the same job.");
|
|
194
|
+
}
|
|
195
|
+
let type = "embedder";
|
|
196
|
+
if (workflow) {
|
|
197
|
+
type = "workflow";
|
|
198
|
+
}
|
|
196
199
|
const redisId = uuidv4();
|
|
197
200
|
const job = await queue.add(
|
|
198
201
|
`${embedder || workflow}`,
|
|
199
202
|
{
|
|
200
|
-
|
|
203
|
+
label,
|
|
201
204
|
...embedder && { embedder },
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
...
|
|
205
|
-
...
|
|
206
|
-
...source && { source },
|
|
207
|
-
...documents && { documents },
|
|
208
|
-
...steps && { steps },
|
|
205
|
+
type: `${type}`,
|
|
206
|
+
inputs,
|
|
207
|
+
...user && { user },
|
|
208
|
+
...role && { role },
|
|
209
209
|
...trigger && { trigger },
|
|
210
|
+
...workflow && { workflow },
|
|
210
211
|
...item && { item },
|
|
211
|
-
|
|
212
|
-
user,
|
|
213
|
-
inputs,
|
|
214
|
-
label,
|
|
215
|
-
session
|
|
212
|
+
...context && { context }
|
|
216
213
|
},
|
|
217
214
|
{
|
|
218
215
|
jobId: redisId
|
|
219
216
|
}
|
|
220
217
|
);
|
|
221
|
-
const { db: db3 } = await postgresClient();
|
|
222
|
-
const now = /* @__PURE__ */ new Date();
|
|
223
|
-
console.log("[EXULU] scheduling new job", inputs);
|
|
224
|
-
const insertData = {
|
|
225
|
-
name: `${label}`,
|
|
226
|
-
redis: job.id,
|
|
227
|
-
status: "waiting",
|
|
228
|
-
type,
|
|
229
|
-
inputs,
|
|
230
|
-
agent,
|
|
231
|
-
item,
|
|
232
|
-
createdAt: now,
|
|
233
|
-
updatedAt: now,
|
|
234
|
-
user,
|
|
235
|
-
session,
|
|
236
|
-
...embedder && { embedder },
|
|
237
|
-
...workflow && { workflow },
|
|
238
|
-
...configuration && { configuration },
|
|
239
|
-
...steps && { steps },
|
|
240
|
-
...updater && { updater },
|
|
241
|
-
...context && { context },
|
|
242
|
-
...source && { source },
|
|
243
|
-
...documents && { documents: documents.map((doc2) => doc2.id) },
|
|
244
|
-
...trigger && { trigger }
|
|
245
|
-
};
|
|
246
|
-
await db3("jobs").insert(insertData).onConflict("redis").merge({
|
|
247
|
-
...insertData,
|
|
248
|
-
updatedAt: now
|
|
249
|
-
// Only updatedAt changes on updates
|
|
250
|
-
});
|
|
251
|
-
const doc = await db3.from("jobs").where({ redis: job.id }).first();
|
|
252
|
-
if (!doc?.id) {
|
|
253
|
-
throw new Error("Failed to get job ID after insert/update");
|
|
254
|
-
}
|
|
255
|
-
console.log("[EXULU] created job", doc?.id);
|
|
256
218
|
return {
|
|
257
219
|
...job,
|
|
258
|
-
id: doc?.id,
|
|
259
220
|
redis: job.id
|
|
260
221
|
};
|
|
261
222
|
};
|
|
@@ -427,6 +388,7 @@ var ExuluEvalUtils = {
|
|
|
427
388
|
// src/registry/classes.ts
|
|
428
389
|
import CryptoJS from "crypto-js";
|
|
429
390
|
import "express";
|
|
391
|
+
import "@opentelemetry/api";
|
|
430
392
|
function sanitizeToolName(name) {
|
|
431
393
|
if (typeof name !== "string") return "";
|
|
432
394
|
let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
@@ -602,6 +564,9 @@ var ExuluAgent = class {
|
|
|
602
564
|
if (prompt && message) {
|
|
603
565
|
throw new Error("Message and prompt cannot be provided at the same time.");
|
|
604
566
|
}
|
|
567
|
+
if (!prompt && !message) {
|
|
568
|
+
throw new Error("Prompt or message is required for generating.");
|
|
569
|
+
}
|
|
605
570
|
const model = this.model.create({
|
|
606
571
|
apiKey: providerApiKey
|
|
607
572
|
});
|
|
@@ -621,28 +586,52 @@ var ExuluAgent = class {
|
|
|
621
586
|
}
|
|
622
587
|
console.log("[EXULU] Model provider key", providerApiKey);
|
|
623
588
|
console.log("[EXULU] Tool configs", toolConfigs);
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
589
|
+
if (prompt) {
|
|
590
|
+
const { text } = await generateText({
|
|
591
|
+
model,
|
|
592
|
+
// Should be a LanguageModelV1
|
|
593
|
+
system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
|
|
594
|
+
prompt,
|
|
595
|
+
maxRetries: 2,
|
|
596
|
+
tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
|
|
597
|
+
stopWhen: [stepCountIs(5)]
|
|
598
|
+
});
|
|
599
|
+
if (statistics) {
|
|
600
|
+
await updateStatistic({
|
|
601
|
+
name: "count",
|
|
602
|
+
label: statistics.label,
|
|
603
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
604
|
+
trigger: statistics.trigger,
|
|
605
|
+
count: 1,
|
|
606
|
+
user,
|
|
607
|
+
role
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return text;
|
|
611
|
+
}
|
|
612
|
+
if (messages) {
|
|
613
|
+
const { text } = await generateText({
|
|
614
|
+
model,
|
|
615
|
+
// Should be a LanguageModelV1
|
|
616
|
+
system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
|
|
617
|
+
messages: convertToModelMessages(messages),
|
|
618
|
+
maxRetries: 2,
|
|
619
|
+
tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
|
|
620
|
+
stopWhen: [stepCountIs(5)]
|
|
643
621
|
});
|
|
622
|
+
if (statistics) {
|
|
623
|
+
await updateStatistic({
|
|
624
|
+
name: "count",
|
|
625
|
+
label: statistics.label,
|
|
626
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
627
|
+
trigger: statistics.trigger,
|
|
628
|
+
count: 1,
|
|
629
|
+
user,
|
|
630
|
+
role
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
return text;
|
|
644
634
|
}
|
|
645
|
-
return text;
|
|
646
635
|
};
|
|
647
636
|
generateStream = async ({ express: express3, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
|
|
648
637
|
if (!this.model) {
|
|
@@ -674,7 +663,7 @@ var ExuluAgent = class {
|
|
|
674
663
|
const result = streamText({
|
|
675
664
|
model,
|
|
676
665
|
// Should be a LanguageModelV1
|
|
677
|
-
messages:
|
|
666
|
+
messages: convertToModelMessages(messages),
|
|
678
667
|
system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
|
|
679
668
|
maxRetries: 2,
|
|
680
669
|
tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
|
|
@@ -1036,8 +1025,36 @@ var ExuluContext = class {
|
|
|
1036
1025
|
const tableExists = await db3.schema.hasTable(this.getTableName());
|
|
1037
1026
|
return tableExists;
|
|
1038
1027
|
};
|
|
1039
|
-
async
|
|
1040
|
-
|
|
1028
|
+
createAndUpsertEmbeddings = async (item, user, statistics, role) => {
|
|
1029
|
+
const { db: db3 } = await postgresClient();
|
|
1030
|
+
const { id: source, chunks } = await this.embedder.generateFromDocument({
|
|
1031
|
+
...item,
|
|
1032
|
+
id: item.id
|
|
1033
|
+
}, {
|
|
1034
|
+
label: statistics.label || this.name,
|
|
1035
|
+
trigger: statistics.trigger || "agent"
|
|
1036
|
+
}, user, role);
|
|
1037
|
+
const exists = await db3.schema.hasTable(this.getChunksTableName());
|
|
1038
|
+
if (!exists) {
|
|
1039
|
+
await this.createChunksTable();
|
|
1040
|
+
}
|
|
1041
|
+
await db3.from(this.getChunksTableName()).where({ source }).delete();
|
|
1042
|
+
await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
|
|
1043
|
+
source,
|
|
1044
|
+
content: chunk.content,
|
|
1045
|
+
chunk_index: chunk.index,
|
|
1046
|
+
embedding: pgvector2.toSql(chunk.vector)
|
|
1047
|
+
})));
|
|
1048
|
+
await db3.from(this.getTableName()).where({ id: item.id }).update({
|
|
1049
|
+
embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1050
|
+
}).returning("id");
|
|
1051
|
+
return {
|
|
1052
|
+
id: item.id,
|
|
1053
|
+
chunks: chunks?.length || 0
|
|
1054
|
+
};
|
|
1055
|
+
};
|
|
1056
|
+
async updateItem(user, item, role, trigger) {
|
|
1057
|
+
if (!item.id) {
|
|
1041
1058
|
throw new Error("Id is required for updating an item.");
|
|
1042
1059
|
}
|
|
1043
1060
|
const { db: db3 } = await postgresClient();
|
|
@@ -1054,51 +1071,37 @@ var ExuluContext = class {
|
|
|
1054
1071
|
delete item.created_at;
|
|
1055
1072
|
delete item.upsert;
|
|
1056
1073
|
item.updated_at = db3.fn.now();
|
|
1057
|
-
const result = await db3.from(this.getTableName()).where({ id }).update(item).returning("id");
|
|
1074
|
+
const result = await db3.from(this.getTableName()).where({ id: item.id }).update(item).returning("id");
|
|
1058
1075
|
if (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always") {
|
|
1059
1076
|
if (this.embedder.queue?.name) {
|
|
1060
1077
|
console.log("[EXULU] embedder is in queue mode, scheduling job.");
|
|
1061
1078
|
const job = await bullmqDecorator({
|
|
1062
|
-
label:
|
|
1079
|
+
label: `${this.embedder.name}`,
|
|
1063
1080
|
embedder: this.embedder.id,
|
|
1064
|
-
|
|
1081
|
+
context: this.id,
|
|
1065
1082
|
inputs: item,
|
|
1083
|
+
item: item.id,
|
|
1066
1084
|
queue: this.embedder.queue,
|
|
1067
|
-
user
|
|
1085
|
+
user,
|
|
1086
|
+
role,
|
|
1087
|
+
trigger: trigger || "agent"
|
|
1068
1088
|
});
|
|
1069
1089
|
return {
|
|
1070
1090
|
id: result[0].id,
|
|
1071
1091
|
job: job.id
|
|
1072
1092
|
};
|
|
1073
1093
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
},
|
|
1078
|
-
label: this.name,
|
|
1079
|
-
trigger: "agent"
|
|
1080
|
-
}, user, role);
|
|
1081
|
-
const exists = await db3.schema.hasTable(this.getChunksTableName());
|
|
1082
|
-
if (!exists) {
|
|
1083
|
-
await this.createChunksTable();
|
|
1084
|
-
}
|
|
1085
|
-
await db3.from(this.getChunksTableName()).where({ source }).delete();
|
|
1086
|
-
await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
|
|
1087
|
-
source,
|
|
1088
|
-
content: chunk.content,
|
|
1089
|
-
chunk_index: chunk.index,
|
|
1090
|
-
embedding: pgvector2.toSql(chunk.vector)
|
|
1091
|
-
})));
|
|
1092
|
-
await db3.from(this.getTableName()).where({ id }).update({
|
|
1093
|
-
embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1094
|
-
}).returning("id");
|
|
1094
|
+
await this.createAndUpsertEmbeddings(item, user, {
|
|
1095
|
+
label: this.embedder.name,
|
|
1096
|
+
trigger: trigger || "agent"
|
|
1097
|
+
}, role);
|
|
1095
1098
|
}
|
|
1096
1099
|
return {
|
|
1097
1100
|
id: result[0].id,
|
|
1098
1101
|
job: void 0
|
|
1099
1102
|
};
|
|
1100
1103
|
}
|
|
1101
|
-
async insertItem(user, item, upsert = false, role) {
|
|
1104
|
+
async insertItem(user, item, upsert = false, role, trigger) {
|
|
1102
1105
|
if (!item.name) {
|
|
1103
1106
|
throw new Error("Name field is required.");
|
|
1104
1107
|
}
|
|
@@ -1109,14 +1112,20 @@ var ExuluContext = class {
|
|
|
1109
1112
|
throw new Error("Item with external id " + item.external_id + " already exists.");
|
|
1110
1113
|
}
|
|
1111
1114
|
if (existingItem && upsert) {
|
|
1112
|
-
await this.updateItem(user,
|
|
1115
|
+
await this.updateItem(user, {
|
|
1116
|
+
...item,
|
|
1117
|
+
id: existingItem.id
|
|
1118
|
+
}, role, trigger);
|
|
1113
1119
|
return existingItem.id;
|
|
1114
1120
|
}
|
|
1115
1121
|
}
|
|
1116
1122
|
if (upsert && item.id) {
|
|
1117
1123
|
const existingItem = await db3.from(this.getTableName()).where({ id: item.id }).first();
|
|
1118
1124
|
if (existingItem && upsert) {
|
|
1119
|
-
await this.updateItem(user,
|
|
1125
|
+
await this.updateItem(user, {
|
|
1126
|
+
...item,
|
|
1127
|
+
id: existingItem.id
|
|
1128
|
+
}, role, trigger);
|
|
1120
1129
|
return existingItem.id;
|
|
1121
1130
|
}
|
|
1122
1131
|
}
|
|
@@ -1141,12 +1150,15 @@ var ExuluContext = class {
|
|
|
1141
1150
|
if (this.embedder.queue?.name) {
|
|
1142
1151
|
console.log("[EXULU] embedder is in queue mode, scheduling job.");
|
|
1143
1152
|
const job = await bullmqDecorator({
|
|
1144
|
-
label:
|
|
1153
|
+
label: `${this.embedder.name}`,
|
|
1145
1154
|
embedder: this.embedder.id,
|
|
1146
|
-
|
|
1155
|
+
context: this.id,
|
|
1147
1156
|
inputs: item,
|
|
1157
|
+
item: item.id,
|
|
1148
1158
|
queue: this.embedder.queue,
|
|
1149
|
-
user
|
|
1159
|
+
user,
|
|
1160
|
+
role,
|
|
1161
|
+
trigger: trigger || "agent"
|
|
1150
1162
|
});
|
|
1151
1163
|
return {
|
|
1152
1164
|
id: result[0].id,
|
|
@@ -1154,27 +1166,13 @@ var ExuluContext = class {
|
|
|
1154
1166
|
};
|
|
1155
1167
|
}
|
|
1156
1168
|
console.log("[EXULU] embedder is not in queue mode, calculating vectors directly.");
|
|
1157
|
-
|
|
1169
|
+
await this.createAndUpsertEmbeddings({
|
|
1158
1170
|
...item,
|
|
1159
1171
|
id: result[0].id
|
|
1160
|
-
}, {
|
|
1161
|
-
label: this.name,
|
|
1162
|
-
trigger: "agent"
|
|
1163
|
-
},
|
|
1164
|
-
const exists = await db3.schema.hasTable(this.getChunksTableName());
|
|
1165
|
-
if (!exists) {
|
|
1166
|
-
await this.createChunksTable();
|
|
1167
|
-
}
|
|
1168
|
-
console.log("[EXULU] Inserting chunks.");
|
|
1169
|
-
await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
|
|
1170
|
-
source,
|
|
1171
|
-
content: chunk.content,
|
|
1172
|
-
chunk_index: chunk.index,
|
|
1173
|
-
embedding: pgvector2.toSql(chunk.vector)
|
|
1174
|
-
})));
|
|
1175
|
-
await db3.from(this.getTableName()).where({ id: result[0].id }).update({
|
|
1176
|
-
embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1177
|
-
}).returning("id");
|
|
1172
|
+
}, user, {
|
|
1173
|
+
label: this.embedder.name,
|
|
1174
|
+
trigger: trigger || "agent"
|
|
1175
|
+
}, role);
|
|
1178
1176
|
}
|
|
1179
1177
|
return {
|
|
1180
1178
|
id: result[0].id,
|
|
@@ -1875,6 +1873,7 @@ import { zerialize } from "zodex";
|
|
|
1875
1873
|
|
|
1876
1874
|
// src/bullmq/queues.ts
|
|
1877
1875
|
import { Queue as Queue3 } from "bullmq";
|
|
1876
|
+
import { BullMQOtel } from "bullmq-otel";
|
|
1878
1877
|
var ExuluQueues = class {
|
|
1879
1878
|
queues;
|
|
1880
1879
|
constructor() {
|
|
@@ -1892,7 +1891,13 @@ var ExuluQueues = class {
|
|
|
1892
1891
|
console.error(`[EXULU] no redis server configured, but you are trying to use a queue ( ${name}), likely in an agent or embedder (look for ExuluQueues.use() ).`);
|
|
1893
1892
|
throw new Error(`[EXULU] no redis server configured.`);
|
|
1894
1893
|
}
|
|
1895
|
-
const newQueue = new Queue3(
|
|
1894
|
+
const newQueue = new Queue3(
|
|
1895
|
+
`${name}`,
|
|
1896
|
+
{
|
|
1897
|
+
connection: redisServer,
|
|
1898
|
+
telemetry: new BullMQOtel("simple-guide")
|
|
1899
|
+
}
|
|
1900
|
+
);
|
|
1896
1901
|
this.queues.push(newQueue);
|
|
1897
1902
|
return newQueue;
|
|
1898
1903
|
}
|
|
@@ -3679,6 +3684,7 @@ Intelligence Management Platform
|
|
|
3679
3684
|
import OpenAI from "openai";
|
|
3680
3685
|
import fs2 from "fs";
|
|
3681
3686
|
import { randomUUID } from "crypto";
|
|
3687
|
+
import "@opentelemetry/api";
|
|
3682
3688
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
3683
3689
|
var global_queues = {
|
|
3684
3690
|
logs_cleaner: "logs-cleaner"
|
|
@@ -3717,7 +3723,7 @@ var createRecurringJobs = async () => {
|
|
|
3717
3723
|
console.table(recurringJobSchedulersLogs);
|
|
3718
3724
|
return queue;
|
|
3719
3725
|
};
|
|
3720
|
-
var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
3726
|
+
var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer) => {
|
|
3721
3727
|
const routeLogs = [];
|
|
3722
3728
|
var corsOptions = {
|
|
3723
3729
|
origin: "*",
|
|
@@ -3811,6 +3817,16 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
|
3811
3817
|
express.json({ limit: REQUEST_SIZE_LIMIT }),
|
|
3812
3818
|
expressMiddleware(server, {
|
|
3813
3819
|
context: async ({ req }) => {
|
|
3820
|
+
logger.info("================");
|
|
3821
|
+
logger.info({
|
|
3822
|
+
message: "Incoming Request",
|
|
3823
|
+
method: req.method,
|
|
3824
|
+
path: req.path,
|
|
3825
|
+
requestId: "req-" + Date.now(),
|
|
3826
|
+
ipAddress: req.ip,
|
|
3827
|
+
userAgent: req.get("User-Agent")
|
|
3828
|
+
});
|
|
3829
|
+
logger.info("================");
|
|
3814
3830
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3815
3831
|
if (!authenticationResult.user?.id) {
|
|
3816
3832
|
throw new Error(authenticationResult.message);
|
|
@@ -4197,7 +4213,15 @@ Mood: friendly and intelligent.
|
|
|
4197
4213
|
if (!exists) {
|
|
4198
4214
|
await context.createItemsTable();
|
|
4199
4215
|
}
|
|
4200
|
-
const result = await context.updateItem(
|
|
4216
|
+
const result = await context.updateItem(
|
|
4217
|
+
authenticationResult.user.id,
|
|
4218
|
+
{
|
|
4219
|
+
...req.body,
|
|
4220
|
+
id: req.params.id
|
|
4221
|
+
},
|
|
4222
|
+
authenticationResult.user.role?.id,
|
|
4223
|
+
authenticationResult.user.type === "api" ? "api" : "user"
|
|
4224
|
+
);
|
|
4201
4225
|
res.status(200).json({
|
|
4202
4226
|
message: "Item updated successfully.",
|
|
4203
4227
|
id: result
|
|
@@ -4244,7 +4268,13 @@ Mood: friendly and intelligent.
|
|
|
4244
4268
|
await context.createItemsTable();
|
|
4245
4269
|
}
|
|
4246
4270
|
console.log("[EXULU] inserting item", req.body);
|
|
4247
|
-
const result = await context.insertItem(
|
|
4271
|
+
const result = await context.insertItem(
|
|
4272
|
+
authenticationResult.user.id,
|
|
4273
|
+
req.body,
|
|
4274
|
+
!!req.body.upsert,
|
|
4275
|
+
authenticationResult.user.role?.id,
|
|
4276
|
+
authenticationResult.user.type === "api" ? "api" : "user"
|
|
4277
|
+
);
|
|
4248
4278
|
console.log("[EXULU] result", result);
|
|
4249
4279
|
res.status(200).json({
|
|
4250
4280
|
message: "Item created successfully.",
|
|
@@ -4593,7 +4623,7 @@ Mood: friendly and intelligent.
|
|
|
4593
4623
|
return;
|
|
4594
4624
|
}
|
|
4595
4625
|
console.log("[EXULU] agent tools", agentInstance.tools);
|
|
4596
|
-
const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
|
|
4626
|
+
const enabledTools = agentInstance.tools.map(({ config: config2, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
|
|
4597
4627
|
console.log("[EXULU] enabled tools", enabledTools);
|
|
4598
4628
|
const variableName = agentInstance.providerApiKey;
|
|
4599
4629
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
@@ -4799,43 +4829,22 @@ import IORedis from "ioredis";
|
|
|
4799
4829
|
import { Worker } from "bullmq";
|
|
4800
4830
|
|
|
4801
4831
|
// src/registry/utils.ts
|
|
4802
|
-
import "bullmq";
|
|
4803
4832
|
var bullmq = {
|
|
4804
|
-
validate: (
|
|
4805
|
-
if (!
|
|
4806
|
-
throw new Error(`Missing job data for job ${
|
|
4833
|
+
validate: (id, data) => {
|
|
4834
|
+
if (!data) {
|
|
4835
|
+
throw new Error(`Missing job data for job ${id}.`);
|
|
4807
4836
|
}
|
|
4808
|
-
if (!
|
|
4809
|
-
throw new Error(`Missing property "type" in data for job ${
|
|
4837
|
+
if (!data.type) {
|
|
4838
|
+
throw new Error(`Missing property "type" in data for job ${id}.`);
|
|
4810
4839
|
}
|
|
4811
|
-
if (!
|
|
4812
|
-
throw new Error(`Missing property "inputs" in data for job ${
|
|
4840
|
+
if (!data.inputs) {
|
|
4841
|
+
throw new Error(`Missing property "inputs" in data for job ${id}.`);
|
|
4813
4842
|
}
|
|
4814
|
-
if (
|
|
4815
|
-
throw new Error(`Property "type" in data for job ${
|
|
4843
|
+
if (data.type !== "embedder" && data.type !== "workflow") {
|
|
4844
|
+
throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
|
|
4816
4845
|
}
|
|
4817
|
-
if (!
|
|
4818
|
-
throw new Error(`
|
|
4819
|
-
}
|
|
4820
|
-
},
|
|
4821
|
-
process: {
|
|
4822
|
-
workflow: async (bullmqJob, exuluJob, workflow, logsDir) => {
|
|
4823
|
-
if (!workflow) {
|
|
4824
|
-
throw new Error(`Workflow function with id: ${bullmqJob.data.backend} not found in registry.`);
|
|
4825
|
-
}
|
|
4826
|
-
console.log("[EXULU] starting workflow with job inputs.", bullmqJob.data.inputs);
|
|
4827
|
-
const logger = new ExuluLogger(exuluJob, logsDir);
|
|
4828
|
-
const output = await workflow.start({
|
|
4829
|
-
job: exuluJob,
|
|
4830
|
-
inputs: bullmqJob.data.inputs,
|
|
4831
|
-
user: bullmqJob.data.user,
|
|
4832
|
-
logger,
|
|
4833
|
-
session: bullmqJob.data.session,
|
|
4834
|
-
agent: bullmqJob.data.agent,
|
|
4835
|
-
label: bullmqJob.data.label
|
|
4836
|
-
});
|
|
4837
|
-
await logger.write(`Workflow completed. ${JSON.stringify(output)}`, "INFO");
|
|
4838
|
-
return output;
|
|
4846
|
+
if (!data.workflow && !data.embedder) {
|
|
4847
|
+
throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
|
|
4839
4848
|
}
|
|
4840
4849
|
}
|
|
4841
4850
|
};
|
|
@@ -4843,9 +4852,10 @@ var bullmq = {
|
|
|
4843
4852
|
// src/registry/workers.ts
|
|
4844
4853
|
import * as fs3 from "fs";
|
|
4845
4854
|
import path2 from "path";
|
|
4855
|
+
import "@opentelemetry/api";
|
|
4846
4856
|
var defaultLogsDir = path2.join(process.cwd(), "logs");
|
|
4847
4857
|
var redisConnection;
|
|
4848
|
-
var createWorkers = async (queues2, contexts, _logsDir) => {
|
|
4858
|
+
var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
|
|
4849
4859
|
if (!redisServer.host || !redisServer.port) {
|
|
4850
4860
|
console.error("[EXULU] you are trying to start worker, but no redis server is configured in the environment.");
|
|
4851
4861
|
throw new Error("No redis server configured in the environment, so cannot start worker.");
|
|
@@ -4862,84 +4872,30 @@ var createWorkers = async (queues2, contexts, _logsDir) => {
|
|
|
4862
4872
|
const worker = new Worker(
|
|
4863
4873
|
`${queue}`,
|
|
4864
4874
|
async (bullmqJob) => {
|
|
4875
|
+
const logger2 = new ExuluLogger(bullmqJob, logsDir);
|
|
4865
4876
|
const { db: db3 } = await postgresClient();
|
|
4866
4877
|
try {
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
}
|
|
4872
|
-
const context = contexts.find((context2) => context2.id === bullmqJob.data.context);
|
|
4878
|
+
const data = bullmqJob.data;
|
|
4879
|
+
bullmq.validate(bullmqJob.id, data);
|
|
4880
|
+
if (data.type === "embedder") {
|
|
4881
|
+
const context = contexts.find((context2) => context2.id === data.context);
|
|
4873
4882
|
if (!context) {
|
|
4874
|
-
throw new Error(`Context ${
|
|
4883
|
+
throw new Error(`Context ${data.context} not found in the registry.`);
|
|
4875
4884
|
}
|
|
4876
|
-
if (!
|
|
4885
|
+
if (!data.embedder) {
|
|
4877
4886
|
throw new Error(`No embedder set for embedder job.`);
|
|
4878
4887
|
}
|
|
4879
|
-
const embedder = contexts.find((context2) => context2.embedder?.id ===
|
|
4888
|
+
const embedder = contexts.find((context2) => context2.embedder?.id === data.embedder);
|
|
4880
4889
|
if (!embedder) {
|
|
4881
|
-
throw new Error(`Embedder ${
|
|
4882
|
-
}
|
|
4883
|
-
if (!bullmqJob.data.source) {
|
|
4884
|
-
throw new Error("No source set for embedder job.");
|
|
4885
|
-
}
|
|
4886
|
-
const source = context.sources.get(bullmqJob.data.source);
|
|
4887
|
-
if (!source) {
|
|
4888
|
-
throw new Error(`Source ${bullmqJob.data.source} not found in the registry.`);
|
|
4889
|
-
}
|
|
4890
|
-
if (!bullmqJob.data.updater) {
|
|
4891
|
-
throw new Error("No updater set for embedder job.");
|
|
4892
|
-
}
|
|
4893
|
-
const updater = source.updaters.find((updater2) => updater2.id === bullmqJob.data.updater);
|
|
4894
|
-
if (!updater) {
|
|
4895
|
-
throw new Error(`Updater ${bullmqJob.data.updater} not found in the registry.`);
|
|
4896
|
-
}
|
|
4897
|
-
if (!bullmqJob.data.documents) {
|
|
4898
|
-
throw new Error("No input documents set for embedder job.");
|
|
4899
|
-
}
|
|
4900
|
-
if (!Array.isArray(bullmqJob.data.documents)) {
|
|
4901
|
-
throw new Error("Input documents must be an array.");
|
|
4890
|
+
throw new Error(`Embedder ${data.embedder} not found in the registry.`);
|
|
4902
4891
|
}
|
|
4903
|
-
const result = await
|
|
4904
|
-
label:
|
|
4905
|
-
trigger:
|
|
4892
|
+
const result = await context.createAndUpsertEmbeddings(data.inputs, data.user, {
|
|
4893
|
+
label: embedder.name,
|
|
4894
|
+
trigger: data.trigger
|
|
4906
4895
|
});
|
|
4907
|
-
const mongoRecord = await db3.from("jobs").where({ redis: bullmqJob.id }).first();
|
|
4908
|
-
if (!mongoRecord) {
|
|
4909
|
-
throw new Error("Job not found in the database.");
|
|
4910
|
-
}
|
|
4911
|
-
const finishedAt = /* @__PURE__ */ new Date();
|
|
4912
|
-
const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
|
|
4913
|
-
await db3.from("jobs").where({ redis: bullmqJob.id }).update({
|
|
4914
|
-
status: "completed",
|
|
4915
|
-
finishedAt,
|
|
4916
|
-
duration,
|
|
4917
|
-
result: JSON.stringify(result)
|
|
4918
|
-
});
|
|
4919
|
-
await db3.from((void 0).getTableName()).where({ id: result[0].id }).update({
|
|
4920
|
-
embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4921
|
-
}).returning("id");
|
|
4922
4896
|
return result;
|
|
4923
4897
|
}
|
|
4924
4898
|
if (bullmqJob.data.type === "workflow") {
|
|
4925
|
-
const workflow = workflows.find((workflow2) => workflow2.id === bullmqJob.data.workflow);
|
|
4926
|
-
if (!workflow) {
|
|
4927
|
-
throw new Error(`Workflow ${bullmqJob.data.workflow} not found in the registry.`);
|
|
4928
|
-
}
|
|
4929
|
-
const exuluJob = await db3.from("jobs").where({ redis: bullmqJob.id }).first();
|
|
4930
|
-
if (!exuluJob) {
|
|
4931
|
-
throw new Error("Job not found in the database.");
|
|
4932
|
-
}
|
|
4933
|
-
const result = await bullmq.process.workflow(bullmqJob, exuluJob, workflow, logsDir);
|
|
4934
|
-
const finishedAt = /* @__PURE__ */ new Date();
|
|
4935
|
-
const duration = (finishedAt.getTime() - new Date(exuluJob.createdAt).getTime()) / 1e3;
|
|
4936
|
-
await db3.from("jobs").where({ redis: bullmqJob.id }).update({
|
|
4937
|
-
status: "completed",
|
|
4938
|
-
finishedAt,
|
|
4939
|
-
duration,
|
|
4940
|
-
result: JSON.stringify(result)
|
|
4941
|
-
});
|
|
4942
|
-
return result;
|
|
4943
4899
|
}
|
|
4944
4900
|
} catch (error) {
|
|
4945
4901
|
await db3.from("jobs").where({ redis: bullmqJob.id }).update({
|
|
@@ -5009,6 +4965,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
5009
4965
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5010
4966
|
import { z as z2 } from "zod";
|
|
5011
4967
|
import "express";
|
|
4968
|
+
import "@opentelemetry/api";
|
|
5012
4969
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
5013
4970
|
var ExuluMCP = class {
|
|
5014
4971
|
server;
|
|
@@ -5016,7 +4973,7 @@ var ExuluMCP = class {
|
|
|
5016
4973
|
express;
|
|
5017
4974
|
constructor() {
|
|
5018
4975
|
}
|
|
5019
|
-
create = async ({ express: express3, contexts, agents, config, tools }) => {
|
|
4976
|
+
create = async ({ express: express3, contexts, agents, config, tools, tracer, logger }) => {
|
|
5020
4977
|
this.express = express3;
|
|
5021
4978
|
if (!this.server) {
|
|
5022
4979
|
console.log("[EXULU] Creating MCP server.");
|
|
@@ -5194,6 +5151,38 @@ var defaultAgent = new ExuluAgent({
|
|
|
5194
5151
|
}
|
|
5195
5152
|
});
|
|
5196
5153
|
|
|
5154
|
+
// src/registry/index.ts
|
|
5155
|
+
import { trace as trace2 } from "@opentelemetry/api";
|
|
5156
|
+
|
|
5157
|
+
// src/registry/logger.ts
|
|
5158
|
+
import { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport";
|
|
5159
|
+
import winston from "winston";
|
|
5160
|
+
var createLogger = ({
|
|
5161
|
+
enableOtel
|
|
5162
|
+
}) => {
|
|
5163
|
+
const logger = winston.createLogger({
|
|
5164
|
+
level: "debug",
|
|
5165
|
+
format: winston.format.combine(
|
|
5166
|
+
winston.format.timestamp(),
|
|
5167
|
+
winston.format.errors({
|
|
5168
|
+
stack: true
|
|
5169
|
+
}),
|
|
5170
|
+
winston.format.metadata(),
|
|
5171
|
+
winston.format.json()
|
|
5172
|
+
),
|
|
5173
|
+
defaultMeta: {
|
|
5174
|
+
service: "Test-Exulu",
|
|
5175
|
+
environment: process.env.NODE_ENV || "development"
|
|
5176
|
+
},
|
|
5177
|
+
transports: [
|
|
5178
|
+
new winston.transports.Console(),
|
|
5179
|
+
...enableOtel ? [new OpenTelemetryTransportV3()] : []
|
|
5180
|
+
]
|
|
5181
|
+
});
|
|
5182
|
+
return logger;
|
|
5183
|
+
};
|
|
5184
|
+
var logger_default = createLogger;
|
|
5185
|
+
|
|
5197
5186
|
// src/registry/index.ts
|
|
5198
5187
|
var ExuluApp = class {
|
|
5199
5188
|
_agents = [];
|
|
@@ -5260,10 +5249,20 @@ var ExuluApp = class {
|
|
|
5260
5249
|
bullmq = {
|
|
5261
5250
|
workers: {
|
|
5262
5251
|
create: async () => {
|
|
5252
|
+
let tracer;
|
|
5253
|
+
if (this._config?.telemetry?.enabled) {
|
|
5254
|
+
console.log("[EXULU] telemetry enabled");
|
|
5255
|
+
tracer = trace2.getTracer("exulu", "1.0.0");
|
|
5256
|
+
}
|
|
5257
|
+
const logger = logger_default({
|
|
5258
|
+
enableOtel: this._config?.workers?.telemetry?.enabled ?? false
|
|
5259
|
+
});
|
|
5263
5260
|
return await createWorkers(
|
|
5264
5261
|
this._queues,
|
|
5262
|
+
logger,
|
|
5265
5263
|
Object.values(this._contexts ?? {}),
|
|
5266
|
-
this._config?.workers?.logsDir
|
|
5264
|
+
this._config?.workers?.logsDir,
|
|
5265
|
+
tracer
|
|
5267
5266
|
);
|
|
5268
5267
|
}
|
|
5269
5268
|
}
|
|
@@ -5275,11 +5274,22 @@ var ExuluApp = class {
|
|
|
5275
5274
|
throw new Error("Express app not initialized");
|
|
5276
5275
|
}
|
|
5277
5276
|
const app = this._expressApp;
|
|
5277
|
+
let tracer;
|
|
5278
|
+
if (this._config?.telemetry?.enabled) {
|
|
5279
|
+
console.log("[EXULU] telemetry enabled");
|
|
5280
|
+
tracer = trace2.getTracer("exulu", "1.0.0");
|
|
5281
|
+
}
|
|
5282
|
+
const logger = logger_default({
|
|
5283
|
+
enableOtel: this._config?.telemetry?.enabled ?? false
|
|
5284
|
+
});
|
|
5278
5285
|
await createExpressRoutes(
|
|
5279
5286
|
app,
|
|
5287
|
+
logger,
|
|
5280
5288
|
this._agents,
|
|
5281
5289
|
this._tools,
|
|
5282
|
-
Object.values(this._contexts ?? {})
|
|
5290
|
+
Object.values(this._contexts ?? {}),
|
|
5291
|
+
this._config,
|
|
5292
|
+
tracer
|
|
5283
5293
|
);
|
|
5284
5294
|
if (this._config?.MCP.enabled) {
|
|
5285
5295
|
const mcp = new ExuluMCP();
|
|
@@ -5288,7 +5298,9 @@ var ExuluApp = class {
|
|
|
5288
5298
|
contexts: this._contexts,
|
|
5289
5299
|
agents: this._agents,
|
|
5290
5300
|
config: this._config,
|
|
5291
|
-
tools: this._tools
|
|
5301
|
+
tools: this._tools,
|
|
5302
|
+
tracer,
|
|
5303
|
+
logger
|
|
5292
5304
|
});
|
|
5293
5305
|
await mcp.connect();
|
|
5294
5306
|
}
|
|
@@ -6701,6 +6713,50 @@ var execute = async () => {
|
|
|
6701
6713
|
return;
|
|
6702
6714
|
};
|
|
6703
6715
|
|
|
6716
|
+
// src/registry/otel.ts
|
|
6717
|
+
import process2 from "process";
|
|
6718
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
6719
|
+
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
6720
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
6721
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
6722
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
6723
|
+
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
6724
|
+
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
6725
|
+
var create = ({
|
|
6726
|
+
SIGNOZ_ACCESS_TOKEN,
|
|
6727
|
+
SIGNOZ_TRACES_URL,
|
|
6728
|
+
SIGNOZ_LOGS_URL
|
|
6729
|
+
}) => {
|
|
6730
|
+
console.log("SIGNOZ_ACCESS_TOKEN", SIGNOZ_ACCESS_TOKEN);
|
|
6731
|
+
console.log("SIGNOZ_TRACES_URL", SIGNOZ_TRACES_URL);
|
|
6732
|
+
console.log("SIGNOZ_LOGS_URL", SIGNOZ_LOGS_URL);
|
|
6733
|
+
const exporterOptions = {
|
|
6734
|
+
url: SIGNOZ_TRACES_URL,
|
|
6735
|
+
headers: {
|
|
6736
|
+
"signoz-access-token": SIGNOZ_ACCESS_TOKEN
|
|
6737
|
+
}
|
|
6738
|
+
};
|
|
6739
|
+
const traceExporter = new OTLPTraceExporter(exporterOptions);
|
|
6740
|
+
const logExporter = new OTLPLogExporter({
|
|
6741
|
+
url: SIGNOZ_LOGS_URL,
|
|
6742
|
+
headers: {
|
|
6743
|
+
"signoz-access-token": SIGNOZ_ACCESS_TOKEN
|
|
6744
|
+
}
|
|
6745
|
+
});
|
|
6746
|
+
const sdk = new NodeSDK({
|
|
6747
|
+
traceExporter,
|
|
6748
|
+
logRecordProcessors: [new BatchLogRecordProcessor(logExporter)],
|
|
6749
|
+
instrumentations: [getNodeAutoInstrumentations()],
|
|
6750
|
+
resource: resourceFromAttributes({
|
|
6751
|
+
[ATTR_SERVICE_NAME]: "Test-Exulu"
|
|
6752
|
+
})
|
|
6753
|
+
});
|
|
6754
|
+
process2.on("SIGTERM", () => {
|
|
6755
|
+
sdk.shutdown().then(() => console.log("Tracing terminated")).catch((error) => console.log("Error terminating tracing", error)).finally(() => process2.exit(0));
|
|
6756
|
+
});
|
|
6757
|
+
return sdk;
|
|
6758
|
+
};
|
|
6759
|
+
|
|
6704
6760
|
// types/enums/jobs.ts
|
|
6705
6761
|
var JOB_STATUS_ENUM = {
|
|
6706
6762
|
completed: "completed",
|
|
@@ -6719,6 +6775,19 @@ var ExuluJobs = {
|
|
|
6719
6775
|
validate: validateJob
|
|
6720
6776
|
}
|
|
6721
6777
|
};
|
|
6778
|
+
var ExuluOtel = {
|
|
6779
|
+
create: ({
|
|
6780
|
+
SIGNOZ_ACCESS_TOKEN,
|
|
6781
|
+
SIGNOZ_TRACES_URL,
|
|
6782
|
+
SIGNOZ_LOGS_URL
|
|
6783
|
+
}) => {
|
|
6784
|
+
return create({
|
|
6785
|
+
SIGNOZ_ACCESS_TOKEN,
|
|
6786
|
+
SIGNOZ_TRACES_URL,
|
|
6787
|
+
SIGNOZ_LOGS_URL
|
|
6788
|
+
});
|
|
6789
|
+
}
|
|
6790
|
+
};
|
|
6722
6791
|
var db2 = {
|
|
6723
6792
|
init: async () => {
|
|
6724
6793
|
await execute();
|
|
@@ -6750,6 +6819,7 @@ export {
|
|
|
6750
6819
|
ExuluEval,
|
|
6751
6820
|
ExuluJobs,
|
|
6752
6821
|
ExuluLogger,
|
|
6822
|
+
ExuluOtel,
|
|
6753
6823
|
queues as ExuluQueues,
|
|
6754
6824
|
ExuluSource,
|
|
6755
6825
|
ExuluTool,
|