@exulu/backend 1.55.0 → 1.56.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/dist/index.cjs CHANGED
@@ -1253,7 +1253,7 @@ var hydrateVariables = async (tool5) => {
1253
1253
  await Promise.all(promises2);
1254
1254
  return tool5;
1255
1255
  };
1256
- var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExuluTools, configs, providerapikey, contexts, rerankers, user, exuluConfig, sessionID, req, project, sessionItems, model, agent) => {
1256
+ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExuluTools, configs, providerapikey, contexts, rerankers, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems) => {
1257
1257
  if (!currentTools) return {};
1258
1258
  if (!allExuluTools) {
1259
1259
  allExuluTools = [];
@@ -1309,7 +1309,8 @@ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExulu
1309
1309
  user,
1310
1310
  role: user?.role?.id,
1311
1311
  model,
1312
- preselectedItemIds: sessionItems
1312
+ preselected: sessionItems,
1313
+ memoryItems
1313
1314
  });
1314
1315
  if (agenticSearchTool) {
1315
1316
  const index = currentTools.findIndex((tool5) => tool5.id === "agentic_context_search");
@@ -1443,6 +1444,8 @@ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExulu
1443
1444
  ...inputs,
1444
1445
  model,
1445
1446
  sessionID,
1447
+ sessionItems,
1448
+ memory: memoryItems,
1446
1449
  req,
1447
1450
  // Convert config to object format if a config object
1448
1451
  // is available, after we added the .value property
@@ -1630,6 +1633,95 @@ var ExuluTool = class {
1630
1633
  };
1631
1634
  };
1632
1635
 
1636
+ // ee/agentic-retrieval/v3/classifier.ts
1637
+ var import_ai2 = require("ai");
1638
+ var import_zod5 = require("zod");
1639
+
1640
+ // src/utils/with-retry.ts
1641
+ async function withRetry(generateFn, maxRetries = 3) {
1642
+ let lastError;
1643
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1644
+ try {
1645
+ return await generateFn();
1646
+ } catch (error) {
1647
+ lastError = error;
1648
+ console.error(`[EXULU] generateText attempt ${attempt} failed:`, error);
1649
+ if (attempt === maxRetries) {
1650
+ throw error;
1651
+ }
1652
+ await new Promise((resolve3) => setTimeout(resolve3, Math.pow(2, attempt) * 1e3));
1653
+ }
1654
+ }
1655
+ throw lastError;
1656
+ }
1657
+
1658
+ // ee/agentic-retrieval/v3/classifier.ts
1659
+ async function classifyQuery(query, contexts, samples, model) {
1660
+ const contextDescriptions = contexts.map((ctx) => {
1661
+ const sample = samples.find((s) => s.contextId === ctx.id);
1662
+ const fieldList = sample?.fields.join(", ") ?? "name, external_id";
1663
+ return `
1664
+ <context>
1665
+ <id>
1666
+ ${ctx.id}
1667
+ </id>
1668
+ <name>
1669
+ ${ctx.name}
1670
+ </name>
1671
+ <description>
1672
+ ${ctx.description}
1673
+ </description>
1674
+ <fields>
1675
+ ${fieldList}
1676
+ </fields>
1677
+ <example_items>
1678
+ ${sample?.exampleItems.map((item) => JSON.stringify(item)).join("\n")}
1679
+ </example_items>
1680
+ </context>
1681
+ `;
1682
+ }).join("\n\n");
1683
+ const result = await withRetry(async () => {
1684
+ const result2 = await (0, import_ai2.generateText)({
1685
+ model,
1686
+ temperature: 0,
1687
+ output: import_ai2.Output.object({
1688
+ schema: import_zod5.z.object({
1689
+ queryType: import_zod5.z.enum(["aggregate", "list", "targeted", "exploratory"]).describe(
1690
+ "aggregate: ONLY use when the user explicitly asks to COUNT how many documents/items/tickets exist in the knowledge base (e.g. 'how many documents about X?', 'total number of tickets'). NEVER use for: real-world statistics stored in a document, intent statements, how-to questions, error/fault descriptions, configuration questions, or any query that does not explicitly ask for a count of knowledge base entries. When in doubt, choose targeted. list: user wants to enumerate matching items/documents (show me all, list documents about). targeted: use for almost everything \u2014 specific fact, answer, configuration, how-to, error/fault, feature/behavior question. Also use for intent statements and short commands describing a desired state (phrases that state what the user wants to do or achieve, even without an explicit question word). Real-world statistics stored in documents also go here. When in doubt, choose targeted over aggregate or exploratory. exploratory: only for broad conceptual questions needing multi-source synthesis (what is the process for Z, explain how X works, general overview of topic Y)."
1691
+ ),
1692
+ language: import_zod5.z.string().describe("ISO 639-3 language code of the query (e.g. eng, deu, fra)"),
1693
+ suggestedContextIds: import_zod5.z.array(import_zod5.z.enum(contexts.map((c) => c.id))).describe(
1694
+ "IDs of knowledge bases most likely to contain the answer. Return empty array to search all contexts."
1695
+ )
1696
+ })
1697
+ }),
1698
+ toolChoice: "none",
1699
+ system: `You are a query classifier for a multi-knowledge-base retrieval system.
1700
+ Classify the query and identify which knowledge bases are most relevant.
1701
+
1702
+ Available knowledge bases:
1703
+ ${contextDescriptions}
1704
+
1705
+ Guidelines for queryType:
1706
+ - Use "aggregate" ONLY when the query contains explicit counting language (e.g., "how many", "count", "total number", "wie viele"). Short statements, commands, or phrases without a question word are NEVER aggregate \u2014 classify them as targeted.
1707
+ - When in doubt between aggregate and targeted: always choose targeted.
1708
+
1709
+ Guidelines for suggestedContextIds:
1710
+ - Be conservative: only suggest contexts that are genuinely likely to contain the answer.
1711
+ Aim for 2\u20133 focused suggestions rather than listing everything.
1712
+ - Use each knowledge base's name and description (shown above) to judge relevance.
1713
+ - Return an empty array only if you truly cannot determine which contexts are relevant.`,
1714
+ prompt: `Query: ${query}`
1715
+ });
1716
+ return result2.output;
1717
+ }, 3);
1718
+ return result;
1719
+ }
1720
+
1721
+ // ee/agentic-retrieval/v3/tools.ts
1722
+ var import_zod6 = require("zod");
1723
+ var import_ai3 = require("ai");
1724
+
1633
1725
  // src/uppy/index.ts
1634
1726
  var import_express = require("express");
1635
1727
  var import_client_s32 = require("@aws-sdk/client-s3");
@@ -3481,6 +3573,45 @@ var promptFavoritesSchema = {
3481
3573
  }
3482
3574
  ]
3483
3575
  };
3576
+ var contextPresetsSchema = {
3577
+ type: "context_presets",
3578
+ name: {
3579
+ plural: "context_presets",
3580
+ singular: "context_preset"
3581
+ },
3582
+ RBAC: true,
3583
+ fields: [
3584
+ {
3585
+ name: "name",
3586
+ type: "text",
3587
+ required: true,
3588
+ index: true
3589
+ },
3590
+ {
3591
+ name: "description",
3592
+ type: "text"
3593
+ },
3594
+ {
3595
+ name: "preset_items",
3596
+ type: "json",
3597
+ required: true
3598
+ },
3599
+ {
3600
+ name: "tags",
3601
+ type: "json"
3602
+ },
3603
+ {
3604
+ name: "usage_count",
3605
+ type: "number",
3606
+ default: 0
3607
+ },
3608
+ {
3609
+ name: "favorite_count",
3610
+ type: "number",
3611
+ default: 0
3612
+ }
3613
+ ]
3614
+ };
3484
3615
  var addCoreFields = (schema) => {
3485
3616
  schema.fields.forEach((field) => {
3486
3617
  if (field.type === "file") {
@@ -3530,7 +3661,8 @@ var coreSchemas = {
3530
3661
  platformConfigurationsSchema: () => addCoreFields(platformConfigurationsSchema),
3531
3662
  promptLibrarySchema: () => addCoreFields(promptLibrarySchema),
3532
3663
  embedderSettingsSchema: () => addCoreFields(embedderSettingsSchema),
3533
- promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema)
3664
+ promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema),
3665
+ contextPresetsSchema: () => addCoreFields(contextPresetsSchema)
3534
3666
  };
3535
3667
  if (license["agent-feedback"]) {
3536
3668
  schemas.feedbackSchema = () => addCoreFields(feedbackSchema);
@@ -4572,7 +4704,7 @@ var ExuluContext2 = class {
4572
4704
  job: jobs.length > 0 ? jobs.join(",") : void 0
4573
4705
  };
4574
4706
  };
4575
- updateItem = async (item, config, user, role, generateEmbeddingsOverwrite) => {
4707
+ updateItem = async (item, config, user, role, generateEmbeddingsOverwrite, runProcessorOverwrite) => {
4576
4708
  console.log("[EXULU] updating item", item);
4577
4709
  const { db: db2 } = await postgresClient();
4578
4710
  if (item.field) {
@@ -4598,7 +4730,7 @@ var ExuluContext2 = class {
4598
4730
  let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always");
4599
4731
  if (this.processor) {
4600
4732
  const processor = this.processor;
4601
- if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
4733
+ if (processor && runProcessorOverwrite !== false && (runProcessorOverwrite || processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
4602
4734
  const { job: processorJob, result: processorResult } = await this.processField(
4603
4735
  "api",
4604
4736
  {
@@ -4864,120 +4996,7 @@ var ExuluContext2 = class {
4864
4996
  };
4865
4997
  };
4866
4998
 
4867
- // ee/agentic-retrieval/v3/context-sampler.ts
4868
- var CACHE_TTL_MS = 60 * 60 * 1e3;
4869
- var ContextSampler = class {
4870
- cache = /* @__PURE__ */ new Map();
4871
- async getSamples(contexts, user, role) {
4872
- return Promise.all(contexts.map((ctx) => this.getSample(ctx, user, role)));
4873
- }
4874
- async getSample(ctx, user, role) {
4875
- const cached = this.cache.get(ctx.id);
4876
- if (cached && Date.now() - cached.sampledAt < CACHE_TTL_MS) {
4877
- return cached;
4878
- }
4879
- const { db: db2 } = await postgresClient();
4880
- const tableName = getTableName(ctx.id);
4881
- const tableDefinition = convertContextToTableDefinition(ctx);
4882
- const customFieldNames = ctx.fields.map((f) => f.name);
4883
- const selectFields = ["id", "name", "external_id", ...customFieldNames];
4884
- let exampleItems = [];
4885
- try {
4886
- let query = db2(tableName).select(selectFields).whereNull("archived").limit(2);
4887
- query = applyAccessControl(tableDefinition, query, user, tableName);
4888
- exampleItems = await query;
4889
- } catch {
4890
- }
4891
- const sample = {
4892
- contextId: ctx.id,
4893
- contextName: ctx.name,
4894
- fields: ["name", "external_id", ...customFieldNames],
4895
- exampleItems,
4896
- sampledAt: Date.now()
4897
- };
4898
- this.cache.set(ctx.id, sample);
4899
- return sample;
4900
- }
4901
- /** Evict a context from cache so it's re-sampled on next use */
4902
- invalidate(contextId) {
4903
- this.cache.delete(contextId);
4904
- }
4905
- };
4906
-
4907
- // ee/agentic-retrieval/v3/classifier.ts
4908
- var import_ai2 = require("ai");
4909
- var import_zod5 = require("zod");
4910
-
4911
- // src/utils/with-retry.ts
4912
- async function withRetry(generateFn, maxRetries = 3) {
4913
- let lastError;
4914
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
4915
- try {
4916
- return await generateFn();
4917
- } catch (error) {
4918
- lastError = error;
4919
- console.error(`[EXULU] generateText attempt ${attempt} failed:`, error);
4920
- if (attempt === maxRetries) {
4921
- throw error;
4922
- }
4923
- await new Promise((resolve3) => setTimeout(resolve3, Math.pow(2, attempt) * 1e3));
4924
- }
4925
- }
4926
- throw lastError;
4927
- }
4928
-
4929
- // ee/agentic-retrieval/v3/classifier.ts
4930
- async function classifyQuery(query, contexts, samples, model) {
4931
- const contextDescriptions = contexts.map((ctx) => {
4932
- const sample = samples.find((s) => s.contextId === ctx.id);
4933
- const fieldList = sample?.fields.join(", ") ?? "name, external_id";
4934
- const exampleStr = sample?.exampleItems.length ? `
4935
- Example records: ${JSON.stringify(sample.exampleItems.slice(0, 2))}` : "";
4936
- return ` - ${ctx.id}: ${ctx.name}
4937
- Description: ${ctx.description}
4938
- Fields: ${fieldList}${exampleStr}`;
4939
- }).join("\n\n");
4940
- const result = await withRetry(async () => {
4941
- const result2 = await (0, import_ai2.generateText)({
4942
- model,
4943
- temperature: 0,
4944
- output: import_ai2.Output.object({
4945
- schema: import_zod5.z.object({
4946
- queryType: import_zod5.z.enum(["aggregate", "list", "targeted", "exploratory"]).describe(
4947
- "aggregate: ONLY use when the user explicitly asks to COUNT how many documents/items/tickets exist in the knowledge base (e.g. 'how many documents about X?', 'total number of tickets'). NEVER use for: real-world statistics stored in a document, intent statements, how-to questions, error/fault descriptions, configuration questions, or any query that does not explicitly ask for a count of knowledge base entries. When in doubt, choose targeted. list: user wants to enumerate matching items/documents (show me all, list documents about). targeted: use for almost everything \u2014 specific fact, answer, configuration, how-to, error/fault, feature/behavior question. Also use for intent statements and short commands describing a desired state (phrases that state what the user wants to do or achieve, even without an explicit question word). Real-world statistics stored in documents also go here. When in doubt, choose targeted over aggregate or exploratory. exploratory: only for broad conceptual questions needing multi-source synthesis (what is the process for Z, explain how X works, general overview of topic Y)."
4948
- ),
4949
- language: import_zod5.z.string().describe("ISO 639-3 language code of the query (e.g. eng, deu, fra)"),
4950
- suggestedContextIds: import_zod5.z.array(import_zod5.z.enum(contexts.map((c) => c.id))).describe(
4951
- "IDs of knowledge bases most likely to contain the answer. Return empty array to search all contexts."
4952
- )
4953
- })
4954
- }),
4955
- toolChoice: "none",
4956
- system: `You are a query classifier for a multi-knowledge-base retrieval system.
4957
- Classify the query and identify which knowledge bases are most relevant.
4958
-
4959
- Available knowledge bases:
4960
- ${contextDescriptions}
4961
-
4962
- Guidelines for queryType:
4963
- - Use "aggregate" ONLY when the query contains explicit counting language (e.g., "how many", "count", "total number", "wie viele"). Short statements, commands, or phrases without a question word are NEVER aggregate \u2014 classify them as targeted.
4964
- - When in doubt between aggregate and targeted: always choose targeted.
4965
-
4966
- Guidelines for suggestedContextIds:
4967
- - Be conservative: only suggest contexts that are genuinely likely to contain the answer.
4968
- Aim for 2\u20133 focused suggestions rather than listing everything.
4969
- - Use each knowledge base's name and description (shown above) to judge relevance.
4970
- - Return an empty array only if you truly cannot determine which contexts are relevant.`,
4971
- prompt: `Query: ${query}`
4972
- });
4973
- return result2.output;
4974
- }, 3);
4975
- return result;
4976
- }
4977
-
4978
4999
  // ee/agentic-retrieval/v3/tools.ts
4979
- var import_zod6 = require("zod");
4980
- var import_ai3 = require("ai");
4981
5000
  function buildContextEnum(contexts) {
4982
5001
  return import_zod6.z.array(import_zod6.z.enum(contexts.map((c) => c.id))).describe(
4983
5002
  contexts.map(
@@ -5019,7 +5038,7 @@ function parseGlobalItemIds(globalIds) {
5019
5038
  return map;
5020
5039
  }
5021
5040
  function createRetrievalTools(params) {
5022
- const { contexts, user, role, updateVirtualFiles, preselectedItemsByContext } = params;
5041
+ const { contexts, toolVariablesConfig, user, role, updateVirtualFiles, preselectedItemsByContext } = params;
5023
5042
  const ctxEnum = buildContextEnum(contexts);
5024
5043
  const count_items_or_chunks = (0, import_ai3.tool)({
5025
5044
  description: "Count items or chunks WITHOUT loading them into context. Use for 'how many', 'count', or 'total number of' queries.",
@@ -5152,7 +5171,7 @@ Use includeContent: true when you need the ACTUAL text to answer a question.
5152
5171
 
5153
5172
  For listing queries: always start with includeContent: false, then use dynamic tools to fetch specific pages.`,
5154
5173
  inputSchema: import_zod6.z.object({
5155
- query: import_zod6.z.string().describe("Search query about the content you're looking for"),
5174
+ userQuery: import_zod6.z.string().describe("The original unaltered question from the user"),
5156
5175
  knowledge_base_id: import_zod6.z.enum(contexts.map((c) => c.id)).describe(
5157
5176
  contexts.map(
5158
5177
  (c) => `<knowledge_base id="${c.id}" name="${c.name}">${c.description}</knowledge_base>`
@@ -5171,7 +5190,7 @@ For listing queries: always start with includeContent: false, then use dynamic t
5171
5190
  limit: import_zod6.z.number().default(20).describe("Max chunks with content (max 20). Without content, up to 200 are returned.")
5172
5191
  }),
5173
5192
  execute: async ({
5174
- query,
5193
+ userQuery,
5175
5194
  knowledge_base_id,
5176
5195
  keywords,
5177
5196
  searchMethod,
@@ -5182,7 +5201,8 @@ For listing queries: always start with includeContent: false, then use dynamic t
5182
5201
  limit
5183
5202
  }) => {
5184
5203
  const [ctx] = resolveContexts([knowledge_base_id], contexts);
5185
- const effectiveLimit = includeContent ? Math.min(limit ?? 20, 20) : Math.min((limit ?? 20) * 20, 400);
5204
+ const maxResults = toolVariablesConfig?.[`${ctx.id}_|_max_results`] || 20;
5205
+ const effectiveLimit = includeContent ? Math.min(limit ?? maxResults, maxResults) : Math.min((limit ?? maxResults) * maxResults, 400);
5186
5206
  const itemFilters = [];
5187
5207
  if (preselectedItemsByContext) {
5188
5208
  const contextItemIds = preselectedItemsByContext.get(knowledge_base_id);
@@ -5204,7 +5224,7 @@ For listing queries: always start with includeContent: false, then use dynamic t
5204
5224
  if (item_names)
5205
5225
  itemFilters.push({ name: { or: item_names.map((n) => ({ contains: n })) } });
5206
5226
  if (item_external_ids) itemFilters.push({ external_id: { in: item_external_ids } });
5207
- const effectiveQuery = query || keywords?.join(" ") || "";
5227
+ const effectiveQuery = userQuery || keywords?.join(" ") || "";
5208
5228
  let method = mapSearchMethod(searchMethod ?? "hybrid");
5209
5229
  if (method === "hybridSearch" || method === "cosineDistance") {
5210
5230
  if (!ctx.embedder) {
@@ -5212,6 +5232,7 @@ For listing queries: always start with includeContent: false, then use dynamic t
5212
5232
  method = "tsvector";
5213
5233
  }
5214
5234
  }
5235
+ const expandChunks = toolVariablesConfig?.[`${ctx.id}_|_expand_chunks`] || 0;
5215
5236
  try {
5216
5237
  const { chunks } = await ctx.search({
5217
5238
  query: effectiveQuery,
@@ -5224,7 +5245,11 @@ For listing queries: always start with includeContent: false, then use dynamic t
5224
5245
  sort: { field: "updatedAt", direction: "desc" },
5225
5246
  user,
5226
5247
  role,
5227
- trigger: "tool"
5248
+ trigger: "tool",
5249
+ expand: expandChunks > 0 ? {
5250
+ before: expandChunks,
5251
+ after: expandChunks
5252
+ } : void 0
5228
5253
  });
5229
5254
  return JSON.stringify(
5230
5255
  chunks.map(
@@ -5996,6 +6021,46 @@ var TrajectoryLogger = class {
5996
6021
  }
5997
6022
  };
5998
6023
 
6024
+ // ee/agentic-retrieval/v3/context-sampler.ts
6025
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
6026
+ var ContextSampler = class {
6027
+ cache = /* @__PURE__ */ new Map();
6028
+ async getSamples(contexts, user, role) {
6029
+ return Promise.all(contexts.map((ctx) => this.getSample(ctx, user, role)));
6030
+ }
6031
+ async getSample(ctx, user, role) {
6032
+ const cached = this.cache.get(ctx.id);
6033
+ if (cached && Date.now() - cached.sampledAt < CACHE_TTL_MS) {
6034
+ return cached;
6035
+ }
6036
+ const { db: db2 } = await postgresClient();
6037
+ const tableName = getTableName(ctx.id);
6038
+ const tableDefinition = convertContextToTableDefinition(ctx);
6039
+ const customFieldNames = ctx.fields.map((f) => f.name);
6040
+ const selectFields = ["id", "name", "external_id", ...customFieldNames];
6041
+ let exampleItems = [];
6042
+ try {
6043
+ let query = db2(tableName).select(selectFields).whereNull("archived").limit(2);
6044
+ query = applyAccessControl(tableDefinition, query, user, tableName);
6045
+ exampleItems = await query;
6046
+ } catch {
6047
+ }
6048
+ const sample = {
6049
+ contextId: ctx.id,
6050
+ contextName: ctx.name,
6051
+ fields: ["name", "external_id", ...customFieldNames],
6052
+ exampleItems,
6053
+ sampledAt: Date.now()
6054
+ };
6055
+ this.cache.set(ctx.id, sample);
6056
+ return sample;
6057
+ }
6058
+ /** Evict a context from cache so it's re-sampled on next use */
6059
+ invalidate(contextId) {
6060
+ this.cache.delete(contextId);
6061
+ }
6062
+ };
6063
+
5999
6064
  // ee/agentic-retrieval/v3/index.ts
6000
6065
  var sampler = new ContextSampler();
6001
6066
  async function* executeV3({
@@ -6003,6 +6068,7 @@ async function* executeV3({
6003
6068
  contexts,
6004
6069
  reranker,
6005
6070
  model,
6071
+ toolVariablesConfig,
6006
6072
  user,
6007
6073
  role,
6008
6074
  customInstructions,
@@ -6028,15 +6094,39 @@ async function* executeV3({
6028
6094
  }
6029
6095
  console.log("[EXULU] v3 \u2014 classified as:", classification);
6030
6096
  const strategy = STRATEGIES[classification.queryType];
6097
+ const contextSpecificInstructions = activeContexts.map((ctx) => {
6098
+ const instructions = toolVariablesConfig?.[`${ctx.id}_|_instructions`] ?? "";
6099
+ if (instructions) {
6100
+ return `
6101
+ <${ctx.id}>
6102
+ ${instructions}
6103
+ </${ctx.id}>
6104
+ `;
6105
+ } else {
6106
+ return null;
6107
+ }
6108
+ }).filter(Boolean).join("\n");
6031
6109
  const suggestedIds = classification.suggestedContextIds;
6032
6110
  const fallbackIds = activeContexts.filter((c) => !suggestedIds.includes(c.id)).map((c) => c.id);
6033
- const contextBase = suggestedIds.length > 0 ? `Suggested priority contexts: [${suggestedIds.join(", ")}]. Also available: [${fallbackIds.join(", ")}]. Custom instructions may require searching additional or all contexts \u2014 follow them.` : `All contexts available: [${activeContexts.map((c) => c.id).join(", ")}].`;
6111
+ let contextBase = suggestedIds.length > 0 ? `
6112
+ Suggested priority contexts: [${suggestedIds.join(", ")}].
6113
+
6114
+ Also available: [${fallbackIds.join(", ")}].
6115
+
6116
+ Custom instructions may require searching additional or all contexts \u2014 follow them.` : `All contexts available: [${activeContexts.map((c) => c.id).join(", ")}].`;
6034
6117
  const preselectedNote = preselectedByContext?.size ? `
6035
6118
  SCOPE CONSTRAINT: Retrieval is scoped to preselected items/contexts. Per context: ${[...preselectedByContext.entries()].map(([ctx, ids]) => ids === null ? `${ctx} (full context)` : `${ctx} (${ids.length} item${ids.length === 1 ? "" : "s"})`).join(", ")}. All tools enforce this scope automatically. For full-context entries you may search freely; for item-restricted entries do NOT use search_items_by_name for discovery \u2014 go directly to search_content or save_search_results.` : "";
6119
+ if (contextSpecificInstructions?.length) {
6120
+ contextBase += `
6121
+ Context specific instructions:
6122
+ ${contextSpecificInstructions}
6123
+ `;
6124
+ }
6036
6125
  const contextGuidance = contextBase + preselectedNote;
6037
6126
  const bashToolkit = await (0, import_bash_tool.createBashTool)({ files: {} });
6038
6127
  const retrievalTools = createRetrievalTools({
6039
6128
  contexts: activeContexts,
6129
+ toolVariablesConfig,
6040
6130
  user,
6041
6131
  role,
6042
6132
  updateVirtualFiles: (files) => bashToolkit.sandbox.writeFiles(files),
@@ -6091,7 +6181,8 @@ function createAgenticRetrievalToolV3({
6091
6181
  user,
6092
6182
  role,
6093
6183
  model,
6094
- preselectedItemIds
6184
+ preselected,
6185
+ memoryItems
6095
6186
  }) {
6096
6187
  const license = checkLicense();
6097
6188
  if (!license["agentic-retrieval"]) {
@@ -6144,27 +6235,57 @@ function createAgenticRetrievalToolV3({
6144
6235
  default: false
6145
6236
  },
6146
6237
  {
6147
- name: "log_trajectories",
6238
+ name: "logging",
6148
6239
  description: "Save a detailed markdown + JSON log of every retrieval execution to disk. Useful for debugging and evaluation.",
6149
6240
  type: "boolean",
6150
6241
  default: false
6151
6242
  },
6152
6243
  ...contexts.map((ctx) => ({
6153
- name: ctx.id,
6244
+ name: ctx.id + "_|_enabled",
6154
6245
  description: `Enable search in "${ctx.name}". ${ctx.description}`,
6155
6246
  type: "boolean",
6156
6247
  default: true
6248
+ })),
6249
+ ...contexts.map((ctx) => ({
6250
+ name: `${ctx.id}_|_instructions`,
6251
+ description: `Instructions for the retrieval agent about how to search in the ${ctx.name} context`,
6252
+ type: "string",
6253
+ default: ""
6254
+ })),
6255
+ ...contexts.map((ctx) => ({
6256
+ name: `${ctx.id}_|_priority`,
6257
+ description: `Defines in which order the context should be searched in, the higher the number the higher the priority, if contexts have the same priority they are searched in parallel`,
6258
+ type: "number",
6259
+ default: 0
6260
+ })),
6261
+ ...contexts.map((ctx) => ({
6262
+ name: `${ctx.id}_|_max_results`,
6263
+ description: `Defines the maximum number of results to return for the ${ctx.name} context`,
6264
+ type: "number",
6265
+ default: 0
6266
+ })),
6267
+ ...contexts.map((ctx) => ({
6268
+ name: `${ctx.id}_|_max_steps`,
6269
+ description: `Defines the maximum number of steps the agent is allowed to take when searching the ${ctx.name} context`,
6270
+ type: "number",
6271
+ default: 0
6272
+ })),
6273
+ ...contexts.map((ctx) => ({
6274
+ name: `${ctx.id}_|_expand_chunks`,
6275
+ description: `Defines if the agent automatically retrieves nearby chunks around the matched chunks, usefull if relevant content might be split up`,
6276
+ type: "number",
6277
+ default: 0
6157
6278
  }))
6158
6279
  ],
6159
6280
  inputSchema: import_zod9.z.object({
6160
- query: import_zod9.z.string().describe("The question or query to answer"),
6281
+ userQuery: import_zod9.z.string().describe("The original unaltered question from the user"),
6161
6282
  userInstructions: import_zod9.z.string().optional().describe("Additional instructions from the user to guide retrieval"),
6162
6283
  confirmedContextIds: import_zod9.z.array(import_zod9.z.string()).optional().describe(
6163
6284
  "Knowledge base IDs explicitly confirmed by the user to be used in the retrieval. When presen only searches these contexts. "
6164
6285
  )
6165
6286
  }),
6166
6287
  execute: async function* ({
6167
- query,
6288
+ userQuery,
6168
6289
  userInstructions,
6169
6290
  confirmedContextIds,
6170
6291
  toolVariablesConfig,
@@ -6182,10 +6303,10 @@ function createAgenticRetrievalToolV3({
6182
6303
  let managedContextEnabled = false;
6183
6304
  if (toolVariablesConfig) {
6184
6305
  configInstructions = toolVariablesConfig["instructions"] ?? "";
6185
- logTrajectory = toolVariablesConfig["log_trajectories"] === true || toolVariablesConfig["log_trajectories"] === "true";
6306
+ logTrajectory = toolVariablesConfig["logging"] === true || toolVariablesConfig["logging"] === "true";
6186
6307
  managedContextEnabled = toolVariablesConfig["managed_context"] === true || toolVariablesConfig["managed_context"] === "true";
6187
6308
  activeContexts = contexts.filter(
6188
- (ctx) => toolVariablesConfig[ctx.id] === true || toolVariablesConfig[ctx.id] === "true" || toolVariablesConfig[ctx.id] === 1
6309
+ (ctx) => toolVariablesConfig[ctx.id + "_|_enabled"] === true || toolVariablesConfig[ctx.id + "_|_enabled"] === "true" || toolVariablesConfig[ctx.id + "_|_enabled"] === 1
6189
6310
  );
6190
6311
  if (activeContexts.length === 0) activeContexts = contexts;
6191
6312
  requiresPreselectedContexts = toolVariablesConfig["require_preselected_contexts"] === true || toolVariablesConfig["require_preselected_contexts"] === "true";
@@ -6195,13 +6316,13 @@ function createAgenticRetrievalToolV3({
6195
6316
  }
6196
6317
  }
6197
6318
  console.log("[EXULU] Managed context enabled:", managedContextEnabled);
6198
- console.log("[EXULU] Preselected item IDs:", preselectedItemIds);
6199
- if (managedContextEnabled && !preselectedItemIds?.length) {
6319
+ console.log("[EXULU] Preselected item IDs:", preselected);
6320
+ if (managedContextEnabled && !preselected?.length) {
6200
6321
  console.log("[EXULU] Managed context was enabled for the agentic retrieval tool. This means that the user must preselect items that the agentic retrieval tool will search in, please notify the user to preselect items before executing the tool.");
6201
6322
  yield { result: "Managed context was enabled for the agentic retrieval tool. This means that the user must preselect items that the agentic retrieval tool will search in, please notify the user to preselect items before executing the tool." };
6202
6323
  return;
6203
6324
  }
6204
- if (requiresPreselectedContexts && !confirmedContextIds?.length && !preselectedItemIds?.length) {
6325
+ if (requiresPreselectedContexts && !confirmedContextIds?.length && !preselected?.length) {
6205
6326
  console.log("[EXULU] The user must choose between the available contexts before executing the tool. The available contexts are: " + activeContexts.map((c) => c.id).join(", ") + ". If the question_ask tool is available use that to ask the user which contexts they want to search in, otherwise just ask them in plain text.");
6206
6327
  yield { result: "The user must choose between the available contexts before executing the tool, the available contexts are: " + activeContexts.map((c) => c.id).join(", ") + ". If the question_ask tool is available use that to ask the user which contexts they want to search in, otherwise just ask them in plain text." };
6207
6328
  return;
@@ -6212,21 +6333,43 @@ function createAgenticRetrievalToolV3({
6212
6333
  if (filtered.length > 0) activeContexts = filtered;
6213
6334
  }
6214
6335
  const combinedInstructions = [
6215
- configInstructions ? `Configuration instructions: ${configInstructions}` : "",
6216
- adminInstructions ? `Admin instructions: ${adminInstructions}` : "",
6217
- userInstructions ? `User instructions: ${userInstructions}` : ""
6336
+ configInstructions ? `
6337
+ Configuration instructions:
6338
+ <configuration_instructions>
6339
+ ${configInstructions}
6340
+ </configuration_instructions>
6341
+ ` : "",
6342
+ adminInstructions ? `
6343
+ Admin instructions:
6344
+ <admin_instructions>
6345
+ ${adminInstructions}
6346
+ </admin_instructions>
6347
+ ` : "",
6348
+ userInstructions ? `
6349
+ User instructions:
6350
+ <user_instructions>
6351
+ ${userInstructions}
6352
+ </user_instructions>
6353
+ ` : "",
6354
+ memoryItems ? `
6355
+ Relevant memories (these are items that the agent has retrieved from the memory context and are relevant to the query):
6356
+ <relevant_memories>
6357
+ ${memoryItems?.map((item) => JSON.stringify(item)).join("\n")}
6358
+ </relevant_memories>
6359
+ ` : ""
6218
6360
  ].filter(Boolean).join("\n");
6219
6361
  for await (const output of executeV3({
6220
- query,
6362
+ query: userQuery,
6221
6363
  contexts: activeContexts,
6222
6364
  reranker: configuredReranker,
6365
+ toolVariablesConfig,
6223
6366
  model,
6224
6367
  user,
6225
6368
  role,
6226
6369
  customInstructions: combinedInstructions || void 0,
6227
6370
  logTrajectory,
6228
6371
  sessionId: sessionID,
6229
- preselectedItemIds
6372
+ preselectedItemIds: preselected
6230
6373
  })) {
6231
6374
  yield { result: JSON.stringify(output) };
6232
6375
  }
@@ -11046,6 +11189,7 @@ var ExuluProvider = class {
11046
11189
  });
11047
11190
  }
11048
11191
  let memoryContext = "";
11192
+ let memoryItems;
11049
11193
  if (agent?.memory && contexts?.length && query) {
11050
11194
  const context = contexts.find((context2) => context2.id === agent?.memory);
11051
11195
  if (!context) {
@@ -11068,6 +11212,7 @@ var ExuluProvider = class {
11068
11212
  page: 1
11069
11213
  });
11070
11214
  if (result?.chunks?.length) {
11215
+ memoryItems = result.chunks;
11071
11216
  memoryContext = `
11072
11217
  Pre-fetched relevant information for this query:
11073
11218
 
@@ -11188,7 +11333,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
11188
11333
  project,
11189
11334
  sessionItems,
11190
11335
  model,
11191
- agent
11336
+ agent,
11337
+ memoryItems
11192
11338
  ),
11193
11339
  stopWhen: [(0, import_ai8.stepCountIs)(maxStepCount || 5)]
11194
11340
  // make configurable
@@ -11265,7 +11411,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
11265
11411
  project,
11266
11412
  sessionItems,
11267
11413
  model,
11268
- agent
11414
+ agent,
11415
+ memoryItems
11269
11416
  ),
11270
11417
  stopWhen: [(0, import_ai8.stepCountIs)(maxStepCount || 5)]
11271
11418
  });
@@ -11447,6 +11594,7 @@ ${extractedText}
11447
11594
  });
11448
11595
  }
11449
11596
  let memoryContext = "";
11597
+ let memoryItems;
11450
11598
  if (agent?.memory && contexts?.length && query) {
11451
11599
  const context = contexts.find((context2) => context2.id === agent?.memory);
11452
11600
  if (!context) {
@@ -11470,6 +11618,7 @@ ${extractedText}
11470
11618
  });
11471
11619
  import_fs.default.writeFileSync("pre-fetched-relevant-information.json", JSON.stringify(result2, null, 2));
11472
11620
  if (result2?.chunks?.length) {
11621
+ memoryItems = result2.chunks;
11473
11622
  memoryContext = `
11474
11623
  <pre-fetched relevant information for this query>:
11475
11624
 
@@ -11489,9 +11638,6 @@ ${extractedText}
11489
11638
  const genericContext = "IMPORTANT: \n\n The current date is " + (/* @__PURE__ */ new Date()).toLocaleDateString() + " and the current time is " + (/* @__PURE__ */ new Date()).toLocaleTimeString() + ". If the user does not explicitly provide the current date, for examle when saying ' this weekend', you should assume they are talking with the current date in mind as a reference.";
11490
11639
  let system = instructions || "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.";
11491
11640
  system += "\n\n" + genericContext;
11492
- if (memoryContext) {
11493
- system += "\n\n" + memoryContext;
11494
- }
11495
11641
  const includesContextSearchTool = currentTools?.some(
11496
11642
  (tool5) => tool5.name.toLowerCase().includes("context_search") || tool5.id.includes("context_search") || tool5.type === "context"
11497
11643
  );
@@ -11576,7 +11722,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
11576
11722
  project,
11577
11723
  sessionItems,
11578
11724
  model,
11579
- agent
11725
+ agent,
11726
+ memoryItems
11580
11727
  ),
11581
11728
  onError: (error) => {
11582
11729
  console.error("[EXULU] chat stream error.", error);
@@ -12169,6 +12316,7 @@ var {
12169
12316
  workflowTemplatesSchema: workflowTemplatesSchema2,
12170
12317
  rbacSchema: rbacSchema2,
12171
12318
  promptLibrarySchema: promptLibrarySchema2,
12319
+ contextPresetsSchema: contextPresetsSchema2,
12172
12320
  embedderSettingsSchema: embedderSettingsSchema2,
12173
12321
  promptFavoritesSchema: promptFavoritesSchema2,
12174
12322
  statisticsSchema: statisticsSchema2
@@ -12216,6 +12364,7 @@ var createExpressRoutes = async (app, providers, tools, contexts, config, evals,
12216
12364
  projectsSchema2(),
12217
12365
  jobResultsSchema2(),
12218
12366
  promptLibrarySchema2(),
12367
+ contextPresetsSchema2(),
12219
12368
  embedderSettingsSchema2(),
12220
12369
  promptFavoritesSchema2(),
12221
12370
  evalRunsSchema2(),
@@ -17282,6 +17431,7 @@ var {
17282
17431
  projectsSchema: projectsSchema3,
17283
17432
  jobResultsSchema: jobResultsSchema3,
17284
17433
  promptLibrarySchema: promptLibrarySchema3,
17434
+ contextPresetsSchema: contextPresetsSchema3,
17285
17435
  embedderSettingsSchema: embedderSettingsSchema3,
17286
17436
  promptFavoritesSchema: promptFavoritesSchema3
17287
17437
  } = coreSchemas.get();
@@ -17319,6 +17469,7 @@ var up = async function(knex) {
17319
17469
  projectsSchema3(),
17320
17470
  jobResultsSchema3(),
17321
17471
  promptLibrarySchema3(),
17472
+ contextPresetsSchema3(),
17322
17473
  embedderSettingsSchema3(),
17323
17474
  promptFavoritesSchema3(),
17324
17475
  rbacSchema3(),