@exulu/backend 1.40.0 → 1.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -3
- package/dist/index.cjs +480 -416
- package/dist/index.d.cts +66 -22
- package/dist/index.d.ts +66 -22
- package/dist/index.js +480 -416
- package/package.json +1 -1
- package/types/models/context.ts +6 -6
- package/types/models/item.ts +1 -1
package/dist/index.cjs
CHANGED
|
@@ -48,7 +48,7 @@ __export(index_exports, {
|
|
|
48
48
|
ExuluUtils: () => ExuluUtils,
|
|
49
49
|
ExuluVariables: () => ExuluVariables,
|
|
50
50
|
db: () => db2,
|
|
51
|
-
logMetadata: () =>
|
|
51
|
+
logMetadata: () => logMetadata
|
|
52
52
|
});
|
|
53
53
|
module.exports = __toCommonJS(index_exports);
|
|
54
54
|
var import_config = require("dotenv/config");
|
|
@@ -203,21 +203,37 @@ async function postgresClient() {
|
|
|
203
203
|
database: dbName,
|
|
204
204
|
password: process.env.POSTGRES_DB_PASSWORD,
|
|
205
205
|
ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false,
|
|
206
|
-
connectionTimeoutMillis:
|
|
206
|
+
connectionTimeoutMillis: 3e4,
|
|
207
|
+
// Increased from 10s to 30s to handle connection spikes
|
|
208
|
+
// PostgreSQL statement timeout (in milliseconds) - kills queries that run too long
|
|
209
|
+
// This prevents runaway queries from blocking connections
|
|
210
|
+
statement_timeout: 18e5,
|
|
211
|
+
// 30 minutes - should be longer than max job timeout (1200s = 20m)
|
|
212
|
+
// Connection idle timeout - how long pg client waits before timing out
|
|
213
|
+
query_timeout: 18e5
|
|
214
|
+
// 30 minutes
|
|
207
215
|
},
|
|
208
216
|
pool: {
|
|
209
|
-
min:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
min: 5,
|
|
218
|
+
// Increased from 2 to ensure enough connections available
|
|
219
|
+
max: 50,
|
|
220
|
+
// Increased from 20 to handle more concurrent operations with processor jobs
|
|
221
|
+
acquireTimeoutMillis: 6e4,
|
|
222
|
+
// Increased from 30s to 60s to handle pool contention
|
|
213
223
|
createTimeoutMillis: 3e4,
|
|
214
|
-
idleTimeoutMillis:
|
|
224
|
+
idleTimeoutMillis: 6e4,
|
|
225
|
+
// Increased to keep connections alive longer
|
|
215
226
|
reapIntervalMillis: 1e3,
|
|
216
227
|
createRetryIntervalMillis: 200,
|
|
217
228
|
// Log pool events to help debug connection issues
|
|
218
229
|
afterCreate: (conn, done) => {
|
|
219
230
|
console.log("[EXULU] New database connection created");
|
|
220
|
-
|
|
231
|
+
conn.query("SET statement_timeout = 1800000", (err) => {
|
|
232
|
+
if (err) {
|
|
233
|
+
console.error("[EXULU] Error setting statement_timeout:", err);
|
|
234
|
+
}
|
|
235
|
+
done(err, conn);
|
|
236
|
+
});
|
|
221
237
|
}
|
|
222
238
|
}
|
|
223
239
|
});
|
|
@@ -1467,6 +1483,10 @@ var addCoreFields = (schema) => {
|
|
|
1467
1483
|
field.name = field.name + "_s3key";
|
|
1468
1484
|
}
|
|
1469
1485
|
});
|
|
1486
|
+
schema.fields.push({
|
|
1487
|
+
name: "last_processed_at",
|
|
1488
|
+
type: "date"
|
|
1489
|
+
});
|
|
1470
1490
|
if (schema.RBAC) {
|
|
1471
1491
|
if (!schema.fields.some((field) => field.name === "rights_mode")) {
|
|
1472
1492
|
schema.fields.push({
|
|
@@ -2349,33 +2369,38 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2349
2369
|
}
|
|
2350
2370
|
};
|
|
2351
2371
|
if (table.type === "items") {
|
|
2352
|
-
if (table.
|
|
2353
|
-
|
|
2372
|
+
if (table.processor) {
|
|
2373
|
+
const contextItemProcessorMutation = async (context, items, user, role) => {
|
|
2374
|
+
let jobs = [];
|
|
2375
|
+
let results = [];
|
|
2376
|
+
await Promise.all(items.map(async (item) => {
|
|
2377
|
+
const result = await context.processField(
|
|
2378
|
+
"api",
|
|
2379
|
+
item,
|
|
2380
|
+
config,
|
|
2381
|
+
user,
|
|
2382
|
+
role
|
|
2383
|
+
);
|
|
2384
|
+
if (result.job) {
|
|
2385
|
+
jobs.push(result.job);
|
|
2386
|
+
}
|
|
2387
|
+
if (result.result) {
|
|
2388
|
+
results.push(result.result);
|
|
2389
|
+
}
|
|
2390
|
+
}));
|
|
2391
|
+
return {
|
|
2392
|
+
message: jobs.length > 0 ? "Processing job scheduled." : "Items processed successfully.",
|
|
2393
|
+
results: results.map((result) => JSON.stringify(result)),
|
|
2394
|
+
jobs
|
|
2395
|
+
};
|
|
2396
|
+
};
|
|
2397
|
+
mutations[`${tableNameSingular}ProcessItem`] = async (_, args, context, info) => {
|
|
2354
2398
|
if (!context.user?.super_admin) {
|
|
2355
2399
|
throw new Error("You are not authorized to process fields via API, user must be super admin.");
|
|
2356
2400
|
}
|
|
2357
|
-
const exists = contexts.find((context2) => context2.id === table.id);
|
|
2358
|
-
if (!exists) {
|
|
2359
|
-
throw new Error(`Context ${table.id} not found.`);
|
|
2360
|
-
}
|
|
2361
|
-
if (!args.field) {
|
|
2362
|
-
throw new Error("Field argument missing, the field argument is required.");
|
|
2363
|
-
}
|
|
2364
2401
|
if (!args.item) {
|
|
2365
2402
|
throw new Error("Item argument missing, the item argument is required.");
|
|
2366
2403
|
}
|
|
2367
|
-
const name = args.field?.replace("_s3key", "");
|
|
2368
|
-
console.log("[EXULU] name", name);
|
|
2369
|
-
console.log("[EXULU] fields", exists.fields.map((field2) => field2.name));
|
|
2370
|
-
const field = exists.fields.find((field2) => {
|
|
2371
|
-
return field2.name.replace("_s3key", "") === name;
|
|
2372
|
-
});
|
|
2373
|
-
if (!field) {
|
|
2374
|
-
throw new Error(`Field ${name} not found in context ${exists.id}].`);
|
|
2375
|
-
}
|
|
2376
|
-
if (!field.processor) {
|
|
2377
|
-
throw new Error(`Processor not set for field ${args.field} in context ${exists.id}.`);
|
|
2378
|
-
}
|
|
2379
2404
|
const { db: db3 } = context;
|
|
2380
2405
|
let query = db3.from(tableNamePlural).select("*").where({ id: args.item });
|
|
2381
2406
|
query = applyAccessControl(table, query, context.user);
|
|
@@ -2383,21 +2408,38 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2383
2408
|
if (!item) {
|
|
2384
2409
|
throw new Error("Item not found, or your user does not have access to it.");
|
|
2385
2410
|
}
|
|
2386
|
-
const
|
|
2387
|
-
|
|
2388
|
-
{
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2411
|
+
const exists = contexts.find((context2) => context2.id === table.id);
|
|
2412
|
+
if (!exists) {
|
|
2413
|
+
throw new Error(`Context ${table.id} not found.`);
|
|
2414
|
+
}
|
|
2415
|
+
return contextItemProcessorMutation(exists, [item], context.user.id, context.user.role?.id);
|
|
2416
|
+
};
|
|
2417
|
+
mutations[`${tableNameSingular}ProcessItems`] = async (_, args, context, info) => {
|
|
2418
|
+
if (!context.user?.super_admin) {
|
|
2419
|
+
throw new Error("You are not authorized to process fields via API, user must be super admin.");
|
|
2420
|
+
}
|
|
2421
|
+
const { limit = 10, filters = [], sort } = args;
|
|
2422
|
+
const { db: db3 } = context;
|
|
2423
|
+
const { items } = await paginationRequest({
|
|
2424
|
+
db: db3,
|
|
2425
|
+
limit,
|
|
2426
|
+
page: 0,
|
|
2427
|
+
filters,
|
|
2428
|
+
sort,
|
|
2429
|
+
table,
|
|
2430
|
+
user: context.user,
|
|
2431
|
+
fields: "*"
|
|
2432
|
+
});
|
|
2433
|
+
const exists = contexts.find((context2) => context2.id === table.id);
|
|
2434
|
+
if (!exists) {
|
|
2435
|
+
throw new Error(`Context ${table.id} not found.`);
|
|
2436
|
+
}
|
|
2437
|
+
return contextItemProcessorMutation(
|
|
2438
|
+
exists,
|
|
2439
|
+
items,
|
|
2393
2440
|
context.user.id,
|
|
2394
2441
|
context.user.role?.id
|
|
2395
2442
|
);
|
|
2396
|
-
return {
|
|
2397
|
-
message: job ? "Processing job scheduled." : "Item processed successfully.",
|
|
2398
|
-
result,
|
|
2399
|
-
job
|
|
2400
|
-
};
|
|
2401
2443
|
};
|
|
2402
2444
|
}
|
|
2403
2445
|
mutations[`${tableNameSingular}ExecuteSource`] = async (_, args, context, info) => {
|
|
@@ -2565,7 +2607,7 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2565
2607
|
}
|
|
2566
2608
|
return mutations;
|
|
2567
2609
|
}
|
|
2568
|
-
var applyAccessControl = (table, query, user) => {
|
|
2610
|
+
var applyAccessControl = (table, query, user, field_prefix) => {
|
|
2569
2611
|
const tableNamePlural = table.name.plural.toLowerCase();
|
|
2570
2612
|
if (table.name.plural !== "agent_sessions" && user?.super_admin === true) {
|
|
2571
2613
|
return query;
|
|
@@ -2581,18 +2623,19 @@ var applyAccessControl = (table, query, user) => {
|
|
|
2581
2623
|
if (!hasRBAC) {
|
|
2582
2624
|
return query;
|
|
2583
2625
|
}
|
|
2626
|
+
const prefix = field_prefix ? field_prefix + "." : "";
|
|
2584
2627
|
try {
|
|
2585
2628
|
query = query.where(function() {
|
|
2586
|
-
this.where(
|
|
2587
|
-
this.orWhere(
|
|
2629
|
+
this.where(`${prefix}rights_mode`, "public");
|
|
2630
|
+
this.orWhere(`${prefix}created_by`, user.id);
|
|
2588
2631
|
this.orWhere(function() {
|
|
2589
|
-
this.where(
|
|
2632
|
+
this.where(`${prefix}rights_mode`, "users").whereExists(function() {
|
|
2590
2633
|
this.select("*").from("rbac").whereRaw("rbac.target_resource_id = " + tableNamePlural + ".id").where("rbac.entity", table.name.singular).where("rbac.access_type", "User").where("rbac.user_id", user.id);
|
|
2591
2634
|
});
|
|
2592
2635
|
});
|
|
2593
2636
|
if (user.role) {
|
|
2594
2637
|
this.orWhere(function() {
|
|
2595
|
-
this.where(
|
|
2638
|
+
this.where(`${prefix}rights_mode`, "roles").whereExists(function() {
|
|
2596
2639
|
this.select("*").from("rbac").whereRaw("rbac.target_resource_id = " + tableNamePlural + ".id").where("rbac.entity", table.name.singular).where("rbac.access_type", "Role").where("rbac.role_id", user.role.id);
|
|
2597
2640
|
});
|
|
2598
2641
|
});
|
|
@@ -2604,9 +2647,11 @@ var applyAccessControl = (table, query, user) => {
|
|
|
2604
2647
|
}
|
|
2605
2648
|
return query;
|
|
2606
2649
|
};
|
|
2607
|
-
var converOperatorToQuery = (query, fieldName, operators, table) => {
|
|
2650
|
+
var converOperatorToQuery = (query, fieldName, operators, table, field_prefix) => {
|
|
2608
2651
|
const field = table?.fields.find((f) => f.name === fieldName);
|
|
2609
2652
|
const isJsonField = field?.type === "json";
|
|
2653
|
+
const prefix = field_prefix ? field_prefix + "." : "";
|
|
2654
|
+
fieldName = prefix + fieldName;
|
|
2610
2655
|
if (operators.eq !== void 0) {
|
|
2611
2656
|
if (isJsonField) {
|
|
2612
2657
|
query = query.whereRaw(`?? = ?::jsonb`, [fieldName, JSON.stringify(operators.eq)]);
|
|
@@ -2916,53 +2961,95 @@ var finalizeRequestedFields = async ({
|
|
|
2916
2961
|
}
|
|
2917
2962
|
const { db: db3 } = await postgresClient();
|
|
2918
2963
|
const query = db3.from(getChunksTableName(context.id)).where({ source: result.id }).select("id", "content", "source", "chunk_index", "createdAt", "updatedAt");
|
|
2919
|
-
query.select(
|
|
2920
|
-
db3.raw("vector_dims(??) as embedding_size", [`embedding`])
|
|
2921
|
-
);
|
|
2922
2964
|
const chunks = await query;
|
|
2923
2965
|
result.chunks = chunks.map((chunk) => ({
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
hybrid_score: 0,
|
|
2927
|
-
content: chunk.content,
|
|
2928
|
-
source: chunk.source,
|
|
2966
|
+
chunk_content: chunk.content,
|
|
2967
|
+
chunk_source: chunk.source,
|
|
2929
2968
|
chunk_index: chunk.chunk_index,
|
|
2930
2969
|
chunk_id: chunk.id,
|
|
2931
2970
|
chunk_created_at: chunk.createdAt,
|
|
2932
2971
|
chunk_updated_at: chunk.updatedAt,
|
|
2933
|
-
|
|
2972
|
+
item_updated_at: chunk.item_updated_at,
|
|
2973
|
+
item_created_at: chunk.item_created_at,
|
|
2974
|
+
item_id: chunk.item_id,
|
|
2975
|
+
item_external_id: chunk.item_external_id,
|
|
2976
|
+
item_name: chunk.item_name
|
|
2934
2977
|
}));
|
|
2935
2978
|
}
|
|
2936
2979
|
}
|
|
2937
2980
|
}
|
|
2938
2981
|
return result;
|
|
2939
2982
|
};
|
|
2940
|
-
var applyFilters = (query, filters, table) => {
|
|
2983
|
+
var applyFilters = (query, filters, table, field_prefix) => {
|
|
2941
2984
|
filters.forEach((filter) => {
|
|
2942
2985
|
Object.entries(filter).forEach(([fieldName, operators]) => {
|
|
2943
2986
|
if (operators) {
|
|
2944
2987
|
if (operators.and !== void 0) {
|
|
2945
2988
|
operators.and.forEach((operator) => {
|
|
2946
|
-
query = converOperatorToQuery(query, fieldName, operator, table);
|
|
2989
|
+
query = converOperatorToQuery(query, fieldName, operator, table, field_prefix);
|
|
2947
2990
|
});
|
|
2948
2991
|
}
|
|
2949
2992
|
if (operators.or !== void 0) {
|
|
2950
2993
|
operators.or.forEach((operator) => {
|
|
2951
|
-
query = converOperatorToQuery(query, fieldName, operator, table);
|
|
2994
|
+
query = converOperatorToQuery(query, fieldName, operator, table, field_prefix);
|
|
2952
2995
|
});
|
|
2953
2996
|
}
|
|
2954
|
-
query = converOperatorToQuery(query, fieldName, operators, table);
|
|
2997
|
+
query = converOperatorToQuery(query, fieldName, operators, table, field_prefix);
|
|
2955
2998
|
}
|
|
2956
2999
|
});
|
|
2957
3000
|
});
|
|
2958
3001
|
return query;
|
|
2959
3002
|
};
|
|
2960
|
-
var applySorting = (query, sort) => {
|
|
3003
|
+
var applySorting = (query, sort, field_prefix) => {
|
|
3004
|
+
const prefix = field_prefix ? field_prefix + "." : "";
|
|
2961
3005
|
if (sort) {
|
|
3006
|
+
sort.field = prefix + sort.field;
|
|
2962
3007
|
query = query.orderBy(sort.field, sort.direction.toLowerCase());
|
|
2963
3008
|
}
|
|
2964
3009
|
return query;
|
|
2965
3010
|
};
|
|
3011
|
+
var paginationRequest = async ({
|
|
3012
|
+
db: db3,
|
|
3013
|
+
limit,
|
|
3014
|
+
page,
|
|
3015
|
+
filters,
|
|
3016
|
+
sort,
|
|
3017
|
+
table,
|
|
3018
|
+
user,
|
|
3019
|
+
fields
|
|
3020
|
+
}) => {
|
|
3021
|
+
if (limit > 1e4) {
|
|
3022
|
+
throw new Error("Limit cannot be greater than 10.000.");
|
|
3023
|
+
}
|
|
3024
|
+
const tableName = table.name.plural.toLowerCase();
|
|
3025
|
+
let countQuery = db3(tableName);
|
|
3026
|
+
countQuery = applyFilters(countQuery, filters, table);
|
|
3027
|
+
countQuery = applyAccessControl(table, countQuery, user);
|
|
3028
|
+
const countResult = await countQuery.count("* as count");
|
|
3029
|
+
const itemCount = Number(countResult[0]?.count || 0);
|
|
3030
|
+
const pageCount = Math.ceil(itemCount / limit);
|
|
3031
|
+
const currentPage = page;
|
|
3032
|
+
const hasPreviousPage = currentPage > 1;
|
|
3033
|
+
const hasNextPage = currentPage < pageCount - 1;
|
|
3034
|
+
let dataQuery = db3(tableName);
|
|
3035
|
+
dataQuery = applyFilters(dataQuery, filters, table);
|
|
3036
|
+
dataQuery = applyAccessControl(table, dataQuery, user);
|
|
3037
|
+
dataQuery = applySorting(dataQuery, sort);
|
|
3038
|
+
if (page > 1) {
|
|
3039
|
+
dataQuery = dataQuery.offset((page - 1) * limit);
|
|
3040
|
+
}
|
|
3041
|
+
let items = await dataQuery.select(fields ? fields : "*").limit(limit);
|
|
3042
|
+
return {
|
|
3043
|
+
items,
|
|
3044
|
+
pageInfo: {
|
|
3045
|
+
pageCount,
|
|
3046
|
+
itemCount,
|
|
3047
|
+
currentPage,
|
|
3048
|
+
hasPreviousPage,
|
|
3049
|
+
hasNextPage
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
};
|
|
2966
3053
|
function createQueries(table, agents, tools, contexts) {
|
|
2967
3054
|
const tableNamePlural = table.name.plural.toLowerCase();
|
|
2968
3055
|
const tableNameSingular = table.name.singular.toLowerCase();
|
|
@@ -2998,38 +3085,22 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2998
3085
|
return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2999
3086
|
},
|
|
3000
3087
|
[`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
|
|
3001
|
-
const { limit = 10, page = 0, filters = [], sort } = args;
|
|
3002
3088
|
const { db: db3 } = context;
|
|
3003
|
-
|
|
3004
|
-
throw new Error("Limit cannot be greater than 500.");
|
|
3005
|
-
}
|
|
3006
|
-
let countQuery = db3(tableNamePlural);
|
|
3007
|
-
countQuery = applyFilters(countQuery, filters, table);
|
|
3008
|
-
countQuery = applyAccessControl(table, countQuery, context.user);
|
|
3009
|
-
const countResult = await countQuery.count("* as count");
|
|
3010
|
-
const itemCount = Number(countResult[0]?.count || 0);
|
|
3011
|
-
const pageCount = Math.ceil(itemCount / limit);
|
|
3012
|
-
const currentPage = page;
|
|
3013
|
-
const hasPreviousPage = currentPage > 1;
|
|
3014
|
-
const hasNextPage = currentPage < pageCount - 1;
|
|
3015
|
-
let dataQuery = db3(tableNamePlural);
|
|
3016
|
-
dataQuery = applyFilters(dataQuery, filters, table);
|
|
3017
|
-
dataQuery = applyAccessControl(table, dataQuery, context.user);
|
|
3089
|
+
const { limit = 10, page = 0, filters = [], sort } = args;
|
|
3018
3090
|
const requestedFields = getRequestedFields(info);
|
|
3019
|
-
dataQuery = applySorting(dataQuery, sort);
|
|
3020
|
-
if (page > 1) {
|
|
3021
|
-
dataQuery = dataQuery.offset((page - 1) * limit);
|
|
3022
|
-
}
|
|
3023
3091
|
const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
|
|
3024
|
-
|
|
3092
|
+
const { items, pageInfo } = await paginationRequest({
|
|
3093
|
+
db: db3,
|
|
3094
|
+
limit,
|
|
3095
|
+
page,
|
|
3096
|
+
filters,
|
|
3097
|
+
sort,
|
|
3098
|
+
table,
|
|
3099
|
+
user: context.user,
|
|
3100
|
+
fields: sanitizedFields
|
|
3101
|
+
});
|
|
3025
3102
|
return {
|
|
3026
|
-
pageInfo
|
|
3027
|
-
pageCount,
|
|
3028
|
-
itemCount,
|
|
3029
|
-
currentPage,
|
|
3030
|
-
hasPreviousPage,
|
|
3031
|
-
hasNextPage
|
|
3032
|
-
},
|
|
3103
|
+
pageInfo,
|
|
3033
3104
|
items: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result: items, user: context.user })
|
|
3034
3105
|
};
|
|
3035
3106
|
},
|
|
@@ -3078,7 +3149,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
3078
3149
|
}
|
|
3079
3150
|
const { limit = 10, page = 0, filters = [], sort } = args;
|
|
3080
3151
|
return await vectorSearch({
|
|
3081
|
-
limit,
|
|
3152
|
+
limit: limit || exists.configuration.maxRetrievalResults || 10,
|
|
3082
3153
|
page,
|
|
3083
3154
|
filters,
|
|
3084
3155
|
sort,
|
|
@@ -3137,106 +3208,111 @@ var vectorSearch = async ({
|
|
|
3137
3208
|
}
|
|
3138
3209
|
const mainTable = getTableName(id);
|
|
3139
3210
|
const chunksTable = getChunksTableName(id);
|
|
3140
|
-
let
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3211
|
+
let chunksQuery = db3(chunksTable + " as chunks").select([
|
|
3212
|
+
"chunks.id as chunk_id",
|
|
3213
|
+
"chunks.source",
|
|
3214
|
+
"chunks.content",
|
|
3215
|
+
"chunks.chunk_index",
|
|
3216
|
+
db3.raw('chunks."createdAt" as chunk_created_at'),
|
|
3217
|
+
db3.raw('chunks."updatedAt" as chunk_updated_at'),
|
|
3218
|
+
"chunks.metadata",
|
|
3219
|
+
"items.id as item_id",
|
|
3220
|
+
"items.name as item_name",
|
|
3221
|
+
"items.external_id as item_external_id",
|
|
3222
|
+
db3.raw('items."updatedAt" as item_updated_at'),
|
|
3223
|
+
db3.raw('items."createdAt" as item_created_at')
|
|
3224
|
+
]);
|
|
3225
|
+
chunksQuery.leftJoin(mainTable + " as items", function() {
|
|
3226
|
+
this.on("chunks.source", "=", "items.id");
|
|
3227
|
+
});
|
|
3228
|
+
chunksQuery = applyFilters(chunksQuery, filters, table, "items");
|
|
3229
|
+
chunksQuery = applyAccessControl(table, chunksQuery, user, "items");
|
|
3230
|
+
chunksQuery = applySorting(chunksQuery, sort, "items");
|
|
3148
3231
|
if (queryRewriter) {
|
|
3149
3232
|
query = await queryRewriter(query);
|
|
3150
3233
|
}
|
|
3151
|
-
|
|
3152
|
-
itemsQuery.leftJoin(chunksTable, function() {
|
|
3153
|
-
this.on(chunksTable + ".source", "=", mainTable + ".id");
|
|
3154
|
-
});
|
|
3155
|
-
itemsQuery.select(chunksTable + ".id as chunk_id");
|
|
3156
|
-
itemsQuery.select(chunksTable + ".source");
|
|
3157
|
-
itemsQuery.select(chunksTable + ".content");
|
|
3158
|
-
itemsQuery.select(chunksTable + ".chunk_index");
|
|
3159
|
-
itemsQuery.select(chunksTable + ".createdAt as chunk_created_at");
|
|
3160
|
-
itemsQuery.select(chunksTable + ".updatedAt as chunk_updated_at");
|
|
3161
|
-
itemsQuery.select(db3.raw("vector_dims(??) as embedding_size", [`${chunksTable}.embedding`]));
|
|
3162
|
-
const { chunks } = await embedder.generateFromQuery(context.id, query, {
|
|
3234
|
+
const { chunks: queryChunks } = await embedder.generateFromQuery(context.id, query, {
|
|
3163
3235
|
label: table.name.singular,
|
|
3164
3236
|
trigger
|
|
3165
3237
|
}, user?.id, role);
|
|
3166
|
-
if (!
|
|
3238
|
+
if (!queryChunks?.[0]?.vector) {
|
|
3167
3239
|
throw new Error("No vector generated for query.");
|
|
3168
3240
|
}
|
|
3169
|
-
const vector =
|
|
3241
|
+
const vector = queryChunks[0].vector;
|
|
3170
3242
|
const vectorStr = `ARRAY[${vector.join(",")}]`;
|
|
3171
3243
|
const vectorExpr = `${vectorStr}::vector`;
|
|
3172
3244
|
const language = configuration.language || "english";
|
|
3173
|
-
let
|
|
3245
|
+
let resultChunks = [];
|
|
3174
3246
|
switch (method) {
|
|
3175
3247
|
case "tsvector":
|
|
3176
|
-
|
|
3177
|
-
|
|
3248
|
+
chunksQuery.limit(limit * 2);
|
|
3249
|
+
chunksQuery.select(db3.raw(
|
|
3250
|
+
`ts_rank(chunks.fts, websearch_to_tsquery(?, ?)) as fts_rank`,
|
|
3178
3251
|
[language, query]
|
|
3179
3252
|
)).whereRaw(
|
|
3180
|
-
|
|
3253
|
+
`chunks.fts @@ websearch_to_tsquery(?, ?)`,
|
|
3181
3254
|
[language, query]
|
|
3182
3255
|
).orderByRaw(`fts_rank DESC`);
|
|
3183
|
-
|
|
3256
|
+
resultChunks = await chunksQuery;
|
|
3184
3257
|
break;
|
|
3185
3258
|
case "cosineDistance":
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3259
|
+
chunksQuery.limit(limit * 2);
|
|
3260
|
+
chunksQuery.whereNotNull(`chunks.embedding`);
|
|
3261
|
+
console.log("[EXULU] Chunks query:", chunksQuery.toQuery());
|
|
3262
|
+
chunksQuery.select(
|
|
3263
|
+
db3.raw(`1 - (chunks.embedding <=> ${vectorExpr}) AS cosine_distance`)
|
|
3190
3264
|
);
|
|
3191
|
-
|
|
3192
|
-
|
|
3265
|
+
chunksQuery.orderByRaw(
|
|
3266
|
+
`chunks.embedding <=> ${vectorExpr} ASC NULLS LAST`
|
|
3193
3267
|
);
|
|
3194
|
-
|
|
3268
|
+
resultChunks = await chunksQuery;
|
|
3195
3269
|
break;
|
|
3196
3270
|
case "hybridSearch":
|
|
3197
|
-
const matchCount = Math.min(limit *
|
|
3271
|
+
const matchCount = Math.min(limit * 2, 100);
|
|
3198
3272
|
const fullTextWeight = 1;
|
|
3199
3273
|
const semanticWeight = 1;
|
|
3200
3274
|
const rrfK = 50;
|
|
3201
3275
|
const hybridSQL = `
|
|
3202
3276
|
WITH full_text AS (
|
|
3203
3277
|
SELECT
|
|
3204
|
-
|
|
3205
|
-
|
|
3278
|
+
chunks.id,
|
|
3279
|
+
chunks.source,
|
|
3206
3280
|
row_number() OVER (
|
|
3207
|
-
ORDER BY
|
|
3281
|
+
ORDER BY ts_rank(chunks.fts, websearch_to_tsquery(?, ?)) DESC
|
|
3208
3282
|
) AS rank_ix
|
|
3209
|
-
FROM ${chunksTable}
|
|
3210
|
-
WHERE
|
|
3283
|
+
FROM ${chunksTable} as chunks
|
|
3284
|
+
WHERE chunks.fts @@ websearch_to_tsquery(?, ?)
|
|
3211
3285
|
ORDER BY rank_ix
|
|
3212
3286
|
LIMIT LEAST(?, 15) * 2
|
|
3213
3287
|
),
|
|
3214
3288
|
semantic AS (
|
|
3215
3289
|
SELECT
|
|
3216
|
-
|
|
3217
|
-
|
|
3290
|
+
chunks.id,
|
|
3291
|
+
chunks.source,
|
|
3218
3292
|
row_number() OVER (
|
|
3219
|
-
ORDER BY
|
|
3293
|
+
ORDER BY chunks.embedding <=> ${vectorExpr} ASC
|
|
3220
3294
|
) AS rank_ix
|
|
3221
|
-
FROM ${chunksTable}
|
|
3222
|
-
WHERE
|
|
3295
|
+
FROM ${chunksTable} as chunks
|
|
3296
|
+
WHERE chunks.embedding IS NOT NULL
|
|
3223
3297
|
ORDER BY rank_ix
|
|
3224
3298
|
LIMIT LEAST(?, 50) * 2
|
|
3225
3299
|
)
|
|
3226
3300
|
SELECT
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3301
|
+
items.id as item_id,
|
|
3302
|
+
items.name as item_name,
|
|
3303
|
+
items.external_id as item_external_id,
|
|
3304
|
+
chunks.id AS chunk_id,
|
|
3305
|
+
chunks.source,
|
|
3306
|
+
chunks.content,
|
|
3307
|
+
chunks.chunk_index,
|
|
3308
|
+
chunks.metadata,
|
|
3309
|
+
chunks."createdAt" as chunk_created_at,
|
|
3310
|
+
chunks."updatedAt" as chunk_updated_at,
|
|
3311
|
+
items."updatedAt" as item_updated_at,
|
|
3312
|
+
items."createdAt" as item_created_at,
|
|
3237
3313
|
/* Per-signal scores for introspection */
|
|
3238
|
-
ts_rank(
|
|
3239
|
-
(1 - (
|
|
3314
|
+
ts_rank(chunks.fts, websearch_to_tsquery(?, ?)) AS fts_rank,
|
|
3315
|
+
(1 - (chunks.embedding <=> ${vectorExpr})) AS cosine_distance,
|
|
3240
3316
|
|
|
3241
3317
|
/* Hybrid RRF score */
|
|
3242
3318
|
(
|
|
@@ -3248,10 +3324,10 @@ var vectorSearch = async ({
|
|
|
3248
3324
|
FROM full_text ft
|
|
3249
3325
|
FULL OUTER JOIN semantic se
|
|
3250
3326
|
ON ft.id = se.id
|
|
3251
|
-
JOIN ${chunksTable}
|
|
3252
|
-
ON COALESCE(ft.id, se.id) =
|
|
3253
|
-
JOIN ${mainTable}
|
|
3254
|
-
ON
|
|
3327
|
+
JOIN ${chunksTable} as chunks
|
|
3328
|
+
ON COALESCE(ft.id, se.id) = chunks.id
|
|
3329
|
+
JOIN ${mainTable} as items
|
|
3330
|
+
ON items.id = chunks.source
|
|
3255
3331
|
ORDER BY hybrid_score DESC
|
|
3256
3332
|
LIMIT LEAST(?, 50)
|
|
3257
3333
|
OFFSET 0
|
|
@@ -3277,86 +3353,57 @@ var vectorSearch = async ({
|
|
|
3277
3353
|
matchCount
|
|
3278
3354
|
// final limit
|
|
3279
3355
|
];
|
|
3280
|
-
|
|
3281
|
-
}
|
|
3282
|
-
console.log("[EXULU] Vector search results:",
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
return acc;
|
|
3326
|
-
}, []);
|
|
3327
|
-
console.log("[EXULU] Vector search results after deduplication:", items?.length);
|
|
3328
|
-
items.forEach((item) => {
|
|
3329
|
-
if (!item.chunks?.length) {
|
|
3330
|
-
return;
|
|
3331
|
-
}
|
|
3332
|
-
if (method === "tsvector") {
|
|
3333
|
-
const ranks = item.chunks.map((c) => typeof c.fts_rank === "number" ? c.fts_rank : 0);
|
|
3334
|
-
const total = ranks.reduce((a, b) => a + b, 0);
|
|
3335
|
-
const average = ranks.length ? total / ranks.length : 0;
|
|
3336
|
-
item.averageRelevance = average;
|
|
3337
|
-
item.totalRelevance = total;
|
|
3338
|
-
} else if (method === "cosineDistance") {
|
|
3339
|
-
let methodProperty = "cosine_distance";
|
|
3340
|
-
const average = item.chunks.reduce((acc, item2) => {
|
|
3341
|
-
return acc + item2[methodProperty];
|
|
3342
|
-
}, 0) / item.chunks.length;
|
|
3343
|
-
const total = item.chunks.reduce((acc, item2) => {
|
|
3344
|
-
return acc + item2[methodProperty];
|
|
3345
|
-
}, 0);
|
|
3346
|
-
item.averageRelevance = average;
|
|
3347
|
-
item.totalRelevance = total;
|
|
3348
|
-
} else if (method === "hybridSearch") {
|
|
3349
|
-
const scores = item.chunks.map((c) => typeof c.hybrid_score === "number" ? c.hybrid_score * 10 + 1 : 0);
|
|
3350
|
-
const total = scores.reduce((a, b) => a + b, 0);
|
|
3351
|
-
const average = scores.length ? total / scores.length : 0;
|
|
3352
|
-
item.averageRelevance = average;
|
|
3353
|
-
item.totalRelevance = total;
|
|
3356
|
+
resultChunks = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
|
|
3357
|
+
}
|
|
3358
|
+
console.log("[EXULU] Vector search chunk results:", resultChunks?.length);
|
|
3359
|
+
resultChunks = resultChunks.map((chunk) => ({
|
|
3360
|
+
chunk_content: chunk.content,
|
|
3361
|
+
chunk_index: chunk.chunk_index,
|
|
3362
|
+
chunk_id: chunk.chunk_id,
|
|
3363
|
+
chunk_source: chunk.source,
|
|
3364
|
+
chunk_metadata: chunk.metadata,
|
|
3365
|
+
chunk_created_at: chunk.chunk_created_at,
|
|
3366
|
+
chunk_updated_at: chunk.chunk_updated_at,
|
|
3367
|
+
item_updated_at: chunk.item_updated_at,
|
|
3368
|
+
item_created_at: chunk.item_created_at,
|
|
3369
|
+
item_id: chunk.item_id,
|
|
3370
|
+
item_external_id: chunk.item_external_id,
|
|
3371
|
+
item_name: chunk.item_name,
|
|
3372
|
+
context: {
|
|
3373
|
+
name: table.name.singular,
|
|
3374
|
+
id: table.id || ""
|
|
3375
|
+
},
|
|
3376
|
+
...method === "cosineDistance" && { chunk_cosine_distance: chunk.cosine_distance },
|
|
3377
|
+
...(method === "tsvector" || method === "hybridSearch") && { chunk_fts_rank: chunk.fts_rank },
|
|
3378
|
+
...method === "hybridSearch" && { chunk_hybrid_score: chunk.hybrid_score }
|
|
3379
|
+
}));
|
|
3380
|
+
if (resultChunks.length > 0 && (method === "cosineDistance" || method === "hybridSearch")) {
|
|
3381
|
+
const scoreKey = method === "cosineDistance" ? "chunk_cosine_distance" : "chunk_hybrid_score";
|
|
3382
|
+
const topScore = resultChunks[0][scoreKey];
|
|
3383
|
+
const bottomScore = resultChunks[resultChunks.length - 1][scoreKey];
|
|
3384
|
+
const medianScore = resultChunks[Math.floor(resultChunks.length / 2)][scoreKey];
|
|
3385
|
+
console.log("[EXULU] Score distribution:", {
|
|
3386
|
+
method,
|
|
3387
|
+
count: resultChunks.length,
|
|
3388
|
+
topScore: topScore?.toFixed(4),
|
|
3389
|
+
bottomScore: bottomScore?.toFixed(4),
|
|
3390
|
+
medianScore: medianScore?.toFixed(4)
|
|
3391
|
+
});
|
|
3392
|
+
const adaptiveThreshold = topScore * 0.7;
|
|
3393
|
+
const beforeFilterCount = resultChunks.length;
|
|
3394
|
+
resultChunks = resultChunks.filter((chunk) => {
|
|
3395
|
+
const score = chunk[scoreKey];
|
|
3396
|
+
return score !== void 0 && score >= adaptiveThreshold;
|
|
3397
|
+
});
|
|
3398
|
+
const filteredCount = beforeFilterCount - resultChunks.length;
|
|
3399
|
+
if (filteredCount > 0) {
|
|
3400
|
+
console.log(`[EXULU] Filtered ${filteredCount} low-quality results (threshold: ${adaptiveThreshold.toFixed(4)})`);
|
|
3354
3401
|
}
|
|
3355
|
-
}
|
|
3402
|
+
}
|
|
3356
3403
|
if (resultReranker && query) {
|
|
3357
|
-
|
|
3404
|
+
resultChunks = await resultReranker(resultChunks);
|
|
3358
3405
|
}
|
|
3359
|
-
|
|
3406
|
+
resultChunks = resultChunks.slice(0, limit);
|
|
3360
3407
|
await updateStatistic({
|
|
3361
3408
|
name: "count",
|
|
3362
3409
|
label: table.name.singular,
|
|
@@ -3374,7 +3421,7 @@ var vectorSearch = async ({
|
|
|
3374
3421
|
id: table.id || "",
|
|
3375
3422
|
embedder: embedder.name
|
|
3376
3423
|
},
|
|
3377
|
-
|
|
3424
|
+
chunks: resultChunks
|
|
3378
3425
|
};
|
|
3379
3426
|
};
|
|
3380
3427
|
var RBACResolver = async (db3, entityName, resourceId, rights_mode) => {
|
|
@@ -3404,10 +3451,10 @@ var contextToTableDefinition = (context) => {
|
|
|
3404
3451
|
plural: tableName?.endsWith("s") ? tableName : tableName + "s"
|
|
3405
3452
|
},
|
|
3406
3453
|
RBAC: true,
|
|
3454
|
+
processor: context.processor,
|
|
3407
3455
|
fields: context.fields.map((field) => ({
|
|
3408
3456
|
name: sanitizeName(field.name),
|
|
3409
3457
|
type: field.type,
|
|
3410
|
-
processor: field.processor,
|
|
3411
3458
|
required: field.required,
|
|
3412
3459
|
default: field.default,
|
|
3413
3460
|
index: field.index,
|
|
@@ -3537,7 +3584,6 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
|
|
|
3537
3584
|
const tableNamePlural = table.name.plural.toLowerCase();
|
|
3538
3585
|
const tableNameSingular = table.name.singular.toLowerCase();
|
|
3539
3586
|
const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
|
|
3540
|
-
const processorFields = table.fields.filter((field) => field.processor?.execute);
|
|
3541
3587
|
typeDefs += `
|
|
3542
3588
|
${tableNameSingular === "agent" ? `${tableNameSingular}ById(id: ID!, project: ID): ${tableNameSingular}` : `${tableNameSingular}ById(id: ID!): ${tableNameSingular}`}
|
|
3543
3589
|
|
|
@@ -3564,9 +3610,10 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
|
|
|
3564
3610
|
${tableNameSingular}ExecuteSource(source: ID!, inputs: JSON!): ${tableNameSingular}ExecuteSourceReturnPayload
|
|
3565
3611
|
${tableNameSingular}DeleteChunks(where: [Filter${tableNameSingularUpperCaseFirst}]): ${tableNameSingular}DeleteChunksReturnPayload
|
|
3566
3612
|
`;
|
|
3567
|
-
if (
|
|
3613
|
+
if (table.processor) {
|
|
3568
3614
|
mutationDefs += `
|
|
3569
|
-
${tableNameSingular}
|
|
3615
|
+
${tableNameSingular}ProcessItem(item: ID!): ${tableNameSingular}ProcessItemFieldReturnPayload
|
|
3616
|
+
${tableNameSingular}ProcessItems(limit: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}ProcessItemFieldReturnPayload
|
|
3570
3617
|
`;
|
|
3571
3618
|
}
|
|
3572
3619
|
modelDefs += `
|
|
@@ -3584,8 +3631,8 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
|
|
|
3584
3631
|
|
|
3585
3632
|
type ${tableNameSingular}ProcessItemFieldReturnPayload {
|
|
3586
3633
|
message: String!
|
|
3587
|
-
|
|
3588
|
-
|
|
3634
|
+
results: [String]
|
|
3635
|
+
jobs: [String]
|
|
3589
3636
|
}
|
|
3590
3637
|
|
|
3591
3638
|
type ${tableNameSingular}DeleteChunksReturnPayload {
|
|
@@ -3600,20 +3647,31 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
|
|
|
3600
3647
|
tsvector
|
|
3601
3648
|
}
|
|
3602
3649
|
|
|
3603
|
-
${
|
|
3604
|
-
|
|
3605
|
-
${processorFields.map((field) => field.name).join("\n")}
|
|
3606
|
-
}
|
|
3607
|
-
` : ""}
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
type ${tableNameSingular}VectorSearchResult {
|
|
3611
|
-
items: [${tableNameSingular}]!
|
|
3650
|
+
type ${tableNameSingular}VectorSearchResult {
|
|
3651
|
+
chunks: [${tableNameSingular}VectorSearchChunk!]!
|
|
3612
3652
|
context: VectoSearchResultContext!
|
|
3613
3653
|
filters: JSON!
|
|
3614
3654
|
query: String!
|
|
3615
3655
|
method: VectorMethodEnum!
|
|
3616
3656
|
}
|
|
3657
|
+
|
|
3658
|
+
type ${tableNameSingular}VectorSearchChunk {
|
|
3659
|
+
chunk_content: String
|
|
3660
|
+
chunk_index: Int
|
|
3661
|
+
chunk_id: String
|
|
3662
|
+
chunk_source: String
|
|
3663
|
+
chunk_metadata: JSON
|
|
3664
|
+
chunk_created_at: Date
|
|
3665
|
+
chunk_updated_at: Date
|
|
3666
|
+
item_updated_at: Date
|
|
3667
|
+
item_created_at: Date
|
|
3668
|
+
item_id: String!
|
|
3669
|
+
item_external_id: String
|
|
3670
|
+
item_name: String!
|
|
3671
|
+
chunk_cosine_distance: Float
|
|
3672
|
+
chunk_fts_rank: Float
|
|
3673
|
+
chunk_hybrid_score: Float
|
|
3674
|
+
}
|
|
3617
3675
|
|
|
3618
3676
|
type VectoSearchResultContext {
|
|
3619
3677
|
name: String!
|
|
@@ -3722,6 +3780,7 @@ type PageInfo {
|
|
|
3722
3780
|
worker: config2.concurrency?.worker || void 0,
|
|
3723
3781
|
queue: config2.concurrency?.queue || void 0
|
|
3724
3782
|
},
|
|
3783
|
+
timeoutInSeconds: config2.timeoutInSeconds,
|
|
3725
3784
|
ratelimit: config2.ratelimit,
|
|
3726
3785
|
isMaxed: await config2.queue.isMaxed(),
|
|
3727
3786
|
isPaused: await config2.queue.isPaused(),
|
|
@@ -3935,25 +3994,21 @@ type PageInfo {
|
|
|
3935
3994
|
};
|
|
3936
3995
|
resolvers.Query["contexts"] = async (_, args, context, info) => {
|
|
3937
3996
|
const data = await Promise.all(contexts.map(async (context2) => {
|
|
3938
|
-
let
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
}
|
|
3954
|
-
return null;
|
|
3955
|
-
}));
|
|
3956
|
-
processors = processors.filter((processor) => processor !== null);
|
|
3997
|
+
let processor = null;
|
|
3998
|
+
if (context2.processor) {
|
|
3999
|
+
processor = await new Promise(async (resolve, reject) => {
|
|
4000
|
+
const config2 = await context2.processor?.config;
|
|
4001
|
+
const queue = await config2?.queue;
|
|
4002
|
+
resolve({
|
|
4003
|
+
name: context2.processor.name,
|
|
4004
|
+
description: context2.processor.description,
|
|
4005
|
+
queue: queue?.queue?.name || void 0,
|
|
4006
|
+
trigger: context2.processor?.config?.trigger || "manual",
|
|
4007
|
+
timeoutInSeconds: queue?.timeoutInSeconds || 600,
|
|
4008
|
+
generateEmbeddings: context2.processor?.config?.generateEmbeddings || false
|
|
4009
|
+
});
|
|
4010
|
+
});
|
|
4011
|
+
}
|
|
3957
4012
|
const sources = await Promise.all(context2.sources.map(async (source) => {
|
|
3958
4013
|
let queueName = void 0;
|
|
3959
4014
|
if (source.config) {
|
|
@@ -3985,7 +4040,7 @@ type PageInfo {
|
|
|
3985
4040
|
slug: "/contexts/" + context2.id,
|
|
3986
4041
|
active: context2.active,
|
|
3987
4042
|
sources,
|
|
3988
|
-
|
|
4043
|
+
processor,
|
|
3989
4044
|
fields: context2.fields.map((field) => {
|
|
3990
4045
|
return {
|
|
3991
4046
|
...field,
|
|
@@ -4011,25 +4066,21 @@ type PageInfo {
|
|
|
4011
4066
|
if (!data) {
|
|
4012
4067
|
return null;
|
|
4013
4068
|
}
|
|
4014
|
-
let
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
}
|
|
4030
|
-
return null;
|
|
4031
|
-
}));
|
|
4032
|
-
processors = processors.filter((processor) => processor !== null);
|
|
4069
|
+
let processor = null;
|
|
4070
|
+
if (data.processor) {
|
|
4071
|
+
processor = await new Promise(async (resolve, reject) => {
|
|
4072
|
+
const config2 = await data.processor?.config;
|
|
4073
|
+
const queue = await config2?.queue;
|
|
4074
|
+
resolve({
|
|
4075
|
+
name: data.processor.name,
|
|
4076
|
+
description: data.processor.description,
|
|
4077
|
+
queue: queue?.queue?.name || void 0,
|
|
4078
|
+
trigger: data.processor?.config?.trigger || "manual",
|
|
4079
|
+
timeoutInSeconds: queue?.timeoutInSeconds || 600,
|
|
4080
|
+
generateEmbeddings: data.processor?.config?.generateEmbeddings || false
|
|
4081
|
+
});
|
|
4082
|
+
});
|
|
4083
|
+
}
|
|
4033
4084
|
const sources = await Promise.all(data.sources.map(async (source) => {
|
|
4034
4085
|
let queueName = void 0;
|
|
4035
4086
|
if (source.config) {
|
|
@@ -4066,39 +4117,18 @@ type PageInfo {
|
|
|
4066
4117
|
slug: "/contexts/" + data.id,
|
|
4067
4118
|
active: data.active,
|
|
4068
4119
|
sources,
|
|
4069
|
-
|
|
4120
|
+
processor,
|
|
4070
4121
|
fields: await Promise.all(data.fields.map(async (field) => {
|
|
4071
4122
|
const label = field.name?.replace("_s3key", "");
|
|
4072
4123
|
if (field.type === "file" && !field.name.endsWith("_s3key")) {
|
|
4073
4124
|
field.name = field.name + "_s3key";
|
|
4074
4125
|
}
|
|
4075
|
-
let queue = null;
|
|
4076
|
-
if (field.processor?.config?.queue) {
|
|
4077
|
-
queue = await field.processor.config.queue;
|
|
4078
|
-
}
|
|
4079
4126
|
return {
|
|
4080
4127
|
...field,
|
|
4081
4128
|
name: sanitizeName(field.name),
|
|
4082
4129
|
...field.type === "file" ? {
|
|
4083
4130
|
allowedFileTypes: field.allowedFileTypes
|
|
4084
4131
|
} : {},
|
|
4085
|
-
...field.processor ? {
|
|
4086
|
-
processor: {
|
|
4087
|
-
description: field.processor?.description,
|
|
4088
|
-
config: {
|
|
4089
|
-
trigger: field.processor?.config?.trigger,
|
|
4090
|
-
queue: {
|
|
4091
|
-
name: queue?.queue.name || void 0,
|
|
4092
|
-
ratelimit: queue?.ratelimit || void 0,
|
|
4093
|
-
concurrency: {
|
|
4094
|
-
worker: queue?.concurrency?.worker || void 0,
|
|
4095
|
-
queue: queue?.concurrency?.queue || void 0
|
|
4096
|
-
}
|
|
4097
|
-
}
|
|
4098
|
-
},
|
|
4099
|
-
execute: "function"
|
|
4100
|
-
}
|
|
4101
|
-
} : {},
|
|
4102
4132
|
label
|
|
4103
4133
|
};
|
|
4104
4134
|
})),
|
|
@@ -4167,6 +4197,7 @@ type PageInfo {
|
|
|
4167
4197
|
type QueueResult {
|
|
4168
4198
|
name: String!
|
|
4169
4199
|
concurrency: QueueConcurrency!
|
|
4200
|
+
timeoutInSeconds: Int!
|
|
4170
4201
|
ratelimit: Int!
|
|
4171
4202
|
isMaxed: Boolean!
|
|
4172
4203
|
isPaused: Boolean!
|
|
@@ -4248,17 +4279,12 @@ type AgentEvalFunctionConfig {
|
|
|
4248
4279
|
}
|
|
4249
4280
|
|
|
4250
4281
|
type ItemChunks {
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
chunk_id: ID
|
|
4258
|
-
chunk_created_at: Date
|
|
4259
|
-
chunk_updated_at: Date
|
|
4260
|
-
embedding_size: Float
|
|
4261
|
-
metadata: JSON
|
|
4282
|
+
chunk_id: String!
|
|
4283
|
+
chunk_index: Int!
|
|
4284
|
+
chunk_content: String!
|
|
4285
|
+
chunk_source: String!
|
|
4286
|
+
chunk_created_at: Date!
|
|
4287
|
+
chunk_updated_at: Date!
|
|
4262
4288
|
}
|
|
4263
4289
|
|
|
4264
4290
|
type Provider {
|
|
@@ -4294,7 +4320,7 @@ type Context {
|
|
|
4294
4320
|
fields: JSON
|
|
4295
4321
|
configuration: JSON
|
|
4296
4322
|
sources: [ContextSource]
|
|
4297
|
-
|
|
4323
|
+
processor: ContextProcessor
|
|
4298
4324
|
}
|
|
4299
4325
|
type Embedder {
|
|
4300
4326
|
name: String!
|
|
@@ -4308,7 +4334,7 @@ type EmbedderConfig {
|
|
|
4308
4334
|
default: String
|
|
4309
4335
|
}
|
|
4310
4336
|
type ContextProcessor {
|
|
4311
|
-
|
|
4337
|
+
name: String!
|
|
4312
4338
|
description: String
|
|
4313
4339
|
queue: String
|
|
4314
4340
|
trigger: String
|
|
@@ -4372,6 +4398,9 @@ type Job {
|
|
|
4372
4398
|
name: String!
|
|
4373
4399
|
returnvalue: JSON
|
|
4374
4400
|
stacktrace: [String]
|
|
4401
|
+
finishedOn: Date
|
|
4402
|
+
processedOn: Date
|
|
4403
|
+
attemptsMade: Int
|
|
4375
4404
|
failedReason: String
|
|
4376
4405
|
state: String!
|
|
4377
4406
|
data: JSON
|
|
@@ -4478,6 +4507,8 @@ var getPresignedUrl = async (bucket, key, config) => {
|
|
|
4478
4507
|
if (!config.fileUploads) {
|
|
4479
4508
|
throw new Error("File uploads are not configured");
|
|
4480
4509
|
}
|
|
4510
|
+
console.log("[EXULU] getting presigned url for bucket", bucket);
|
|
4511
|
+
console.log("[EXULU] getting presigned url for key", key);
|
|
4481
4512
|
const url = await (0, import_s3_request_presigner.getSignedUrl)(
|
|
4482
4513
|
getS3Client(config),
|
|
4483
4514
|
new import_client_s3.GetObjectCommand({
|
|
@@ -6044,12 +6075,12 @@ var ExuluContext = class {
|
|
|
6044
6075
|
name;
|
|
6045
6076
|
active;
|
|
6046
6077
|
fields;
|
|
6078
|
+
processor;
|
|
6047
6079
|
rateLimit;
|
|
6048
6080
|
description;
|
|
6049
6081
|
embedder;
|
|
6050
6082
|
queryRewriter;
|
|
6051
6083
|
resultReranker;
|
|
6052
|
-
// todo typings
|
|
6053
6084
|
configuration;
|
|
6054
6085
|
sources = [];
|
|
6055
6086
|
constructor({
|
|
@@ -6057,6 +6088,7 @@ var ExuluContext = class {
|
|
|
6057
6088
|
name,
|
|
6058
6089
|
description,
|
|
6059
6090
|
embedder,
|
|
6091
|
+
processor,
|
|
6060
6092
|
active,
|
|
6061
6093
|
rateLimit,
|
|
6062
6094
|
fields,
|
|
@@ -6069,10 +6101,12 @@ var ExuluContext = class {
|
|
|
6069
6101
|
this.name = name;
|
|
6070
6102
|
this.fields = fields || [];
|
|
6071
6103
|
this.sources = sources || [];
|
|
6104
|
+
this.processor = processor;
|
|
6072
6105
|
this.configuration = configuration || {
|
|
6073
6106
|
calculateVectors: "manual",
|
|
6074
6107
|
language: "english",
|
|
6075
|
-
defaultRightsMode: "private"
|
|
6108
|
+
defaultRightsMode: "private",
|
|
6109
|
+
maxRetrievalResults: 10
|
|
6076
6110
|
};
|
|
6077
6111
|
this.description = description;
|
|
6078
6112
|
this.embedder = embedder;
|
|
@@ -6082,26 +6116,18 @@ var ExuluContext = class {
|
|
|
6082
6116
|
this.resultReranker = resultReranker;
|
|
6083
6117
|
}
|
|
6084
6118
|
processField = async (trigger, item, exuluConfig, user, role) => {
|
|
6085
|
-
|
|
6086
|
-
throw new Error("Field property on item is required for running a specific processor.");
|
|
6087
|
-
}
|
|
6088
|
-
console.log("[EXULU] processing field", item.field, " in context", this.id);
|
|
6089
|
-
console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
|
|
6090
|
-
const field = this.fields.find((field2) => {
|
|
6091
|
-
return field2.name.replace("_s3key", "") === item.field.replace("_s3key", "");
|
|
6092
|
-
});
|
|
6093
|
-
if (!field || !field.processor) {
|
|
6094
|
-
console.error("[EXULU] field not found or processor not set for field", item.field, " in context", this.id);
|
|
6095
|
-
throw new Error("Field not found or processor not set for field " + item.field + " in context " + this.id);
|
|
6096
|
-
}
|
|
6119
|
+
console.log("[EXULU] processing item, ", item, " in context", this.id);
|
|
6097
6120
|
const exuluStorage = new ExuluStorage({ config: exuluConfig });
|
|
6098
|
-
|
|
6121
|
+
if (!this.processor) {
|
|
6122
|
+
throw new Error(`Processor is not set for this context: ${this.id}.`);
|
|
6123
|
+
}
|
|
6124
|
+
const queue = await this.processor.config?.queue;
|
|
6099
6125
|
if (queue?.queue.name) {
|
|
6100
6126
|
console.log("[EXULU] processor is in queue mode, scheduling job.");
|
|
6101
6127
|
const job = await bullmqDecorator({
|
|
6102
|
-
timeoutInSeconds:
|
|
6103
|
-
label: `${this.name} ${
|
|
6104
|
-
processor: `${this.id}-${
|
|
6128
|
+
timeoutInSeconds: this.processor.config?.timeoutInSeconds || 600,
|
|
6129
|
+
label: `${this.name} ${this.processor.name} data processor`,
|
|
6130
|
+
processor: `${this.id}-${this.processor.name}`,
|
|
6105
6131
|
context: this.id,
|
|
6106
6132
|
inputs: item,
|
|
6107
6133
|
item: item.id,
|
|
@@ -6116,12 +6142,12 @@ var ExuluContext = class {
|
|
|
6116
6142
|
trigger
|
|
6117
6143
|
});
|
|
6118
6144
|
return {
|
|
6119
|
-
result:
|
|
6145
|
+
result: void 0,
|
|
6120
6146
|
job: job.id
|
|
6121
6147
|
};
|
|
6122
6148
|
}
|
|
6123
6149
|
console.log("[EXULU] POS 1 -- EXULU CONTEXT PROCESS FIELD");
|
|
6124
|
-
const processorResult = await
|
|
6150
|
+
const processorResult = await this.processor.execute({
|
|
6125
6151
|
item,
|
|
6126
6152
|
user,
|
|
6127
6153
|
role,
|
|
@@ -6138,7 +6164,8 @@ var ExuluContext = class {
|
|
|
6138
6164
|
await db3.from(getTableName(this.id)).where({
|
|
6139
6165
|
id: processorResult.id
|
|
6140
6166
|
}).update({
|
|
6141
|
-
...processorResult
|
|
6167
|
+
...processorResult,
|
|
6168
|
+
last_processed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6142
6169
|
});
|
|
6143
6170
|
return {
|
|
6144
6171
|
result: processorResult,
|
|
@@ -6152,7 +6179,8 @@ var ExuluContext = class {
|
|
|
6152
6179
|
user: options.user,
|
|
6153
6180
|
role: options.role,
|
|
6154
6181
|
context: this,
|
|
6155
|
-
db: db3
|
|
6182
|
+
db: db3,
|
|
6183
|
+
limit: options?.limit || this.configuration.maxRetrievalResults || 10
|
|
6156
6184
|
});
|
|
6157
6185
|
return result;
|
|
6158
6186
|
};
|
|
@@ -6256,9 +6284,8 @@ var ExuluContext = class {
|
|
|
6256
6284
|
console.log("[EXULU] context configuration", this.configuration);
|
|
6257
6285
|
let jobs = [];
|
|
6258
6286
|
let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
const processor = this.fields.find((field) => field.name === key.replace("_s3key", ""))?.processor;
|
|
6287
|
+
if (this.processor) {
|
|
6288
|
+
const processor = this.processor;
|
|
6262
6289
|
console.log("[EXULU] Processor found", processor);
|
|
6263
6290
|
if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
|
|
6264
6291
|
const {
|
|
@@ -6268,8 +6295,7 @@ var ExuluContext = class {
|
|
|
6268
6295
|
"api",
|
|
6269
6296
|
{
|
|
6270
6297
|
...item,
|
|
6271
|
-
id: results[0].id
|
|
6272
|
-
field: key
|
|
6298
|
+
id: results[0].id
|
|
6273
6299
|
},
|
|
6274
6300
|
config,
|
|
6275
6301
|
user,
|
|
@@ -6336,8 +6362,8 @@ var ExuluContext = class {
|
|
|
6336
6362
|
await mutation;
|
|
6337
6363
|
let jobs = [];
|
|
6338
6364
|
let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always");
|
|
6339
|
-
|
|
6340
|
-
const processor = this.
|
|
6365
|
+
if (this.processor) {
|
|
6366
|
+
const processor = this.processor;
|
|
6341
6367
|
if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
|
|
6342
6368
|
const {
|
|
6343
6369
|
job: processorJob,
|
|
@@ -6346,8 +6372,7 @@ var ExuluContext = class {
|
|
|
6346
6372
|
"api",
|
|
6347
6373
|
{
|
|
6348
6374
|
...item,
|
|
6349
|
-
id: record.id
|
|
6350
|
-
field: key
|
|
6375
|
+
id: record.id
|
|
6351
6376
|
},
|
|
6352
6377
|
config,
|
|
6353
6378
|
user,
|
|
@@ -6585,22 +6610,26 @@ var ExuluContext = class {
|
|
|
6585
6610
|
};
|
|
6586
6611
|
// Exports the context as a tool that can be used by an agent
|
|
6587
6612
|
tool = () => {
|
|
6613
|
+
if (this.configuration.enableAsTool === false) {
|
|
6614
|
+
return null;
|
|
6615
|
+
}
|
|
6588
6616
|
return new ExuluTool2({
|
|
6589
6617
|
id: this.id,
|
|
6590
|
-
name: `${this.name}`,
|
|
6618
|
+
name: `${this.name}_context_search`,
|
|
6591
6619
|
type: "context",
|
|
6592
6620
|
category: "contexts",
|
|
6593
6621
|
inputSchema: import_zod.z.object({
|
|
6594
|
-
|
|
6622
|
+
originalQuestion: import_zod.z.string().describe("The original question that the user asked"),
|
|
6623
|
+
relevantKeywords: import_zod.z.array(import_zod.z.string()).describe("The keywords that are relevant to the user's question, for example names of specific products, systems or parts, IDs, etc.")
|
|
6595
6624
|
}),
|
|
6596
6625
|
config: [],
|
|
6597
6626
|
description: `Gets information from the context called: ${this.name}. The context description is: ${this.description}.`,
|
|
6598
|
-
execute: async ({
|
|
6627
|
+
execute: async ({ originalQuestion, relevantKeywords, user, role }) => {
|
|
6599
6628
|
const { db: db3 } = await postgresClient();
|
|
6600
6629
|
const result = await vectorSearch({
|
|
6601
6630
|
page: 1,
|
|
6602
|
-
limit:
|
|
6603
|
-
query,
|
|
6631
|
+
limit: this.configuration.maxRetrievalResults ?? 10,
|
|
6632
|
+
query: originalQuestion,
|
|
6604
6633
|
filters: [],
|
|
6605
6634
|
user,
|
|
6606
6635
|
role,
|
|
@@ -6620,7 +6649,13 @@ var ExuluContext = class {
|
|
|
6620
6649
|
role: user?.role?.id
|
|
6621
6650
|
});
|
|
6622
6651
|
return {
|
|
6623
|
-
|
|
6652
|
+
result: JSON.stringify(result.chunks.map((chunk) => ({
|
|
6653
|
+
...chunk,
|
|
6654
|
+
context: {
|
|
6655
|
+
name: this.name,
|
|
6656
|
+
id: this.id
|
|
6657
|
+
}
|
|
6658
|
+
})))
|
|
6624
6659
|
};
|
|
6625
6660
|
}
|
|
6626
6661
|
});
|
|
@@ -7487,7 +7522,7 @@ var import_ai3 = require("ai");
|
|
|
7487
7522
|
var import_crypto_js4 = __toESM(require("crypto-js"), 1);
|
|
7488
7523
|
|
|
7489
7524
|
// src/registry/log-metadata.ts
|
|
7490
|
-
function
|
|
7525
|
+
function logMetadata(id, additionalMetadata) {
|
|
7491
7526
|
return {
|
|
7492
7527
|
__logMetadata: true,
|
|
7493
7528
|
id,
|
|
@@ -7497,9 +7532,32 @@ function logMetadata2(id, additionalMetadata) {
|
|
|
7497
7532
|
|
|
7498
7533
|
// src/registry/workers.ts
|
|
7499
7534
|
var redisConnection;
|
|
7535
|
+
var unhandledRejectionHandlerInstalled = false;
|
|
7536
|
+
var installGlobalErrorHandlers = () => {
|
|
7537
|
+
if (unhandledRejectionHandlerInstalled) return;
|
|
7538
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
7539
|
+
console.error("[EXULU] Unhandled Promise Rejection detected! This would have crashed the worker.", {
|
|
7540
|
+
reason: reason instanceof Error ? reason.message : String(reason),
|
|
7541
|
+
stack: reason instanceof Error ? reason.stack : void 0
|
|
7542
|
+
});
|
|
7543
|
+
});
|
|
7544
|
+
process.on("uncaughtException", (error) => {
|
|
7545
|
+
console.error("[EXULU] Uncaught Exception detected! This would have crashed the worker.", {
|
|
7546
|
+
error: error.message,
|
|
7547
|
+
stack: error.stack
|
|
7548
|
+
});
|
|
7549
|
+
if (error.message.includes("FATAL") || error.message.includes("Cannot find module")) {
|
|
7550
|
+
console.error("[EXULU] Fatal error detected, exiting process.");
|
|
7551
|
+
process.exit(1);
|
|
7552
|
+
}
|
|
7553
|
+
});
|
|
7554
|
+
unhandledRejectionHandlerInstalled = true;
|
|
7555
|
+
console.log("[EXULU] Global error handlers installed to prevent worker crashes");
|
|
7556
|
+
};
|
|
7500
7557
|
var createWorkers = async (agents, queues2, config, contexts, evals, tools, tracer) => {
|
|
7501
7558
|
console.log("[EXULU] creating workers for " + queues2?.length + " queues.");
|
|
7502
7559
|
console.log("[EXULU] queues", queues2.map((q) => q.queue.name));
|
|
7560
|
+
installGlobalErrorHandlers();
|
|
7503
7561
|
if (!redisServer.host || !redisServer.port) {
|
|
7504
7562
|
console.error("[EXULU] you are trying to start worker, but no redis server is configured in the environment.");
|
|
7505
7563
|
throw new Error("No redis server configured in the environment, so cannot start worker.");
|
|
@@ -7524,7 +7582,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7524
7582
|
const worker = new import_bullmq4.Worker(
|
|
7525
7583
|
`${queue.queue.name}`,
|
|
7526
7584
|
async (bullmqJob) => {
|
|
7527
|
-
console.log("[EXULU] starting execution for job",
|
|
7585
|
+
console.log("[EXULU] starting execution for job", logMetadata(bullmqJob.name, {
|
|
7528
7586
|
name: bullmqJob.name,
|
|
7529
7587
|
jobId: bullmqJob.id,
|
|
7530
7588
|
status: await bullmqJob.getState(),
|
|
@@ -7534,9 +7592,12 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7534
7592
|
const data = bullmqJob.data;
|
|
7535
7593
|
const timeoutInSeconds = data.timeoutInSeconds || 600;
|
|
7536
7594
|
const timeoutMs = timeoutInSeconds * 1e3;
|
|
7595
|
+
let timeoutHandle;
|
|
7537
7596
|
const timeoutPromise = new Promise((_, reject) => {
|
|
7538
|
-
setTimeout(() => {
|
|
7539
|
-
|
|
7597
|
+
timeoutHandle = setTimeout(() => {
|
|
7598
|
+
const timeoutError = new Error(`Timeout for job ${bullmqJob.id} reached after ${timeoutInSeconds}s`);
|
|
7599
|
+
console.error(`[EXULU] ${timeoutError.message}`);
|
|
7600
|
+
reject(timeoutError);
|
|
7540
7601
|
}, timeoutMs);
|
|
7541
7602
|
});
|
|
7542
7603
|
const workPromise = (async () => {
|
|
@@ -7544,7 +7605,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7544
7605
|
console.log(`[EXULU] Job ${bullmqJob.id} - Log file: logs/jobs/job-${bullmqJob.id}.log`);
|
|
7545
7606
|
bullmq.validate(bullmqJob.id, data);
|
|
7546
7607
|
if (data.type === "embedder") {
|
|
7547
|
-
console.log("[EXULU] running an embedder job.",
|
|
7608
|
+
console.log("[EXULU] running an embedder job.", logMetadata(bullmqJob.name));
|
|
7548
7609
|
const label = `embedder-${bullmqJob.name}`;
|
|
7549
7610
|
await db3.from("job_results").insert({
|
|
7550
7611
|
job_id: bullmqJob.id,
|
|
@@ -7564,12 +7625,12 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7564
7625
|
if (!embedder) {
|
|
7565
7626
|
throw new Error(`Embedder ${data.embedder} not found in the registry.`);
|
|
7566
7627
|
}
|
|
7567
|
-
const
|
|
7628
|
+
const result = await context.createAndUpsertEmbeddings(data.inputs, config, data.user, {
|
|
7568
7629
|
label: embedder.name,
|
|
7569
7630
|
trigger: data.trigger
|
|
7570
7631
|
}, data.role, bullmqJob.id);
|
|
7571
7632
|
return {
|
|
7572
|
-
result
|
|
7633
|
+
result,
|
|
7573
7634
|
metadata: {}
|
|
7574
7635
|
};
|
|
7575
7636
|
}
|
|
@@ -7587,21 +7648,15 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7587
7648
|
if (!context) {
|
|
7588
7649
|
throw new Error(`Context ${data.context} not found in the registry.`);
|
|
7589
7650
|
}
|
|
7590
|
-
const field = context.fields.find((field2) => {
|
|
7591
|
-
return field2.name.replace("_s3key", "") === data.inputs.field.replace("_s3key", "");
|
|
7592
|
-
});
|
|
7593
|
-
if (!field) {
|
|
7594
|
-
throw new Error(`Field ${data.inputs.field} not found in the context ${data.context}.`);
|
|
7595
|
-
}
|
|
7596
|
-
if (!field.processor) {
|
|
7597
|
-
throw new Error(`Processor not set for field ${data.inputs.field} in the context ${data.context}.`);
|
|
7598
|
-
}
|
|
7599
7651
|
if (!data.inputs.id) {
|
|
7600
|
-
throw new Error(`[EXULU] Item not set for processor
|
|
7652
|
+
throw new Error(`[EXULU] Item not set for processor in context ${context.id}, running in job ${bullmqJob.id}.`);
|
|
7653
|
+
}
|
|
7654
|
+
if (!context.processor) {
|
|
7655
|
+
throw new Error(`Tried to run a processor job for context ${context.id}, but no processor is set.`);
|
|
7601
7656
|
}
|
|
7602
7657
|
const exuluStorage = new ExuluStorage({ config });
|
|
7603
7658
|
console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
|
|
7604
|
-
const processorResult = await
|
|
7659
|
+
const processorResult = await context.processor.execute({
|
|
7605
7660
|
item: data.inputs,
|
|
7606
7661
|
user: data.user,
|
|
7607
7662
|
role: data.role,
|
|
@@ -7611,16 +7666,17 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7611
7666
|
exuluConfig: config
|
|
7612
7667
|
});
|
|
7613
7668
|
if (!processorResult) {
|
|
7614
|
-
throw new Error(`[EXULU] Processor
|
|
7669
|
+
throw new Error(`[EXULU] Processor in context ${context.id}, running in job ${bullmqJob.id} did not return an item.`);
|
|
7615
7670
|
}
|
|
7616
7671
|
delete processorResult.field;
|
|
7617
7672
|
await db3.from(getTableName(context.id)).where({
|
|
7618
7673
|
id: processorResult.id
|
|
7619
7674
|
}).update({
|
|
7620
|
-
...processorResult
|
|
7675
|
+
...processorResult,
|
|
7676
|
+
last_processed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7621
7677
|
});
|
|
7622
7678
|
let jobs = [];
|
|
7623
|
-
if (
|
|
7679
|
+
if (context.processor?.config?.generateEmbeddings) {
|
|
7624
7680
|
const fullItem = await db3.from(getTableName(context.id)).where({
|
|
7625
7681
|
id: processorResult.id
|
|
7626
7682
|
}).first();
|
|
@@ -7646,7 +7702,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7646
7702
|
};
|
|
7647
7703
|
}
|
|
7648
7704
|
if (data.type === "eval_run") {
|
|
7649
|
-
console.log("[EXULU] running an eval run job.",
|
|
7705
|
+
console.log("[EXULU] running an eval run job.", logMetadata(bullmqJob.name));
|
|
7650
7706
|
const label = `eval-run-${data.eval_run_id}-${data.test_case_id}`;
|
|
7651
7707
|
const existingResult = await db3.from("job_results").where({ label }).first();
|
|
7652
7708
|
if (existingResult) {
|
|
@@ -7695,7 +7751,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7695
7751
|
resolve(messages2);
|
|
7696
7752
|
break;
|
|
7697
7753
|
} catch (error) {
|
|
7698
|
-
console.error(`[EXULU] error processing UI messages flow for agent ${agentInstance.name} (${agentInstance.id}).`,
|
|
7754
|
+
console.error(`[EXULU] error processing UI messages flow for agent ${agentInstance.name} (${agentInstance.id}).`, logMetadata(bullmqJob.name, {
|
|
7699
7755
|
error: error instanceof Error ? error.message : String(error)
|
|
7700
7756
|
}));
|
|
7701
7757
|
attempts++;
|
|
@@ -7706,9 +7762,9 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7706
7762
|
}
|
|
7707
7763
|
}
|
|
7708
7764
|
});
|
|
7709
|
-
const
|
|
7710
|
-
const messages =
|
|
7711
|
-
const metadata =
|
|
7765
|
+
const result = await promise;
|
|
7766
|
+
const messages = result.messages;
|
|
7767
|
+
const metadata = result.metadata;
|
|
7712
7768
|
const evalFunctions = evalRun.eval_functions;
|
|
7713
7769
|
let evalFunctionResults = [];
|
|
7714
7770
|
for (const evalFunction of evalFunctions) {
|
|
@@ -7716,7 +7772,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7716
7772
|
if (!evalMethod) {
|
|
7717
7773
|
throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
|
|
7718
7774
|
}
|
|
7719
|
-
let
|
|
7775
|
+
let result2;
|
|
7720
7776
|
if (evalMethod.queue) {
|
|
7721
7777
|
const queue2 = await evalMethod.queue;
|
|
7722
7778
|
const jobData = {
|
|
@@ -7747,21 +7803,21 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7747
7803
|
if (!job.id) {
|
|
7748
7804
|
throw new Error(`Tried to add job to queue ${queue2.queue.name} but failed to get the job ID.`);
|
|
7749
7805
|
}
|
|
7750
|
-
|
|
7806
|
+
result2 = await pollJobResult({ queue: queue2, jobId: job.id });
|
|
7751
7807
|
const evalFunctionResult = {
|
|
7752
7808
|
test_case_id: testCase.id,
|
|
7753
7809
|
eval_run_id: evalRun.id,
|
|
7754
7810
|
eval_function_id: evalFunction.id,
|
|
7755
7811
|
eval_function_name: evalFunction.name,
|
|
7756
7812
|
eval_function_config: evalFunction.config || {},
|
|
7757
|
-
result:
|
|
7813
|
+
result: result2 || 0
|
|
7758
7814
|
};
|
|
7759
|
-
console.log(`[EXULU] eval function ${evalFunction.id} result: ${
|
|
7760
|
-
result:
|
|
7815
|
+
console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata(bullmqJob.name, {
|
|
7816
|
+
result: result2 || 0
|
|
7761
7817
|
}));
|
|
7762
7818
|
evalFunctionResults.push(evalFunctionResult);
|
|
7763
7819
|
} else {
|
|
7764
|
-
|
|
7820
|
+
result2 = await evalMethod.run(
|
|
7765
7821
|
agentInstance,
|
|
7766
7822
|
agentBackend,
|
|
7767
7823
|
testCase,
|
|
@@ -7772,15 +7828,15 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7772
7828
|
test_case_id: testCase.id,
|
|
7773
7829
|
eval_run_id: evalRun.id,
|
|
7774
7830
|
eval_function_id: evalFunction.id,
|
|
7775
|
-
result:
|
|
7831
|
+
result: result2 || 0
|
|
7776
7832
|
};
|
|
7777
7833
|
evalFunctionResults.push(evalFunctionResult);
|
|
7778
|
-
console.log(`[EXULU] eval function ${evalFunction.id} result: ${
|
|
7779
|
-
result:
|
|
7834
|
+
console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata(bullmqJob.name, {
|
|
7835
|
+
result: result2 || 0
|
|
7780
7836
|
}));
|
|
7781
7837
|
}
|
|
7782
7838
|
}
|
|
7783
|
-
const scores = evalFunctionResults.map((
|
|
7839
|
+
const scores = evalFunctionResults.map((result2) => result2.result);
|
|
7784
7840
|
console.log("[EXULU] Exulu eval run scores for test case: " + testCase.id, scores);
|
|
7785
7841
|
let score = 0;
|
|
7786
7842
|
switch (data.scoring_method?.toLowerCase()) {
|
|
@@ -7812,7 +7868,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7812
7868
|
};
|
|
7813
7869
|
}
|
|
7814
7870
|
if (data.type === "eval_function") {
|
|
7815
|
-
console.log("[EXULU] running an eval function job.",
|
|
7871
|
+
console.log("[EXULU] running an eval function job.", logMetadata(bullmqJob.name));
|
|
7816
7872
|
if (data.eval_functions?.length !== 1) {
|
|
7817
7873
|
throw new Error(`Expected 1 eval function for eval function job, got ${data.eval_functions?.length}.`);
|
|
7818
7874
|
}
|
|
@@ -7845,30 +7901,30 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7845
7901
|
messages: inputMessages
|
|
7846
7902
|
} = await validateEvalPayload(data, agents);
|
|
7847
7903
|
const evalFunctions = evalRun.eval_functions;
|
|
7848
|
-
let
|
|
7904
|
+
let result;
|
|
7849
7905
|
for (const evalFunction of evalFunctions) {
|
|
7850
7906
|
const evalMethod = evals.find((e) => e.id === evalFunction.id);
|
|
7851
7907
|
if (!evalMethod) {
|
|
7852
7908
|
throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
|
|
7853
7909
|
}
|
|
7854
|
-
|
|
7910
|
+
result = await evalMethod.run(
|
|
7855
7911
|
agentInstance,
|
|
7856
7912
|
backend,
|
|
7857
7913
|
testCase,
|
|
7858
7914
|
inputMessages,
|
|
7859
7915
|
evalFunction.config || {}
|
|
7860
7916
|
);
|
|
7861
|
-
console.log(`[EXULU] eval function ${evalFunction.id} result: ${
|
|
7862
|
-
result:
|
|
7917
|
+
console.log(`[EXULU] eval function ${evalFunction.id} result: ${result}`, logMetadata(bullmqJob.name, {
|
|
7918
|
+
result: result || 0
|
|
7863
7919
|
}));
|
|
7864
7920
|
}
|
|
7865
7921
|
return {
|
|
7866
|
-
result
|
|
7922
|
+
result,
|
|
7867
7923
|
metadata: {}
|
|
7868
7924
|
};
|
|
7869
7925
|
}
|
|
7870
7926
|
if (data.type === "source") {
|
|
7871
|
-
console.log("[EXULU] running a source job.",
|
|
7927
|
+
console.log("[EXULU] running a source job.", logMetadata(bullmqJob.name));
|
|
7872
7928
|
if (!data.source) {
|
|
7873
7929
|
throw new Error(`No source id set for source job.`);
|
|
7874
7930
|
}
|
|
@@ -7883,10 +7939,10 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7883
7939
|
if (!source) {
|
|
7884
7940
|
throw new Error(`Source ${data.source} not found in the context ${context.id}.`);
|
|
7885
7941
|
}
|
|
7886
|
-
const
|
|
7942
|
+
const result = await source.execute(data.inputs);
|
|
7887
7943
|
let jobs = [];
|
|
7888
7944
|
let items = [];
|
|
7889
|
-
for (const item of
|
|
7945
|
+
for (const item of result) {
|
|
7890
7946
|
const { item: createdItem, job } = await context.createItem(
|
|
7891
7947
|
item,
|
|
7892
7948
|
config,
|
|
@@ -7896,14 +7952,14 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7896
7952
|
);
|
|
7897
7953
|
if (job) {
|
|
7898
7954
|
jobs.push(job);
|
|
7899
|
-
console.log(`[EXULU] Scheduled job through source update job for item ${createdItem.id} (Job ID: ${job})`,
|
|
7955
|
+
console.log(`[EXULU] Scheduled job through source update job for item ${createdItem.id} (Job ID: ${job})`, logMetadata(bullmqJob.name, {
|
|
7900
7956
|
item: createdItem,
|
|
7901
7957
|
job
|
|
7902
7958
|
}));
|
|
7903
7959
|
}
|
|
7904
7960
|
if (createdItem.id) {
|
|
7905
7961
|
items.push(createdItem.id);
|
|
7906
|
-
console.log(`[EXULU] created item through source update job ${createdItem.id}`,
|
|
7962
|
+
console.log(`[EXULU] created item through source update job ${createdItem.id}`, logMetadata(bullmqJob.name, {
|
|
7907
7963
|
item: createdItem
|
|
7908
7964
|
}));
|
|
7909
7965
|
}
|
|
@@ -7918,7 +7974,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7918
7974
|
role: data?.role
|
|
7919
7975
|
});
|
|
7920
7976
|
return {
|
|
7921
|
-
result
|
|
7977
|
+
result,
|
|
7922
7978
|
metadata: {
|
|
7923
7979
|
jobs,
|
|
7924
7980
|
items
|
|
@@ -7931,8 +7987,15 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7931
7987
|
throw error;
|
|
7932
7988
|
}
|
|
7933
7989
|
})();
|
|
7934
|
-
|
|
7935
|
-
|
|
7990
|
+
try {
|
|
7991
|
+
const result = await Promise.race([workPromise, timeoutPromise]);
|
|
7992
|
+
clearTimeout(timeoutHandle);
|
|
7993
|
+
return result;
|
|
7994
|
+
} catch (error) {
|
|
7995
|
+
clearTimeout(timeoutHandle);
|
|
7996
|
+
console.error(`[EXULU] job ${bullmqJob.id} failed (error caught in race handler).`, error instanceof Error ? error.message : String(error));
|
|
7997
|
+
throw error;
|
|
7998
|
+
}
|
|
7936
7999
|
},
|
|
7937
8000
|
{
|
|
7938
8001
|
autorun: true,
|
|
@@ -7967,7 +8030,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7967
8030
|
});
|
|
7968
8031
|
return;
|
|
7969
8032
|
}
|
|
7970
|
-
console.error(`[EXULU] job failed.`, job?.name ?
|
|
8033
|
+
console.error(`[EXULU] job failed.`, job?.name ? logMetadata(job.name, {
|
|
7971
8034
|
error: error instanceof Error ? error.message : String(error)
|
|
7972
8035
|
}) : error);
|
|
7973
8036
|
});
|
|
@@ -7975,7 +8038,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
|
|
|
7975
8038
|
console.error(`[EXULU] worker error.`, error);
|
|
7976
8039
|
});
|
|
7977
8040
|
worker.on("progress", (job, progress) => {
|
|
7978
|
-
console.log(`[EXULU] job progress ${job.id}.`,
|
|
8041
|
+
console.log(`[EXULU] job progress ${job.id}.`, logMetadata(job.name, {
|
|
7979
8042
|
progress
|
|
7980
8043
|
}));
|
|
7981
8044
|
});
|
|
@@ -9141,7 +9204,7 @@ var ExuluQueues = class {
|
|
|
9141
9204
|
// method of ExuluQueues we need to store the desired rate limit on the queue
|
|
9142
9205
|
// here so we can use the value when creating workers for the queue instance
|
|
9143
9206
|
// as there is no way to store a rate limit value natively on a bullm queue.
|
|
9144
|
-
register = (name, concurrency, ratelimit = 1) => {
|
|
9207
|
+
register = (name, concurrency, ratelimit = 1, timeoutInSeconds = 180) => {
|
|
9145
9208
|
const queueConcurrency = concurrency.queue || 1;
|
|
9146
9209
|
const workerConcurrency = concurrency.worker || 1;
|
|
9147
9210
|
const use = async () => {
|
|
@@ -9157,7 +9220,8 @@ var ExuluQueues = class {
|
|
|
9157
9220
|
concurrency: {
|
|
9158
9221
|
worker: workerConcurrency,
|
|
9159
9222
|
queue: queueConcurrency
|
|
9160
|
-
}
|
|
9223
|
+
},
|
|
9224
|
+
timeoutInSeconds
|
|
9161
9225
|
};
|
|
9162
9226
|
}
|
|
9163
9227
|
if (!redisServer.host?.length || !redisServer.port?.length) {
|
|
@@ -9183,7 +9247,8 @@ var ExuluQueues = class {
|
|
|
9183
9247
|
concurrency: {
|
|
9184
9248
|
worker: workerConcurrency,
|
|
9185
9249
|
queue: queueConcurrency
|
|
9186
|
-
}
|
|
9250
|
+
},
|
|
9251
|
+
timeoutInSeconds
|
|
9187
9252
|
});
|
|
9188
9253
|
return {
|
|
9189
9254
|
queue: newQueue,
|
|
@@ -9191,7 +9256,8 @@ var ExuluQueues = class {
|
|
|
9191
9256
|
concurrency: {
|
|
9192
9257
|
worker: workerConcurrency,
|
|
9193
9258
|
queue: queueConcurrency
|
|
9194
|
-
}
|
|
9259
|
+
},
|
|
9260
|
+
timeoutInSeconds
|
|
9195
9261
|
};
|
|
9196
9262
|
};
|
|
9197
9263
|
this.list.set(name, {
|
|
@@ -9201,6 +9267,7 @@ var ExuluQueues = class {
|
|
|
9201
9267
|
queue: queueConcurrency
|
|
9202
9268
|
},
|
|
9203
9269
|
ratelimit,
|
|
9270
|
+
timeoutInSeconds,
|
|
9204
9271
|
use
|
|
9205
9272
|
});
|
|
9206
9273
|
return {
|
|
@@ -10266,7 +10333,7 @@ var ExuluApp = class {
|
|
|
10266
10333
|
...[previewPdfTool],
|
|
10267
10334
|
...todoTools,
|
|
10268
10335
|
// Add contexts as tools
|
|
10269
|
-
...Object.values(contexts || {}).map((context) => context.tool())
|
|
10336
|
+
...Object.values(contexts || {}).map((context) => context.tool()).filter(Boolean)
|
|
10270
10337
|
// Because agents are stored in the database, we add those as tools
|
|
10271
10338
|
// at request time, not during ExuluApp initialization. We add them
|
|
10272
10339
|
// in the grahql tools resolver.
|
|
@@ -10428,10 +10495,7 @@ var ExuluApp = class {
|
|
|
10428
10495
|
console.warn("[EXULU] No queue configured for source", source.name);
|
|
10429
10496
|
continue;
|
|
10430
10497
|
}
|
|
10431
|
-
if (queue) {
|
|
10432
|
-
if (!source.config?.schedule) {
|
|
10433
|
-
throw new Error("Schedule is required for source when configuring a queue: " + source.name);
|
|
10434
|
-
}
|
|
10498
|
+
if (queue && source.config?.schedule) {
|
|
10435
10499
|
console.log("[EXULU] Creating ContextSource scheduler for", source.name, "in queue", queue.queue?.name);
|
|
10436
10500
|
await queue.queue?.upsertJobScheduler(source.id, {
|
|
10437
10501
|
pattern: source.config?.schedule
|