@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.js CHANGED
@@ -1193,7 +1193,7 @@ var hydrateVariables = async (tool5) => {
1193
1193
  await Promise.all(promises2);
1194
1194
  return tool5;
1195
1195
  };
1196
- var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExuluTools, configs, providerapikey, contexts, rerankers, user, exuluConfig, sessionID, req, project, sessionItems, model, agent) => {
1196
+ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExuluTools, configs, providerapikey, contexts, rerankers, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems) => {
1197
1197
  if (!currentTools) return {};
1198
1198
  if (!allExuluTools) {
1199
1199
  allExuluTools = [];
@@ -1249,7 +1249,8 @@ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExulu
1249
1249
  user,
1250
1250
  role: user?.role?.id,
1251
1251
  model,
1252
- preselectedItemIds: sessionItems
1252
+ preselected: sessionItems,
1253
+ memoryItems
1253
1254
  });
1254
1255
  if (agenticSearchTool) {
1255
1256
  const index = currentTools.findIndex((tool5) => tool5.id === "agentic_context_search");
@@ -1383,6 +1384,8 @@ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExulu
1383
1384
  ...inputs,
1384
1385
  model,
1385
1386
  sessionID,
1387
+ sessionItems,
1388
+ memory: memoryItems,
1386
1389
  req,
1387
1390
  // Convert config to object format if a config object
1388
1391
  // is available, after we added the .value property
@@ -1570,6 +1573,95 @@ var ExuluTool = class {
1570
1573
  };
1571
1574
  };
1572
1575
 
1576
+ // ee/agentic-retrieval/v3/classifier.ts
1577
+ import { generateText, Output } from "ai";
1578
+ import { z as z5 } from "zod";
1579
+
1580
+ // src/utils/with-retry.ts
1581
+ async function withRetry(generateFn, maxRetries = 3) {
1582
+ let lastError;
1583
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1584
+ try {
1585
+ return await generateFn();
1586
+ } catch (error) {
1587
+ lastError = error;
1588
+ console.error(`[EXULU] generateText attempt ${attempt} failed:`, error);
1589
+ if (attempt === maxRetries) {
1590
+ throw error;
1591
+ }
1592
+ await new Promise((resolve3) => setTimeout(resolve3, Math.pow(2, attempt) * 1e3));
1593
+ }
1594
+ }
1595
+ throw lastError;
1596
+ }
1597
+
1598
+ // ee/agentic-retrieval/v3/classifier.ts
1599
+ async function classifyQuery(query, contexts, samples, model) {
1600
+ const contextDescriptions = contexts.map((ctx) => {
1601
+ const sample = samples.find((s) => s.contextId === ctx.id);
1602
+ const fieldList = sample?.fields.join(", ") ?? "name, external_id";
1603
+ return `
1604
+ <context>
1605
+ <id>
1606
+ ${ctx.id}
1607
+ </id>
1608
+ <name>
1609
+ ${ctx.name}
1610
+ </name>
1611
+ <description>
1612
+ ${ctx.description}
1613
+ </description>
1614
+ <fields>
1615
+ ${fieldList}
1616
+ </fields>
1617
+ <example_items>
1618
+ ${sample?.exampleItems.map((item) => JSON.stringify(item)).join("\n")}
1619
+ </example_items>
1620
+ </context>
1621
+ `;
1622
+ }).join("\n\n");
1623
+ const result = await withRetry(async () => {
1624
+ const result2 = await generateText({
1625
+ model,
1626
+ temperature: 0,
1627
+ output: Output.object({
1628
+ schema: z5.object({
1629
+ queryType: z5.enum(["aggregate", "list", "targeted", "exploratory"]).describe(
1630
+ "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)."
1631
+ ),
1632
+ language: z5.string().describe("ISO 639-3 language code of the query (e.g. eng, deu, fra)"),
1633
+ suggestedContextIds: z5.array(z5.enum(contexts.map((c) => c.id))).describe(
1634
+ "IDs of knowledge bases most likely to contain the answer. Return empty array to search all contexts."
1635
+ )
1636
+ })
1637
+ }),
1638
+ toolChoice: "none",
1639
+ system: `You are a query classifier for a multi-knowledge-base retrieval system.
1640
+ Classify the query and identify which knowledge bases are most relevant.
1641
+
1642
+ Available knowledge bases:
1643
+ ${contextDescriptions}
1644
+
1645
+ Guidelines for queryType:
1646
+ - 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.
1647
+ - When in doubt between aggregate and targeted: always choose targeted.
1648
+
1649
+ Guidelines for suggestedContextIds:
1650
+ - Be conservative: only suggest contexts that are genuinely likely to contain the answer.
1651
+ Aim for 2\u20133 focused suggestions rather than listing everything.
1652
+ - Use each knowledge base's name and description (shown above) to judge relevance.
1653
+ - Return an empty array only if you truly cannot determine which contexts are relevant.`,
1654
+ prompt: `Query: ${query}`
1655
+ });
1656
+ return result2.output;
1657
+ }, 3);
1658
+ return result;
1659
+ }
1660
+
1661
+ // ee/agentic-retrieval/v3/tools.ts
1662
+ import { z as z6 } from "zod";
1663
+ import { tool as tool2 } from "ai";
1664
+
1573
1665
  // src/uppy/index.ts
1574
1666
  import "express";
1575
1667
  import {
@@ -3434,6 +3526,45 @@ var promptFavoritesSchema = {
3434
3526
  }
3435
3527
  ]
3436
3528
  };
3529
+ var contextPresetsSchema = {
3530
+ type: "context_presets",
3531
+ name: {
3532
+ plural: "context_presets",
3533
+ singular: "context_preset"
3534
+ },
3535
+ RBAC: true,
3536
+ fields: [
3537
+ {
3538
+ name: "name",
3539
+ type: "text",
3540
+ required: true,
3541
+ index: true
3542
+ },
3543
+ {
3544
+ name: "description",
3545
+ type: "text"
3546
+ },
3547
+ {
3548
+ name: "preset_items",
3549
+ type: "json",
3550
+ required: true
3551
+ },
3552
+ {
3553
+ name: "tags",
3554
+ type: "json"
3555
+ },
3556
+ {
3557
+ name: "usage_count",
3558
+ type: "number",
3559
+ default: 0
3560
+ },
3561
+ {
3562
+ name: "favorite_count",
3563
+ type: "number",
3564
+ default: 0
3565
+ }
3566
+ ]
3567
+ };
3437
3568
  var addCoreFields = (schema) => {
3438
3569
  schema.fields.forEach((field) => {
3439
3570
  if (field.type === "file") {
@@ -3483,7 +3614,8 @@ var coreSchemas = {
3483
3614
  platformConfigurationsSchema: () => addCoreFields(platformConfigurationsSchema),
3484
3615
  promptLibrarySchema: () => addCoreFields(promptLibrarySchema),
3485
3616
  embedderSettingsSchema: () => addCoreFields(embedderSettingsSchema),
3486
- promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema)
3617
+ promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema),
3618
+ contextPresetsSchema: () => addCoreFields(contextPresetsSchema)
3487
3619
  };
3488
3620
  if (license["agent-feedback"]) {
3489
3621
  schemas.feedbackSchema = () => addCoreFields(feedbackSchema);
@@ -4525,7 +4657,7 @@ var ExuluContext2 = class {
4525
4657
  job: jobs.length > 0 ? jobs.join(",") : void 0
4526
4658
  };
4527
4659
  };
4528
- updateItem = async (item, config, user, role, generateEmbeddingsOverwrite) => {
4660
+ updateItem = async (item, config, user, role, generateEmbeddingsOverwrite, runProcessorOverwrite) => {
4529
4661
  console.log("[EXULU] updating item", item);
4530
4662
  const { db: db2 } = await postgresClient();
4531
4663
  if (item.field) {
@@ -4551,7 +4683,7 @@ var ExuluContext2 = class {
4551
4683
  let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always");
4552
4684
  if (this.processor) {
4553
4685
  const processor = this.processor;
4554
- if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
4686
+ if (processor && runProcessorOverwrite !== false && (runProcessorOverwrite || processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
4555
4687
  const { job: processorJob, result: processorResult } = await this.processField(
4556
4688
  "api",
4557
4689
  {
@@ -4817,120 +4949,7 @@ var ExuluContext2 = class {
4817
4949
  };
4818
4950
  };
4819
4951
 
4820
- // ee/agentic-retrieval/v3/context-sampler.ts
4821
- var CACHE_TTL_MS = 60 * 60 * 1e3;
4822
- var ContextSampler = class {
4823
- cache = /* @__PURE__ */ new Map();
4824
- async getSamples(contexts, user, role) {
4825
- return Promise.all(contexts.map((ctx) => this.getSample(ctx, user, role)));
4826
- }
4827
- async getSample(ctx, user, role) {
4828
- const cached = this.cache.get(ctx.id);
4829
- if (cached && Date.now() - cached.sampledAt < CACHE_TTL_MS) {
4830
- return cached;
4831
- }
4832
- const { db: db2 } = await postgresClient();
4833
- const tableName = getTableName(ctx.id);
4834
- const tableDefinition = convertContextToTableDefinition(ctx);
4835
- const customFieldNames = ctx.fields.map((f) => f.name);
4836
- const selectFields = ["id", "name", "external_id", ...customFieldNames];
4837
- let exampleItems = [];
4838
- try {
4839
- let query = db2(tableName).select(selectFields).whereNull("archived").limit(2);
4840
- query = applyAccessControl(tableDefinition, query, user, tableName);
4841
- exampleItems = await query;
4842
- } catch {
4843
- }
4844
- const sample = {
4845
- contextId: ctx.id,
4846
- contextName: ctx.name,
4847
- fields: ["name", "external_id", ...customFieldNames],
4848
- exampleItems,
4849
- sampledAt: Date.now()
4850
- };
4851
- this.cache.set(ctx.id, sample);
4852
- return sample;
4853
- }
4854
- /** Evict a context from cache so it's re-sampled on next use */
4855
- invalidate(contextId) {
4856
- this.cache.delete(contextId);
4857
- }
4858
- };
4859
-
4860
- // ee/agentic-retrieval/v3/classifier.ts
4861
- import { generateText, Output } from "ai";
4862
- import { z as z5 } from "zod";
4863
-
4864
- // src/utils/with-retry.ts
4865
- async function withRetry(generateFn, maxRetries = 3) {
4866
- let lastError;
4867
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
4868
- try {
4869
- return await generateFn();
4870
- } catch (error) {
4871
- lastError = error;
4872
- console.error(`[EXULU] generateText attempt ${attempt} failed:`, error);
4873
- if (attempt === maxRetries) {
4874
- throw error;
4875
- }
4876
- await new Promise((resolve3) => setTimeout(resolve3, Math.pow(2, attempt) * 1e3));
4877
- }
4878
- }
4879
- throw lastError;
4880
- }
4881
-
4882
- // ee/agentic-retrieval/v3/classifier.ts
4883
- async function classifyQuery(query, contexts, samples, model) {
4884
- const contextDescriptions = contexts.map((ctx) => {
4885
- const sample = samples.find((s) => s.contextId === ctx.id);
4886
- const fieldList = sample?.fields.join(", ") ?? "name, external_id";
4887
- const exampleStr = sample?.exampleItems.length ? `
4888
- Example records: ${JSON.stringify(sample.exampleItems.slice(0, 2))}` : "";
4889
- return ` - ${ctx.id}: ${ctx.name}
4890
- Description: ${ctx.description}
4891
- Fields: ${fieldList}${exampleStr}`;
4892
- }).join("\n\n");
4893
- const result = await withRetry(async () => {
4894
- const result2 = await generateText({
4895
- model,
4896
- temperature: 0,
4897
- output: Output.object({
4898
- schema: z5.object({
4899
- queryType: z5.enum(["aggregate", "list", "targeted", "exploratory"]).describe(
4900
- "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)."
4901
- ),
4902
- language: z5.string().describe("ISO 639-3 language code of the query (e.g. eng, deu, fra)"),
4903
- suggestedContextIds: z5.array(z5.enum(contexts.map((c) => c.id))).describe(
4904
- "IDs of knowledge bases most likely to contain the answer. Return empty array to search all contexts."
4905
- )
4906
- })
4907
- }),
4908
- toolChoice: "none",
4909
- system: `You are a query classifier for a multi-knowledge-base retrieval system.
4910
- Classify the query and identify which knowledge bases are most relevant.
4911
-
4912
- Available knowledge bases:
4913
- ${contextDescriptions}
4914
-
4915
- Guidelines for queryType:
4916
- - 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.
4917
- - When in doubt between aggregate and targeted: always choose targeted.
4918
-
4919
- Guidelines for suggestedContextIds:
4920
- - Be conservative: only suggest contexts that are genuinely likely to contain the answer.
4921
- Aim for 2\u20133 focused suggestions rather than listing everything.
4922
- - Use each knowledge base's name and description (shown above) to judge relevance.
4923
- - Return an empty array only if you truly cannot determine which contexts are relevant.`,
4924
- prompt: `Query: ${query}`
4925
- });
4926
- return result2.output;
4927
- }, 3);
4928
- return result;
4929
- }
4930
-
4931
4952
  // ee/agentic-retrieval/v3/tools.ts
4932
- import { z as z6 } from "zod";
4933
- import { tool as tool2 } from "ai";
4934
4953
  function buildContextEnum(contexts) {
4935
4954
  return z6.array(z6.enum(contexts.map((c) => c.id))).describe(
4936
4955
  contexts.map(
@@ -4972,7 +4991,7 @@ function parseGlobalItemIds(globalIds) {
4972
4991
  return map;
4973
4992
  }
4974
4993
  function createRetrievalTools(params) {
4975
- const { contexts, user, role, updateVirtualFiles, preselectedItemsByContext } = params;
4994
+ const { contexts, toolVariablesConfig, user, role, updateVirtualFiles, preselectedItemsByContext } = params;
4976
4995
  const ctxEnum = buildContextEnum(contexts);
4977
4996
  const count_items_or_chunks = tool2({
4978
4997
  description: "Count items or chunks WITHOUT loading them into context. Use for 'how many', 'count', or 'total number of' queries.",
@@ -5105,7 +5124,7 @@ Use includeContent: true when you need the ACTUAL text to answer a question.
5105
5124
 
5106
5125
  For listing queries: always start with includeContent: false, then use dynamic tools to fetch specific pages.`,
5107
5126
  inputSchema: z6.object({
5108
- query: z6.string().describe("Search query about the content you're looking for"),
5127
+ userQuery: z6.string().describe("The original unaltered question from the user"),
5109
5128
  knowledge_base_id: z6.enum(contexts.map((c) => c.id)).describe(
5110
5129
  contexts.map(
5111
5130
  (c) => `<knowledge_base id="${c.id}" name="${c.name}">${c.description}</knowledge_base>`
@@ -5124,7 +5143,7 @@ For listing queries: always start with includeContent: false, then use dynamic t
5124
5143
  limit: z6.number().default(20).describe("Max chunks with content (max 20). Without content, up to 200 are returned.")
5125
5144
  }),
5126
5145
  execute: async ({
5127
- query,
5146
+ userQuery,
5128
5147
  knowledge_base_id,
5129
5148
  keywords,
5130
5149
  searchMethod,
@@ -5135,7 +5154,8 @@ For listing queries: always start with includeContent: false, then use dynamic t
5135
5154
  limit
5136
5155
  }) => {
5137
5156
  const [ctx] = resolveContexts([knowledge_base_id], contexts);
5138
- const effectiveLimit = includeContent ? Math.min(limit ?? 20, 20) : Math.min((limit ?? 20) * 20, 400);
5157
+ const maxResults = toolVariablesConfig?.[`${ctx.id}_|_max_results`] || 20;
5158
+ const effectiveLimit = includeContent ? Math.min(limit ?? maxResults, maxResults) : Math.min((limit ?? maxResults) * maxResults, 400);
5139
5159
  const itemFilters = [];
5140
5160
  if (preselectedItemsByContext) {
5141
5161
  const contextItemIds = preselectedItemsByContext.get(knowledge_base_id);
@@ -5157,7 +5177,7 @@ For listing queries: always start with includeContent: false, then use dynamic t
5157
5177
  if (item_names)
5158
5178
  itemFilters.push({ name: { or: item_names.map((n) => ({ contains: n })) } });
5159
5179
  if (item_external_ids) itemFilters.push({ external_id: { in: item_external_ids } });
5160
- const effectiveQuery = query || keywords?.join(" ") || "";
5180
+ const effectiveQuery = userQuery || keywords?.join(" ") || "";
5161
5181
  let method = mapSearchMethod(searchMethod ?? "hybrid");
5162
5182
  if (method === "hybridSearch" || method === "cosineDistance") {
5163
5183
  if (!ctx.embedder) {
@@ -5165,6 +5185,7 @@ For listing queries: always start with includeContent: false, then use dynamic t
5165
5185
  method = "tsvector";
5166
5186
  }
5167
5187
  }
5188
+ const expandChunks = toolVariablesConfig?.[`${ctx.id}_|_expand_chunks`] || 0;
5168
5189
  try {
5169
5190
  const { chunks } = await ctx.search({
5170
5191
  query: effectiveQuery,
@@ -5177,7 +5198,11 @@ For listing queries: always start with includeContent: false, then use dynamic t
5177
5198
  sort: { field: "updatedAt", direction: "desc" },
5178
5199
  user,
5179
5200
  role,
5180
- trigger: "tool"
5201
+ trigger: "tool",
5202
+ expand: expandChunks > 0 ? {
5203
+ before: expandChunks,
5204
+ after: expandChunks
5205
+ } : void 0
5181
5206
  });
5182
5207
  return JSON.stringify(
5183
5208
  chunks.map(
@@ -5949,6 +5974,46 @@ var TrajectoryLogger = class {
5949
5974
  }
5950
5975
  };
5951
5976
 
5977
+ // ee/agentic-retrieval/v3/context-sampler.ts
5978
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
5979
+ var ContextSampler = class {
5980
+ cache = /* @__PURE__ */ new Map();
5981
+ async getSamples(contexts, user, role) {
5982
+ return Promise.all(contexts.map((ctx) => this.getSample(ctx, user, role)));
5983
+ }
5984
+ async getSample(ctx, user, role) {
5985
+ const cached = this.cache.get(ctx.id);
5986
+ if (cached && Date.now() - cached.sampledAt < CACHE_TTL_MS) {
5987
+ return cached;
5988
+ }
5989
+ const { db: db2 } = await postgresClient();
5990
+ const tableName = getTableName(ctx.id);
5991
+ const tableDefinition = convertContextToTableDefinition(ctx);
5992
+ const customFieldNames = ctx.fields.map((f) => f.name);
5993
+ const selectFields = ["id", "name", "external_id", ...customFieldNames];
5994
+ let exampleItems = [];
5995
+ try {
5996
+ let query = db2(tableName).select(selectFields).whereNull("archived").limit(2);
5997
+ query = applyAccessControl(tableDefinition, query, user, tableName);
5998
+ exampleItems = await query;
5999
+ } catch {
6000
+ }
6001
+ const sample = {
6002
+ contextId: ctx.id,
6003
+ contextName: ctx.name,
6004
+ fields: ["name", "external_id", ...customFieldNames],
6005
+ exampleItems,
6006
+ sampledAt: Date.now()
6007
+ };
6008
+ this.cache.set(ctx.id, sample);
6009
+ return sample;
6010
+ }
6011
+ /** Evict a context from cache so it's re-sampled on next use */
6012
+ invalidate(contextId) {
6013
+ this.cache.delete(contextId);
6014
+ }
6015
+ };
6016
+
5952
6017
  // ee/agentic-retrieval/v3/index.ts
5953
6018
  var sampler = new ContextSampler();
5954
6019
  async function* executeV3({
@@ -5956,6 +6021,7 @@ async function* executeV3({
5956
6021
  contexts,
5957
6022
  reranker,
5958
6023
  model,
6024
+ toolVariablesConfig,
5959
6025
  user,
5960
6026
  role,
5961
6027
  customInstructions,
@@ -5981,15 +6047,39 @@ async function* executeV3({
5981
6047
  }
5982
6048
  console.log("[EXULU] v3 \u2014 classified as:", classification);
5983
6049
  const strategy = STRATEGIES[classification.queryType];
6050
+ const contextSpecificInstructions = activeContexts.map((ctx) => {
6051
+ const instructions = toolVariablesConfig?.[`${ctx.id}_|_instructions`] ?? "";
6052
+ if (instructions) {
6053
+ return `
6054
+ <${ctx.id}>
6055
+ ${instructions}
6056
+ </${ctx.id}>
6057
+ `;
6058
+ } else {
6059
+ return null;
6060
+ }
6061
+ }).filter(Boolean).join("\n");
5984
6062
  const suggestedIds = classification.suggestedContextIds;
5985
6063
  const fallbackIds = activeContexts.filter((c) => !suggestedIds.includes(c.id)).map((c) => c.id);
5986
- 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(", ")}].`;
6064
+ let contextBase = suggestedIds.length > 0 ? `
6065
+ Suggested priority contexts: [${suggestedIds.join(", ")}].
6066
+
6067
+ Also available: [${fallbackIds.join(", ")}].
6068
+
6069
+ Custom instructions may require searching additional or all contexts \u2014 follow them.` : `All contexts available: [${activeContexts.map((c) => c.id).join(", ")}].`;
5987
6070
  const preselectedNote = preselectedByContext?.size ? `
5988
6071
  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.` : "";
6072
+ if (contextSpecificInstructions?.length) {
6073
+ contextBase += `
6074
+ Context specific instructions:
6075
+ ${contextSpecificInstructions}
6076
+ `;
6077
+ }
5989
6078
  const contextGuidance = contextBase + preselectedNote;
5990
6079
  const bashToolkit = await createBashTool({ files: {} });
5991
6080
  const retrievalTools = createRetrievalTools({
5992
6081
  contexts: activeContexts,
6082
+ toolVariablesConfig,
5993
6083
  user,
5994
6084
  role,
5995
6085
  updateVirtualFiles: (files) => bashToolkit.sandbox.writeFiles(files),
@@ -6044,7 +6134,8 @@ function createAgenticRetrievalToolV3({
6044
6134
  user,
6045
6135
  role,
6046
6136
  model,
6047
- preselectedItemIds
6137
+ preselected,
6138
+ memoryItems
6048
6139
  }) {
6049
6140
  const license = checkLicense();
6050
6141
  if (!license["agentic-retrieval"]) {
@@ -6097,27 +6188,57 @@ function createAgenticRetrievalToolV3({
6097
6188
  default: false
6098
6189
  },
6099
6190
  {
6100
- name: "log_trajectories",
6191
+ name: "logging",
6101
6192
  description: "Save a detailed markdown + JSON log of every retrieval execution to disk. Useful for debugging and evaluation.",
6102
6193
  type: "boolean",
6103
6194
  default: false
6104
6195
  },
6105
6196
  ...contexts.map((ctx) => ({
6106
- name: ctx.id,
6197
+ name: ctx.id + "_|_enabled",
6107
6198
  description: `Enable search in "${ctx.name}". ${ctx.description}`,
6108
6199
  type: "boolean",
6109
6200
  default: true
6201
+ })),
6202
+ ...contexts.map((ctx) => ({
6203
+ name: `${ctx.id}_|_instructions`,
6204
+ description: `Instructions for the retrieval agent about how to search in the ${ctx.name} context`,
6205
+ type: "string",
6206
+ default: ""
6207
+ })),
6208
+ ...contexts.map((ctx) => ({
6209
+ name: `${ctx.id}_|_priority`,
6210
+ 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`,
6211
+ type: "number",
6212
+ default: 0
6213
+ })),
6214
+ ...contexts.map((ctx) => ({
6215
+ name: `${ctx.id}_|_max_results`,
6216
+ description: `Defines the maximum number of results to return for the ${ctx.name} context`,
6217
+ type: "number",
6218
+ default: 0
6219
+ })),
6220
+ ...contexts.map((ctx) => ({
6221
+ name: `${ctx.id}_|_max_steps`,
6222
+ description: `Defines the maximum number of steps the agent is allowed to take when searching the ${ctx.name} context`,
6223
+ type: "number",
6224
+ default: 0
6225
+ })),
6226
+ ...contexts.map((ctx) => ({
6227
+ name: `${ctx.id}_|_expand_chunks`,
6228
+ description: `Defines if the agent automatically retrieves nearby chunks around the matched chunks, usefull if relevant content might be split up`,
6229
+ type: "number",
6230
+ default: 0
6110
6231
  }))
6111
6232
  ],
6112
6233
  inputSchema: z9.object({
6113
- query: z9.string().describe("The question or query to answer"),
6234
+ userQuery: z9.string().describe("The original unaltered question from the user"),
6114
6235
  userInstructions: z9.string().optional().describe("Additional instructions from the user to guide retrieval"),
6115
6236
  confirmedContextIds: z9.array(z9.string()).optional().describe(
6116
6237
  "Knowledge base IDs explicitly confirmed by the user to be used in the retrieval. When presen only searches these contexts. "
6117
6238
  )
6118
6239
  }),
6119
6240
  execute: async function* ({
6120
- query,
6241
+ userQuery,
6121
6242
  userInstructions,
6122
6243
  confirmedContextIds,
6123
6244
  toolVariablesConfig,
@@ -6135,10 +6256,10 @@ function createAgenticRetrievalToolV3({
6135
6256
  let managedContextEnabled = false;
6136
6257
  if (toolVariablesConfig) {
6137
6258
  configInstructions = toolVariablesConfig["instructions"] ?? "";
6138
- logTrajectory = toolVariablesConfig["log_trajectories"] === true || toolVariablesConfig["log_trajectories"] === "true";
6259
+ logTrajectory = toolVariablesConfig["logging"] === true || toolVariablesConfig["logging"] === "true";
6139
6260
  managedContextEnabled = toolVariablesConfig["managed_context"] === true || toolVariablesConfig["managed_context"] === "true";
6140
6261
  activeContexts = contexts.filter(
6141
- (ctx) => toolVariablesConfig[ctx.id] === true || toolVariablesConfig[ctx.id] === "true" || toolVariablesConfig[ctx.id] === 1
6262
+ (ctx) => toolVariablesConfig[ctx.id + "_|_enabled"] === true || toolVariablesConfig[ctx.id + "_|_enabled"] === "true" || toolVariablesConfig[ctx.id + "_|_enabled"] === 1
6142
6263
  );
6143
6264
  if (activeContexts.length === 0) activeContexts = contexts;
6144
6265
  requiresPreselectedContexts = toolVariablesConfig["require_preselected_contexts"] === true || toolVariablesConfig["require_preselected_contexts"] === "true";
@@ -6148,13 +6269,13 @@ function createAgenticRetrievalToolV3({
6148
6269
  }
6149
6270
  }
6150
6271
  console.log("[EXULU] Managed context enabled:", managedContextEnabled);
6151
- console.log("[EXULU] Preselected item IDs:", preselectedItemIds);
6152
- if (managedContextEnabled && !preselectedItemIds?.length) {
6272
+ console.log("[EXULU] Preselected item IDs:", preselected);
6273
+ if (managedContextEnabled && !preselected?.length) {
6153
6274
  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.");
6154
6275
  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." };
6155
6276
  return;
6156
6277
  }
6157
- if (requiresPreselectedContexts && !confirmedContextIds?.length && !preselectedItemIds?.length) {
6278
+ if (requiresPreselectedContexts && !confirmedContextIds?.length && !preselected?.length) {
6158
6279
  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.");
6159
6280
  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." };
6160
6281
  return;
@@ -6165,21 +6286,43 @@ function createAgenticRetrievalToolV3({
6165
6286
  if (filtered.length > 0) activeContexts = filtered;
6166
6287
  }
6167
6288
  const combinedInstructions = [
6168
- configInstructions ? `Configuration instructions: ${configInstructions}` : "",
6169
- adminInstructions ? `Admin instructions: ${adminInstructions}` : "",
6170
- userInstructions ? `User instructions: ${userInstructions}` : ""
6289
+ configInstructions ? `
6290
+ Configuration instructions:
6291
+ <configuration_instructions>
6292
+ ${configInstructions}
6293
+ </configuration_instructions>
6294
+ ` : "",
6295
+ adminInstructions ? `
6296
+ Admin instructions:
6297
+ <admin_instructions>
6298
+ ${adminInstructions}
6299
+ </admin_instructions>
6300
+ ` : "",
6301
+ userInstructions ? `
6302
+ User instructions:
6303
+ <user_instructions>
6304
+ ${userInstructions}
6305
+ </user_instructions>
6306
+ ` : "",
6307
+ memoryItems ? `
6308
+ Relevant memories (these are items that the agent has retrieved from the memory context and are relevant to the query):
6309
+ <relevant_memories>
6310
+ ${memoryItems?.map((item) => JSON.stringify(item)).join("\n")}
6311
+ </relevant_memories>
6312
+ ` : ""
6171
6313
  ].filter(Boolean).join("\n");
6172
6314
  for await (const output of executeV3({
6173
- query,
6315
+ query: userQuery,
6174
6316
  contexts: activeContexts,
6175
6317
  reranker: configuredReranker,
6318
+ toolVariablesConfig,
6176
6319
  model,
6177
6320
  user,
6178
6321
  role,
6179
6322
  customInstructions: combinedInstructions || void 0,
6180
6323
  logTrajectory,
6181
6324
  sessionId: sessionID,
6182
- preselectedItemIds
6325
+ preselectedItemIds: preselected
6183
6326
  })) {
6184
6327
  yield { result: JSON.stringify(output) };
6185
6328
  }
@@ -11006,6 +11149,7 @@ var ExuluProvider = class {
11006
11149
  });
11007
11150
  }
11008
11151
  let memoryContext = "";
11152
+ let memoryItems;
11009
11153
  if (agent?.memory && contexts?.length && query) {
11010
11154
  const context = contexts.find((context2) => context2.id === agent?.memory);
11011
11155
  if (!context) {
@@ -11028,6 +11172,7 @@ var ExuluProvider = class {
11028
11172
  page: 1
11029
11173
  });
11030
11174
  if (result?.chunks?.length) {
11175
+ memoryItems = result.chunks;
11031
11176
  memoryContext = `
11032
11177
  Pre-fetched relevant information for this query:
11033
11178
 
@@ -11148,7 +11293,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
11148
11293
  project,
11149
11294
  sessionItems,
11150
11295
  model,
11151
- agent
11296
+ agent,
11297
+ memoryItems
11152
11298
  ),
11153
11299
  stopWhen: [stepCountIs2(maxStepCount || 5)]
11154
11300
  // make configurable
@@ -11225,7 +11371,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
11225
11371
  project,
11226
11372
  sessionItems,
11227
11373
  model,
11228
- agent
11374
+ agent,
11375
+ memoryItems
11229
11376
  ),
11230
11377
  stopWhen: [stepCountIs2(maxStepCount || 5)]
11231
11378
  });
@@ -11407,6 +11554,7 @@ ${extractedText}
11407
11554
  });
11408
11555
  }
11409
11556
  let memoryContext = "";
11557
+ let memoryItems;
11410
11558
  if (agent?.memory && contexts?.length && query) {
11411
11559
  const context = contexts.find((context2) => context2.id === agent?.memory);
11412
11560
  if (!context) {
@@ -11430,6 +11578,7 @@ ${extractedText}
11430
11578
  });
11431
11579
  fs2.writeFileSync("pre-fetched-relevant-information.json", JSON.stringify(result2, null, 2));
11432
11580
  if (result2?.chunks?.length) {
11581
+ memoryItems = result2.chunks;
11433
11582
  memoryContext = `
11434
11583
  <pre-fetched relevant information for this query>:
11435
11584
 
@@ -11449,9 +11598,6 @@ ${extractedText}
11449
11598
  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.";
11450
11599
  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.";
11451
11600
  system += "\n\n" + genericContext;
11452
- if (memoryContext) {
11453
- system += "\n\n" + memoryContext;
11454
- }
11455
11601
  const includesContextSearchTool = currentTools?.some(
11456
11602
  (tool5) => tool5.name.toLowerCase().includes("context_search") || tool5.id.includes("context_search") || tool5.type === "context"
11457
11603
  );
@@ -11536,7 +11682,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
11536
11682
  project,
11537
11683
  sessionItems,
11538
11684
  model,
11539
- agent
11685
+ agent,
11686
+ memoryItems
11540
11687
  ),
11541
11688
  onError: (error) => {
11542
11689
  console.error("[EXULU] chat stream error.", error);
@@ -12133,6 +12280,7 @@ var {
12133
12280
  workflowTemplatesSchema: workflowTemplatesSchema2,
12134
12281
  rbacSchema: rbacSchema2,
12135
12282
  promptLibrarySchema: promptLibrarySchema2,
12283
+ contextPresetsSchema: contextPresetsSchema2,
12136
12284
  embedderSettingsSchema: embedderSettingsSchema2,
12137
12285
  promptFavoritesSchema: promptFavoritesSchema2,
12138
12286
  statisticsSchema: statisticsSchema2
@@ -12180,6 +12328,7 @@ var createExpressRoutes = async (app, providers, tools, contexts, config, evals,
12180
12328
  projectsSchema2(),
12181
12329
  jobResultsSchema2(),
12182
12330
  promptLibrarySchema2(),
12331
+ contextPresetsSchema2(),
12183
12332
  embedderSettingsSchema2(),
12184
12333
  promptFavoritesSchema2(),
12185
12334
  evalRunsSchema2(),
@@ -17246,6 +17395,7 @@ var {
17246
17395
  projectsSchema: projectsSchema3,
17247
17396
  jobResultsSchema: jobResultsSchema3,
17248
17397
  promptLibrarySchema: promptLibrarySchema3,
17398
+ contextPresetsSchema: contextPresetsSchema3,
17249
17399
  embedderSettingsSchema: embedderSettingsSchema3,
17250
17400
  promptFavoritesSchema: promptFavoritesSchema3
17251
17401
  } = coreSchemas.get();
@@ -17283,6 +17433,7 @@ var up = async function(knex) {
17283
17433
  projectsSchema3(),
17284
17434
  jobResultsSchema3(),
17285
17435
  promptLibrarySchema3(),
17436
+ contextPresetsSchema3(),
17286
17437
  embedderSettingsSchema3(),
17287
17438
  promptFavoritesSchema3(),
17288
17439
  rbacSchema3(),