@exulu/backend 1.55.0 → 1.57.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 +479 -257
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +481 -258
- package/ee/agentic-retrieval/v3/classifier.ts +19 -5
- package/ee/agentic-retrieval/v3/context-sampler.ts +10 -1
- package/ee/agentic-retrieval/v3/index.ts +110 -28
- package/ee/agentic-retrieval/v3/tools.ts +13 -5
- package/ee/agentic-retrieval/v4/agent-loop.ts +208 -0
- package/ee/agentic-retrieval/v4/context-sampler.ts +79 -0
- package/ee/agentic-retrieval/v4/index.ts +690 -0
- package/ee/agentic-retrieval/v4/types.ts +58 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
|
@@ -1543,7 +1546,7 @@ var ExuluTool = class {
|
|
|
1543
1546
|
});
|
|
1544
1547
|
}
|
|
1545
1548
|
execute = async ({
|
|
1546
|
-
agent:
|
|
1549
|
+
agent: agentId,
|
|
1547
1550
|
config,
|
|
1548
1551
|
user,
|
|
1549
1552
|
inputs,
|
|
@@ -1551,14 +1554,14 @@ var ExuluTool = class {
|
|
|
1551
1554
|
items
|
|
1552
1555
|
}) => {
|
|
1553
1556
|
console.log("[EXULU] Calling tool execute directly", {
|
|
1554
|
-
agentId
|
|
1557
|
+
agentId,
|
|
1555
1558
|
config,
|
|
1556
1559
|
user,
|
|
1557
1560
|
inputs,
|
|
1558
1561
|
project,
|
|
1559
1562
|
items
|
|
1560
1563
|
});
|
|
1561
|
-
const agent = await exuluApp.get().agent(
|
|
1564
|
+
const agent = await exuluApp.get().agent(agentId);
|
|
1562
1565
|
if (!agent) {
|
|
1563
1566
|
throw new Error("Agent not found.");
|
|
1564
1567
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
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["
|
|
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:",
|
|
6199
|
-
if (managedContextEnabled && !
|
|
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 && !
|
|
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 ? `
|
|
6216
|
-
|
|
6217
|
-
|
|
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);
|
|
@@ -11691,27 +11838,141 @@ var providerRateLimiter = async (key, windowSeconds, limit, points) => {
|
|
|
11691
11838
|
// src/exulu/openai-gateway.ts
|
|
11692
11839
|
var import_express2 = require("express");
|
|
11693
11840
|
var import_ai9 = require("ai");
|
|
11841
|
+
|
|
11842
|
+
// src/exulu/openai-transformer.ts
|
|
11843
|
+
function transformStreamChunk(chunk, ctx) {
|
|
11844
|
+
const base = {
|
|
11845
|
+
id: ctx.completionId,
|
|
11846
|
+
object: "chat.completion.chunk",
|
|
11847
|
+
created: ctx.created,
|
|
11848
|
+
model: ctx.modelId
|
|
11849
|
+
};
|
|
11850
|
+
switch (chunk.type) {
|
|
11851
|
+
case "text-delta":
|
|
11852
|
+
return {
|
|
11853
|
+
...base,
|
|
11854
|
+
choices: [{ index: 0, delta: { content: chunk.text }, finish_reason: null }]
|
|
11855
|
+
};
|
|
11856
|
+
case "tool-input-start":
|
|
11857
|
+
return {
|
|
11858
|
+
...base,
|
|
11859
|
+
choices: [
|
|
11860
|
+
{
|
|
11861
|
+
index: 0,
|
|
11862
|
+
delta: {
|
|
11863
|
+
tool_calls: [
|
|
11864
|
+
{
|
|
11865
|
+
index: 0,
|
|
11866
|
+
id: chunk.id,
|
|
11867
|
+
type: "function",
|
|
11868
|
+
function: { name: chunk.toolName, arguments: "" }
|
|
11869
|
+
}
|
|
11870
|
+
]
|
|
11871
|
+
},
|
|
11872
|
+
finish_reason: null
|
|
11873
|
+
}
|
|
11874
|
+
]
|
|
11875
|
+
};
|
|
11876
|
+
case "tool-input-delta":
|
|
11877
|
+
return {
|
|
11878
|
+
...base,
|
|
11879
|
+
choices: [
|
|
11880
|
+
{
|
|
11881
|
+
index: 0,
|
|
11882
|
+
delta: { tool_calls: [{ index: 0, function: { arguments: chunk.delta } }] },
|
|
11883
|
+
finish_reason: null
|
|
11884
|
+
}
|
|
11885
|
+
]
|
|
11886
|
+
};
|
|
11887
|
+
case "finish": {
|
|
11888
|
+
const inputTokens = chunk.usage?.inputTokens ?? 0;
|
|
11889
|
+
const outputTokens = chunk.usage?.outputTokens ?? 0;
|
|
11890
|
+
const finishReason = chunk.finishReason === "tool-calls" ? "tool_calls" : "stop";
|
|
11891
|
+
return {
|
|
11892
|
+
...base,
|
|
11893
|
+
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
11894
|
+
usage: {
|
|
11895
|
+
prompt_tokens: inputTokens,
|
|
11896
|
+
completion_tokens: outputTokens,
|
|
11897
|
+
total_tokens: inputTokens + outputTokens
|
|
11898
|
+
}
|
|
11899
|
+
};
|
|
11900
|
+
}
|
|
11901
|
+
default:
|
|
11902
|
+
return null;
|
|
11903
|
+
}
|
|
11904
|
+
}
|
|
11905
|
+
function transformCompletion(text, inputTokens, outputTokens, ctx) {
|
|
11906
|
+
return {
|
|
11907
|
+
id: ctx.completionId,
|
|
11908
|
+
object: "chat.completion",
|
|
11909
|
+
created: ctx.created,
|
|
11910
|
+
model: ctx.modelId,
|
|
11911
|
+
choices: [
|
|
11912
|
+
{
|
|
11913
|
+
index: 0,
|
|
11914
|
+
message: { role: "assistant", content: text },
|
|
11915
|
+
finish_reason: "stop"
|
|
11916
|
+
}
|
|
11917
|
+
],
|
|
11918
|
+
usage: {
|
|
11919
|
+
prompt_tokens: inputTokens,
|
|
11920
|
+
completion_tokens: outputTokens,
|
|
11921
|
+
total_tokens: inputTokens + outputTokens
|
|
11922
|
+
}
|
|
11923
|
+
};
|
|
11924
|
+
}
|
|
11925
|
+
|
|
11926
|
+
// src/exulu/openai-gateway.ts
|
|
11694
11927
|
var import_node_crypto4 = require("crypto");
|
|
11695
11928
|
var import_crypto_js6 = __toESM(require("crypto-js"), 1);
|
|
11696
11929
|
var import_express3 = __toESM(require("express"), 1);
|
|
11697
|
-
function
|
|
11930
|
+
function convertOpenAIToolsToAiSdkTools(tools) {
|
|
11931
|
+
return Object.fromEntries(
|
|
11932
|
+
tools.map((t) => {
|
|
11933
|
+
const params = t.function.parameters ?? {};
|
|
11934
|
+
return [
|
|
11935
|
+
t.function.name,
|
|
11936
|
+
{
|
|
11937
|
+
description: t.function.description ?? "",
|
|
11938
|
+
inputSchema: (0, import_ai9.jsonSchema)({
|
|
11939
|
+
type: "object",
|
|
11940
|
+
properties: params.properties ?? {},
|
|
11941
|
+
...params.required ? { required: params.required } : {}
|
|
11942
|
+
})
|
|
11943
|
+
}
|
|
11944
|
+
];
|
|
11945
|
+
})
|
|
11946
|
+
);
|
|
11947
|
+
}
|
|
11948
|
+
function convertOpenAIMessagesToModelMessages(messages) {
|
|
11698
11949
|
const systemParts = [];
|
|
11699
11950
|
const coreMessages = [];
|
|
11951
|
+
const toolCallIdToName = /* @__PURE__ */ new Map();
|
|
11700
11952
|
for (const msg of messages) {
|
|
11701
11953
|
if (msg.role === "system") {
|
|
11702
11954
|
systemParts.push(typeof msg.content === "string" ? msg.content : "");
|
|
11703
11955
|
continue;
|
|
11704
11956
|
}
|
|
11705
11957
|
if (msg.role === "user") {
|
|
11958
|
+
const last = coreMessages[coreMessages.length - 1];
|
|
11706
11959
|
if (typeof msg.content === "string") {
|
|
11707
|
-
|
|
11960
|
+
if (last?.role === "user" && typeof last.content === "string") {
|
|
11961
|
+
last.content += "\n\n" + msg.content;
|
|
11962
|
+
} else {
|
|
11963
|
+
coreMessages.push({ role: "user", content: msg.content });
|
|
11964
|
+
}
|
|
11708
11965
|
} else if (Array.isArray(msg.content)) {
|
|
11709
11966
|
const parts = msg.content.flatMap((part) => {
|
|
11710
11967
|
if (part.type === "text") return [{ type: "text", text: part.text }];
|
|
11711
11968
|
if (part.type === "image_url") return [{ type: "image", image: part.image_url.url }];
|
|
11712
11969
|
return [];
|
|
11713
11970
|
});
|
|
11714
|
-
|
|
11971
|
+
if (last?.role === "user" && Array.isArray(last.content)) {
|
|
11972
|
+
last.content.push(...parts);
|
|
11973
|
+
} else {
|
|
11974
|
+
coreMessages.push({ role: "user", content: parts });
|
|
11975
|
+
}
|
|
11715
11976
|
}
|
|
11716
11977
|
continue;
|
|
11717
11978
|
}
|
|
@@ -11722,11 +11983,20 @@ function convertOpenAIMessagesToCoreMessages(messages) {
|
|
|
11722
11983
|
parts.push({ type: "text", text: msg.content });
|
|
11723
11984
|
}
|
|
11724
11985
|
for (const tc of msg.tool_calls) {
|
|
11986
|
+
toolCallIdToName.set(tc.id, tc.function.name);
|
|
11987
|
+
const rawArgs = tc.function.arguments;
|
|
11988
|
+
const input = rawArgs == null ? {} : typeof rawArgs === "object" ? rawArgs : (() => {
|
|
11989
|
+
try {
|
|
11990
|
+
return JSON.parse(rawArgs);
|
|
11991
|
+
} catch {
|
|
11992
|
+
return {};
|
|
11993
|
+
}
|
|
11994
|
+
})();
|
|
11725
11995
|
parts.push({
|
|
11726
11996
|
type: "tool-call",
|
|
11727
11997
|
toolCallId: tc.id,
|
|
11728
11998
|
toolName: tc.function.name,
|
|
11729
|
-
|
|
11999
|
+
input
|
|
11730
12000
|
});
|
|
11731
12001
|
}
|
|
11732
12002
|
coreMessages.push({ role: "assistant", content: parts });
|
|
@@ -11739,13 +12009,17 @@ function convertOpenAIMessagesToCoreMessages(messages) {
|
|
|
11739
12009
|
continue;
|
|
11740
12010
|
}
|
|
11741
12011
|
if (msg.role === "tool") {
|
|
12012
|
+
const toolCallId = msg.tool_call_id ?? "";
|
|
12013
|
+
const toolName = toolCallIdToName.get(toolCallId) ?? "unknown";
|
|
12014
|
+
const resultText = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
11742
12015
|
coreMessages.push({
|
|
11743
12016
|
role: "tool",
|
|
11744
12017
|
content: [
|
|
11745
12018
|
{
|
|
11746
12019
|
type: "tool-result",
|
|
11747
|
-
toolCallId
|
|
11748
|
-
|
|
12020
|
+
toolCallId,
|
|
12021
|
+
toolName,
|
|
12022
|
+
output: { type: "text", value: resultText }
|
|
11749
12023
|
}
|
|
11750
12024
|
]
|
|
11751
12025
|
});
|
|
@@ -11862,8 +12136,32 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
11862
12136
|
}
|
|
11863
12137
|
);
|
|
11864
12138
|
app.post(
|
|
11865
|
-
"/gateway/open-ai/v1/chat/completions",
|
|
12139
|
+
["/gateway/open-ai/v1/chat/completions", "/gateway/open-ai/v1/completions"],
|
|
11866
12140
|
import_express3.default.json({ limit: REQUEST_SIZE_LIMIT }),
|
|
12141
|
+
(req, _res, next) => {
|
|
12142
|
+
console.log("[OPENAI GATEWAY] incoming request:", {
|
|
12143
|
+
url: req.originalUrl,
|
|
12144
|
+
method: req.method,
|
|
12145
|
+
headers: {
|
|
12146
|
+
authorization: req.headers["authorization"] ? "[present]" : "[missing]",
|
|
12147
|
+
"x-api-key": req.headers["x-api-key"] ? "[present]" : "[missing]",
|
|
12148
|
+
"exulu-api-key": req.headers["exulu-api-key"] ? "[present]" : "[missing]",
|
|
12149
|
+
"content-type": req.headers["content-type"]
|
|
12150
|
+
},
|
|
12151
|
+
body: {
|
|
12152
|
+
model: req.body?.model,
|
|
12153
|
+
stream: req.body?.stream,
|
|
12154
|
+
messagesCount: req.body?.messages?.length,
|
|
12155
|
+
hasPrompt: typeof req.body?.prompt === "string",
|
|
12156
|
+
tools: req.body?.tools
|
|
12157
|
+
}
|
|
12158
|
+
});
|
|
12159
|
+
if (typeof req.body.prompt === "string") {
|
|
12160
|
+
req.body.messages = [{ role: "user", content: req.body.prompt }];
|
|
12161
|
+
delete req.body.prompt;
|
|
12162
|
+
}
|
|
12163
|
+
next();
|
|
12164
|
+
},
|
|
11867
12165
|
async (req, res) => {
|
|
11868
12166
|
try {
|
|
11869
12167
|
const { db: db2 } = await postgresClient();
|
|
@@ -11969,8 +12267,10 @@ var registerOpenAIGatewayRoutes = async (app, providers, tools, contexts, config
|
|
|
11969
12267
|
languageModel,
|
|
11970
12268
|
agent
|
|
11971
12269
|
);
|
|
12270
|
+
const clientTools = Array.isArray(req.body.tools) ? req.body.tools : [];
|
|
12271
|
+
const activeTools = clientTools.length > 0 ? convertOpenAIToolsToAiSdkTools(clientTools) : convertedTools;
|
|
11972
12272
|
const openaiMessages = req.body.messages ?? [];
|
|
11973
|
-
const { systemPrompt: requestSystemPrompt, coreMessages } =
|
|
12273
|
+
const { systemPrompt: requestSystemPrompt, coreMessages } = convertOpenAIMessagesToModelMessages(openaiMessages);
|
|
11974
12274
|
const agentInstructions = agent.instructions ?? "";
|
|
11975
12275
|
const systemParts = [
|
|
11976
12276
|
agentInstructions ? `You are an agent named: ${agent.name}
|
|
@@ -11982,7 +12282,8 @@ ${project.description}` : ""}` : "",
|
|
|
11982
12282
|
const systemPrompt = systemParts.join("\n\n");
|
|
11983
12283
|
const completionId = `chatcmpl-${(0, import_node_crypto4.randomUUID)()}`;
|
|
11984
12284
|
const created = Math.floor(Date.now() / 1e3);
|
|
11985
|
-
const hasTools = Object.keys(
|
|
12285
|
+
const hasTools = Object.keys(activeTools).length > 0;
|
|
12286
|
+
const ctx = { completionId, created, modelId };
|
|
11986
12287
|
if (req.body.stream === true) {
|
|
11987
12288
|
res.setHeader("Content-Type", "text/event-stream");
|
|
11988
12289
|
res.setHeader("Cache-Control", "no-cache");
|
|
@@ -11991,9 +12292,9 @@ ${project.description}` : ""}` : "",
|
|
|
11991
12292
|
model: languageModel,
|
|
11992
12293
|
system: systemPrompt || void 0,
|
|
11993
12294
|
messages: coreMessages,
|
|
11994
|
-
tools: hasTools ?
|
|
12295
|
+
tools: hasTools ? activeTools : void 0,
|
|
11995
12296
|
maxRetries: 2,
|
|
11996
|
-
stopWhen: [(0, import_ai9.stepCountIs)(5)],
|
|
12297
|
+
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai9.stepCountIs)(5)],
|
|
11997
12298
|
onError: (error) => {
|
|
11998
12299
|
console.error("[OPENAI GATEWAY] stream error:", error);
|
|
11999
12300
|
}
|
|
@@ -12012,83 +12313,17 @@ ${project.description}` : ""}` : "",
|
|
|
12012
12313
|
let inputTokens = 0;
|
|
12013
12314
|
let outputTokens = 0;
|
|
12014
12315
|
for await (const chunk of result.fullStream) {
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
`
|
|
12026
|
-
);
|
|
12027
|
-
} else if (chunk.type === "tool-input-start") {
|
|
12028
|
-
res.write(
|
|
12029
|
-
`data: ${JSON.stringify({
|
|
12030
|
-
id: completionId,
|
|
12031
|
-
object: "chat.completion.chunk",
|
|
12032
|
-
created,
|
|
12033
|
-
model: modelId,
|
|
12034
|
-
choices: [
|
|
12035
|
-
{
|
|
12036
|
-
index: 0,
|
|
12037
|
-
delta: {
|
|
12038
|
-
tool_calls: [
|
|
12039
|
-
{
|
|
12040
|
-
index: 0,
|
|
12041
|
-
id: chunk.id,
|
|
12042
|
-
type: "function",
|
|
12043
|
-
function: { name: chunk.toolName, arguments: "" }
|
|
12044
|
-
}
|
|
12045
|
-
]
|
|
12046
|
-
},
|
|
12047
|
-
finish_reason: null
|
|
12048
|
-
}
|
|
12049
|
-
]
|
|
12050
|
-
})}
|
|
12051
|
-
|
|
12052
|
-
`
|
|
12053
|
-
);
|
|
12054
|
-
} else if (chunk.type === "tool-input-delta") {
|
|
12055
|
-
res.write(
|
|
12056
|
-
`data: ${JSON.stringify({
|
|
12057
|
-
id: completionId,
|
|
12058
|
-
object: "chat.completion.chunk",
|
|
12059
|
-
created,
|
|
12060
|
-
model: modelId,
|
|
12061
|
-
choices: [
|
|
12062
|
-
{
|
|
12063
|
-
index: 0,
|
|
12064
|
-
delta: { tool_calls: [{ index: 0, function: { arguments: chunk.delta } }] },
|
|
12065
|
-
finish_reason: null
|
|
12066
|
-
}
|
|
12067
|
-
]
|
|
12068
|
-
})}
|
|
12069
|
-
|
|
12070
|
-
`
|
|
12071
|
-
);
|
|
12072
|
-
} else if (chunk.type === "finish") {
|
|
12073
|
-
inputTokens = chunk.usage?.inputTokens ?? 0;
|
|
12074
|
-
outputTokens = chunk.usage?.outputTokens ?? 0;
|
|
12075
|
-
const finishReason = chunk.finishReason === "tool-calls" ? "tool_calls" : "stop";
|
|
12076
|
-
res.write(
|
|
12077
|
-
`data: ${JSON.stringify({
|
|
12078
|
-
id: completionId,
|
|
12079
|
-
object: "chat.completion.chunk",
|
|
12080
|
-
created,
|
|
12081
|
-
model: modelId,
|
|
12082
|
-
choices: [{ index: 0, delta: {}, finish_reason: finishReason }],
|
|
12083
|
-
usage: {
|
|
12084
|
-
prompt_tokens: inputTokens,
|
|
12085
|
-
completion_tokens: outputTokens,
|
|
12086
|
-
total_tokens: inputTokens + outputTokens
|
|
12087
|
-
}
|
|
12088
|
-
})}
|
|
12316
|
+
console.log("[OPENAI GATEWAY] chunk:", chunk.type);
|
|
12317
|
+
const openAIChunk = transformStreamChunk(chunk, ctx);
|
|
12318
|
+
if (openAIChunk) {
|
|
12319
|
+
if (chunk.type === "finish") {
|
|
12320
|
+
inputTokens = chunk.usage?.inputTokens ?? 0;
|
|
12321
|
+
outputTokens = chunk.usage?.outputTokens ?? 0;
|
|
12322
|
+
console.log("[OPENAI GATEWAY] finish_reason:", openAIChunk.choices[0]?.finish_reason);
|
|
12323
|
+
}
|
|
12324
|
+
res.write(`data: ${JSON.stringify(openAIChunk)}
|
|
12089
12325
|
|
|
12090
|
-
`
|
|
12091
|
-
);
|
|
12326
|
+
`);
|
|
12092
12327
|
}
|
|
12093
12328
|
}
|
|
12094
12329
|
res.write("data: [DONE]\n\n");
|
|
@@ -12099,29 +12334,12 @@ ${project.description}` : ""}` : "",
|
|
|
12099
12334
|
model: languageModel,
|
|
12100
12335
|
system: systemPrompt || void 0,
|
|
12101
12336
|
messages: coreMessages,
|
|
12102
|
-
tools: hasTools ?
|
|
12337
|
+
tools: hasTools ? activeTools : void 0,
|
|
12103
12338
|
maxRetries: 2,
|
|
12104
|
-
stopWhen: [(0, import_ai9.stepCountIs)(5)]
|
|
12105
|
-
});
|
|
12106
|
-
res.json({
|
|
12107
|
-
id: completionId,
|
|
12108
|
-
object: "chat.completion",
|
|
12109
|
-
created,
|
|
12110
|
-
model: agentId,
|
|
12111
|
-
choices: [
|
|
12112
|
-
{
|
|
12113
|
-
index: 0,
|
|
12114
|
-
message: { role: "assistant", content: text },
|
|
12115
|
-
finish_reason: "stop"
|
|
12116
|
-
}
|
|
12117
|
-
],
|
|
12118
|
-
usage: {
|
|
12119
|
-
prompt_tokens: usage.promptTokens,
|
|
12120
|
-
completion_tokens: usage.completionTokens,
|
|
12121
|
-
total_tokens: usage.totalTokens
|
|
12122
|
-
}
|
|
12339
|
+
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai9.stepCountIs)(5)]
|
|
12123
12340
|
});
|
|
12124
|
-
|
|
12341
|
+
res.json(transformCompletion(text, usage.inputTokens ?? 0, usage.outputTokens ?? 0, ctx));
|
|
12342
|
+
await writeStatistics(agent, project, user, usage.inputTokens ?? 0, usage.outputTokens ?? 0);
|
|
12125
12343
|
}
|
|
12126
12344
|
} catch (error) {
|
|
12127
12345
|
console.error("[OPENAI GATEWAY] /v1/chat/completions error:", error);
|
|
@@ -12169,6 +12387,7 @@ var {
|
|
|
12169
12387
|
workflowTemplatesSchema: workflowTemplatesSchema2,
|
|
12170
12388
|
rbacSchema: rbacSchema2,
|
|
12171
12389
|
promptLibrarySchema: promptLibrarySchema2,
|
|
12390
|
+
contextPresetsSchema: contextPresetsSchema2,
|
|
12172
12391
|
embedderSettingsSchema: embedderSettingsSchema2,
|
|
12173
12392
|
promptFavoritesSchema: promptFavoritesSchema2,
|
|
12174
12393
|
statisticsSchema: statisticsSchema2
|
|
@@ -12216,6 +12435,7 @@ var createExpressRoutes = async (app, providers, tools, contexts, config, evals,
|
|
|
12216
12435
|
projectsSchema2(),
|
|
12217
12436
|
jobResultsSchema2(),
|
|
12218
12437
|
promptLibrarySchema2(),
|
|
12438
|
+
contextPresetsSchema2(),
|
|
12219
12439
|
embedderSettingsSchema2(),
|
|
12220
12440
|
promptFavoritesSchema2(),
|
|
12221
12441
|
evalRunsSchema2(),
|
|
@@ -17282,6 +17502,7 @@ var {
|
|
|
17282
17502
|
projectsSchema: projectsSchema3,
|
|
17283
17503
|
jobResultsSchema: jobResultsSchema3,
|
|
17284
17504
|
promptLibrarySchema: promptLibrarySchema3,
|
|
17505
|
+
contextPresetsSchema: contextPresetsSchema3,
|
|
17285
17506
|
embedderSettingsSchema: embedderSettingsSchema3,
|
|
17286
17507
|
promptFavoritesSchema: promptFavoritesSchema3
|
|
17287
17508
|
} = coreSchemas.get();
|
|
@@ -17319,6 +17540,7 @@ var up = async function(knex) {
|
|
|
17319
17540
|
projectsSchema3(),
|
|
17320
17541
|
jobResultsSchema3(),
|
|
17321
17542
|
promptLibrarySchema3(),
|
|
17543
|
+
contextPresetsSchema3(),
|
|
17322
17544
|
embedderSettingsSchema3(),
|
|
17323
17545
|
promptFavoritesSchema3(),
|
|
17324
17546
|
rbacSchema3(),
|