@exulu/backend 1.54.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 +2275 -1330
- package/dist/index.d.cts +8 -30
- package/dist/index.d.ts +8 -30
- package/dist/index.js +2256 -1306
- package/ee/agentic-retrieval/v3/agent-loop.ts +49 -3
- package/ee/agentic-retrieval/v3/classifier.ts +61 -42
- package/ee/agentic-retrieval/v3/context-sampler.ts +10 -1
- package/ee/agentic-retrieval/v3/index.ts +211 -35
- package/ee/agentic-retrieval/v3/session-tools-registry.ts +20 -0
- package/ee/agentic-retrieval/v3/strategies.ts +28 -24
- package/ee/agentic-retrieval/v3/tools.ts +236 -113
- package/ee/agentic-retrieval/v3/trajectory.ts +227 -14
- package/ee/agentic-retrieval/v4/agent-loop.ts +142 -55
- package/ee/agentic-retrieval/v4/context-sampler.ts +79 -0
- package/ee/agentic-retrieval/v4/index.ts +673 -164
- package/ee/agentic-retrieval/v4/types.ts +33 -4
- package/ee/invoke-skills/create-sandbox.ts +119 -0
- package/ee/python/documents/processing/doc_processor.ts +106 -14
- package/package.json +4 -2
- package/ee/agentic-retrieval/ANALYSIS.md +0 -658
- package/ee/agentic-retrieval/index.ts +0 -1109
- package/ee/agentic-retrieval/logs/README.md +0 -198
- package/ee/agentic-retrieval/v2.ts +0 -1628
- package/ee/agentic-retrieval/v4/embed-preprocessor.ts +0 -76
- package/ee/agentic-retrieval/v4/system-prompt.ts +0 -248
- package/ee/agentic-retrieval/v4/tools.ts +0 -241
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { createBashTool } from "bash-tool";
|
|
3
|
-
import type { LanguageModel } from "ai";
|
|
3
|
+
import type { LanguageModel, Tool } from "ai";
|
|
4
4
|
import type { ExuluContext } from "@SRC/exulu/context";
|
|
5
5
|
import type { ExuluReranker } from "@SRC/exulu/reranker";
|
|
6
6
|
import { ExuluTool } from "@SRC/exulu/tool";
|
|
7
7
|
import type { User } from "@EXULU_TYPES/models/user";
|
|
8
8
|
import { checkLicense } from "@EE/entitlements";
|
|
9
|
-
import { ContextSampler } from "./context-sampler";
|
|
10
9
|
import { classifyQuery } from "./classifier";
|
|
11
|
-
import { createRetrievalTools } from "./tools";
|
|
10
|
+
import { createRetrievalTools, parseGlobalItemIds } from "./tools";
|
|
12
11
|
import { STRATEGIES } from "./strategies";
|
|
13
12
|
import { runAgentLoop } from "./agent-loop";
|
|
14
13
|
import { TrajectoryLogger } from "./trajectory";
|
|
15
14
|
import type { AgenticRetrievalOutput, QueryType } from "./types";
|
|
15
|
+
import type { ExuluItem } from "@SRC/index";
|
|
16
|
+
import { ContextSampler } from "./context-sampler";
|
|
16
17
|
|
|
17
18
|
// Module-level sampler — shared across all tool instances so the cache is warm
|
|
18
19
|
// across requests within the same process.
|
|
@@ -23,27 +24,45 @@ async function* executeV3({
|
|
|
23
24
|
contexts,
|
|
24
25
|
reranker,
|
|
25
26
|
model,
|
|
27
|
+
toolVariablesConfig,
|
|
26
28
|
user,
|
|
27
29
|
role,
|
|
28
30
|
customInstructions,
|
|
31
|
+
logTrajectory,
|
|
32
|
+
sessionId,
|
|
33
|
+
preselectedItemIds,
|
|
29
34
|
}: {
|
|
30
35
|
query: string;
|
|
31
36
|
contexts: ExuluContext[];
|
|
32
37
|
reranker?: ExuluReranker;
|
|
38
|
+
toolVariablesConfig?: Record<string, any>;
|
|
33
39
|
model: LanguageModel;
|
|
34
40
|
user?: User;
|
|
35
41
|
role?: string;
|
|
36
42
|
customInstructions?: string;
|
|
43
|
+
logTrajectory?: boolean;
|
|
44
|
+
sessionId?: string;
|
|
45
|
+
preselectedItemIds?: string[];
|
|
37
46
|
}): AsyncGenerator<AgenticRetrievalOutput> {
|
|
38
|
-
// ── 1.
|
|
47
|
+
// ── 1. Parse preselected item IDs (global format: "<context_id>/<item_id>") ─
|
|
48
|
+
const preselectedByContext = preselectedItemIds?.length
|
|
49
|
+
? parseGlobalItemIds(preselectedItemIds)
|
|
50
|
+
: undefined;
|
|
51
|
+
|
|
52
|
+
// When preselection is active, restrict to only contexts that have selected items
|
|
53
|
+
const activeContexts = preselectedByContext?.size
|
|
54
|
+
? contexts.filter((c) => preselectedByContext.has(c.id))
|
|
55
|
+
: contexts;
|
|
56
|
+
|
|
57
|
+
// ── 2. Sample example records from each context (cached) ──────────────────
|
|
39
58
|
console.log("[EXULU] v3 — sampling contexts");
|
|
40
|
-
const samples = await sampler.getSamples(
|
|
59
|
+
const samples = await sampler.getSamples(activeContexts, user, role);
|
|
41
60
|
|
|
42
|
-
// ──
|
|
61
|
+
// ── 3. Classify query (single fast LLM call) ──────────────────────────────
|
|
43
62
|
console.log("[EXULU] v3 — classifying query");
|
|
44
63
|
let classification;
|
|
45
64
|
try {
|
|
46
|
-
classification = await classifyQuery(query,
|
|
65
|
+
classification = await classifyQuery(query, activeContexts, samples, model);
|
|
47
66
|
} catch (err) {
|
|
48
67
|
console.warn("[EXULU] v3 — classification failed, falling back to exploratory:", err);
|
|
49
68
|
classification = {
|
|
@@ -54,32 +73,63 @@ async function* executeV3({
|
|
|
54
73
|
}
|
|
55
74
|
console.log("[EXULU] v3 — classified as:", classification);
|
|
56
75
|
|
|
57
|
-
// ──
|
|
76
|
+
// ── 4. Select strategy ────────────────────────────────────────────────────
|
|
58
77
|
const strategy = STRATEGIES[classification.queryType];
|
|
59
|
-
|
|
78
|
+
const contextSpecificInstructions = activeContexts.map(ctx => {
|
|
79
|
+
const instructions = toolVariablesConfig?.[`${ctx.id}_|_instructions`] ?? "";
|
|
80
|
+
if (instructions) {
|
|
81
|
+
return `
|
|
82
|
+
<${ctx.id}>
|
|
83
|
+
${instructions}
|
|
84
|
+
</${ctx.id}>
|
|
85
|
+
`;
|
|
86
|
+
} else {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}).filter(Boolean).join("\n");
|
|
60
90
|
// Build context guidance: the classifier is a priority hint, not a hard filter.
|
|
61
91
|
// All contexts remain available so the agent can fall back if suggested ones miss.
|
|
62
92
|
const suggestedIds = classification.suggestedContextIds;
|
|
63
|
-
const fallbackIds =
|
|
93
|
+
const fallbackIds = activeContexts
|
|
64
94
|
.filter((c) => !suggestedIds.includes(c.id))
|
|
65
95
|
.map((c) => c.id);
|
|
66
|
-
|
|
96
|
+
let contextBase =
|
|
67
97
|
suggestedIds.length > 0
|
|
68
|
-
? `
|
|
69
|
-
|
|
98
|
+
? `
|
|
99
|
+
Suggested priority contexts: [${suggestedIds.join(", ")}].
|
|
100
|
+
|
|
101
|
+
Also available: [${fallbackIds.join(", ")}].
|
|
102
|
+
|
|
103
|
+
Custom instructions may require searching additional or all contexts — follow them.`
|
|
104
|
+
: `All contexts available: [${activeContexts.map((c) => c.id).join(", ")}].`;
|
|
105
|
+
|
|
106
|
+
const preselectedNote = preselectedByContext?.size
|
|
107
|
+
? `\nSCOPE 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 — go directly to search_content or save_search_results.`
|
|
108
|
+
: "";
|
|
109
|
+
|
|
110
|
+
if (contextSpecificInstructions?.length) {
|
|
111
|
+
contextBase += `
|
|
112
|
+
Context specific instructions:
|
|
113
|
+
${contextSpecificInstructions}
|
|
114
|
+
`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const contextGuidance = contextBase + preselectedNote;
|
|
70
118
|
|
|
71
|
-
// ──
|
|
119
|
+
// ── 5. Initialize tools ───────────────────────────────────────────────────
|
|
72
120
|
const bashToolkit = await createBashTool({ files: {} });
|
|
73
121
|
|
|
74
122
|
const retrievalTools = createRetrievalTools({
|
|
75
|
-
contexts,
|
|
123
|
+
contexts: activeContexts,
|
|
124
|
+
toolVariablesConfig,
|
|
76
125
|
user,
|
|
77
126
|
role,
|
|
78
127
|
updateVirtualFiles: (files) => bashToolkit.sandbox.writeFiles(files),
|
|
128
|
+
preselectedItemsByContext: preselectedByContext,
|
|
79
129
|
});
|
|
80
130
|
|
|
81
131
|
// Build the tool set for this strategy
|
|
82
|
-
const activeTools: Record<string,
|
|
132
|
+
const activeTools: Record<string, Tool> = {};
|
|
83
133
|
for (const name of strategy.retrieval_tools) {
|
|
84
134
|
if (name in retrievalTools) {
|
|
85
135
|
activeTools[name] = retrievalTools[name as keyof typeof retrievalTools];
|
|
@@ -89,10 +139,10 @@ async function* executeV3({
|
|
|
89
139
|
Object.assign(activeTools, bashToolkit.tools);
|
|
90
140
|
}
|
|
91
141
|
|
|
92
|
-
// ──
|
|
93
|
-
const trajectory = new TrajectoryLogger(query, classification);
|
|
142
|
+
// ── 6. Set up trajectory logging ──────────────────────────────────────────
|
|
143
|
+
const trajectory = new TrajectoryLogger(query, classification, undefined, preselectedItemIds);
|
|
94
144
|
|
|
95
|
-
// ──
|
|
145
|
+
// ── 7. Run agent loop ─────────────────────────────────────────────────────
|
|
96
146
|
let finalOutput: AgenticRetrievalOutput | undefined;
|
|
97
147
|
let executionError: Error | undefined;
|
|
98
148
|
|
|
@@ -106,7 +156,9 @@ async function* executeV3({
|
|
|
106
156
|
contextGuidance,
|
|
107
157
|
customInstructions,
|
|
108
158
|
classification,
|
|
159
|
+
sessionId,
|
|
109
160
|
onStepComplete: (step) => trajectory.recordStep(step),
|
|
161
|
+
onTrajectoryStep: (data) => trajectory.recordRichStep(data),
|
|
110
162
|
})) {
|
|
111
163
|
finalOutput = output;
|
|
112
164
|
yield output;
|
|
@@ -117,7 +169,7 @@ async function* executeV3({
|
|
|
117
169
|
throw err;
|
|
118
170
|
} finally {
|
|
119
171
|
if (finalOutput) {
|
|
120
|
-
const trajectoryFile = await trajectory.finalize(finalOutput, !executionError, executionError);
|
|
172
|
+
const trajectoryFile = await trajectory.finalize(finalOutput, !executionError, executionError, logTrajectory);
|
|
121
173
|
if (trajectoryFile) {
|
|
122
174
|
finalOutput.trajectoryFile = trajectoryFile;
|
|
123
175
|
}
|
|
@@ -141,6 +193,8 @@ export function createAgenticRetrievalToolV3({
|
|
|
141
193
|
user,
|
|
142
194
|
role,
|
|
143
195
|
model,
|
|
196
|
+
preselected,
|
|
197
|
+
memoryItems
|
|
144
198
|
}: {
|
|
145
199
|
contexts: ExuluContext[];
|
|
146
200
|
rerankers: ExuluReranker[];
|
|
@@ -148,6 +202,8 @@ export function createAgenticRetrievalToolV3({
|
|
|
148
202
|
role?: string;
|
|
149
203
|
model?: LanguageModel;
|
|
150
204
|
instructions?: string;
|
|
205
|
+
preselected?: string[];
|
|
206
|
+
memoryItems?: ExuluItem[];
|
|
151
207
|
}): ExuluTool | undefined {
|
|
152
208
|
const license = checkLicense();
|
|
153
209
|
if (!license["agentic-retrieval"]) {
|
|
@@ -177,6 +233,12 @@ export function createAgenticRetrievalToolV3({
|
|
|
177
233
|
type: "string",
|
|
178
234
|
default: "none",
|
|
179
235
|
},
|
|
236
|
+
{
|
|
237
|
+
name: "managed_context",
|
|
238
|
+
description: "Makes sure the user defines which items from which contexts the agentic retrieval tool will search in",
|
|
239
|
+
type: "boolean",
|
|
240
|
+
default: false,
|
|
241
|
+
},
|
|
180
242
|
{
|
|
181
243
|
name: "reasoning_model",
|
|
182
244
|
description: "By default the agentic retrieval tool uses the model from the agent calling the tool, but you can overwrite this here for the reasoning phase",
|
|
@@ -189,35 +251,89 @@ export function createAgenticRetrievalToolV3({
|
|
|
189
251
|
type: "string",
|
|
190
252
|
default: "",
|
|
191
253
|
},
|
|
254
|
+
{
|
|
255
|
+
name: "require_preselected_contexts",
|
|
256
|
+
description: "Require the user to preselect contexts before executing the tool, meaning the user will be asked to select the contexts they want to search in",
|
|
257
|
+
type: "boolean",
|
|
258
|
+
default: false,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "logging",
|
|
262
|
+
description: "Save a detailed markdown + JSON log of every retrieval execution to disk. Useful for debugging and evaluation.",
|
|
263
|
+
type: "boolean",
|
|
264
|
+
default: false,
|
|
265
|
+
},
|
|
192
266
|
...contexts.map((ctx) => ({
|
|
193
|
-
name: ctx.id,
|
|
267
|
+
name: ctx.id + "_|_enabled",
|
|
194
268
|
description: `Enable search in "${ctx.name}". ${ctx.description}`,
|
|
195
269
|
type: "boolean" as const,
|
|
196
270
|
default: true,
|
|
271
|
+
}
|
|
272
|
+
)),
|
|
273
|
+
...contexts.map((ctx) => ({
|
|
274
|
+
name: `${ctx.id}_|_instructions`,
|
|
275
|
+
description: `Instructions for the retrieval agent about how to search in the ${ctx.name} context`,
|
|
276
|
+
type: "string" as const,
|
|
277
|
+
default: "",
|
|
278
|
+
})),
|
|
279
|
+
...contexts.map((ctx) => ({
|
|
280
|
+
name: `${ctx.id}_|_priority`,
|
|
281
|
+
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`,
|
|
282
|
+
type: "number" as const,
|
|
283
|
+
default: 0,
|
|
197
284
|
})),
|
|
285
|
+
...contexts.map((ctx) => ({
|
|
286
|
+
name: `${ctx.id}_|_max_results`,
|
|
287
|
+
description: `Defines the maximum number of results to return for the ${ctx.name} context`,
|
|
288
|
+
type: "number" as const,
|
|
289
|
+
default: 0,
|
|
290
|
+
})),
|
|
291
|
+
...contexts.map((ctx) => ({
|
|
292
|
+
name: `${ctx.id}_|_max_steps`,
|
|
293
|
+
description: `Defines the maximum number of steps the agent is allowed to take when searching the ${ctx.name} context`,
|
|
294
|
+
type: "number" as const,
|
|
295
|
+
default: 0,
|
|
296
|
+
})),
|
|
297
|
+
...contexts.map((ctx) => ({
|
|
298
|
+
name: `${ctx.id}_|_expand_chunks`,
|
|
299
|
+
description: `Defines if the agent automatically retrieves nearby chunks around the matched chunks, usefull if relevant content might be split up`,
|
|
300
|
+
type: "number" as const,
|
|
301
|
+
default: 0,
|
|
302
|
+
}))
|
|
198
303
|
],
|
|
199
304
|
inputSchema: z.object({
|
|
200
|
-
|
|
305
|
+
userQuery: z.string().describe("The original unaltered question from the user"),
|
|
201
306
|
userInstructions: z
|
|
202
307
|
.string()
|
|
203
308
|
.optional()
|
|
204
309
|
.describe("Additional instructions from the user to guide retrieval"),
|
|
310
|
+
confirmedContextIds: z
|
|
311
|
+
.array(z.string())
|
|
312
|
+
.optional()
|
|
313
|
+
.describe(
|
|
314
|
+
"Knowledge base IDs explicitly confirmed by the user to be used in the retrieval. " +
|
|
315
|
+
"When presen only searches these contexts. "
|
|
316
|
+
)
|
|
205
317
|
}),
|
|
206
318
|
execute: async function* ({
|
|
207
|
-
|
|
319
|
+
userQuery,
|
|
208
320
|
userInstructions,
|
|
321
|
+
confirmedContextIds,
|
|
209
322
|
toolVariablesConfig,
|
|
323
|
+
sessionID,
|
|
210
324
|
}: {
|
|
211
|
-
|
|
325
|
+
userQuery: string;
|
|
212
326
|
userInstructions?: string;
|
|
327
|
+
confirmedContextIds?: string[];
|
|
213
328
|
toolVariablesConfig?: Record<string, any>;
|
|
329
|
+
sessionID?: string;
|
|
214
330
|
}) {
|
|
215
|
-
|
|
331
|
+
|
|
216
332
|
/* ROADMAP:
|
|
217
333
|
const app = exuluApp.get();
|
|
218
334
|
let reasoningModel: LanguageModel | undefined = model;
|
|
219
335
|
let searchModel: LanguageModel | undefined = model;
|
|
220
|
-
|
|
336
|
+
|
|
221
337
|
|
|
222
338
|
if (toolVariablesConfig?.reasoning_model) {
|
|
223
339
|
reasoningModel = app.provider(toolVariablesConfig.reasoning_model)?.model?.create({});
|
|
@@ -225,7 +341,7 @@ export function createAgenticRetrievalToolV3({
|
|
|
225
341
|
throw new Error("Reasoning model not found");
|
|
226
342
|
}
|
|
227
343
|
}
|
|
228
|
-
|
|
344
|
+
|
|
229
345
|
if (toolVariablesConfig?.search_model) {
|
|
230
346
|
searchModel = app.provider(toolVariablesConfig.search_model);
|
|
231
347
|
if (!searchModel) {
|
|
@@ -234,48 +350,108 @@ export function createAgenticRetrievalToolV3({
|
|
|
234
350
|
} */
|
|
235
351
|
|
|
236
352
|
if (!model) {
|
|
237
|
-
|
|
353
|
+
yield { result: "Model is required for executing the agentic retrieval tool" };
|
|
354
|
+
return;
|
|
238
355
|
}
|
|
356
|
+
|
|
239
357
|
let activeContexts = contexts;
|
|
240
358
|
let configuredReranker: ExuluReranker | undefined;
|
|
241
359
|
let configInstructions = "";
|
|
360
|
+
let logTrajectory = false;
|
|
361
|
+
let requiresPreselectedContexts = false;
|
|
362
|
+
let managedContextEnabled = false;
|
|
242
363
|
|
|
243
364
|
if (toolVariablesConfig) {
|
|
244
365
|
configInstructions = toolVariablesConfig["instructions"] ?? "";
|
|
366
|
+
logTrajectory =
|
|
367
|
+
toolVariablesConfig["logging"] === true ||
|
|
368
|
+
toolVariablesConfig["logging"] === "true";
|
|
369
|
+
|
|
370
|
+
managedContextEnabled = toolVariablesConfig["managed_context"] === true || toolVariablesConfig["managed_context"] === "true";
|
|
245
371
|
|
|
246
372
|
activeContexts = contexts.filter(
|
|
247
373
|
(ctx) =>
|
|
248
|
-
toolVariablesConfig[ctx.id] === true ||
|
|
249
|
-
toolVariablesConfig[ctx.id] === "true" ||
|
|
250
|
-
toolVariablesConfig[ctx.id] === 1,
|
|
374
|
+
toolVariablesConfig[ctx.id + "_|_enabled"] === true ||
|
|
375
|
+
toolVariablesConfig[ctx.id + "_|_enabled"] === "true" ||
|
|
376
|
+
toolVariablesConfig[ctx.id + "_|_enabled"] === 1,
|
|
251
377
|
);
|
|
252
378
|
if (activeContexts.length === 0) activeContexts = contexts;
|
|
253
379
|
|
|
380
|
+
requiresPreselectedContexts = toolVariablesConfig["require_preselected_contexts"] === true || toolVariablesConfig["require_preselected_contexts"] === "true";
|
|
381
|
+
|
|
254
382
|
const rerankerId = toolVariablesConfig["reranker"];
|
|
383
|
+
|
|
255
384
|
if (rerankerId && rerankerId !== "none") {
|
|
256
385
|
configuredReranker = rerankers.find((r) => r.id === rerankerId);
|
|
257
386
|
}
|
|
258
387
|
}
|
|
259
388
|
|
|
389
|
+
console.log("[EXULU] Managed context enabled:", managedContextEnabled);
|
|
390
|
+
console.log("[EXULU] Preselected item IDs:", preselected);
|
|
391
|
+
|
|
392
|
+
if (managedContextEnabled && !preselected?.length) {
|
|
393
|
+
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.");
|
|
394
|
+
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." };
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (requiresPreselectedContexts && !confirmedContextIds?.length && !preselected?.length) {
|
|
399
|
+
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.");
|
|
400
|
+
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." };
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (confirmedContextIds?.length) {
|
|
405
|
+
const confirmed = new Set(confirmedContextIds);
|
|
406
|
+
const filtered = activeContexts.filter((c) => confirmed.has(c.id));
|
|
407
|
+
if (filtered.length > 0) activeContexts = filtered;
|
|
408
|
+
}
|
|
409
|
+
|
|
260
410
|
const combinedInstructions = [
|
|
261
|
-
configInstructions ? `
|
|
262
|
-
|
|
263
|
-
|
|
411
|
+
configInstructions ? `
|
|
412
|
+
Configuration instructions:
|
|
413
|
+
<configuration_instructions>
|
|
414
|
+
${configInstructions}
|
|
415
|
+
</configuration_instructions>
|
|
416
|
+
` : "",
|
|
417
|
+
adminInstructions ? `
|
|
418
|
+
Admin instructions:
|
|
419
|
+
<admin_instructions>
|
|
420
|
+
${adminInstructions}
|
|
421
|
+
</admin_instructions>
|
|
422
|
+
` : "",
|
|
423
|
+
userInstructions ? `
|
|
424
|
+
User instructions:
|
|
425
|
+
<user_instructions>
|
|
426
|
+
${userInstructions}
|
|
427
|
+
</user_instructions>
|
|
428
|
+
` : "",
|
|
429
|
+
memoryItems ? `
|
|
430
|
+
Relevant memories (these are items that the agent has retrieved from the memory context and are relevant to the query):
|
|
431
|
+
<relevant_memories>
|
|
432
|
+
${memoryItems?.map(item => JSON.stringify(item)).join("\n")}
|
|
433
|
+
</relevant_memories>
|
|
434
|
+
` : "",
|
|
264
435
|
]
|
|
265
436
|
.filter(Boolean)
|
|
266
437
|
.join("\n");
|
|
267
438
|
|
|
268
439
|
for await (const output of executeV3({
|
|
269
|
-
query,
|
|
440
|
+
query: userQuery,
|
|
270
441
|
contexts: activeContexts,
|
|
271
442
|
reranker: configuredReranker,
|
|
443
|
+
toolVariablesConfig,
|
|
272
444
|
model,
|
|
273
445
|
user,
|
|
274
446
|
role,
|
|
275
447
|
customInstructions: combinedInstructions || undefined,
|
|
448
|
+
logTrajectory,
|
|
449
|
+
sessionId: sessionID,
|
|
450
|
+
preselectedItemIds: preselected,
|
|
276
451
|
})) {
|
|
277
452
|
yield { result: JSON.stringify(output) };
|
|
278
453
|
}
|
|
454
|
+
return;
|
|
279
455
|
},
|
|
280
456
|
});
|
|
281
457
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Tool as AITool } from "ai";
|
|
2
|
+
|
|
3
|
+
// Persists dynamic tools (get_more_content_from_X, get_X_page_N_content) created
|
|
4
|
+
// during an agentic retrieval run, keyed by session ID. This lets the outer chat
|
|
5
|
+
// agent call them directly on follow-up questions without re-running retrieval.
|
|
6
|
+
const registry = new Map<string, Map<string, AITool>>();
|
|
7
|
+
|
|
8
|
+
export function registerSessionTools(sessionId: string, tools: Record<string, AITool>): void {
|
|
9
|
+
const existing = registry.get(sessionId) ?? new Map();
|
|
10
|
+
for (const [name, toolDef] of Object.entries(tools)) {
|
|
11
|
+
existing.set(name, toolDef);
|
|
12
|
+
}
|
|
13
|
+
registry.set(sessionId, existing);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getSessionTools(sessionId: string): Record<string, AITool> {
|
|
17
|
+
const toolMap = registry.get(sessionId);
|
|
18
|
+
if (!toolMap || toolMap.size === 0) return {};
|
|
19
|
+
return Object.fromEntries(toolMap.entries());
|
|
20
|
+
}
|
|
@@ -25,9 +25,10 @@ another component will do that based on what you retrieve.
|
|
|
25
25
|
Always respond in the SAME LANGUAGE as the user's query.
|
|
26
26
|
Always write search queries in the SAME LANGUAGE as the user's query — do NOT translate to English.
|
|
27
27
|
|
|
28
|
-
SEARCH APPROACH —
|
|
29
|
-
1.
|
|
30
|
-
|
|
28
|
+
SEARCH APPROACH — one knowledge base at a time, then go deep:
|
|
29
|
+
1. search_content and save_search_results accept ONE knowledge base per call. Make a separate
|
|
30
|
+
call for each knowledge base you need to cover — never skip one. Search all relevant
|
|
31
|
+
knowledge bases before concluding, even if the first one already returned good results.
|
|
31
32
|
2. After finding a relevant document, use get_more_content_from_{item} dynamic tools to load
|
|
32
33
|
additional pages/sections. The specific answer is often NOT in the first retrieved chunk —
|
|
33
34
|
always explore adjacent content before concluding.
|
|
@@ -44,9 +45,8 @@ export const AGGREGATE_INSTRUCTIONS = `
|
|
|
44
45
|
${BASE_INSTRUCTIONS}
|
|
45
46
|
|
|
46
47
|
STRATEGY: This is a COUNTING or AGGREGATION query.
|
|
47
|
-
- Use count_items_or_chunks exclusively
|
|
48
|
+
- Use count_items_or_chunks exclusively — it accepts multiple knowledge bases in one call for efficiency
|
|
48
49
|
- Do NOT use search_content — it loads unnecessary data
|
|
49
|
-
- Search ALL contexts in parallel in a single tool call
|
|
50
50
|
- Return immediately after counting — one step is sufficient
|
|
51
51
|
- If the count needs a content filter, use content_query parameter
|
|
52
52
|
`.trim();
|
|
@@ -81,9 +81,23 @@ Search language:
|
|
|
81
81
|
- Always write search queries in the SAME LANGUAGE as the user's query.
|
|
82
82
|
- Do NOT translate the query to English — the documents are indexed in their original language.
|
|
83
83
|
|
|
84
|
-
Step 1 —
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
Step 1 — match the opening move to what the query actually needs:
|
|
85
|
+
|
|
86
|
+
Query references a SPECIFIC NAMED DOCUMENT (product manual, titled report, named file):
|
|
87
|
+
→ ALWAYS start with search_items_by_name — searches document name/title directly
|
|
88
|
+
→ Only proceed to load content if the document is found
|
|
89
|
+
|
|
90
|
+
Query asks WHETHER a topic EXISTS or WHAT documents cover a topic (no specific title given):
|
|
91
|
+
→ search_content with includeContent: false
|
|
92
|
+
→ Returns matching document names without loading chunk text — efficient and precise
|
|
93
|
+
→ Load content with dynamic get_{item}_page_{n}_content tools only if needed in step 2
|
|
94
|
+
|
|
95
|
+
Query asks for CONTENT itself (procedures, parameters, explanations, how-to):
|
|
96
|
+
→ search_content with includeContent: true, limit 20, searchMethod: "hybrid"
|
|
97
|
+
→ Make one call per knowledge base — search each separately before concluding
|
|
98
|
+
|
|
99
|
+
Query provides an EXACT TERM (error code, product code, ID, parameter name):
|
|
100
|
+
→ search_content with searchMethod: "keyword"
|
|
87
101
|
|
|
88
102
|
Step 2+ — depth and follow-up:
|
|
89
103
|
- For any relevant document found with fewer than 5 chunks, use get_more_content_from_{item}
|
|
@@ -93,19 +107,9 @@ Step 2+ — depth and follow-up:
|
|
|
93
107
|
- Try alternative phrasings if the first query doesn't surface the right answer.
|
|
94
108
|
|
|
95
109
|
Product-specific filtering:
|
|
96
|
-
- When the query mentions a specific
|
|
97
|
-
item_names: ["<
|
|
110
|
+
- When the query mentions a specific named entity (product, model, version), you MAY use
|
|
111
|
+
item_names: ["<entity>"] on a follow-up search to narrow results — but only after an initial
|
|
98
112
|
wide search. Never start with item_names filtering alone.
|
|
99
|
-
|
|
100
|
-
Two-step approach — use includeContent: false first:
|
|
101
|
-
- Only when you expect many results (>20) and need to identify the right document first.
|
|
102
|
-
- Step 1: search_content with includeContent: false → see which documents/chunks match.
|
|
103
|
-
- Step 2: use dynamic get_{item}_page_{n}_content tools to load specific pages.
|
|
104
|
-
|
|
105
|
-
Search method selection:
|
|
106
|
-
- hybrid (default): best for most queries
|
|
107
|
-
- keyword: exact product codes, document IDs, error codes
|
|
108
|
-
- semantic: conceptual questions, synonyms, paraphrasing
|
|
109
113
|
`.trim();
|
|
110
114
|
|
|
111
115
|
export const EXPLORATORY_INSTRUCTIONS = `
|
|
@@ -114,13 +118,13 @@ ${BASE_INSTRUCTIONS}
|
|
|
114
118
|
STRATEGY: This is an EXPLORATORY query — general question requiring broad search.
|
|
115
119
|
|
|
116
120
|
Recommended approach:
|
|
117
|
-
1.
|
|
121
|
+
1. Search each relevant knowledge base separately with hybrid search (includeContent: true, limit: 20) — one call per knowledge base
|
|
118
122
|
2. If results are insufficient: try alternative search terms or different search method
|
|
119
123
|
3. Use save_search_results + bash grep when you need to scan many results without context bloat
|
|
120
124
|
4. Use dynamic get_more_content_from_{item} tools to read adjacent pages when a relevant item is found
|
|
121
125
|
|
|
122
126
|
When to declare done:
|
|
123
|
-
- You have retrieved chunks that cover the key aspects of the query
|
|
127
|
+
- You have retrieved chunks that cover the key aspects of the query from all relevant knowledge bases
|
|
124
128
|
- OR you have tried 3+ different search strategies and found nothing relevant
|
|
125
129
|
|
|
126
130
|
Do NOT use count_items_or_chunks for exploratory queries — the user wants content, not statistics.
|
|
@@ -140,7 +144,7 @@ export const STRATEGIES: Record<QueryType, StrategyConfig> = {
|
|
|
140
144
|
},
|
|
141
145
|
list: {
|
|
142
146
|
queryType: "list",
|
|
143
|
-
stepBudget:
|
|
147
|
+
stepBudget: 3,
|
|
144
148
|
retrieval_tools: ["count_items_or_chunks", "search_items_by_name", "search_content"],
|
|
145
149
|
include_bash: false,
|
|
146
150
|
instructions: LIST_INSTRUCTIONS,
|
|
@@ -154,7 +158,7 @@ export const STRATEGIES: Record<QueryType, StrategyConfig> = {
|
|
|
154
158
|
},
|
|
155
159
|
exploratory: {
|
|
156
160
|
queryType: "exploratory",
|
|
157
|
-
stepBudget:
|
|
161
|
+
stepBudget: 5,
|
|
158
162
|
retrieval_tools: [
|
|
159
163
|
"count_items_or_chunks",
|
|
160
164
|
"search_items_by_name",
|