@exulu/backend 3.7.3 → 4.0.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/{chunk-T6JVFT7L.js → chunk-QMN6MVHQ.js} +6 -1
- package/dist/{chunk-BNTL6LYY.js → chunk-RBEWHG7I.js} +404 -59
- package/dist/cli/start-whisper.js +1 -1
- package/dist/{convert-exulu-tools-to-ai-sdk-tools-UQSLJDXE.js → convert-exulu-tools-to-ai-sdk-tools-6RU4IZMI.js} +1 -1
- package/dist/index.cjs +957 -474
- package/dist/index.d.cts +3 -6
- package/dist/index.d.ts +3 -6
- package/dist/index.js +258 -182
- package/dist/python-setup-DRJ3QX5F.js +17 -0
- package/ee/LICENSE.md +2 -2
- package/ee/agentic-retrieval/pipeline/config.test.ts +18 -1
- package/ee/agentic-retrieval/pipeline/config.ts +15 -0
- package/ee/agentic-retrieval/pipeline/index.test.ts +73 -0
- package/ee/agentic-retrieval/pipeline/index.ts +67 -13
- package/ee/agentic-retrieval/pipeline/memory.test.ts +59 -0
- package/ee/agentic-retrieval/pipeline/memory.ts +181 -11
- package/ee/agentic-retrieval/pipeline/pin-rerun.test.ts +17 -0
- package/ee/agentic-retrieval/pipeline/pin-rerun.ts +29 -0
- package/ee/agentic-retrieval/pipeline/routing.test.ts +34 -0
- package/ee/agentic-retrieval/pipeline/routing.ts +96 -5
- package/ee/agentic-retrieval/pipeline/search.ts +9 -6
- package/ee/agentic-retrieval/pipeline/timing.test.ts +24 -0
- package/ee/agentic-retrieval/pipeline/timing.ts +26 -0
- package/ee/agentic-retrieval/pipeline/types.ts +2 -0
- package/ee/invoke-skills/artifact-filter.test.ts +49 -0
- package/ee/invoke-skills/artifact-filter.ts +38 -0
- package/ee/invoke-skills/create-sandbox.ts +56 -4
- package/ee/python/documents/processing/README.md +2 -3
- package/ee/python/documents/processing/doc_processor.ts +21 -61
- package/ee/python/documents/processing/split_pdf.py +25 -30
- package/ee/python/documents/processing/tests/__init__.py +0 -0
- package/ee/python/documents/processing/tests/test_split_pdf.py +230 -0
- package/ee/python/requirements.txt +17 -2
- package/ee/python/setup.sh +40 -1
- package/ee/python/transcription/pipeline.py +109 -15
- package/ee/python/transcription/tests/test_align_model_licensing.py +184 -0
- package/ee/workers.ts +2 -7
- package/license.md +2 -2
- package/package.json +3 -4
- package/scripts/postinstall.cjs +52 -1
- package/ee/python/documents/processing/document_to_markdown.py +0 -413
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
isPythonEnvironmentSetup,
|
|
6
6
|
setupPythonEnvironment,
|
|
7
7
|
validatePythonEnvironment
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-QMN6MVHQ.js";
|
|
9
9
|
import {
|
|
10
10
|
COMPACTION_INSUFFICIENT,
|
|
11
11
|
ContextCompactionRequiredError,
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
imageAttachmentGuard,
|
|
63
63
|
initAudit,
|
|
64
64
|
invalidateBudgetCaches,
|
|
65
|
+
isIgnoredArtifactPath,
|
|
65
66
|
isLiteLLMEnabled,
|
|
66
67
|
listS3ObjectsByPrefix,
|
|
67
68
|
listTagsByPrefix,
|
|
@@ -75,6 +76,7 @@ import {
|
|
|
75
76
|
resolveReranker,
|
|
76
77
|
sanitizeName,
|
|
77
78
|
sanitizeToolName,
|
|
79
|
+
sessionFilePrefix,
|
|
78
80
|
setBudgetSettings,
|
|
79
81
|
setLiteLLMPackageRoot,
|
|
80
82
|
sliceHistoryAtCheckpoint,
|
|
@@ -88,7 +90,7 @@ import {
|
|
|
88
90
|
verifyCredentialNonce,
|
|
89
91
|
waitForLiteLLMReady,
|
|
90
92
|
withRetry
|
|
91
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-RBEWHG7I.js";
|
|
92
94
|
import {
|
|
93
95
|
LiteLLMAdminError,
|
|
94
96
|
findLiteLLMModel,
|
|
@@ -1418,6 +1420,67 @@ function resolveSearchQueryTexts(query) {
|
|
|
1418
1420
|
hybridOrQuery: buildFullTextOrQuery(query, ftsText)
|
|
1419
1421
|
};
|
|
1420
1422
|
}
|
|
1423
|
+
function chooseFullTextQuery(opts) {
|
|
1424
|
+
if (opts.strictMatches || countOrTerms(opts.orText) > MAX_OR_TERMS) {
|
|
1425
|
+
return { fn: "plainto_tsquery", text: opts.strictText };
|
|
1426
|
+
}
|
|
1427
|
+
return { fn: "websearch_to_tsquery", text: opts.orText };
|
|
1428
|
+
}
|
|
1429
|
+
var MAX_OR_TERMS = 12;
|
|
1430
|
+
function countOrTerms(orText) {
|
|
1431
|
+
return orText.trim() ? orText.split(/\s+or\s+/i).length : 0;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// src/graphql/resolvers/expand-neighbours.ts
|
|
1435
|
+
function planNeighbourFetch(results, expand) {
|
|
1436
|
+
const before = Math.max(0, expand.before ?? 0);
|
|
1437
|
+
const after = Math.max(0, expand.after ?? 0);
|
|
1438
|
+
const plan = /* @__PURE__ */ new Map();
|
|
1439
|
+
if (before === 0 && after === 0) return plan;
|
|
1440
|
+
const present = new Set(results.map((r) => `${r.item_id}-${r.chunk_index}`));
|
|
1441
|
+
for (const r of results) {
|
|
1442
|
+
for (let i = r.chunk_index - before; i <= r.chunk_index + after; i++) {
|
|
1443
|
+
if (i < 0 || i === r.chunk_index || present.has(`${r.item_id}-${i}`)) continue;
|
|
1444
|
+
if (!plan.has(r.item_id)) plan.set(r.item_id, /* @__PURE__ */ new Set());
|
|
1445
|
+
plan.get(r.item_id).add(i);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return plan;
|
|
1449
|
+
}
|
|
1450
|
+
function mergeNeighbours(results, rows, plan, context) {
|
|
1451
|
+
const byItem = /* @__PURE__ */ new Map();
|
|
1452
|
+
for (const r of results) if (!byItem.has(r.item_id)) byItem.set(r.item_id, r);
|
|
1453
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1454
|
+
for (const r of results) merged.set(`${r.item_id}-${r.chunk_index}`, r);
|
|
1455
|
+
for (const row of rows) {
|
|
1456
|
+
if (!plan.get(row.source)?.has(row.chunk_index)) continue;
|
|
1457
|
+
const key = `${row.source}-${row.chunk_index}`;
|
|
1458
|
+
if (merged.has(key)) continue;
|
|
1459
|
+
const parent = byItem.get(row.source);
|
|
1460
|
+
if (!parent) continue;
|
|
1461
|
+
merged.set(key, {
|
|
1462
|
+
chunk_content: row.content,
|
|
1463
|
+
chunk_index: row.chunk_index,
|
|
1464
|
+
chunk_id: row.id,
|
|
1465
|
+
chunk_source: row.source,
|
|
1466
|
+
chunk_metadata: row.metadata,
|
|
1467
|
+
chunk_created_at: row.createdAt,
|
|
1468
|
+
chunk_updated_at: row.updatedAt,
|
|
1469
|
+
item_updated_at: parent.item_updated_at,
|
|
1470
|
+
item_created_at: parent.item_created_at,
|
|
1471
|
+
item_id: parent.item_id,
|
|
1472
|
+
item_external_id: parent.item_external_id,
|
|
1473
|
+
item_name: parent.item_name,
|
|
1474
|
+
chunk_cosine_distance: 0,
|
|
1475
|
+
chunk_fts_rank: 0,
|
|
1476
|
+
chunk_hybrid_score: 0,
|
|
1477
|
+
context
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
return Array.from(merged.values()).sort(
|
|
1481
|
+
(a, b) => a.item_id === b.item_id ? a.chunk_index - b.chunk_index : 0
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1421
1484
|
|
|
1422
1485
|
// src/graphql/resolvers/field-allow-list.ts
|
|
1423
1486
|
var ALWAYS_ALLOWED = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt"]);
|
|
@@ -2385,6 +2448,14 @@ var agentsSchema = {
|
|
|
2385
2448
|
name: "max_tool_steps",
|
|
2386
2449
|
type: "number"
|
|
2387
2450
|
},
|
|
2451
|
+
{
|
|
2452
|
+
// Thinking budget of the answer model, forwarded as LiteLLM's
|
|
2453
|
+
// reasoning_effort ("none" | "disable" | "minimal" | "low" | "medium" |
|
|
2454
|
+
// "high"). null = provider default. See resolve-reasoning-effort.ts.
|
|
2455
|
+
// Auto-ALTERed on boot.
|
|
2456
|
+
name: "reasoning_effort",
|
|
2457
|
+
type: "text"
|
|
2458
|
+
},
|
|
2388
2459
|
{
|
|
2389
2460
|
name: "guest_access",
|
|
2390
2461
|
type: "boolean",
|
|
@@ -3099,6 +3170,14 @@ var convertContextToTableDefinition = (context) => {
|
|
|
3099
3170
|
return addCoreFields(definition);
|
|
3100
3171
|
};
|
|
3101
3172
|
|
|
3173
|
+
// src/graphql/resolvers/query-embedding-policy.ts
|
|
3174
|
+
function needsQueryEmbedding(method) {
|
|
3175
|
+
return method !== "tsvector";
|
|
3176
|
+
}
|
|
3177
|
+
function boostsWithQueryEntities(method) {
|
|
3178
|
+
return method !== "tsvector";
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3102
3181
|
// src/exulu/entities/config.ts
|
|
3103
3182
|
var hydrateEntityTypes = async (context) => {
|
|
3104
3183
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -3863,7 +3942,9 @@ var vectorSearch = async ({
|
|
|
3863
3942
|
const embedText = texts.embedText;
|
|
3864
3943
|
hybridOrQuery = texts.hybridOrQuery;
|
|
3865
3944
|
query = texts.ftsText;
|
|
3866
|
-
if (
|
|
3945
|
+
if (!needsQueryEmbedding(method)) {
|
|
3946
|
+
_embedSource = "none";
|
|
3947
|
+
} else if (queryEmbedding && queryEmbedding.length) {
|
|
3867
3948
|
vector = queryEmbedding;
|
|
3868
3949
|
_embedSource = "reused";
|
|
3869
3950
|
} else {
|
|
@@ -3896,8 +3977,10 @@ var vectorSearch = async ({
|
|
|
3896
3977
|
vector = queryVector;
|
|
3897
3978
|
_embedSource = "computed";
|
|
3898
3979
|
}
|
|
3899
|
-
|
|
3900
|
-
|
|
3980
|
+
if (vector.length) {
|
|
3981
|
+
vectorStr = `ARRAY[${vector.join(",")}]`;
|
|
3982
|
+
vectorExpr = `${vectorStr}::vector`;
|
|
3983
|
+
}
|
|
3901
3984
|
}
|
|
3902
3985
|
let keywordsQuery = [];
|
|
3903
3986
|
if (keywords) {
|
|
@@ -3955,14 +4038,22 @@ var vectorSearch = async ({
|
|
|
3955
4038
|
]);
|
|
3956
4039
|
resultChunks = await chunksQuery;
|
|
3957
4040
|
break;
|
|
3958
|
-
case "hybridSearch":
|
|
4041
|
+
case "hybridSearch": {
|
|
4042
|
+
let strictMatches = false;
|
|
4043
|
+
if (query && hybridOrQuery) {
|
|
4044
|
+
const probe = await db(chunksTable + " as chunks").select(db.raw("1")).whereRaw(`(${languages.map((lang) => `chunks.fts @@ plainto_tsquery('${lang}', ?)`).join(" OR ")})`, languages.map(() => query)).first();
|
|
4045
|
+
strictMatches = Boolean(probe);
|
|
4046
|
+
}
|
|
4047
|
+
const fullText = chooseFullTextQuery({ strictMatches, strictText: query ?? "", orText: hybridOrQuery });
|
|
4048
|
+
const ftsFn = fullText.fn;
|
|
4049
|
+
hybridOrQuery = fullText.text;
|
|
3959
4050
|
const matchCount = Math.min(limit * 2);
|
|
3960
4051
|
const fullTextWeight = 2;
|
|
3961
4052
|
const semanticWeight = 1;
|
|
3962
4053
|
const rrfK = 50;
|
|
3963
|
-
const ftRankExpression = languages.map((lang) => `ts_rank(chunks.fts,
|
|
4054
|
+
const ftRankExpression = languages.map((lang) => `ts_rank(chunks.fts, ${ftsFn}('${lang}', ?))`).join(", ");
|
|
3964
4055
|
const ftRankParams = languages.map(() => hybridOrQuery);
|
|
3965
|
-
const ftMatchExpression = languages.map((lang) => `chunks.fts @@
|
|
4056
|
+
const ftMatchExpression = languages.map((lang) => `chunks.fts @@ ${ftsFn}('${lang}', ?)`).join(" OR ");
|
|
3966
4057
|
const ftMatchParams = languages.map(() => hybridOrQuery);
|
|
3967
4058
|
let fullTextQuery = db(chunksTable + " as chunks").select([
|
|
3968
4059
|
"chunks.id",
|
|
@@ -4003,7 +4094,7 @@ var vectorSearch = async ({
|
|
|
4003
4094
|
db.raw('items."updatedAt" as item_updated_at'),
|
|
4004
4095
|
db.raw('items."createdAt" as item_created_at'),
|
|
4005
4096
|
db.raw(
|
|
4006
|
-
`GREATEST(${languages.map((lang) => `ts_rank(chunks.fts,
|
|
4097
|
+
`GREATEST(${languages.map((lang) => `ts_rank(chunks.fts, ${ftsFn}('${lang}', ?))`).join(", ")}) AS fts_rank`,
|
|
4007
4098
|
languages.map(() => hybridOrQuery)
|
|
4008
4099
|
),
|
|
4009
4100
|
db.raw(`(1 - (chunks.embedding <=> ${vectorExpr})) AS cosine_distance`),
|
|
@@ -4029,12 +4120,14 @@ var vectorSearch = async ({
|
|
|
4029
4120
|
`,
|
|
4030
4121
|
[rrfK, fullTextWeight, rrfK, semanticWeight, cutoffs?.hybrid || 0]
|
|
4031
4122
|
).whereRaw(
|
|
4032
|
-
`(chunks.fts IS NULL OR GREATEST(${languages.map((lang) => `ts_rank(chunks.fts,
|
|
4123
|
+
`(chunks.fts IS NULL OR GREATEST(${languages.map((lang) => `ts_rank(chunks.fts, ${ftsFn}('${lang}', ?))`).join(", ")}) > ?)`,
|
|
4033
4124
|
[...languages.map(() => hybridOrQuery), cutoffs?.tsvector || 0]
|
|
4034
4125
|
).whereRaw(`(chunks.embedding IS NULL OR (1 - (chunks.embedding <=> ${vectorExpr})) >= ?)`, [
|
|
4035
4126
|
cutoffs?.cosineDistance || 0
|
|
4036
4127
|
]).orderByRaw("hybrid_score DESC").limit(Math.min(matchCount, 250));
|
|
4037
4128
|
resultChunks = await hybridQuery;
|
|
4129
|
+
break;
|
|
4130
|
+
}
|
|
4038
4131
|
}
|
|
4039
4132
|
if (process.env.EXULU_VS_TIMING) {
|
|
4040
4133
|
console.log(
|
|
@@ -4086,7 +4179,7 @@ var vectorSearch = async ({
|
|
|
4086
4179
|
}
|
|
4087
4180
|
let queryEntities = [];
|
|
4088
4181
|
let entityInsights;
|
|
4089
|
-
if (entitiesOn && rawQuery) {
|
|
4182
|
+
if (entitiesOn && rawQuery && boostsWithQueryEntities(method)) {
|
|
4090
4183
|
try {
|
|
4091
4184
|
const types = await hydrateEntityTypes(context);
|
|
4092
4185
|
const { mentions: queryMentions } = await extractEntitiesForItem({
|
|
@@ -4118,104 +4211,18 @@ var vectorSearch = async ({
|
|
|
4118
4211
|
}
|
|
4119
4212
|
results = results.slice(0, limit);
|
|
4120
4213
|
if (expand?.before || expand?.after) {
|
|
4121
|
-
const
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
const indicesToFetch = Array.from(
|
|
4128
|
-
{ length: expand.before },
|
|
4129
|
-
(_, i) => chunk.chunk_index - expand.before + i
|
|
4130
|
-
).filter((index) => index >= 0);
|
|
4131
|
-
await Promise.all(
|
|
4132
|
-
indicesToFetch.map(async (index) => {
|
|
4133
|
-
if (expandedMap.has(`${chunk.item_id}-${index}`)) {
|
|
4134
|
-
return;
|
|
4135
|
-
}
|
|
4136
|
-
const expandedChunk = await db(chunksTable).where({
|
|
4137
|
-
source: chunk.item_id,
|
|
4138
|
-
chunk_index: index
|
|
4139
|
-
}).first();
|
|
4140
|
-
if (expandedChunk) {
|
|
4141
|
-
if (expandedChunk) {
|
|
4142
|
-
expandedMap.set(`${chunk.item_id}-${index}`, {
|
|
4143
|
-
chunk_content: expandedChunk.content,
|
|
4144
|
-
chunk_index: expandedChunk.chunk_index,
|
|
4145
|
-
chunk_id: expandedChunk.id,
|
|
4146
|
-
chunk_source: expandedChunk.source,
|
|
4147
|
-
chunk_metadata: expandedChunk.metadata,
|
|
4148
|
-
chunk_created_at: expandedChunk.createdAt,
|
|
4149
|
-
chunk_updated_at: expandedChunk.updatedAt,
|
|
4150
|
-
item_updated_at: chunk.item_updated_at,
|
|
4151
|
-
item_created_at: chunk.item_created_at,
|
|
4152
|
-
item_id: chunk.item_id,
|
|
4153
|
-
item_external_id: chunk.item_external_id,
|
|
4154
|
-
item_name: chunk.item_name,
|
|
4155
|
-
chunk_cosine_distance: 0,
|
|
4156
|
-
chunk_fts_rank: 0,
|
|
4157
|
-
chunk_hybrid_score: 0,
|
|
4158
|
-
context: {
|
|
4159
|
-
name: table.name.singular,
|
|
4160
|
-
id: table.id || ""
|
|
4161
|
-
}
|
|
4162
|
-
});
|
|
4163
|
-
}
|
|
4164
|
-
}
|
|
4165
|
-
})
|
|
4166
|
-
);
|
|
4167
|
-
}
|
|
4168
|
-
}
|
|
4169
|
-
if (expand?.after) {
|
|
4170
|
-
for (const chunk of results) {
|
|
4171
|
-
const indicesToFetch = Array.from(
|
|
4172
|
-
{ length: expand.after },
|
|
4173
|
-
(_, i) => chunk.chunk_index + i + 1
|
|
4174
|
-
);
|
|
4175
|
-
await Promise.all(
|
|
4176
|
-
indicesToFetch.map(async (index) => {
|
|
4177
|
-
if (expandedMap.has(`${chunk.item_id}-${index}`)) {
|
|
4178
|
-
return;
|
|
4179
|
-
}
|
|
4180
|
-
const expandedChunk = await db(chunksTable).where({
|
|
4181
|
-
source: chunk.item_id,
|
|
4182
|
-
chunk_index: index
|
|
4183
|
-
}).first();
|
|
4184
|
-
if (expandedChunk) {
|
|
4185
|
-
expandedMap.set(`${chunk.item_id}-${index}`, {
|
|
4186
|
-
chunk_content: expandedChunk.content,
|
|
4187
|
-
chunk_index: expandedChunk.chunk_index,
|
|
4188
|
-
chunk_id: expandedChunk.id,
|
|
4189
|
-
chunk_source: expandedChunk.source,
|
|
4190
|
-
chunk_metadata: expandedChunk.metadata,
|
|
4191
|
-
chunk_created_at: expandedChunk.createdAt,
|
|
4192
|
-
chunk_updated_at: expandedChunk.updatedAt,
|
|
4193
|
-
item_updated_at: chunk.item_updated_at,
|
|
4194
|
-
item_created_at: chunk.item_created_at,
|
|
4195
|
-
item_id: chunk.item_id,
|
|
4196
|
-
item_external_id: chunk.item_external_id,
|
|
4197
|
-
item_name: chunk.item_name,
|
|
4198
|
-
chunk_cosine_distance: 0,
|
|
4199
|
-
chunk_fts_rank: 0,
|
|
4200
|
-
chunk_hybrid_score: 0,
|
|
4201
|
-
context: {
|
|
4202
|
-
name: table.name.singular,
|
|
4203
|
-
id: table.id || ""
|
|
4204
|
-
}
|
|
4205
|
-
});
|
|
4206
|
-
}
|
|
4207
|
-
})
|
|
4208
|
-
);
|
|
4209
|
-
}
|
|
4214
|
+
const plan = planNeighbourFetch(results, expand);
|
|
4215
|
+
if (plan.size > 0) {
|
|
4216
|
+
const itemIds = Array.from(plan.keys());
|
|
4217
|
+
const indices = Array.from(new Set(Array.from(plan.values()).flatMap((s) => Array.from(s))));
|
|
4218
|
+
const rows = await db(chunksTable).select(["id", "source", "chunk_index", "content", "metadata", "createdAt", "updatedAt"]).whereIn("source", itemIds).whereIn("chunk_index", indices);
|
|
4219
|
+
results = mergeNeighbours(results, rows, plan, { name: table.name.singular, id: table.id || "" });
|
|
4210
4220
|
}
|
|
4211
|
-
results = Array.from(expandedMap.values());
|
|
4212
4221
|
results = results.sort((a, b) => {
|
|
4213
4222
|
if (a.item_id !== b.item_id) {
|
|
4214
4223
|
return a.item_id.localeCompare(b.item_id);
|
|
4215
4224
|
}
|
|
4216
|
-
|
|
4217
|
-
const bIndex = Number(b.chunk_index);
|
|
4218
|
-
return aIndex - bIndex;
|
|
4225
|
+
return Number(a.chunk_index) - Number(b.chunk_index);
|
|
4219
4226
|
});
|
|
4220
4227
|
}
|
|
4221
4228
|
if (entitiesOn) {
|
|
@@ -7741,6 +7748,19 @@ function serializeError(err, depth = 0) {
|
|
|
7741
7748
|
return { message: String(err) };
|
|
7742
7749
|
}
|
|
7743
7750
|
|
|
7751
|
+
// src/exulu/turn-metadata.ts
|
|
7752
|
+
function finishTurnMetadata(opts) {
|
|
7753
|
+
const now = opts.now ?? Date.now();
|
|
7754
|
+
return {
|
|
7755
|
+
totalTokens: opts.totalUsage.totalTokens,
|
|
7756
|
+
reasoningTokens: opts.totalUsage.reasoningTokens,
|
|
7757
|
+
inputTokens: opts.totalUsage.inputTokens,
|
|
7758
|
+
outputTokens: opts.totalUsage.outputTokens,
|
|
7759
|
+
cachedInputTokens: opts.totalUsage.cachedInputTokens,
|
|
7760
|
+
durationMs: Math.max(0, now - opts.startedAt)
|
|
7761
|
+
};
|
|
7762
|
+
}
|
|
7763
|
+
|
|
7744
7764
|
// src/exulu/agent-as-tool.ts
|
|
7745
7765
|
import { z as z2 } from "zod";
|
|
7746
7766
|
|
|
@@ -7846,6 +7866,54 @@ async function resolveFreshFileUrl(url, opts) {
|
|
|
7846
7866
|
}
|
|
7847
7867
|
}
|
|
7848
7868
|
|
|
7869
|
+
// src/exulu/session-file-listing.ts
|
|
7870
|
+
var DEFAULT_MAX = 25;
|
|
7871
|
+
function formatSize(bytes) {
|
|
7872
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
7873
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
7874
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
7875
|
+
}
|
|
7876
|
+
function formatAge(then, now) {
|
|
7877
|
+
const minutes = Math.max(0, Math.round((now.getTime() - then.getTime()) / 6e4));
|
|
7878
|
+
if (minutes < 1) return "just now";
|
|
7879
|
+
if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
7880
|
+
const hours = Math.round(minutes / 60);
|
|
7881
|
+
if (hours < 48) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
7882
|
+
const days = Math.round(hours / 24);
|
|
7883
|
+
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
7884
|
+
}
|
|
7885
|
+
function describeSessionFiles(files, opts = {}) {
|
|
7886
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
7887
|
+
const max = opts.max ?? DEFAULT_MAX;
|
|
7888
|
+
const usable = files.filter((f) => f.name && !f.name.endsWith("/") && !isIgnoredArtifactPath(f.name)).sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
7889
|
+
if (usable.length === 0) return "";
|
|
7890
|
+
const shown = usable.slice(0, max);
|
|
7891
|
+
const lines = shown.map((f) => {
|
|
7892
|
+
const isNew = opts.lastTurnAt ? f.lastModified.getTime() > opts.lastTurnAt.getTime() : false;
|
|
7893
|
+
return `- ${f.name} (${formatSize(f.size)}, ${formatAge(f.lastModified, now)})${isNew ? " [NEW since your last answer]" : ""}`;
|
|
7894
|
+
});
|
|
7895
|
+
const omitted = usable.length - shown.length;
|
|
7896
|
+
if (omitted > 0) lines.push(`\u2026 and ${omitted} more file${omitted === 1 ? "" : "s"} (list them with \`ls\`).`);
|
|
7897
|
+
return "Files currently in this session (newest first):\n" + lines.join("\n") + '\nThese files are available to you. When the user refers to one of these files, to "the document" or "the attachment", or asks something only such a file can answer, read it with parse_document, view_document_page or read_session_file. Otherwise proceed as usual, e.g. with the knowledge bases.';
|
|
7898
|
+
}
|
|
7899
|
+
async function loadSessionFileListing(opts) {
|
|
7900
|
+
const uploads = opts.exuluConfig?.fileUploads;
|
|
7901
|
+
if (!uploads?.s3Bucket) return "";
|
|
7902
|
+
const prefix = sessionFilePrefix(opts.ownerId, opts.sessionID, uploads.s3prefix);
|
|
7903
|
+
try {
|
|
7904
|
+
const objects = await listS3ObjectsByPrefix(prefix, opts.exuluConfig);
|
|
7905
|
+
const files = objects.map((o) => ({
|
|
7906
|
+
name: o.key.slice(o.key.indexOf(prefix) + prefix.length),
|
|
7907
|
+
size: o.size,
|
|
7908
|
+
lastModified: new Date(o.lastModified)
|
|
7909
|
+
}));
|
|
7910
|
+
return describeSessionFiles(files, { lastTurnAt: opts.lastTurnAt });
|
|
7911
|
+
} catch (err) {
|
|
7912
|
+
console.warn(`[EXULU] could not list session files for prompt (session ${opts.sessionID}):`, err);
|
|
7913
|
+
return "";
|
|
7914
|
+
}
|
|
7915
|
+
}
|
|
7916
|
+
|
|
7849
7917
|
// src/exulu/generate-stream.ts
|
|
7850
7918
|
import {
|
|
7851
7919
|
convertToModelMessages,
|
|
@@ -8085,11 +8153,12 @@ function resolveTurnStepBudget(maxStepCount, agent) {
|
|
|
8085
8153
|
}
|
|
8086
8154
|
return DEFAULT_MAX_STEPS;
|
|
8087
8155
|
}
|
|
8156
|
+
var TOOL_INPUT_FLATTEN_CHARS = 6e3;
|
|
8088
8157
|
function flattenPart(part) {
|
|
8089
8158
|
const p = part;
|
|
8090
8159
|
if (p?.type === "text") return p.text ?? "";
|
|
8091
8160
|
if (p?.type === "tool-call") {
|
|
8092
|
-
return `Earlier, the assistant ran the "${p.toolName}" tool with input: ${JSON.stringify(p.input ?? {}).slice(0,
|
|
8161
|
+
return `Earlier, the assistant ran the "${p.toolName}" tool with input: ${JSON.stringify(p.input ?? {}).slice(0, TOOL_INPUT_FLATTEN_CHARS)}`;
|
|
8093
8162
|
}
|
|
8094
8163
|
if (p?.type === "tool-result") {
|
|
8095
8164
|
const out = p.output?.value ?? p.output;
|
|
@@ -8111,7 +8180,7 @@ function flattenToolHistory(messages) {
|
|
|
8111
8180
|
return m;
|
|
8112
8181
|
});
|
|
8113
8182
|
}
|
|
8114
|
-
var FINAL_ANSWER_INSTRUCTION = `This is your last step for this turn. Answer the user's original question now, in plain text, using only the information gathered above. If you could not finish the task, tell the user you reached the maximum number of tool steps, summarize what you found and did so far, and say what remains \u2014 they can ask you to continue. Do not attempt any further tool calls. Write your answer as normal prose for the user: do not output tool-call syntax, JSON commands, or bracketed lines such as "[called tool ...]" \u2014 describe anything you did or still plan to do in plain language.`;
|
|
8183
|
+
var FINAL_ANSWER_INSTRUCTION = `This is your last step for this turn. Answer the user's original question now, in plain text, using only the information gathered above. Do not invent, estimate or "fill in" values that were not actually gathered: report only what the tools returned or what you wrote down, and name explicitly what is missing. If you could not finish the task, tell the user you reached the maximum number of tool steps, summarize what you found and did so far, and say what remains \u2014 they can ask you to continue. Do not attempt any further tool calls. Write your answer as normal prose for the user: do not output tool-call syntax, JSON commands, or bracketed lines such as "[called tool ...]" \u2014 describe anything you did or still plan to do in plain language.`;
|
|
8115
8184
|
function finalAnswerGuard(maxSteps) {
|
|
8116
8185
|
return ({ stepNumber, messages }) => stepNumber >= maxSteps - 1 ? {
|
|
8117
8186
|
toolChoice: "none",
|
|
@@ -8136,6 +8205,35 @@ function retrievalBudgetGuard(limit, agenticToolKey, allToolKeys) {
|
|
|
8136
8205
|
};
|
|
8137
8206
|
}
|
|
8138
8207
|
|
|
8208
|
+
// src/exulu/resolve-reasoning-effort.ts
|
|
8209
|
+
var REASONING_EFFORTS = ["none", "disable", "minimal", "low", "medium", "high"];
|
|
8210
|
+
function resolveReasoningEffort(agent) {
|
|
8211
|
+
const raw = agent?.reasoning_effort;
|
|
8212
|
+
if (typeof raw !== "string") return void 0;
|
|
8213
|
+
const normalized = raw.trim().toLowerCase();
|
|
8214
|
+
return REASONING_EFFORTS.includes(normalized) ? normalized : void 0;
|
|
8215
|
+
}
|
|
8216
|
+
function resolveProviderOptions(agent) {
|
|
8217
|
+
const effort = resolveReasoningEffort(agent);
|
|
8218
|
+
return {
|
|
8219
|
+
openai: { reasoningSummary: "auto" },
|
|
8220
|
+
...effort ? { litellm: { reasoningEffort: effort } } : {}
|
|
8221
|
+
};
|
|
8222
|
+
}
|
|
8223
|
+
|
|
8224
|
+
// src/exulu/stream-error.ts
|
|
8225
|
+
function onChatStreamError({ error }) {
|
|
8226
|
+
const detail = error instanceof Error ? error.message : error === void 0 ? "unknown error" : safeStringify(error);
|
|
8227
|
+
console.error("[EXULU] chat stream error.", detail);
|
|
8228
|
+
}
|
|
8229
|
+
function safeStringify(value) {
|
|
8230
|
+
try {
|
|
8231
|
+
return JSON.stringify(value) ?? String(value);
|
|
8232
|
+
} catch {
|
|
8233
|
+
return String(value);
|
|
8234
|
+
}
|
|
8235
|
+
}
|
|
8236
|
+
|
|
8139
8237
|
// src/exulu/generate-stream.ts
|
|
8140
8238
|
var processFilePartsInMessages = async (messages, offloadCtx) => {
|
|
8141
8239
|
const processedMessages = await Promise.all(
|
|
@@ -8229,6 +8327,15 @@ var saveChat = async ({
|
|
|
8229
8327
|
await mutation;
|
|
8230
8328
|
}
|
|
8231
8329
|
};
|
|
8330
|
+
var lastMessageTime = (rows) => {
|
|
8331
|
+
let latest;
|
|
8332
|
+
for (const row of rows) {
|
|
8333
|
+
if (!row.createdAt) continue;
|
|
8334
|
+
const d = new Date(row.createdAt);
|
|
8335
|
+
if (!latest || d > latest) latest = d;
|
|
8336
|
+
}
|
|
8337
|
+
return latest;
|
|
8338
|
+
};
|
|
8232
8339
|
var getAgentMessages = async ({
|
|
8233
8340
|
session,
|
|
8234
8341
|
user,
|
|
@@ -8285,10 +8392,13 @@ var generateSync = async ({
|
|
|
8285
8392
|
}
|
|
8286
8393
|
let project;
|
|
8287
8394
|
let sessionItems;
|
|
8395
|
+
let sessionOwnerId;
|
|
8396
|
+
let lastTurnAt;
|
|
8288
8397
|
if (session) {
|
|
8289
8398
|
const sessionData = await getSession({ sessionID: session });
|
|
8290
8399
|
sessionItems = sessionData.session_items;
|
|
8291
8400
|
project = sessionData.project;
|
|
8401
|
+
sessionOwnerId = sessionData.user ?? void 0;
|
|
8292
8402
|
}
|
|
8293
8403
|
const model = languageModel;
|
|
8294
8404
|
console.log("[EXULU] Model created for generating sync.");
|
|
@@ -8298,6 +8408,7 @@ var generateSync = async ({
|
|
|
8298
8408
|
session,
|
|
8299
8409
|
user: user.id
|
|
8300
8410
|
});
|
|
8411
|
+
lastTurnAt = lastMessageTime(previousMessages);
|
|
8301
8412
|
const previousMessagesContent = previousMessages.map(
|
|
8302
8413
|
(message) => JSON.parse(message.content)
|
|
8303
8414
|
);
|
|
@@ -8392,7 +8503,8 @@ var generateSync = async ({
|
|
|
8392
8503
|
agent,
|
|
8393
8504
|
memoryItems,
|
|
8394
8505
|
contextWindow,
|
|
8395
|
-
disabledTools
|
|
8506
|
+
disabledTools,
|
|
8507
|
+
sessionOwnerId
|
|
8396
8508
|
);
|
|
8397
8509
|
const agenticEntry = currentTools?.find((t) => t.id === "agentic_context_search");
|
|
8398
8510
|
const agenticToolKey = agenticEntry ? sanitizeToolName(agenticEntry.name) : void 0;
|
|
@@ -8459,6 +8571,15 @@ var generateSync = async ({
|
|
|
8459
8571
|
commands like \`node create_doc.js\`) live in the same place. These files are scoped to
|
|
8460
8572
|
this single session; they are NOT visible in other sessions, projects, or knowledge bases.
|
|
8461
8573
|
`;
|
|
8574
|
+
if (session) {
|
|
8575
|
+
const listing = await loadSessionFileListing({
|
|
8576
|
+
sessionID: session,
|
|
8577
|
+
ownerId: sessionOwnerId ?? user?.id ?? "api",
|
|
8578
|
+
exuluConfig,
|
|
8579
|
+
lastTurnAt
|
|
8580
|
+
});
|
|
8581
|
+
if (listing) system += "\n\n" + listing;
|
|
8582
|
+
}
|
|
8462
8583
|
system += `
|
|
8463
8584
|
|
|
8464
8585
|
When a tool execution is not approved by the user, do not retry it unless explicitly asked by the user. ' +
|
|
@@ -8627,10 +8748,13 @@ var generateStream = async ({
|
|
|
8627
8748
|
let previousMessagesContent = previousMessages || [];
|
|
8628
8749
|
let project;
|
|
8629
8750
|
let sessionItems;
|
|
8751
|
+
let sessionOwnerId;
|
|
8752
|
+
let lastTurnAt;
|
|
8630
8753
|
if (session) {
|
|
8631
8754
|
const sessionData = await getSession({ sessionID: session });
|
|
8632
8755
|
project = sessionData.project;
|
|
8633
8756
|
sessionItems = sessionData.session_items;
|
|
8757
|
+
sessionOwnerId = sessionData.user ?? void 0;
|
|
8634
8758
|
console.log("[EXULU] loading previous messages from session: " + session);
|
|
8635
8759
|
const previousMessages2 = await getAgentMessages({
|
|
8636
8760
|
session,
|
|
@@ -8641,6 +8765,7 @@ var generateStream = async ({
|
|
|
8641
8765
|
includeAllUsers: isRunSessionMetadata(sessionData.metadata)
|
|
8642
8766
|
});
|
|
8643
8767
|
previousMessagesContent = previousMessages2.map((message2) => JSON.parse(message2.content));
|
|
8768
|
+
lastTurnAt = lastMessageTime(previousMessages2);
|
|
8644
8769
|
}
|
|
8645
8770
|
const model = languageModel;
|
|
8646
8771
|
messages = await validateUIMessages({
|
|
@@ -8783,6 +8908,15 @@ ${skillsList}
|
|
|
8783
8908
|
truncation notice, e.g. tool-output-*.txt). Use the read_session_file tool with offset/limit
|
|
8784
8909
|
to page through it \u2014 do not ask the user to re-upload.
|
|
8785
8910
|
`;
|
|
8911
|
+
if (session) {
|
|
8912
|
+
const listing = await loadSessionFileListing({
|
|
8913
|
+
sessionID: session,
|
|
8914
|
+
ownerId: sessionOwnerId ?? user?.id ?? "api",
|
|
8915
|
+
exuluConfig,
|
|
8916
|
+
lastTurnAt
|
|
8917
|
+
});
|
|
8918
|
+
if (listing) system += "\n\n" + listing;
|
|
8919
|
+
}
|
|
8786
8920
|
system += `
|
|
8787
8921
|
|
|
8788
8922
|
When a tool execution is not approved by the user, do not retry it unless explicitly asked by the user. ' +
|
|
@@ -8810,7 +8944,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
8810
8944
|
agent,
|
|
8811
8945
|
memoryItems,
|
|
8812
8946
|
contextWindow,
|
|
8813
|
-
disabledTools
|
|
8947
|
+
disabledTools,
|
|
8948
|
+
sessionOwnerId
|
|
8814
8949
|
);
|
|
8815
8950
|
console.log("[EXULU] Converted tools", Object.keys(tools));
|
|
8816
8951
|
const includesContextSearchTool = currentTools?.some(
|
|
@@ -8884,18 +9019,12 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
8884
9019
|
// for the first step or change other parameters.
|
|
8885
9020
|
system,
|
|
8886
9021
|
maxRetries: 2,
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
}
|
|
8891
|
-
},
|
|
9022
|
+
// OpenAI reasoning summaries + the agent's optional thinking budget
|
|
9023
|
+
// (agents.reasoning_effort → LiteLLM reasoning_effort).
|
|
9024
|
+
providerOptions: resolveProviderOptions(agent),
|
|
8892
9025
|
tools,
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
throw new Error(
|
|
8896
|
-
`Chat stream error: ${error instanceof Error ? error.message : JSON.stringify(error)}`
|
|
8897
|
-
);
|
|
8898
|
-
},
|
|
9026
|
+
// Log only — throwing here crashed the process (see stream-error.ts).
|
|
9027
|
+
onError: onChatStreamError,
|
|
8899
9028
|
// todo allow configuring the step budget per skill
|
|
8900
9029
|
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
8901
9030
|
stopWhen: [stepCountIs(turnBudget), hasToolCall("image_generation")]
|
|
@@ -10738,13 +10867,7 @@ var processUiMessagesFlow = async ({
|
|
|
10738
10867
|
messageMetadata: ({ part }) => {
|
|
10739
10868
|
console.log("[EXULU] part", part.type);
|
|
10740
10869
|
if (part.type === "finish") {
|
|
10741
|
-
return {
|
|
10742
|
-
totalTokens: part.totalUsage.totalTokens,
|
|
10743
|
-
reasoningTokens: part.totalUsage.reasoningTokens,
|
|
10744
|
-
inputTokens: part.totalUsage.inputTokens,
|
|
10745
|
-
outputTokens: part.totalUsage.outputTokens,
|
|
10746
|
-
cachedInputTokens: part.totalUsage.cachedInputTokens
|
|
10747
|
-
};
|
|
10870
|
+
return finishTurnMetadata({ totalUsage: part.totalUsage, startedAt: startTime });
|
|
10748
10871
|
}
|
|
10749
10872
|
return void 0;
|
|
10750
10873
|
},
|
|
@@ -16679,6 +16802,7 @@ var createExpressRoutes = async (app, tools, contexts, config, evals, tracer) =>
|
|
|
16679
16802
|
|
|
16680
16803
|
${customInstructions}` : agent.instructions;
|
|
16681
16804
|
if (headers.session) markStreamActive(headers.session);
|
|
16805
|
+
const turnStartedAt = Date.now();
|
|
16682
16806
|
let result;
|
|
16683
16807
|
try {
|
|
16684
16808
|
result = await generateStream({
|
|
@@ -16720,13 +16844,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
16720
16844
|
};
|
|
16721
16845
|
}
|
|
16722
16846
|
if (part.type === "finish") {
|
|
16723
|
-
return {
|
|
16724
|
-
totalTokens: part.totalUsage.totalTokens,
|
|
16725
|
-
reasoningTokens: part.totalUsage.reasoningTokens,
|
|
16726
|
-
inputTokens: part.totalUsage.inputTokens,
|
|
16727
|
-
outputTokens: part.totalUsage.outputTokens,
|
|
16728
|
-
cachedInputTokens: part.totalUsage.cachedInputTokens
|
|
16729
|
-
};
|
|
16847
|
+
return finishTurnMetadata({ totalUsage: part.totalUsage, startedAt: turnStartedAt });
|
|
16730
16848
|
}
|
|
16731
16849
|
return void 0;
|
|
16732
16850
|
},
|
|
@@ -24145,7 +24263,7 @@ function reconstructTableHeaders(document, validationResults, verbose = false) {
|
|
|
24145
24263
|
}
|
|
24146
24264
|
}
|
|
24147
24265
|
async function validateWithVLM(document, model, verbose = false, concurrency = 10) {
|
|
24148
|
-
console.log(`[EXULU] Starting VLM validation for
|
|
24266
|
+
console.log(`[EXULU] Starting VLM validation for processor output, ${document.length} pages...`);
|
|
24149
24267
|
console.log(`[EXULU] Concurrency limit: ${concurrency}`);
|
|
24150
24268
|
const limit = pLimit(concurrency);
|
|
24151
24269
|
const validationResults = /* @__PURE__ */ new Map();
|
|
@@ -24280,48 +24398,7 @@ async function processDocument(filePath, fileType, buffer, tempDir, config, verb
|
|
|
24280
24398
|
async function processPdf(buffer, paths, config, verbose = false) {
|
|
24281
24399
|
try {
|
|
24282
24400
|
let json = [];
|
|
24283
|
-
if (config?.processor.name === "
|
|
24284
|
-
console.log(`[EXULU] Validating Python environment...`);
|
|
24285
|
-
const validation = await validatePythonEnvironment(void 0, true);
|
|
24286
|
-
if (!validation.valid) {
|
|
24287
|
-
console.log(`[EXULU] Python environment not ready, setting up automatically...`);
|
|
24288
|
-
console.log(`[EXULU] Reason: ${validation.message}`);
|
|
24289
|
-
const setupResult = await setupPythonEnvironment({
|
|
24290
|
-
verbose: true,
|
|
24291
|
-
force: false
|
|
24292
|
-
// Only setup if not already done
|
|
24293
|
-
});
|
|
24294
|
-
if (!setupResult.success) {
|
|
24295
|
-
throw new Error(`Failed to setup Python environment: ${setupResult.message}
|
|
24296
|
-
|
|
24297
|
-
${setupResult.output || ""}`);
|
|
24298
|
-
}
|
|
24299
|
-
console.log(`[EXULU] Python environment setup completed successfully`);
|
|
24300
|
-
} else {
|
|
24301
|
-
console.log(`[EXULU] Python environment is valid`);
|
|
24302
|
-
}
|
|
24303
|
-
console.log(`[EXULU] Processing document with document_to_markdown.py`);
|
|
24304
|
-
const result = await executePythonScript({
|
|
24305
|
-
scriptPath: "ee/python/documents/processing/document_to_markdown.py",
|
|
24306
|
-
args: [
|
|
24307
|
-
paths.source,
|
|
24308
|
-
"-o",
|
|
24309
|
-
paths.json,
|
|
24310
|
-
"--images-dir",
|
|
24311
|
-
paths.images
|
|
24312
|
-
],
|
|
24313
|
-
timeout: 30 * 60 * 1e3
|
|
24314
|
-
// 30 minutes for large documents
|
|
24315
|
-
});
|
|
24316
|
-
if (result.stderr) {
|
|
24317
|
-
console.log("Processing info:", result.stderr.trim());
|
|
24318
|
-
}
|
|
24319
|
-
if (!result.success) {
|
|
24320
|
-
throw new Error(`Document processing failed: ${result.stderr}`);
|
|
24321
|
-
}
|
|
24322
|
-
const jsonContent = await fs3.promises.readFile(paths.json, "utf-8");
|
|
24323
|
-
json = JSON.parse(jsonContent);
|
|
24324
|
-
} else if (config?.processor.name === "officeparser") {
|
|
24401
|
+
if (config?.processor.name === "officeparser") {
|
|
24325
24402
|
const text = await parseOfficeAsync2(buffer, {
|
|
24326
24403
|
outputErrorToConsole: false,
|
|
24327
24404
|
newlineDelimiter: "\n"
|
|
@@ -24412,14 +24489,16 @@ stderr: ${splitResult.stderr.slice(-1e3)}`
|
|
|
24412
24489
|
image: screenshots.find((s) => s.pageNum === page.pageNum)?.imagePath
|
|
24413
24490
|
}));
|
|
24414
24491
|
fs3.writeFileSync(paths.json, JSON.stringify(json, null, 2));
|
|
24492
|
+
} else {
|
|
24493
|
+
const configured = String(config?.processor?.name ?? "");
|
|
24494
|
+
throw new Error(
|
|
24495
|
+
configured === "" ? "[EXULU] No document processor configured. Set processor.name to one of: mistral, liteparse, officeparser." : `[EXULU] Unknown document processor "${configured}". Supported processors are: mistral, liteparse, officeparser.` + (configured === "docling" ? ' The "docling" processor was removed: it depended on PyMuPDF, which is AGPL-licensed. Use "mistral" for PDF OCR.' : "")
|
|
24496
|
+
);
|
|
24415
24497
|
}
|
|
24416
24498
|
console.log(`[EXULU]
|
|
24417
24499
|
\u2713 Document processing completed successfully`);
|
|
24418
24500
|
console.log(`[EXULU] Total pages: ${json.length}`);
|
|
24419
24501
|
console.log(`[EXULU] Output file: ${paths.json}`);
|
|
24420
|
-
if (config?.vlm?.model) {
|
|
24421
|
-
console.error("[EXULU] VLM validation is only supported when docling is enabled, skipping validation.");
|
|
24422
|
-
}
|
|
24423
24502
|
const vlmModel = config?.vlm?.model ? await resolveVlmModel(config) : void 0;
|
|
24424
24503
|
if (vlmModel && json.length > 0) {
|
|
24425
24504
|
json = await validateWithVLM(
|
|
@@ -24537,9 +24616,6 @@ async function documentProcessor({
|
|
|
24537
24616
|
} = await loadFile(file, name, tempDir);
|
|
24538
24617
|
let supportedTypes = [];
|
|
24539
24618
|
switch (config?.processor.name) {
|
|
24540
|
-
case "docling":
|
|
24541
|
-
supportedTypes = ["pdf", "docx", "doc", "txt", "md", "jpg", "jpeg", "png", "gif", "webp"];
|
|
24542
|
-
break;
|
|
24543
24619
|
case "officeparser":
|
|
24544
24620
|
supportedTypes = ["docx", "pptx", "xlsx", "odt", "odp", "ods", "pdf", "rtf", "csv", "md", "html"];
|
|
24545
24621
|
break;
|