@exulu/backend 1.27.2 → 1.28.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/.github/workflows/{release.yml → release-backend.yml} +2 -2
- package/.nvmrc +1 -1
- package/CHANGELOG.md +2 -2
- package/dist/index.cjs +1490 -572
- package/dist/index.d.cts +161 -28
- package/dist/index.d.ts +161 -28
- package/dist/index.js +1486 -569
- package/package.json +3 -3
- package/types/models/eval-run.ts +37 -0
- package/types/models/test-case.ts +25 -0
package/dist/index.cjs
CHANGED
|
@@ -40,14 +40,15 @@ __export(index_exports, {
|
|
|
40
40
|
ExuluDefaultAgents: () => ExuluDefaultAgents,
|
|
41
41
|
ExuluDefaultContexts: () => ExuluDefaultContexts,
|
|
42
42
|
ExuluEmbedder: () => ExuluEmbedder,
|
|
43
|
-
ExuluEval: () =>
|
|
43
|
+
ExuluEval: () => ExuluEval2,
|
|
44
44
|
ExuluJobs: () => ExuluJobs,
|
|
45
45
|
ExuluOtel: () => ExuluOtel,
|
|
46
46
|
ExuluQueues: () => queues,
|
|
47
47
|
ExuluTool: () => ExuluTool2,
|
|
48
48
|
ExuluUtils: () => ExuluUtils,
|
|
49
49
|
ExuluVariables: () => ExuluVariables,
|
|
50
|
-
db: () => db2
|
|
50
|
+
db: () => db2,
|
|
51
|
+
logMetadata: () => logMetadata
|
|
51
52
|
});
|
|
52
53
|
module.exports = __toCommonJS(index_exports);
|
|
53
54
|
var import_config = require("dotenv/config");
|
|
@@ -150,7 +151,17 @@ async function ensureDatabaseExists() {
|
|
|
150
151
|
database: "postgres",
|
|
151
152
|
// Connect to default database
|
|
152
153
|
password: process.env.POSTGRES_DB_PASSWORD,
|
|
153
|
-
ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
|
|
154
|
+
ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false,
|
|
155
|
+
connectionTimeoutMillis: 1e4
|
|
156
|
+
},
|
|
157
|
+
pool: {
|
|
158
|
+
min: 2,
|
|
159
|
+
max: 4,
|
|
160
|
+
acquireTimeoutMillis: 3e4,
|
|
161
|
+
createTimeoutMillis: 3e4,
|
|
162
|
+
idleTimeoutMillis: 3e4,
|
|
163
|
+
reapIntervalMillis: 1e3,
|
|
164
|
+
createRetryIntervalMillis: 200
|
|
154
165
|
}
|
|
155
166
|
});
|
|
156
167
|
try {
|
|
@@ -195,7 +206,17 @@ async function postgresClient() {
|
|
|
195
206
|
user: process.env.POSTGRES_DB_USER,
|
|
196
207
|
database: dbName,
|
|
197
208
|
password: process.env.POSTGRES_DB_PASSWORD,
|
|
198
|
-
ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
|
|
209
|
+
ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false,
|
|
210
|
+
connectionTimeoutMillis: 1e4
|
|
211
|
+
},
|
|
212
|
+
pool: {
|
|
213
|
+
min: 2,
|
|
214
|
+
max: 20,
|
|
215
|
+
acquireTimeoutMillis: 3e4,
|
|
216
|
+
createTimeoutMillis: 3e4,
|
|
217
|
+
idleTimeoutMillis: 3e4,
|
|
218
|
+
reapIntervalMillis: 1e3,
|
|
219
|
+
createRetryIntervalMillis: 200
|
|
199
220
|
}
|
|
200
221
|
});
|
|
201
222
|
try {
|
|
@@ -243,7 +264,9 @@ var bullmqDecorator = async ({
|
|
|
243
264
|
workflow,
|
|
244
265
|
item,
|
|
245
266
|
context,
|
|
246
|
-
retries
|
|
267
|
+
retries,
|
|
268
|
+
backoff,
|
|
269
|
+
timeoutInSeconds
|
|
247
270
|
}) => {
|
|
248
271
|
const types = [
|
|
249
272
|
embedder,
|
|
@@ -267,30 +290,35 @@ var bullmqDecorator = async ({
|
|
|
267
290
|
if (embedder) {
|
|
268
291
|
type = "embedder";
|
|
269
292
|
}
|
|
293
|
+
const jobData = {
|
|
294
|
+
label,
|
|
295
|
+
type: `${type}`,
|
|
296
|
+
timeoutInSeconds: timeoutInSeconds || 180,
|
|
297
|
+
// 3 minutes default
|
|
298
|
+
inputs,
|
|
299
|
+
...user && { user },
|
|
300
|
+
...role && { role },
|
|
301
|
+
...trigger && { trigger },
|
|
302
|
+
...workflow && { workflow },
|
|
303
|
+
...embedder && { embedder },
|
|
304
|
+
...processor && { processor },
|
|
305
|
+
...evaluation && { evaluation },
|
|
306
|
+
...item && { item },
|
|
307
|
+
...context && { context }
|
|
308
|
+
};
|
|
270
309
|
const redisId = (0, import_uuid.v4)();
|
|
271
310
|
const job = await queue.add(
|
|
272
311
|
`${embedder || workflow || processor || evaluation}`,
|
|
273
|
-
|
|
274
|
-
label,
|
|
275
|
-
type: `${type}`,
|
|
276
|
-
inputs,
|
|
277
|
-
...user && { user },
|
|
278
|
-
...role && { role },
|
|
279
|
-
...trigger && { trigger },
|
|
280
|
-
...workflow && { workflow },
|
|
281
|
-
...embedder && { embedder },
|
|
282
|
-
...processor && { processor },
|
|
283
|
-
...evaluation && { evaluation },
|
|
284
|
-
...item && { item },
|
|
285
|
-
...context && { context }
|
|
286
|
-
},
|
|
312
|
+
jobData,
|
|
287
313
|
{
|
|
288
314
|
jobId: redisId,
|
|
289
315
|
// Setting it to 3 as a sensible default, as
|
|
290
316
|
// many AI services are quite unstable.
|
|
291
317
|
attempts: retries || 3,
|
|
292
318
|
// todo make this configurable?
|
|
293
|
-
|
|
319
|
+
removeOnComplete: 5e3,
|
|
320
|
+
removeOnFail: 1e4,
|
|
321
|
+
backoff: backoff || {
|
|
294
322
|
type: "exponential",
|
|
295
323
|
delay: 2e3
|
|
296
324
|
}
|
|
@@ -735,6 +763,17 @@ var requestValidators = {
|
|
|
735
763
|
// src/registry/utils/graphql.ts
|
|
736
764
|
var import_bcryptjs3 = __toESM(require("bcryptjs"), 1);
|
|
737
765
|
|
|
766
|
+
// types/enums/jobs.ts
|
|
767
|
+
var JOB_STATUS_ENUM = {
|
|
768
|
+
completed: "completed",
|
|
769
|
+
failed: "failed",
|
|
770
|
+
delayed: "delayed",
|
|
771
|
+
active: "active",
|
|
772
|
+
waiting: "waiting",
|
|
773
|
+
paused: "paused",
|
|
774
|
+
stuck: "stuck"
|
|
775
|
+
};
|
|
776
|
+
|
|
738
777
|
// src/postgres/core-schema.ts
|
|
739
778
|
var agentMessagesSchema = {
|
|
740
779
|
type: "agent_messages",
|
|
@@ -1169,6 +1208,45 @@ var evalSetsSchema = {
|
|
|
1169
1208
|
}
|
|
1170
1209
|
]
|
|
1171
1210
|
};
|
|
1211
|
+
var jobResultsSchema = {
|
|
1212
|
+
type: "job_results",
|
|
1213
|
+
name: {
|
|
1214
|
+
plural: "job_results",
|
|
1215
|
+
singular: "job_result"
|
|
1216
|
+
},
|
|
1217
|
+
fields: [
|
|
1218
|
+
{
|
|
1219
|
+
name: "job_id",
|
|
1220
|
+
type: "text"
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
name: "state",
|
|
1224
|
+
type: "text"
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
name: "error",
|
|
1228
|
+
type: "json"
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
name: "label",
|
|
1232
|
+
type: "text",
|
|
1233
|
+
index: true
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
name: "tries",
|
|
1237
|
+
type: "number",
|
|
1238
|
+
default: 0
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
name: "result",
|
|
1242
|
+
type: "json"
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
name: "metadata",
|
|
1246
|
+
type: "json"
|
|
1247
|
+
}
|
|
1248
|
+
]
|
|
1249
|
+
};
|
|
1172
1250
|
var evalRunsSchema = {
|
|
1173
1251
|
type: "eval_runs",
|
|
1174
1252
|
name: {
|
|
@@ -1177,6 +1255,15 @@ var evalRunsSchema = {
|
|
|
1177
1255
|
},
|
|
1178
1256
|
RBAC: true,
|
|
1179
1257
|
fields: [
|
|
1258
|
+
{
|
|
1259
|
+
name: "name",
|
|
1260
|
+
type: "text"
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
name: "timeout_in_seconds",
|
|
1264
|
+
type: "number",
|
|
1265
|
+
default: 180
|
|
1266
|
+
},
|
|
1180
1267
|
{
|
|
1181
1268
|
name: "eval_set_id",
|
|
1182
1269
|
type: "uuid",
|
|
@@ -1188,7 +1275,7 @@ var evalRunsSchema = {
|
|
|
1188
1275
|
required: true
|
|
1189
1276
|
},
|
|
1190
1277
|
{
|
|
1191
|
-
name: "
|
|
1278
|
+
name: "eval_functions",
|
|
1192
1279
|
type: "json",
|
|
1193
1280
|
required: true
|
|
1194
1281
|
},
|
|
@@ -1199,7 +1286,7 @@ var evalRunsSchema = {
|
|
|
1199
1286
|
{
|
|
1200
1287
|
name: "scoring_method",
|
|
1201
1288
|
type: "enum",
|
|
1202
|
-
enumValues: ["
|
|
1289
|
+
enumValues: ["median", "sum", "average"],
|
|
1203
1290
|
required: true
|
|
1204
1291
|
},
|
|
1205
1292
|
{
|
|
@@ -1323,7 +1410,8 @@ var coreSchemas = {
|
|
|
1323
1410
|
variablesSchema: () => addCoreFields(variablesSchema),
|
|
1324
1411
|
rbacSchema: () => addCoreFields(rbacSchema),
|
|
1325
1412
|
workflowTemplatesSchema: () => addCoreFields(workflowTemplatesSchema),
|
|
1326
|
-
platformConfigurationsSchema: () => addCoreFields(platformConfigurationsSchema)
|
|
1413
|
+
platformConfigurationsSchema: () => addCoreFields(platformConfigurationsSchema),
|
|
1414
|
+
jobResultsSchema: () => addCoreFields(jobResultsSchema)
|
|
1327
1415
|
};
|
|
1328
1416
|
}
|
|
1329
1417
|
};
|
|
@@ -1386,11 +1474,11 @@ var bullmq = {
|
|
|
1386
1474
|
if (!data.inputs) {
|
|
1387
1475
|
throw new Error(`Missing property "inputs" in data for job ${id}.`);
|
|
1388
1476
|
}
|
|
1389
|
-
if (data.type !== "embedder" && data.type !== "workflow") {
|
|
1390
|
-
throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "
|
|
1477
|
+
if (data.type !== "embedder" && data.type !== "workflow" && data.type !== "processor" && data.type !== "eval_run" && data.type !== "eval_function") {
|
|
1478
|
+
throw new Error(`Property "type" in data for job ${id} must be of value "embedder", "workflow", "processor", "eval_run" or "eval_function".`);
|
|
1391
1479
|
}
|
|
1392
|
-
if (!data.workflow && !data.embedder) {
|
|
1393
|
-
throw new Error(`Either a workflow or
|
|
1480
|
+
if (!data.workflow && !data.embedder && !data.processor && !data.eval_run_id && !data.eval_functions?.length) {
|
|
1481
|
+
throw new Error(`Either a workflow, embedder, processor, eval_run or eval_functions must be set for job ${id}.`);
|
|
1394
1482
|
}
|
|
1395
1483
|
}
|
|
1396
1484
|
};
|
|
@@ -1594,6 +1682,7 @@ var generateApiKey = async (name, email) => {
|
|
|
1594
1682
|
};
|
|
1595
1683
|
|
|
1596
1684
|
// src/registry/utils/graphql.ts
|
|
1685
|
+
var import_uuid2 = require("uuid");
|
|
1597
1686
|
var GraphQLDate = new import_graphql2.GraphQLScalarType({
|
|
1598
1687
|
name: "Date",
|
|
1599
1688
|
description: "Date custom scalar type",
|
|
@@ -1689,7 +1778,6 @@ ${enumValues}
|
|
|
1689
1778
|
fields.push(" maxContextLength: Int");
|
|
1690
1779
|
fields.push(" provider: String");
|
|
1691
1780
|
fields.push(" slug: String");
|
|
1692
|
-
fields.push(" evals: [AgentEvalFunction]");
|
|
1693
1781
|
}
|
|
1694
1782
|
const rbacField = table.RBAC ? " RBAC: RBACData" : "";
|
|
1695
1783
|
const typeDef = `
|
|
@@ -1901,11 +1989,6 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
1901
1989
|
if (!record) {
|
|
1902
1990
|
throw new Error("Record not found");
|
|
1903
1991
|
}
|
|
1904
|
-
if (tableNamePlural === "jobs") {
|
|
1905
|
-
if (!user.super_admin && record.created_by !== user.id) {
|
|
1906
|
-
throw new Error("You are not authorized to edit this record");
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
1992
|
if (record.rights_mode === "public") {
|
|
1910
1993
|
return true;
|
|
1911
1994
|
}
|
|
@@ -2328,10 +2411,6 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2328
2411
|
}
|
|
2329
2412
|
var applyAccessControl = (table, user, query) => {
|
|
2330
2413
|
const tableNamePlural = table.name.plural.toLowerCase();
|
|
2331
|
-
if (!user.super_admin && table.name.plural === "jobs") {
|
|
2332
|
-
query = query.where("created_by", user.id);
|
|
2333
|
-
return query;
|
|
2334
|
-
}
|
|
2335
2414
|
if (table.name.plural !== "agent_sessions" && user.super_admin === true) {
|
|
2336
2415
|
return query;
|
|
2337
2416
|
}
|
|
@@ -2466,14 +2545,6 @@ var addAgentFields = async (requestedFields, agents, result, tools, user) => {
|
|
|
2466
2545
|
if (requestedFields.includes("provider")) {
|
|
2467
2546
|
result.provider = backend?.provider || "";
|
|
2468
2547
|
}
|
|
2469
|
-
if (requestedFields.includes("evals")) {
|
|
2470
|
-
result.evals = backend?.evals?.map((evalFunc) => ({
|
|
2471
|
-
id: evalFunc.id,
|
|
2472
|
-
name: evalFunc.name,
|
|
2473
|
-
description: evalFunc.description,
|
|
2474
|
-
config: evalFunc.config || []
|
|
2475
|
-
})) || [];
|
|
2476
|
-
}
|
|
2477
2548
|
if (!requestedFields.includes("backend")) {
|
|
2478
2549
|
delete result.backend;
|
|
2479
2550
|
}
|
|
@@ -3176,7 +3247,7 @@ var contextToTableDefinition = (context) => {
|
|
|
3176
3247
|
});
|
|
3177
3248
|
return addCoreFields(definition);
|
|
3178
3249
|
};
|
|
3179
|
-
function createSDL(tables, contexts, agents, tools, config) {
|
|
3250
|
+
function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
|
|
3180
3251
|
const contextSchemas = contexts.map((context) => contextToTableDefinition(context));
|
|
3181
3252
|
tables.forEach((table) => {
|
|
3182
3253
|
if (!table.fields.some((field) => field.name === "createdAt")) {
|
|
@@ -3371,15 +3442,39 @@ type PageInfo {
|
|
|
3371
3442
|
typeDefs += `
|
|
3372
3443
|
providers: ProviderPaginationResult
|
|
3373
3444
|
`;
|
|
3445
|
+
typeDefs += `
|
|
3446
|
+
queue(queue: QueueEnum!): QueueResult
|
|
3447
|
+
`;
|
|
3448
|
+
typeDefs += `
|
|
3449
|
+
evals: EvalPaginationResult
|
|
3450
|
+
`;
|
|
3374
3451
|
typeDefs += `
|
|
3375
3452
|
contexts: ContextPaginationResult
|
|
3376
3453
|
`;
|
|
3377
3454
|
typeDefs += `
|
|
3378
3455
|
contextById(id: ID!): Context
|
|
3379
3456
|
`;
|
|
3457
|
+
mutationDefs += `
|
|
3458
|
+
runEval(id: ID!, test_case_ids: [ID!]): RunEvalReturnPayload
|
|
3459
|
+
`;
|
|
3460
|
+
mutationDefs += `
|
|
3461
|
+
drainQueue(queue: QueueEnum!): JobActionReturnPayload
|
|
3462
|
+
`;
|
|
3463
|
+
mutationDefs += `
|
|
3464
|
+
pauseQueue(queue: QueueEnum!): JobActionReturnPayload
|
|
3465
|
+
`;
|
|
3466
|
+
mutationDefs += `
|
|
3467
|
+
resumeQueue(queue: QueueEnum!): JobActionReturnPayload
|
|
3468
|
+
`;
|
|
3469
|
+
mutationDefs += `
|
|
3470
|
+
deleteJob(queue: QueueEnum!, id: String!): JobActionReturnPayload
|
|
3471
|
+
`;
|
|
3380
3472
|
typeDefs += `
|
|
3381
3473
|
tools: ToolPaginationResult
|
|
3382
3474
|
`;
|
|
3475
|
+
typeDefs += `
|
|
3476
|
+
jobs(queue: QueueEnum!, statusses: [JobStateEnum!]): JobPaginationResult
|
|
3477
|
+
`;
|
|
3383
3478
|
resolvers.Query["providers"] = async (_, args, context, info) => {
|
|
3384
3479
|
const requestedFields = getRequestedFields(info);
|
|
3385
3480
|
return {
|
|
@@ -3392,6 +3487,227 @@ type PageInfo {
|
|
|
3392
3487
|
})
|
|
3393
3488
|
};
|
|
3394
3489
|
};
|
|
3490
|
+
resolvers.Query["queue"] = async (_, args, context, info) => {
|
|
3491
|
+
if (!args.queue) {
|
|
3492
|
+
throw new Error("Queue name is required");
|
|
3493
|
+
}
|
|
3494
|
+
const queue = queues.list.get(args.queue);
|
|
3495
|
+
if (!queue) {
|
|
3496
|
+
throw new Error("Queue not found");
|
|
3497
|
+
}
|
|
3498
|
+
const config2 = await queue.use();
|
|
3499
|
+
return {
|
|
3500
|
+
name: config2.queue.name,
|
|
3501
|
+
concurrency: config2.concurrency,
|
|
3502
|
+
ratelimit: config2.ratelimit,
|
|
3503
|
+
isMaxed: await config2.queue.isMaxed(),
|
|
3504
|
+
isPaused: await config2.queue.isPaused(),
|
|
3505
|
+
jobs: {
|
|
3506
|
+
paused: await config2.queue.isPaused(),
|
|
3507
|
+
completed: await config2.queue.getJobCountByTypes("completed"),
|
|
3508
|
+
failed: await config2.queue.getJobCountByTypes("failed"),
|
|
3509
|
+
waiting: await config2.queue.getJobCountByTypes("waiting"),
|
|
3510
|
+
active: await config2.queue.getJobCountByTypes("active"),
|
|
3511
|
+
delayed: await config2.queue.getJobCountByTypes("delayed")
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
};
|
|
3515
|
+
resolvers.Mutation["runEval"] = async (_, args, context, info) => {
|
|
3516
|
+
console.log("[EXULU] /evals/run/:id", args.id);
|
|
3517
|
+
const user = context.user;
|
|
3518
|
+
const eval_run_id = args.id;
|
|
3519
|
+
if (!user.super_admin && (!user.role || user.role.evals !== "write")) {
|
|
3520
|
+
throw new Error("You don't have permission to run evals. Required: super_admin or evals write access.");
|
|
3521
|
+
}
|
|
3522
|
+
const { db: db3 } = await postgresClient();
|
|
3523
|
+
const evalRun = await db3.from("eval_runs").where({ id: eval_run_id }).first();
|
|
3524
|
+
if (!evalRun) {
|
|
3525
|
+
throw new Error("Eval run not found in database.");
|
|
3526
|
+
}
|
|
3527
|
+
const hasAccessToEvalRun = await checkRecordAccess(evalRun, "write", user);
|
|
3528
|
+
if (!hasAccessToEvalRun) {
|
|
3529
|
+
throw new Error("You don't have access to this eval run.");
|
|
3530
|
+
}
|
|
3531
|
+
let testCaseIds = evalRun.test_case_ids ? typeof evalRun.test_case_ids === "string" ? JSON.parse(evalRun.test_case_ids) : evalRun.test_case_ids : [];
|
|
3532
|
+
const eval_functions = evalRun.eval_functions ? typeof evalRun.eval_functions === "string" ? JSON.parse(evalRun.eval_functions) : evalRun.eval_functions : [];
|
|
3533
|
+
if (!testCaseIds || testCaseIds.length === 0) {
|
|
3534
|
+
throw new Error("No test cases selected for this eval run.");
|
|
3535
|
+
}
|
|
3536
|
+
if (!eval_functions || eval_functions.length === 0) {
|
|
3537
|
+
throw new Error("No eval functions selected for this eval run.");
|
|
3538
|
+
}
|
|
3539
|
+
if (args.test_case_ids) {
|
|
3540
|
+
testCaseIds = testCaseIds.filter((testCase) => args.test_case_ids.includes(testCase));
|
|
3541
|
+
}
|
|
3542
|
+
console.log("[EXULU] test cases ids filtered", testCaseIds);
|
|
3543
|
+
const testCases = await db3.from("test_cases").whereIn("id", testCaseIds);
|
|
3544
|
+
if (testCases.length === 0) {
|
|
3545
|
+
throw new Error("No test cases found for eval run.");
|
|
3546
|
+
}
|
|
3547
|
+
const agentInstance = await loadAgent(evalRun.agent_id);
|
|
3548
|
+
if (!agentInstance) {
|
|
3549
|
+
throw new Error("Agent instance not found for eval run.");
|
|
3550
|
+
}
|
|
3551
|
+
const evalQueue = await queues.register("eval_runs", 1, 1).use();
|
|
3552
|
+
const jobIds = [];
|
|
3553
|
+
for (const testCase of testCases) {
|
|
3554
|
+
const jobData = {
|
|
3555
|
+
label: `Eval Run ${eval_run_id} - Test Case ${testCase.id}`,
|
|
3556
|
+
trigger: "api",
|
|
3557
|
+
timeoutInSeconds: evalRun.timeout_in_seconds || 180,
|
|
3558
|
+
// default to 3 minutes
|
|
3559
|
+
type: "eval_run",
|
|
3560
|
+
eval_run_id,
|
|
3561
|
+
eval_run_name: evalRun.name,
|
|
3562
|
+
test_case_id: testCase.id,
|
|
3563
|
+
test_case_name: testCase.name,
|
|
3564
|
+
eval_functions,
|
|
3565
|
+
// Array of eval function IDs - worker will create child jobs for these
|
|
3566
|
+
agent_id: evalRun.agent_id,
|
|
3567
|
+
inputs: testCase.inputs,
|
|
3568
|
+
expected_output: testCase.expected_output,
|
|
3569
|
+
expected_tools: testCase.expected_tools,
|
|
3570
|
+
expected_knowledge_sources: testCase.expected_knowledge_sources,
|
|
3571
|
+
expected_agent_tools: testCase.expected_agent_tools,
|
|
3572
|
+
config: evalRun.config,
|
|
3573
|
+
scoring_method: evalRun.scoring_method,
|
|
3574
|
+
pass_threshold: evalRun.pass_threshold,
|
|
3575
|
+
user: user.id,
|
|
3576
|
+
role: user.role?.id
|
|
3577
|
+
};
|
|
3578
|
+
const redisId = (0, import_uuid2.v4)();
|
|
3579
|
+
const job = await evalQueue.queue.add("eval_run", jobData, {
|
|
3580
|
+
jobId: redisId,
|
|
3581
|
+
// Setting it to 3 as a sensible default, as
|
|
3582
|
+
// many AI services are quite unstable.
|
|
3583
|
+
attempts: evalQueue.retries || 1,
|
|
3584
|
+
removeOnComplete: 5e3,
|
|
3585
|
+
removeOnFail: 1e4,
|
|
3586
|
+
backoff: evalQueue.backoff || {
|
|
3587
|
+
type: "exponential",
|
|
3588
|
+
delay: 2e3
|
|
3589
|
+
}
|
|
3590
|
+
});
|
|
3591
|
+
jobIds.push(job.id);
|
|
3592
|
+
}
|
|
3593
|
+
const response = {
|
|
3594
|
+
jobs: jobIds,
|
|
3595
|
+
count: jobIds.length
|
|
3596
|
+
};
|
|
3597
|
+
const requestedFields = getRequestedFields(info);
|
|
3598
|
+
const mapped = {};
|
|
3599
|
+
requestedFields.forEach((field) => {
|
|
3600
|
+
mapped[field] = response[field];
|
|
3601
|
+
});
|
|
3602
|
+
return mapped;
|
|
3603
|
+
};
|
|
3604
|
+
resolvers.Mutation["drainQueue"] = async (_, args, context, info) => {
|
|
3605
|
+
if (!args.queue) {
|
|
3606
|
+
throw new Error("Queue name is required");
|
|
3607
|
+
}
|
|
3608
|
+
const queue = queues.list.get(args.queue);
|
|
3609
|
+
if (!queue) {
|
|
3610
|
+
throw new Error("Queue not found");
|
|
3611
|
+
}
|
|
3612
|
+
const config2 = await queue.use();
|
|
3613
|
+
await config2.queue.drain();
|
|
3614
|
+
return { success: true };
|
|
3615
|
+
};
|
|
3616
|
+
resolvers.Mutation["pauseQueue"] = async (_, args, context, info) => {
|
|
3617
|
+
if (!args.queue) {
|
|
3618
|
+
throw new Error("Queue name is required");
|
|
3619
|
+
}
|
|
3620
|
+
const queue = queues.list.get(args.queue);
|
|
3621
|
+
if (!queue) {
|
|
3622
|
+
throw new Error("Queue not found");
|
|
3623
|
+
}
|
|
3624
|
+
const config2 = await queue.use();
|
|
3625
|
+
await config2.queue.pause();
|
|
3626
|
+
return { success: true };
|
|
3627
|
+
};
|
|
3628
|
+
resolvers.Mutation["resumeQueue"] = async (_, args, context, info) => {
|
|
3629
|
+
if (!args.queue) {
|
|
3630
|
+
throw new Error("Queue name is required");
|
|
3631
|
+
}
|
|
3632
|
+
const queue = queues.list.get(args.queue);
|
|
3633
|
+
if (!queue) {
|
|
3634
|
+
throw new Error("Queue not found");
|
|
3635
|
+
}
|
|
3636
|
+
const config2 = await queue.use();
|
|
3637
|
+
await config2.queue.resume();
|
|
3638
|
+
return { success: true };
|
|
3639
|
+
};
|
|
3640
|
+
resolvers.Mutation["deleteJob"] = async (_, args, context, info) => {
|
|
3641
|
+
if (!args.id) {
|
|
3642
|
+
throw new Error("Job ID is required");
|
|
3643
|
+
}
|
|
3644
|
+
if (!args.queue) {
|
|
3645
|
+
throw new Error("Queue name is required");
|
|
3646
|
+
}
|
|
3647
|
+
const queue = queues.list.get(args.queue);
|
|
3648
|
+
if (!queue) {
|
|
3649
|
+
throw new Error("Queue not found");
|
|
3650
|
+
}
|
|
3651
|
+
const config2 = await queue.use();
|
|
3652
|
+
await config2.queue.remove(args.id);
|
|
3653
|
+
return { success: true };
|
|
3654
|
+
};
|
|
3655
|
+
resolvers.Query["evals"] = async (_, args, context, info) => {
|
|
3656
|
+
const requestedFields = getRequestedFields(info);
|
|
3657
|
+
return {
|
|
3658
|
+
items: evals.map((_eval) => {
|
|
3659
|
+
const object = {};
|
|
3660
|
+
requestedFields.forEach((field) => {
|
|
3661
|
+
object[field] = _eval[field];
|
|
3662
|
+
});
|
|
3663
|
+
return object;
|
|
3664
|
+
})
|
|
3665
|
+
};
|
|
3666
|
+
};
|
|
3667
|
+
resolvers.Query["jobs"] = async (_, args, context, info) => {
|
|
3668
|
+
if (!args.queue) {
|
|
3669
|
+
throw new Error("Queue name is required");
|
|
3670
|
+
}
|
|
3671
|
+
const { client: client2 } = await redisClient();
|
|
3672
|
+
if (!client2) {
|
|
3673
|
+
throw new Error("Redis client not created properly");
|
|
3674
|
+
}
|
|
3675
|
+
const {
|
|
3676
|
+
jobs,
|
|
3677
|
+
count
|
|
3678
|
+
} = await getJobsByQueueAndName(
|
|
3679
|
+
args.queue,
|
|
3680
|
+
args.statusses,
|
|
3681
|
+
args.page || 1,
|
|
3682
|
+
args.limit || 100
|
|
3683
|
+
);
|
|
3684
|
+
console.log("[EXULU] jobs", jobs.map((job) => job.name));
|
|
3685
|
+
const requestedFields = getRequestedFields(info);
|
|
3686
|
+
return {
|
|
3687
|
+
items: await Promise.all(jobs.map(async (job) => {
|
|
3688
|
+
const object = {};
|
|
3689
|
+
for (const field of requestedFields) {
|
|
3690
|
+
if (field === "data") {
|
|
3691
|
+
object[field] = job[field];
|
|
3692
|
+
} else if (field === "timestamp") {
|
|
3693
|
+
object[field] = new Date(job[field]).toISOString();
|
|
3694
|
+
} else if (field === "state") {
|
|
3695
|
+
object[field] = await job.getState();
|
|
3696
|
+
} else {
|
|
3697
|
+
object[field] = job[field];
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
return object;
|
|
3701
|
+
})),
|
|
3702
|
+
pageInfo: {
|
|
3703
|
+
pageCount: Math.ceil(count / (args.limit || 100)),
|
|
3704
|
+
itemCount: count,
|
|
3705
|
+
currentPage: args.page || 1,
|
|
3706
|
+
hasPreviousPage: args.page && args.page > 1 ? true : false,
|
|
3707
|
+
hasNextPage: args.page && args.page < Math.ceil(jobs.length / (args.limit || 100)) ? true : false
|
|
3708
|
+
}
|
|
3709
|
+
};
|
|
3710
|
+
};
|
|
3395
3711
|
resolvers.Query["contexts"] = async (_, args, context, info) => {
|
|
3396
3712
|
const data = contexts.map((context2) => ({
|
|
3397
3713
|
id: context2.id,
|
|
@@ -3497,7 +3813,32 @@ type PageInfo {
|
|
|
3497
3813
|
};
|
|
3498
3814
|
modelDefs += `
|
|
3499
3815
|
type ProviderPaginationResult {
|
|
3500
|
-
|
|
3816
|
+
items: [Provider]!
|
|
3817
|
+
}
|
|
3818
|
+
`;
|
|
3819
|
+
modelDefs += `
|
|
3820
|
+
type QueueResult {
|
|
3821
|
+
name: String!
|
|
3822
|
+
concurrency: Int!
|
|
3823
|
+
ratelimit: Int!
|
|
3824
|
+
isMaxed: Boolean!
|
|
3825
|
+
isPaused: Boolean!
|
|
3826
|
+
jobs: QueueJobsCounts
|
|
3827
|
+
}
|
|
3828
|
+
`;
|
|
3829
|
+
modelDefs += `
|
|
3830
|
+
type QueueJobsCounts {
|
|
3831
|
+
paused: Int!
|
|
3832
|
+
completed: Int!
|
|
3833
|
+
failed: Int!
|
|
3834
|
+
waiting: Int!
|
|
3835
|
+
active: Int!
|
|
3836
|
+
delayed: Int!
|
|
3837
|
+
}
|
|
3838
|
+
`;
|
|
3839
|
+
modelDefs += `
|
|
3840
|
+
type EvalPaginationResult {
|
|
3841
|
+
items: [Eval]!
|
|
3501
3842
|
}
|
|
3502
3843
|
`;
|
|
3503
3844
|
modelDefs += `
|
|
@@ -3510,6 +3851,12 @@ type PageInfo {
|
|
|
3510
3851
|
items: [Tool]!
|
|
3511
3852
|
}
|
|
3512
3853
|
`;
|
|
3854
|
+
modelDefs += `
|
|
3855
|
+
type JobPaginationResult {
|
|
3856
|
+
items: [Job]!
|
|
3857
|
+
pageInfo: PageInfo!
|
|
3858
|
+
}
|
|
3859
|
+
`;
|
|
3513
3860
|
typeDefs += "}\n";
|
|
3514
3861
|
mutationDefs += "}\n";
|
|
3515
3862
|
const genericTypes = `
|
|
@@ -3567,6 +3914,19 @@ type Provider {
|
|
|
3567
3914
|
type: EnumProviderType!
|
|
3568
3915
|
}
|
|
3569
3916
|
|
|
3917
|
+
type Eval {
|
|
3918
|
+
id: ID!
|
|
3919
|
+
name: String!
|
|
3920
|
+
description: String!
|
|
3921
|
+
llm: Boolean!
|
|
3922
|
+
config: [EvalConfig!]
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
type EvalConfig {
|
|
3926
|
+
name: String!
|
|
3927
|
+
description: String!
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3570
3930
|
type Context {
|
|
3571
3931
|
id: ID!
|
|
3572
3932
|
name: String!
|
|
@@ -3578,6 +3938,15 @@ type Context {
|
|
|
3578
3938
|
configuration: JSON
|
|
3579
3939
|
}
|
|
3580
3940
|
|
|
3941
|
+
type RunEvalReturnPayload {
|
|
3942
|
+
jobs: [String!]!
|
|
3943
|
+
count: Int!
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
type JobActionReturnPayload {
|
|
3947
|
+
success: Boolean!
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3581
3950
|
type ContextField {
|
|
3582
3951
|
name: String!
|
|
3583
3952
|
type: String!
|
|
@@ -3593,10 +3962,35 @@ type Tool {
|
|
|
3593
3962
|
config: JSON
|
|
3594
3963
|
}
|
|
3595
3964
|
|
|
3965
|
+
type Job {
|
|
3966
|
+
id: String!
|
|
3967
|
+
name: String!
|
|
3968
|
+
returnvalue: JSON
|
|
3969
|
+
stacktrace: [String]
|
|
3970
|
+
failedReason: String
|
|
3971
|
+
state: String!
|
|
3972
|
+
data: JSON
|
|
3973
|
+
timestamp: Date
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3596
3976
|
enum EnumProviderType {
|
|
3597
3977
|
agent
|
|
3598
3978
|
}
|
|
3599
3979
|
|
|
3980
|
+
enum QueueEnum {
|
|
3981
|
+
${queues.list.keys().toArray().join("\n")}
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
enum JobStateEnum {
|
|
3985
|
+
${JOB_STATUS_ENUM.active}
|
|
3986
|
+
${JOB_STATUS_ENUM.waiting}
|
|
3987
|
+
${JOB_STATUS_ENUM.delayed}
|
|
3988
|
+
${JOB_STATUS_ENUM.failed}
|
|
3989
|
+
${JOB_STATUS_ENUM.completed}
|
|
3990
|
+
${JOB_STATUS_ENUM.paused}
|
|
3991
|
+
${JOB_STATUS_ENUM.stuck}
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3600
3994
|
type StatisticsResult {
|
|
3601
3995
|
group: String!
|
|
3602
3996
|
count: Int!
|
|
@@ -3626,6 +4020,25 @@ var validateCreateOrRemoveSuperAdminPermission = async (tableNamePlural, input,
|
|
|
3626
4020
|
}
|
|
3627
4021
|
}
|
|
3628
4022
|
};
|
|
4023
|
+
async function getJobsByQueueAndName(queueName, statusses, page, limit) {
|
|
4024
|
+
const queue = queues.list.get(queueName);
|
|
4025
|
+
if (!queue) {
|
|
4026
|
+
throw new Error(`Queue ${queueName} not found`);
|
|
4027
|
+
}
|
|
4028
|
+
const config = await queue.use();
|
|
4029
|
+
const startIndex = (page || 1) - 1;
|
|
4030
|
+
const endIndex = startIndex + (limit || 100);
|
|
4031
|
+
const jobs = await config.queue.getJobs(statusses || [], startIndex, endIndex, false);
|
|
4032
|
+
const counts = await config.queue.getJobCounts(...statusses || []);
|
|
4033
|
+
let total = 0;
|
|
4034
|
+
if (counts) {
|
|
4035
|
+
total = Object.keys(counts).reduce((acc, key) => acc + (counts[key] || 0), 0);
|
|
4036
|
+
}
|
|
4037
|
+
return {
|
|
4038
|
+
jobs,
|
|
4039
|
+
count: total
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
3629
4042
|
|
|
3630
4043
|
// src/registry/classes.ts
|
|
3631
4044
|
var import_client_s32 = require("@aws-sdk/client-s3");
|
|
@@ -3640,6 +4053,9 @@ var import_node_crypto = require("crypto");
|
|
|
3640
4053
|
var expiresIn = 60 * 60 * 24 * 1;
|
|
3641
4054
|
var s3Client;
|
|
3642
4055
|
function getS3Client(config) {
|
|
4056
|
+
if (!config.fileUploads) {
|
|
4057
|
+
throw new Error("File uploads are not configured");
|
|
4058
|
+
}
|
|
3643
4059
|
s3Client ??= new import_client_s3.S3Client({
|
|
3644
4060
|
region: config.fileUploads.s3region,
|
|
3645
4061
|
...config.fileUploads.s3endpoint && {
|
|
@@ -3654,6 +4070,9 @@ function getS3Client(config) {
|
|
|
3654
4070
|
return s3Client;
|
|
3655
4071
|
}
|
|
3656
4072
|
var getPresignedUrl = async (key, config) => {
|
|
4073
|
+
if (!config.fileUploads) {
|
|
4074
|
+
throw new Error("File uploads are not configured");
|
|
4075
|
+
}
|
|
3657
4076
|
const url = await (0, import_s3_request_presigner.getSignedUrl)(
|
|
3658
4077
|
getS3Client(config),
|
|
3659
4078
|
new import_client_s3.GetObjectCommand({
|
|
@@ -3665,6 +4084,9 @@ var getPresignedUrl = async (key, config) => {
|
|
|
3665
4084
|
return url;
|
|
3666
4085
|
};
|
|
3667
4086
|
var addPrefixToKey = (keyPath, config) => {
|
|
4087
|
+
if (!config.fileUploads) {
|
|
4088
|
+
throw new Error("File uploads are not configured");
|
|
4089
|
+
}
|
|
3668
4090
|
if (!config.fileUploads.s3prefix) {
|
|
3669
4091
|
return keyPath;
|
|
3670
4092
|
}
|
|
@@ -3675,6 +4097,10 @@ var addPrefixToKey = (keyPath, config) => {
|
|
|
3675
4097
|
return `${prefix}/${keyPath}`;
|
|
3676
4098
|
};
|
|
3677
4099
|
var uploadFile = async (user, file, key, config, options = {}) => {
|
|
4100
|
+
console.log("[EXULU] Uploading file to S3", key);
|
|
4101
|
+
if (!config.fileUploads) {
|
|
4102
|
+
throw new Error("File uploads are not configured");
|
|
4103
|
+
}
|
|
3678
4104
|
const client2 = getS3Client(config);
|
|
3679
4105
|
let folder = `${user}/`;
|
|
3680
4106
|
const fullKey = addPrefixToKey(!key.includes(folder) ? folder + key : key, config);
|
|
@@ -3690,7 +4116,13 @@ var uploadFile = async (user, file, key, config, options = {}) => {
|
|
|
3690
4116
|
return key;
|
|
3691
4117
|
};
|
|
3692
4118
|
var createUppyRoutes = async (app, config) => {
|
|
4119
|
+
if (!config.fileUploads) {
|
|
4120
|
+
throw new Error("File uploads are not configured");
|
|
4121
|
+
}
|
|
3693
4122
|
const extractUserPrefix = (key) => {
|
|
4123
|
+
if (!config.fileUploads) {
|
|
4124
|
+
throw new Error("File uploads are not configured");
|
|
4125
|
+
}
|
|
3694
4126
|
if (!config.fileUploads.s3prefix) {
|
|
3695
4127
|
return key.split("/")[0];
|
|
3696
4128
|
}
|
|
@@ -3718,6 +4150,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
3718
4150
|
};
|
|
3719
4151
|
let stsClient;
|
|
3720
4152
|
function getSTSClient() {
|
|
4153
|
+
if (!config.fileUploads) {
|
|
4154
|
+
throw new Error("File uploads are not configured");
|
|
4155
|
+
}
|
|
3721
4156
|
stsClient ??= new import_client_sts.STSClient({
|
|
3722
4157
|
region: config.fileUploads.s3region,
|
|
3723
4158
|
...config.fileUploads.s3endpoint && { endpoint: config.fileUploads.s3endpoint },
|
|
@@ -3729,6 +4164,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
3729
4164
|
return stsClient;
|
|
3730
4165
|
}
|
|
3731
4166
|
app.delete("/s3/delete", async (req, res, next) => {
|
|
4167
|
+
if (!config.fileUploads) {
|
|
4168
|
+
throw new Error("File uploads are not configured");
|
|
4169
|
+
}
|
|
3732
4170
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
3733
4171
|
const internalkey = req.headers["internal-key"] || null;
|
|
3734
4172
|
const { db: db3 } = await postgresClient();
|
|
@@ -3819,6 +4257,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
3819
4257
|
}
|
|
3820
4258
|
});
|
|
3821
4259
|
app.post("/s3/object", async (req, res, next) => {
|
|
4260
|
+
if (!config.fileUploads) {
|
|
4261
|
+
throw new Error("File uploads are not configured");
|
|
4262
|
+
}
|
|
3822
4263
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
3823
4264
|
const internalkey = req.headers["internal-key"] || null;
|
|
3824
4265
|
const { db: db3 } = await postgresClient();
|
|
@@ -3849,6 +4290,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
3849
4290
|
res.end();
|
|
3850
4291
|
});
|
|
3851
4292
|
app.get("/s3/list", async (req, res, next) => {
|
|
4293
|
+
if (!config.fileUploads) {
|
|
4294
|
+
throw new Error("File uploads are not configured");
|
|
4295
|
+
}
|
|
3852
4296
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
3853
4297
|
const internalkey = req.headers["internal-key"] || null;
|
|
3854
4298
|
const { db: db3 } = await postgresClient();
|
|
@@ -3885,6 +4329,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
3885
4329
|
res.end();
|
|
3886
4330
|
});
|
|
3887
4331
|
app.get("/s3/sts", (req, res, next) => {
|
|
4332
|
+
if (!config.fileUploads) {
|
|
4333
|
+
throw new Error("File uploads are not configured");
|
|
4334
|
+
}
|
|
3888
4335
|
getSTSClient().send(new import_client_sts.GetFederationTokenCommand({
|
|
3889
4336
|
Name: "Exulu",
|
|
3890
4337
|
// The duration, in seconds, of the role session. The value specified
|
|
@@ -3897,8 +4344,8 @@ var createUppyRoutes = async (app, config) => {
|
|
|
3897
4344
|
res.setHeader("Cache-Control", `public,max-age=${expiresIn}`);
|
|
3898
4345
|
res.json({
|
|
3899
4346
|
credentials: response.Credentials,
|
|
3900
|
-
bucket: config.fileUploads
|
|
3901
|
-
region: config.fileUploads
|
|
4347
|
+
bucket: config.fileUploads?.s3Bucket,
|
|
4348
|
+
region: config.fileUploads?.s3region
|
|
3902
4349
|
});
|
|
3903
4350
|
}, next);
|
|
3904
4351
|
});
|
|
@@ -3917,6 +4364,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
3917
4364
|
};
|
|
3918
4365
|
const generateS3Key2 = (filename) => `${(0, import_node_crypto.randomUUID)()}-_EXULU_${filename}`;
|
|
3919
4366
|
const signOnServer = async (req, res, next) => {
|
|
4367
|
+
if (!config.fileUploads) {
|
|
4368
|
+
throw new Error("File uploads are not configured");
|
|
4369
|
+
}
|
|
3920
4370
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
3921
4371
|
const { db: db3 } = await postgresClient();
|
|
3922
4372
|
let authtoken = null;
|
|
@@ -3962,6 +4412,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
3962
4412
|
return await signOnServer(req, res, next);
|
|
3963
4413
|
});
|
|
3964
4414
|
app.post("/s3/multipart", async (req, res, next) => {
|
|
4415
|
+
if (!config.fileUploads) {
|
|
4416
|
+
throw new Error("File uploads are not configured");
|
|
4417
|
+
}
|
|
3965
4418
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
3966
4419
|
const { db: db3 } = await postgresClient();
|
|
3967
4420
|
let authtoken = null;
|
|
@@ -4017,6 +4470,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4017
4470
|
return Number.isInteger(partNumber) && partNumber >= 1 && partNumber <= 1e4;
|
|
4018
4471
|
}
|
|
4019
4472
|
app.get("/s3/multipart/:uploadId/:partNumber", (req, res, next) => {
|
|
4473
|
+
if (!config.fileUploads) {
|
|
4474
|
+
throw new Error("File uploads are not configured");
|
|
4475
|
+
}
|
|
4020
4476
|
const { uploadId, partNumber } = req.params;
|
|
4021
4477
|
const { key } = req.query;
|
|
4022
4478
|
if (!validatePartNumber(partNumber)) {
|
|
@@ -4046,6 +4502,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4046
4502
|
}
|
|
4047
4503
|
const parts = [];
|
|
4048
4504
|
function listPartsPage(startAt) {
|
|
4505
|
+
if (!config.fileUploads) {
|
|
4506
|
+
throw new Error("File uploads are not configured");
|
|
4507
|
+
}
|
|
4049
4508
|
client2.send(new import_client_s3.ListPartsCommand({
|
|
4050
4509
|
Bucket: config.fileUploads.s3Bucket,
|
|
4051
4510
|
Key: key,
|
|
@@ -4070,6 +4529,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4070
4529
|
return part && typeof part === "object" && Number(part.PartNumber) && typeof part.ETag === "string";
|
|
4071
4530
|
}
|
|
4072
4531
|
app.post("/s3/multipart/:uploadId/complete", (req, res, next) => {
|
|
4532
|
+
if (!config.fileUploads) {
|
|
4533
|
+
throw new Error("File uploads are not configured");
|
|
4534
|
+
}
|
|
4073
4535
|
const client2 = getS3Client(config);
|
|
4074
4536
|
const { uploadId } = req.params;
|
|
4075
4537
|
const { key } = req.query;
|
|
@@ -4100,6 +4562,9 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4100
4562
|
});
|
|
4101
4563
|
});
|
|
4102
4564
|
app.delete("/s3/multipart/:uploadId", (req, res, next) => {
|
|
4565
|
+
if (!config.fileUploads) {
|
|
4566
|
+
throw new Error("File uploads are not configured");
|
|
4567
|
+
}
|
|
4103
4568
|
const client2 = getS3Client(config);
|
|
4104
4569
|
const { uploadId } = req.params;
|
|
4105
4570
|
const { key } = req.query;
|
|
@@ -4288,27 +4753,34 @@ function errorHandler(error) {
|
|
|
4288
4753
|
}
|
|
4289
4754
|
return JSON.stringify(error);
|
|
4290
4755
|
}
|
|
4291
|
-
var
|
|
4756
|
+
var ExuluEval2 = class {
|
|
4292
4757
|
id;
|
|
4293
4758
|
name;
|
|
4294
4759
|
description;
|
|
4760
|
+
llm;
|
|
4295
4761
|
execute;
|
|
4296
4762
|
config;
|
|
4297
4763
|
queue;
|
|
4298
|
-
constructor({ id, name, description, execute: execute2, config, queue }) {
|
|
4764
|
+
constructor({ id, name, description, execute: execute2, config, queue, llm }) {
|
|
4299
4765
|
this.id = id;
|
|
4300
4766
|
this.name = name;
|
|
4301
4767
|
this.description = description;
|
|
4302
4768
|
this.execute = execute2;
|
|
4303
4769
|
this.config = config;
|
|
4770
|
+
this.llm = llm;
|
|
4304
4771
|
this.queue = queue;
|
|
4305
4772
|
}
|
|
4306
|
-
async run(
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4773
|
+
async run(agent, backend, testCase, messages, config) {
|
|
4774
|
+
try {
|
|
4775
|
+
const score = await this.execute({ agent, backend, testCase, messages, config });
|
|
4776
|
+
if (score < 0 || score > 100) {
|
|
4777
|
+
throw new Error(`Eval function ${this.name} must return a score between 0 and 100, got ${score}`);
|
|
4778
|
+
}
|
|
4779
|
+
return score;
|
|
4780
|
+
} catch (error) {
|
|
4781
|
+
console.error(`[EXULU] error running eval function ${this.name}.`, error);
|
|
4782
|
+
throw new Error(`Error running eval function ${this.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4310
4783
|
}
|
|
4311
|
-
return score;
|
|
4312
4784
|
}
|
|
4313
4785
|
};
|
|
4314
4786
|
var ExuluAgent2 = class {
|
|
@@ -4323,13 +4795,13 @@ var ExuluAgent2 = class {
|
|
|
4323
4795
|
type;
|
|
4324
4796
|
streaming = false;
|
|
4325
4797
|
maxContextLength;
|
|
4798
|
+
queue;
|
|
4326
4799
|
rateLimit;
|
|
4327
4800
|
config;
|
|
4328
|
-
evals;
|
|
4329
4801
|
// private memory: Memory | undefined; // TODO do own implementation
|
|
4330
4802
|
model;
|
|
4331
4803
|
capabilities;
|
|
4332
|
-
constructor({ id, name, description, config, rateLimit, capabilities, type, maxContextLength,
|
|
4804
|
+
constructor({ id, name, description, config, rateLimit, capabilities, type, maxContextLength, provider, queue }) {
|
|
4333
4805
|
this.id = id;
|
|
4334
4806
|
this.name = name;
|
|
4335
4807
|
this.description = description;
|
|
@@ -4338,7 +4810,7 @@ var ExuluAgent2 = class {
|
|
|
4338
4810
|
this.config = config;
|
|
4339
4811
|
this.type = type;
|
|
4340
4812
|
this.maxContextLength = maxContextLength;
|
|
4341
|
-
this.
|
|
4813
|
+
this.queue = queue;
|
|
4342
4814
|
this.capabilities = capabilities || {
|
|
4343
4815
|
text: false,
|
|
4344
4816
|
images: [],
|
|
@@ -4437,7 +4909,7 @@ var ExuluAgent2 = class {
|
|
|
4437
4909
|
prompt,
|
|
4438
4910
|
user,
|
|
4439
4911
|
session,
|
|
4440
|
-
|
|
4912
|
+
inputMessages,
|
|
4441
4913
|
currentTools,
|
|
4442
4914
|
allExuluTools,
|
|
4443
4915
|
statistics,
|
|
@@ -4455,10 +4927,10 @@ var ExuluAgent2 = class {
|
|
|
4455
4927
|
if (!this.config) {
|
|
4456
4928
|
throw new Error("Config is required for generating.");
|
|
4457
4929
|
}
|
|
4458
|
-
if (prompt &&
|
|
4930
|
+
if (prompt && inputMessages?.length) {
|
|
4459
4931
|
throw new Error("Message and prompt cannot be provided at the same time.");
|
|
4460
4932
|
}
|
|
4461
|
-
if (!prompt && !
|
|
4933
|
+
if (!prompt && !inputMessages?.length) {
|
|
4462
4934
|
throw new Error("Prompt or message is required for generating.");
|
|
4463
4935
|
}
|
|
4464
4936
|
if (outputSchema && !prompt) {
|
|
@@ -4468,18 +4940,18 @@ var ExuluAgent2 = class {
|
|
|
4468
4940
|
apiKey: providerapikey
|
|
4469
4941
|
});
|
|
4470
4942
|
console.log("[EXULU] Model for agent: " + this.name, " created for generating sync.");
|
|
4471
|
-
let messages = [];
|
|
4472
|
-
if (
|
|
4943
|
+
let messages = inputMessages || [];
|
|
4944
|
+
if (messages && session && user) {
|
|
4473
4945
|
const previousMessages = await getAgentMessages({
|
|
4474
4946
|
session,
|
|
4475
4947
|
user: user.id,
|
|
4476
4948
|
limit: 50,
|
|
4477
4949
|
page: 1
|
|
4478
4950
|
});
|
|
4479
|
-
const previousMessagesContent = previousMessages.map((
|
|
4951
|
+
const previousMessagesContent = previousMessages.map((message) => JSON.parse(message.content));
|
|
4480
4952
|
messages = await (0, import_ai.validateUIMessages)({
|
|
4481
4953
|
// append the new message to the previous messages:
|
|
4482
|
-
messages: [...previousMessagesContent,
|
|
4954
|
+
messages: [...previousMessagesContent, ...messages]
|
|
4483
4955
|
});
|
|
4484
4956
|
}
|
|
4485
4957
|
console.log("[EXULU] Message count for agent: " + this.name, "loaded for generating sync.", messages.length);
|
|
@@ -4615,13 +5087,12 @@ var ExuluAgent2 = class {
|
|
|
4615
5087
|
return "";
|
|
4616
5088
|
};
|
|
4617
5089
|
generateStream = async ({
|
|
4618
|
-
express: express3,
|
|
4619
5090
|
user,
|
|
4620
5091
|
session,
|
|
4621
5092
|
message,
|
|
5093
|
+
previousMessages,
|
|
4622
5094
|
currentTools,
|
|
4623
5095
|
allExuluTools,
|
|
4624
|
-
statistics,
|
|
4625
5096
|
toolConfigs,
|
|
4626
5097
|
providerapikey,
|
|
4627
5098
|
contexts,
|
|
@@ -4629,27 +5100,34 @@ var ExuluAgent2 = class {
|
|
|
4629
5100
|
instructions
|
|
4630
5101
|
}) => {
|
|
4631
5102
|
if (!this.model) {
|
|
5103
|
+
console.error("[EXULU] Model is required for streaming.");
|
|
4632
5104
|
throw new Error("Model is required for streaming.");
|
|
4633
5105
|
}
|
|
4634
5106
|
if (!this.config) {
|
|
5107
|
+
console.error("[EXULU] Config is required for streaming.");
|
|
4635
5108
|
throw new Error("Config is required for generating.");
|
|
4636
5109
|
}
|
|
4637
5110
|
if (!message) {
|
|
5111
|
+
console.error("[EXULU] Message is required for streaming.");
|
|
4638
5112
|
throw new Error("Message is required for streaming.");
|
|
4639
5113
|
}
|
|
4640
5114
|
const model = this.model.create({
|
|
4641
5115
|
apiKey: providerapikey
|
|
4642
5116
|
});
|
|
4643
5117
|
let messages = [];
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
5118
|
+
let previousMessagesContent = previousMessages || [];
|
|
5119
|
+
if (session) {
|
|
5120
|
+
console.log("[EXULU] loading previous messages from session: " + session);
|
|
5121
|
+
const previousMessages2 = await getAgentMessages({
|
|
5122
|
+
session,
|
|
5123
|
+
user: user.id,
|
|
5124
|
+
limit: 50,
|
|
5125
|
+
page: 1
|
|
5126
|
+
});
|
|
5127
|
+
previousMessagesContent = previousMessages2.map(
|
|
5128
|
+
(message2) => JSON.parse(message2.content)
|
|
5129
|
+
);
|
|
5130
|
+
}
|
|
4653
5131
|
messages = await (0, import_ai.validateUIMessages)({
|
|
4654
5132
|
// append the new message to the previous messages:
|
|
4655
5133
|
messages: [...previousMessagesContent, message]
|
|
@@ -4682,80 +5160,17 @@ var ExuluAgent2 = class {
|
|
|
4682
5160
|
user,
|
|
4683
5161
|
exuluConfig
|
|
4684
5162
|
),
|
|
4685
|
-
onError: (error) =>
|
|
5163
|
+
onError: (error) => {
|
|
5164
|
+
console.error("[EXULU] chat stream error.", error);
|
|
5165
|
+
throw new Error(`Chat stream error: ${error instanceof Error ? error.message : String(error)}`);
|
|
5166
|
+
}
|
|
4686
5167
|
// stopWhen: [stepCountIs(1)],
|
|
4687
5168
|
});
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
messageMetadata: ({ part }) => {
|
|
4691
|
-
if (part.type === "finish") {
|
|
4692
|
-
return {
|
|
4693
|
-
totalTokens: part.totalUsage.totalTokens,
|
|
4694
|
-
reasoningTokens: part.totalUsage.reasoningTokens,
|
|
4695
|
-
inputTokens: part.totalUsage.inputTokens,
|
|
4696
|
-
outputTokens: part.totalUsage.outputTokens,
|
|
4697
|
-
cachedInputTokens: part.totalUsage.cachedInputTokens
|
|
4698
|
-
};
|
|
4699
|
-
}
|
|
4700
|
-
},
|
|
5169
|
+
return {
|
|
5170
|
+
stream: result,
|
|
4701
5171
|
originalMessages: messages,
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
onError: (error) => {
|
|
4705
|
-
console.error("[EXULU] chat response error.", error);
|
|
4706
|
-
return errorHandler(error);
|
|
4707
|
-
},
|
|
4708
|
-
generateMessageId: (0, import_ai.createIdGenerator)({
|
|
4709
|
-
prefix: "msg_",
|
|
4710
|
-
size: 16
|
|
4711
|
-
}),
|
|
4712
|
-
onFinish: async ({ messages: messages2, isContinuation, isAborted, responseMessage }) => {
|
|
4713
|
-
if (session) {
|
|
4714
|
-
await saveChat({
|
|
4715
|
-
session,
|
|
4716
|
-
user: user.id,
|
|
4717
|
-
messages: messages2.filter((x) => !previousMessagesContent.find((y) => y.id === x.id))
|
|
4718
|
-
});
|
|
4719
|
-
}
|
|
4720
|
-
const metadata = messages2[messages2.length - 1]?.metadata;
|
|
4721
|
-
console.log("[EXULU] Finished streaming", metadata);
|
|
4722
|
-
console.log("[EXULU] Statistics", statistics);
|
|
4723
|
-
if (statistics) {
|
|
4724
|
-
await Promise.all([
|
|
4725
|
-
updateStatistic({
|
|
4726
|
-
name: "count",
|
|
4727
|
-
label: statistics.label,
|
|
4728
|
-
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
4729
|
-
trigger: statistics.trigger,
|
|
4730
|
-
count: 1,
|
|
4731
|
-
user: user.id,
|
|
4732
|
-
role: user?.role?.id
|
|
4733
|
-
}),
|
|
4734
|
-
...metadata?.inputTokens ? [
|
|
4735
|
-
updateStatistic({
|
|
4736
|
-
name: "inputTokens",
|
|
4737
|
-
label: statistics.label,
|
|
4738
|
-
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
4739
|
-
trigger: statistics.trigger,
|
|
4740
|
-
count: metadata?.inputTokens,
|
|
4741
|
-
user: user.id,
|
|
4742
|
-
role: user?.role?.id
|
|
4743
|
-
})
|
|
4744
|
-
] : [],
|
|
4745
|
-
...metadata?.outputTokens ? [
|
|
4746
|
-
updateStatistic({
|
|
4747
|
-
name: "outputTokens",
|
|
4748
|
-
label: statistics.label,
|
|
4749
|
-
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
4750
|
-
trigger: statistics.trigger,
|
|
4751
|
-
count: metadata?.outputTokens
|
|
4752
|
-
})
|
|
4753
|
-
] : []
|
|
4754
|
-
]);
|
|
4755
|
-
}
|
|
4756
|
-
}
|
|
4757
|
-
});
|
|
4758
|
-
return;
|
|
5172
|
+
previousMessages: previousMessagesContent
|
|
5173
|
+
};
|
|
4759
5174
|
};
|
|
4760
5175
|
};
|
|
4761
5176
|
var getAgentMessages = async ({ session, user, limit, page }) => {
|
|
@@ -4946,16 +5361,21 @@ var ExuluContext = class {
|
|
|
4946
5361
|
if (queue?.queue.name) {
|
|
4947
5362
|
console.log("[EXULU] processor is in queue mode, scheduling job.");
|
|
4948
5363
|
const job = await bullmqDecorator({
|
|
5364
|
+
timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 180,
|
|
4949
5365
|
label: `${this.name} ${field.name} data processor`,
|
|
4950
5366
|
processor: `${this.id}-${field.name}`,
|
|
4951
5367
|
context: this.id,
|
|
4952
5368
|
inputs: item,
|
|
4953
5369
|
item: item.id,
|
|
4954
5370
|
queue: queue.queue,
|
|
5371
|
+
backoff: queue.backoff || {
|
|
5372
|
+
type: "exponential",
|
|
5373
|
+
delay: 2e3
|
|
5374
|
+
},
|
|
5375
|
+
retries: queue.retries || 2,
|
|
4955
5376
|
user,
|
|
4956
5377
|
role,
|
|
4957
|
-
trigger
|
|
4958
|
-
retries: 2
|
|
5378
|
+
trigger
|
|
4959
5379
|
});
|
|
4960
5380
|
return {
|
|
4961
5381
|
result: "",
|
|
@@ -5025,12 +5445,14 @@ var ExuluContext = class {
|
|
|
5025
5445
|
role
|
|
5026
5446
|
);
|
|
5027
5447
|
await db3.from(getChunksTableName(this.id)).where({ source }).delete();
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5448
|
+
if (chunks?.length) {
|
|
5449
|
+
await db3.from(getChunksTableName(this.id)).insert(chunks.map((chunk) => ({
|
|
5450
|
+
source,
|
|
5451
|
+
content: chunk.content,
|
|
5452
|
+
chunk_index: chunk.index,
|
|
5453
|
+
embedding: import_knex5.default.toSql(chunk.vector)
|
|
5454
|
+
})));
|
|
5455
|
+
}
|
|
5034
5456
|
await db3.from(getTableName(this.id)).where({ id: item.id }).update({
|
|
5035
5457
|
embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5036
5458
|
}).returning("id");
|
|
@@ -5159,9 +5581,15 @@ var ExuluContext = class {
|
|
|
5159
5581
|
if (queue?.queue.name) {
|
|
5160
5582
|
console.log("[EXULU] embedder is in queue mode, scheduling job.");
|
|
5161
5583
|
const job = await bullmqDecorator({
|
|
5584
|
+
timeoutInSeconds: queue.timeoutInSeconds || 180,
|
|
5162
5585
|
label: `${this.embedder.name}`,
|
|
5163
5586
|
embedder: this.embedder.id,
|
|
5164
5587
|
context: this.id,
|
|
5588
|
+
backoff: queue.backoff || {
|
|
5589
|
+
type: "exponential",
|
|
5590
|
+
delay: 2e3
|
|
5591
|
+
},
|
|
5592
|
+
retries: queue.retries || 2,
|
|
5165
5593
|
inputs: item,
|
|
5166
5594
|
item: item.id,
|
|
5167
5595
|
queue: queue.queue,
|
|
@@ -5404,68 +5832,8 @@ var import_express7 = require("express");
|
|
|
5404
5832
|
|
|
5405
5833
|
// src/registry/routes.ts
|
|
5406
5834
|
var import_express3 = require("express");
|
|
5407
|
-
|
|
5408
|
-
// src/bullmq/queues.ts
|
|
5409
|
-
var import_bullmq4 = require("bullmq");
|
|
5410
|
-
var import_bullmq_otel = require("bullmq-otel");
|
|
5411
|
-
var ExuluQueues = class {
|
|
5412
|
-
queues;
|
|
5413
|
-
constructor() {
|
|
5414
|
-
this.queues = [];
|
|
5415
|
-
}
|
|
5416
|
-
queue(name) {
|
|
5417
|
-
return this.queues.find((x) => x.queue?.name === name);
|
|
5418
|
-
}
|
|
5419
|
-
// name: string
|
|
5420
|
-
// concurrency: global concurrency for the queue
|
|
5421
|
-
// ratelimit: maximum number of jobs per second
|
|
5422
|
-
// Rate limit is set on workers (see workers.ts), even global rate limits,
|
|
5423
|
-
// that is a bit counter-intuitive. Since queues are registered using .user
|
|
5424
|
-
// method of ExuluQueues we need to store the desired rate limit on the queue
|
|
5425
|
-
// here so we can use the value when creating workers for the queue instance
|
|
5426
|
-
// as there is no way to store a rate limit value natively on a bullm queue.
|
|
5427
|
-
use = async (name, concurrency = 1, ratelimit = 1) => {
|
|
5428
|
-
const existing = this.queues.find((x) => x.queue?.name === name);
|
|
5429
|
-
if (existing) {
|
|
5430
|
-
const globalConcurrency = await existing.queue.getGlobalConcurrency();
|
|
5431
|
-
if (globalConcurrency !== concurrency) {
|
|
5432
|
-
await existing.queue.setGlobalConcurrency(concurrency);
|
|
5433
|
-
}
|
|
5434
|
-
return {
|
|
5435
|
-
queue: existing.queue,
|
|
5436
|
-
ratelimit,
|
|
5437
|
-
concurrency
|
|
5438
|
-
};
|
|
5439
|
-
}
|
|
5440
|
-
if (!redisServer.host?.length || !redisServer.port?.length) {
|
|
5441
|
-
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() ).`);
|
|
5442
|
-
throw new Error(`[EXULU] no redis server configured.`);
|
|
5443
|
-
}
|
|
5444
|
-
const newQueue = new import_bullmq4.Queue(
|
|
5445
|
-
`${name}`,
|
|
5446
|
-
{
|
|
5447
|
-
connection: redisServer,
|
|
5448
|
-
telemetry: new import_bullmq_otel.BullMQOtel("simple-guide")
|
|
5449
|
-
}
|
|
5450
|
-
);
|
|
5451
|
-
await newQueue.setGlobalConcurrency(concurrency);
|
|
5452
|
-
this.queues.push({
|
|
5453
|
-
queue: newQueue,
|
|
5454
|
-
ratelimit,
|
|
5455
|
-
concurrency
|
|
5456
|
-
});
|
|
5457
|
-
return {
|
|
5458
|
-
queue: newQueue,
|
|
5459
|
-
ratelimit,
|
|
5460
|
-
concurrency
|
|
5461
|
-
};
|
|
5462
|
-
};
|
|
5463
|
-
};
|
|
5464
|
-
var queues = new ExuluQueues();
|
|
5465
|
-
|
|
5466
|
-
// src/registry/routes.ts
|
|
5467
5835
|
var import_express4 = __toESM(require("express"), 1);
|
|
5468
|
-
var
|
|
5836
|
+
var import_server2 = require("@apollo/server");
|
|
5469
5837
|
var import_cors = __toESM(require("cors"), 1);
|
|
5470
5838
|
var import_reflect_metadata = require("reflect-metadata");
|
|
5471
5839
|
var import_express5 = require("@as-integrations/express5");
|
|
@@ -5476,8 +5844,6 @@ var import_openai = __toESM(require("openai"), 1);
|
|
|
5476
5844
|
var import_fs = __toESM(require("fs"), 1);
|
|
5477
5845
|
var import_node_crypto3 = require("crypto");
|
|
5478
5846
|
var import_api = require("@opentelemetry/api");
|
|
5479
|
-
var import_ai2 = require("ai");
|
|
5480
|
-
var import_express_http_proxy = __toESM(require("express-http-proxy"), 1);
|
|
5481
5847
|
var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
|
|
5482
5848
|
|
|
5483
5849
|
// src/registry/utils/claude-messages.ts
|
|
@@ -5503,13 +5869,15 @@ var CLAUDE_MESSAGES = {
|
|
|
5503
5869
|
};
|
|
5504
5870
|
|
|
5505
5871
|
// src/registry/routes.ts
|
|
5872
|
+
var import_ai2 = require("ai");
|
|
5506
5873
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
5507
5874
|
var global_queues = {
|
|
5508
|
-
|
|
5875
|
+
eval_runs: "eval_runs"
|
|
5509
5876
|
};
|
|
5510
5877
|
var {
|
|
5511
5878
|
agentsSchema: agentsSchema2,
|
|
5512
5879
|
projectsSchema: projectsSchema2,
|
|
5880
|
+
jobResultsSchema: jobResultsSchema2,
|
|
5513
5881
|
testCasesSchema: testCasesSchema2,
|
|
5514
5882
|
evalSetsSchema: evalSetsSchema2,
|
|
5515
5883
|
evalRunsSchema: evalRunsSchema2,
|
|
@@ -5523,38 +5891,7 @@ var {
|
|
|
5523
5891
|
rbacSchema: rbacSchema2,
|
|
5524
5892
|
statisticsSchema: statisticsSchema2
|
|
5525
5893
|
} = coreSchemas.get();
|
|
5526
|
-
var
|
|
5527
|
-
console.log("[EXULU] creating recurring jobs.");
|
|
5528
|
-
const recurringJobSchedulersLogs = [];
|
|
5529
|
-
const queue = await queues.use(global_queues.logs_cleaner);
|
|
5530
|
-
recurringJobSchedulersLogs.push({
|
|
5531
|
-
name: global_queues.logs_cleaner,
|
|
5532
|
-
pattern: "0 10 * * * *",
|
|
5533
|
-
ttld: "30 days",
|
|
5534
|
-
opts: {
|
|
5535
|
-
backoff: 3,
|
|
5536
|
-
attempts: 5,
|
|
5537
|
-
removeOnFail: 1e3
|
|
5538
|
-
}
|
|
5539
|
-
});
|
|
5540
|
-
await queue.queue.upsertJobScheduler(
|
|
5541
|
-
"logs-cleaner-scheduler",
|
|
5542
|
-
{ pattern: "0 10 * * * *" },
|
|
5543
|
-
// every 10 minutes
|
|
5544
|
-
{
|
|
5545
|
-
name: global_queues.logs_cleaner,
|
|
5546
|
-
data: { ttld: 30 },
|
|
5547
|
-
// time to live in days
|
|
5548
|
-
opts: {
|
|
5549
|
-
backoff: 3,
|
|
5550
|
-
attempts: 5,
|
|
5551
|
-
removeOnFail: 1e3
|
|
5552
|
-
}
|
|
5553
|
-
}
|
|
5554
|
-
);
|
|
5555
|
-
return queue;
|
|
5556
|
-
};
|
|
5557
|
-
var createExpressRoutes = async (app, agents, tools, contexts, config, tracer) => {
|
|
5894
|
+
var createExpressRoutes = async (app, agents, tools, contexts, config, evals, tracer, queues2) => {
|
|
5558
5895
|
var corsOptions = {
|
|
5559
5896
|
origin: "*",
|
|
5560
5897
|
exposedHeaders: "*",
|
|
@@ -5573,19 +5910,15 @@ var createExpressRoutes = async (app, agents, tools, contexts, config, tracer) =
|
|
|
5573
5910
|
\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
5574
5911
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
5575
5912
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
5576
|
-
Intelligence Management Platform
|
|
5913
|
+
Intelligence Management Platform - Server
|
|
5577
5914
|
|
|
5578
5915
|
`);
|
|
5579
|
-
if (redisServer.host?.length && redisServer.port?.length) {
|
|
5580
|
-
await createRecurringJobs();
|
|
5581
|
-
} else {
|
|
5582
|
-
console.log("[EXULU] no redis server configured, not setting up recurring jobs.");
|
|
5583
|
-
}
|
|
5584
5916
|
const schema = createSDL([
|
|
5585
5917
|
usersSchema2(),
|
|
5586
5918
|
rolesSchema2(),
|
|
5587
5919
|
agentsSchema2(),
|
|
5588
5920
|
projectsSchema2(),
|
|
5921
|
+
jobResultsSchema2(),
|
|
5589
5922
|
evalRunsSchema2(),
|
|
5590
5923
|
platformConfigurationsSchema2(),
|
|
5591
5924
|
evalSetsSchema2(),
|
|
@@ -5596,8 +5929,8 @@ var createExpressRoutes = async (app, agents, tools, contexts, config, tracer) =
|
|
|
5596
5929
|
workflowTemplatesSchema2(),
|
|
5597
5930
|
statisticsSchema2(),
|
|
5598
5931
|
rbacSchema2()
|
|
5599
|
-
], contexts ?? [], agents, tools, config);
|
|
5600
|
-
const server = new
|
|
5932
|
+
], contexts ?? [], agents, tools, config, evals, queues2 || []);
|
|
5933
|
+
const server = new import_server2.ApolloServer({
|
|
5601
5934
|
cache: new import_utils3.InMemoryLRUCache(),
|
|
5602
5935
|
schema,
|
|
5603
5936
|
introspection: true
|
|
@@ -5727,103 +6060,6 @@ Mood: friendly and intelligent.
|
|
|
5727
6060
|
image: `${process.env.BACKEND}/${uuid}.png`
|
|
5728
6061
|
});
|
|
5729
6062
|
});
|
|
5730
|
-
app.post("/evals/run/:id", async (req, res) => {
|
|
5731
|
-
console.log("[EXULU] /evals/run/:id", req.params.id);
|
|
5732
|
-
const authenticationResult = await requestValidators.authenticate(req);
|
|
5733
|
-
if (!authenticationResult.user?.id) {
|
|
5734
|
-
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
5735
|
-
return;
|
|
5736
|
-
}
|
|
5737
|
-
const user = authenticationResult.user;
|
|
5738
|
-
const evalRunId = req.params.id;
|
|
5739
|
-
if (!user.super_admin && (!user.role || user.role.evals !== "write")) {
|
|
5740
|
-
res.status(403).json({
|
|
5741
|
-
message: "You don't have permission to run evals. Required: super_admin or evals write access."
|
|
5742
|
-
});
|
|
5743
|
-
return;
|
|
5744
|
-
}
|
|
5745
|
-
const { db: db3 } = await postgresClient();
|
|
5746
|
-
const evalRun = await db3.from("eval_runs").where({ id: evalRunId }).first();
|
|
5747
|
-
if (!evalRun) {
|
|
5748
|
-
res.status(404).json({
|
|
5749
|
-
message: "Eval run not found."
|
|
5750
|
-
});
|
|
5751
|
-
return;
|
|
5752
|
-
}
|
|
5753
|
-
const hasAccessToEvalRun = await checkRecordAccess(evalRun, "write", user);
|
|
5754
|
-
if (!hasAccessToEvalRun) {
|
|
5755
|
-
res.status(403).json({
|
|
5756
|
-
message: "You don't have access to this eval run."
|
|
5757
|
-
});
|
|
5758
|
-
return;
|
|
5759
|
-
}
|
|
5760
|
-
const testCaseIds = evalRun.test_case_ids ? typeof evalRun.test_case_ids === "string" ? JSON.parse(evalRun.test_case_ids) : evalRun.test_case_ids : [];
|
|
5761
|
-
const evalFunctionIds = evalRun.eval_function_ids ? typeof evalRun.eval_function_ids === "string" ? JSON.parse(evalRun.eval_function_ids) : evalRun.eval_function_ids : [];
|
|
5762
|
-
if (!testCaseIds || testCaseIds.length === 0) {
|
|
5763
|
-
res.status(400).json({
|
|
5764
|
-
message: "No test cases selected for this eval run."
|
|
5765
|
-
});
|
|
5766
|
-
return;
|
|
5767
|
-
}
|
|
5768
|
-
if (!evalFunctionIds || evalFunctionIds.length === 0) {
|
|
5769
|
-
res.status(400).json({
|
|
5770
|
-
message: "No eval functions selected for this eval run."
|
|
5771
|
-
});
|
|
5772
|
-
return;
|
|
5773
|
-
}
|
|
5774
|
-
const testCases = await db3.from("test_cases").whereIn("id", testCaseIds);
|
|
5775
|
-
if (testCases.length === 0) {
|
|
5776
|
-
res.status(404).json({
|
|
5777
|
-
message: "No test cases found."
|
|
5778
|
-
});
|
|
5779
|
-
return;
|
|
5780
|
-
}
|
|
5781
|
-
const agentInstance = await loadAgent(evalRun.agentId);
|
|
5782
|
-
if (!agentInstance) {
|
|
5783
|
-
res.status(404).json({
|
|
5784
|
-
message: "Agent instance not found."
|
|
5785
|
-
});
|
|
5786
|
-
return;
|
|
5787
|
-
}
|
|
5788
|
-
const evalQueue = await queues.use("evals");
|
|
5789
|
-
const jobIds = [];
|
|
5790
|
-
for (const testCase of testCases) {
|
|
5791
|
-
const existingJobs = await evalQueue.queue.getJobs(["waiting", "active", "delayed", "paused"]);
|
|
5792
|
-
const duplicateJob = existingJobs.find(
|
|
5793
|
-
(job2) => job2.data.evalRunId === evalRunId && job2.data.testCaseId === testCase.id && job2.data.type === "eval"
|
|
5794
|
-
);
|
|
5795
|
-
if (duplicateJob) {
|
|
5796
|
-
console.log(`[EXULU] Skipping duplicate job for eval run ${evalRunId} and test case ${testCase.id}`);
|
|
5797
|
-
continue;
|
|
5798
|
-
}
|
|
5799
|
-
const job = await evalQueue.queue.add(`eval-${testCase.id}`, {
|
|
5800
|
-
type: "eval",
|
|
5801
|
-
evalRunId,
|
|
5802
|
-
testCaseId: testCase.id,
|
|
5803
|
-
evalFunctionIds,
|
|
5804
|
-
// Array of eval function IDs - worker will create child jobs for these
|
|
5805
|
-
agentId: evalRun.agentId,
|
|
5806
|
-
inputs: testCase.inputs,
|
|
5807
|
-
expected_output: testCase.expected_output,
|
|
5808
|
-
expected_tools: testCase.expected_tools,
|
|
5809
|
-
expected_knowledge_sources: testCase.expected_knowledge_sources,
|
|
5810
|
-
expected_agent_tools: testCase.expected_agent_tools,
|
|
5811
|
-
config: evalRun.config,
|
|
5812
|
-
scoring_method: evalRun.scoring_method,
|
|
5813
|
-
pass_threshold: evalRun.pass_threshold,
|
|
5814
|
-
user: user.id,
|
|
5815
|
-
role: user.role?.id
|
|
5816
|
-
});
|
|
5817
|
-
jobIds.push(job.id);
|
|
5818
|
-
}
|
|
5819
|
-
res.status(200).json({
|
|
5820
|
-
message: `Created ${jobIds.length} eval jobs.`,
|
|
5821
|
-
jobIds,
|
|
5822
|
-
evalRunId,
|
|
5823
|
-
testCaseCount: testCases.length,
|
|
5824
|
-
evalFunctionCount: evalFunctionIds.length
|
|
5825
|
-
});
|
|
5826
|
-
});
|
|
5827
6063
|
app.get("/ping", async (req, res) => {
|
|
5828
6064
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
5829
6065
|
if (!authenticationResult.user?.id) {
|
|
@@ -5952,11 +6188,11 @@ Mood: friendly and intelligent.
|
|
|
5952
6188
|
providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
|
|
5953
6189
|
}
|
|
5954
6190
|
if (!!headers.stream) {
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
6191
|
+
const statistics = {
|
|
6192
|
+
label: agent.name,
|
|
6193
|
+
trigger: "agent"
|
|
6194
|
+
};
|
|
6195
|
+
const result = await agent.generateStream({
|
|
5960
6196
|
contexts,
|
|
5961
6197
|
user,
|
|
5962
6198
|
instructions: agentInstance.instructions,
|
|
@@ -5966,10 +6202,79 @@ Mood: friendly and intelligent.
|
|
|
5966
6202
|
allExuluTools: tools,
|
|
5967
6203
|
providerapikey,
|
|
5968
6204
|
toolConfigs: agentInstance.tools,
|
|
5969
|
-
exuluConfig: config
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
6205
|
+
exuluConfig: config
|
|
6206
|
+
});
|
|
6207
|
+
result.stream.consumeStream();
|
|
6208
|
+
result.stream.pipeUIMessageStreamToResponse(res, {
|
|
6209
|
+
messageMetadata: ({ part }) => {
|
|
6210
|
+
if (part.type === "finish") {
|
|
6211
|
+
return {
|
|
6212
|
+
totalTokens: part.totalUsage.totalTokens,
|
|
6213
|
+
reasoningTokens: part.totalUsage.reasoningTokens,
|
|
6214
|
+
inputTokens: part.totalUsage.inputTokens,
|
|
6215
|
+
outputTokens: part.totalUsage.outputTokens,
|
|
6216
|
+
cachedInputTokens: part.totalUsage.cachedInputTokens
|
|
6217
|
+
};
|
|
6218
|
+
}
|
|
6219
|
+
},
|
|
6220
|
+
originalMessages: result.originalMessages,
|
|
6221
|
+
sendReasoning: true,
|
|
6222
|
+
sendSources: true,
|
|
6223
|
+
onError: (error) => {
|
|
6224
|
+
console.error("[EXULU] chat response error.", error);
|
|
6225
|
+
return errorHandler(error);
|
|
6226
|
+
},
|
|
6227
|
+
generateMessageId: (0, import_ai2.createIdGenerator)({
|
|
6228
|
+
prefix: "msg_",
|
|
6229
|
+
size: 16
|
|
6230
|
+
}),
|
|
6231
|
+
onFinish: async ({ messages, isContinuation, isAborted, responseMessage }) => {
|
|
6232
|
+
if (headers.session) {
|
|
6233
|
+
await saveChat({
|
|
6234
|
+
session: headers.session,
|
|
6235
|
+
user: user.id,
|
|
6236
|
+
messages: messages.filter((x) => !result.previousMessages.find((y) => y.id === x.id))
|
|
6237
|
+
});
|
|
6238
|
+
}
|
|
6239
|
+
const metadata = messages[messages.length - 1]?.metadata;
|
|
6240
|
+
console.log("[EXULU] Finished streaming", metadata);
|
|
6241
|
+
console.log("[EXULU] Statistics", {
|
|
6242
|
+
label: agent.name,
|
|
6243
|
+
trigger: "agent"
|
|
6244
|
+
});
|
|
6245
|
+
if (statistics) {
|
|
6246
|
+
await Promise.all([
|
|
6247
|
+
updateStatistic({
|
|
6248
|
+
name: "count",
|
|
6249
|
+
label: statistics.label,
|
|
6250
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
6251
|
+
trigger: statistics.trigger,
|
|
6252
|
+
count: 1,
|
|
6253
|
+
user: user.id,
|
|
6254
|
+
role: user?.role?.id
|
|
6255
|
+
}),
|
|
6256
|
+
...metadata?.inputTokens ? [
|
|
6257
|
+
updateStatistic({
|
|
6258
|
+
name: "inputTokens",
|
|
6259
|
+
label: statistics.label,
|
|
6260
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
6261
|
+
trigger: statistics.trigger,
|
|
6262
|
+
count: metadata?.inputTokens,
|
|
6263
|
+
user: user.id,
|
|
6264
|
+
role: user?.role?.id
|
|
6265
|
+
})
|
|
6266
|
+
] : [],
|
|
6267
|
+
...metadata?.outputTokens ? [
|
|
6268
|
+
updateStatistic({
|
|
6269
|
+
name: "outputTokens",
|
|
6270
|
+
label: statistics.label,
|
|
6271
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
6272
|
+
trigger: statistics.trigger,
|
|
6273
|
+
count: metadata?.outputTokens
|
|
6274
|
+
})
|
|
6275
|
+
] : []
|
|
6276
|
+
]);
|
|
6277
|
+
}
|
|
5973
6278
|
}
|
|
5974
6279
|
});
|
|
5975
6280
|
return;
|
|
@@ -5978,7 +6283,7 @@ Mood: friendly and intelligent.
|
|
|
5978
6283
|
user,
|
|
5979
6284
|
instructions: agentInstance.instructions,
|
|
5980
6285
|
session: headers.session,
|
|
5981
|
-
|
|
6286
|
+
inputMessages: [req.body.message],
|
|
5982
6287
|
contexts,
|
|
5983
6288
|
currentTools: enabledTools,
|
|
5984
6289
|
allExuluTools: tools,
|
|
@@ -5995,96 +6300,11 @@ Mood: friendly and intelligent.
|
|
|
5995
6300
|
}
|
|
5996
6301
|
});
|
|
5997
6302
|
});
|
|
5998
|
-
if (config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
|
|
6303
|
+
if (config?.fileUploads && config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
|
|
5999
6304
|
await createUppyRoutes(app, config);
|
|
6000
6305
|
} else {
|
|
6001
6306
|
console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in ExuluApp instance.");
|
|
6002
6307
|
}
|
|
6003
|
-
app.use("/xxx/anthropic/:id", import_express4.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), (0, import_express_http_proxy.default)(
|
|
6004
|
-
(req, res, next) => {
|
|
6005
|
-
return "https://api.anthropic.com";
|
|
6006
|
-
},
|
|
6007
|
-
{
|
|
6008
|
-
limit: "50mb",
|
|
6009
|
-
memoizeHost: false,
|
|
6010
|
-
preserveHostHdr: true,
|
|
6011
|
-
secure: false,
|
|
6012
|
-
reqAsBuffer: true,
|
|
6013
|
-
proxyReqBodyDecorator: function(bodyContent, srcReq) {
|
|
6014
|
-
return bodyContent;
|
|
6015
|
-
},
|
|
6016
|
-
userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
|
|
6017
|
-
console.log("[EXULU] Proxy response!", proxyResData);
|
|
6018
|
-
proxyResData = proxyResData.toString();
|
|
6019
|
-
console.log("[EXULU] Proxy response string!", proxyResData);
|
|
6020
|
-
return proxyResData;
|
|
6021
|
-
},
|
|
6022
|
-
proxyReqPathResolver: (req) => {
|
|
6023
|
-
const prefix = `/gateway/anthropic/${req.params.id}`;
|
|
6024
|
-
let path = req.url.startsWith(prefix) ? req.url.slice(prefix.length) : req.url;
|
|
6025
|
-
if (!path.startsWith("/")) path = "/" + path;
|
|
6026
|
-
console.log("[EXULU] Provider path:", path);
|
|
6027
|
-
return path;
|
|
6028
|
-
},
|
|
6029
|
-
proxyReqOptDecorator: function(proxyReqOpts, srcReq) {
|
|
6030
|
-
return new Promise(async (resolve, reject) => {
|
|
6031
|
-
try {
|
|
6032
|
-
const authenticationResult = await requestValidators.authenticate(srcReq);
|
|
6033
|
-
if (!authenticationResult.user?.id) {
|
|
6034
|
-
console.log("[EXULU] failed authentication result", authenticationResult);
|
|
6035
|
-
reject(authenticationResult.message);
|
|
6036
|
-
return;
|
|
6037
|
-
}
|
|
6038
|
-
console.log("[EXULU] Authenticated call", authenticationResult.user?.email);
|
|
6039
|
-
const { db: db3 } = await postgresClient();
|
|
6040
|
-
let query = db3("agents");
|
|
6041
|
-
query.select("*");
|
|
6042
|
-
query = applyAccessControl(agentsSchema2(), authenticationResult.user, query);
|
|
6043
|
-
query.where({ id: srcReq.params.id });
|
|
6044
|
-
const agent = await query.first();
|
|
6045
|
-
if (!agent) {
|
|
6046
|
-
reject(new Error("Agent with id " + srcReq.params.id + " not found."));
|
|
6047
|
-
return;
|
|
6048
|
-
}
|
|
6049
|
-
console.log("[EXULU] Agent loaded", agent.name);
|
|
6050
|
-
const backend = agents.find((x) => x.id === agent.backend);
|
|
6051
|
-
if (!process.env.NEXTAUTH_SECRET) {
|
|
6052
|
-
reject(new Error("Missing NEXTAUTH_SECRET"));
|
|
6053
|
-
return;
|
|
6054
|
-
}
|
|
6055
|
-
if (!agent.providerapikey) {
|
|
6056
|
-
reject(new Error("API Key not set for agent"));
|
|
6057
|
-
return;
|
|
6058
|
-
}
|
|
6059
|
-
const variableName = agent.providerapikey;
|
|
6060
|
-
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
6061
|
-
console.log("[EXULU] Variable loaded", variable);
|
|
6062
|
-
let providerapikey = variable.value;
|
|
6063
|
-
if (!variable.encrypted) {
|
|
6064
|
-
reject(new Error("API Key not encrypted for agent"));
|
|
6065
|
-
return;
|
|
6066
|
-
}
|
|
6067
|
-
if (variable.encrypted) {
|
|
6068
|
-
const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
6069
|
-
providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
|
|
6070
|
-
}
|
|
6071
|
-
console.log("[EXULU] Provider API key", providerapikey);
|
|
6072
|
-
proxyReqOpts.headers["x-api-key"] = providerapikey;
|
|
6073
|
-
proxyReqOpts.rejectUnauthorized = false;
|
|
6074
|
-
delete proxyReqOpts.headers["provider"];
|
|
6075
|
-
const url = new URL("https://api.anthropic.com");
|
|
6076
|
-
proxyReqOpts.headers["host"] = url.host;
|
|
6077
|
-
proxyReqOpts.headers["anthropic-version"] = "2023-06-01";
|
|
6078
|
-
console.log("[EXULU] Proxy request headers", proxyReqOpts.headers);
|
|
6079
|
-
resolve(proxyReqOpts);
|
|
6080
|
-
} catch (error) {
|
|
6081
|
-
console.error("[EXULU] Proxy error", error);
|
|
6082
|
-
reject(error);
|
|
6083
|
-
}
|
|
6084
|
-
});
|
|
6085
|
-
}
|
|
6086
|
-
}
|
|
6087
|
-
));
|
|
6088
6308
|
app.get("/config", async (req, res) => {
|
|
6089
6309
|
res.status(200).json({
|
|
6090
6310
|
message: "Config fetched successfully.",
|
|
@@ -6256,10 +6476,36 @@ var createCustomAnthropicStreamingMessage = (message) => {
|
|
|
6256
6476
|
|
|
6257
6477
|
// src/registry/workers.ts
|
|
6258
6478
|
var import_ioredis = __toESM(require("ioredis"), 1);
|
|
6259
|
-
var
|
|
6479
|
+
var import_bullmq4 = require("bullmq");
|
|
6260
6480
|
var import_api2 = require("@opentelemetry/api");
|
|
6481
|
+
var import_uuid3 = require("uuid");
|
|
6482
|
+
var import_ai3 = require("ai");
|
|
6483
|
+
var import_crypto_js4 = __toESM(require("crypto-js"), 1);
|
|
6484
|
+
|
|
6485
|
+
// src/registry/log-metadata.ts
|
|
6486
|
+
function logMetadata(id, additionalMetadata) {
|
|
6487
|
+
return {
|
|
6488
|
+
__logMetadata: true,
|
|
6489
|
+
id,
|
|
6490
|
+
...additionalMetadata
|
|
6491
|
+
};
|
|
6492
|
+
}
|
|
6493
|
+
|
|
6494
|
+
// src/registry/workers.ts
|
|
6261
6495
|
var redisConnection;
|
|
6262
|
-
var createWorkers = async (queues2, config, contexts, tracer) => {
|
|
6496
|
+
var createWorkers = async (agents, queues2, config, contexts, evals, tools, tracer) => {
|
|
6497
|
+
console.log(`
|
|
6498
|
+
\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
|
|
6499
|
+
\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
|
|
6500
|
+
\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
6501
|
+
\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
6502
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
6503
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
6504
|
+
Intelligence Management Platform - Workers
|
|
6505
|
+
|
|
6506
|
+
`);
|
|
6507
|
+
console.log("[EXULU] creating workers for " + queues2?.length + " queues.");
|
|
6508
|
+
console.log("[EXULU] queues", queues2.map((q) => q.queue.name));
|
|
6263
6509
|
if (!redisServer.host || !redisServer.port) {
|
|
6264
6510
|
console.error("[EXULU] you are trying to start worker, but no redis server is configured in the environment.");
|
|
6265
6511
|
throw new Error("No redis server configured in the environment, so cannot start worker.");
|
|
@@ -6267,80 +6513,333 @@ var createWorkers = async (queues2, config, contexts, tracer) => {
|
|
|
6267
6513
|
if (!redisConnection) {
|
|
6268
6514
|
redisConnection = new import_ioredis.default({
|
|
6269
6515
|
...redisServer,
|
|
6516
|
+
enableOfflineQueue: true,
|
|
6517
|
+
retryStrategy: function(times) {
|
|
6518
|
+
return Math.max(Math.min(Math.exp(times), 2e4), 1e3);
|
|
6519
|
+
},
|
|
6270
6520
|
maxRetriesPerRequest: null
|
|
6271
6521
|
});
|
|
6272
6522
|
}
|
|
6273
6523
|
const workers = queues2.map((queue) => {
|
|
6274
|
-
console.log(`[EXULU] creating worker for queue ${queue}.`);
|
|
6275
|
-
const worker = new
|
|
6276
|
-
`${queue}`,
|
|
6524
|
+
console.log(`[EXULU] creating worker for queue ${queue.queue.name}.`);
|
|
6525
|
+
const worker = new import_bullmq4.Worker(
|
|
6526
|
+
`${queue.queue.name}`,
|
|
6277
6527
|
async (bullmqJob) => {
|
|
6528
|
+
console.log("[EXULU] starting execution for job", logMetadata(bullmqJob.name, {
|
|
6529
|
+
name: bullmqJob.name,
|
|
6530
|
+
status: await bullmqJob.getState(),
|
|
6531
|
+
type: bullmqJob.data.type
|
|
6532
|
+
}));
|
|
6278
6533
|
const { db: db3 } = await postgresClient();
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6534
|
+
const data = bullmqJob.data;
|
|
6535
|
+
const timeoutMs = data.timeoutInSeconds * 1e3;
|
|
6536
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
6537
|
+
setTimeout(() => {
|
|
6538
|
+
reject(new Error(`Timeout for job ${bullmqJob.id} reached after ${data.timeoutInSeconds}s`));
|
|
6539
|
+
}, timeoutMs);
|
|
6540
|
+
});
|
|
6541
|
+
const workPromise = (async () => {
|
|
6542
|
+
try {
|
|
6543
|
+
bullmq.validate(bullmqJob.id, data);
|
|
6544
|
+
if (data.type === "embedder") {
|
|
6545
|
+
console.log("[EXULU] running an embedder job.", logMetadata(bullmqJob.name));
|
|
6546
|
+
const label = `embedder-${bullmqJob.name}`;
|
|
6547
|
+
await db3.from("job_results").insert({
|
|
6548
|
+
job_id: bullmqJob.id,
|
|
6549
|
+
label,
|
|
6550
|
+
state: await bullmqJob.getState(),
|
|
6551
|
+
result: null,
|
|
6552
|
+
metadata: {}
|
|
6553
|
+
});
|
|
6554
|
+
const context = contexts.find((context2) => context2.id === data.context);
|
|
6555
|
+
if (!context) {
|
|
6556
|
+
throw new Error(`Context ${data.context} not found in the registry.`);
|
|
6557
|
+
}
|
|
6558
|
+
if (!data.embedder) {
|
|
6559
|
+
throw new Error(`No embedder set for embedder job.`);
|
|
6560
|
+
}
|
|
6561
|
+
const embedder = contexts.find((context2) => context2.embedder?.id === data.embedder);
|
|
6562
|
+
if (!embedder) {
|
|
6563
|
+
throw new Error(`Embedder ${data.embedder} not found in the registry.`);
|
|
6564
|
+
}
|
|
6565
|
+
const result = await context.createAndUpsertEmbeddings(data.inputs, config, data.user, {
|
|
6566
|
+
label: embedder.name,
|
|
6567
|
+
trigger: data.trigger
|
|
6568
|
+
}, data.role, bullmqJob.id);
|
|
6569
|
+
return {
|
|
6570
|
+
result,
|
|
6571
|
+
metadata: {}
|
|
6572
|
+
};
|
|
6311
6573
|
}
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6574
|
+
if (data.type === "processor") {
|
|
6575
|
+
console.log("[EXULU] running a processor job.", logMetadata(bullmqJob.name));
|
|
6576
|
+
const label = `processor-${bullmqJob.name}`;
|
|
6577
|
+
await db3.from("job_results").insert({
|
|
6578
|
+
job_id: bullmqJob.id,
|
|
6579
|
+
label,
|
|
6580
|
+
state: await bullmqJob.getState(),
|
|
6581
|
+
result: null,
|
|
6582
|
+
metadata: {}
|
|
6583
|
+
});
|
|
6584
|
+
const context = contexts.find((context2) => context2.id === data.context);
|
|
6585
|
+
if (!context) {
|
|
6586
|
+
throw new Error(`Context ${data.context} not found in the registry.`);
|
|
6587
|
+
}
|
|
6588
|
+
const field = context.fields.find((field2) => field2.name === data.inputs.field);
|
|
6589
|
+
if (!field) {
|
|
6590
|
+
throw new Error(`Field ${data.inputs.field} not found in the context ${data.context}.`);
|
|
6591
|
+
}
|
|
6592
|
+
if (!field.processor) {
|
|
6593
|
+
throw new Error(`Processor not set for field ${data.inputs.field} in the context ${data.context}.`);
|
|
6594
|
+
}
|
|
6595
|
+
const exuluStorage = new ExuluStorage({ config });
|
|
6596
|
+
if (!data.user) {
|
|
6597
|
+
throw new Error(`User not set for processor job.`);
|
|
6598
|
+
}
|
|
6599
|
+
if (!data.role) {
|
|
6600
|
+
throw new Error(`Role not set for processor job.`);
|
|
6601
|
+
}
|
|
6602
|
+
const result = await field.processor.execute({
|
|
6603
|
+
item: data.inputs,
|
|
6604
|
+
user: data.user,
|
|
6605
|
+
role: data.role,
|
|
6606
|
+
utils: {
|
|
6607
|
+
storage: exuluStorage,
|
|
6608
|
+
items: {
|
|
6609
|
+
update: context.updateItem,
|
|
6610
|
+
create: context.createItem,
|
|
6611
|
+
delete: context.deleteItem
|
|
6612
|
+
}
|
|
6613
|
+
},
|
|
6614
|
+
config
|
|
6615
|
+
});
|
|
6616
|
+
return {
|
|
6617
|
+
result,
|
|
6618
|
+
metadata: {}
|
|
6619
|
+
};
|
|
6315
6620
|
}
|
|
6316
|
-
if (
|
|
6317
|
-
|
|
6621
|
+
if (data.type === "eval_run") {
|
|
6622
|
+
console.log("[EXULU] running an eval run job.", logMetadata(bullmqJob.name));
|
|
6623
|
+
const label = `eval-run-${data.eval_run_id}-${data.test_case_id}`;
|
|
6624
|
+
const existingResult = await db3.from("job_results").where({ label }).first();
|
|
6625
|
+
if (existingResult) {
|
|
6626
|
+
await db3.from("job_results").where({ label }).update({
|
|
6627
|
+
job_id: bullmqJob.id,
|
|
6628
|
+
label,
|
|
6629
|
+
state: await bullmqJob.getState(),
|
|
6630
|
+
result: null,
|
|
6631
|
+
metadata: {},
|
|
6632
|
+
tries: existingResult.tries + 1
|
|
6633
|
+
});
|
|
6634
|
+
} else {
|
|
6635
|
+
await db3.from("job_results").insert({
|
|
6636
|
+
job_id: bullmqJob.id,
|
|
6637
|
+
label,
|
|
6638
|
+
state: await bullmqJob.getState(),
|
|
6639
|
+
result: null,
|
|
6640
|
+
metadata: {},
|
|
6641
|
+
tries: 1
|
|
6642
|
+
});
|
|
6643
|
+
}
|
|
6644
|
+
const {
|
|
6645
|
+
agentInstance,
|
|
6646
|
+
backend: agentBackend,
|
|
6647
|
+
user,
|
|
6648
|
+
evalRun,
|
|
6649
|
+
testCase,
|
|
6650
|
+
messages: inputMessages
|
|
6651
|
+
} = await validateEvalPayload(data, agents);
|
|
6652
|
+
const retries = 3;
|
|
6653
|
+
let attempts = 0;
|
|
6654
|
+
const promise = new Promise(async (resolve, reject) => {
|
|
6655
|
+
while (attempts < retries) {
|
|
6656
|
+
try {
|
|
6657
|
+
const messages2 = await processUiMessagesFlow({
|
|
6658
|
+
agents,
|
|
6659
|
+
agentInstance,
|
|
6660
|
+
agentBackend,
|
|
6661
|
+
inputMessages,
|
|
6662
|
+
contexts,
|
|
6663
|
+
user,
|
|
6664
|
+
tools,
|
|
6665
|
+
config
|
|
6666
|
+
});
|
|
6667
|
+
resolve(messages2);
|
|
6668
|
+
break;
|
|
6669
|
+
} catch (error) {
|
|
6670
|
+
console.error(`[EXULU] error processing UI messages flow for agent ${agentInstance.name} (${agentInstance.id}).`, logMetadata(bullmqJob.name, {
|
|
6671
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6672
|
+
}));
|
|
6673
|
+
attempts++;
|
|
6674
|
+
if (attempts >= retries) {
|
|
6675
|
+
reject(error);
|
|
6676
|
+
}
|
|
6677
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
6678
|
+
}
|
|
6679
|
+
}
|
|
6680
|
+
});
|
|
6681
|
+
const result = await promise;
|
|
6682
|
+
const messages = result.messages;
|
|
6683
|
+
const metadata = result.metadata;
|
|
6684
|
+
const evalFunctions = evalRun.eval_functions;
|
|
6685
|
+
let evalFunctionResults = [];
|
|
6686
|
+
for (const evalFunction of evalFunctions) {
|
|
6687
|
+
const evalMethod = evals.find((e) => e.id === evalFunction.id);
|
|
6688
|
+
if (!evalMethod) {
|
|
6689
|
+
throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
|
|
6690
|
+
}
|
|
6691
|
+
let result2;
|
|
6692
|
+
if (evalMethod.queue) {
|
|
6693
|
+
const queue2 = await evalMethod.queue;
|
|
6694
|
+
const jobData = {
|
|
6695
|
+
...data,
|
|
6696
|
+
type: "eval_function",
|
|
6697
|
+
eval_functions: [{
|
|
6698
|
+
id: evalFunction.id,
|
|
6699
|
+
config: evalFunction.config || {}
|
|
6700
|
+
}],
|
|
6701
|
+
// updating the input messages with the messages we want to run the eval
|
|
6702
|
+
// function on, which are the output messages from the agent.
|
|
6703
|
+
inputs: messages
|
|
6704
|
+
};
|
|
6705
|
+
const redisId = (0, import_uuid3.v4)();
|
|
6706
|
+
const job = await queue2.queue.add("eval_function", jobData, {
|
|
6707
|
+
jobId: redisId,
|
|
6708
|
+
// Setting it to 3 as a sensible default, as
|
|
6709
|
+
// many AI services are quite unstable.
|
|
6710
|
+
attempts: queue2.retries || 3,
|
|
6711
|
+
// todo make this configurable?
|
|
6712
|
+
removeOnComplete: 5e3,
|
|
6713
|
+
removeOnFail: 1e4,
|
|
6714
|
+
backoff: queue2.backoff || {
|
|
6715
|
+
type: "exponential",
|
|
6716
|
+
delay: 2e3
|
|
6717
|
+
}
|
|
6718
|
+
});
|
|
6719
|
+
if (!job.id) {
|
|
6720
|
+
throw new Error(`Tried to add job to queue ${queue2.queue.name} but failed to get the job ID.`);
|
|
6721
|
+
}
|
|
6722
|
+
result2 = await pollJobResult({ queue: queue2, jobId: job.id });
|
|
6723
|
+
const evalFunctionResult = {
|
|
6724
|
+
test_case_id: testCase.id,
|
|
6725
|
+
eval_run_id: evalRun.id,
|
|
6726
|
+
eval_function_id: evalFunction.id,
|
|
6727
|
+
result: result2 || 0
|
|
6728
|
+
};
|
|
6729
|
+
console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata(bullmqJob.name, {
|
|
6730
|
+
result: result2 || 0
|
|
6731
|
+
}));
|
|
6732
|
+
evalFunctionResults.push(evalFunctionResult);
|
|
6733
|
+
} else {
|
|
6734
|
+
result2 = await evalMethod.run(
|
|
6735
|
+
agentInstance,
|
|
6736
|
+
agentBackend,
|
|
6737
|
+
testCase,
|
|
6738
|
+
messages,
|
|
6739
|
+
evalFunction.config || {}
|
|
6740
|
+
);
|
|
6741
|
+
const evalFunctionResult = {
|
|
6742
|
+
test_case_id: testCase.id,
|
|
6743
|
+
eval_run_id: evalRun.id,
|
|
6744
|
+
eval_function_id: evalFunction.id,
|
|
6745
|
+
result: result2 || 0
|
|
6746
|
+
};
|
|
6747
|
+
evalFunctionResults.push(evalFunctionResult);
|
|
6748
|
+
console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata(bullmqJob.name, {
|
|
6749
|
+
result: result2 || 0
|
|
6750
|
+
}));
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
const scores = evalFunctionResults.map((result2) => result2.result);
|
|
6754
|
+
let score = 0;
|
|
6755
|
+
switch (data.scoring_method) {
|
|
6756
|
+
case "median":
|
|
6757
|
+
score = getMedian(scores);
|
|
6758
|
+
break;
|
|
6759
|
+
case "average":
|
|
6760
|
+
score = getAverage(scores);
|
|
6761
|
+
break;
|
|
6762
|
+
case "sum":
|
|
6763
|
+
score = getSum(scores);
|
|
6764
|
+
break;
|
|
6765
|
+
}
|
|
6766
|
+
return {
|
|
6767
|
+
result: score,
|
|
6768
|
+
metadata: {
|
|
6769
|
+
...evalFunctionResults,
|
|
6770
|
+
...metadata
|
|
6771
|
+
}
|
|
6772
|
+
};
|
|
6318
6773
|
}
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6774
|
+
if (data.type === "eval_function") {
|
|
6775
|
+
console.log("[EXULU] running an eval function job.", logMetadata(bullmqJob.name));
|
|
6776
|
+
if (data.eval_functions?.length !== 1) {
|
|
6777
|
+
throw new Error(`Expected 1 eval function for eval function job, got ${data.eval_functions?.length}.`);
|
|
6778
|
+
}
|
|
6779
|
+
const label = `eval-function-${data.eval_run_id}-${data.test_case_id}-${data.eval_functions?.[0]?.id}`;
|
|
6780
|
+
const existingResult = await db3.from("job_results").where({ label }).first();
|
|
6781
|
+
if (existingResult) {
|
|
6782
|
+
await db3.from("job_results").where({ label }).update({
|
|
6783
|
+
job_id: bullmqJob.id,
|
|
6784
|
+
label,
|
|
6785
|
+
state: await bullmqJob.getState(),
|
|
6786
|
+
result: null,
|
|
6787
|
+
metadata: {},
|
|
6788
|
+
tries: existingResult.tries + 1
|
|
6789
|
+
});
|
|
6790
|
+
} else {
|
|
6791
|
+
await db3.from("job_results").insert({
|
|
6792
|
+
job_id: bullmqJob.id,
|
|
6793
|
+
label,
|
|
6794
|
+
state: await bullmqJob.getState(),
|
|
6795
|
+
result: null,
|
|
6796
|
+
metadata: {},
|
|
6797
|
+
tries: 1
|
|
6798
|
+
});
|
|
6799
|
+
}
|
|
6800
|
+
const {
|
|
6801
|
+
evalRun,
|
|
6802
|
+
agentInstance,
|
|
6803
|
+
backend,
|
|
6804
|
+
testCase,
|
|
6805
|
+
messages: inputMessages
|
|
6806
|
+
} = await validateEvalPayload(data, agents);
|
|
6807
|
+
const evalFunctions = evalRun.eval_functions;
|
|
6808
|
+
let result;
|
|
6809
|
+
for (const evalFunction of evalFunctions) {
|
|
6810
|
+
const evalMethod = evals.find((e) => e.id === evalFunction.id);
|
|
6811
|
+
if (!evalMethod) {
|
|
6812
|
+
throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
|
|
6327
6813
|
}
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6814
|
+
result = await evalMethod.run(
|
|
6815
|
+
agentInstance,
|
|
6816
|
+
backend,
|
|
6817
|
+
testCase,
|
|
6818
|
+
inputMessages,
|
|
6819
|
+
evalFunction.config || {}
|
|
6820
|
+
);
|
|
6821
|
+
console.log(`[EXULU] eval function ${evalFunction.id} result: ${result}`, logMetadata(bullmqJob.name, {
|
|
6822
|
+
result: result || 0
|
|
6823
|
+
}));
|
|
6824
|
+
}
|
|
6825
|
+
return {
|
|
6826
|
+
result,
|
|
6827
|
+
metadata: {}
|
|
6828
|
+
};
|
|
6829
|
+
}
|
|
6830
|
+
throw new Error(`Invalid job type: ${data.type} for job ${bullmqJob.name}.`);
|
|
6831
|
+
} catch (error) {
|
|
6832
|
+
console.error(`[EXULU] job failed.`, error instanceof Error ? error.message : String(error));
|
|
6833
|
+
throw error;
|
|
6332
6834
|
}
|
|
6333
|
-
}
|
|
6334
|
-
|
|
6335
|
-
status: "failed",
|
|
6336
|
-
finishedAt: /* @__PURE__ */ new Date(),
|
|
6337
|
-
error: error instanceof Error ? error.message : String(error)
|
|
6338
|
-
});
|
|
6339
|
-
throw new Error(error instanceof Error ? error.message : String(error));
|
|
6340
|
-
}
|
|
6835
|
+
})();
|
|
6836
|
+
return Promise.race([workPromise, timeoutPromise]);
|
|
6341
6837
|
},
|
|
6342
6838
|
{
|
|
6839
|
+
autorun: true,
|
|
6343
6840
|
connection: redisConnection,
|
|
6841
|
+
removeOnComplete: { count: 1e3 },
|
|
6842
|
+
removeOnFail: { count: 5e3 },
|
|
6344
6843
|
...queue.ratelimit && {
|
|
6345
6844
|
limiter: {
|
|
6346
6845
|
max: queue.ratelimit,
|
|
@@ -6349,22 +6848,312 @@ var createWorkers = async (queues2, config, contexts, tracer) => {
|
|
|
6349
6848
|
}
|
|
6350
6849
|
}
|
|
6351
6850
|
);
|
|
6352
|
-
worker.on("completed", (job, returnvalue) => {
|
|
6353
|
-
console.log(`[EXULU] completed job ${job.id}.`,
|
|
6851
|
+
worker.on("completed", async (job, returnvalue) => {
|
|
6852
|
+
console.log(`[EXULU] completed job ${job.id}.`, logMetadata(job.name, {
|
|
6853
|
+
result: returnvalue
|
|
6854
|
+
}));
|
|
6855
|
+
const { db: db3 } = await postgresClient();
|
|
6856
|
+
await db3.from("job_results").where({ job_id: job.id }).update({
|
|
6857
|
+
state: JOB_STATUS_ENUM.completed,
|
|
6858
|
+
result: returnvalue.result,
|
|
6859
|
+
metadata: returnvalue.metadata
|
|
6860
|
+
});
|
|
6354
6861
|
});
|
|
6355
|
-
worker.on("failed", (job, error, prev) => {
|
|
6862
|
+
worker.on("failed", async (job, error, prev) => {
|
|
6356
6863
|
if (job?.id) {
|
|
6357
|
-
|
|
6864
|
+
const { db: db3 } = await postgresClient();
|
|
6865
|
+
console.error(`[EXULU] failed job ${job.id}.`, error);
|
|
6866
|
+
await db3.from("job_results").where({ job_id: job.id }).update({
|
|
6867
|
+
state: JOB_STATUS_ENUM.failed,
|
|
6868
|
+
error
|
|
6869
|
+
});
|
|
6870
|
+
return;
|
|
6358
6871
|
}
|
|
6359
|
-
console.error(`[EXULU] job
|
|
6872
|
+
console.error(`[EXULU] job failed.`, job?.name ? logMetadata(job.name, {
|
|
6873
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6874
|
+
}) : error);
|
|
6875
|
+
});
|
|
6876
|
+
worker.on("error", (error) => {
|
|
6877
|
+
console.error(`[EXULU] worker error.`, error);
|
|
6360
6878
|
});
|
|
6361
6879
|
worker.on("progress", (job, progress) => {
|
|
6362
|
-
console.log(`[EXULU] job progress ${job.id}.`,
|
|
6880
|
+
console.log(`[EXULU] job progress ${job.id}.`, logMetadata(job.name, {
|
|
6881
|
+
progress
|
|
6882
|
+
}));
|
|
6363
6883
|
});
|
|
6884
|
+
const gracefulShutdown = async (signal) => {
|
|
6885
|
+
console.log(`Received ${signal}, closing server...`);
|
|
6886
|
+
await worker.close();
|
|
6887
|
+
process.exit(0);
|
|
6888
|
+
};
|
|
6889
|
+
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
6890
|
+
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
6364
6891
|
return worker;
|
|
6365
6892
|
});
|
|
6366
6893
|
return workers;
|
|
6367
6894
|
};
|
|
6895
|
+
var validateEvalPayload = async (data, agents) => {
|
|
6896
|
+
if (!data.eval_run_id) {
|
|
6897
|
+
throw new Error(`No eval run ID set for eval job.`);
|
|
6898
|
+
}
|
|
6899
|
+
if (!data.test_case_id) {
|
|
6900
|
+
throw new Error(`No test case ID set for eval job.`);
|
|
6901
|
+
}
|
|
6902
|
+
if (!data.user) {
|
|
6903
|
+
throw new Error(`No user set for eval job.`);
|
|
6904
|
+
}
|
|
6905
|
+
if (!data.role) {
|
|
6906
|
+
throw new Error(`No role set for eval job.`);
|
|
6907
|
+
}
|
|
6908
|
+
if (!data.agent_id) {
|
|
6909
|
+
throw new Error(`No agent ID set for eval job.`);
|
|
6910
|
+
}
|
|
6911
|
+
if (!data.inputs?.length) {
|
|
6912
|
+
throw new Error(`No inputs set for eval job, expected array of UIMessage objects.`);
|
|
6913
|
+
}
|
|
6914
|
+
const { db: db3 } = await postgresClient();
|
|
6915
|
+
const evalRun = await db3.from("eval_runs").where({ id: data.eval_run_id }).first();
|
|
6916
|
+
if (!evalRun) {
|
|
6917
|
+
throw new Error(`Eval run ${data.eval_run_id} not found in the database.`);
|
|
6918
|
+
}
|
|
6919
|
+
const agentInstance = await loadAgent(evalRun.agent_id);
|
|
6920
|
+
if (!agentInstance) {
|
|
6921
|
+
throw new Error(`Agent ${evalRun.agent_id} not found in the database.`);
|
|
6922
|
+
}
|
|
6923
|
+
const backend = agents.find((a) => a.id === agentInstance.backend);
|
|
6924
|
+
if (!backend) {
|
|
6925
|
+
throw new Error(`Backend ${agentInstance.backend} not found in the database.`);
|
|
6926
|
+
}
|
|
6927
|
+
const user = await db3.from("users").where({ id: data.user }).first();
|
|
6928
|
+
if (!user) {
|
|
6929
|
+
throw new Error(`User ${data.user} not found in the database.`);
|
|
6930
|
+
}
|
|
6931
|
+
const testCase = await db3.from("test_cases").where({ id: data.test_case_id }).first();
|
|
6932
|
+
if (!testCase) {
|
|
6933
|
+
throw new Error(`Test case ${data.test_case_id} not found in the database.`);
|
|
6934
|
+
}
|
|
6935
|
+
return {
|
|
6936
|
+
agentInstance,
|
|
6937
|
+
backend,
|
|
6938
|
+
user,
|
|
6939
|
+
testCase,
|
|
6940
|
+
evalRun,
|
|
6941
|
+
messages: data.inputs
|
|
6942
|
+
};
|
|
6943
|
+
};
|
|
6944
|
+
var pollJobResult = async ({ queue, jobId }) => {
|
|
6945
|
+
let attempts = 0;
|
|
6946
|
+
let timeoutInSeconds = queue.timeoutInSeconds || 180;
|
|
6947
|
+
const startTime = Date.now();
|
|
6948
|
+
let result;
|
|
6949
|
+
while (true) {
|
|
6950
|
+
attempts++;
|
|
6951
|
+
const job = await import_bullmq4.Job.fromId(queue.queue, jobId);
|
|
6952
|
+
if (!job) {
|
|
6953
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
6954
|
+
continue;
|
|
6955
|
+
}
|
|
6956
|
+
const elapsedTime = Date.now() - startTime;
|
|
6957
|
+
if (elapsedTime > timeoutInSeconds * 1e3) {
|
|
6958
|
+
throw new Error(`Job ${job.id} timed out after ${timeoutInSeconds} seconds for job eval function job ${job.name}.`);
|
|
6959
|
+
}
|
|
6960
|
+
console.log(`[EXULU] polling eval function job ${job.name} for state... (attempt ${attempts})`);
|
|
6961
|
+
const jobState = await job.getState();
|
|
6962
|
+
console.log(`[EXULU] eval function job ${job.name} state: ${jobState}`);
|
|
6963
|
+
if (jobState === "failed") {
|
|
6964
|
+
throw new Error(`Job ${job.name} (${job.id}) failed with error: ${job.failedReason}.`);
|
|
6965
|
+
}
|
|
6966
|
+
if (jobState === "completed") {
|
|
6967
|
+
console.log(`[EXULU] eval function job ${job.name} completed, getting result from database...`);
|
|
6968
|
+
const { db: db3 } = await postgresClient();
|
|
6969
|
+
const entry = await db3.from("job_results").where({ job_id: job.id }).first();
|
|
6970
|
+
result = entry?.result;
|
|
6971
|
+
if (result === void 0 || result === null || result === "") {
|
|
6972
|
+
throw new Error(`Eval function ${job.id} result not found in database for job eval function job ${job.name}.`);
|
|
6973
|
+
}
|
|
6974
|
+
console.log(`[EXULU] eval function ${job.id} result: ${result}`);
|
|
6975
|
+
break;
|
|
6976
|
+
}
|
|
6977
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
6978
|
+
}
|
|
6979
|
+
return result;
|
|
6980
|
+
};
|
|
6981
|
+
var processUiMessagesFlow = async ({
|
|
6982
|
+
agents,
|
|
6983
|
+
agentInstance,
|
|
6984
|
+
agentBackend,
|
|
6985
|
+
inputMessages,
|
|
6986
|
+
contexts,
|
|
6987
|
+
user,
|
|
6988
|
+
tools,
|
|
6989
|
+
config
|
|
6990
|
+
}) => {
|
|
6991
|
+
console.log("[EXULU] processing UI messages flow for agent.");
|
|
6992
|
+
console.log("[EXULU] input messages", inputMessages);
|
|
6993
|
+
console.log("[EXULU] agent tools", agentInstance.tools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6994
|
+
const disabledTools = [];
|
|
6995
|
+
let enabledTools = await getEnabledTools(agentInstance, tools, disabledTools, agents, user);
|
|
6996
|
+
console.log("[EXULU] enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
6997
|
+
const variableName = agentInstance.providerapikey;
|
|
6998
|
+
const { db: db3 } = await postgresClient();
|
|
6999
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
7000
|
+
if (!variable) {
|
|
7001
|
+
throw new Error(`Provider API key variable not found for agent ${agentInstance.name} (${agentInstance.id}).`);
|
|
7002
|
+
}
|
|
7003
|
+
let providerapikey = variable.value;
|
|
7004
|
+
if (!variable.encrypted) {
|
|
7005
|
+
throw new Error(`Provider API key variable not encrypted for agent ${agentInstance.name} (${agentInstance.id}), for security reasons you are only allowed to use encrypted variables for provider API keys.`);
|
|
7006
|
+
}
|
|
7007
|
+
if (variable.encrypted) {
|
|
7008
|
+
const bytes = import_crypto_js4.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
7009
|
+
providerapikey = bytes.toString(import_crypto_js4.default.enc.Utf8);
|
|
7010
|
+
}
|
|
7011
|
+
const messagesWithoutPlaceholder = inputMessages.filter(
|
|
7012
|
+
(message) => message.metadata?.type !== "placeholder"
|
|
7013
|
+
);
|
|
7014
|
+
console.log("[EXULU] messages without placeholder", messagesWithoutPlaceholder);
|
|
7015
|
+
let index = 0;
|
|
7016
|
+
let messageHistory = {
|
|
7017
|
+
messages: [],
|
|
7018
|
+
metadata: {
|
|
7019
|
+
tokens: {
|
|
7020
|
+
totalTokens: 0,
|
|
7021
|
+
reasoningTokens: 0,
|
|
7022
|
+
inputTokens: 0,
|
|
7023
|
+
outputTokens: 0,
|
|
7024
|
+
cachedInputTokens: 0
|
|
7025
|
+
},
|
|
7026
|
+
duration: 0
|
|
7027
|
+
}
|
|
7028
|
+
};
|
|
7029
|
+
for (const currentMessage of messagesWithoutPlaceholder) {
|
|
7030
|
+
console.log("[EXULU] running through the conversation");
|
|
7031
|
+
console.log("[EXULU] current index", index);
|
|
7032
|
+
console.log("[EXULU] current message", currentMessage);
|
|
7033
|
+
console.log("[EXULU] message history", messageHistory);
|
|
7034
|
+
const statistics = {
|
|
7035
|
+
label: agentInstance.name,
|
|
7036
|
+
trigger: "agent"
|
|
7037
|
+
};
|
|
7038
|
+
messageHistory = await new Promise(async (resolve, reject) => {
|
|
7039
|
+
const startTime = Date.now();
|
|
7040
|
+
try {
|
|
7041
|
+
const result = await agentBackend.generateStream({
|
|
7042
|
+
contexts,
|
|
7043
|
+
user,
|
|
7044
|
+
instructions: agentInstance.instructions,
|
|
7045
|
+
session: void 0,
|
|
7046
|
+
previousMessages: messageHistory.messages,
|
|
7047
|
+
message: currentMessage,
|
|
7048
|
+
currentTools: enabledTools,
|
|
7049
|
+
allExuluTools: tools,
|
|
7050
|
+
providerapikey,
|
|
7051
|
+
toolConfigs: agentInstance.tools,
|
|
7052
|
+
exuluConfig: config
|
|
7053
|
+
});
|
|
7054
|
+
console.log("[EXULU] consuming stream for agent.");
|
|
7055
|
+
const stream = result.stream.toUIMessageStream({
|
|
7056
|
+
messageMetadata: ({ part }) => {
|
|
7057
|
+
console.log("[EXULU] part", part.type);
|
|
7058
|
+
if (part.type === "finish") {
|
|
7059
|
+
return {
|
|
7060
|
+
totalTokens: part.totalUsage.totalTokens,
|
|
7061
|
+
reasoningTokens: part.totalUsage.reasoningTokens,
|
|
7062
|
+
inputTokens: part.totalUsage.inputTokens,
|
|
7063
|
+
outputTokens: part.totalUsage.outputTokens,
|
|
7064
|
+
cachedInputTokens: part.totalUsage.cachedInputTokens
|
|
7065
|
+
};
|
|
7066
|
+
}
|
|
7067
|
+
},
|
|
7068
|
+
originalMessages: result.originalMessages,
|
|
7069
|
+
sendReasoning: true,
|
|
7070
|
+
sendSources: true,
|
|
7071
|
+
onError: (error) => {
|
|
7072
|
+
console.error("[EXULU] Ui message stream error.", error);
|
|
7073
|
+
reject(error);
|
|
7074
|
+
return `Ui message stream error: ${error instanceof Error ? error.message : String(error)}`;
|
|
7075
|
+
},
|
|
7076
|
+
onFinish: async ({ messages, isContinuation, responseMessage }) => {
|
|
7077
|
+
const metadata = messages[messages.length - 1]?.metadata;
|
|
7078
|
+
console.log("[EXULU] Stream finished with messages:", messages);
|
|
7079
|
+
console.log("[EXULU] Stream metadata", metadata);
|
|
7080
|
+
await Promise.all([
|
|
7081
|
+
updateStatistic({
|
|
7082
|
+
name: "count",
|
|
7083
|
+
label: statistics.label,
|
|
7084
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
7085
|
+
trigger: statistics.trigger,
|
|
7086
|
+
count: 1,
|
|
7087
|
+
user: user.id,
|
|
7088
|
+
role: user?.role?.id
|
|
7089
|
+
}),
|
|
7090
|
+
...metadata?.inputTokens ? [
|
|
7091
|
+
updateStatistic({
|
|
7092
|
+
name: "inputTokens",
|
|
7093
|
+
label: statistics.label,
|
|
7094
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
7095
|
+
trigger: statistics.trigger,
|
|
7096
|
+
count: metadata?.inputTokens,
|
|
7097
|
+
user: user.id,
|
|
7098
|
+
role: user?.role?.id
|
|
7099
|
+
})
|
|
7100
|
+
] : [],
|
|
7101
|
+
...metadata?.outputTokens ? [
|
|
7102
|
+
updateStatistic({
|
|
7103
|
+
name: "outputTokens",
|
|
7104
|
+
label: statistics.label,
|
|
7105
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
7106
|
+
trigger: statistics.trigger,
|
|
7107
|
+
count: metadata?.outputTokens
|
|
7108
|
+
})
|
|
7109
|
+
] : []
|
|
7110
|
+
]);
|
|
7111
|
+
resolve({
|
|
7112
|
+
messages,
|
|
7113
|
+
metadata: {
|
|
7114
|
+
tokens: {
|
|
7115
|
+
totalTokens: messageHistory.metadata.tokens.totalTokens + metadata?.totalTokens,
|
|
7116
|
+
reasoningTokens: messageHistory.metadata.tokens.reasoningTokens + metadata?.reasoningTokens,
|
|
7117
|
+
inputTokens: messageHistory.metadata.tokens.inputTokens + metadata?.inputTokens,
|
|
7118
|
+
outputTokens: messageHistory.metadata.tokens.outputTokens + metadata?.outputTokens,
|
|
7119
|
+
cachedInputTokens: messageHistory.metadata.tokens.cachedInputTokens + metadata?.cachedInputTokens
|
|
7120
|
+
},
|
|
7121
|
+
duration: messageHistory.metadata.duration + (Date.now() - startTime)
|
|
7122
|
+
}
|
|
7123
|
+
});
|
|
7124
|
+
}
|
|
7125
|
+
});
|
|
7126
|
+
for await (const message of stream) {
|
|
7127
|
+
console.log("[EXULU] message", message);
|
|
7128
|
+
}
|
|
7129
|
+
} catch (error) {
|
|
7130
|
+
console.error(`[EXULU] error generating stream for agent ${agentInstance.name} (${agentInstance.id}).`, error);
|
|
7131
|
+
reject(error);
|
|
7132
|
+
}
|
|
7133
|
+
});
|
|
7134
|
+
index++;
|
|
7135
|
+
}
|
|
7136
|
+
console.log("[EXULU] finished processing UI messages flow for agent, messages result", messageHistory);
|
|
7137
|
+
return messageHistory;
|
|
7138
|
+
};
|
|
7139
|
+
function getMedian(arr) {
|
|
7140
|
+
if (arr.length === 0) return 0;
|
|
7141
|
+
const sortedArr = arr.slice().sort((a, b) => a - b);
|
|
7142
|
+
const mid = Math.floor(sortedArr.length / 2);
|
|
7143
|
+
if (sortedArr.length % 2 !== 0) {
|
|
7144
|
+
return sortedArr[mid];
|
|
7145
|
+
} else {
|
|
7146
|
+
return (sortedArr[mid - 1] + sortedArr[mid]) / 2;
|
|
7147
|
+
}
|
|
7148
|
+
}
|
|
7149
|
+
function getSum(arr) {
|
|
7150
|
+
if (arr.length === 0) return 0;
|
|
7151
|
+
return arr.reduce((a, b) => a + b, 0);
|
|
7152
|
+
}
|
|
7153
|
+
function getAverage(arr) {
|
|
7154
|
+
if (arr.length === 0) return 0;
|
|
7155
|
+
return arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
7156
|
+
}
|
|
6368
7157
|
|
|
6369
7158
|
// src/mcp/index.ts
|
|
6370
7159
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
@@ -6517,7 +7306,6 @@ var claudeOpus4Agent = new ExuluAgent2({
|
|
|
6517
7306
|
audio: [],
|
|
6518
7307
|
video: []
|
|
6519
7308
|
},
|
|
6520
|
-
evals: [],
|
|
6521
7309
|
maxContextLength: 2e5,
|
|
6522
7310
|
config: {
|
|
6523
7311
|
name: `CLAUDE-OPUS-4`,
|
|
@@ -6552,7 +7340,6 @@ var claudeSonnet4Agent = new ExuluAgent2({
|
|
|
6552
7340
|
audio: [],
|
|
6553
7341
|
video: []
|
|
6554
7342
|
},
|
|
6555
|
-
evals: [],
|
|
6556
7343
|
maxContextLength: 2e5,
|
|
6557
7344
|
config: {
|
|
6558
7345
|
name: `CLAUDE-SONNET-4`,
|
|
@@ -6580,7 +7367,6 @@ var claudeSonnet45Agent = new ExuluAgent2({
|
|
|
6580
7367
|
audio: [],
|
|
6581
7368
|
video: []
|
|
6582
7369
|
},
|
|
6583
|
-
evals: [],
|
|
6584
7370
|
maxContextLength: 2e5,
|
|
6585
7371
|
config: {
|
|
6586
7372
|
name: `CLAUDE-SONNET-4.5`,
|
|
@@ -6611,7 +7397,6 @@ var gpt5proAgent = new ExuluAgent2({
|
|
|
6611
7397
|
audio: [],
|
|
6612
7398
|
video: []
|
|
6613
7399
|
},
|
|
6614
|
-
evals: [],
|
|
6615
7400
|
maxContextLength: 4e5,
|
|
6616
7401
|
config: {
|
|
6617
7402
|
name: `GPT-5-PRO`,
|
|
@@ -6639,7 +7424,6 @@ var gpt5CodexAgent = new ExuluAgent2({
|
|
|
6639
7424
|
audio: [],
|
|
6640
7425
|
video: []
|
|
6641
7426
|
},
|
|
6642
|
-
evals: [],
|
|
6643
7427
|
maxContextLength: 4e5,
|
|
6644
7428
|
config: {
|
|
6645
7429
|
name: `GPT-5-CODEX`,
|
|
@@ -6667,7 +7451,6 @@ var gpt5MiniAgent = new ExuluAgent2({
|
|
|
6667
7451
|
audio: [],
|
|
6668
7452
|
video: []
|
|
6669
7453
|
},
|
|
6670
|
-
evals: [],
|
|
6671
7454
|
maxContextLength: 4e5,
|
|
6672
7455
|
config: {
|
|
6673
7456
|
name: `GPT-5-MINI`,
|
|
@@ -6702,7 +7485,6 @@ var gpt5agent = new ExuluAgent2({
|
|
|
6702
7485
|
audio: [],
|
|
6703
7486
|
video: []
|
|
6704
7487
|
},
|
|
6705
|
-
evals: [],
|
|
6706
7488
|
maxContextLength: 4e5,
|
|
6707
7489
|
config: {
|
|
6708
7490
|
name: `GPT-5`,
|
|
@@ -6737,7 +7519,6 @@ var gpt5NanoAgent = new ExuluAgent2({
|
|
|
6737
7519
|
audio: [],
|
|
6738
7520
|
video: []
|
|
6739
7521
|
},
|
|
6740
|
-
evals: [],
|
|
6741
7522
|
maxContextLength: 4e5,
|
|
6742
7523
|
config: {
|
|
6743
7524
|
name: `GPT-5-NANO`,
|
|
@@ -6765,7 +7546,6 @@ var gpt41Agent = new ExuluAgent2({
|
|
|
6765
7546
|
audio: [],
|
|
6766
7547
|
video: []
|
|
6767
7548
|
},
|
|
6768
|
-
evals: [],
|
|
6769
7549
|
maxContextLength: 1047576,
|
|
6770
7550
|
config: {
|
|
6771
7551
|
name: `GPT-4.1`,
|
|
@@ -6793,7 +7573,6 @@ var gpt41MiniAgent = new ExuluAgent2({
|
|
|
6793
7573
|
audio: [],
|
|
6794
7574
|
video: []
|
|
6795
7575
|
},
|
|
6796
|
-
evals: [],
|
|
6797
7576
|
maxContextLength: 1047576,
|
|
6798
7577
|
config: {
|
|
6799
7578
|
name: `GPT-4.1-MINI`,
|
|
@@ -6821,7 +7600,6 @@ var gpt4oAgent = new ExuluAgent2({
|
|
|
6821
7600
|
audio: [],
|
|
6822
7601
|
video: []
|
|
6823
7602
|
},
|
|
6824
|
-
evals: [],
|
|
6825
7603
|
maxContextLength: 128e3,
|
|
6826
7604
|
config: {
|
|
6827
7605
|
name: `Default agent`,
|
|
@@ -6849,7 +7627,6 @@ var gpt4oMiniAgent = new ExuluAgent2({
|
|
|
6849
7627
|
audio: [],
|
|
6850
7628
|
video: []
|
|
6851
7629
|
},
|
|
6852
|
-
evals: [],
|
|
6853
7630
|
maxContextLength: 128e3,
|
|
6854
7631
|
config: {
|
|
6855
7632
|
name: `GPT-4O-MINI`,
|
|
@@ -6940,6 +7717,55 @@ var outputsContext = new ExuluContext({
|
|
|
6940
7717
|
// src/registry/index.ts
|
|
6941
7718
|
var import_winston2 = __toESM(require("winston"), 1);
|
|
6942
7719
|
var import_util = __toESM(require("util"), 1);
|
|
7720
|
+
|
|
7721
|
+
// src/templates/evals/index.ts
|
|
7722
|
+
var import_zod3 = require("zod");
|
|
7723
|
+
var llmAsJudgeEval = new ExuluEval2({
|
|
7724
|
+
id: "llm_as_judge",
|
|
7725
|
+
name: "LLM as Judge",
|
|
7726
|
+
description: "Evaluate the output of the LLM as a judge.",
|
|
7727
|
+
execute: async ({ agent, backend, messages, testCase, config }) => {
|
|
7728
|
+
console.log("[EXULU] running llm as judge eval", { agent, backend, messages, testCase, config });
|
|
7729
|
+
let prompt = config?.prompt;
|
|
7730
|
+
if (!prompt) {
|
|
7731
|
+
console.error("[EXULU] prompt is required.");
|
|
7732
|
+
throw new Error("Prompt is required.");
|
|
7733
|
+
}
|
|
7734
|
+
const lastMessage = messages[messages.length - 1]?.parts?.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
7735
|
+
console.log("[EXULU] last message", lastMessage);
|
|
7736
|
+
if (!lastMessage) {
|
|
7737
|
+
return 0;
|
|
7738
|
+
}
|
|
7739
|
+
prompt = prompt.replace("{actual_output}", lastMessage);
|
|
7740
|
+
prompt = prompt.replace("{expected_output}", testCase.expected_output);
|
|
7741
|
+
if (!agent.providerapikey) {
|
|
7742
|
+
throw new Error(`Provider API key for agent ${agent.name} is required, variable name is ${agent.providerapikey} but it is not set.`);
|
|
7743
|
+
}
|
|
7744
|
+
const providerapikey = await ExuluVariables.get(agent.providerapikey);
|
|
7745
|
+
console.log("[EXULU] prompt", prompt);
|
|
7746
|
+
const response = await backend.generateSync({
|
|
7747
|
+
prompt,
|
|
7748
|
+
outputSchema: import_zod3.z.object({
|
|
7749
|
+
score: import_zod3.z.number().min(0).max(100).describe("The score between 0 and 100.")
|
|
7750
|
+
}),
|
|
7751
|
+
providerapikey
|
|
7752
|
+
});
|
|
7753
|
+
console.log("[EXULU] response", response);
|
|
7754
|
+
const score = parseFloat(response.score);
|
|
7755
|
+
if (isNaN(score)) {
|
|
7756
|
+
throw new Error(`Generated score from llm as a judge eval is not a number: ${response.score}`);
|
|
7757
|
+
}
|
|
7758
|
+
return score;
|
|
7759
|
+
},
|
|
7760
|
+
config: [{
|
|
7761
|
+
name: "prompt",
|
|
7762
|
+
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."
|
|
7763
|
+
}],
|
|
7764
|
+
queue: queues.register("llm_as_judge", 1, 1).use(),
|
|
7765
|
+
llm: true
|
|
7766
|
+
});
|
|
7767
|
+
|
|
7768
|
+
// src/registry/index.ts
|
|
6943
7769
|
var isDev = process.env.NODE_ENV !== "production";
|
|
6944
7770
|
var consoleTransport = new import_winston2.default.transports.Console({
|
|
6945
7771
|
format: isDev ? import_winston2.default.format.combine(
|
|
@@ -6950,6 +7776,20 @@ var consoleTransport = new import_winston2.default.transports.Console({
|
|
|
6950
7776
|
})
|
|
6951
7777
|
) : import_winston2.default.format.json()
|
|
6952
7778
|
});
|
|
7779
|
+
var formatArg = (arg) => typeof arg === "object" ? import_util.default.inspect(arg, { depth: null, colors: isDev }) : String(arg);
|
|
7780
|
+
var createLogMethod = (logger, logLevel) => {
|
|
7781
|
+
return (...args) => {
|
|
7782
|
+
const lastArg = args[args.length - 1];
|
|
7783
|
+
let metadata = void 0;
|
|
7784
|
+
let messageArgs = args;
|
|
7785
|
+
if (lastArg && typeof lastArg === "object" && lastArg.__logMetadata === true) {
|
|
7786
|
+
metadata = lastArg;
|
|
7787
|
+
messageArgs = args.slice(0, -1);
|
|
7788
|
+
}
|
|
7789
|
+
const message = messageArgs.map(formatArg).join(" ");
|
|
7790
|
+
logger[logLevel](message, metadata);
|
|
7791
|
+
};
|
|
7792
|
+
};
|
|
6953
7793
|
var isValidPostgresName = (id) => {
|
|
6954
7794
|
const regex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
6955
7795
|
const isValid = regex.test(id);
|
|
@@ -6959,6 +7799,7 @@ var isValidPostgresName = (id) => {
|
|
|
6959
7799
|
var ExuluApp = class {
|
|
6960
7800
|
_agents = [];
|
|
6961
7801
|
_config;
|
|
7802
|
+
_evals = [];
|
|
6962
7803
|
_queues = [];
|
|
6963
7804
|
_contexts = {};
|
|
6964
7805
|
_tools = [];
|
|
@@ -6967,7 +7808,11 @@ var ExuluApp = class {
|
|
|
6967
7808
|
}
|
|
6968
7809
|
// Factory function so we can async
|
|
6969
7810
|
// initialize the MCP server if needed.
|
|
6970
|
-
create = async ({ contexts, agents, config, tools }) => {
|
|
7811
|
+
create = async ({ contexts, agents, config, tools, evals }) => {
|
|
7812
|
+
this._evals = [
|
|
7813
|
+
llmAsJudgeEval,
|
|
7814
|
+
...evals ?? []
|
|
7815
|
+
];
|
|
6971
7816
|
this._contexts = {
|
|
6972
7817
|
...contexts,
|
|
6973
7818
|
codeStandardsContext,
|
|
@@ -7019,11 +7864,12 @@ var ExuluApp = class {
|
|
|
7019
7864
|
console.error(`%c[EXULU] Invalid ID found for a context, tool or agent: ${invalid.map((x) => x.id).join(", ")}. An ID must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or underscores and be a max length of 80 characters and at least 5 characters long.`, "color: orange; font-weight: bold; \n \n");
|
|
7020
7865
|
throw new Error(`Invalid ID found for a context, tool or agent: ${invalid.map((x) => x.id).join(", ")}. An ID must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or underscores and be a max length of 80 characters and at least 5 characters long.`);
|
|
7021
7866
|
}
|
|
7022
|
-
const contextsArray = Object.values(contexts || {});
|
|
7023
7867
|
const queueSet = /* @__PURE__ */ new Set();
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7868
|
+
if (redisServer.host?.length && redisServer.port?.length) {
|
|
7869
|
+
queues.register(global_queues.eval_runs, 1, 1);
|
|
7870
|
+
for (const queue of queues.list.values()) {
|
|
7871
|
+
const config2 = await queue.use();
|
|
7872
|
+
queueSet.add(config2);
|
|
7027
7873
|
}
|
|
7028
7874
|
}
|
|
7029
7875
|
this._queues = [...new Set(queueSet.values())];
|
|
@@ -7099,7 +7945,7 @@ var ExuluApp = class {
|
|
|
7099
7945
|
};
|
|
7100
7946
|
bullmq = {
|
|
7101
7947
|
workers: {
|
|
7102
|
-
create: async () => {
|
|
7948
|
+
create: async (queues2) => {
|
|
7103
7949
|
if (!this._config) {
|
|
7104
7950
|
throw new Error("Config not initialized, make sure to call await ExuluApp.create() first when starting your server.");
|
|
7105
7951
|
}
|
|
@@ -7112,16 +7958,22 @@ var ExuluApp = class {
|
|
|
7112
7958
|
enableOtel: this._config?.workers?.telemetry?.enabled ?? false,
|
|
7113
7959
|
transports
|
|
7114
7960
|
});
|
|
7115
|
-
|
|
7116
|
-
console.
|
|
7117
|
-
console.
|
|
7118
|
-
console.
|
|
7119
|
-
console.
|
|
7120
|
-
|
|
7961
|
+
console.log = createLogMethod(logger, "info");
|
|
7962
|
+
console.info = createLogMethod(logger, "info");
|
|
7963
|
+
console.warn = createLogMethod(logger, "warn");
|
|
7964
|
+
console.error = createLogMethod(logger, "error");
|
|
7965
|
+
console.debug = createLogMethod(logger, "debug");
|
|
7966
|
+
let filteredQueues = this._queues;
|
|
7967
|
+
if (queues2) {
|
|
7968
|
+
filteredQueues = filteredQueues.filter((q) => queues2.includes(q.queue.name));
|
|
7969
|
+
}
|
|
7121
7970
|
return await createWorkers(
|
|
7122
|
-
this.
|
|
7971
|
+
this._agents,
|
|
7972
|
+
filteredQueues,
|
|
7123
7973
|
this._config,
|
|
7124
7974
|
Object.values(this._contexts ?? {}),
|
|
7975
|
+
this._evals,
|
|
7976
|
+
this._tools,
|
|
7125
7977
|
tracer
|
|
7126
7978
|
);
|
|
7127
7979
|
}
|
|
@@ -7138,16 +7990,16 @@ var ExuluApp = class {
|
|
|
7138
7990
|
if (this._config?.telemetry?.enabled) {
|
|
7139
7991
|
tracer = import_api4.trace.getTracer("exulu", "1.0.0");
|
|
7140
7992
|
}
|
|
7993
|
+
const transports = this._config?.logger?.winston?.transports ?? [consoleTransport];
|
|
7141
7994
|
const logger = logger_default({
|
|
7142
7995
|
enableOtel: this._config?.telemetry?.enabled ?? false,
|
|
7143
|
-
transports
|
|
7996
|
+
transports
|
|
7144
7997
|
});
|
|
7145
|
-
|
|
7146
|
-
console.
|
|
7147
|
-
console.
|
|
7148
|
-
console.
|
|
7149
|
-
console.
|
|
7150
|
-
console.debug = (...args) => logger.debug(args.map(formatArg).join(" "));
|
|
7998
|
+
console.log = createLogMethod(logger, "info");
|
|
7999
|
+
console.info = createLogMethod(logger, "info");
|
|
8000
|
+
console.warn = createLogMethod(logger, "warn");
|
|
8001
|
+
console.error = createLogMethod(logger, "error");
|
|
8002
|
+
console.debug = createLogMethod(logger, "debug");
|
|
7151
8003
|
if (!this._config) {
|
|
7152
8004
|
throw new Error("Config not initialized, make sure to call await ExuluApp.create() first when starting your server.");
|
|
7153
8005
|
}
|
|
@@ -7157,7 +8009,9 @@ var ExuluApp = class {
|
|
|
7157
8009
|
this._tools,
|
|
7158
8010
|
Object.values(this._contexts ?? {}),
|
|
7159
8011
|
this._config,
|
|
7160
|
-
|
|
8012
|
+
this._evals,
|
|
8013
|
+
tracer,
|
|
8014
|
+
this._queues
|
|
7161
8015
|
);
|
|
7162
8016
|
if (this._config?.MCP.enabled) {
|
|
7163
8017
|
const mcp = new ExuluMCP();
|
|
@@ -7177,6 +8031,80 @@ var ExuluApp = class {
|
|
|
7177
8031
|
};
|
|
7178
8032
|
};
|
|
7179
8033
|
|
|
8034
|
+
// src/bullmq/queues.ts
|
|
8035
|
+
var import_bullmq5 = require("bullmq");
|
|
8036
|
+
var import_bullmq_otel = require("bullmq-otel");
|
|
8037
|
+
var ExuluQueues = class {
|
|
8038
|
+
queues;
|
|
8039
|
+
constructor() {
|
|
8040
|
+
this.queues = [];
|
|
8041
|
+
}
|
|
8042
|
+
list = /* @__PURE__ */ new Map();
|
|
8043
|
+
// list of queue names
|
|
8044
|
+
queue(name) {
|
|
8045
|
+
return this.queues.find((x) => x.queue?.name === name);
|
|
8046
|
+
}
|
|
8047
|
+
// name: string
|
|
8048
|
+
// concurrency: global concurrency for the queue
|
|
8049
|
+
// ratelimit: maximum number of jobs per second
|
|
8050
|
+
// Rate limit is set on workers (see workers.ts), even global rate limits,
|
|
8051
|
+
// that is a bit counter-intuitive. Since queues are registered using .user
|
|
8052
|
+
// method of ExuluQueues we need to store the desired rate limit on the queue
|
|
8053
|
+
// here so we can use the value when creating workers for the queue instance
|
|
8054
|
+
// as there is no way to store a rate limit value natively on a bullm queue.
|
|
8055
|
+
register = (name, concurrency = 1, ratelimit = 1) => {
|
|
8056
|
+
const use = async () => {
|
|
8057
|
+
const existing = this.queues.find((x) => x.queue?.name === name);
|
|
8058
|
+
if (existing) {
|
|
8059
|
+
const globalConcurrency = await existing.queue.getGlobalConcurrency();
|
|
8060
|
+
if (globalConcurrency !== concurrency) {
|
|
8061
|
+
await existing.queue.setGlobalConcurrency(concurrency);
|
|
8062
|
+
}
|
|
8063
|
+
return {
|
|
8064
|
+
queue: existing.queue,
|
|
8065
|
+
ratelimit,
|
|
8066
|
+
concurrency
|
|
8067
|
+
};
|
|
8068
|
+
}
|
|
8069
|
+
if (!redisServer.host?.length || !redisServer.port?.length) {
|
|
8070
|
+
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.register().use() ).`);
|
|
8071
|
+
throw new Error(`[EXULU] no redis server configured.`);
|
|
8072
|
+
}
|
|
8073
|
+
const newQueue = new import_bullmq5.Queue(
|
|
8074
|
+
`${name}`,
|
|
8075
|
+
{
|
|
8076
|
+
connection: {
|
|
8077
|
+
...redisServer,
|
|
8078
|
+
enableOfflineQueue: false
|
|
8079
|
+
},
|
|
8080
|
+
telemetry: new import_bullmq_otel.BullMQOtel("simple-guide")
|
|
8081
|
+
}
|
|
8082
|
+
);
|
|
8083
|
+
await newQueue.setGlobalConcurrency(concurrency);
|
|
8084
|
+
this.queues.push({
|
|
8085
|
+
queue: newQueue,
|
|
8086
|
+
ratelimit,
|
|
8087
|
+
concurrency
|
|
8088
|
+
});
|
|
8089
|
+
return {
|
|
8090
|
+
queue: newQueue,
|
|
8091
|
+
ratelimit,
|
|
8092
|
+
concurrency
|
|
8093
|
+
};
|
|
8094
|
+
};
|
|
8095
|
+
this.list.set(name, {
|
|
8096
|
+
name,
|
|
8097
|
+
concurrency,
|
|
8098
|
+
ratelimit,
|
|
8099
|
+
use
|
|
8100
|
+
});
|
|
8101
|
+
return {
|
|
8102
|
+
use
|
|
8103
|
+
};
|
|
8104
|
+
};
|
|
8105
|
+
};
|
|
8106
|
+
var queues = new ExuluQueues();
|
|
8107
|
+
|
|
7180
8108
|
// src/chunking/types/base.ts
|
|
7181
8109
|
var Chunk = class _Chunk {
|
|
7182
8110
|
/** The text of the chunk. */
|
|
@@ -8386,7 +9314,8 @@ var {
|
|
|
8386
9314
|
variablesSchema: variablesSchema3,
|
|
8387
9315
|
workflowTemplatesSchema: workflowTemplatesSchema3,
|
|
8388
9316
|
rbacSchema: rbacSchema3,
|
|
8389
|
-
projectsSchema: projectsSchema3
|
|
9317
|
+
projectsSchema: projectsSchema3,
|
|
9318
|
+
jobResultsSchema: jobResultsSchema3
|
|
8390
9319
|
} = coreSchemas.get();
|
|
8391
9320
|
var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
8392
9321
|
for (const field of fields) {
|
|
@@ -8420,6 +9349,7 @@ var up = async function(knex) {
|
|
|
8420
9349
|
platformConfigurationsSchema3(),
|
|
8421
9350
|
statisticsSchema3(),
|
|
8422
9351
|
projectsSchema3(),
|
|
9352
|
+
jobResultsSchema3(),
|
|
8423
9353
|
rbacSchema3(),
|
|
8424
9354
|
agentsSchema3(),
|
|
8425
9355
|
variablesSchema3(),
|
|
@@ -8604,20 +9534,7 @@ var create = ({
|
|
|
8604
9534
|
};
|
|
8605
9535
|
|
|
8606
9536
|
// src/index.ts
|
|
8607
|
-
var
|
|
8608
|
-
|
|
8609
|
-
// types/enums/jobs.ts
|
|
8610
|
-
var JOB_STATUS_ENUM = {
|
|
8611
|
-
completed: "completed",
|
|
8612
|
-
failed: "failed",
|
|
8613
|
-
delayed: "delayed",
|
|
8614
|
-
active: "active",
|
|
8615
|
-
waiting: "waiting",
|
|
8616
|
-
paused: "paused",
|
|
8617
|
-
stuck: "stuck"
|
|
8618
|
-
};
|
|
8619
|
-
|
|
8620
|
-
// src/index.ts
|
|
9537
|
+
var import_crypto_js5 = __toESM(require("crypto-js"), 1);
|
|
8621
9538
|
var ExuluJobs = {
|
|
8622
9539
|
redis: redisClient,
|
|
8623
9540
|
jobs: {
|
|
@@ -8654,8 +9571,8 @@ var ExuluVariables = {
|
|
|
8654
9571
|
throw new Error(`Variable ${name} not found.`);
|
|
8655
9572
|
}
|
|
8656
9573
|
if (variable.encrypted) {
|
|
8657
|
-
const bytes =
|
|
8658
|
-
variable.value = bytes.toString(
|
|
9574
|
+
const bytes = import_crypto_js5.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
9575
|
+
variable.value = bytes.toString(import_crypto_js5.default.enc.Utf8);
|
|
8659
9576
|
}
|
|
8660
9577
|
return variable.value;
|
|
8661
9578
|
}
|
|
@@ -8791,5 +9708,6 @@ var ExuluChunkers = {
|
|
|
8791
9708
|
ExuluTool,
|
|
8792
9709
|
ExuluUtils,
|
|
8793
9710
|
ExuluVariables,
|
|
8794
|
-
db
|
|
9711
|
+
db,
|
|
9712
|
+
logMetadata
|
|
8795
9713
|
});
|