@retrivora-ai/rag-engine 1.7.9 → 1.8.1
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/DocumentChunker-Dh9TvmGG.d.mts +45 -0
- package/dist/DocumentChunker-Dh9TvmGG.d.ts +45 -0
- package/dist/{index-wCRqMtdX.d.mts → ILLMProvider-BfRgI1Xh.d.mts} +59 -1
- package/dist/{index-wCRqMtdX.d.ts → ILLMProvider-BfRgI1Xh.d.ts} +59 -1
- package/dist/MultiTablePostgresProvider-YY7LPNJK.mjs +8 -0
- package/dist/{chunk-UHGYLTTY.mjs → chunk-BFYLQYQU.mjs} +846 -458
- package/dist/chunk-R3RGUMHE.mjs +218 -0
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +1033 -622
- package/dist/handlers/index.mjs +1 -1
- package/dist/{index-BDqz2_Yu.d.ts → index-1Z4GuYBi.d.ts} +7 -1
- package/dist/{index-DhsG2o5q.d.mts → index-BV0z5mb6.d.mts} +7 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +507 -352
- package/dist/index.mjs +427 -253
- package/dist/server.d.mts +35 -5
- package/dist/server.d.ts +35 -5
- package/dist/server.js +1183 -795
- package/dist/server.mjs +163 -176
- package/package.json +4 -2
- package/src/app/page.tsx +1 -2
- package/src/components/DynamicChart.tsx +10 -35
- package/src/components/MessageBubble.tsx +223 -74
- package/src/components/ProductCard.tsx +2 -2
- package/src/components/VisualizationRenderer.tsx +347 -247
- package/src/config/RagConfig.ts +5 -0
- package/src/config/serverConfig.ts +3 -1
- package/src/core/Pipeline.ts +70 -209
- package/src/core/ProviderRegistry.ts +2 -2
- package/src/core/VectorPlugin.ts +9 -0
- package/src/handlers/index.ts +23 -7
- package/src/hooks/useRagChat.ts +2 -2
- package/src/llm/LLMFactory.ts +54 -2
- package/src/llm/providers/AnthropicProvider.ts +12 -8
- package/src/llm/providers/GeminiProvider.ts +188 -143
- package/src/llm/providers/OllamaProvider.ts +7 -3
- package/src/llm/providers/OpenAIProvider.ts +12 -8
- package/src/types/chat.ts +6 -0
- package/src/types/index.ts +81 -0
- package/src/utils/SchemaMapper.ts +129 -0
- package/src/utils/UITransformer.ts +524 -194
- package/dist/DocumentChunker-Bmscbh-X.d.ts +0 -93
- package/dist/DocumentChunker-DCuxrOdM.d.mts +0 -93
- package/dist/PostgreSQLProvider-BMOETDZA.mjs +0 -8
- package/dist/chunk-FLOSGE6A.mjs +0 -202
package/dist/handlers/index.js
CHANGED
|
@@ -26,6 +26,18 @@ var __spreadValues = (a, b) => {
|
|
|
26
26
|
return a;
|
|
27
27
|
};
|
|
28
28
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
29
|
+
var __objRest = (source, exclude) => {
|
|
30
|
+
var target = {};
|
|
31
|
+
for (var prop in source)
|
|
32
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
33
|
+
target[prop] = source[prop];
|
|
34
|
+
if (source != null && __getOwnPropSymbols)
|
|
35
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
36
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
37
|
+
target[prop] = source[prop];
|
|
38
|
+
}
|
|
39
|
+
return target;
|
|
40
|
+
};
|
|
29
41
|
var __esm = (fn, res) => function __init() {
|
|
30
42
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
31
43
|
};
|
|
@@ -322,196 +334,205 @@ var init_PineconeProvider = __esm({
|
|
|
322
334
|
}
|
|
323
335
|
});
|
|
324
336
|
|
|
325
|
-
// src/providers/vectordb/
|
|
326
|
-
var
|
|
327
|
-
__export(
|
|
328
|
-
|
|
337
|
+
// src/providers/vectordb/MultiTablePostgresProvider.ts
|
|
338
|
+
var MultiTablePostgresProvider_exports = {};
|
|
339
|
+
__export(MultiTablePostgresProvider_exports, {
|
|
340
|
+
MultiTablePostgresProvider: () => MultiTablePostgresProvider
|
|
329
341
|
});
|
|
330
|
-
var import_pg,
|
|
331
|
-
var
|
|
332
|
-
"src/providers/vectordb/
|
|
342
|
+
var import_pg, MultiTablePostgresProvider;
|
|
343
|
+
var init_MultiTablePostgresProvider = __esm({
|
|
344
|
+
"src/providers/vectordb/MultiTablePostgresProvider.ts"() {
|
|
333
345
|
"use strict";
|
|
334
346
|
import_pg = require("pg");
|
|
335
347
|
init_BaseVectorProvider();
|
|
336
|
-
|
|
348
|
+
MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
337
349
|
constructor(config) {
|
|
338
|
-
var _a;
|
|
350
|
+
var _a, _b, _c, _d, _e;
|
|
339
351
|
super(config);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
352
|
+
const opts = config.options || {};
|
|
353
|
+
if (!opts.connectionString) {
|
|
354
|
+
throw new Error("[MultiTablePostgresProvider] options.connectionString is required");
|
|
355
|
+
}
|
|
343
356
|
this.connectionString = opts.connectionString;
|
|
344
|
-
this.dimensions = (_a = opts.dimensions) != null ? _a :
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
message: "PostgreSQL connection string is required",
|
|
355
|
-
suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
|
|
356
|
-
severity: "error"
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
|
|
360
|
-
errors.push({
|
|
361
|
-
field: "vectorDb.options.tables",
|
|
362
|
-
message: "PostgreSQL tables must be a string or a string array",
|
|
363
|
-
severity: "error"
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
return errors;
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
static getHealthChecker() {
|
|
371
|
-
return {
|
|
372
|
-
async check(config) {
|
|
373
|
-
const opts = config.options || {};
|
|
374
|
-
const timestamp = Date.now();
|
|
375
|
-
try {
|
|
376
|
-
const { Client } = await import("pg");
|
|
377
|
-
const client = new Client({ connectionString: opts.connectionString });
|
|
378
|
-
await client.connect();
|
|
379
|
-
const result = await client.query(`
|
|
380
|
-
SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
|
|
381
|
-
`);
|
|
382
|
-
const hasVector = result.rows[0].exists;
|
|
383
|
-
await client.end();
|
|
384
|
-
return {
|
|
385
|
-
healthy: true,
|
|
386
|
-
provider: "postgresql",
|
|
387
|
-
capabilities: { pgvectorInstalled: hasVector },
|
|
388
|
-
timestamp
|
|
389
|
-
};
|
|
390
|
-
} catch (error) {
|
|
391
|
-
return {
|
|
392
|
-
healthy: false,
|
|
393
|
-
provider: "postgresql",
|
|
394
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
395
|
-
timestamp
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
};
|
|
357
|
+
this.dimensions = (_a = opts.dimensions) != null ? _a : 768;
|
|
358
|
+
const rawTables = (_c = (_b = opts.tables) != null ? _b : process.env.VECTOR_DB_TABLES) != null ? _c : "";
|
|
359
|
+
this.tables = typeof rawTables === "string" ? rawTables.split(",").map((t) => t.trim()).filter(Boolean) : rawTables;
|
|
360
|
+
if (this.tables.length === 0) {
|
|
361
|
+
console.warn(
|
|
362
|
+
"[MultiTablePostgresProvider] No tables configured. Set VECTOR_DB_TABLES as a comma-separated list of table names or pass options.tables."
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const rawSearchFields = (_e = (_d = opts.searchFields) != null ? _d : process.env.VECTOR_DB_SEARCH_FIELDS) != null ? _e : ["name", "product_name", "productname", "title"];
|
|
366
|
+
this.searchFields = typeof rawSearchFields === "string" ? rawSearchFields.split(",").map((f) => f.trim()).filter(Boolean) : rawSearchFields;
|
|
400
367
|
}
|
|
401
368
|
async initialize() {
|
|
402
369
|
this.pool = new import_pg.Pool({ connectionString: this.connectionString });
|
|
403
370
|
const client = await this.pool.connect();
|
|
404
371
|
try {
|
|
405
|
-
await client.query(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
content TEXT NOT NULL,
|
|
411
|
-
metadata JSONB,
|
|
412
|
-
embedding VECTOR(${this.dimensions})
|
|
413
|
-
)
|
|
414
|
-
`);
|
|
415
|
-
await client.query(`
|
|
416
|
-
CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
|
|
417
|
-
ON ${this.tableName}
|
|
418
|
-
USING hnsw (embedding vector_cosine_ops)
|
|
372
|
+
const res = await client.query(`
|
|
373
|
+
SELECT table_name
|
|
374
|
+
FROM information_schema.columns
|
|
375
|
+
WHERE column_name = 'embedding'
|
|
376
|
+
AND table_schema = 'public'
|
|
419
377
|
`);
|
|
378
|
+
const discoveredTables = res.rows.map((r) => r.table_name);
|
|
379
|
+
if (this.tables.length === 0) {
|
|
380
|
+
this.tables = discoveredTables;
|
|
381
|
+
} else {
|
|
382
|
+
const staticTables = [...this.tables];
|
|
383
|
+
this.tables = staticTables.filter((t) => discoveredTables.includes(t));
|
|
384
|
+
if (this.tables.length < staticTables.length) {
|
|
385
|
+
const missing = staticTables.filter((t) => !discoveredTables.includes(t));
|
|
386
|
+
console.warn(`[MultiTablePostgresProvider] Some configured tables were skipped (missing 'embedding' column): ${missing.join(", ")}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (this.tables.length === 0) {
|
|
390
|
+
console.warn('[MultiTablePostgresProvider] No tables with "embedding" columns found in the database.');
|
|
391
|
+
} else {
|
|
392
|
+
console.log(
|
|
393
|
+
`[MultiTablePostgresProvider] Connected. Searching across ${this.tables.length} table(s): ${this.tables.join(", ")}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
420
396
|
} finally {
|
|
421
397
|
client.release();
|
|
422
398
|
}
|
|
423
399
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
SET namespace = EXCLUDED.namespace,
|
|
432
|
-
content = EXCLUDED.content,
|
|
433
|
-
metadata = EXCLUDED.metadata,
|
|
434
|
-
embedding = EXCLUDED.embedding`,
|
|
435
|
-
[doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), vectorLiteral]
|
|
400
|
+
/**
|
|
401
|
+
* Upsert is not supported for MultiTablePostgresProvider as it's designed for
|
|
402
|
+
* searching across pre-existing tables with varying schemas.
|
|
403
|
+
*/
|
|
404
|
+
async upsert(_doc, _namespace) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
"[MultiTablePostgresProvider] upsert() is not supported in multi-table mode. Please use the standard PostgreSQLProvider for single-table managed indices."
|
|
436
407
|
);
|
|
437
408
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const batch = docs.slice(i, i + BATCH_SIZE);
|
|
446
|
-
const values = [];
|
|
447
|
-
const valuePlaceholders = batch.map((doc, idx) => {
|
|
448
|
-
var _a;
|
|
449
|
-
const offset = idx * 5;
|
|
450
|
-
values.push(doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), `[${doc.vector.join(",")}]`);
|
|
451
|
-
return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}::vector)`;
|
|
452
|
-
}).join(", ");
|
|
453
|
-
const query = `
|
|
454
|
-
INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
|
|
455
|
-
VALUES ${valuePlaceholders}
|
|
456
|
-
ON CONFLICT (id) DO UPDATE
|
|
457
|
-
SET namespace = EXCLUDED.namespace,
|
|
458
|
-
content = EXCLUDED.content,
|
|
459
|
-
metadata = EXCLUDED.metadata,
|
|
460
|
-
embedding = EXCLUDED.embedding
|
|
461
|
-
`;
|
|
462
|
-
await client.query(query, values);
|
|
463
|
-
}
|
|
464
|
-
await client.query("COMMIT");
|
|
465
|
-
} catch (error) {
|
|
466
|
-
await client.query("ROLLBACK");
|
|
467
|
-
throw error;
|
|
468
|
-
} finally {
|
|
469
|
-
client.release();
|
|
470
|
-
}
|
|
409
|
+
/**
|
|
410
|
+
* Batch upsert is not supported for MultiTablePostgresProvider.
|
|
411
|
+
*/
|
|
412
|
+
async batchUpsert(_docs, _namespace) {
|
|
413
|
+
throw new Error(
|
|
414
|
+
"[MultiTablePostgresProvider] batchUpsert() is not supported in multi-table mode."
|
|
415
|
+
);
|
|
471
416
|
}
|
|
472
|
-
|
|
417
|
+
/**
|
|
418
|
+
* Query all configured tables and merge results, sorted by cosine similarity score.
|
|
419
|
+
*/
|
|
420
|
+
async query(vector, topK, _namespace, _filter) {
|
|
421
|
+
var _a, _b, _c;
|
|
422
|
+
if (!this.pool) {
|
|
423
|
+
throw new Error("[MultiTablePostgresProvider] Provider not initialized. Call initialize() first.");
|
|
424
|
+
}
|
|
473
425
|
const vectorLiteral = `[${vector.join(",")}]`;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
426
|
+
const allResults = [];
|
|
427
|
+
console.log(`[MultiTablePostgresProvider] --- Starting Multi-Table Search ---`);
|
|
428
|
+
const queryText = _filter == null ? void 0 : _filter.queryText;
|
|
429
|
+
const entityHints = Array.isArray(_filter == null ? void 0 : _filter.__entityHints) ? _filter.__entityHints.filter(
|
|
430
|
+
(hint) => typeof hint === "object" && hint !== null && typeof hint.value === "string"
|
|
431
|
+
).map((hint) => hint.value.trim().toLowerCase()).filter(Boolean) : [];
|
|
432
|
+
console.log(`[MultiTablePostgresProvider] queryText: "${queryText}"`);
|
|
433
|
+
console.log(`[MultiTablePostgresProvider] entityHints: [${entityHints.join(", ")}]`);
|
|
434
|
+
const getDynamicKeywordQuery = () => {
|
|
435
|
+
if (entityHints.length > 0) {
|
|
436
|
+
return entityHints.map((h) => h.includes(" ") ? `"${h}"` : h).join(" & ");
|
|
437
|
+
}
|
|
438
|
+
if (queryText) {
|
|
439
|
+
return queryText.toLowerCase().replace(/\b(show|me|the|product[s]?|item[s]?|card[s]?|of|category|find|list|browse|what|are|display|all|under|for|in|with|about)\b/g, "").trim().replace(/\s+/g, " & ");
|
|
440
|
+
}
|
|
441
|
+
return "";
|
|
442
|
+
};
|
|
443
|
+
const dynamicKeywordQuery = getDynamicKeywordQuery();
|
|
444
|
+
const queryPromises = this.tables.map(async (table) => {
|
|
445
|
+
try {
|
|
446
|
+
let sqlQuery = "";
|
|
447
|
+
let params = [];
|
|
448
|
+
if (queryText) {
|
|
449
|
+
const hasEntityHints = entityHints.length > 0;
|
|
450
|
+
const exactNameScoreExpr = hasEntityHints ? `+ (
|
|
451
|
+
SELECT COALESCE(MAX(
|
|
452
|
+
CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
|
|
453
|
+
THEN 1.0 ELSE 0.0 END
|
|
454
|
+
), 0)
|
|
455
|
+
FROM jsonb_each_text(to_jsonb(t)) AS kv(key, val)
|
|
456
|
+
WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
|
|
457
|
+
) * 3.0` : "";
|
|
458
|
+
sqlQuery = `
|
|
459
|
+
SELECT *,
|
|
460
|
+
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
461
|
+
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
462
|
+
((1 - (embedding <=> $1::vector)) + (COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) * 2.0) ${exactNameScoreExpr}) AS hybrid_score
|
|
463
|
+
FROM "${table}" t
|
|
464
|
+
ORDER BY hybrid_score DESC
|
|
465
|
+
LIMIT 50
|
|
466
|
+
`;
|
|
467
|
+
params = [vectorLiteral, dynamicKeywordQuery];
|
|
468
|
+
} else {
|
|
469
|
+
sqlQuery = `
|
|
470
|
+
SELECT *,
|
|
471
|
+
(1 - (embedding <=> $1::vector)) AS hybrid_score
|
|
472
|
+
FROM "${table}" t
|
|
473
|
+
ORDER BY hybrid_score DESC
|
|
474
|
+
LIMIT 50
|
|
475
|
+
`;
|
|
476
|
+
params = [vectorLiteral];
|
|
477
|
+
}
|
|
478
|
+
const result = await this.pool.query(sqlQuery, params);
|
|
479
|
+
if (result.rowCount && result.rowCount > 0) {
|
|
480
|
+
console.log(` \u2705 Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].hybrid_score.toFixed(4)}`);
|
|
481
|
+
} else {
|
|
482
|
+
console.log(` \u26AA Table "${table}": No relevant matches found.`);
|
|
483
|
+
}
|
|
484
|
+
const tableResults = [];
|
|
485
|
+
for (const row of result.rows) {
|
|
486
|
+
const _a2 = row, { hybrid_score, id } = _a2, rest = __objRest(_a2, ["hybrid_score", "id"]);
|
|
487
|
+
delete rest.embedding;
|
|
488
|
+
delete rest.vector_score;
|
|
489
|
+
delete rest.keyword_score;
|
|
490
|
+
delete rest.exact_name_score;
|
|
491
|
+
const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
|
|
492
|
+
` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
|
|
493
|
+
tableResults.push({
|
|
494
|
+
id: `${table}-${id}`,
|
|
495
|
+
score: parseFloat(String(hybrid_score)),
|
|
496
|
+
content,
|
|
497
|
+
metadata: __spreadProps(__spreadValues({}, rest), { source_table: table })
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return tableResults;
|
|
501
|
+
} catch (err) {
|
|
502
|
+
console.error(
|
|
503
|
+
`[MultiTablePostgresProvider] Error querying table "${table}":`,
|
|
504
|
+
err.message
|
|
505
|
+
);
|
|
506
|
+
return [];
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
const resultsArray = await Promise.all(queryPromises);
|
|
510
|
+
for (const tableResults of resultsArray) {
|
|
511
|
+
allResults.push(...tableResults);
|
|
485
512
|
}
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
params
|
|
497
|
-
);
|
|
498
|
-
return result.rows.map((row) => ({
|
|
499
|
-
id: String(row["id"]),
|
|
500
|
-
score: parseFloat(String(row["score"])),
|
|
501
|
-
content: String(row["content"]),
|
|
502
|
-
metadata: row["metadata"]
|
|
503
|
-
}));
|
|
504
|
-
} finally {
|
|
505
|
-
client.release();
|
|
513
|
+
const resultsByTable = {};
|
|
514
|
+
for (const res of allResults) {
|
|
515
|
+
const table = (_a = res.metadata) == null ? void 0 : _a.source_table;
|
|
516
|
+
if (!resultsByTable[table]) resultsByTable[table] = [];
|
|
517
|
+
resultsByTable[table].push(res);
|
|
518
|
+
}
|
|
519
|
+
const balancedResults = [];
|
|
520
|
+
const tables = Object.keys(resultsByTable);
|
|
521
|
+
for (const table of tables) {
|
|
522
|
+
balancedResults.push(...resultsByTable[table].slice(0, 3));
|
|
506
523
|
}
|
|
524
|
+
const finalSorted = balancedResults.sort((a, b) => b.score - a.score);
|
|
525
|
+
if (finalSorted.length > 0) {
|
|
526
|
+
console.log(`[MultiTablePostgresProvider] --- Search Complete ---`);
|
|
527
|
+
console.log(`[MultiTablePostgresProvider] Final top match from "${(_c = (_b = finalSorted[0].metadata) == null ? void 0 : _b.source_table) != null ? _c : "unknown"}" with score ${finalSorted[0].score.toFixed(4)}`);
|
|
528
|
+
}
|
|
529
|
+
return finalSorted.slice(0, Math.max(topK, 15));
|
|
507
530
|
}
|
|
508
|
-
async delete(
|
|
509
|
-
|
|
510
|
-
const params = namespace ? [id, namespace] : [id];
|
|
511
|
-
await this.pool.query(`DELETE FROM ${this.tableName} ${where}`, params);
|
|
531
|
+
async delete(_id, _namespace) {
|
|
532
|
+
console.warn("[MultiTablePostgresProvider] delete() is a no-op for multi-table mode.");
|
|
512
533
|
}
|
|
513
|
-
async deleteNamespace(
|
|
514
|
-
|
|
534
|
+
async deleteNamespace(_namespace) {
|
|
535
|
+
console.warn("[MultiTablePostgresProvider] deleteNamespace() is a no-op for multi-table mode.");
|
|
515
536
|
}
|
|
516
537
|
async ping() {
|
|
517
538
|
try {
|
|
@@ -522,7 +543,9 @@ var init_PostgreSQLProvider = __esm({
|
|
|
522
543
|
}
|
|
523
544
|
}
|
|
524
545
|
async disconnect() {
|
|
525
|
-
|
|
546
|
+
if (this.pool) {
|
|
547
|
+
await this.pool.end();
|
|
548
|
+
}
|
|
526
549
|
}
|
|
527
550
|
};
|
|
528
551
|
}
|
|
@@ -1691,7 +1714,7 @@ function readEnum(env, name, fallback, allowed) {
|
|
|
1691
1714
|
throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
|
|
1692
1715
|
}
|
|
1693
1716
|
function getEnvConfig(env = process.env, base) {
|
|
1694
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa, _qa, _ra, _sa, _ta, _ua, _va;
|
|
1717
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca, _da, _ea, _fa, _ga, _ha, _ia, _ja, _ka, _la, _ma, _na, _oa, _pa, _qa, _ra, _sa, _ta, _ua, _va, _wa, _xa, _ya, _za, _Aa, _Ba, _Ca, _Da, _Ea, _Fa;
|
|
1695
1718
|
const projectId = (_c = (_b = (_a = readString(env, "RAG_PROJECT_ID")) != null ? _a : readString(env, "NEXT_PUBLIC_PROJECT_ID")) != null ? _b : base == null ? void 0 : base.projectId) != null ? _c : "__default__";
|
|
1696
1719
|
const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
|
|
1697
1720
|
const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
|
|
@@ -1702,33 +1725,35 @@ function getEnvConfig(env = process.env, base) {
|
|
|
1702
1725
|
vectorDbOptions.apiKey = (_g = (_f = readString(env, "PINECONE_API_KEY")) != null ? _f : (_e = (_d = base == null ? void 0 : base.vectorDb) == null ? void 0 : _d.options) == null ? void 0 : _e.apiKey) != null ? _g : "";
|
|
1703
1726
|
vectorDbOptions.indexName = (_l = (_i = readString(env, "PINECONE_INDEX")) != null ? _i : (_h = base == null ? void 0 : base.vectorDb) == null ? void 0 : _h.indexName) != null ? _l : (_k = (_j = base == null ? void 0 : base.vectorDb) == null ? void 0 : _j.options) == null ? void 0 : _k.indexName;
|
|
1704
1727
|
} else if (vectorProvider === "pgvector" || vectorProvider === "postgresql") {
|
|
1705
|
-
vectorDbOptions.connectionString = (_p = (
|
|
1728
|
+
vectorDbOptions.connectionString = (_q = (_p = (_m = readString(env, "PGVECTOR_CONNECTION_STRING")) != null ? _m : readString(env, "POSTGRES_URL")) != null ? _p : (_o = (_n = base == null ? void 0 : base.vectorDb) == null ? void 0 : _n.options) == null ? void 0 : _o.connectionString) != null ? _q : "";
|
|
1729
|
+
vectorDbOptions.tables = (_v = (_s = (_r = readString(env, "VECTOR_DB_TABLES")) != null ? _r : readString(env, "POSTGRES_TABLES")) == null ? void 0 : _s.split(",").map((t) => t.trim())) != null ? _v : (_u = (_t = base == null ? void 0 : base.vectorDb) == null ? void 0 : _t.options) == null ? void 0 : _u.tables;
|
|
1730
|
+
vectorDbOptions.searchFields = (_z = (_w = readString(env, "POSTGRES_SEARCH_FIELDS")) == null ? void 0 : _w.split(",").map((f) => f.trim())) != null ? _z : (_y = (_x = base == null ? void 0 : base.vectorDb) == null ? void 0 : _x.options) == null ? void 0 : _y.searchFields;
|
|
1706
1731
|
vectorDbOptions.dimensions = embeddingDimensions;
|
|
1707
1732
|
} else if (vectorProvider === "mongodb") {
|
|
1708
|
-
vectorDbOptions.uri = (
|
|
1709
|
-
vectorDbOptions.database = (
|
|
1710
|
-
vectorDbOptions.collection = (
|
|
1711
|
-
vectorDbOptions.indexName = (
|
|
1733
|
+
vectorDbOptions.uri = (_D = (_C = readString(env, "MONGODB_URI")) != null ? _C : (_B = (_A = base == null ? void 0 : base.vectorDb) == null ? void 0 : _A.options) == null ? void 0 : _B.uri) != null ? _D : "";
|
|
1734
|
+
vectorDbOptions.database = (_H = (_G = readString(env, "MONGODB_DB")) != null ? _G : (_F = (_E = base == null ? void 0 : base.vectorDb) == null ? void 0 : _E.options) == null ? void 0 : _F.database) != null ? _H : "";
|
|
1735
|
+
vectorDbOptions.collection = (_L = (_K = readString(env, "MONGODB_COLLECTION")) != null ? _K : (_J = (_I = base == null ? void 0 : base.vectorDb) == null ? void 0 : _I.options) == null ? void 0 : _J.collection) != null ? _L : "";
|
|
1736
|
+
vectorDbOptions.indexName = (_R = (_Q = (_N = readString(env, "MONGODB_INDEX_NAME")) != null ? _N : (_M = base == null ? void 0 : base.vectorDb) == null ? void 0 : _M.indexName) != null ? _Q : (_P = (_O = base == null ? void 0 : base.vectorDb) == null ? void 0 : _O.options) == null ? void 0 : _P.indexName) != null ? _R : "vector_index";
|
|
1712
1737
|
} else if (vectorProvider === "qdrant") {
|
|
1713
|
-
vectorDbOptions.baseUrl = (
|
|
1714
|
-
vectorDbOptions.apiKey = (
|
|
1738
|
+
vectorDbOptions.baseUrl = (_V = (_U = readString(env, "QDRANT_URL")) != null ? _U : (_T = (_S = base == null ? void 0 : base.vectorDb) == null ? void 0 : _S.options) == null ? void 0 : _T.baseUrl) != null ? _V : "http://localhost:6333";
|
|
1739
|
+
vectorDbOptions.apiKey = (_Y = readString(env, "QDRANT_API_KEY")) != null ? _Y : (_X = (_W = base == null ? void 0 : base.vectorDb) == null ? void 0 : _W.options) == null ? void 0 : _X.apiKey;
|
|
1715
1740
|
vectorDbOptions.dimensions = embeddingDimensions;
|
|
1716
1741
|
} else if (vectorProvider === "milvus") {
|
|
1717
|
-
vectorDbOptions.baseUrl = (
|
|
1742
|
+
vectorDbOptions.baseUrl = (_Z = readString(env, "MILVUS_URL")) != null ? _Z : "http://localhost:19530";
|
|
1718
1743
|
vectorDbOptions.apiKey = readString(env, "MILVUS_API_KEY");
|
|
1719
1744
|
} else if (vectorProvider === "chromadb") {
|
|
1720
|
-
vectorDbOptions.baseUrl = (
|
|
1745
|
+
vectorDbOptions.baseUrl = (__ = readString(env, "CHROMADB_URL")) != null ? __ : "http://localhost:8000";
|
|
1721
1746
|
} else if (vectorProvider === "weaviate") {
|
|
1722
|
-
vectorDbOptions.baseUrl = (
|
|
1747
|
+
vectorDbOptions.baseUrl = (_$ = readString(env, "WEAVIATE_URL")) != null ? _$ : "http://localhost:8080";
|
|
1723
1748
|
vectorDbOptions.apiKey = readString(env, "WEAVIATE_API_KEY");
|
|
1724
1749
|
} else if (vectorProvider === "redis") {
|
|
1725
|
-
vectorDbOptions.baseUrl = (
|
|
1750
|
+
vectorDbOptions.baseUrl = (_aa = readString(env, "REDIS_URL")) != null ? _aa : "";
|
|
1726
1751
|
vectorDbOptions.apiKey = readString(env, "REDIS_API_KEY");
|
|
1727
1752
|
} else if (vectorProvider === "rest") {
|
|
1728
|
-
vectorDbOptions.baseUrl = (
|
|
1753
|
+
vectorDbOptions.baseUrl = (_ba = readString(env, "VECTOR_DB_REST_URL")) != null ? _ba : "";
|
|
1729
1754
|
vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { "api-key": readString(env, "VECTOR_DB_REST_API_KEY") } : {};
|
|
1730
1755
|
} else if (vectorProvider === "universal_rest") {
|
|
1731
|
-
vectorDbOptions.baseUrl = (
|
|
1756
|
+
vectorDbOptions.baseUrl = (_da = (_ca = readString(env, "VECTOR_BASE_URL")) != null ? _ca : readString(env, "VECTOR_DB_REST_URL")) != null ? _da : "";
|
|
1732
1757
|
vectorDbOptions.profile = readString(env, "VECTOR_UNIVERSAL_PROFILE");
|
|
1733
1758
|
vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { Authorization: `Bearer ${readString(env, "VECTOR_DB_REST_API_KEY")}` } : {};
|
|
1734
1759
|
}
|
|
@@ -1745,20 +1770,20 @@ function getEnvConfig(env = process.env, base) {
|
|
|
1745
1770
|
openai: readString(env, "OPENAI_API_KEY"),
|
|
1746
1771
|
gemini: readString(env, "GEMINI_API_KEY"),
|
|
1747
1772
|
ollama: void 0,
|
|
1748
|
-
universal_rest: (
|
|
1749
|
-
custom: (
|
|
1773
|
+
universal_rest: (_ea = readString(env, "EMBEDDING_API_KEY")) != null ? _ea : readString(env, "OPENAI_API_KEY"),
|
|
1774
|
+
custom: (_fa = readString(env, "EMBEDDING_API_KEY")) != null ? _fa : readString(env, "OPENAI_API_KEY")
|
|
1750
1775
|
};
|
|
1751
1776
|
return {
|
|
1752
1777
|
projectId,
|
|
1753
1778
|
vectorDb: {
|
|
1754
1779
|
provider: vectorProvider,
|
|
1755
|
-
indexName: (
|
|
1780
|
+
indexName: (_ha = (_ga = readString(env, "VECTOR_DB_INDEX")) != null ? _ga : vectorDbOptions.indexName) != null ? _ha : "rag-index",
|
|
1756
1781
|
options: vectorDbOptions
|
|
1757
1782
|
},
|
|
1758
1783
|
llm: {
|
|
1759
1784
|
provider: llmProvider,
|
|
1760
|
-
model: (
|
|
1761
|
-
apiKey: (
|
|
1785
|
+
model: (_ia = readString(env, "LLM_MODEL")) != null ? _ia : "gpt-4o",
|
|
1786
|
+
apiKey: (_ja = llmApiKeyByProvider[llmProvider]) != null ? _ja : "",
|
|
1762
1787
|
baseUrl: readString(env, "LLM_BASE_URL"),
|
|
1763
1788
|
systemPrompt: readString(env, "LLM_SYSTEM_PROMPT"),
|
|
1764
1789
|
maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
|
|
@@ -1769,7 +1794,7 @@ function getEnvConfig(env = process.env, base) {
|
|
|
1769
1794
|
},
|
|
1770
1795
|
embedding: {
|
|
1771
1796
|
provider: embeddingProvider,
|
|
1772
|
-
model: (
|
|
1797
|
+
model: (_ka = readString(env, "EMBEDDING_MODEL")) != null ? _ka : "text-embedding-3-small",
|
|
1773
1798
|
apiKey: embeddingApiKeyByProvider[embeddingProvider],
|
|
1774
1799
|
baseUrl: readString(env, "EMBEDDING_BASE_URL"),
|
|
1775
1800
|
dimensions: embeddingDimensions,
|
|
@@ -1780,17 +1805,17 @@ function getEnvConfig(env = process.env, base) {
|
|
|
1780
1805
|
}
|
|
1781
1806
|
},
|
|
1782
1807
|
ui: {
|
|
1783
|
-
title: (
|
|
1784
|
-
subtitle: (
|
|
1785
|
-
primaryColor: (
|
|
1786
|
-
accentColor: (
|
|
1787
|
-
logoUrl: (
|
|
1788
|
-
placeholder: (
|
|
1789
|
-
showSources: ((
|
|
1790
|
-
welcomeMessage: (
|
|
1791
|
-
visualStyle: (
|
|
1792
|
-
borderRadius: (
|
|
1793
|
-
allowUpload: ((
|
|
1808
|
+
title: (_ma = (_la = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _la : readString(env, "UI_TITLE")) != null ? _ma : "AI Assistant",
|
|
1809
|
+
subtitle: (_oa = (_na = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _na : readString(env, "UI_SUBTITLE")) != null ? _oa : "Powered by RAG",
|
|
1810
|
+
primaryColor: (_qa = (_pa = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _pa : readString(env, "UI_PRIMARY_COLOR")) != null ? _qa : "#10b981",
|
|
1811
|
+
accentColor: (_sa = (_ra = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ra : readString(env, "UI_ACCENT_COLOR")) != null ? _sa : "#3b82f6",
|
|
1812
|
+
logoUrl: (_ta = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _ta : readString(env, "UI_LOGO_URL"),
|
|
1813
|
+
placeholder: (_va = (_ua = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ua : readString(env, "UI_PLACEHOLDER")) != null ? _va : "Ask me anything\u2026",
|
|
1814
|
+
showSources: ((_xa = (_wa = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _wa : readString(env, "UI_SHOW_SOURCES")) != null ? _xa : "true") !== "false",
|
|
1815
|
+
welcomeMessage: (_za = (_ya = readString(env, "NEXT_PUBLIC_WELCOME_MESSAGE")) != null ? _ya : readString(env, "UI_WELCOME_MESSAGE")) != null ? _za : "Hello! I'm your AI assistant. Ask me anything about your documents.",
|
|
1816
|
+
visualStyle: (_Ba = (_Aa = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _Aa : readString(env, "UI_VISUAL_STYLE")) != null ? _Ba : "glass",
|
|
1817
|
+
borderRadius: (_Da = (_Ca = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _Ca : readString(env, "UI_BORDER_RADIUS")) != null ? _Da : "xl",
|
|
1818
|
+
allowUpload: ((_Fa = (_Ea = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _Ea : readString(env, "UI_ALLOW_UPLOAD")) != null ? _Fa : "false") === "true"
|
|
1794
1819
|
},
|
|
1795
1820
|
rag: {
|
|
1796
1821
|
topK: readNumber(env, "RAG_TOP_K", 5),
|
|
@@ -1907,14 +1932,14 @@ var OpenAIProvider = class {
|
|
|
1907
1932
|
};
|
|
1908
1933
|
}
|
|
1909
1934
|
async chat(messages, context, options) {
|
|
1910
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1911
|
-
const
|
|
1935
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1936
|
+
const resolvedSystemPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Answer questions based on the provided context.
|
|
1912
1937
|
|
|
1913
1938
|
Context:
|
|
1914
1939
|
${context}`;
|
|
1915
1940
|
const systemMessage = {
|
|
1916
1941
|
role: "system",
|
|
1917
|
-
content:
|
|
1942
|
+
content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
|
|
1918
1943
|
|
|
1919
1944
|
Context:
|
|
1920
1945
|
${context}`
|
|
@@ -1929,22 +1954,22 @@ ${context}`
|
|
|
1929
1954
|
const completion = await this.client.chat.completions.create({
|
|
1930
1955
|
model: this.llmConfig.model,
|
|
1931
1956
|
messages: formattedMessages,
|
|
1932
|
-
max_tokens: (
|
|
1933
|
-
temperature: (
|
|
1957
|
+
max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
|
|
1958
|
+
temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
|
|
1934
1959
|
stop: options == null ? void 0 : options.stop
|
|
1935
1960
|
});
|
|
1936
|
-
return (
|
|
1961
|
+
return (_i = (_h = (_g = completion.choices[0]) == null ? void 0 : _g.message) == null ? void 0 : _h.content) != null ? _i : "";
|
|
1937
1962
|
}
|
|
1938
1963
|
chatStream(messages, context, options) {
|
|
1939
1964
|
return __asyncGenerator(this, null, function* () {
|
|
1940
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1941
|
-
const
|
|
1965
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1966
|
+
const resolvedSystemPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Answer questions based on the provided context.
|
|
1942
1967
|
|
|
1943
1968
|
Context:
|
|
1944
1969
|
${context}`;
|
|
1945
1970
|
const systemMessage = {
|
|
1946
1971
|
role: "system",
|
|
1947
|
-
content:
|
|
1972
|
+
content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
|
|
1948
1973
|
|
|
1949
1974
|
Context:
|
|
1950
1975
|
${context}`
|
|
@@ -1959,8 +1984,8 @@ ${context}`
|
|
|
1959
1984
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
1960
1985
|
model: this.llmConfig.model,
|
|
1961
1986
|
messages: formattedMessages,
|
|
1962
|
-
max_tokens: (
|
|
1963
|
-
temperature: (
|
|
1987
|
+
max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
|
|
1988
|
+
temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
|
|
1964
1989
|
stop: options == null ? void 0 : options.stop,
|
|
1965
1990
|
stream: true
|
|
1966
1991
|
}));
|
|
@@ -1970,7 +1995,7 @@ ${context}`
|
|
|
1970
1995
|
try {
|
|
1971
1996
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
1972
1997
|
const chunk = temp.value;
|
|
1973
|
-
const content = ((
|
|
1998
|
+
const content = ((_h = (_g = chunk.choices[0]) == null ? void 0 : _g.delta) == null ? void 0 : _h.content) || "";
|
|
1974
1999
|
if (content) yield content;
|
|
1975
2000
|
}
|
|
1976
2001
|
} catch (temp) {
|
|
@@ -2068,12 +2093,12 @@ var AnthropicProvider = class {
|
|
|
2068
2093
|
};
|
|
2069
2094
|
}
|
|
2070
2095
|
async chat(messages, context, options) {
|
|
2071
|
-
var _a, _b, _c;
|
|
2072
|
-
const
|
|
2096
|
+
var _a, _b, _c, _d;
|
|
2097
|
+
const resolvedPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Use the context below to answer the user's question accurately.
|
|
2073
2098
|
|
|
2074
2099
|
Context:
|
|
2075
2100
|
${context}`;
|
|
2076
|
-
const system =
|
|
2101
|
+
const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
|
|
2077
2102
|
|
|
2078
2103
|
Context:
|
|
2079
2104
|
${context}`;
|
|
@@ -2083,7 +2108,7 @@ ${context}`;
|
|
|
2083
2108
|
}));
|
|
2084
2109
|
const response = await this.client.messages.create({
|
|
2085
2110
|
model: this.llmConfig.model,
|
|
2086
|
-
max_tokens: (
|
|
2111
|
+
max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
|
|
2087
2112
|
system,
|
|
2088
2113
|
messages: anthropicMessages
|
|
2089
2114
|
});
|
|
@@ -2092,12 +2117,12 @@ ${context}`;
|
|
|
2092
2117
|
}
|
|
2093
2118
|
chatStream(messages, context, options) {
|
|
2094
2119
|
return __asyncGenerator(this, null, function* () {
|
|
2095
|
-
var _a, _b, _c;
|
|
2096
|
-
const
|
|
2120
|
+
var _a, _b, _c, _d;
|
|
2121
|
+
const resolvedPrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : `You are a helpful assistant. Use the context below to answer the user's question accurately.
|
|
2097
2122
|
|
|
2098
2123
|
Context:
|
|
2099
2124
|
${context}`;
|
|
2100
|
-
const system =
|
|
2125
|
+
const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
|
|
2101
2126
|
|
|
2102
2127
|
Context:
|
|
2103
2128
|
${context}`;
|
|
@@ -2107,7 +2132,7 @@ ${context}`;
|
|
|
2107
2132
|
}));
|
|
2108
2133
|
const stream = yield new __await(this.client.messages.create({
|
|
2109
2134
|
model: this.llmConfig.model,
|
|
2110
|
-
max_tokens: (
|
|
2135
|
+
max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
|
|
2111
2136
|
system,
|
|
2112
2137
|
messages: anthropicMessages,
|
|
2113
2138
|
stream: true
|
|
@@ -2222,7 +2247,7 @@ var OllamaProvider = class {
|
|
|
2222
2247
|
}
|
|
2223
2248
|
async chat(messages, context, options) {
|
|
2224
2249
|
var _a, _b, _c, _d;
|
|
2225
|
-
const system = this.buildSystemPrompt(context);
|
|
2250
|
+
const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
|
|
2226
2251
|
const { data } = await this.http.post("/api/chat", {
|
|
2227
2252
|
model: this.llmConfig.model,
|
|
2228
2253
|
stream: false,
|
|
@@ -2240,7 +2265,7 @@ var OllamaProvider = class {
|
|
|
2240
2265
|
chatStream(messages, context, options) {
|
|
2241
2266
|
return __asyncGenerator(this, null, function* () {
|
|
2242
2267
|
var _a, _b, _c, _d, _e, _f;
|
|
2243
|
-
const system = this.buildSystemPrompt(context);
|
|
2268
|
+
const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
|
|
2244
2269
|
const response = yield new __await(this.http.post("/api/chat", {
|
|
2245
2270
|
model: this.llmConfig.model,
|
|
2246
2271
|
stream: true,
|
|
@@ -2296,8 +2321,11 @@ var OllamaProvider = class {
|
|
|
2296
2321
|
}
|
|
2297
2322
|
});
|
|
2298
2323
|
}
|
|
2299
|
-
buildSystemPrompt(context) {
|
|
2324
|
+
buildSystemPrompt(context, overridePrompt) {
|
|
2300
2325
|
var _a;
|
|
2326
|
+
if (overridePrompt) {
|
|
2327
|
+
return overridePrompt;
|
|
2328
|
+
}
|
|
2301
2329
|
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
|
|
2302
2330
|
|
|
2303
2331
|
Context:
|
|
@@ -2349,20 +2377,41 @@ ${context}`;
|
|
|
2349
2377
|
};
|
|
2350
2378
|
|
|
2351
2379
|
// src/llm/providers/GeminiProvider.ts
|
|
2352
|
-
var
|
|
2380
|
+
var import_generative_ai = require("@google/generative-ai");
|
|
2381
|
+
var DEFAULT_EMBEDDING_MODEL = "text-embedding-004";
|
|
2382
|
+
var DEFAULT_TEMPERATURE = 0.7;
|
|
2383
|
+
var DEFAULT_MAX_TOKENS = 1024;
|
|
2384
|
+
function sanitizeModel(model) {
|
|
2385
|
+
return model ? model.split(":")[0] : model;
|
|
2386
|
+
}
|
|
2387
|
+
function buildClient(apiKey) {
|
|
2388
|
+
return new import_generative_ai.GoogleGenerativeAI(apiKey);
|
|
2389
|
+
}
|
|
2390
|
+
function applyPrefix(text, taskType, queryPrefix, docPrefix) {
|
|
2391
|
+
if (taskType === "query" && queryPrefix && !text.startsWith(queryPrefix)) {
|
|
2392
|
+
return `${queryPrefix}${text}`;
|
|
2393
|
+
}
|
|
2394
|
+
if (taskType === "document" && docPrefix && !text.startsWith(docPrefix)) {
|
|
2395
|
+
return `${docPrefix}${text}`;
|
|
2396
|
+
}
|
|
2397
|
+
return text;
|
|
2398
|
+
}
|
|
2353
2399
|
var GeminiProvider = class {
|
|
2354
2400
|
constructor(llmConfig, embeddingConfig) {
|
|
2355
|
-
if (!llmConfig.apiKey)
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2401
|
+
if (!llmConfig.apiKey) {
|
|
2402
|
+
throw new Error("[GeminiProvider] llmConfig.apiKey is required");
|
|
2403
|
+
}
|
|
2404
|
+
this.llmConfig = __spreadProps(__spreadValues({}, llmConfig), { model: sanitizeModel(llmConfig.model) });
|
|
2405
|
+
this.client = buildClient(this.llmConfig.apiKey);
|
|
2360
2406
|
if (embeddingConfig) {
|
|
2361
2407
|
this.embeddingConfig = __spreadProps(__spreadValues({}, embeddingConfig), {
|
|
2362
|
-
model:
|
|
2408
|
+
model: sanitizeModel(embeddingConfig.model)
|
|
2363
2409
|
});
|
|
2364
2410
|
}
|
|
2365
2411
|
}
|
|
2412
|
+
// -------------------------------------------------------------------------
|
|
2413
|
+
// Static factory helpers
|
|
2414
|
+
// -------------------------------------------------------------------------
|
|
2366
2415
|
static getValidator() {
|
|
2367
2416
|
return {
|
|
2368
2417
|
validate(config) {
|
|
@@ -2376,7 +2425,11 @@ var GeminiProvider = class {
|
|
|
2376
2425
|
});
|
|
2377
2426
|
}
|
|
2378
2427
|
if (!config.model) {
|
|
2379
|
-
errors.push({
|
|
2428
|
+
errors.push({
|
|
2429
|
+
field: "llm.model",
|
|
2430
|
+
message: "Gemini model name is required",
|
|
2431
|
+
severity: "error"
|
|
2432
|
+
});
|
|
2380
2433
|
}
|
|
2381
2434
|
return errors;
|
|
2382
2435
|
}
|
|
@@ -2385,13 +2438,15 @@ var GeminiProvider = class {
|
|
|
2385
2438
|
static getHealthChecker() {
|
|
2386
2439
|
return {
|
|
2387
2440
|
async check(config) {
|
|
2441
|
+
var _a, _b;
|
|
2388
2442
|
const timestamp = Date.now();
|
|
2389
|
-
const apiKey = config.apiKey
|
|
2390
|
-
const modelName = config.model;
|
|
2443
|
+
const apiKey = (_b = (_a = config.apiKey) != null ? _a : process.env.GOOGLE_GENAI_API_KEY) != null ? _b : "";
|
|
2444
|
+
const modelName = sanitizeModel(config.model);
|
|
2391
2445
|
try {
|
|
2392
|
-
const {
|
|
2393
|
-
const
|
|
2394
|
-
|
|
2446
|
+
const { GoogleGenerativeAI: GoogleGenerativeAI2 } = await import("@google/generative-ai");
|
|
2447
|
+
const ai = new GoogleGenerativeAI2(apiKey);
|
|
2448
|
+
const model = ai.getGenerativeModel({ model: DEFAULT_EMBEDDING_MODEL });
|
|
2449
|
+
await model.embedContent("health-check");
|
|
2395
2450
|
return {
|
|
2396
2451
|
healthy: true,
|
|
2397
2452
|
provider: "gemini",
|
|
@@ -2409,70 +2464,90 @@ var GeminiProvider = class {
|
|
|
2409
2464
|
}
|
|
2410
2465
|
};
|
|
2411
2466
|
}
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2467
|
+
// -------------------------------------------------------------------------
|
|
2468
|
+
// Private utilities
|
|
2469
|
+
// -------------------------------------------------------------------------
|
|
2470
|
+
/** Resolve the embedding client — uses a separate client when the embedding
|
|
2471
|
+
* API key differs from the LLM API key. */
|
|
2472
|
+
get embeddingClient() {
|
|
2473
|
+
var _a;
|
|
2474
|
+
if (((_a = this.embeddingConfig) == null ? void 0 : _a.apiKey) && this.embeddingConfig.apiKey !== this.llmConfig.apiKey) {
|
|
2475
|
+
return buildClient(this.embeddingConfig.apiKey);
|
|
2476
|
+
}
|
|
2477
|
+
return this.client;
|
|
2478
|
+
}
|
|
2479
|
+
/** Resolve the embedding model to use, in order of specificity. */
|
|
2480
|
+
resolveEmbeddingModel(optionsModel) {
|
|
2481
|
+
var _a, _b;
|
|
2482
|
+
return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
|
|
2415
2483
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2484
|
+
/**
|
|
2485
|
+
* Build the system instruction string, inserting the RAG context either via
|
|
2486
|
+
* the `{{context}}` placeholder or by appending it.
|
|
2487
|
+
*/
|
|
2488
|
+
buildSystemInstruction(context) {
|
|
2489
|
+
var _a;
|
|
2490
|
+
const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
2419
2491
|
|
|
2420
2492
|
Context:
|
|
2421
2493
|
${context}`;
|
|
2422
|
-
|
|
2494
|
+
return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
|
|
2423
2495
|
|
|
2424
2496
|
Context:
|
|
2425
2497
|
${context}`;
|
|
2426
|
-
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Convert ChatMessage[] to the Gemini contents format.
|
|
2501
|
+
*
|
|
2502
|
+
* Rules enforced by the Gemini API:
|
|
2503
|
+
* - Only `user` and `model` roles are allowed in `contents`.
|
|
2504
|
+
* - System messages must be passed via `systemInstruction`, not here.
|
|
2505
|
+
* - Messages must strictly alternate between `user` and `model`.
|
|
2506
|
+
*/
|
|
2507
|
+
buildGeminiContents(messages) {
|
|
2508
|
+
return messages.filter((m) => m.role !== "system").map((m) => ({
|
|
2427
2509
|
role: m.role === "assistant" ? "model" : "user",
|
|
2428
2510
|
parts: [{ text: m.content }]
|
|
2429
2511
|
}));
|
|
2430
|
-
|
|
2512
|
+
}
|
|
2513
|
+
// -------------------------------------------------------------------------
|
|
2514
|
+
// ILLMProvider — chat
|
|
2515
|
+
// -------------------------------------------------------------------------
|
|
2516
|
+
async chat(messages, context, options) {
|
|
2517
|
+
var _a, _b, _c, _d;
|
|
2518
|
+
const model = this.client.getGenerativeModel({
|
|
2431
2519
|
model: this.llmConfig.model,
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2520
|
+
systemInstruction: this.buildSystemInstruction(context)
|
|
2521
|
+
});
|
|
2522
|
+
const result = await model.generateContent({
|
|
2523
|
+
contents: this.buildGeminiContents(messages),
|
|
2524
|
+
generationConfig: {
|
|
2525
|
+
temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
|
|
2526
|
+
maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
|
|
2437
2527
|
stopSequences: options == null ? void 0 : options.stop
|
|
2438
2528
|
}
|
|
2439
2529
|
});
|
|
2440
|
-
|
|
2441
|
-
return text;
|
|
2530
|
+
return result.response.text();
|
|
2442
2531
|
}
|
|
2443
2532
|
chatStream(messages, context, options) {
|
|
2444
2533
|
return __asyncGenerator(this, null, function* () {
|
|
2445
|
-
var _a, _b, _c, _d
|
|
2446
|
-
const
|
|
2447
|
-
|
|
2448
|
-
Context:
|
|
2449
|
-
${context}`;
|
|
2450
|
-
const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2451
|
-
|
|
2452
|
-
Context:
|
|
2453
|
-
${context}`;
|
|
2454
|
-
const geminiMessages = messages.map((m) => ({
|
|
2455
|
-
role: m.role === "assistant" ? "model" : "user",
|
|
2456
|
-
parts: [{ text: m.content }]
|
|
2457
|
-
}));
|
|
2458
|
-
const result = yield new __await(this.client.models.generateContentStream({
|
|
2534
|
+
var _a, _b, _c, _d;
|
|
2535
|
+
const model = this.client.getGenerativeModel({
|
|
2459
2536
|
model: this.llmConfig.model,
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2537
|
+
systemInstruction: this.buildSystemInstruction(context)
|
|
2538
|
+
});
|
|
2539
|
+
const result = yield new __await(model.generateContentStream({
|
|
2540
|
+
contents: this.buildGeminiContents(messages),
|
|
2541
|
+
generationConfig: {
|
|
2542
|
+
temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
|
|
2543
|
+
maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
|
|
2465
2544
|
stopSequences: options == null ? void 0 : options.stop
|
|
2466
2545
|
}
|
|
2467
2546
|
}));
|
|
2468
|
-
const stream = (_f = result == null ? void 0 : result.stream) != null ? _f : result;
|
|
2469
|
-
if (!stream) {
|
|
2470
|
-
throw new Error("[GeminiProvider] generateContentStream returned undefined");
|
|
2471
|
-
}
|
|
2472
2547
|
try {
|
|
2473
|
-
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2548
|
+
for (var iter = __forAwait(result.stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2474
2549
|
const chunk = temp.value;
|
|
2475
|
-
const text =
|
|
2550
|
+
const text = chunk.text();
|
|
2476
2551
|
if (text) yield text;
|
|
2477
2552
|
}
|
|
2478
2553
|
} catch (temp) {
|
|
@@ -2487,69 +2562,69 @@ ${context}`;
|
|
|
2487
2562
|
}
|
|
2488
2563
|
});
|
|
2489
2564
|
}
|
|
2565
|
+
// -------------------------------------------------------------------------
|
|
2566
|
+
// ILLMProvider — embeddings
|
|
2567
|
+
// -------------------------------------------------------------------------
|
|
2490
2568
|
async embed(text, options) {
|
|
2491
|
-
var _a, _b
|
|
2492
|
-
const
|
|
2493
|
-
|
|
2569
|
+
var _a, _b;
|
|
2570
|
+
const content = applyPrefix(
|
|
2571
|
+
text,
|
|
2572
|
+
options == null ? void 0 : options.taskType,
|
|
2573
|
+
(_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
|
|
2574
|
+
(_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
|
|
2494
2575
|
);
|
|
2495
|
-
const
|
|
2496
|
-
const
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
2500
|
-
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
2501
|
-
if (!content.startsWith(queryPrefix)) {
|
|
2502
|
-
content = `${queryPrefix}${text}`;
|
|
2503
|
-
}
|
|
2504
|
-
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
2505
|
-
if (!content.startsWith(docPrefix)) {
|
|
2506
|
-
content = `${docPrefix}${text}`;
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
const response = await client.models.embedContent({
|
|
2510
|
-
model,
|
|
2511
|
-
contents: content,
|
|
2512
|
-
config: ((_h = this.embeddingConfig) == null ? void 0 : _h.dimensions) ? { outputDimensionality: this.embeddingConfig.dimensions } : void 0
|
|
2576
|
+
const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
|
|
2577
|
+
const model = this.embeddingClient.getGenerativeModel({ model: modelName });
|
|
2578
|
+
const response = await model.embedContent({
|
|
2579
|
+
content: { role: "user", parts: [{ text: content }] }
|
|
2513
2580
|
}).catch((err) => {
|
|
2514
|
-
console.error(`[GeminiProvider] Embedding failed for model "${
|
|
2581
|
+
console.error(`[GeminiProvider] Embedding failed for model "${modelName}":`, err.message);
|
|
2515
2582
|
throw err;
|
|
2516
2583
|
});
|
|
2517
|
-
return
|
|
2584
|
+
return response.embedding.values;
|
|
2518
2585
|
}
|
|
2519
2586
|
async batchEmbed(texts, options) {
|
|
2520
|
-
|
|
2521
|
-
const model = this.
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2587
|
+
const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
|
|
2588
|
+
const model = this.embeddingClient.getGenerativeModel({ model: modelName });
|
|
2589
|
+
const requests = texts.map((text) => {
|
|
2590
|
+
var _a, _b;
|
|
2591
|
+
return {
|
|
2592
|
+
content: {
|
|
2593
|
+
role: "user",
|
|
2594
|
+
parts: [{
|
|
2595
|
+
text: applyPrefix(
|
|
2596
|
+
text,
|
|
2597
|
+
options == null ? void 0 : options.taskType,
|
|
2598
|
+
(_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
|
|
2599
|
+
(_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
|
|
2600
|
+
)
|
|
2601
|
+
}]
|
|
2602
|
+
}
|
|
2603
|
+
};
|
|
2604
|
+
});
|
|
2605
|
+
const response = await model.batchEmbedContents({
|
|
2606
|
+
requests: requests.map((r) => ({
|
|
2607
|
+
model: `models/${modelName}`,
|
|
2608
|
+
content: r.content
|
|
2609
|
+
}))
|
|
2538
2610
|
}).catch((err) => {
|
|
2539
|
-
console.error(`[GeminiProvider] Batch embedding failed for model "${
|
|
2611
|
+
console.error(`[GeminiProvider] Batch embedding failed for model "${modelName}":`, err.message);
|
|
2540
2612
|
throw err;
|
|
2541
2613
|
});
|
|
2542
|
-
return
|
|
2543
|
-
var
|
|
2544
|
-
return (
|
|
2545
|
-
})
|
|
2614
|
+
return response.embeddings.map((e) => {
|
|
2615
|
+
var _a;
|
|
2616
|
+
return (_a = e.values) != null ? _a : [];
|
|
2617
|
+
});
|
|
2546
2618
|
}
|
|
2619
|
+
// -------------------------------------------------------------------------
|
|
2620
|
+
// ILLMProvider — health
|
|
2621
|
+
// -------------------------------------------------------------------------
|
|
2547
2622
|
async ping() {
|
|
2548
2623
|
try {
|
|
2549
|
-
|
|
2550
|
-
model:
|
|
2551
|
-
contents: "ping"
|
|
2624
|
+
const model = this.embeddingClient.getGenerativeModel({
|
|
2625
|
+
model: this.resolveEmbeddingModel()
|
|
2552
2626
|
});
|
|
2627
|
+
await model.embedContent("ping");
|
|
2553
2628
|
return true;
|
|
2554
2629
|
} catch (err) {
|
|
2555
2630
|
console.error("[GeminiProvider] Ping failed:", err);
|
|
@@ -2706,9 +2781,51 @@ ${context != null ? context : "None"}` },
|
|
|
2706
2781
|
};
|
|
2707
2782
|
|
|
2708
2783
|
// src/llm/LLMFactory.ts
|
|
2784
|
+
var customProviders = /* @__PURE__ */ new Map();
|
|
2709
2785
|
var LLMFactory = class _LLMFactory {
|
|
2786
|
+
/**
|
|
2787
|
+
* Register a custom LLM provider factory at runtime.
|
|
2788
|
+
*
|
|
2789
|
+
* Use this to add support for any LLM backend (Azure OpenAI, Cohere, Mistral,
|
|
2790
|
+
* Bedrock, etc.) without modifying this library's source code.
|
|
2791
|
+
*
|
|
2792
|
+
* @example
|
|
2793
|
+
* // In your Next.js app initialization:
|
|
2794
|
+
* import { LLMFactory } from '@retrivora-ai/rag-engine/server';
|
|
2795
|
+
* import { MyCustomProvider } from './providers/MyCustomProvider';
|
|
2796
|
+
*
|
|
2797
|
+
* LLMFactory.register('my-provider', (config) => new MyCustomProvider(config));
|
|
2798
|
+
*
|
|
2799
|
+
* // Then set in your .env.local:
|
|
2800
|
+
* // LLM_PROVIDER=my-provider
|
|
2801
|
+
*/
|
|
2802
|
+
static register(name, factory) {
|
|
2803
|
+
customProviders.set(name.toLowerCase(), factory);
|
|
2804
|
+
console.log(`[LLMFactory] Registered custom provider: "${name}"`);
|
|
2805
|
+
}
|
|
2806
|
+
/**
|
|
2807
|
+
* Unregister a previously registered custom provider.
|
|
2808
|
+
*/
|
|
2809
|
+
static unregister(name) {
|
|
2810
|
+
customProviders.delete(name.toLowerCase());
|
|
2811
|
+
}
|
|
2812
|
+
/**
|
|
2813
|
+
* List all registered provider names (built-in + custom).
|
|
2814
|
+
*/
|
|
2815
|
+
static listProviders() {
|
|
2816
|
+
return [
|
|
2817
|
+
"openai",
|
|
2818
|
+
"anthropic",
|
|
2819
|
+
"ollama",
|
|
2820
|
+
"gemini",
|
|
2821
|
+
"rest",
|
|
2822
|
+
"universal_rest",
|
|
2823
|
+
"custom",
|
|
2824
|
+
...Array.from(customProviders.keys())
|
|
2825
|
+
];
|
|
2826
|
+
}
|
|
2710
2827
|
static create(llmConfig, embeddingConfig) {
|
|
2711
|
-
var _a;
|
|
2828
|
+
var _a, _b;
|
|
2712
2829
|
switch (llmConfig.provider) {
|
|
2713
2830
|
case "openai":
|
|
2714
2831
|
return new OpenAIProvider(llmConfig, embeddingConfig);
|
|
@@ -2722,11 +2839,19 @@ var LLMFactory = class _LLMFactory {
|
|
|
2722
2839
|
case "universal_rest":
|
|
2723
2840
|
case "custom":
|
|
2724
2841
|
return new UniversalLLMAdapter(llmConfig);
|
|
2725
|
-
default:
|
|
2726
|
-
|
|
2842
|
+
default: {
|
|
2843
|
+
const providerName = String((_a = llmConfig.provider) != null ? _a : "").toLowerCase();
|
|
2844
|
+
const customFactory = customProviders.get(providerName);
|
|
2845
|
+
if (customFactory) {
|
|
2846
|
+
return customFactory(llmConfig);
|
|
2847
|
+
}
|
|
2848
|
+
if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
|
|
2727
2849
|
return new UniversalLLMAdapter(llmConfig);
|
|
2728
2850
|
}
|
|
2729
|
-
throw new Error(
|
|
2851
|
+
throw new Error(
|
|
2852
|
+
`[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2730
2855
|
}
|
|
2731
2856
|
}
|
|
2732
2857
|
static getValidator(provider) {
|
|
@@ -2816,8 +2941,8 @@ var ProviderRegistry = class {
|
|
|
2816
2941
|
}
|
|
2817
2942
|
case "pgvector":
|
|
2818
2943
|
case "postgresql": {
|
|
2819
|
-
const {
|
|
2820
|
-
return
|
|
2944
|
+
const { MultiTablePostgresProvider: MultiTablePostgresProvider2 } = await Promise.resolve().then(() => (init_MultiTablePostgresProvider(), MultiTablePostgresProvider_exports));
|
|
2945
|
+
return MultiTablePostgresProvider2;
|
|
2821
2946
|
}
|
|
2822
2947
|
case "mongodb": {
|
|
2823
2948
|
const { MongoDBProvider: MongoDBProvider2 } = await Promise.resolve().then(() => (init_MongoDBProvider(), MongoDBProvider_exports));
|
|
@@ -3779,69 +3904,40 @@ var QueryProcessor = class {
|
|
|
3779
3904
|
var UITransformer = class {
|
|
3780
3905
|
/**
|
|
3781
3906
|
* Main transformation method
|
|
3782
|
-
* Analyzes user query and retrieved data to determine
|
|
3783
|
-
*
|
|
3784
|
-
* @param userQuery - The original user question/query
|
|
3785
|
-
* @param retrievedData - Vector database retrieval results
|
|
3786
|
-
* @returns Structured JSON response for UI rendering
|
|
3907
|
+
* Analyzes user query and retrieved data to determine if a product carousel is needed.
|
|
3787
3908
|
*/
|
|
3788
|
-
static transform(userQuery, retrievedData) {
|
|
3909
|
+
static transform(userQuery, retrievedData, config, trainedSchema) {
|
|
3789
3910
|
if (!retrievedData || retrievedData.length === 0) {
|
|
3790
3911
|
return this.createTextResponse("No data available", "No relevant data found for your query.");
|
|
3791
3912
|
}
|
|
3792
|
-
const
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
case "line_chart":
|
|
3801
|
-
return this.transformToLineChart(retrievedData);
|
|
3802
|
-
case "table":
|
|
3803
|
-
return this.transformToTable(retrievedData);
|
|
3804
|
-
default:
|
|
3805
|
-
return this.transformToText(retrievedData);
|
|
3806
|
-
}
|
|
3807
|
-
}
|
|
3808
|
-
/**
|
|
3809
|
-
* Analyzes the data and query to suggest the best visualization type
|
|
3810
|
-
*/
|
|
3811
|
-
static analyzeData(query, data) {
|
|
3812
|
-
const queryLower = query.toLowerCase();
|
|
3813
|
-
const hasProducts = data.some(
|
|
3814
|
-
(item) => this.isProductData(item)
|
|
3815
|
-
);
|
|
3816
|
-
const hasTimeSeries = data.some(
|
|
3817
|
-
(item) => this.isTimeSeriesData(item)
|
|
3818
|
-
);
|
|
3819
|
-
const hasCategories = this.detectCategories(data).length > 0;
|
|
3820
|
-
const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
|
|
3821
|
-
const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
|
|
3822
|
-
const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
|
|
3823
|
-
if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
|
|
3824
|
-
return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
|
|
3913
|
+
const isStockRequest = this.isStockQuery(userQuery);
|
|
3914
|
+
const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
|
|
3915
|
+
const categories = this.detectCategories(filteredData);
|
|
3916
|
+
const hasProducts = filteredData.some((item) => this.isProductData(item));
|
|
3917
|
+
const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
|
|
3918
|
+
const isTrendQuery = this.isTrendQuery(userQuery);
|
|
3919
|
+
if (isTrendQuery && isTimeSeries) {
|
|
3920
|
+
return this.transformToLineChart(filteredData);
|
|
3825
3921
|
}
|
|
3826
|
-
if (
|
|
3827
|
-
return
|
|
3922
|
+
if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
|
|
3923
|
+
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
3828
3924
|
}
|
|
3829
|
-
if (
|
|
3830
|
-
return
|
|
3925
|
+
if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
|
|
3926
|
+
return this.transformToPieChart(filteredData);
|
|
3831
3927
|
}
|
|
3832
|
-
if (
|
|
3833
|
-
return
|
|
3928
|
+
if (hasProducts) {
|
|
3929
|
+
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
3834
3930
|
}
|
|
3835
|
-
if (
|
|
3836
|
-
return
|
|
3931
|
+
if (this.hasMultipleFields(filteredData)) {
|
|
3932
|
+
return this.transformToTable(filteredData);
|
|
3837
3933
|
}
|
|
3838
|
-
return
|
|
3934
|
+
return this.transformToText(filteredData);
|
|
3839
3935
|
}
|
|
3840
3936
|
/**
|
|
3841
3937
|
* Transform data to product carousel format
|
|
3842
3938
|
*/
|
|
3843
|
-
static transformToProductCarousel(data) {
|
|
3844
|
-
const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
|
|
3939
|
+
static transformToProductCarousel(data, config, trainedSchema) {
|
|
3940
|
+
const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item, config, trainedSchema)).filter((p) => p !== null);
|
|
3845
3941
|
return {
|
|
3846
3942
|
type: "product_carousel",
|
|
3847
3943
|
title: "Recommended Products",
|
|
@@ -3855,11 +3951,15 @@ var UITransformer = class {
|
|
|
3855
3951
|
static transformToPieChart(data) {
|
|
3856
3952
|
const categories = this.detectCategories(data);
|
|
3857
3953
|
const categoryData = this.aggregateByCategory(data, categories);
|
|
3858
|
-
const pieData = Object.entries(categoryData).map(([label, count]) =>
|
|
3859
|
-
label,
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3954
|
+
const pieData = Object.entries(categoryData).map(([label, count]) => {
|
|
3955
|
+
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
|
|
3956
|
+
return {
|
|
3957
|
+
label,
|
|
3958
|
+
value: count,
|
|
3959
|
+
inStockCount,
|
|
3960
|
+
outOfStockCount
|
|
3961
|
+
};
|
|
3962
|
+
});
|
|
3863
3963
|
return {
|
|
3864
3964
|
type: "pie_chart",
|
|
3865
3965
|
title: "Distribution by Category",
|
|
@@ -3873,11 +3973,15 @@ var UITransformer = class {
|
|
|
3873
3973
|
static transformToBarChart(data) {
|
|
3874
3974
|
const categories = this.detectCategories(data);
|
|
3875
3975
|
const categoryData = this.aggregateByCategory(data, categories);
|
|
3876
|
-
const barData = Object.entries(categoryData).map(([category, value]) =>
|
|
3877
|
-
category,
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3976
|
+
const barData = Object.entries(categoryData).map(([category, value]) => {
|
|
3977
|
+
const { inStockCount, outOfStockCount } = this.calculateStockCounts(category, data);
|
|
3978
|
+
return {
|
|
3979
|
+
category,
|
|
3980
|
+
value,
|
|
3981
|
+
inStockCount,
|
|
3982
|
+
outOfStockCount
|
|
3983
|
+
};
|
|
3984
|
+
});
|
|
3881
3985
|
return {
|
|
3882
3986
|
type: "bar_chart",
|
|
3883
3987
|
title: "Comparison by Category",
|
|
@@ -3941,49 +4045,261 @@ var UITransformer = class {
|
|
|
3941
4045
|
data: { content }
|
|
3942
4046
|
};
|
|
3943
4047
|
}
|
|
4048
|
+
static parseTransformationResponse(raw) {
|
|
4049
|
+
const payloadText = this.extractJsonCandidate(raw);
|
|
4050
|
+
if (!payloadText) return null;
|
|
4051
|
+
try {
|
|
4052
|
+
const parsed = JSON.parse(payloadText);
|
|
4053
|
+
return this.normalizeTransformation(parsed);
|
|
4054
|
+
} catch (e) {
|
|
4055
|
+
return null;
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
static extractJsonCandidate(raw) {
|
|
4059
|
+
if (!raw) return null;
|
|
4060
|
+
const cleaned = raw.replace(/```(?:json|chart|ui)?\s*/gi, "").replace(/```/g, "").trim();
|
|
4061
|
+
const start = cleaned.indexOf("{");
|
|
4062
|
+
if (start === -1) return null;
|
|
4063
|
+
let depth = 0;
|
|
4064
|
+
let inString = false;
|
|
4065
|
+
let escape = false;
|
|
4066
|
+
for (let i = start; i < cleaned.length; i += 1) {
|
|
4067
|
+
const char = cleaned[i];
|
|
4068
|
+
if (escape) {
|
|
4069
|
+
escape = false;
|
|
4070
|
+
continue;
|
|
4071
|
+
}
|
|
4072
|
+
if (char === "\\") {
|
|
4073
|
+
escape = true;
|
|
4074
|
+
continue;
|
|
4075
|
+
}
|
|
4076
|
+
if (char === '"') {
|
|
4077
|
+
inString = !inString;
|
|
4078
|
+
continue;
|
|
4079
|
+
}
|
|
4080
|
+
if (inString) continue;
|
|
4081
|
+
if (char === "{") depth += 1;
|
|
4082
|
+
if (char === "}") {
|
|
4083
|
+
depth -= 1;
|
|
4084
|
+
if (depth === 0) {
|
|
4085
|
+
return cleaned.slice(start, i + 1);
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
return null;
|
|
4090
|
+
}
|
|
4091
|
+
static normalizeTransformation(payload) {
|
|
4092
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
4093
|
+
if (!payload || typeof payload !== "object") return null;
|
|
4094
|
+
const payloadObj = payload;
|
|
4095
|
+
const type = this.normalizeVisualizationType(String((_c = (_b = (_a = payloadObj.type) != null ? _a : payloadObj.view) != null ? _b : payloadObj.chartType) != null ? _c : ""));
|
|
4096
|
+
if (!type) return null;
|
|
4097
|
+
const title = String((_e = (_d = payloadObj.title) != null ? _d : payloadObj.heading) != null ? _e : "Visualization");
|
|
4098
|
+
const description = payloadObj.description ? String(payloadObj.description) : void 0;
|
|
4099
|
+
const rawData = (_j = (_i = (_h = (_g = (_f = payloadObj.data) != null ? _f : payloadObj.table) != null ? _g : payloadObj.rows) != null ? _h : payloadObj.items) != null ? _i : payloadObj.content) != null ? _j : null;
|
|
4100
|
+
const data = type === "text" && typeof rawData === "string" ? { content: rawData } : rawData;
|
|
4101
|
+
const transformation = {
|
|
4102
|
+
type,
|
|
4103
|
+
title,
|
|
4104
|
+
description,
|
|
4105
|
+
data
|
|
4106
|
+
};
|
|
4107
|
+
return this.validateTransformation(transformation) ? transformation : null;
|
|
4108
|
+
}
|
|
4109
|
+
static normalizeVisualizationType(type) {
|
|
4110
|
+
var _a;
|
|
4111
|
+
const mapping = {
|
|
4112
|
+
pie: "pie_chart",
|
|
4113
|
+
pie_chart: "pie_chart",
|
|
4114
|
+
bar: "bar_chart",
|
|
4115
|
+
bar_chart: "bar_chart",
|
|
4116
|
+
line: "line_chart",
|
|
4117
|
+
line_chart: "line_chart",
|
|
4118
|
+
radar: "radar_chart",
|
|
4119
|
+
radar_chart: "radar_chart",
|
|
4120
|
+
table: "table",
|
|
4121
|
+
text: "text",
|
|
4122
|
+
product_carousel: "product_carousel",
|
|
4123
|
+
carousel: "carousel"
|
|
4124
|
+
};
|
|
4125
|
+
return (_a = mapping[type.toLowerCase()]) != null ? _a : null;
|
|
4126
|
+
}
|
|
4127
|
+
static validateTransformation(transformation) {
|
|
4128
|
+
const { type, data } = transformation;
|
|
4129
|
+
switch (type) {
|
|
4130
|
+
case "pie_chart":
|
|
4131
|
+
case "bar_chart":
|
|
4132
|
+
return Array.isArray(data) && data.every(
|
|
4133
|
+
(item) => item !== null && typeof item === "object" && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
|
|
4134
|
+
);
|
|
4135
|
+
case "line_chart":
|
|
4136
|
+
return Array.isArray(data) && data.every(
|
|
4137
|
+
(item) => item !== null && typeof item === "object" && "timestamp" in item && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
|
|
4138
|
+
);
|
|
4139
|
+
case "radar_chart":
|
|
4140
|
+
return Array.isArray(data) && data.every(
|
|
4141
|
+
(item) => item !== null && typeof item === "object" && "attribute" in item
|
|
4142
|
+
);
|
|
4143
|
+
case "table":
|
|
4144
|
+
return typeof data === "object" && data !== null && Array.isArray(data.columns) && Array.isArray(data.rows);
|
|
4145
|
+
case "text":
|
|
4146
|
+
return typeof data === "object" && data !== null && typeof data.content === "string";
|
|
4147
|
+
case "product_carousel":
|
|
4148
|
+
case "carousel":
|
|
4149
|
+
return Array.isArray(data) && data.every(
|
|
4150
|
+
(item) => item !== null && typeof item === "object" && ("id" in item || "name" in item)
|
|
4151
|
+
);
|
|
4152
|
+
default:
|
|
4153
|
+
return false;
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
3944
4156
|
/**
|
|
3945
4157
|
* Helper: Check if data item is product-related
|
|
3946
4158
|
*/
|
|
3947
4159
|
static isProductData(item) {
|
|
3948
4160
|
const content = (item.content || "").toLowerCase();
|
|
3949
|
-
const productKeywords = [
|
|
3950
|
-
|
|
3951
|
-
|
|
4161
|
+
const productKeywords = [
|
|
4162
|
+
"product",
|
|
4163
|
+
"price",
|
|
4164
|
+
"stock",
|
|
4165
|
+
"item",
|
|
4166
|
+
"sku",
|
|
4167
|
+
"brand",
|
|
4168
|
+
"model",
|
|
4169
|
+
"msrp",
|
|
4170
|
+
"inventory",
|
|
4171
|
+
"buy",
|
|
4172
|
+
"shop",
|
|
4173
|
+
"beauty",
|
|
4174
|
+
"care",
|
|
4175
|
+
"cosmetic",
|
|
4176
|
+
"facial",
|
|
4177
|
+
"cream",
|
|
4178
|
+
"serum",
|
|
4179
|
+
"mask",
|
|
4180
|
+
"makeup",
|
|
4181
|
+
"fragrance"
|
|
4182
|
+
];
|
|
4183
|
+
const hasKeywords = productKeywords.some((kw) => content.includes(kw));
|
|
4184
|
+
const hasMetadataKey = Object.keys(item.metadata || {}).some(
|
|
4185
|
+
(k) => ["name", "price", "product", "sku", "brand", "model", "cost", "item"].includes(k.toLowerCase())
|
|
3952
4186
|
);
|
|
4187
|
+
const hasPricePattern = /\$\s*\d+/.test(content);
|
|
4188
|
+
return hasKeywords || hasMetadataKey || hasPricePattern;
|
|
3953
4189
|
}
|
|
3954
4190
|
/**
|
|
3955
4191
|
* Helper: Check if data contains time series
|
|
3956
4192
|
*/
|
|
3957
4193
|
static isTimeSeriesData(item) {
|
|
3958
4194
|
const content = (item.content || "").toLowerCase();
|
|
3959
|
-
const timeKeywords = ["
|
|
3960
|
-
|
|
4195
|
+
const timeKeywords = ["trend", "historical", "growth", "decline", "change", "increase", "decrease"];
|
|
4196
|
+
const hasTimeKeyword = timeKeywords.some((kw) => content.includes(kw));
|
|
4197
|
+
const metadata = item.metadata || {};
|
|
4198
|
+
const maybeDateKeys = Object.keys(metadata).filter(
|
|
3961
4199
|
(k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
|
|
3962
4200
|
);
|
|
4201
|
+
const hasValidDateValue = maybeDateKeys.some((key) => {
|
|
4202
|
+
const value = metadata[key];
|
|
4203
|
+
if (typeof value === "string") {
|
|
4204
|
+
return !Number.isNaN(Date.parse(value)) && value.trim().length > 0;
|
|
4205
|
+
}
|
|
4206
|
+
return typeof value === "number";
|
|
4207
|
+
});
|
|
4208
|
+
return hasTimeKeyword || hasValidDateValue;
|
|
4209
|
+
}
|
|
4210
|
+
static shouldShowCategoryChart(query, categories) {
|
|
4211
|
+
if (categories.length < 2) {
|
|
4212
|
+
return false;
|
|
4213
|
+
}
|
|
4214
|
+
const normalized = query.toLowerCase();
|
|
4215
|
+
const chartKeywords = [
|
|
4216
|
+
"distribution",
|
|
4217
|
+
"breakdown",
|
|
4218
|
+
"by category",
|
|
4219
|
+
"by type",
|
|
4220
|
+
"compare",
|
|
4221
|
+
"share",
|
|
4222
|
+
"percentage",
|
|
4223
|
+
"segmentation",
|
|
4224
|
+
"split",
|
|
4225
|
+
"category breakdown",
|
|
4226
|
+
"category distribution"
|
|
4227
|
+
];
|
|
4228
|
+
return chartKeywords.some((keyword) => normalized.includes(keyword));
|
|
4229
|
+
}
|
|
4230
|
+
static isTrendQuery(query) {
|
|
4231
|
+
const normalized = query.toLowerCase();
|
|
4232
|
+
const trendKeywords = [
|
|
4233
|
+
"trend",
|
|
4234
|
+
"over time",
|
|
4235
|
+
"historical",
|
|
4236
|
+
"growth",
|
|
4237
|
+
"decline",
|
|
4238
|
+
"increase",
|
|
4239
|
+
"decrease",
|
|
4240
|
+
"year",
|
|
4241
|
+
"month",
|
|
4242
|
+
"week",
|
|
4243
|
+
"day",
|
|
4244
|
+
"comparison",
|
|
4245
|
+
"compare",
|
|
4246
|
+
"changes",
|
|
4247
|
+
"timeline"
|
|
4248
|
+
];
|
|
4249
|
+
return trendKeywords.some((keyword) => normalized.includes(keyword));
|
|
4250
|
+
}
|
|
4251
|
+
static isStockQuery(query) {
|
|
4252
|
+
const normalized = query.toLowerCase();
|
|
4253
|
+
return normalized.includes("in stock") || normalized.includes("available") || normalized.includes("availability") || normalized.includes("inventory") || normalized.includes("stock status");
|
|
3963
4254
|
}
|
|
3964
4255
|
/**
|
|
3965
|
-
* Helper: Extract
|
|
4256
|
+
* Helper: Extract property from metadata using mapping, AI training, case-insensitivity, and synonyms.
|
|
3966
4257
|
*/
|
|
3967
|
-
static
|
|
4258
|
+
static getDynamicVal(meta, uiKey, config, trainedSchema) {
|
|
4259
|
+
var _a;
|
|
4260
|
+
if (!meta) return void 0;
|
|
4261
|
+
const mapping = (_a = config == null ? void 0 : config.rag) == null ? void 0 : _a.uiMapping;
|
|
4262
|
+
if (mapping && mapping[uiKey]) {
|
|
4263
|
+
const mappedKey = mapping[uiKey];
|
|
4264
|
+
if (meta[mappedKey] !== void 0) return meta[mappedKey];
|
|
4265
|
+
}
|
|
4266
|
+
if (trainedSchema && typeof trainedSchema === "object" && trainedSchema !== null) {
|
|
4267
|
+
const trainedKey = trainedSchema[uiKey];
|
|
4268
|
+
if (trainedKey && meta[trainedKey] !== void 0) return meta[trainedKey];
|
|
4269
|
+
}
|
|
4270
|
+
const synonyms = this.SYNONYMS[uiKey] || [];
|
|
4271
|
+
const searchKeys = [uiKey, ...synonyms].map((k) => k.toLowerCase());
|
|
4272
|
+
const foundDirectKey = Object.keys(meta).find((k) => searchKeys.includes(k.toLowerCase()));
|
|
4273
|
+
if (foundDirectKey) return meta[foundDirectKey];
|
|
4274
|
+
const foundPartialKey = Object.keys(meta).find((k) => {
|
|
4275
|
+
const keyLow = k.toLowerCase();
|
|
4276
|
+
return searchKeys.some((sk) => keyLow.includes(sk) || sk.includes(keyLow));
|
|
4277
|
+
});
|
|
4278
|
+
return foundPartialKey ? meta[foundPartialKey] : void 0;
|
|
4279
|
+
}
|
|
4280
|
+
static extractProductInfo(item, config, trainedSchema) {
|
|
3968
4281
|
const meta = item.metadata || {};
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
4282
|
+
const name = this.getDynamicVal(meta, "name", config, trainedSchema);
|
|
4283
|
+
const price = this.getDynamicVal(meta, "price", config, trainedSchema);
|
|
4284
|
+
const brand = this.getDynamicVal(meta, "brand", config, trainedSchema);
|
|
4285
|
+
if (name || this.isProductData(item)) {
|
|
4286
|
+
let finalName = name ? String(name) : void 0;
|
|
4287
|
+
if (!finalName) {
|
|
4288
|
+
const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
|
|
4289
|
+
finalName = nameMatch ? nameMatch[1].trim() : item.content.split("\n")[0].substring(0, 60);
|
|
4290
|
+
}
|
|
4291
|
+
let finalPrice = typeof price === "number" || typeof price === "string" ? price : void 0;
|
|
4292
|
+
if (!finalPrice) {
|
|
4293
|
+
const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d,.]+)/i) || item.content.match(/\$\s*([\d,.]+)/);
|
|
4294
|
+
if (priceMatch) finalPrice = priceMatch[1].replace(/,/g, "");
|
|
4295
|
+
}
|
|
4296
|
+
const imageValue = this.getDynamicVal(meta, "image", config, trainedSchema);
|
|
3983
4297
|
return {
|
|
3984
4298
|
id: item.id,
|
|
3985
|
-
name:
|
|
3986
|
-
price:
|
|
4299
|
+
name: finalName,
|
|
4300
|
+
price: finalPrice,
|
|
4301
|
+
image: typeof imageValue === "string" ? imageValue : void 0,
|
|
4302
|
+
brand: brand ? String(brand) : void 0,
|
|
3987
4303
|
description: item.content,
|
|
3988
4304
|
inStock: this.determineStockStatus(item)
|
|
3989
4305
|
};
|
|
@@ -4007,6 +4323,10 @@ var UITransformer = class {
|
|
|
4007
4323
|
const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
|
|
4008
4324
|
tags.forEach((t) => categories.add(String(t)));
|
|
4009
4325
|
}
|
|
4326
|
+
const contentCategories = Array.from(new Set(
|
|
4327
|
+
Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm)).map((match) => match[1].trim()).filter(Boolean)
|
|
4328
|
+
));
|
|
4329
|
+
contentCategories.forEach((category) => categories.add(category));
|
|
4010
4330
|
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
4011
4331
|
if (categoryMatch) {
|
|
4012
4332
|
categories.add(categoryMatch[1].trim());
|
|
@@ -4074,14 +4394,23 @@ var UITransformer = class {
|
|
|
4074
4394
|
});
|
|
4075
4395
|
}
|
|
4076
4396
|
/**
|
|
4077
|
-
* Helper:
|
|
4397
|
+
* Helper: Calculate stock counts by category
|
|
4078
4398
|
*/
|
|
4079
|
-
static
|
|
4080
|
-
|
|
4399
|
+
static calculateStockCounts(category, data) {
|
|
4400
|
+
let inStock = 0;
|
|
4401
|
+
let outOfStock = 0;
|
|
4402
|
+
data.forEach((d) => {
|
|
4081
4403
|
const meta = d.metadata || {};
|
|
4082
|
-
|
|
4404
|
+
const itemCategory = meta.category || meta.type || "Other";
|
|
4405
|
+
if (itemCategory === category) {
|
|
4406
|
+
if (this.determineStockStatus(d)) {
|
|
4407
|
+
inStock++;
|
|
4408
|
+
} else {
|
|
4409
|
+
outOfStock++;
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4083
4412
|
});
|
|
4084
|
-
return
|
|
4413
|
+
return { inStockCount: inStock, outOfStockCount: outOfStock };
|
|
4085
4414
|
}
|
|
4086
4415
|
/**
|
|
4087
4416
|
* Helper: Determine if item is in stock
|
|
@@ -4118,6 +4447,225 @@ var UITransformer = class {
|
|
|
4118
4447
|
});
|
|
4119
4448
|
return fieldCount.size > 2;
|
|
4120
4449
|
}
|
|
4450
|
+
// ─── LLM-Driven Visualization Decision ────────────────────────────────────
|
|
4451
|
+
/**
|
|
4452
|
+
* analyzeAndDecide — sends user question + RAG data to the LLM with a
|
|
4453
|
+
* structured system prompt and parses the JSON response into a
|
|
4454
|
+
* UITransformationResponse.
|
|
4455
|
+
*
|
|
4456
|
+
* This is the recommended entry point for production use. The heuristic
|
|
4457
|
+
* `transform()` method is used as a fallback if the LLM call fails.
|
|
4458
|
+
*
|
|
4459
|
+
* System prompt instructs the LLM to:
|
|
4460
|
+
* - Analyze the question and retrieved data
|
|
4461
|
+
* - Choose the best visualization: bar_chart | line_chart | pie_chart | table | text
|
|
4462
|
+
* - Return a strict JSON object — no prose, no markdown fences
|
|
4463
|
+
*
|
|
4464
|
+
* @param query - the original user question
|
|
4465
|
+
* @param sources - vector DB matches returned by RAG retrieval
|
|
4466
|
+
* @param llm - any ILLMProvider instance (OpenAI, Anthropic, Ollama, Gemini, REST…)
|
|
4467
|
+
* @returns - a validated UITransformationResponse (type + title + description + data)
|
|
4468
|
+
*/
|
|
4469
|
+
static async analyzeAndDecide(query, sources, llm) {
|
|
4470
|
+
try {
|
|
4471
|
+
const context = this.buildContextSummary(sources);
|
|
4472
|
+
const systemPrompt = this.buildVisualizationSystemPrompt();
|
|
4473
|
+
const userPrompt = [
|
|
4474
|
+
`USER QUESTION: ${query}`,
|
|
4475
|
+
"",
|
|
4476
|
+
"RETRIEVED DATA (JSON):",
|
|
4477
|
+
context
|
|
4478
|
+
].join("\n");
|
|
4479
|
+
const rawResponse = await llm.chat(
|
|
4480
|
+
[{ role: "user", content: userPrompt }],
|
|
4481
|
+
"",
|
|
4482
|
+
{ systemPrompt, temperature: 0 }
|
|
4483
|
+
);
|
|
4484
|
+
const parsed = this.parseTransformationResponse(rawResponse);
|
|
4485
|
+
if (parsed) {
|
|
4486
|
+
console.debug("[UITransformer] LLM chose visualization type:", parsed.type);
|
|
4487
|
+
return parsed;
|
|
4488
|
+
}
|
|
4489
|
+
console.warn("[UITransformer] LLM returned unparseable response; falling back to heuristic.");
|
|
4490
|
+
} catch (err) {
|
|
4491
|
+
console.warn("[UITransformer] analyzeAndDecide LLM call failed; falling back to heuristic.", err);
|
|
4492
|
+
}
|
|
4493
|
+
return this.transform(query, sources);
|
|
4494
|
+
}
|
|
4495
|
+
/**
|
|
4496
|
+
* Build the system prompt that instructs the LLM to return a visualization JSON.
|
|
4497
|
+
*/
|
|
4498
|
+
static buildVisualizationSystemPrompt() {
|
|
4499
|
+
return `You are a data visualization expert embedded in a RAG chat system.
|
|
4500
|
+
You will receive a user question and structured data retrieved from a vector database.
|
|
4501
|
+
Your ONLY job is to analyze this information and return a single JSON object that tells
|
|
4502
|
+
the frontend how to visualize it.
|
|
4503
|
+
|
|
4504
|
+
Return ONLY a valid JSON object \u2014 no markdown code fences, no explanation, no prose.
|
|
4505
|
+
|
|
4506
|
+
The JSON must have this exact shape:
|
|
4507
|
+
{
|
|
4508
|
+
"type": "bar_chart" | "line_chart" | "pie_chart" | "radar_chart" | "table" | "text",
|
|
4509
|
+
"title": "A concise, descriptive title for the visualization",
|
|
4510
|
+
"description": "One sentence describing what the visualization shows",
|
|
4511
|
+
"data": <structured data \u2014 see rules below>
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
DATA SHAPE per type:
|
|
4515
|
+
- bar_chart: array of { "category": string, "value": number }
|
|
4516
|
+
- line_chart: array of { "timestamp": string, "value": number, "label": string }
|
|
4517
|
+
- pie_chart: array of { "label": string, "value": number }
|
|
4518
|
+
- radar_chart: array of { "attribute": string, "[series1]": number, "[series2]": number, ... }
|
|
4519
|
+
Example for radar_chart:
|
|
4520
|
+
[
|
|
4521
|
+
{ "attribute": "Longevity", "Dolce Shine": 4, "CK One": 3 },
|
|
4522
|
+
{ "attribute": "Sillage", "Dolce Shine": 3, "CK One": 4 },
|
|
4523
|
+
{ "attribute": "Freshness", "Dolce Shine": 5, "CK One": 4 }
|
|
4524
|
+
]
|
|
4525
|
+
- table: { "columns": string[], "rows": (string|number)[][] }
|
|
4526
|
+
- text: { "content": "<prose answer>" }
|
|
4527
|
+
|
|
4528
|
+
DECISION RULES (follow strictly):
|
|
4529
|
+
1. bar_chart \u2192 comparing quantities across categories (e.g. sales by region, price by product). Use this when there is only ONE value per category.
|
|
4530
|
+
2. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
|
|
4531
|
+
3. pie_chart \u2192 proportional breakdown or percentage distribution
|
|
4532
|
+
4. radar_chart \u2192 comparing 2 or more products across MULTIPLE attributes (e.g. comparing features, ratings, or characteristics of 2 specific products). If the user asks to "compare" products and the data contains multiple dimensions or ratings for them, you MUST use this.
|
|
4533
|
+
5. table \u2192 multi-field structured records where each item has \u2265 3 attributes
|
|
4534
|
+
6. text \u2192 conversational or free-form answers where no chart adds value
|
|
4535
|
+
|
|
4536
|
+
IMPORTANT:
|
|
4537
|
+
- Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
|
|
4538
|
+
- Ensure all "value" fields are numbers, not strings.
|
|
4539
|
+
- For bar/line/pie, keep at most 12 data points for readability.
|
|
4540
|
+
- Never include nested objects or arrays inside bar_chart / line_chart / pie_chart data items.`;
|
|
4541
|
+
}
|
|
4542
|
+
/**
|
|
4543
|
+
* Serialize retrieved vector matches into a compact JSON context string.
|
|
4544
|
+
* Limits the total character count to avoid exceeding LLM context windows.
|
|
4545
|
+
*/
|
|
4546
|
+
static buildContextSummary(sources, maxChars = 6e3) {
|
|
4547
|
+
const items = sources.map((s, i) => {
|
|
4548
|
+
var _a, _b, _c, _d;
|
|
4549
|
+
return {
|
|
4550
|
+
index: i + 1,
|
|
4551
|
+
content: (_b = (_a = s.content) == null ? void 0 : _a.substring(0, 400)) != null ? _b : "",
|
|
4552
|
+
metadata: (_c = s.metadata) != null ? _c : {},
|
|
4553
|
+
score: (_d = s.score) != null ? _d : 0
|
|
4554
|
+
};
|
|
4555
|
+
});
|
|
4556
|
+
const full = JSON.stringify(items, null, 2);
|
|
4557
|
+
if (full.length <= maxChars) return full;
|
|
4558
|
+
const partial = [];
|
|
4559
|
+
let chars = 2;
|
|
4560
|
+
for (const item of items) {
|
|
4561
|
+
const chunk = JSON.stringify(item);
|
|
4562
|
+
if (chars + chunk.length + 2 > maxChars) break;
|
|
4563
|
+
partial.push(item);
|
|
4564
|
+
chars += chunk.length + 2;
|
|
4565
|
+
}
|
|
4566
|
+
return JSON.stringify(partial, null, 2) + "\n// ... truncated";
|
|
4567
|
+
}
|
|
4568
|
+
};
|
|
4569
|
+
/**
|
|
4570
|
+
* Central dictionary of common synonyms for UI properties.
|
|
4571
|
+
* This allows the system to be schema-agnostic by guessing field names.
|
|
4572
|
+
*/
|
|
4573
|
+
UITransformer.SYNONYMS = {
|
|
4574
|
+
name: ["product", "item", "title", "label", "heading", "subject"],
|
|
4575
|
+
price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
|
|
4576
|
+
brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
|
|
4577
|
+
image: ["imageUrl", "thumbnail", "img", "url", "photo", "picture", "media", "image_url", "main_image", "product_image", "thumb"],
|
|
4578
|
+
stock: ["inventory", "quantity", "count", "availability", "stock_level", "inStock", "is_available"],
|
|
4579
|
+
description: ["summary", "content", "body", "text", "info", "details"]
|
|
4580
|
+
};
|
|
4581
|
+
|
|
4582
|
+
// src/utils/SchemaMapper.ts
|
|
4583
|
+
var SchemaMapper = class {
|
|
4584
|
+
/**
|
|
4585
|
+
* Trains the plugin on a set of keys.
|
|
4586
|
+
* This is done once per schema and cached.
|
|
4587
|
+
*/
|
|
4588
|
+
static async train(llm, projectId, keys) {
|
|
4589
|
+
const cacheKey = `${projectId}:${keys.sort().join(",")}`;
|
|
4590
|
+
if (this.cache.has(cacheKey)) {
|
|
4591
|
+
return this.cache.get(cacheKey);
|
|
4592
|
+
}
|
|
4593
|
+
console.log(`[SchemaMapper] \u{1F9E0} Training AI on new schema keys: ${keys.join(", ")}`);
|
|
4594
|
+
const propertyList = Object.entries(this.TARGET_PROPERTIES).map(([prop, desc]) => `- ${prop} (${desc})`).join("\n");
|
|
4595
|
+
const prompt = `
|
|
4596
|
+
Given these metadata keys from a database: [${keys.join(", ")}]
|
|
4597
|
+
|
|
4598
|
+
Identify which keys best correspond to these standard UI properties:
|
|
4599
|
+
${propertyList}
|
|
4600
|
+
|
|
4601
|
+
Return ONLY a JSON object where the keys are the UI properties and the values are the matching database keys.
|
|
4602
|
+
If no good match is found for a property, omit it.
|
|
4603
|
+
|
|
4604
|
+
Example:
|
|
4605
|
+
{
|
|
4606
|
+
"name": "Product_Title",
|
|
4607
|
+
"price": "MSRP_USD",
|
|
4608
|
+
"brand": "VendorName"
|
|
4609
|
+
}
|
|
4610
|
+
`;
|
|
4611
|
+
try {
|
|
4612
|
+
const response = await llm.chat(
|
|
4613
|
+
[
|
|
4614
|
+
{ role: "system", content: "You are a database schema expert. You ONLY respond with valid JSON." },
|
|
4615
|
+
{ role: "user", content: prompt }
|
|
4616
|
+
],
|
|
4617
|
+
""
|
|
4618
|
+
);
|
|
4619
|
+
const startIdx = response.indexOf("{");
|
|
4620
|
+
if (startIdx !== -1) {
|
|
4621
|
+
let braceCount = 0;
|
|
4622
|
+
let endIdx = -1;
|
|
4623
|
+
for (let i = startIdx; i < response.length; i++) {
|
|
4624
|
+
if (response[i] === "{") braceCount++;
|
|
4625
|
+
else if (response[i] === "}") {
|
|
4626
|
+
braceCount--;
|
|
4627
|
+
if (braceCount === 0) {
|
|
4628
|
+
endIdx = i;
|
|
4629
|
+
break;
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
}
|
|
4633
|
+
if (endIdx !== -1) {
|
|
4634
|
+
const jsonContent = response.substring(startIdx, endIdx + 1);
|
|
4635
|
+
const cleanJson = this.sanitizeJson(jsonContent);
|
|
4636
|
+
const mapping = JSON.parse(cleanJson);
|
|
4637
|
+
this.cache.set(cacheKey, mapping);
|
|
4638
|
+
return mapping;
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
} catch (error) {
|
|
4642
|
+
console.warn("[SchemaMapper] AI training failed, falling back to heuristics:", error);
|
|
4643
|
+
}
|
|
4644
|
+
return {};
|
|
4645
|
+
}
|
|
4646
|
+
/**
|
|
4647
|
+
* Forgiving JSON parser that fixes common AI formatting mistakes.
|
|
4648
|
+
*/
|
|
4649
|
+
static sanitizeJson(s) {
|
|
4650
|
+
return s.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm, "").replace(/[\u201C\u201D\u2018\u2019]/g, '"').replace(/'/g, '"').replace(/([{,]\s*)([a-zA-Z0-9_]+?)\s*:/g, '$1"$2":').replace(/,\s*([}\]])/g, "$1").trim();
|
|
4651
|
+
}
|
|
4652
|
+
static getCached(projectId, keys) {
|
|
4653
|
+
const cacheKey = `${projectId}:${keys.sort().join(",")}`;
|
|
4654
|
+
return this.cache.get(cacheKey);
|
|
4655
|
+
}
|
|
4656
|
+
};
|
|
4657
|
+
SchemaMapper.cache = /* @__PURE__ */ new Map();
|
|
4658
|
+
/**
|
|
4659
|
+
* Descriptions of standard UI properties to help the AI map fields accurately.
|
|
4660
|
+
*/
|
|
4661
|
+
SchemaMapper.TARGET_PROPERTIES = {
|
|
4662
|
+
name: "The primary title, name, or label of the item",
|
|
4663
|
+
price: "The numeric cost, price, MSRP, or amount",
|
|
4664
|
+
brand: "The manufacturer, vendor, brand, or maker",
|
|
4665
|
+
image: "The URL to an image, thumbnail, photo, or picture",
|
|
4666
|
+
stock: "The availability, inventory count, quantity, or stock level",
|
|
4667
|
+
description: "The detailed text content, summary, or body info",
|
|
4668
|
+
category: "The group, department, type, or category name"
|
|
4121
4669
|
};
|
|
4122
4670
|
|
|
4123
4671
|
// src/core/Pipeline.ts
|
|
@@ -4166,205 +4714,31 @@ var Pipeline = class {
|
|
|
4166
4714
|
}
|
|
4167
4715
|
this.reranker = new Reranker();
|
|
4168
4716
|
}
|
|
4717
|
+
/**
|
|
4718
|
+
* Expose the underlying LLM provider (set after initialize()).
|
|
4719
|
+
* Used by the stream handler to pass to UITransformer.analyzeAndDecide().
|
|
4720
|
+
*/
|
|
4721
|
+
getLLMProvider() {
|
|
4722
|
+
return this.initialised ? this.llmProvider : void 0;
|
|
4723
|
+
}
|
|
4169
4724
|
async initialize() {
|
|
4170
|
-
var _a
|
|
4725
|
+
var _a;
|
|
4171
4726
|
if (this.initialised) return;
|
|
4172
|
-
const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
|
|
4173
4727
|
const chartInstruction = `
|
|
4728
|
+
You are a helpful product assistant. Use the provided context to answer questions accurately.
|
|
4174
4729
|
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
---
|
|
4181
|
-
|
|
4182
|
-
## 1. INTENT CLASSIFICATION (MANDATORY)
|
|
4183
|
-
|
|
4184
|
-
Classify the query into ONE of the following intents:
|
|
4185
|
-
|
|
4186
|
-
- "explore" \u2192 user wants to browse items (e.g., "show me products")
|
|
4187
|
-
- "analyze" \u2192 user wants aggregation/distribution (e.g., "distribution of products")
|
|
4188
|
-
- "compare" \u2192 user wants side-by-side comparison
|
|
4189
|
-
- "detail" \u2192 user wants structured/tabular data
|
|
4190
|
-
|
|
4191
|
-
---
|
|
4192
|
-
|
|
4193
|
-
## 2. VIEW MAPPING (STRICT)
|
|
4194
|
-
|
|
4195
|
-
| Intent | View |
|
|
4196
|
-
|----------|-----------|
|
|
4197
|
-
| explore | carousel |
|
|
4198
|
-
| analyze | chart |
|
|
4199
|
-
| compare | table |
|
|
4200
|
-
| detail | table |
|
|
4201
|
-
|
|
4202
|
-
\u26A0\uFE0F Override Rules:
|
|
4203
|
-
- If query contains "distribution", "percentage", "ratio" \u2192 ALWAYS chart
|
|
4204
|
-
- If query contains "show products", "list items" \u2192 ALWAYS carousel
|
|
4205
|
-
- NEVER mix views
|
|
4206
|
-
|
|
4207
|
-
---
|
|
4208
|
-
|
|
4209
|
-
## 3. RESPONSE FORMAT (STRICT JSON ONLY INSIDE BLOCK)
|
|
4210
|
-
|
|
4211
|
-
\`\`\`ui
|
|
4212
|
-
{
|
|
4213
|
-
"view": "carousel" | "chart" | "table",
|
|
4214
|
-
"title": "string",
|
|
4215
|
-
"description": "string",
|
|
4216
|
-
"metadata": {
|
|
4217
|
-
"intent": "explore" | "analyze" | "compare" | "detail",
|
|
4218
|
-
"confidence": 0.0-1.0
|
|
4219
|
-
},
|
|
4220
|
-
|
|
4221
|
-
// CHART ONLY
|
|
4222
|
-
"chart": {
|
|
4223
|
-
"type": "pie" | "bar" | "line",
|
|
4224
|
-
"xKey": "label",
|
|
4225
|
-
"yKeys": ["value"],
|
|
4226
|
-
"data": []
|
|
4227
|
-
},
|
|
4228
|
-
|
|
4229
|
-
// TABLE ONLY
|
|
4230
|
-
"table": {
|
|
4231
|
-
"columns": [],
|
|
4232
|
-
"rows": []
|
|
4233
|
-
},
|
|
4234
|
-
|
|
4235
|
-
// CAROUSEL ONLY
|
|
4236
|
-
"carousel": {
|
|
4237
|
-
"items": []
|
|
4238
|
-
},
|
|
4239
|
-
|
|
4240
|
-
"insights": []
|
|
4241
|
-
}
|
|
4242
|
-
\`\`\`
|
|
4243
|
-
|
|
4244
|
-
---
|
|
4245
|
-
|
|
4246
|
-
## 4. DATA CONTRACT (CRITICAL)
|
|
4247
|
-
|
|
4248
|
-
### \u{1F539} CAROUSEL ITEM FORMAT
|
|
4249
|
-
{
|
|
4250
|
-
"id": "string",
|
|
4251
|
-
"name": "string",
|
|
4252
|
-
"brand": "string",
|
|
4253
|
-
"price": number,
|
|
4254
|
-
"image": "url",
|
|
4255
|
-
"link": "url",
|
|
4256
|
-
"inStock": boolean
|
|
4257
|
-
}
|
|
4258
|
-
|
|
4259
|
-
### \u{1F539} CHART DATA FORMAT
|
|
4260
|
-
{
|
|
4261
|
-
"label": "string",
|
|
4262
|
-
"value": number,
|
|
4263
|
-
"inStock": boolean (optional)
|
|
4264
|
-
}
|
|
4265
|
-
|
|
4266
|
-
### \u{1F539} TABLE FORMAT
|
|
4267
|
-
- columns MUST match row keys
|
|
4268
|
-
- rows MUST be flat objects
|
|
4269
|
-
|
|
4270
|
-
---
|
|
4271
|
-
|
|
4272
|
-
## 5. DATA RULES
|
|
4273
|
-
|
|
4274
|
-
- NEVER hallucinate fields or data
|
|
4275
|
-
- ONLY use retrieved vector DB data
|
|
4276
|
-
- NO placeholders ("...", "N/A")
|
|
4277
|
-
- If image/link missing \u2192 REMOVE FIELD
|
|
4278
|
-
- Aggregate BEFORE rendering chart
|
|
4279
|
-
- IF NO DATA IS FOUND in the retrieved context:
|
|
4280
|
-
\u2192 DO NOT output a \`\`\`ui\`\`\` block
|
|
4281
|
-
\u2192 Instead, state clearly that no relevant products or data were found.
|
|
4282
|
-
|
|
4283
|
-
---
|
|
4284
|
-
|
|
4285
|
-
## 6. SMART RULES (IMPORTANT)
|
|
4286
|
-
|
|
4287
|
-
- If user asks BOTH:
|
|
4288
|
-
"show products AND distribution"
|
|
4289
|
-
\u2192 PRIORITIZE "explore" \u2192 carousel
|
|
4290
|
-
|
|
4291
|
-
- If dataset size > 20:
|
|
4292
|
-
\u2192 aggregate \u2192 chart or table
|
|
4293
|
-
|
|
4294
|
-
---
|
|
4295
|
-
|
|
4296
|
-
## 7. OUTPUT RULES
|
|
4297
|
-
|
|
4298
|
-
- ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block
|
|
4299
|
-
- JSON must be VALID
|
|
4300
|
-
- No explanation inside block
|
|
4301
|
-
- NEVER use ASCII charts, Markdown tables, or text-based drawings for data.
|
|
4302
|
-
- If data is tabular or statistical, ALWAYS use the \`\`\`ui\`\`\` block.
|
|
4730
|
+
### UI STYLE RULES (CRITICAL):
|
|
4731
|
+
- NEVER generate markdown tables. If you do, the UI will break.
|
|
4732
|
+
- NEVER generate HTML tags like <figure>, <tbody>, <tr>, etc.
|
|
4733
|
+
- NEVER generate text-based charts or graphs.
|
|
4734
|
+
- ONLY use plain text and bullet points.
|
|
4303
4735
|
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
User: "show me beauty products"
|
|
4309
|
-
|
|
4310
|
-
\`\`\`ui
|
|
4311
|
-
{
|
|
4312
|
-
"view": "carousel",
|
|
4313
|
-
"title": "Beauty Products",
|
|
4314
|
-
"description": "Browse available beauty items",
|
|
4315
|
-
"metadata": {
|
|
4316
|
-
"intent": "explore",
|
|
4317
|
-
"confidence": 0.95
|
|
4318
|
-
},
|
|
4319
|
-
"carousel": {
|
|
4320
|
-
"items": [
|
|
4321
|
-
{
|
|
4322
|
-
"id": "1",
|
|
4323
|
-
"name": "Mascara",
|
|
4324
|
-
"brand": "Essence",
|
|
4325
|
-
"price": 9.99,
|
|
4326
|
-
"image": "https://example.com/mascara.jpg",
|
|
4327
|
-
"link": "https://example.com/p1",
|
|
4328
|
-
"inStock": true
|
|
4329
|
-
}
|
|
4330
|
-
]
|
|
4331
|
-
},
|
|
4332
|
-
"insights": ["Most products are affordable"]
|
|
4333
|
-
}
|
|
4334
|
-
\`\`\`
|
|
4335
|
-
|
|
4336
|
-
---
|
|
4337
|
-
|
|
4338
|
-
User: "distribution of products across categories"
|
|
4339
|
-
|
|
4340
|
-
\`\`\`ui
|
|
4341
|
-
{
|
|
4342
|
-
"view": "chart",
|
|
4343
|
-
"title": "Product Distribution",
|
|
4344
|
-
"description": "Category-wise product distribution",
|
|
4345
|
-
"metadata": {
|
|
4346
|
-
"intent": "analyze",
|
|
4347
|
-
"confidence": 0.98
|
|
4348
|
-
},
|
|
4349
|
-
"chart": {
|
|
4350
|
-
"type": "pie",
|
|
4351
|
-
"xKey": "label",
|
|
4352
|
-
"yKeys": ["value"],
|
|
4353
|
-
"data": [
|
|
4354
|
-
{ "label": "Beauty", "value": 15 },
|
|
4355
|
-
{ "label": "Electronics", "value": 10 }
|
|
4356
|
-
]
|
|
4357
|
-
},
|
|
4358
|
-
"insights": ["Beauty dominates inventory"]
|
|
4359
|
-
}
|
|
4360
|
-
\`\`\`
|
|
4736
|
+
### PRODUCT DISPLAY:
|
|
4737
|
+
- When recommending products, simply list their names, prices, and features in a friendly, conversational manner.
|
|
4738
|
+
- The UI will automatically detect these products and show high-quality product cards in a carousel below your message.
|
|
4739
|
+
- Do NOT try to format product lists as tables.
|
|
4361
4740
|
`;
|
|
4362
|
-
|
|
4363
|
-
let cleanPrompt = this.config.llm.systemPrompt || "";
|
|
4364
|
-
cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
|
|
4365
|
-
cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
|
|
4366
|
-
this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
|
|
4367
|
-
}
|
|
4741
|
+
this.config.llm.systemPrompt = chartInstruction;
|
|
4368
4742
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
4369
4743
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
4370
4744
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
@@ -4379,7 +4753,7 @@ User: "distribution of products across categories"
|
|
|
4379
4753
|
this.entityExtractor = new EntityExtractor(this.llmProvider);
|
|
4380
4754
|
}
|
|
4381
4755
|
await this.vectorDB.initialize();
|
|
4382
|
-
if (((
|
|
4756
|
+
if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic") {
|
|
4383
4757
|
this.agent = new LangChainAgent(this, this.config);
|
|
4384
4758
|
await this.agent.initialize(this.llmProvider);
|
|
4385
4759
|
}
|
|
@@ -4516,14 +4890,16 @@ User: "distribution of products across categories"
|
|
|
4516
4890
|
let reply = "";
|
|
4517
4891
|
let sources = [];
|
|
4518
4892
|
let graphData;
|
|
4893
|
+
let uiTransformation;
|
|
4519
4894
|
try {
|
|
4520
4895
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
4521
4896
|
const chunk = temp.value;
|
|
4522
4897
|
if (typeof chunk === "string") {
|
|
4523
4898
|
reply += chunk;
|
|
4524
|
-
} else if ("
|
|
4525
|
-
sources = chunk.sources;
|
|
4526
|
-
graphData = chunk.graphData;
|
|
4899
|
+
} else if (typeof chunk === "object" && chunk !== null) {
|
|
4900
|
+
if ("sources" in chunk) sources = chunk.sources;
|
|
4901
|
+
if ("graphData" in chunk) graphData = chunk.graphData;
|
|
4902
|
+
if ("ui_transformation" in chunk) uiTransformation = chunk.ui_transformation;
|
|
4527
4903
|
}
|
|
4528
4904
|
}
|
|
4529
4905
|
} catch (temp) {
|
|
@@ -4536,7 +4912,7 @@ User: "distribution of products across categories"
|
|
|
4536
4912
|
throw error[0];
|
|
4537
4913
|
}
|
|
4538
4914
|
}
|
|
4539
|
-
return { reply, sources, graphData };
|
|
4915
|
+
return { reply, sources, graphData, ui_transformation: uiTransformation };
|
|
4540
4916
|
}
|
|
4541
4917
|
/**
|
|
4542
4918
|
* High-performance streaming RAG flow.
|
|
@@ -4580,7 +4956,12 @@ ${graphContext}
|
|
|
4580
4956
|
VECTOR CONTEXT:
|
|
4581
4957
|
${context}`;
|
|
4582
4958
|
}
|
|
4583
|
-
const
|
|
4959
|
+
const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
|
|
4960
|
+
const trainingPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys) : Promise.resolve(void 0);
|
|
4961
|
+
const restrictionSuffix = "\n\n(IMPORTANT: Use plain text only. NEVER generate tables, HTML figures, or text charts. If listing products, use simple bullet points.)";
|
|
4962
|
+
const hardenedHistory = [...history];
|
|
4963
|
+
const userQuestion = { role: "user", content: question + restrictionSuffix };
|
|
4964
|
+
const messages = [...hardenedHistory, userQuestion];
|
|
4584
4965
|
if (this.llmProvider.chatStream) {
|
|
4585
4966
|
const stream = this.llmProvider.chatStream(messages, context);
|
|
4586
4967
|
if (!stream) {
|
|
@@ -4605,7 +4986,8 @@ ${context}`;
|
|
|
4605
4986
|
const reply = yield new __await(this.llmProvider.chat(messages, context));
|
|
4606
4987
|
yield reply;
|
|
4607
4988
|
}
|
|
4608
|
-
const
|
|
4989
|
+
const trainedSchema = yield new __await(trainingPromise);
|
|
4990
|
+
const uiTransformation = yield new __await(this.generateUiTransformation(question, sources, context, trainedSchema));
|
|
4609
4991
|
yield {
|
|
4610
4992
|
reply: "",
|
|
4611
4993
|
sources,
|
|
@@ -4621,6 +5003,17 @@ ${context}`;
|
|
|
4621
5003
|
* Universal retrieval method combining all enabled providers.
|
|
4622
5004
|
* Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
|
|
4623
5005
|
*/
|
|
5006
|
+
async generateUiTransformation(question, sources, _context, trainedSchema) {
|
|
5007
|
+
if (!sources || sources.length === 0) {
|
|
5008
|
+
return UITransformer.transform(question, sources, this.config, trainedSchema);
|
|
5009
|
+
}
|
|
5010
|
+
try {
|
|
5011
|
+
return await UITransformer.analyzeAndDecide(question, sources, this.llmProvider);
|
|
5012
|
+
} catch (err) {
|
|
5013
|
+
console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
|
|
5014
|
+
return UITransformer.transform(question, sources, this.config, trainedSchema);
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
4624
5017
|
async retrieve(query, options) {
|
|
4625
5018
|
var _a, _b, _c;
|
|
4626
5019
|
const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
|
|
@@ -4823,6 +5216,14 @@ var VectorPlugin = class {
|
|
|
4823
5216
|
getConfig() {
|
|
4824
5217
|
return this.config;
|
|
4825
5218
|
}
|
|
5219
|
+
/**
|
|
5220
|
+
* Get the initialized LLM provider (available after the first request).
|
|
5221
|
+
* Used by handlers to pass to UITransformer.analyzeAndDecide() for
|
|
5222
|
+
* LLM-driven visualization decisions.
|
|
5223
|
+
*/
|
|
5224
|
+
getLLMProvider() {
|
|
5225
|
+
return this.pipeline.getLLMProvider();
|
|
5226
|
+
}
|
|
4826
5227
|
/**
|
|
4827
5228
|
* Perform pre-flight health checks on all configured providers.
|
|
4828
5229
|
* Useful to verify connectivity before running operations.
|
|
@@ -4944,7 +5345,7 @@ function sseMetaFrame(meta) {
|
|
|
4944
5345
|
`;
|
|
4945
5346
|
}
|
|
4946
5347
|
function sseUIFrame(uiTransformation) {
|
|
4947
|
-
return `data: ${JSON.stringify(
|
|
5348
|
+
return `data: ${JSON.stringify({ type: "ui_transformation", data: uiTransformation })}
|
|
4948
5349
|
|
|
4949
5350
|
`;
|
|
4950
5351
|
}
|
|
@@ -4999,6 +5400,7 @@ function createStreamHandler(configOrPlugin) {
|
|
|
4999
5400
|
const encoder = new TextEncoder();
|
|
5000
5401
|
const stream = new ReadableStream({
|
|
5001
5402
|
async start(controller) {
|
|
5403
|
+
var _a, _b;
|
|
5002
5404
|
const enqueue = (text) => controller.enqueue(encoder.encode(text));
|
|
5003
5405
|
try {
|
|
5004
5406
|
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
@@ -5009,13 +5411,22 @@ function createStreamHandler(configOrPlugin) {
|
|
|
5009
5411
|
enqueue(sseTextFrame(chunk));
|
|
5010
5412
|
} else {
|
|
5011
5413
|
enqueue(sseMetaFrame(chunk));
|
|
5012
|
-
const
|
|
5013
|
-
|
|
5414
|
+
const responseChunk = chunk;
|
|
5415
|
+
const sources = (responseChunk == null ? void 0 : responseChunk.sources) || [];
|
|
5416
|
+
if (sources.length > 0) {
|
|
5014
5417
|
try {
|
|
5015
|
-
const
|
|
5016
|
-
|
|
5418
|
+
const llmProvider = (_a = plugin.getLLMProvider) == null ? void 0 : _a.call(plugin);
|
|
5419
|
+
const uiTransformation = (_b = responseChunk == null ? void 0 : responseChunk.ui_transformation) != null ? _b : llmProvider ? await UITransformer.analyzeAndDecide(message, sources, llmProvider) : UITransformer.transform(message, sources, plugin.getConfig());
|
|
5420
|
+
if (uiTransformation) {
|
|
5421
|
+
enqueue(sseUIFrame(uiTransformation));
|
|
5422
|
+
}
|
|
5017
5423
|
} catch (transformError) {
|
|
5018
5424
|
console.warn("[createStreamHandler] UI transformation warning:", transformError);
|
|
5425
|
+
try {
|
|
5426
|
+
const fallback = UITransformer.transform(message, sources, plugin.getConfig());
|
|
5427
|
+
if (fallback) enqueue(sseUIFrame(fallback));
|
|
5428
|
+
} catch (e) {
|
|
5429
|
+
}
|
|
5019
5430
|
}
|
|
5020
5431
|
}
|
|
5021
5432
|
}
|