@exulu/backend 1.39.2 → 1.40.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 +7 -2
- package/changelog-backend-10.11.2025_03.12.2025.md +1 -1
- package/dist/index.cjs +308 -184
- package/dist/index.d.cts +24 -14
- package/dist/index.d.ts +24 -14
- package/dist/index.js +308 -184
- package/package.json +1 -1
- package/types/models/context.ts +8 -0
package/dist/index.cjs
CHANGED
|
@@ -67,7 +67,6 @@ var redisServer = {
|
|
|
67
67
|
// src/redis/client.ts
|
|
68
68
|
var client = {};
|
|
69
69
|
async function redisClient() {
|
|
70
|
-
console.log("[EXULU] redisServer:", redisServer);
|
|
71
70
|
if (!redisServer.host || !redisServer.port) {
|
|
72
71
|
return { client: null };
|
|
73
72
|
}
|
|
@@ -148,7 +147,6 @@ var db = {};
|
|
|
148
147
|
var databaseExistsChecked = false;
|
|
149
148
|
var dbName = process.env.POSTGRES_DB_NAME || "exulu";
|
|
150
149
|
async function ensureDatabaseExists() {
|
|
151
|
-
console.log(`[EXULU] Ensuring ${dbName} database exists...`);
|
|
152
150
|
const defaultKnex = (0, import_knex.default)({
|
|
153
151
|
client: "pg",
|
|
154
152
|
connection: {
|
|
@@ -192,16 +190,7 @@ async function ensureDatabaseExists() {
|
|
|
192
190
|
async function postgresClient() {
|
|
193
191
|
if (!db["exulu"]) {
|
|
194
192
|
try {
|
|
195
|
-
console.log(`[EXULU] Connecting to ${dbName} database.`);
|
|
196
|
-
console.log("[EXULU] POSTGRES_DB_HOST:", process.env.POSTGRES_DB_HOST);
|
|
197
|
-
console.log("[EXULU] POSTGRES_DB_PORT:", process.env.POSTGRES_DB_PORT);
|
|
198
|
-
console.log("[EXULU] POSTGRES_DB_USER:", process.env.POSTGRES_DB_USER);
|
|
199
|
-
console.log("[EXULU] POSTGRES_DB_PASSWORD:", process.env.POSTGRES_DB_PASSWORD);
|
|
200
|
-
console.log("[EXULU] POSTGRES_DB_NAME:", dbName);
|
|
201
|
-
console.log("[EXULU] POSTGRES_DB_SSL:", process.env.POSTGRES_DB_SSL);
|
|
202
|
-
console.log("[EXULU] Database exists checked:", databaseExistsChecked);
|
|
203
193
|
if (!databaseExistsChecked) {
|
|
204
|
-
console.log(`[EXULU] Ensuring ${dbName} database exists...`);
|
|
205
194
|
await ensureDatabaseExists();
|
|
206
195
|
databaseExistsChecked = true;
|
|
207
196
|
}
|
|
@@ -526,7 +515,6 @@ var authentication = async ({
|
|
|
526
515
|
}
|
|
527
516
|
if (authtoken) {
|
|
528
517
|
try {
|
|
529
|
-
console.log("[EXULU] authtoken", authtoken);
|
|
530
518
|
if (!authtoken?.email) {
|
|
531
519
|
return {
|
|
532
520
|
error: true,
|
|
@@ -3730,7 +3718,10 @@ type PageInfo {
|
|
|
3730
3718
|
const config2 = await queue.use();
|
|
3731
3719
|
return {
|
|
3732
3720
|
name: config2.queue.name,
|
|
3733
|
-
concurrency:
|
|
3721
|
+
concurrency: {
|
|
3722
|
+
worker: config2.concurrency?.worker || void 0,
|
|
3723
|
+
queue: config2.concurrency?.queue || void 0
|
|
3724
|
+
},
|
|
3734
3725
|
ratelimit: config2.ratelimit,
|
|
3735
3726
|
isMaxed: await config2.queue.isMaxed(),
|
|
3736
3727
|
isPaused: await config2.queue.isPaused(),
|
|
@@ -3780,7 +3771,10 @@ type PageInfo {
|
|
|
3780
3771
|
if (!agentInstance) {
|
|
3781
3772
|
throw new Error("Agent instance not found for eval run.");
|
|
3782
3773
|
}
|
|
3783
|
-
const evalQueue = await queues.register("eval_runs",
|
|
3774
|
+
const evalQueue = await queues.register("eval_runs", {
|
|
3775
|
+
worker: 1,
|
|
3776
|
+
queue: 1
|
|
3777
|
+
}, 1).use();
|
|
3784
3778
|
const jobIds = [];
|
|
3785
3779
|
for (const testCase of testCases) {
|
|
3786
3780
|
const jobData = {
|
|
@@ -3904,7 +3898,6 @@ type PageInfo {
|
|
|
3904
3898
|
if (!client2) {
|
|
3905
3899
|
throw new Error("Redis client not created properly");
|
|
3906
3900
|
}
|
|
3907
|
-
console.log("[EXULU] Jobs pagination args", args);
|
|
3908
3901
|
const {
|
|
3909
3902
|
jobs,
|
|
3910
3903
|
count
|
|
@@ -3914,7 +3907,6 @@ type PageInfo {
|
|
|
3914
3907
|
args.page || 1,
|
|
3915
3908
|
args.limit || 100
|
|
3916
3909
|
);
|
|
3917
|
-
console.log("[EXULU] jobs", jobs.map((job) => job.name));
|
|
3918
3910
|
const requestedFields = getRequestedFields(info);
|
|
3919
3911
|
return {
|
|
3920
3912
|
items: await Promise.all(jobs.map(async (job) => {
|
|
@@ -3943,6 +3935,25 @@ type PageInfo {
|
|
|
3943
3935
|
};
|
|
3944
3936
|
resolvers.Query["contexts"] = async (_, args, context, info) => {
|
|
3945
3937
|
const data = await Promise.all(contexts.map(async (context2) => {
|
|
3938
|
+
let processors = await Promise.all(context2.fields.map(async (field) => {
|
|
3939
|
+
if (field.processor) {
|
|
3940
|
+
let queueName = void 0;
|
|
3941
|
+
if (field.processor?.config?.queue) {
|
|
3942
|
+
const config2 = await field.processor?.config?.queue;
|
|
3943
|
+
queueName = config2?.queue?.name || void 0;
|
|
3944
|
+
}
|
|
3945
|
+
return {
|
|
3946
|
+
field: field.name,
|
|
3947
|
+
description: field.processor?.description,
|
|
3948
|
+
queue: queueName,
|
|
3949
|
+
trigger: field.processor?.config?.trigger,
|
|
3950
|
+
timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 600,
|
|
3951
|
+
generateEmbeddings: field.processor?.config?.generateEmbeddings || false
|
|
3952
|
+
};
|
|
3953
|
+
}
|
|
3954
|
+
return null;
|
|
3955
|
+
}));
|
|
3956
|
+
processors = processors.filter((processor) => processor !== null);
|
|
3946
3957
|
const sources = await Promise.all(context2.sources.map(async (source) => {
|
|
3947
3958
|
let queueName = void 0;
|
|
3948
3959
|
if (source.config) {
|
|
@@ -3974,6 +3985,7 @@ type PageInfo {
|
|
|
3974
3985
|
slug: "/contexts/" + context2.id,
|
|
3975
3986
|
active: context2.active,
|
|
3976
3987
|
sources,
|
|
3988
|
+
processors,
|
|
3977
3989
|
fields: context2.fields.map((field) => {
|
|
3978
3990
|
return {
|
|
3979
3991
|
...field,
|
|
@@ -3999,6 +4011,25 @@ type PageInfo {
|
|
|
3999
4011
|
if (!data) {
|
|
4000
4012
|
return null;
|
|
4001
4013
|
}
|
|
4014
|
+
let processors = await Promise.all(data.fields.map(async (field) => {
|
|
4015
|
+
if (field.processor) {
|
|
4016
|
+
let queueName = void 0;
|
|
4017
|
+
if (field.processor?.config?.queue) {
|
|
4018
|
+
const config2 = await field.processor?.config?.queue;
|
|
4019
|
+
queueName = config2?.queue?.name || void 0;
|
|
4020
|
+
}
|
|
4021
|
+
return {
|
|
4022
|
+
field: field.name,
|
|
4023
|
+
description: field.processor?.description,
|
|
4024
|
+
queue: queueName,
|
|
4025
|
+
trigger: field.processor?.config?.trigger,
|
|
4026
|
+
timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 600,
|
|
4027
|
+
generateEmbeddings: field.processor?.config?.generateEmbeddings || false
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
return null;
|
|
4031
|
+
}));
|
|
4032
|
+
processors = processors.filter((processor) => processor !== null);
|
|
4002
4033
|
const sources = await Promise.all(data.sources.map(async (source) => {
|
|
4003
4034
|
let queueName = void 0;
|
|
4004
4035
|
if (source.config) {
|
|
@@ -4035,6 +4066,7 @@ type PageInfo {
|
|
|
4035
4066
|
slug: "/contexts/" + data.id,
|
|
4036
4067
|
active: data.active,
|
|
4037
4068
|
sources,
|
|
4069
|
+
processors,
|
|
4038
4070
|
fields: await Promise.all(data.fields.map(async (field) => {
|
|
4039
4071
|
const label = field.name?.replace("_s3key", "");
|
|
4040
4072
|
if (field.type === "file" && !field.name.endsWith("_s3key")) {
|
|
@@ -4058,7 +4090,10 @@ type PageInfo {
|
|
|
4058
4090
|
queue: {
|
|
4059
4091
|
name: queue?.queue.name || void 0,
|
|
4060
4092
|
ratelimit: queue?.ratelimit || void 0,
|
|
4061
|
-
concurrency:
|
|
4093
|
+
concurrency: {
|
|
4094
|
+
worker: queue?.concurrency?.worker || void 0,
|
|
4095
|
+
queue: queue?.concurrency?.queue || void 0
|
|
4096
|
+
}
|
|
4062
4097
|
}
|
|
4063
4098
|
},
|
|
4064
4099
|
execute: "function"
|
|
@@ -4131,13 +4166,19 @@ type PageInfo {
|
|
|
4131
4166
|
modelDefs += `
|
|
4132
4167
|
type QueueResult {
|
|
4133
4168
|
name: String!
|
|
4134
|
-
concurrency:
|
|
4169
|
+
concurrency: QueueConcurrency!
|
|
4135
4170
|
ratelimit: Int!
|
|
4136
4171
|
isMaxed: Boolean!
|
|
4137
4172
|
isPaused: Boolean!
|
|
4138
4173
|
jobs: QueueJobsCounts
|
|
4139
4174
|
}
|
|
4140
4175
|
`;
|
|
4176
|
+
modelDefs += `
|
|
4177
|
+
type QueueConcurrency {
|
|
4178
|
+
worker: Int
|
|
4179
|
+
queue: Int
|
|
4180
|
+
}
|
|
4181
|
+
`;
|
|
4141
4182
|
modelDefs += `
|
|
4142
4183
|
type QueueJobsCounts {
|
|
4143
4184
|
paused: Int!
|
|
@@ -4252,7 +4293,8 @@ type Context {
|
|
|
4252
4293
|
active: Boolean
|
|
4253
4294
|
fields: JSON
|
|
4254
4295
|
configuration: JSON
|
|
4255
|
-
sources: [ContextSource
|
|
4296
|
+
sources: [ContextSource]
|
|
4297
|
+
processors: [ContextProcessor]
|
|
4256
4298
|
}
|
|
4257
4299
|
type Embedder {
|
|
4258
4300
|
name: String!
|
|
@@ -4265,6 +4307,14 @@ type EmbedderConfig {
|
|
|
4265
4307
|
description: String
|
|
4266
4308
|
default: String
|
|
4267
4309
|
}
|
|
4310
|
+
type ContextProcessor {
|
|
4311
|
+
field: String!
|
|
4312
|
+
description: String
|
|
4313
|
+
queue: String
|
|
4314
|
+
trigger: String
|
|
4315
|
+
timeoutInSeconds: Int
|
|
4316
|
+
generateEmbeddings: Boolean
|
|
4317
|
+
}
|
|
4268
4318
|
|
|
4269
4319
|
type ContextSource {
|
|
4270
4320
|
id: String!
|
|
@@ -4383,10 +4433,7 @@ async function getJobsByQueueName(queueName, statusses, page, limit) {
|
|
|
4383
4433
|
const config = await queue.use();
|
|
4384
4434
|
const startIndex = (page || 1) - 1;
|
|
4385
4435
|
const endIndex = startIndex - 1 + (limit || 100);
|
|
4386
|
-
console.log("[EXULU] Jobs pagination startIndex", startIndex);
|
|
4387
|
-
console.log("[EXULU] Jobs pagination endIndex", endIndex);
|
|
4388
4436
|
const jobs = await config.queue.getJobs(statusses || [], startIndex, endIndex, false);
|
|
4389
|
-
console.log("[EXULU] Jobs pagination jobs", jobs?.length);
|
|
4390
4437
|
const counts = await config.queue.getJobCounts(...statusses || []);
|
|
4391
4438
|
let total = 0;
|
|
4392
4439
|
if (counts) {
|
|
@@ -4427,21 +4474,10 @@ function getS3Client(config) {
|
|
|
4427
4474
|
});
|
|
4428
4475
|
return s3Client;
|
|
4429
4476
|
}
|
|
4430
|
-
var getPresignedUrl = async (key, config) => {
|
|
4477
|
+
var getPresignedUrl = async (bucket, key, config) => {
|
|
4431
4478
|
if (!config.fileUploads) {
|
|
4432
4479
|
throw new Error("File uploads are not configured");
|
|
4433
4480
|
}
|
|
4434
|
-
let bucket = config.fileUploads.s3Bucket;
|
|
4435
|
-
if (key.includes("[bucket:")) {
|
|
4436
|
-
console.log("[EXULU] key includes [bucket:name]", key);
|
|
4437
|
-
bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
|
|
4438
|
-
if (!bucket?.length) {
|
|
4439
|
-
throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
|
|
4440
|
-
}
|
|
4441
|
-
key = key.split("]")[1] || "";
|
|
4442
|
-
console.log("[EXULU] bucket", bucket);
|
|
4443
|
-
console.log("[EXULU] key", key);
|
|
4444
|
-
}
|
|
4445
4481
|
const url = await (0, import_s3_request_presigner.getSignedUrl)(
|
|
4446
4482
|
getS3Client(config),
|
|
4447
4483
|
new import_client_s3.GetObjectCommand({
|
|
@@ -4452,7 +4488,7 @@ var getPresignedUrl = async (key, config) => {
|
|
|
4452
4488
|
);
|
|
4453
4489
|
return url;
|
|
4454
4490
|
};
|
|
4455
|
-
var
|
|
4491
|
+
var addGeneralPrefixToKey = (keyPath, config) => {
|
|
4456
4492
|
if (!config.fileUploads) {
|
|
4457
4493
|
throw new Error("File uploads are not configured");
|
|
4458
4494
|
}
|
|
@@ -4465,58 +4501,50 @@ var addPrefixToKey = (keyPath, config) => {
|
|
|
4465
4501
|
}
|
|
4466
4502
|
return `${prefix}/${keyPath}`;
|
|
4467
4503
|
};
|
|
4468
|
-
var
|
|
4504
|
+
var addUserPrefixToKey = (key, user) => {
|
|
4505
|
+
if (!user) {
|
|
4506
|
+
return key;
|
|
4507
|
+
}
|
|
4508
|
+
if (key.includes(`/user_${user}/`)) {
|
|
4509
|
+
return key;
|
|
4510
|
+
}
|
|
4511
|
+
return `user_${user}/${key}`;
|
|
4512
|
+
};
|
|
4513
|
+
var addBucketPrefixToKey = (key, bucket) => {
|
|
4514
|
+
if (key.includes(`/${bucket}/`)) {
|
|
4515
|
+
return key;
|
|
4516
|
+
}
|
|
4517
|
+
return `${bucket}/${key}`;
|
|
4518
|
+
};
|
|
4519
|
+
var uploadFile = async (file, fileName, config, options = {}, user, customBucket) => {
|
|
4469
4520
|
if (!config.fileUploads) {
|
|
4470
4521
|
throw new Error("File uploads are not configured (in the exported uploadFile function)");
|
|
4471
4522
|
}
|
|
4472
4523
|
const client2 = getS3Client(config);
|
|
4473
4524
|
let defaultBucket = config.fileUploads.s3Bucket;
|
|
4474
|
-
let
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
if (!customBucket?.length) {
|
|
4479
|
-
throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
|
|
4480
|
-
}
|
|
4481
|
-
key = key.split("]")[1] || "";
|
|
4482
|
-
console.log("[EXULU] custom bucket", customBucket);
|
|
4483
|
-
}
|
|
4484
|
-
let folder = user ? `${user}/` : "";
|
|
4485
|
-
const fullKey = addPrefixToKey(!key.includes(folder) ? folder + key : key, config);
|
|
4486
|
-
console.log("[EXULU] uploading file to s3 into bucket", customBucket || defaultBucket, "with key", fullKey);
|
|
4525
|
+
let key = fileName;
|
|
4526
|
+
key = addGeneralPrefixToKey(key, config);
|
|
4527
|
+
key = addUserPrefixToKey(key, user || "api");
|
|
4528
|
+
console.log("[EXULU] uploading file to s3 into bucket", defaultBucket, "with key", key);
|
|
4487
4529
|
const command = new import_client_s3.PutObjectCommand({
|
|
4488
4530
|
Bucket: customBucket || defaultBucket,
|
|
4489
|
-
Key:
|
|
4531
|
+
Key: key,
|
|
4490
4532
|
Body: file,
|
|
4491
4533
|
ContentType: options.contentType,
|
|
4492
4534
|
Metadata: options.metadata,
|
|
4493
4535
|
ContentLength: file.byteLength
|
|
4494
4536
|
});
|
|
4495
4537
|
await client2.send(command);
|
|
4496
|
-
console.log("[EXULU] file uploaded to s3 into bucket", customBucket || defaultBucket, "with key",
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4538
|
+
console.log("[EXULU] file uploaded to s3 into bucket", customBucket || defaultBucket, "with key", key);
|
|
4539
|
+
return addBucketPrefixToKey(
|
|
4540
|
+
key,
|
|
4541
|
+
customBucket || defaultBucket
|
|
4542
|
+
);
|
|
4501
4543
|
};
|
|
4502
|
-
var createUppyRoutes = async (app, config) => {
|
|
4544
|
+
var createUppyRoutes = async (app, contexts, config) => {
|
|
4503
4545
|
if (!config.fileUploads) {
|
|
4504
4546
|
throw new Error("File uploads are not configured");
|
|
4505
4547
|
}
|
|
4506
|
-
const extractUserPrefix = (key) => {
|
|
4507
|
-
if (!config.fileUploads) {
|
|
4508
|
-
throw new Error("File uploads are not configured");
|
|
4509
|
-
}
|
|
4510
|
-
if (!config.fileUploads.s3prefix) {
|
|
4511
|
-
return key.split("/")[0];
|
|
4512
|
-
}
|
|
4513
|
-
const prefix = config.fileUploads.s3prefix.replace(/\/$/, "");
|
|
4514
|
-
if (key.startsWith(prefix + "/")) {
|
|
4515
|
-
const keyWithoutPrefix = key.slice(prefix.length + 1);
|
|
4516
|
-
return keyWithoutPrefix.split("/")[0];
|
|
4517
|
-
}
|
|
4518
|
-
return key.split("/")[0];
|
|
4519
|
-
};
|
|
4520
4548
|
const policy = {
|
|
4521
4549
|
Version: "2012-10-17",
|
|
4522
4550
|
Statement: [
|
|
@@ -4568,20 +4596,16 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4568
4596
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
4569
4597
|
return;
|
|
4570
4598
|
}
|
|
4599
|
+
const user = authenticationResult.user;
|
|
4571
4600
|
let { key } = req.query;
|
|
4572
4601
|
if (typeof key !== "string" || key.trim() === "") {
|
|
4573
4602
|
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4574
4603
|
return;
|
|
4575
4604
|
}
|
|
4576
|
-
let bucket =
|
|
4577
|
-
if (key.includes(
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
if (!bucket?.length) {
|
|
4581
|
-
throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
|
|
4582
|
-
}
|
|
4583
|
-
key = key.split("]")[1] || "";
|
|
4584
|
-
console.log("[EXULU] bucket", bucket);
|
|
4605
|
+
let bucket = key.split("/")[0];
|
|
4606
|
+
if (user.type !== "api" && !key.includes(`/user_${user.id}/`) && !user.super_admin) {
|
|
4607
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4608
|
+
return;
|
|
4585
4609
|
}
|
|
4586
4610
|
const client2 = getS3Client(config);
|
|
4587
4611
|
const command = new import_client_s3.DeleteObjectCommand({
|
|
@@ -4609,13 +4633,32 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4609
4633
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
4610
4634
|
return;
|
|
4611
4635
|
}
|
|
4612
|
-
const
|
|
4636
|
+
const user = authenticationResult.user;
|
|
4637
|
+
let { key } = req.query;
|
|
4638
|
+
if (!key || typeof key !== "string" || key.trim() === "") {
|
|
4639
|
+
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4640
|
+
return;
|
|
4641
|
+
}
|
|
4642
|
+
let bucket = key.split("/")[0];
|
|
4643
|
+
if (!bucket || typeof bucket !== "string" || bucket.trim() === "") {
|
|
4644
|
+
res.status(400).json({ error: "Missing or invalid `bucket` (should be the first part of the key before the first slash)." });
|
|
4645
|
+
return;
|
|
4646
|
+
}
|
|
4647
|
+
key = key.split("/").slice(1).join("/");
|
|
4613
4648
|
if (typeof key !== "string" || key.trim() === "") {
|
|
4614
4649
|
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4615
4650
|
return;
|
|
4616
4651
|
}
|
|
4652
|
+
let allowed = false;
|
|
4653
|
+
if (user.type === "api" || user.super_admin || key.includes(`user_${user.id}/`)) {
|
|
4654
|
+
allowed = true;
|
|
4655
|
+
}
|
|
4656
|
+
if (!allowed) {
|
|
4657
|
+
res.status(405).json({ error: "Not allowed to access the file based on authenticated user." });
|
|
4658
|
+
return;
|
|
4659
|
+
}
|
|
4617
4660
|
try {
|
|
4618
|
-
const url = await getPresignedUrl(key, config);
|
|
4661
|
+
const url = await getPresignedUrl(bucket, key, config);
|
|
4619
4662
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4620
4663
|
res.json({ url, method: "GET", expiresIn });
|
|
4621
4664
|
} catch (err) {
|
|
@@ -4644,16 +4687,15 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4644
4687
|
return;
|
|
4645
4688
|
}
|
|
4646
4689
|
let { key } = req.body;
|
|
4647
|
-
let bucket =
|
|
4648
|
-
if (
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
}
|
|
4655
|
-
|
|
4656
|
-
console.log("[EXULU] key", key);
|
|
4690
|
+
let bucket = key.split("/")[0];
|
|
4691
|
+
if (!bucket || typeof bucket !== "string" || bucket.trim() === "") {
|
|
4692
|
+
res.status(400).json({ error: "Missing or invalid `bucket` (should be the first part of the key before the first slash)." });
|
|
4693
|
+
return;
|
|
4694
|
+
}
|
|
4695
|
+
key = key.split("/").slice(1).join("/");
|
|
4696
|
+
if (!key || typeof key !== "string" || key.trim() === "") {
|
|
4697
|
+
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4698
|
+
return;
|
|
4657
4699
|
}
|
|
4658
4700
|
const client2 = getS3Client(config);
|
|
4659
4701
|
const command = new import_client_s3.HeadObjectCommand({
|
|
@@ -4757,11 +4799,12 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4757
4799
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
4758
4800
|
return;
|
|
4759
4801
|
}
|
|
4802
|
+
const user = authenticationResult.user;
|
|
4760
4803
|
const { filename, contentType } = extractFileParameters(req);
|
|
4761
4804
|
validateFileParameters(filename, contentType);
|
|
4762
4805
|
const key = generateS3Key2(filename);
|
|
4763
|
-
let
|
|
4764
|
-
|
|
4806
|
+
let fullKey = addGeneralPrefixToKey(key, config);
|
|
4807
|
+
fullKey = addUserPrefixToKey(fullKey, user.type === "api" ? "api" : user.id);
|
|
4765
4808
|
(0, import_s3_request_presigner.getSignedUrl)(
|
|
4766
4809
|
getS3Client(config),
|
|
4767
4810
|
new import_client_s3.PutObjectCommand({
|
|
@@ -4805,6 +4848,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4805
4848
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
4806
4849
|
return;
|
|
4807
4850
|
}
|
|
4851
|
+
const user = authenticationResult.user;
|
|
4808
4852
|
const client2 = getS3Client(config);
|
|
4809
4853
|
const { type, metadata, filename } = req.body;
|
|
4810
4854
|
if (typeof filename !== "string") {
|
|
@@ -4814,13 +4858,8 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4814
4858
|
return res.status(400).json({ error: "s3: content type must be a string" });
|
|
4815
4859
|
}
|
|
4816
4860
|
const key = `${(0, import_node_crypto.randomUUID)()}-_EXULU_${filename}`;
|
|
4817
|
-
let
|
|
4818
|
-
|
|
4819
|
-
folder = `api/`;
|
|
4820
|
-
} else {
|
|
4821
|
-
folder = `${authenticationResult.user.id}/`;
|
|
4822
|
-
}
|
|
4823
|
-
const fullKey = addPrefixToKey(folder + key, config);
|
|
4861
|
+
let fullKey = addGeneralPrefixToKey(key, config);
|
|
4862
|
+
fullKey = addUserPrefixToKey(fullKey, user.type === "api" ? "api" : user.id);
|
|
4824
4863
|
const params = {
|
|
4825
4864
|
Bucket: config.fileUploads.s3Bucket,
|
|
4826
4865
|
Key: fullKey,
|
|
@@ -5023,7 +5062,7 @@ var createProjectRetrievalTool = async ({
|
|
|
5023
5062
|
if (!context) {
|
|
5024
5063
|
throw new Error("The item added to the project does not have a valid gid with the context id as the prefix before the first slash.");
|
|
5025
5064
|
}
|
|
5026
|
-
const id = item.split("/")
|
|
5065
|
+
const id = item.split("/").slice(1).join("/");
|
|
5027
5066
|
if (set[context]) {
|
|
5028
5067
|
set[context].push(id);
|
|
5029
5068
|
} else {
|
|
@@ -5969,12 +6008,20 @@ var ExuluStorage = class {
|
|
|
5969
6008
|
this.config = config;
|
|
5970
6009
|
}
|
|
5971
6010
|
getPresignedUrl = async (key) => {
|
|
5972
|
-
|
|
6011
|
+
const bucket = key.split("/")[0];
|
|
6012
|
+
if (!bucket || typeof bucket !== "string" || bucket.trim() === "") {
|
|
6013
|
+
throw new Error("Invalid S3 key, must be in the format of <bucket>/<key>.");
|
|
6014
|
+
}
|
|
6015
|
+
key = key.split("/").slice(1).join("/");
|
|
6016
|
+
if (!key || typeof key !== "string" || key.trim() === "") {
|
|
6017
|
+
throw new Error("Invalid S3 key, must be in the format of <bucket>/<key>.");
|
|
6018
|
+
}
|
|
6019
|
+
return await getPresignedUrl(bucket, key, this.config);
|
|
5973
6020
|
};
|
|
5974
|
-
uploadFile = async (file,
|
|
6021
|
+
uploadFile = async (file, fileName, type, user, metadata, customBucket) => {
|
|
5975
6022
|
return await uploadFile(
|
|
5976
6023
|
file,
|
|
5977
|
-
|
|
6024
|
+
fileName,
|
|
5978
6025
|
this.config,
|
|
5979
6026
|
{
|
|
5980
6027
|
contentType: type,
|
|
@@ -5983,7 +6030,8 @@ var ExuluStorage = class {
|
|
|
5983
6030
|
type
|
|
5984
6031
|
}
|
|
5985
6032
|
},
|
|
5986
|
-
user
|
|
6033
|
+
user,
|
|
6034
|
+
customBucket
|
|
5987
6035
|
);
|
|
5988
6036
|
};
|
|
5989
6037
|
// todo add upload and delete methods
|
|
@@ -6034,6 +6082,9 @@ var ExuluContext = class {
|
|
|
6034
6082
|
this.resultReranker = resultReranker;
|
|
6035
6083
|
}
|
|
6036
6084
|
processField = async (trigger, item, exuluConfig, user, role) => {
|
|
6085
|
+
if (!item.field) {
|
|
6086
|
+
throw new Error("Field property on item is required for running a specific processor.");
|
|
6087
|
+
}
|
|
6037
6088
|
console.log("[EXULU] processing field", item.field, " in context", this.id);
|
|
6038
6089
|
console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
|
|
6039
6090
|
const field = this.fields.find((field2) => {
|
|
@@ -6065,27 +6116,32 @@ var ExuluContext = class {
|
|
|
6065
6116
|
trigger
|
|
6066
6117
|
});
|
|
6067
6118
|
return {
|
|
6068
|
-
result:
|
|
6119
|
+
result: {},
|
|
6069
6120
|
job: job.id
|
|
6070
6121
|
};
|
|
6071
6122
|
}
|
|
6072
6123
|
console.log("[EXULU] POS 1 -- EXULU CONTEXT PROCESS FIELD");
|
|
6073
|
-
const
|
|
6124
|
+
const processorResult = await field.processor.execute({
|
|
6074
6125
|
item,
|
|
6075
6126
|
user,
|
|
6076
6127
|
role,
|
|
6077
6128
|
utils: {
|
|
6078
|
-
storage: exuluStorage
|
|
6079
|
-
items: {
|
|
6080
|
-
update: this.updateItem,
|
|
6081
|
-
create: this.createItem,
|
|
6082
|
-
delete: this.deleteItem
|
|
6083
|
-
}
|
|
6129
|
+
storage: exuluStorage
|
|
6084
6130
|
},
|
|
6085
6131
|
exuluConfig
|
|
6086
6132
|
});
|
|
6133
|
+
if (!processorResult) {
|
|
6134
|
+
throw new Error("Processor result is required for updating the item in the db.");
|
|
6135
|
+
}
|
|
6136
|
+
const { db: db3 } = await postgresClient();
|
|
6137
|
+
delete processorResult.field;
|
|
6138
|
+
await db3.from(getTableName(this.id)).where({
|
|
6139
|
+
id: processorResult.id
|
|
6140
|
+
}).update({
|
|
6141
|
+
...processorResult
|
|
6142
|
+
});
|
|
6087
6143
|
return {
|
|
6088
|
-
result,
|
|
6144
|
+
result: processorResult,
|
|
6089
6145
|
job: void 0
|
|
6090
6146
|
};
|
|
6091
6147
|
};
|
|
@@ -6170,6 +6226,8 @@ var ExuluContext = class {
|
|
|
6170
6226
|
};
|
|
6171
6227
|
};
|
|
6172
6228
|
createItem = async (item, config, user, role, upsert, generateEmbeddingsOverwrite) => {
|
|
6229
|
+
console.log("[EXULU] creating item", item);
|
|
6230
|
+
console.log("[EXULU] upsert", upsert);
|
|
6173
6231
|
if (upsert && (!item.id && !item.external_id)) {
|
|
6174
6232
|
throw new Error("Item id or external id is required for upsert.");
|
|
6175
6233
|
}
|
|
@@ -6197,7 +6255,7 @@ var ExuluContext = class {
|
|
|
6197
6255
|
}
|
|
6198
6256
|
console.log("[EXULU] context configuration", this.configuration);
|
|
6199
6257
|
let jobs = [];
|
|
6200
|
-
let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "
|
|
6258
|
+
let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
|
|
6201
6259
|
for (const [key, value] of Object.entries(item)) {
|
|
6202
6260
|
console.log("[EXULU] Checking for processors for field", key);
|
|
6203
6261
|
const processor = this.fields.find((field) => field.name === key.replace("_s3key", ""))?.processor;
|
|
@@ -6220,8 +6278,13 @@ var ExuluContext = class {
|
|
|
6220
6278
|
if (processorJob) {
|
|
6221
6279
|
jobs.push(processorJob);
|
|
6222
6280
|
}
|
|
6223
|
-
if (!processorJob
|
|
6224
|
-
|
|
6281
|
+
if (!processorJob) {
|
|
6282
|
+
await db3.from(getTableName(this.id)).where({ id: results[0].id }).update({
|
|
6283
|
+
...processorResult
|
|
6284
|
+
});
|
|
6285
|
+
if (processor.config?.generateEmbeddings) {
|
|
6286
|
+
shouldGenerateEmbeddings = true;
|
|
6287
|
+
}
|
|
6225
6288
|
}
|
|
6226
6289
|
}
|
|
6227
6290
|
}
|
|
@@ -6247,6 +6310,7 @@ var ExuluContext = class {
|
|
|
6247
6310
|
};
|
|
6248
6311
|
};
|
|
6249
6312
|
updateItem = async (item, config, user, role, generateEmbeddingsOverwrite) => {
|
|
6313
|
+
console.log("[EXULU] updating item", item);
|
|
6250
6314
|
const { db: db3 } = await postgresClient();
|
|
6251
6315
|
if (item.field) {
|
|
6252
6316
|
delete item.field;
|
|
@@ -6292,8 +6356,13 @@ var ExuluContext = class {
|
|
|
6292
6356
|
if (processorJob) {
|
|
6293
6357
|
jobs.push(processorJob);
|
|
6294
6358
|
}
|
|
6295
|
-
if (!processorJob
|
|
6296
|
-
|
|
6359
|
+
if (!processorJob) {
|
|
6360
|
+
await db3.from(getTableName(this.id)).where({ id: record.id }).update({
|
|
6361
|
+
...processorResult
|
|
6362
|
+
});
|
|
6363
|
+
if (processor.config?.generateEmbeddings) {
|
|
6364
|
+
shouldGenerateEmbeddings = true;
|
|
6365
|
+
}
|
|
6297
6366
|
}
|
|
6298
6367
|
}
|
|
6299
6368
|
}
|
|
@@ -6317,23 +6386,23 @@ var ExuluContext = class {
|
|
|
6317
6386
|
};
|
|
6318
6387
|
deleteItem = async (item, user, role) => {
|
|
6319
6388
|
const { db: db3 } = await postgresClient();
|
|
6389
|
+
if (!item.id && !item.external_id) {
|
|
6390
|
+
throw new Error("Item id or external id is required for deleting an item.");
|
|
6391
|
+
}
|
|
6320
6392
|
if (!item.id?.length && item?.external_id) {
|
|
6321
6393
|
item = await db3.from(getTableName(this.id)).where({ external_id: item.external_id }).first();
|
|
6322
6394
|
if (!item || !item.id) {
|
|
6323
6395
|
throw new Error(`Item not found for external id ${item?.external_id || "undefined"}.`);
|
|
6324
6396
|
}
|
|
6325
6397
|
}
|
|
6326
|
-
|
|
6327
|
-
if (
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
}
|
|
6332
|
-
}
|
|
6333
|
-
const chunks = await db3.from(getChunksTableName(this.id)).where({ source: item.id }).select("id");
|
|
6334
|
-
if (chunks.length > 0) {
|
|
6335
|
-
await db3.from(getChunksTableName(this.id)).where({ source: item.id }).delete();
|
|
6398
|
+
const chunkTableExists = await this.chunksTableExists();
|
|
6399
|
+
if (chunkTableExists) {
|
|
6400
|
+
const chunks = await db3.from(getChunksTableName(this.id)).where({ source: item.id }).select("id");
|
|
6401
|
+
if (chunks.length > 0) {
|
|
6402
|
+
await db3.from(getChunksTableName(this.id)).where({ source: item.id }).delete();
|
|
6403
|
+
}
|
|
6336
6404
|
}
|
|
6405
|
+
await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
|
|
6337
6406
|
return {
|
|
6338
6407
|
id: item.id,
|
|
6339
6408
|
job: void 0
|
|
@@ -6684,6 +6753,18 @@ var CLAUDE_MESSAGES = {
|
|
|
6684
6753
|
var import_ai2 = require("ai");
|
|
6685
6754
|
var import_cookie_parser = __toESM(require("cookie-parser"), 1);
|
|
6686
6755
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
6756
|
+
var getExuluVersionNumber = async () => {
|
|
6757
|
+
try {
|
|
6758
|
+
const path = process.cwd();
|
|
6759
|
+
const packageJson = import_fs.default.readFileSync(path + "/package.json", "utf8");
|
|
6760
|
+
const packageData = JSON.parse(packageJson);
|
|
6761
|
+
const exuluVersion = packageData.dependencies["@exulu/backend"];
|
|
6762
|
+
console.log(`[EXULU] Installed exulu-backend version: ${exuluVersion}`);
|
|
6763
|
+
return exuluVersion;
|
|
6764
|
+
} catch (error) {
|
|
6765
|
+
console.error("Could not find or import package.json:", error.message);
|
|
6766
|
+
}
|
|
6767
|
+
};
|
|
6687
6768
|
var global_queues = {
|
|
6688
6769
|
eval_runs: "eval_runs"
|
|
6689
6770
|
};
|
|
@@ -6720,6 +6801,13 @@ var createExpressRoutes = async (app, agents, tools, contexts, config, evals, tr
|
|
|
6720
6801
|
app.use(import_body_parser.default.urlencoded({ extended: true, limit: REQUEST_SIZE_LIMIT }));
|
|
6721
6802
|
app.use(import_body_parser.default.json({ limit: REQUEST_SIZE_LIMIT }));
|
|
6722
6803
|
app.use((0, import_cookie_parser.default)());
|
|
6804
|
+
app.use(async (req, res, next) => {
|
|
6805
|
+
const version = await getExuluVersionNumber();
|
|
6806
|
+
if (version) {
|
|
6807
|
+
res.setHeader("exulu-version", version);
|
|
6808
|
+
}
|
|
6809
|
+
next();
|
|
6810
|
+
});
|
|
6723
6811
|
console.log(`
|
|
6724
6812
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
|
|
6725
6813
|
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
@@ -7125,7 +7213,7 @@ Mood: friendly and intelligent.
|
|
|
7125
7213
|
});
|
|
7126
7214
|
});
|
|
7127
7215
|
if (config?.fileUploads && config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
|
|
7128
|
-
await createUppyRoutes(app, config);
|
|
7216
|
+
await createUppyRoutes(app, contexts ?? [], config);
|
|
7129
7217
|
} else {
|
|
7130
7218
|
console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in ExuluApp instance.");
|
|
7131
7219
|
}
|
|
@@ -7438,6 +7526,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7438
7526
|
async (bullmqJob) => {
|
|
7439
7527
|
console.log("[EXULU] starting execution for job", logMetadata2(bullmqJob.name, {
|
|
7440
7528
|
name: bullmqJob.name,
|
|
7529
|
+
jobId: bullmqJob.id,
|
|
7441
7530
|
status: await bullmqJob.getState(),
|
|
7442
7531
|
type: bullmqJob.data.type
|
|
7443
7532
|
}));
|
|
@@ -7452,6 +7541,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7452
7541
|
});
|
|
7453
7542
|
const workPromise = (async () => {
|
|
7454
7543
|
try {
|
|
7544
|
+
console.log(`[EXULU] Job ${bullmqJob.id} - Log file: logs/jobs/job-${bullmqJob.id}.log`);
|
|
7455
7545
|
bullmq.validate(bullmqJob.id, data);
|
|
7456
7546
|
if (data.type === "embedder") {
|
|
7457
7547
|
console.log("[EXULU] running an embedder job.", logMetadata2(bullmqJob.name));
|
|
@@ -7474,17 +7564,17 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7474
7564
|
if (!embedder) {
|
|
7475
7565
|
throw new Error(`Embedder ${data.embedder} not found in the registry.`);
|
|
7476
7566
|
}
|
|
7477
|
-
const
|
|
7567
|
+
const result2 = await context.createAndUpsertEmbeddings(data.inputs, config, data.user, {
|
|
7478
7568
|
label: embedder.name,
|
|
7479
7569
|
trigger: data.trigger
|
|
7480
7570
|
}, data.role, bullmqJob.id);
|
|
7481
7571
|
return {
|
|
7482
|
-
result,
|
|
7572
|
+
result: result2,
|
|
7483
7573
|
metadata: {}
|
|
7484
7574
|
};
|
|
7485
7575
|
}
|
|
7486
7576
|
if (data.type === "processor") {
|
|
7487
|
-
console.log("[EXULU] running a processor job.",
|
|
7577
|
+
console.log("[EXULU] running a processor job, job name: ", bullmqJob.name, " job id: ", bullmqJob.id, " job data: ", data, " job queue: ", bullmqJob.queueName);
|
|
7488
7578
|
const label = `processor-${bullmqJob.name}`;
|
|
7489
7579
|
await db3.from("job_results").insert({
|
|
7490
7580
|
job_id: bullmqJob.id,
|
|
@@ -7506,35 +7596,42 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7506
7596
|
if (!field.processor) {
|
|
7507
7597
|
throw new Error(`Processor not set for field ${data.inputs.field} in the context ${data.context}.`);
|
|
7508
7598
|
}
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
throw new Error(`User not set for processor job.`);
|
|
7512
|
-
}
|
|
7513
|
-
if (!data.role) {
|
|
7514
|
-
throw new Error(`Role not set for processor job.`);
|
|
7599
|
+
if (!data.inputs.id) {
|
|
7600
|
+
throw new Error(`[EXULU] Item not set for processor ${field.name} in context ${context.id}, running in job ${bullmqJob.id}.`);
|
|
7515
7601
|
}
|
|
7602
|
+
const exuluStorage = new ExuluStorage({ config });
|
|
7516
7603
|
console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
|
|
7517
|
-
const
|
|
7604
|
+
const processorResult = await field.processor.execute({
|
|
7518
7605
|
item: data.inputs,
|
|
7519
7606
|
user: data.user,
|
|
7520
7607
|
role: data.role,
|
|
7521
7608
|
utils: {
|
|
7522
|
-
storage: exuluStorage
|
|
7523
|
-
items: {
|
|
7524
|
-
update: context.updateItem,
|
|
7525
|
-
create: context.createItem,
|
|
7526
|
-
delete: context.deleteItem
|
|
7527
|
-
}
|
|
7609
|
+
storage: exuluStorage
|
|
7528
7610
|
},
|
|
7529
|
-
config
|
|
7611
|
+
exuluConfig: config
|
|
7612
|
+
});
|
|
7613
|
+
if (!processorResult) {
|
|
7614
|
+
throw new Error(`[EXULU] Processor ${field.name} in context ${context.id}, running in job ${bullmqJob.id} did not return an item.`);
|
|
7615
|
+
}
|
|
7616
|
+
delete processorResult.field;
|
|
7617
|
+
await db3.from(getTableName(context.id)).where({
|
|
7618
|
+
id: processorResult.id
|
|
7619
|
+
}).update({
|
|
7620
|
+
...processorResult
|
|
7530
7621
|
});
|
|
7531
7622
|
let jobs = [];
|
|
7532
7623
|
if (field.processor.config?.generateEmbeddings) {
|
|
7624
|
+
const fullItem = await db3.from(getTableName(context.id)).where({
|
|
7625
|
+
id: processorResult.id
|
|
7626
|
+
}).first();
|
|
7627
|
+
if (!fullItem) {
|
|
7628
|
+
throw new Error(`[EXULU] Item ${processorResult.id} not found after processor update in context ${context.id}`);
|
|
7629
|
+
}
|
|
7533
7630
|
const { job: embeddingsJob } = await context.embeddings.generate.one({
|
|
7534
|
-
item:
|
|
7631
|
+
item: fullItem,
|
|
7535
7632
|
user: data.user,
|
|
7536
7633
|
role: data.role,
|
|
7537
|
-
trigger: "
|
|
7634
|
+
trigger: "processor",
|
|
7538
7635
|
config
|
|
7539
7636
|
});
|
|
7540
7637
|
if (embeddingsJob) {
|
|
@@ -7542,7 +7639,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7542
7639
|
}
|
|
7543
7640
|
}
|
|
7544
7641
|
return {
|
|
7545
|
-
result,
|
|
7642
|
+
result: processorResult,
|
|
7546
7643
|
metadata: {
|
|
7547
7644
|
jobs: jobs.length > 0 ? jobs.join(",") : void 0
|
|
7548
7645
|
}
|
|
@@ -7609,9 +7706,9 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7609
7706
|
}
|
|
7610
7707
|
}
|
|
7611
7708
|
});
|
|
7612
|
-
const
|
|
7613
|
-
const messages =
|
|
7614
|
-
const metadata =
|
|
7709
|
+
const result2 = await promise;
|
|
7710
|
+
const messages = result2.messages;
|
|
7711
|
+
const metadata = result2.metadata;
|
|
7615
7712
|
const evalFunctions = evalRun.eval_functions;
|
|
7616
7713
|
let evalFunctionResults = [];
|
|
7617
7714
|
for (const evalFunction of evalFunctions) {
|
|
@@ -7619,7 +7716,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7619
7716
|
if (!evalMethod) {
|
|
7620
7717
|
throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
|
|
7621
7718
|
}
|
|
7622
|
-
let
|
|
7719
|
+
let result3;
|
|
7623
7720
|
if (evalMethod.queue) {
|
|
7624
7721
|
const queue2 = await evalMethod.queue;
|
|
7625
7722
|
const jobData = {
|
|
@@ -7650,21 +7747,21 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7650
7747
|
if (!job.id) {
|
|
7651
7748
|
throw new Error(`Tried to add job to queue ${queue2.queue.name} but failed to get the job ID.`);
|
|
7652
7749
|
}
|
|
7653
|
-
|
|
7750
|
+
result3 = await pollJobResult({ queue: queue2, jobId: job.id });
|
|
7654
7751
|
const evalFunctionResult = {
|
|
7655
7752
|
test_case_id: testCase.id,
|
|
7656
7753
|
eval_run_id: evalRun.id,
|
|
7657
7754
|
eval_function_id: evalFunction.id,
|
|
7658
7755
|
eval_function_name: evalFunction.name,
|
|
7659
7756
|
eval_function_config: evalFunction.config || {},
|
|
7660
|
-
result:
|
|
7757
|
+
result: result3 || 0
|
|
7661
7758
|
};
|
|
7662
|
-
console.log(`[EXULU] eval function ${evalFunction.id} result: ${
|
|
7663
|
-
result:
|
|
7759
|
+
console.log(`[EXULU] eval function ${evalFunction.id} result: ${result3}`, logMetadata2(bullmqJob.name, {
|
|
7760
|
+
result: result3 || 0
|
|
7664
7761
|
}));
|
|
7665
7762
|
evalFunctionResults.push(evalFunctionResult);
|
|
7666
7763
|
} else {
|
|
7667
|
-
|
|
7764
|
+
result3 = await evalMethod.run(
|
|
7668
7765
|
agentInstance,
|
|
7669
7766
|
agentBackend,
|
|
7670
7767
|
testCase,
|
|
@@ -7675,15 +7772,15 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7675
7772
|
test_case_id: testCase.id,
|
|
7676
7773
|
eval_run_id: evalRun.id,
|
|
7677
7774
|
eval_function_id: evalFunction.id,
|
|
7678
|
-
result:
|
|
7775
|
+
result: result3 || 0
|
|
7679
7776
|
};
|
|
7680
7777
|
evalFunctionResults.push(evalFunctionResult);
|
|
7681
|
-
console.log(`[EXULU] eval function ${evalFunction.id} result: ${
|
|
7682
|
-
result:
|
|
7778
|
+
console.log(`[EXULU] eval function ${evalFunction.id} result: ${result3}`, logMetadata2(bullmqJob.name, {
|
|
7779
|
+
result: result3 || 0
|
|
7683
7780
|
}));
|
|
7684
7781
|
}
|
|
7685
7782
|
}
|
|
7686
|
-
const scores = evalFunctionResults.map((
|
|
7783
|
+
const scores = evalFunctionResults.map((result3) => result3.result);
|
|
7687
7784
|
console.log("[EXULU] Exulu eval run scores for test case: " + testCase.id, scores);
|
|
7688
7785
|
let score = 0;
|
|
7689
7786
|
switch (data.scoring_method?.toLowerCase()) {
|
|
@@ -7748,25 +7845,25 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7748
7845
|
messages: inputMessages
|
|
7749
7846
|
} = await validateEvalPayload(data, agents);
|
|
7750
7847
|
const evalFunctions = evalRun.eval_functions;
|
|
7751
|
-
let
|
|
7848
|
+
let result2;
|
|
7752
7849
|
for (const evalFunction of evalFunctions) {
|
|
7753
7850
|
const evalMethod = evals.find((e) => e.id === evalFunction.id);
|
|
7754
7851
|
if (!evalMethod) {
|
|
7755
7852
|
throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
|
|
7756
7853
|
}
|
|
7757
|
-
|
|
7854
|
+
result2 = await evalMethod.run(
|
|
7758
7855
|
agentInstance,
|
|
7759
7856
|
backend,
|
|
7760
7857
|
testCase,
|
|
7761
7858
|
inputMessages,
|
|
7762
7859
|
evalFunction.config || {}
|
|
7763
7860
|
);
|
|
7764
|
-
console.log(`[EXULU] eval function ${evalFunction.id} result: ${
|
|
7765
|
-
result:
|
|
7861
|
+
console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata2(bullmqJob.name, {
|
|
7862
|
+
result: result2 || 0
|
|
7766
7863
|
}));
|
|
7767
7864
|
}
|
|
7768
7865
|
return {
|
|
7769
|
-
result,
|
|
7866
|
+
result: result2,
|
|
7770
7867
|
metadata: {}
|
|
7771
7868
|
};
|
|
7772
7869
|
}
|
|
@@ -7786,10 +7883,10 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7786
7883
|
if (!source) {
|
|
7787
7884
|
throw new Error(`Source ${data.source} not found in the context ${context.id}.`);
|
|
7788
7885
|
}
|
|
7789
|
-
const
|
|
7886
|
+
const result2 = await source.execute(data.inputs);
|
|
7790
7887
|
let jobs = [];
|
|
7791
7888
|
let items = [];
|
|
7792
|
-
for (const item of
|
|
7889
|
+
for (const item of result2) {
|
|
7793
7890
|
const { item: createdItem, job } = await context.createItem(
|
|
7794
7891
|
item,
|
|
7795
7892
|
config,
|
|
@@ -7821,7 +7918,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7821
7918
|
role: data?.role
|
|
7822
7919
|
});
|
|
7823
7920
|
return {
|
|
7824
|
-
result,
|
|
7921
|
+
result: result2,
|
|
7825
7922
|
metadata: {
|
|
7826
7923
|
jobs,
|
|
7827
7924
|
items
|
|
@@ -7834,11 +7931,13 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7834
7931
|
throw error;
|
|
7835
7932
|
}
|
|
7836
7933
|
})();
|
|
7837
|
-
|
|
7934
|
+
const result = await Promise.race([workPromise, timeoutPromise]);
|
|
7935
|
+
return result;
|
|
7838
7936
|
},
|
|
7839
7937
|
{
|
|
7840
7938
|
autorun: true,
|
|
7841
7939
|
connection: redisConnection,
|
|
7940
|
+
concurrency: queue.concurrency?.worker || 1,
|
|
7842
7941
|
removeOnComplete: { count: 1e3 },
|
|
7843
7942
|
removeOnFail: { count: 5e3 },
|
|
7844
7943
|
...queue.ratelimit && {
|
|
@@ -9042,18 +9141,23 @@ var ExuluQueues = class {
|
|
|
9042
9141
|
// method of ExuluQueues we need to store the desired rate limit on the queue
|
|
9043
9142
|
// here so we can use the value when creating workers for the queue instance
|
|
9044
9143
|
// as there is no way to store a rate limit value natively on a bullm queue.
|
|
9045
|
-
register = (name, concurrency
|
|
9144
|
+
register = (name, concurrency, ratelimit = 1) => {
|
|
9145
|
+
const queueConcurrency = concurrency.queue || 1;
|
|
9146
|
+
const workerConcurrency = concurrency.worker || 1;
|
|
9046
9147
|
const use = async () => {
|
|
9047
9148
|
const existing = this.queues.find((x) => x.queue?.name === name);
|
|
9048
9149
|
if (existing) {
|
|
9049
9150
|
const globalConcurrency = await existing.queue.getGlobalConcurrency();
|
|
9050
|
-
if (globalConcurrency !==
|
|
9051
|
-
await existing.queue.setGlobalConcurrency(
|
|
9151
|
+
if (globalConcurrency !== queueConcurrency) {
|
|
9152
|
+
await existing.queue.setGlobalConcurrency(queueConcurrency);
|
|
9052
9153
|
}
|
|
9053
9154
|
return {
|
|
9054
9155
|
queue: existing.queue,
|
|
9055
9156
|
ratelimit,
|
|
9056
|
-
concurrency
|
|
9157
|
+
concurrency: {
|
|
9158
|
+
worker: workerConcurrency,
|
|
9159
|
+
queue: queueConcurrency
|
|
9160
|
+
}
|
|
9057
9161
|
};
|
|
9058
9162
|
}
|
|
9059
9163
|
if (!redisServer.host?.length || !redisServer.port?.length) {
|
|
@@ -9072,21 +9176,30 @@ var ExuluQueues = class {
|
|
|
9072
9176
|
telemetry: new import_bullmq_otel.BullMQOtel("simple-guide")
|
|
9073
9177
|
}
|
|
9074
9178
|
);
|
|
9075
|
-
await newQueue.setGlobalConcurrency(
|
|
9179
|
+
await newQueue.setGlobalConcurrency(queueConcurrency);
|
|
9076
9180
|
this.queues.push({
|
|
9077
9181
|
queue: newQueue,
|
|
9078
9182
|
ratelimit,
|
|
9079
|
-
concurrency
|
|
9183
|
+
concurrency: {
|
|
9184
|
+
worker: workerConcurrency,
|
|
9185
|
+
queue: queueConcurrency
|
|
9186
|
+
}
|
|
9080
9187
|
});
|
|
9081
9188
|
return {
|
|
9082
9189
|
queue: newQueue,
|
|
9083
9190
|
ratelimit,
|
|
9084
|
-
concurrency
|
|
9191
|
+
concurrency: {
|
|
9192
|
+
worker: workerConcurrency,
|
|
9193
|
+
queue: queueConcurrency
|
|
9194
|
+
}
|
|
9085
9195
|
};
|
|
9086
9196
|
};
|
|
9087
9197
|
this.list.set(name, {
|
|
9088
9198
|
name,
|
|
9089
|
-
concurrency
|
|
9199
|
+
concurrency: {
|
|
9200
|
+
worker: workerConcurrency,
|
|
9201
|
+
queue: queueConcurrency
|
|
9202
|
+
},
|
|
9090
9203
|
ratelimit,
|
|
9091
9204
|
use
|
|
9092
9205
|
});
|
|
@@ -9142,7 +9255,10 @@ var llmAsJudgeEval = () => {
|
|
|
9142
9255
|
name: "prompt",
|
|
9143
9256
|
description: "The prompt to send to the LLM as a judge, make sure to instruct the LLM to output a numerical score between 0 and 100. Add {actual_output} to the prompt to replace with the last message content, and {expected_output} to replace with the expected output."
|
|
9144
9257
|
}],
|
|
9145
|
-
queue: queues.register("llm_as_judge",
|
|
9258
|
+
queue: queues.register("llm_as_judge", {
|
|
9259
|
+
worker: 1,
|
|
9260
|
+
queue: 1
|
|
9261
|
+
}, 1).use(),
|
|
9146
9262
|
llm: true
|
|
9147
9263
|
});
|
|
9148
9264
|
}
|
|
@@ -9777,11 +9893,16 @@ var previewPdfTool = new ExuluTool2({
|
|
|
9777
9893
|
type: "function",
|
|
9778
9894
|
config: [],
|
|
9779
9895
|
inputSchema: import_zod5.z.object({
|
|
9780
|
-
s3key: import_zod5.z.string().describe("The S3 key of the PDF file to preview
|
|
9896
|
+
s3key: import_zod5.z.string().describe("The S3 key of the PDF file to preview."),
|
|
9781
9897
|
page: import_zod5.z.number().describe("The page number to preview, defaults to 1.").optional()
|
|
9782
9898
|
}),
|
|
9783
9899
|
execute: async ({ s3key, page, exuluConfig }) => {
|
|
9784
|
-
const
|
|
9900
|
+
const bucket = s3key.split("/")[0];
|
|
9901
|
+
const key = s3key.split("/").slice(1).join("/");
|
|
9902
|
+
if (!bucket || !key) {
|
|
9903
|
+
throw new Error("Invalid S3 key, must be in the format of <bucket>/<key>.");
|
|
9904
|
+
}
|
|
9905
|
+
const url = await getPresignedUrl(bucket, key, exuluConfig);
|
|
9785
9906
|
if (!url) {
|
|
9786
9907
|
throw new Error("No URL provided for PDF preview");
|
|
9787
9908
|
}
|
|
@@ -10175,7 +10296,10 @@ var ExuluApp = class {
|
|
|
10175
10296
|
}
|
|
10176
10297
|
const queueSet = /* @__PURE__ */ new Set();
|
|
10177
10298
|
if (redisServer.host?.length && redisServer.port?.length) {
|
|
10178
|
-
queues.register(global_queues.eval_runs,
|
|
10299
|
+
queues.register(global_queues.eval_runs, {
|
|
10300
|
+
worker: 1,
|
|
10301
|
+
queue: 1
|
|
10302
|
+
}, 1);
|
|
10179
10303
|
for (const queue of queues.list.values()) {
|
|
10180
10304
|
const config2 = await queue.use();
|
|
10181
10305
|
queueSet.add(config2);
|