@exulu/backend 1.66.0 → 1.68.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/ee/workers.ts CHANGED
@@ -7,12 +7,12 @@ import { ExuluStorage } from "@SRC/exulu/storage.ts";
7
7
  import type { ExuluAgent } from "@EXULU_TYPES/models/agent.ts";
8
8
  import type { ExuluQueueConfig } from "@EXULU_TYPES/queue-config.ts";
9
9
  import { getTableName, type ExuluContext } from "@SRC/exulu/context.ts";
10
- import type { ExuluReranker } from "@SRC/exulu/reranker.ts";
11
10
  import type { ExuluEval } from "@SRC/exulu/evals.ts";
12
11
  import type { ExuluTool } from "@SRC/exulu/tool.ts";
13
12
  import { resolveModel } from "@SRC/exulu/resolve-model.ts";
14
13
  import { postgresClient } from "@SRC/postgres/client";
15
14
  import type { BullMqJobData } from "@EE/queues/decorator.ts";
15
+ import { maybePruneJobResults } from "@EE/queues/prune-job-results.ts";
16
16
  import { type Tracer } from "@opentelemetry/api";
17
17
  import { v4 as uuidv4 } from "uuid";
18
18
  import { type UIMessage } from "ai";
@@ -114,7 +114,6 @@ export const createWorkers = async (
114
114
  queues: ExuluQueueConfig[],
115
115
  config: ExuluConfig,
116
116
  contexts: ExuluContext[],
117
- rerankers: ExuluReranker[],
118
117
  evals: ExuluEval[],
119
118
  tools: ExuluTool[],
120
119
  tracer?: Tracer,
@@ -275,13 +274,7 @@ export const createWorkers = async (
275
274
 
276
275
  const label = `embedder-${bullmqJob.name}`;
277
276
 
278
- await db.from("job_results").insert({
279
- job_id: bullmqJob.id,
280
- label: label,
281
- state: await bullmqJob.getState(),
282
- result: null,
283
- metadata: {},
284
- });
277
+ await upsertJobStart(db, bullmqJob, label, "embedder");
285
278
 
286
279
  const context = contexts.find((context) => context.id === data.context);
287
280
 
@@ -289,14 +282,8 @@ export const createWorkers = async (
289
282
  throw new Error(`Context ${data.context} not found in the registry.`);
290
283
  }
291
284
 
292
- if (!data.embedder) {
293
- throw new Error(`No embedder set for embedder job.`);
294
- }
295
-
296
- const embedder = contexts.find((context) => context.embedder?.id === data.embedder);
297
-
298
- if (!embedder) {
299
- throw new Error(`Embedder ${data.embedder} not found in the registry.`);
285
+ if (!context.embedder) {
286
+ throw new Error(`No embedder configured for context ${data.context}.`);
300
287
  }
301
288
 
302
289
  const result = await context.createAndUpsertEmbeddings(
@@ -304,7 +291,7 @@ export const createWorkers = async (
304
291
  config,
305
292
  data.user,
306
293
  {
307
- label: embedder.name,
294
+ label: context.embedder.model,
308
295
  trigger: data.trigger,
309
296
  },
310
297
  data.role,
@@ -331,13 +318,7 @@ export const createWorkers = async (
331
318
 
332
319
  const label = `processor-${bullmqJob.name}`;
333
320
 
334
- await db.from("job_results").insert({
335
- job_id: bullmqJob.id,
336
- label: label,
337
- state: await bullmqJob.getState(),
338
- result: null,
339
- metadata: {},
340
- });
321
+ await upsertJobStart(db, bullmqJob, label, "processor");
341
322
 
342
323
  const context = contexts.find((context) => context.id === data.context);
343
324
 
@@ -502,6 +483,7 @@ export const createWorkers = async (
502
483
  agent,
503
484
  provider,
504
485
  user,
486
+ workflow,
505
487
  messages: inputMessages,
506
488
  } = await validateWorkflowPayload(data, providers);
507
489
 
@@ -530,11 +512,12 @@ export const createWorkers = async (
530
512
  provider,
531
513
  inputMessages,
532
514
  contexts,
533
- rerankers,
534
515
  user,
535
516
  tools,
536
517
  config,
537
518
  variables: data.inputs,
519
+ // Tag LLM spend to this routine (cron + ad-hoc share this path).
520
+ routine: { id: workflow.id, name: workflow.name },
538
521
  });
539
522
  resolve(messages);
540
523
  break;
@@ -631,7 +614,6 @@ export const createWorkers = async (
631
614
  provider,
632
615
  inputMessages,
633
616
  contexts,
634
- rerankers,
635
617
  user,
636
618
  tools,
637
619
  config,
@@ -1021,6 +1003,9 @@ export const createWorkers = async (
1021
1003
  result: returnvalue.result != null ? JSON.stringify(returnvalue.result) : null,
1022
1004
  metadata: returnvalue.metadata != null ? JSON.stringify(returnvalue.metadata) : null,
1023
1005
  });
1006
+
1007
+ // Cap the table as rows become terminal (every Nth, idempotent).
1008
+ void maybePruneJobResults(db);
1024
1009
  },
1025
1010
  );
1026
1011
 
@@ -1034,6 +1019,9 @@ export const createWorkers = async (
1034
1019
  state: JOB_STATUS_ENUM.failed,
1035
1020
  error,
1036
1021
  });
1022
+
1023
+ // Cap the table as rows become terminal (every Nth, idempotent).
1024
+ void maybePruneJobResults(db);
1037
1025
  return;
1038
1026
  }
1039
1027
  console.error(
@@ -1326,22 +1314,29 @@ export const processUiMessagesFlow = async ({
1326
1314
  provider,
1327
1315
  inputMessages,
1328
1316
  contexts,
1329
- rerankers,
1330
1317
  user,
1331
1318
  tools,
1332
1319
  config,
1333
1320
  variables,
1321
+ routine,
1334
1322
  }: {
1335
1323
  providers: ExuluProvider[];
1336
1324
  agent: ExuluAgent;
1337
1325
  provider: ExuluProvider;
1338
1326
  inputMessages: UIMessage[];
1339
1327
  contexts: ExuluContext[];
1340
- rerankers: ExuluReranker[];
1341
1328
  user: User;
1342
1329
  tools: ExuluTool[];
1343
1330
  config: ExuluConfig;
1344
1331
  variables?: Record<string, any>;
1332
+ /**
1333
+ * Set when this flow is invoked from a workflow_template run (one-shot via
1334
+ * runWorkflow or cron via upsertWorkflowSchedule). Forwarded to resolveModel
1335
+ * so buildTags() emits routine_id_/routine_name_ alongside user/agent tags
1336
+ * for /analytics + /admin/budgets attribution. /chat and /openai-gateway
1337
+ * callers leave this undefined — they have no routine context.
1338
+ */
1339
+ routine?: { id: string; name: string };
1345
1340
  }): Promise<{
1346
1341
  messages: UIMessage[];
1347
1342
  metadata: {
@@ -1369,7 +1364,6 @@ export const processUiMessagesFlow = async ({
1369
1364
  agent,
1370
1365
  tools,
1371
1366
  contexts,
1372
- rerankers,
1373
1367
  disabledTools,
1374
1368
  providers,
1375
1369
  user,
@@ -1390,7 +1384,8 @@ export const processUiMessagesFlow = async ({
1390
1384
  modelId: agent.model,
1391
1385
  user,
1392
1386
  providers,
1393
- agent: agent
1387
+ agent: agent,
1388
+ routine,
1394
1389
  });
1395
1390
  const providerapikey = resolved.apiKey;
1396
1391
  const resolvedLanguageModel = resolved.languageModel;
@@ -1487,7 +1482,6 @@ export const processUiMessagesFlow = async ({
1487
1482
  try {
1488
1483
  const result = await provider.generateStream({
1489
1484
  contexts,
1490
- rerankers,
1491
1485
  agent: agent,
1492
1486
  user,
1493
1487
  approvedTools: tools.map((tool) => "tool-" + sanitizeToolName(tool.name)),
@@ -1632,3 +1626,36 @@ function getAverage(arr: number[]): number {
1632
1626
  if (arr.length === 0) return 0; // Handle empty array
1633
1627
  return arr.reduce((a, b) => a + b, 0) / arr.length;
1634
1628
  }
1629
+
1630
+ // KB-7: at worker pickup, advance the enqueue-time job_results row (state
1631
+ // "waiting", written by the queue decorator) to the live state instead of
1632
+ // inserting a duplicate. Falls back to an insert for jobs enqueued before
1633
+ // this change. Used by the processor + embedder handlers; the completed/
1634
+ // failed worker events then drive the same row to its terminal state.
1635
+ async function upsertJobStart(
1636
+ db: any,
1637
+ bullmqJob: { id?: string; data?: any; getState: () => Promise<string> },
1638
+ label: string,
1639
+ fallbackType: string,
1640
+ ): Promise<void> {
1641
+ const state = await bullmqJob.getState();
1642
+ const rawItem = bullmqJob.data?.item;
1643
+ const itemId =
1644
+ rawItem == null ? null : typeof rawItem === "object" ? (rawItem.id ?? null) : rawItem;
1645
+ const updated = await db
1646
+ .from("job_results")
1647
+ .where({ job_id: bullmqJob.id })
1648
+ .update({ label, state });
1649
+ if (!updated) {
1650
+ await db.from("job_results").insert({
1651
+ job_id: bullmqJob.id,
1652
+ label,
1653
+ state,
1654
+ result: null,
1655
+ metadata: {},
1656
+ type: bullmqJob.data?.type ?? fallbackType,
1657
+ item: itemId == null ? null : String(itemId),
1658
+ context: bullmqJob.data?.context ? String(bullmqJob.data.context) : null,
1659
+ });
1660
+ }
1661
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "1.66.0",
4
+ "version": "1.68.0",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {