@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/server.js
CHANGED
|
@@ -334,196 +334,205 @@ var init_PineconeProvider = __esm({
|
|
|
334
334
|
}
|
|
335
335
|
});
|
|
336
336
|
|
|
337
|
-
// src/providers/vectordb/
|
|
338
|
-
var
|
|
339
|
-
__export(
|
|
340
|
-
|
|
337
|
+
// src/providers/vectordb/MultiTablePostgresProvider.ts
|
|
338
|
+
var MultiTablePostgresProvider_exports = {};
|
|
339
|
+
__export(MultiTablePostgresProvider_exports, {
|
|
340
|
+
MultiTablePostgresProvider: () => MultiTablePostgresProvider
|
|
341
341
|
});
|
|
342
|
-
var import_pg,
|
|
343
|
-
var
|
|
344
|
-
"src/providers/vectordb/
|
|
342
|
+
var import_pg, MultiTablePostgresProvider;
|
|
343
|
+
var init_MultiTablePostgresProvider = __esm({
|
|
344
|
+
"src/providers/vectordb/MultiTablePostgresProvider.ts"() {
|
|
345
345
|
"use strict";
|
|
346
346
|
import_pg = require("pg");
|
|
347
347
|
init_BaseVectorProvider();
|
|
348
|
-
|
|
348
|
+
MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
349
349
|
constructor(config) {
|
|
350
|
-
var _a;
|
|
350
|
+
var _a, _b, _c, _d, _e;
|
|
351
351
|
super(config);
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
352
|
+
const opts = config.options || {};
|
|
353
|
+
if (!opts.connectionString) {
|
|
354
|
+
throw new Error("[MultiTablePostgresProvider] options.connectionString is required");
|
|
355
|
+
}
|
|
355
356
|
this.connectionString = opts.connectionString;
|
|
356
|
-
this.dimensions = (_a = opts.dimensions) != null ? _a :
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
message: "PostgreSQL connection string is required",
|
|
367
|
-
suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
|
|
368
|
-
severity: "error"
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
|
|
372
|
-
errors.push({
|
|
373
|
-
field: "vectorDb.options.tables",
|
|
374
|
-
message: "PostgreSQL tables must be a string or a string array",
|
|
375
|
-
severity: "error"
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
return errors;
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
static getHealthChecker() {
|
|
383
|
-
return {
|
|
384
|
-
async check(config) {
|
|
385
|
-
const opts = config.options || {};
|
|
386
|
-
const timestamp = Date.now();
|
|
387
|
-
try {
|
|
388
|
-
const { Client } = await import("pg");
|
|
389
|
-
const client = new Client({ connectionString: opts.connectionString });
|
|
390
|
-
await client.connect();
|
|
391
|
-
const result = await client.query(`
|
|
392
|
-
SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
|
|
393
|
-
`);
|
|
394
|
-
const hasVector = result.rows[0].exists;
|
|
395
|
-
await client.end();
|
|
396
|
-
return {
|
|
397
|
-
healthy: true,
|
|
398
|
-
provider: "postgresql",
|
|
399
|
-
capabilities: { pgvectorInstalled: hasVector },
|
|
400
|
-
timestamp
|
|
401
|
-
};
|
|
402
|
-
} catch (error) {
|
|
403
|
-
return {
|
|
404
|
-
healthy: false,
|
|
405
|
-
provider: "postgresql",
|
|
406
|
-
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
407
|
-
timestamp
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
};
|
|
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;
|
|
412
367
|
}
|
|
413
368
|
async initialize() {
|
|
414
369
|
this.pool = new import_pg.Pool({ connectionString: this.connectionString });
|
|
415
370
|
const client = await this.pool.connect();
|
|
416
371
|
try {
|
|
417
|
-
await client.query(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
content TEXT NOT NULL,
|
|
423
|
-
metadata JSONB,
|
|
424
|
-
embedding VECTOR(${this.dimensions})
|
|
425
|
-
)
|
|
426
|
-
`);
|
|
427
|
-
await client.query(`
|
|
428
|
-
CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
|
|
429
|
-
ON ${this.tableName}
|
|
430
|
-
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'
|
|
431
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
|
+
}
|
|
432
396
|
} finally {
|
|
433
397
|
client.release();
|
|
434
398
|
}
|
|
435
399
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
SET namespace = EXCLUDED.namespace,
|
|
444
|
-
content = EXCLUDED.content,
|
|
445
|
-
metadata = EXCLUDED.metadata,
|
|
446
|
-
embedding = EXCLUDED.embedding`,
|
|
447
|
-
[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."
|
|
448
407
|
);
|
|
449
408
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const batch = docs.slice(i, i + BATCH_SIZE);
|
|
458
|
-
const values = [];
|
|
459
|
-
const valuePlaceholders = batch.map((doc, idx) => {
|
|
460
|
-
var _a;
|
|
461
|
-
const offset = idx * 5;
|
|
462
|
-
values.push(doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), `[${doc.vector.join(",")}]`);
|
|
463
|
-
return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}::vector)`;
|
|
464
|
-
}).join(", ");
|
|
465
|
-
const query = `
|
|
466
|
-
INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
|
|
467
|
-
VALUES ${valuePlaceholders}
|
|
468
|
-
ON CONFLICT (id) DO UPDATE
|
|
469
|
-
SET namespace = EXCLUDED.namespace,
|
|
470
|
-
content = EXCLUDED.content,
|
|
471
|
-
metadata = EXCLUDED.metadata,
|
|
472
|
-
embedding = EXCLUDED.embedding
|
|
473
|
-
`;
|
|
474
|
-
await client.query(query, values);
|
|
475
|
-
}
|
|
476
|
-
await client.query("COMMIT");
|
|
477
|
-
} catch (error) {
|
|
478
|
-
await client.query("ROLLBACK");
|
|
479
|
-
throw error;
|
|
480
|
-
} finally {
|
|
481
|
-
client.release();
|
|
482
|
-
}
|
|
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
|
+
);
|
|
483
416
|
}
|
|
484
|
-
|
|
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
|
+
}
|
|
485
425
|
const vectorLiteral = `[${vector.join(",")}]`;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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);
|
|
497
512
|
}
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
content: String(row["content"]),
|
|
514
|
-
metadata: row["metadata"]
|
|
515
|
-
}));
|
|
516
|
-
} finally {
|
|
517
|
-
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));
|
|
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)}`);
|
|
518
528
|
}
|
|
529
|
+
return finalSorted.slice(0, Math.max(topK, 15));
|
|
519
530
|
}
|
|
520
|
-
async delete(
|
|
521
|
-
|
|
522
|
-
const params = namespace ? [id, namespace] : [id];
|
|
523
|
-
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.");
|
|
524
533
|
}
|
|
525
|
-
async deleteNamespace(
|
|
526
|
-
|
|
534
|
+
async deleteNamespace(_namespace) {
|
|
535
|
+
console.warn("[MultiTablePostgresProvider] deleteNamespace() is a no-op for multi-table mode.");
|
|
527
536
|
}
|
|
528
537
|
async ping() {
|
|
529
538
|
try {
|
|
@@ -534,7 +543,9 @@ var init_PostgreSQLProvider = __esm({
|
|
|
534
543
|
}
|
|
535
544
|
}
|
|
536
545
|
async disconnect() {
|
|
537
|
-
|
|
546
|
+
if (this.pool) {
|
|
547
|
+
await this.pool.end();
|
|
548
|
+
}
|
|
538
549
|
}
|
|
539
550
|
};
|
|
540
551
|
}
|
|
@@ -1736,7 +1747,7 @@ function getRagConfig(baseConfig, env = process.env) {
|
|
|
1736
1747
|
return getEnvConfig(env, baseConfig);
|
|
1737
1748
|
}
|
|
1738
1749
|
function getEnvConfig(env = process.env, base) {
|
|
1739
|
-
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;
|
|
1750
|
+
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;
|
|
1740
1751
|
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__";
|
|
1741
1752
|
const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
|
|
1742
1753
|
const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
|
|
@@ -1747,33 +1758,35 @@ function getEnvConfig(env = process.env, base) {
|
|
|
1747
1758
|
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 : "";
|
|
1748
1759
|
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;
|
|
1749
1760
|
} else if (vectorProvider === "pgvector" || vectorProvider === "postgresql") {
|
|
1750
|
-
vectorDbOptions.connectionString = (_p = (
|
|
1761
|
+
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 : "";
|
|
1762
|
+
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;
|
|
1763
|
+
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;
|
|
1751
1764
|
vectorDbOptions.dimensions = embeddingDimensions;
|
|
1752
1765
|
} else if (vectorProvider === "mongodb") {
|
|
1753
|
-
vectorDbOptions.uri = (
|
|
1754
|
-
vectorDbOptions.database = (
|
|
1755
|
-
vectorDbOptions.collection = (
|
|
1756
|
-
vectorDbOptions.indexName = (
|
|
1766
|
+
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 : "";
|
|
1767
|
+
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 : "";
|
|
1768
|
+
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 : "";
|
|
1769
|
+
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";
|
|
1757
1770
|
} else if (vectorProvider === "qdrant") {
|
|
1758
|
-
vectorDbOptions.baseUrl = (
|
|
1759
|
-
vectorDbOptions.apiKey = (
|
|
1771
|
+
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";
|
|
1772
|
+
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;
|
|
1760
1773
|
vectorDbOptions.dimensions = embeddingDimensions;
|
|
1761
1774
|
} else if (vectorProvider === "milvus") {
|
|
1762
|
-
vectorDbOptions.baseUrl = (
|
|
1775
|
+
vectorDbOptions.baseUrl = (_Z = readString(env, "MILVUS_URL")) != null ? _Z : "http://localhost:19530";
|
|
1763
1776
|
vectorDbOptions.apiKey = readString(env, "MILVUS_API_KEY");
|
|
1764
1777
|
} else if (vectorProvider === "chromadb") {
|
|
1765
|
-
vectorDbOptions.baseUrl = (
|
|
1778
|
+
vectorDbOptions.baseUrl = (__ = readString(env, "CHROMADB_URL")) != null ? __ : "http://localhost:8000";
|
|
1766
1779
|
} else if (vectorProvider === "weaviate") {
|
|
1767
|
-
vectorDbOptions.baseUrl = (
|
|
1780
|
+
vectorDbOptions.baseUrl = (_$ = readString(env, "WEAVIATE_URL")) != null ? _$ : "http://localhost:8080";
|
|
1768
1781
|
vectorDbOptions.apiKey = readString(env, "WEAVIATE_API_KEY");
|
|
1769
1782
|
} else if (vectorProvider === "redis") {
|
|
1770
|
-
vectorDbOptions.baseUrl = (
|
|
1783
|
+
vectorDbOptions.baseUrl = (_aa = readString(env, "REDIS_URL")) != null ? _aa : "";
|
|
1771
1784
|
vectorDbOptions.apiKey = readString(env, "REDIS_API_KEY");
|
|
1772
1785
|
} else if (vectorProvider === "rest") {
|
|
1773
|
-
vectorDbOptions.baseUrl = (
|
|
1786
|
+
vectorDbOptions.baseUrl = (_ba = readString(env, "VECTOR_DB_REST_URL")) != null ? _ba : "";
|
|
1774
1787
|
vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { "api-key": readString(env, "VECTOR_DB_REST_API_KEY") } : {};
|
|
1775
1788
|
} else if (vectorProvider === "universal_rest") {
|
|
1776
|
-
vectorDbOptions.baseUrl = (
|
|
1789
|
+
vectorDbOptions.baseUrl = (_da = (_ca = readString(env, "VECTOR_BASE_URL")) != null ? _ca : readString(env, "VECTOR_DB_REST_URL")) != null ? _da : "";
|
|
1777
1790
|
vectorDbOptions.profile = readString(env, "VECTOR_UNIVERSAL_PROFILE");
|
|
1778
1791
|
vectorDbOptions.headers = readString(env, "VECTOR_DB_REST_API_KEY") ? { Authorization: `Bearer ${readString(env, "VECTOR_DB_REST_API_KEY")}` } : {};
|
|
1779
1792
|
}
|
|
@@ -1790,20 +1803,20 @@ function getEnvConfig(env = process.env, base) {
|
|
|
1790
1803
|
openai: readString(env, "OPENAI_API_KEY"),
|
|
1791
1804
|
gemini: readString(env, "GEMINI_API_KEY"),
|
|
1792
1805
|
ollama: void 0,
|
|
1793
|
-
universal_rest: (
|
|
1794
|
-
custom: (
|
|
1806
|
+
universal_rest: (_ea = readString(env, "EMBEDDING_API_KEY")) != null ? _ea : readString(env, "OPENAI_API_KEY"),
|
|
1807
|
+
custom: (_fa = readString(env, "EMBEDDING_API_KEY")) != null ? _fa : readString(env, "OPENAI_API_KEY")
|
|
1795
1808
|
};
|
|
1796
1809
|
return {
|
|
1797
1810
|
projectId,
|
|
1798
1811
|
vectorDb: {
|
|
1799
1812
|
provider: vectorProvider,
|
|
1800
|
-
indexName: (
|
|
1813
|
+
indexName: (_ha = (_ga = readString(env, "VECTOR_DB_INDEX")) != null ? _ga : vectorDbOptions.indexName) != null ? _ha : "rag-index",
|
|
1801
1814
|
options: vectorDbOptions
|
|
1802
1815
|
},
|
|
1803
1816
|
llm: {
|
|
1804
1817
|
provider: llmProvider,
|
|
1805
|
-
model: (
|
|
1806
|
-
apiKey: (
|
|
1818
|
+
model: (_ia = readString(env, "LLM_MODEL")) != null ? _ia : "gpt-4o",
|
|
1819
|
+
apiKey: (_ja = llmApiKeyByProvider[llmProvider]) != null ? _ja : "",
|
|
1807
1820
|
baseUrl: readString(env, "LLM_BASE_URL"),
|
|
1808
1821
|
systemPrompt: readString(env, "LLM_SYSTEM_PROMPT"),
|
|
1809
1822
|
maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
|
|
@@ -1814,7 +1827,7 @@ function getEnvConfig(env = process.env, base) {
|
|
|
1814
1827
|
},
|
|
1815
1828
|
embedding: {
|
|
1816
1829
|
provider: embeddingProvider,
|
|
1817
|
-
model: (
|
|
1830
|
+
model: (_ka = readString(env, "EMBEDDING_MODEL")) != null ? _ka : "text-embedding-3-small",
|
|
1818
1831
|
apiKey: embeddingApiKeyByProvider[embeddingProvider],
|
|
1819
1832
|
baseUrl: readString(env, "EMBEDDING_BASE_URL"),
|
|
1820
1833
|
dimensions: embeddingDimensions,
|
|
@@ -1825,17 +1838,17 @@ function getEnvConfig(env = process.env, base) {
|
|
|
1825
1838
|
}
|
|
1826
1839
|
},
|
|
1827
1840
|
ui: {
|
|
1828
|
-
title: (
|
|
1829
|
-
subtitle: (
|
|
1830
|
-
primaryColor: (
|
|
1831
|
-
accentColor: (
|
|
1832
|
-
logoUrl: (
|
|
1833
|
-
placeholder: (
|
|
1834
|
-
showSources: ((
|
|
1835
|
-
welcomeMessage: (
|
|
1836
|
-
visualStyle: (
|
|
1837
|
-
borderRadius: (
|
|
1838
|
-
allowUpload: ((
|
|
1841
|
+
title: (_ma = (_la = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _la : readString(env, "UI_TITLE")) != null ? _ma : "AI Assistant",
|
|
1842
|
+
subtitle: (_oa = (_na = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _na : readString(env, "UI_SUBTITLE")) != null ? _oa : "Powered by RAG",
|
|
1843
|
+
primaryColor: (_qa = (_pa = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _pa : readString(env, "UI_PRIMARY_COLOR")) != null ? _qa : "#10b981",
|
|
1844
|
+
accentColor: (_sa = (_ra = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _ra : readString(env, "UI_ACCENT_COLOR")) != null ? _sa : "#3b82f6",
|
|
1845
|
+
logoUrl: (_ta = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _ta : readString(env, "UI_LOGO_URL"),
|
|
1846
|
+
placeholder: (_va = (_ua = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _ua : readString(env, "UI_PLACEHOLDER")) != null ? _va : "Ask me anything\u2026",
|
|
1847
|
+
showSources: ((_xa = (_wa = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _wa : readString(env, "UI_SHOW_SOURCES")) != null ? _xa : "true") !== "false",
|
|
1848
|
+
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.",
|
|
1849
|
+
visualStyle: (_Ba = (_Aa = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _Aa : readString(env, "UI_VISUAL_STYLE")) != null ? _Ba : "glass",
|
|
1850
|
+
borderRadius: (_Da = (_Ca = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _Ca : readString(env, "UI_BORDER_RADIUS")) != null ? _Da : "xl",
|
|
1851
|
+
allowUpload: ((_Fa = (_Ea = readString(env, "NEXT_PUBLIC_ALLOW_UPLOAD")) != null ? _Ea : readString(env, "UI_ALLOW_UPLOAD")) != null ? _Fa : "false") === "true"
|
|
1839
1852
|
},
|
|
1840
1853
|
rag: {
|
|
1841
1854
|
topK: readNumber(env, "RAG_TOP_K", 5),
|
|
@@ -1952,14 +1965,14 @@ var OpenAIProvider = class {
|
|
|
1952
1965
|
};
|
|
1953
1966
|
}
|
|
1954
1967
|
async chat(messages, context, options) {
|
|
1955
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1956
|
-
const
|
|
1968
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1969
|
+
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.
|
|
1957
1970
|
|
|
1958
1971
|
Context:
|
|
1959
1972
|
${context}`;
|
|
1960
1973
|
const systemMessage = {
|
|
1961
1974
|
role: "system",
|
|
1962
|
-
content:
|
|
1975
|
+
content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
|
|
1963
1976
|
|
|
1964
1977
|
Context:
|
|
1965
1978
|
${context}`
|
|
@@ -1974,22 +1987,22 @@ ${context}`
|
|
|
1974
1987
|
const completion = await this.client.chat.completions.create({
|
|
1975
1988
|
model: this.llmConfig.model,
|
|
1976
1989
|
messages: formattedMessages,
|
|
1977
|
-
max_tokens: (
|
|
1978
|
-
temperature: (
|
|
1990
|
+
max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
|
|
1991
|
+
temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
|
|
1979
1992
|
stop: options == null ? void 0 : options.stop
|
|
1980
1993
|
});
|
|
1981
|
-
return (
|
|
1994
|
+
return (_i = (_h = (_g = completion.choices[0]) == null ? void 0 : _g.message) == null ? void 0 : _h.content) != null ? _i : "";
|
|
1982
1995
|
}
|
|
1983
1996
|
chatStream(messages, context, options) {
|
|
1984
1997
|
return __asyncGenerator(this, null, function* () {
|
|
1985
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1986
|
-
const
|
|
1998
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1999
|
+
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.
|
|
1987
2000
|
|
|
1988
2001
|
Context:
|
|
1989
2002
|
${context}`;
|
|
1990
2003
|
const systemMessage = {
|
|
1991
2004
|
role: "system",
|
|
1992
|
-
content:
|
|
2005
|
+
content: resolvedSystemPrompt.includes("{{context}}") ? resolvedSystemPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedSystemPrompt : `${resolvedSystemPrompt}
|
|
1993
2006
|
|
|
1994
2007
|
Context:
|
|
1995
2008
|
${context}`
|
|
@@ -2004,8 +2017,8 @@ ${context}`
|
|
|
2004
2017
|
const stream = yield new __await(this.client.chat.completions.create({
|
|
2005
2018
|
model: this.llmConfig.model,
|
|
2006
2019
|
messages: formattedMessages,
|
|
2007
|
-
max_tokens: (
|
|
2008
|
-
temperature: (
|
|
2020
|
+
max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
|
|
2021
|
+
temperature: (_f = (_e = options == null ? void 0 : options.temperature) != null ? _e : this.llmConfig.temperature) != null ? _f : 0.7,
|
|
2009
2022
|
stop: options == null ? void 0 : options.stop,
|
|
2010
2023
|
stream: true
|
|
2011
2024
|
}));
|
|
@@ -2015,7 +2028,7 @@ ${context}`
|
|
|
2015
2028
|
try {
|
|
2016
2029
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2017
2030
|
const chunk = temp.value;
|
|
2018
|
-
const content = ((
|
|
2031
|
+
const content = ((_h = (_g = chunk.choices[0]) == null ? void 0 : _g.delta) == null ? void 0 : _h.content) || "";
|
|
2019
2032
|
if (content) yield content;
|
|
2020
2033
|
}
|
|
2021
2034
|
} catch (temp) {
|
|
@@ -2113,12 +2126,12 @@ var AnthropicProvider = class {
|
|
|
2113
2126
|
};
|
|
2114
2127
|
}
|
|
2115
2128
|
async chat(messages, context, options) {
|
|
2116
|
-
var _a, _b, _c;
|
|
2117
|
-
const
|
|
2129
|
+
var _a, _b, _c, _d;
|
|
2130
|
+
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.
|
|
2118
2131
|
|
|
2119
2132
|
Context:
|
|
2120
2133
|
${context}`;
|
|
2121
|
-
const system =
|
|
2134
|
+
const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
|
|
2122
2135
|
|
|
2123
2136
|
Context:
|
|
2124
2137
|
${context}`;
|
|
@@ -2128,7 +2141,7 @@ ${context}`;
|
|
|
2128
2141
|
}));
|
|
2129
2142
|
const response = await this.client.messages.create({
|
|
2130
2143
|
model: this.llmConfig.model,
|
|
2131
|
-
max_tokens: (
|
|
2144
|
+
max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
|
|
2132
2145
|
system,
|
|
2133
2146
|
messages: anthropicMessages
|
|
2134
2147
|
});
|
|
@@ -2137,12 +2150,12 @@ ${context}`;
|
|
|
2137
2150
|
}
|
|
2138
2151
|
chatStream(messages, context, options) {
|
|
2139
2152
|
return __asyncGenerator(this, null, function* () {
|
|
2140
|
-
var _a, _b, _c;
|
|
2141
|
-
const
|
|
2153
|
+
var _a, _b, _c, _d;
|
|
2154
|
+
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.
|
|
2142
2155
|
|
|
2143
2156
|
Context:
|
|
2144
2157
|
${context}`;
|
|
2145
|
-
const system =
|
|
2158
|
+
const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
|
|
2146
2159
|
|
|
2147
2160
|
Context:
|
|
2148
2161
|
${context}`;
|
|
@@ -2152,7 +2165,7 @@ ${context}`;
|
|
|
2152
2165
|
}));
|
|
2153
2166
|
const stream = yield new __await(this.client.messages.create({
|
|
2154
2167
|
model: this.llmConfig.model,
|
|
2155
|
-
max_tokens: (
|
|
2168
|
+
max_tokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : 1024,
|
|
2156
2169
|
system,
|
|
2157
2170
|
messages: anthropicMessages,
|
|
2158
2171
|
stream: true
|
|
@@ -2267,7 +2280,7 @@ var OllamaProvider = class {
|
|
|
2267
2280
|
}
|
|
2268
2281
|
async chat(messages, context, options) {
|
|
2269
2282
|
var _a, _b, _c, _d;
|
|
2270
|
-
const system = this.buildSystemPrompt(context);
|
|
2283
|
+
const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
|
|
2271
2284
|
const { data } = await this.http.post("/api/chat", {
|
|
2272
2285
|
model: this.llmConfig.model,
|
|
2273
2286
|
stream: false,
|
|
@@ -2285,7 +2298,7 @@ var OllamaProvider = class {
|
|
|
2285
2298
|
chatStream(messages, context, options) {
|
|
2286
2299
|
return __asyncGenerator(this, null, function* () {
|
|
2287
2300
|
var _a, _b, _c, _d, _e, _f;
|
|
2288
|
-
const system = this.buildSystemPrompt(context);
|
|
2301
|
+
const system = this.buildSystemPrompt(context, options == null ? void 0 : options.systemPrompt);
|
|
2289
2302
|
const response = yield new __await(this.http.post("/api/chat", {
|
|
2290
2303
|
model: this.llmConfig.model,
|
|
2291
2304
|
stream: true,
|
|
@@ -2341,8 +2354,11 @@ var OllamaProvider = class {
|
|
|
2341
2354
|
}
|
|
2342
2355
|
});
|
|
2343
2356
|
}
|
|
2344
|
-
buildSystemPrompt(context) {
|
|
2357
|
+
buildSystemPrompt(context, overridePrompt) {
|
|
2345
2358
|
var _a;
|
|
2359
|
+
if (overridePrompt) {
|
|
2360
|
+
return overridePrompt;
|
|
2361
|
+
}
|
|
2346
2362
|
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
|
|
2347
2363
|
|
|
2348
2364
|
Context:
|
|
@@ -2394,20 +2410,41 @@ ${context}`;
|
|
|
2394
2410
|
};
|
|
2395
2411
|
|
|
2396
2412
|
// src/llm/providers/GeminiProvider.ts
|
|
2397
|
-
var
|
|
2413
|
+
var import_generative_ai = require("@google/generative-ai");
|
|
2414
|
+
var DEFAULT_EMBEDDING_MODEL = "text-embedding-004";
|
|
2415
|
+
var DEFAULT_TEMPERATURE = 0.7;
|
|
2416
|
+
var DEFAULT_MAX_TOKENS = 1024;
|
|
2417
|
+
function sanitizeModel(model) {
|
|
2418
|
+
return model ? model.split(":")[0] : model;
|
|
2419
|
+
}
|
|
2420
|
+
function buildClient(apiKey) {
|
|
2421
|
+
return new import_generative_ai.GoogleGenerativeAI(apiKey);
|
|
2422
|
+
}
|
|
2423
|
+
function applyPrefix(text, taskType, queryPrefix, docPrefix) {
|
|
2424
|
+
if (taskType === "query" && queryPrefix && !text.startsWith(queryPrefix)) {
|
|
2425
|
+
return `${queryPrefix}${text}`;
|
|
2426
|
+
}
|
|
2427
|
+
if (taskType === "document" && docPrefix && !text.startsWith(docPrefix)) {
|
|
2428
|
+
return `${docPrefix}${text}`;
|
|
2429
|
+
}
|
|
2430
|
+
return text;
|
|
2431
|
+
}
|
|
2398
2432
|
var GeminiProvider = class {
|
|
2399
2433
|
constructor(llmConfig, embeddingConfig) {
|
|
2400
|
-
if (!llmConfig.apiKey)
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2434
|
+
if (!llmConfig.apiKey) {
|
|
2435
|
+
throw new Error("[GeminiProvider] llmConfig.apiKey is required");
|
|
2436
|
+
}
|
|
2437
|
+
this.llmConfig = __spreadProps(__spreadValues({}, llmConfig), { model: sanitizeModel(llmConfig.model) });
|
|
2438
|
+
this.client = buildClient(this.llmConfig.apiKey);
|
|
2405
2439
|
if (embeddingConfig) {
|
|
2406
2440
|
this.embeddingConfig = __spreadProps(__spreadValues({}, embeddingConfig), {
|
|
2407
|
-
model:
|
|
2441
|
+
model: sanitizeModel(embeddingConfig.model)
|
|
2408
2442
|
});
|
|
2409
2443
|
}
|
|
2410
2444
|
}
|
|
2445
|
+
// -------------------------------------------------------------------------
|
|
2446
|
+
// Static factory helpers
|
|
2447
|
+
// -------------------------------------------------------------------------
|
|
2411
2448
|
static getValidator() {
|
|
2412
2449
|
return {
|
|
2413
2450
|
validate(config) {
|
|
@@ -2421,7 +2458,11 @@ var GeminiProvider = class {
|
|
|
2421
2458
|
});
|
|
2422
2459
|
}
|
|
2423
2460
|
if (!config.model) {
|
|
2424
|
-
errors.push({
|
|
2461
|
+
errors.push({
|
|
2462
|
+
field: "llm.model",
|
|
2463
|
+
message: "Gemini model name is required",
|
|
2464
|
+
severity: "error"
|
|
2465
|
+
});
|
|
2425
2466
|
}
|
|
2426
2467
|
return errors;
|
|
2427
2468
|
}
|
|
@@ -2430,13 +2471,15 @@ var GeminiProvider = class {
|
|
|
2430
2471
|
static getHealthChecker() {
|
|
2431
2472
|
return {
|
|
2432
2473
|
async check(config) {
|
|
2474
|
+
var _a, _b;
|
|
2433
2475
|
const timestamp = Date.now();
|
|
2434
|
-
const apiKey = config.apiKey
|
|
2435
|
-
const modelName = config.model;
|
|
2476
|
+
const apiKey = (_b = (_a = config.apiKey) != null ? _a : process.env.GOOGLE_GENAI_API_KEY) != null ? _b : "";
|
|
2477
|
+
const modelName = sanitizeModel(config.model);
|
|
2436
2478
|
try {
|
|
2437
|
-
const {
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2479
|
+
const { GoogleGenerativeAI: GoogleGenerativeAI2 } = await import("@google/generative-ai");
|
|
2480
|
+
const ai = new GoogleGenerativeAI2(apiKey);
|
|
2481
|
+
const model = ai.getGenerativeModel({ model: DEFAULT_EMBEDDING_MODEL });
|
|
2482
|
+
await model.embedContent("health-check");
|
|
2440
2483
|
return {
|
|
2441
2484
|
healthy: true,
|
|
2442
2485
|
provider: "gemini",
|
|
@@ -2454,70 +2497,90 @@ var GeminiProvider = class {
|
|
|
2454
2497
|
}
|
|
2455
2498
|
};
|
|
2456
2499
|
}
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2500
|
+
// -------------------------------------------------------------------------
|
|
2501
|
+
// Private utilities
|
|
2502
|
+
// -------------------------------------------------------------------------
|
|
2503
|
+
/** Resolve the embedding client — uses a separate client when the embedding
|
|
2504
|
+
* API key differs from the LLM API key. */
|
|
2505
|
+
get embeddingClient() {
|
|
2506
|
+
var _a;
|
|
2507
|
+
if (((_a = this.embeddingConfig) == null ? void 0 : _a.apiKey) && this.embeddingConfig.apiKey !== this.llmConfig.apiKey) {
|
|
2508
|
+
return buildClient(this.embeddingConfig.apiKey);
|
|
2509
|
+
}
|
|
2510
|
+
return this.client;
|
|
2460
2511
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2512
|
+
/** Resolve the embedding model to use, in order of specificity. */
|
|
2513
|
+
resolveEmbeddingModel(optionsModel) {
|
|
2514
|
+
var _a, _b;
|
|
2515
|
+
return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Build the system instruction string, inserting the RAG context either via
|
|
2519
|
+
* the `{{context}}` placeholder or by appending it.
|
|
2520
|
+
*/
|
|
2521
|
+
buildSystemInstruction(context) {
|
|
2522
|
+
var _a;
|
|
2523
|
+
const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
2464
2524
|
|
|
2465
2525
|
Context:
|
|
2466
2526
|
${context}`;
|
|
2467
|
-
|
|
2527
|
+
return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
|
|
2468
2528
|
|
|
2469
2529
|
Context:
|
|
2470
2530
|
${context}`;
|
|
2471
|
-
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Convert ChatMessage[] to the Gemini contents format.
|
|
2534
|
+
*
|
|
2535
|
+
* Rules enforced by the Gemini API:
|
|
2536
|
+
* - Only `user` and `model` roles are allowed in `contents`.
|
|
2537
|
+
* - System messages must be passed via `systemInstruction`, not here.
|
|
2538
|
+
* - Messages must strictly alternate between `user` and `model`.
|
|
2539
|
+
*/
|
|
2540
|
+
buildGeminiContents(messages) {
|
|
2541
|
+
return messages.filter((m) => m.role !== "system").map((m) => ({
|
|
2472
2542
|
role: m.role === "assistant" ? "model" : "user",
|
|
2473
2543
|
parts: [{ text: m.content }]
|
|
2474
2544
|
}));
|
|
2475
|
-
|
|
2545
|
+
}
|
|
2546
|
+
// -------------------------------------------------------------------------
|
|
2547
|
+
// ILLMProvider — chat
|
|
2548
|
+
// -------------------------------------------------------------------------
|
|
2549
|
+
async chat(messages, context, options) {
|
|
2550
|
+
var _a, _b, _c, _d;
|
|
2551
|
+
const model = this.client.getGenerativeModel({
|
|
2476
2552
|
model: this.llmConfig.model,
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2553
|
+
systemInstruction: this.buildSystemInstruction(context)
|
|
2554
|
+
});
|
|
2555
|
+
const result = await model.generateContent({
|
|
2556
|
+
contents: this.buildGeminiContents(messages),
|
|
2557
|
+
generationConfig: {
|
|
2558
|
+
temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
|
|
2559
|
+
maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
|
|
2482
2560
|
stopSequences: options == null ? void 0 : options.stop
|
|
2483
2561
|
}
|
|
2484
2562
|
});
|
|
2485
|
-
|
|
2486
|
-
return text;
|
|
2563
|
+
return result.response.text();
|
|
2487
2564
|
}
|
|
2488
2565
|
chatStream(messages, context, options) {
|
|
2489
2566
|
return __asyncGenerator(this, null, function* () {
|
|
2490
|
-
var _a, _b, _c, _d
|
|
2491
|
-
const
|
|
2492
|
-
|
|
2493
|
-
Context:
|
|
2494
|
-
${context}`;
|
|
2495
|
-
const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2496
|
-
|
|
2497
|
-
Context:
|
|
2498
|
-
${context}`;
|
|
2499
|
-
const geminiMessages = messages.map((m) => ({
|
|
2500
|
-
role: m.role === "assistant" ? "model" : "user",
|
|
2501
|
-
parts: [{ text: m.content }]
|
|
2502
|
-
}));
|
|
2503
|
-
const result = yield new __await(this.client.models.generateContentStream({
|
|
2567
|
+
var _a, _b, _c, _d;
|
|
2568
|
+
const model = this.client.getGenerativeModel({
|
|
2504
2569
|
model: this.llmConfig.model,
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2570
|
+
systemInstruction: this.buildSystemInstruction(context)
|
|
2571
|
+
});
|
|
2572
|
+
const result = yield new __await(model.generateContentStream({
|
|
2573
|
+
contents: this.buildGeminiContents(messages),
|
|
2574
|
+
generationConfig: {
|
|
2575
|
+
temperature: (_b = (_a = options == null ? void 0 : options.temperature) != null ? _a : this.llmConfig.temperature) != null ? _b : DEFAULT_TEMPERATURE,
|
|
2576
|
+
maxOutputTokens: (_d = (_c = options == null ? void 0 : options.maxTokens) != null ? _c : this.llmConfig.maxTokens) != null ? _d : DEFAULT_MAX_TOKENS,
|
|
2510
2577
|
stopSequences: options == null ? void 0 : options.stop
|
|
2511
2578
|
}
|
|
2512
2579
|
}));
|
|
2513
|
-
const stream = (_f = result == null ? void 0 : result.stream) != null ? _f : result;
|
|
2514
|
-
if (!stream) {
|
|
2515
|
-
throw new Error("[GeminiProvider] generateContentStream returned undefined");
|
|
2516
|
-
}
|
|
2517
2580
|
try {
|
|
2518
|
-
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2581
|
+
for (var iter = __forAwait(result.stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2519
2582
|
const chunk = temp.value;
|
|
2520
|
-
const text =
|
|
2583
|
+
const text = chunk.text();
|
|
2521
2584
|
if (text) yield text;
|
|
2522
2585
|
}
|
|
2523
2586
|
} catch (temp) {
|
|
@@ -2532,69 +2595,69 @@ ${context}`;
|
|
|
2532
2595
|
}
|
|
2533
2596
|
});
|
|
2534
2597
|
}
|
|
2598
|
+
// -------------------------------------------------------------------------
|
|
2599
|
+
// ILLMProvider — embeddings
|
|
2600
|
+
// -------------------------------------------------------------------------
|
|
2535
2601
|
async embed(text, options) {
|
|
2536
|
-
var _a, _b
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2602
|
+
var _a, _b;
|
|
2603
|
+
const content = applyPrefix(
|
|
2604
|
+
text,
|
|
2605
|
+
options == null ? void 0 : options.taskType,
|
|
2606
|
+
(_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
|
|
2607
|
+
(_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
|
|
2539
2608
|
);
|
|
2540
|
-
const
|
|
2541
|
-
const
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
const docPrefix = (_g = this.embeddingConfig) == null ? void 0 : _g.documentPrefix;
|
|
2545
|
-
if ((options == null ? void 0 : options.taskType) === "query" && queryPrefix) {
|
|
2546
|
-
if (!content.startsWith(queryPrefix)) {
|
|
2547
|
-
content = `${queryPrefix}${text}`;
|
|
2548
|
-
}
|
|
2549
|
-
} else if ((options == null ? void 0 : options.taskType) === "document" && docPrefix) {
|
|
2550
|
-
if (!content.startsWith(docPrefix)) {
|
|
2551
|
-
content = `${docPrefix}${text}`;
|
|
2552
|
-
}
|
|
2553
|
-
}
|
|
2554
|
-
const response = await client.models.embedContent({
|
|
2555
|
-
model,
|
|
2556
|
-
contents: content,
|
|
2557
|
-
config: ((_h = this.embeddingConfig) == null ? void 0 : _h.dimensions) ? { outputDimensionality: this.embeddingConfig.dimensions } : void 0
|
|
2609
|
+
const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
|
|
2610
|
+
const model = this.embeddingClient.getGenerativeModel({ model: modelName });
|
|
2611
|
+
const response = await model.embedContent({
|
|
2612
|
+
content: { role: "user", parts: [{ text: content }] }
|
|
2558
2613
|
}).catch((err) => {
|
|
2559
|
-
console.error(`[GeminiProvider] Embedding failed for model "${
|
|
2614
|
+
console.error(`[GeminiProvider] Embedding failed for model "${modelName}":`, err.message);
|
|
2560
2615
|
throw err;
|
|
2561
2616
|
});
|
|
2562
|
-
return
|
|
2617
|
+
return response.embedding.values;
|
|
2563
2618
|
}
|
|
2564
2619
|
async batchEmbed(texts, options) {
|
|
2565
|
-
|
|
2566
|
-
const model = this.
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2620
|
+
const modelName = this.resolveEmbeddingModel(options == null ? void 0 : options.model);
|
|
2621
|
+
const model = this.embeddingClient.getGenerativeModel({ model: modelName });
|
|
2622
|
+
const requests = texts.map((text) => {
|
|
2623
|
+
var _a, _b;
|
|
2624
|
+
return {
|
|
2625
|
+
content: {
|
|
2626
|
+
role: "user",
|
|
2627
|
+
parts: [{
|
|
2628
|
+
text: applyPrefix(
|
|
2629
|
+
text,
|
|
2630
|
+
options == null ? void 0 : options.taskType,
|
|
2631
|
+
(_a = this.embeddingConfig) == null ? void 0 : _a.queryPrefix,
|
|
2632
|
+
(_b = this.embeddingConfig) == null ? void 0 : _b.documentPrefix
|
|
2633
|
+
)
|
|
2634
|
+
}]
|
|
2635
|
+
}
|
|
2636
|
+
};
|
|
2637
|
+
});
|
|
2638
|
+
const response = await model.batchEmbedContents({
|
|
2639
|
+
requests: requests.map((r) => ({
|
|
2640
|
+
model: `models/${modelName}`,
|
|
2641
|
+
content: r.content
|
|
2642
|
+
}))
|
|
2583
2643
|
}).catch((err) => {
|
|
2584
|
-
console.error(`[GeminiProvider] Batch embedding failed for model "${
|
|
2644
|
+
console.error(`[GeminiProvider] Batch embedding failed for model "${modelName}":`, err.message);
|
|
2585
2645
|
throw err;
|
|
2586
2646
|
});
|
|
2587
|
-
return
|
|
2588
|
-
var
|
|
2589
|
-
return (
|
|
2590
|
-
})
|
|
2647
|
+
return response.embeddings.map((e) => {
|
|
2648
|
+
var _a;
|
|
2649
|
+
return (_a = e.values) != null ? _a : [];
|
|
2650
|
+
});
|
|
2591
2651
|
}
|
|
2652
|
+
// -------------------------------------------------------------------------
|
|
2653
|
+
// ILLMProvider — health
|
|
2654
|
+
// -------------------------------------------------------------------------
|
|
2592
2655
|
async ping() {
|
|
2593
2656
|
try {
|
|
2594
|
-
|
|
2595
|
-
model:
|
|
2596
|
-
contents: "ping"
|
|
2657
|
+
const model = this.embeddingClient.getGenerativeModel({
|
|
2658
|
+
model: this.resolveEmbeddingModel()
|
|
2597
2659
|
});
|
|
2660
|
+
await model.embedContent("ping");
|
|
2598
2661
|
return true;
|
|
2599
2662
|
} catch (err) {
|
|
2600
2663
|
console.error("[GeminiProvider] Ping failed:", err);
|
|
@@ -2790,9 +2853,51 @@ ${context != null ? context : "None"}` },
|
|
|
2790
2853
|
};
|
|
2791
2854
|
|
|
2792
2855
|
// src/llm/LLMFactory.ts
|
|
2856
|
+
var customProviders = /* @__PURE__ */ new Map();
|
|
2793
2857
|
var LLMFactory = class _LLMFactory {
|
|
2858
|
+
/**
|
|
2859
|
+
* Register a custom LLM provider factory at runtime.
|
|
2860
|
+
*
|
|
2861
|
+
* Use this to add support for any LLM backend (Azure OpenAI, Cohere, Mistral,
|
|
2862
|
+
* Bedrock, etc.) without modifying this library's source code.
|
|
2863
|
+
*
|
|
2864
|
+
* @example
|
|
2865
|
+
* // In your Next.js app initialization:
|
|
2866
|
+
* import { LLMFactory } from '@retrivora-ai/rag-engine/server';
|
|
2867
|
+
* import { MyCustomProvider } from './providers/MyCustomProvider';
|
|
2868
|
+
*
|
|
2869
|
+
* LLMFactory.register('my-provider', (config) => new MyCustomProvider(config));
|
|
2870
|
+
*
|
|
2871
|
+
* // Then set in your .env.local:
|
|
2872
|
+
* // LLM_PROVIDER=my-provider
|
|
2873
|
+
*/
|
|
2874
|
+
static register(name, factory) {
|
|
2875
|
+
customProviders.set(name.toLowerCase(), factory);
|
|
2876
|
+
console.log(`[LLMFactory] Registered custom provider: "${name}"`);
|
|
2877
|
+
}
|
|
2878
|
+
/**
|
|
2879
|
+
* Unregister a previously registered custom provider.
|
|
2880
|
+
*/
|
|
2881
|
+
static unregister(name) {
|
|
2882
|
+
customProviders.delete(name.toLowerCase());
|
|
2883
|
+
}
|
|
2884
|
+
/**
|
|
2885
|
+
* List all registered provider names (built-in + custom).
|
|
2886
|
+
*/
|
|
2887
|
+
static listProviders() {
|
|
2888
|
+
return [
|
|
2889
|
+
"openai",
|
|
2890
|
+
"anthropic",
|
|
2891
|
+
"ollama",
|
|
2892
|
+
"gemini",
|
|
2893
|
+
"rest",
|
|
2894
|
+
"universal_rest",
|
|
2895
|
+
"custom",
|
|
2896
|
+
...Array.from(customProviders.keys())
|
|
2897
|
+
];
|
|
2898
|
+
}
|
|
2794
2899
|
static create(llmConfig, embeddingConfig) {
|
|
2795
|
-
var _a;
|
|
2900
|
+
var _a, _b;
|
|
2796
2901
|
switch (llmConfig.provider) {
|
|
2797
2902
|
case "openai":
|
|
2798
2903
|
return new OpenAIProvider(llmConfig, embeddingConfig);
|
|
@@ -2806,11 +2911,19 @@ var LLMFactory = class _LLMFactory {
|
|
|
2806
2911
|
case "universal_rest":
|
|
2807
2912
|
case "custom":
|
|
2808
2913
|
return new UniversalLLMAdapter(llmConfig);
|
|
2809
|
-
default:
|
|
2810
|
-
|
|
2914
|
+
default: {
|
|
2915
|
+
const providerName = String((_a = llmConfig.provider) != null ? _a : "").toLowerCase();
|
|
2916
|
+
const customFactory = customProviders.get(providerName);
|
|
2917
|
+
if (customFactory) {
|
|
2918
|
+
return customFactory(llmConfig);
|
|
2919
|
+
}
|
|
2920
|
+
if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
|
|
2811
2921
|
return new UniversalLLMAdapter(llmConfig);
|
|
2812
2922
|
}
|
|
2813
|
-
throw new Error(
|
|
2923
|
+
throw new Error(
|
|
2924
|
+
`[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2814
2927
|
}
|
|
2815
2928
|
}
|
|
2816
2929
|
static getValidator(provider) {
|
|
@@ -2900,8 +3013,8 @@ var ProviderRegistry = class {
|
|
|
2900
3013
|
}
|
|
2901
3014
|
case "pgvector":
|
|
2902
3015
|
case "postgresql": {
|
|
2903
|
-
const {
|
|
2904
|
-
return
|
|
3016
|
+
const { MultiTablePostgresProvider: MultiTablePostgresProvider2 } = await Promise.resolve().then(() => (init_MultiTablePostgresProvider(), MultiTablePostgresProvider_exports));
|
|
3017
|
+
return MultiTablePostgresProvider2;
|
|
2905
3018
|
}
|
|
2906
3019
|
case "mongodb": {
|
|
2907
3020
|
const { MongoDBProvider: MongoDBProvider2 } = await Promise.resolve().then(() => (init_MongoDBProvider(), MongoDBProvider_exports));
|
|
@@ -3869,69 +3982,40 @@ var QueryProcessor = class {
|
|
|
3869
3982
|
var UITransformer = class {
|
|
3870
3983
|
/**
|
|
3871
3984
|
* Main transformation method
|
|
3872
|
-
* Analyzes user query and retrieved data to determine
|
|
3873
|
-
*
|
|
3874
|
-
* @param userQuery - The original user question/query
|
|
3875
|
-
* @param retrievedData - Vector database retrieval results
|
|
3876
|
-
* @returns Structured JSON response for UI rendering
|
|
3985
|
+
* Analyzes user query and retrieved data to determine if a product carousel is needed.
|
|
3877
3986
|
*/
|
|
3878
|
-
static transform(userQuery, retrievedData) {
|
|
3987
|
+
static transform(userQuery, retrievedData, config, trainedSchema) {
|
|
3879
3988
|
if (!retrievedData || retrievedData.length === 0) {
|
|
3880
3989
|
return this.createTextResponse("No data available", "No relevant data found for your query.");
|
|
3881
3990
|
}
|
|
3882
|
-
const
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
case "line_chart":
|
|
3891
|
-
return this.transformToLineChart(retrievedData);
|
|
3892
|
-
case "table":
|
|
3893
|
-
return this.transformToTable(retrievedData);
|
|
3894
|
-
default:
|
|
3895
|
-
return this.transformToText(retrievedData);
|
|
3896
|
-
}
|
|
3897
|
-
}
|
|
3898
|
-
/**
|
|
3899
|
-
* Analyzes the data and query to suggest the best visualization type
|
|
3900
|
-
*/
|
|
3901
|
-
static analyzeData(query, data) {
|
|
3902
|
-
const queryLower = query.toLowerCase();
|
|
3903
|
-
const hasProducts = data.some(
|
|
3904
|
-
(item) => this.isProductData(item)
|
|
3905
|
-
);
|
|
3906
|
-
const hasTimeSeries = data.some(
|
|
3907
|
-
(item) => this.isTimeSeriesData(item)
|
|
3908
|
-
);
|
|
3909
|
-
const hasCategories = this.detectCategories(data).length > 0;
|
|
3910
|
-
const isDistributionQuery = queryLower.includes("distribution") || queryLower.includes("breakdown") || queryLower.includes("percentage") || queryLower.includes("proportion");
|
|
3911
|
-
const isComparisonQuery = queryLower.includes("compare") || queryLower.includes("vs") || queryLower.includes("difference");
|
|
3912
|
-
const isTrendQuery = queryLower.includes("trend") || queryLower.includes("over time") || queryLower.includes("growth") || queryLower.includes("decline");
|
|
3913
|
-
if (hasProducts && !hasTimeSeries && !isDistributionQuery) {
|
|
3914
|
-
return { suggestedType: "product_carousel", confidence: 0.95, hasProducts, hasTimeSeries, hasCategories };
|
|
3991
|
+
const isStockRequest = this.isStockQuery(userQuery);
|
|
3992
|
+
const filteredData = isStockRequest ? retrievedData.filter((item) => this.determineStockStatus(item)) : retrievedData;
|
|
3993
|
+
const categories = this.detectCategories(filteredData);
|
|
3994
|
+
const hasProducts = filteredData.some((item) => this.isProductData(item));
|
|
3995
|
+
const isTimeSeries = filteredData.some((item) => this.isTimeSeriesData(item));
|
|
3996
|
+
const isTrendQuery = this.isTrendQuery(userQuery);
|
|
3997
|
+
if (isTrendQuery && isTimeSeries) {
|
|
3998
|
+
return this.transformToLineChart(filteredData);
|
|
3915
3999
|
}
|
|
3916
|
-
if (
|
|
3917
|
-
return
|
|
4000
|
+
if (hasProducts && !this.shouldShowCategoryChart(userQuery, categories)) {
|
|
4001
|
+
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
3918
4002
|
}
|
|
3919
|
-
if (
|
|
3920
|
-
return
|
|
4003
|
+
if (categories.length > 1 && this.shouldShowCategoryChart(userQuery, categories)) {
|
|
4004
|
+
return this.transformToPieChart(filteredData);
|
|
3921
4005
|
}
|
|
3922
|
-
if (
|
|
3923
|
-
return
|
|
4006
|
+
if (hasProducts) {
|
|
4007
|
+
return this.transformToProductCarousel(filteredData, config, trainedSchema);
|
|
3924
4008
|
}
|
|
3925
|
-
if (
|
|
3926
|
-
return
|
|
4009
|
+
if (this.hasMultipleFields(filteredData)) {
|
|
4010
|
+
return this.transformToTable(filteredData);
|
|
3927
4011
|
}
|
|
3928
|
-
return
|
|
4012
|
+
return this.transformToText(filteredData);
|
|
3929
4013
|
}
|
|
3930
4014
|
/**
|
|
3931
4015
|
* Transform data to product carousel format
|
|
3932
4016
|
*/
|
|
3933
|
-
static transformToProductCarousel(data) {
|
|
3934
|
-
const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item)).filter((p) => p !== null);
|
|
4017
|
+
static transformToProductCarousel(data, config, trainedSchema) {
|
|
4018
|
+
const products = data.filter((item) => this.isProductData(item)).map((item) => this.extractProductInfo(item, config, trainedSchema)).filter((p) => p !== null);
|
|
3935
4019
|
return {
|
|
3936
4020
|
type: "product_carousel",
|
|
3937
4021
|
title: "Recommended Products",
|
|
@@ -3945,12 +4029,16 @@ var UITransformer = class {
|
|
|
3945
4029
|
static transformToPieChart(data) {
|
|
3946
4030
|
const categories = this.detectCategories(data);
|
|
3947
4031
|
const categoryData = this.aggregateByCategory(data, categories);
|
|
3948
|
-
const pieData = Object.entries(categoryData).map(([label, count]) =>
|
|
3949
|
-
label,
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
4032
|
+
const pieData = Object.entries(categoryData).map(([label, count]) => {
|
|
4033
|
+
const { inStockCount, outOfStockCount } = this.calculateStockCounts(label, data);
|
|
4034
|
+
return {
|
|
4035
|
+
label,
|
|
4036
|
+
value: count,
|
|
4037
|
+
inStockCount,
|
|
4038
|
+
outOfStockCount
|
|
4039
|
+
};
|
|
4040
|
+
});
|
|
4041
|
+
return {
|
|
3954
4042
|
type: "pie_chart",
|
|
3955
4043
|
title: "Distribution by Category",
|
|
3956
4044
|
description: `Showing breakdown across ${categories.length} categories`,
|
|
@@ -3963,11 +4051,15 @@ var UITransformer = class {
|
|
|
3963
4051
|
static transformToBarChart(data) {
|
|
3964
4052
|
const categories = this.detectCategories(data);
|
|
3965
4053
|
const categoryData = this.aggregateByCategory(data, categories);
|
|
3966
|
-
const barData = Object.entries(categoryData).map(([category, value]) =>
|
|
3967
|
-
category,
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
4054
|
+
const barData = Object.entries(categoryData).map(([category, value]) => {
|
|
4055
|
+
const { inStockCount, outOfStockCount } = this.calculateStockCounts(category, data);
|
|
4056
|
+
return {
|
|
4057
|
+
category,
|
|
4058
|
+
value,
|
|
4059
|
+
inStockCount,
|
|
4060
|
+
outOfStockCount
|
|
4061
|
+
};
|
|
4062
|
+
});
|
|
3971
4063
|
return {
|
|
3972
4064
|
type: "bar_chart",
|
|
3973
4065
|
title: "Comparison by Category",
|
|
@@ -4031,49 +4123,261 @@ var UITransformer = class {
|
|
|
4031
4123
|
data: { content }
|
|
4032
4124
|
};
|
|
4033
4125
|
}
|
|
4126
|
+
static parseTransformationResponse(raw) {
|
|
4127
|
+
const payloadText = this.extractJsonCandidate(raw);
|
|
4128
|
+
if (!payloadText) return null;
|
|
4129
|
+
try {
|
|
4130
|
+
const parsed = JSON.parse(payloadText);
|
|
4131
|
+
return this.normalizeTransformation(parsed);
|
|
4132
|
+
} catch (e) {
|
|
4133
|
+
return null;
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
static extractJsonCandidate(raw) {
|
|
4137
|
+
if (!raw) return null;
|
|
4138
|
+
const cleaned = raw.replace(/```(?:json|chart|ui)?\s*/gi, "").replace(/```/g, "").trim();
|
|
4139
|
+
const start = cleaned.indexOf("{");
|
|
4140
|
+
if (start === -1) return null;
|
|
4141
|
+
let depth = 0;
|
|
4142
|
+
let inString = false;
|
|
4143
|
+
let escape = false;
|
|
4144
|
+
for (let i = start; i < cleaned.length; i += 1) {
|
|
4145
|
+
const char = cleaned[i];
|
|
4146
|
+
if (escape) {
|
|
4147
|
+
escape = false;
|
|
4148
|
+
continue;
|
|
4149
|
+
}
|
|
4150
|
+
if (char === "\\") {
|
|
4151
|
+
escape = true;
|
|
4152
|
+
continue;
|
|
4153
|
+
}
|
|
4154
|
+
if (char === '"') {
|
|
4155
|
+
inString = !inString;
|
|
4156
|
+
continue;
|
|
4157
|
+
}
|
|
4158
|
+
if (inString) continue;
|
|
4159
|
+
if (char === "{") depth += 1;
|
|
4160
|
+
if (char === "}") {
|
|
4161
|
+
depth -= 1;
|
|
4162
|
+
if (depth === 0) {
|
|
4163
|
+
return cleaned.slice(start, i + 1);
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
return null;
|
|
4168
|
+
}
|
|
4169
|
+
static normalizeTransformation(payload) {
|
|
4170
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
4171
|
+
if (!payload || typeof payload !== "object") return null;
|
|
4172
|
+
const payloadObj = payload;
|
|
4173
|
+
const type = this.normalizeVisualizationType(String((_c = (_b = (_a = payloadObj.type) != null ? _a : payloadObj.view) != null ? _b : payloadObj.chartType) != null ? _c : ""));
|
|
4174
|
+
if (!type) return null;
|
|
4175
|
+
const title = String((_e = (_d = payloadObj.title) != null ? _d : payloadObj.heading) != null ? _e : "Visualization");
|
|
4176
|
+
const description = payloadObj.description ? String(payloadObj.description) : void 0;
|
|
4177
|
+
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;
|
|
4178
|
+
const data = type === "text" && typeof rawData === "string" ? { content: rawData } : rawData;
|
|
4179
|
+
const transformation = {
|
|
4180
|
+
type,
|
|
4181
|
+
title,
|
|
4182
|
+
description,
|
|
4183
|
+
data
|
|
4184
|
+
};
|
|
4185
|
+
return this.validateTransformation(transformation) ? transformation : null;
|
|
4186
|
+
}
|
|
4187
|
+
static normalizeVisualizationType(type) {
|
|
4188
|
+
var _a;
|
|
4189
|
+
const mapping = {
|
|
4190
|
+
pie: "pie_chart",
|
|
4191
|
+
pie_chart: "pie_chart",
|
|
4192
|
+
bar: "bar_chart",
|
|
4193
|
+
bar_chart: "bar_chart",
|
|
4194
|
+
line: "line_chart",
|
|
4195
|
+
line_chart: "line_chart",
|
|
4196
|
+
radar: "radar_chart",
|
|
4197
|
+
radar_chart: "radar_chart",
|
|
4198
|
+
table: "table",
|
|
4199
|
+
text: "text",
|
|
4200
|
+
product_carousel: "product_carousel",
|
|
4201
|
+
carousel: "carousel"
|
|
4202
|
+
};
|
|
4203
|
+
return (_a = mapping[type.toLowerCase()]) != null ? _a : null;
|
|
4204
|
+
}
|
|
4205
|
+
static validateTransformation(transformation) {
|
|
4206
|
+
const { type, data } = transformation;
|
|
4207
|
+
switch (type) {
|
|
4208
|
+
case "pie_chart":
|
|
4209
|
+
case "bar_chart":
|
|
4210
|
+
return Array.isArray(data) && data.every(
|
|
4211
|
+
(item) => item !== null && typeof item === "object" && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
|
|
4212
|
+
);
|
|
4213
|
+
case "line_chart":
|
|
4214
|
+
return Array.isArray(data) && data.every(
|
|
4215
|
+
(item) => item !== null && typeof item === "object" && "timestamp" in item && (typeof item.value === "number" || !Number.isNaN(Number(item.value)))
|
|
4216
|
+
);
|
|
4217
|
+
case "radar_chart":
|
|
4218
|
+
return Array.isArray(data) && data.every(
|
|
4219
|
+
(item) => item !== null && typeof item === "object" && "attribute" in item
|
|
4220
|
+
);
|
|
4221
|
+
case "table":
|
|
4222
|
+
return typeof data === "object" && data !== null && Array.isArray(data.columns) && Array.isArray(data.rows);
|
|
4223
|
+
case "text":
|
|
4224
|
+
return typeof data === "object" && data !== null && typeof data.content === "string";
|
|
4225
|
+
case "product_carousel":
|
|
4226
|
+
case "carousel":
|
|
4227
|
+
return Array.isArray(data) && data.every(
|
|
4228
|
+
(item) => item !== null && typeof item === "object" && ("id" in item || "name" in item)
|
|
4229
|
+
);
|
|
4230
|
+
default:
|
|
4231
|
+
return false;
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4034
4234
|
/**
|
|
4035
4235
|
* Helper: Check if data item is product-related
|
|
4036
4236
|
*/
|
|
4037
4237
|
static isProductData(item) {
|
|
4038
4238
|
const content = (item.content || "").toLowerCase();
|
|
4039
|
-
const productKeywords = [
|
|
4040
|
-
|
|
4041
|
-
|
|
4239
|
+
const productKeywords = [
|
|
4240
|
+
"product",
|
|
4241
|
+
"price",
|
|
4242
|
+
"stock",
|
|
4243
|
+
"item",
|
|
4244
|
+
"sku",
|
|
4245
|
+
"brand",
|
|
4246
|
+
"model",
|
|
4247
|
+
"msrp",
|
|
4248
|
+
"inventory",
|
|
4249
|
+
"buy",
|
|
4250
|
+
"shop",
|
|
4251
|
+
"beauty",
|
|
4252
|
+
"care",
|
|
4253
|
+
"cosmetic",
|
|
4254
|
+
"facial",
|
|
4255
|
+
"cream",
|
|
4256
|
+
"serum",
|
|
4257
|
+
"mask",
|
|
4258
|
+
"makeup",
|
|
4259
|
+
"fragrance"
|
|
4260
|
+
];
|
|
4261
|
+
const hasKeywords = productKeywords.some((kw) => content.includes(kw));
|
|
4262
|
+
const hasMetadataKey = Object.keys(item.metadata || {}).some(
|
|
4263
|
+
(k) => ["name", "price", "product", "sku", "brand", "model", "cost", "item"].includes(k.toLowerCase())
|
|
4042
4264
|
);
|
|
4265
|
+
const hasPricePattern = /\$\s*\d+/.test(content);
|
|
4266
|
+
return hasKeywords || hasMetadataKey || hasPricePattern;
|
|
4043
4267
|
}
|
|
4044
4268
|
/**
|
|
4045
4269
|
* Helper: Check if data contains time series
|
|
4046
4270
|
*/
|
|
4047
4271
|
static isTimeSeriesData(item) {
|
|
4048
4272
|
const content = (item.content || "").toLowerCase();
|
|
4049
|
-
const timeKeywords = ["
|
|
4050
|
-
|
|
4273
|
+
const timeKeywords = ["trend", "historical", "growth", "decline", "change", "increase", "decrease"];
|
|
4274
|
+
const hasTimeKeyword = timeKeywords.some((kw) => content.includes(kw));
|
|
4275
|
+
const metadata = item.metadata || {};
|
|
4276
|
+
const maybeDateKeys = Object.keys(metadata).filter(
|
|
4051
4277
|
(k) => ["date", "timestamp", "time", "period"].includes(k.toLowerCase())
|
|
4052
4278
|
);
|
|
4279
|
+
const hasValidDateValue = maybeDateKeys.some((key) => {
|
|
4280
|
+
const value = metadata[key];
|
|
4281
|
+
if (typeof value === "string") {
|
|
4282
|
+
return !Number.isNaN(Date.parse(value)) && value.trim().length > 0;
|
|
4283
|
+
}
|
|
4284
|
+
return typeof value === "number";
|
|
4285
|
+
});
|
|
4286
|
+
return hasTimeKeyword || hasValidDateValue;
|
|
4287
|
+
}
|
|
4288
|
+
static shouldShowCategoryChart(query, categories) {
|
|
4289
|
+
if (categories.length < 2) {
|
|
4290
|
+
return false;
|
|
4291
|
+
}
|
|
4292
|
+
const normalized = query.toLowerCase();
|
|
4293
|
+
const chartKeywords = [
|
|
4294
|
+
"distribution",
|
|
4295
|
+
"breakdown",
|
|
4296
|
+
"by category",
|
|
4297
|
+
"by type",
|
|
4298
|
+
"compare",
|
|
4299
|
+
"share",
|
|
4300
|
+
"percentage",
|
|
4301
|
+
"segmentation",
|
|
4302
|
+
"split",
|
|
4303
|
+
"category breakdown",
|
|
4304
|
+
"category distribution"
|
|
4305
|
+
];
|
|
4306
|
+
return chartKeywords.some((keyword) => normalized.includes(keyword));
|
|
4307
|
+
}
|
|
4308
|
+
static isTrendQuery(query) {
|
|
4309
|
+
const normalized = query.toLowerCase();
|
|
4310
|
+
const trendKeywords = [
|
|
4311
|
+
"trend",
|
|
4312
|
+
"over time",
|
|
4313
|
+
"historical",
|
|
4314
|
+
"growth",
|
|
4315
|
+
"decline",
|
|
4316
|
+
"increase",
|
|
4317
|
+
"decrease",
|
|
4318
|
+
"year",
|
|
4319
|
+
"month",
|
|
4320
|
+
"week",
|
|
4321
|
+
"day",
|
|
4322
|
+
"comparison",
|
|
4323
|
+
"compare",
|
|
4324
|
+
"changes",
|
|
4325
|
+
"timeline"
|
|
4326
|
+
];
|
|
4327
|
+
return trendKeywords.some((keyword) => normalized.includes(keyword));
|
|
4328
|
+
}
|
|
4329
|
+
static isStockQuery(query) {
|
|
4330
|
+
const normalized = query.toLowerCase();
|
|
4331
|
+
return normalized.includes("in stock") || normalized.includes("available") || normalized.includes("availability") || normalized.includes("inventory") || normalized.includes("stock status");
|
|
4053
4332
|
}
|
|
4054
4333
|
/**
|
|
4055
|
-
* Helper: Extract
|
|
4334
|
+
* Helper: Extract property from metadata using mapping, AI training, case-insensitivity, and synonyms.
|
|
4056
4335
|
*/
|
|
4057
|
-
static
|
|
4336
|
+
static getDynamicVal(meta, uiKey, config, trainedSchema) {
|
|
4337
|
+
var _a;
|
|
4338
|
+
if (!meta) return void 0;
|
|
4339
|
+
const mapping = (_a = config == null ? void 0 : config.rag) == null ? void 0 : _a.uiMapping;
|
|
4340
|
+
if (mapping && mapping[uiKey]) {
|
|
4341
|
+
const mappedKey = mapping[uiKey];
|
|
4342
|
+
if (meta[mappedKey] !== void 0) return meta[mappedKey];
|
|
4343
|
+
}
|
|
4344
|
+
if (trainedSchema && typeof trainedSchema === "object" && trainedSchema !== null) {
|
|
4345
|
+
const trainedKey = trainedSchema[uiKey];
|
|
4346
|
+
if (trainedKey && meta[trainedKey] !== void 0) return meta[trainedKey];
|
|
4347
|
+
}
|
|
4348
|
+
const synonyms = this.SYNONYMS[uiKey] || [];
|
|
4349
|
+
const searchKeys = [uiKey, ...synonyms].map((k) => k.toLowerCase());
|
|
4350
|
+
const foundDirectKey = Object.keys(meta).find((k) => searchKeys.includes(k.toLowerCase()));
|
|
4351
|
+
if (foundDirectKey) return meta[foundDirectKey];
|
|
4352
|
+
const foundPartialKey = Object.keys(meta).find((k) => {
|
|
4353
|
+
const keyLow = k.toLowerCase();
|
|
4354
|
+
return searchKeys.some((sk) => keyLow.includes(sk) || sk.includes(keyLow));
|
|
4355
|
+
});
|
|
4356
|
+
return foundPartialKey ? meta[foundPartialKey] : void 0;
|
|
4357
|
+
}
|
|
4358
|
+
static extractProductInfo(item, config, trainedSchema) {
|
|
4058
4359
|
const meta = item.metadata || {};
|
|
4059
|
-
|
|
4360
|
+
const name = this.getDynamicVal(meta, "name", config, trainedSchema);
|
|
4361
|
+
const price = this.getDynamicVal(meta, "price", config, trainedSchema);
|
|
4362
|
+
const brand = this.getDynamicVal(meta, "brand", config, trainedSchema);
|
|
4363
|
+
if (name || this.isProductData(item)) {
|
|
4364
|
+
let finalName = name ? String(name) : void 0;
|
|
4365
|
+
if (!finalName) {
|
|
4366
|
+
const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
|
|
4367
|
+
finalName = nameMatch ? nameMatch[1].trim() : item.content.split("\n")[0].substring(0, 60);
|
|
4368
|
+
}
|
|
4369
|
+
let finalPrice = typeof price === "number" || typeof price === "string" ? price : void 0;
|
|
4370
|
+
if (!finalPrice) {
|
|
4371
|
+
const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d,.]+)/i) || item.content.match(/\$\s*([\d,.]+)/);
|
|
4372
|
+
if (priceMatch) finalPrice = priceMatch[1].replace(/,/g, "");
|
|
4373
|
+
}
|
|
4374
|
+
const imageValue = this.getDynamicVal(meta, "image", config, trainedSchema);
|
|
4060
4375
|
return {
|
|
4061
4376
|
id: item.id,
|
|
4062
|
-
name:
|
|
4063
|
-
price:
|
|
4064
|
-
image:
|
|
4065
|
-
brand:
|
|
4066
|
-
description: item.content,
|
|
4067
|
-
inStock: this.determineStockStatus(item)
|
|
4068
|
-
};
|
|
4069
|
-
}
|
|
4070
|
-
const nameMatch = item.content.match(/(?:Product|Item|Name):\s*([^\n]+)/i);
|
|
4071
|
-
const priceMatch = item.content.match(/(?:Price|Cost):\s*\$?([\d.]+)/i);
|
|
4072
|
-
if (nameMatch) {
|
|
4073
|
-
return {
|
|
4074
|
-
id: item.id,
|
|
4075
|
-
name: nameMatch[1],
|
|
4076
|
-
price: priceMatch ? parseFloat(priceMatch[1]) : void 0,
|
|
4377
|
+
name: finalName,
|
|
4378
|
+
price: finalPrice,
|
|
4379
|
+
image: typeof imageValue === "string" ? imageValue : void 0,
|
|
4380
|
+
brand: brand ? String(brand) : void 0,
|
|
4077
4381
|
description: item.content,
|
|
4078
4382
|
inStock: this.determineStockStatus(item)
|
|
4079
4383
|
};
|
|
@@ -4097,6 +4401,10 @@ var UITransformer = class {
|
|
|
4097
4401
|
const tags = Array.isArray(meta.tag) ? meta.tag : [meta.tag];
|
|
4098
4402
|
tags.forEach((t) => categories.add(String(t)));
|
|
4099
4403
|
}
|
|
4404
|
+
const contentCategories = Array.from(new Set(
|
|
4405
|
+
Array.from(item.content.matchAll(/^\s*([^:\n]+):\s*(?:•|\*|-)*/gm)).map((match) => match[1].trim()).filter(Boolean)
|
|
4406
|
+
));
|
|
4407
|
+
contentCategories.forEach((category) => categories.add(category));
|
|
4100
4408
|
const categoryMatch = item.content.match(/(?:Category|Type|Class):\s*([^\n]+)/i);
|
|
4101
4409
|
if (categoryMatch) {
|
|
4102
4410
|
categories.add(categoryMatch[1].trim());
|
|
@@ -4164,14 +4472,23 @@ var UITransformer = class {
|
|
|
4164
4472
|
});
|
|
4165
4473
|
}
|
|
4166
4474
|
/**
|
|
4167
|
-
* Helper:
|
|
4475
|
+
* Helper: Calculate stock counts by category
|
|
4168
4476
|
*/
|
|
4169
|
-
static
|
|
4170
|
-
|
|
4477
|
+
static calculateStockCounts(category, data) {
|
|
4478
|
+
let inStock = 0;
|
|
4479
|
+
let outOfStock = 0;
|
|
4480
|
+
data.forEach((d) => {
|
|
4171
4481
|
const meta = d.metadata || {};
|
|
4172
|
-
|
|
4482
|
+
const itemCategory = meta.category || meta.type || "Other";
|
|
4483
|
+
if (itemCategory === category) {
|
|
4484
|
+
if (this.determineStockStatus(d)) {
|
|
4485
|
+
inStock++;
|
|
4486
|
+
} else {
|
|
4487
|
+
outOfStock++;
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4173
4490
|
});
|
|
4174
|
-
return
|
|
4491
|
+
return { inStockCount: inStock, outOfStockCount: outOfStock };
|
|
4175
4492
|
}
|
|
4176
4493
|
/**
|
|
4177
4494
|
* Helper: Determine if item is in stock
|
|
@@ -4208,6 +4525,225 @@ var UITransformer = class {
|
|
|
4208
4525
|
});
|
|
4209
4526
|
return fieldCount.size > 2;
|
|
4210
4527
|
}
|
|
4528
|
+
// ─── LLM-Driven Visualization Decision ────────────────────────────────────
|
|
4529
|
+
/**
|
|
4530
|
+
* analyzeAndDecide — sends user question + RAG data to the LLM with a
|
|
4531
|
+
* structured system prompt and parses the JSON response into a
|
|
4532
|
+
* UITransformationResponse.
|
|
4533
|
+
*
|
|
4534
|
+
* This is the recommended entry point for production use. The heuristic
|
|
4535
|
+
* `transform()` method is used as a fallback if the LLM call fails.
|
|
4536
|
+
*
|
|
4537
|
+
* System prompt instructs the LLM to:
|
|
4538
|
+
* - Analyze the question and retrieved data
|
|
4539
|
+
* - Choose the best visualization: bar_chart | line_chart | pie_chart | table | text
|
|
4540
|
+
* - Return a strict JSON object — no prose, no markdown fences
|
|
4541
|
+
*
|
|
4542
|
+
* @param query - the original user question
|
|
4543
|
+
* @param sources - vector DB matches returned by RAG retrieval
|
|
4544
|
+
* @param llm - any ILLMProvider instance (OpenAI, Anthropic, Ollama, Gemini, REST…)
|
|
4545
|
+
* @returns - a validated UITransformationResponse (type + title + description + data)
|
|
4546
|
+
*/
|
|
4547
|
+
static async analyzeAndDecide(query, sources, llm) {
|
|
4548
|
+
try {
|
|
4549
|
+
const context = this.buildContextSummary(sources);
|
|
4550
|
+
const systemPrompt = this.buildVisualizationSystemPrompt();
|
|
4551
|
+
const userPrompt = [
|
|
4552
|
+
`USER QUESTION: ${query}`,
|
|
4553
|
+
"",
|
|
4554
|
+
"RETRIEVED DATA (JSON):",
|
|
4555
|
+
context
|
|
4556
|
+
].join("\n");
|
|
4557
|
+
const rawResponse = await llm.chat(
|
|
4558
|
+
[{ role: "user", content: userPrompt }],
|
|
4559
|
+
"",
|
|
4560
|
+
{ systemPrompt, temperature: 0 }
|
|
4561
|
+
);
|
|
4562
|
+
const parsed = this.parseTransformationResponse(rawResponse);
|
|
4563
|
+
if (parsed) {
|
|
4564
|
+
console.debug("[UITransformer] LLM chose visualization type:", parsed.type);
|
|
4565
|
+
return parsed;
|
|
4566
|
+
}
|
|
4567
|
+
console.warn("[UITransformer] LLM returned unparseable response; falling back to heuristic.");
|
|
4568
|
+
} catch (err) {
|
|
4569
|
+
console.warn("[UITransformer] analyzeAndDecide LLM call failed; falling back to heuristic.", err);
|
|
4570
|
+
}
|
|
4571
|
+
return this.transform(query, sources);
|
|
4572
|
+
}
|
|
4573
|
+
/**
|
|
4574
|
+
* Build the system prompt that instructs the LLM to return a visualization JSON.
|
|
4575
|
+
*/
|
|
4576
|
+
static buildVisualizationSystemPrompt() {
|
|
4577
|
+
return `You are a data visualization expert embedded in a RAG chat system.
|
|
4578
|
+
You will receive a user question and structured data retrieved from a vector database.
|
|
4579
|
+
Your ONLY job is to analyze this information and return a single JSON object that tells
|
|
4580
|
+
the frontend how to visualize it.
|
|
4581
|
+
|
|
4582
|
+
Return ONLY a valid JSON object \u2014 no markdown code fences, no explanation, no prose.
|
|
4583
|
+
|
|
4584
|
+
The JSON must have this exact shape:
|
|
4585
|
+
{
|
|
4586
|
+
"type": "bar_chart" | "line_chart" | "pie_chart" | "radar_chart" | "table" | "text",
|
|
4587
|
+
"title": "A concise, descriptive title for the visualization",
|
|
4588
|
+
"description": "One sentence describing what the visualization shows",
|
|
4589
|
+
"data": <structured data \u2014 see rules below>
|
|
4590
|
+
}
|
|
4591
|
+
|
|
4592
|
+
DATA SHAPE per type:
|
|
4593
|
+
- bar_chart: array of { "category": string, "value": number }
|
|
4594
|
+
- line_chart: array of { "timestamp": string, "value": number, "label": string }
|
|
4595
|
+
- pie_chart: array of { "label": string, "value": number }
|
|
4596
|
+
- radar_chart: array of { "attribute": string, "[series1]": number, "[series2]": number, ... }
|
|
4597
|
+
Example for radar_chart:
|
|
4598
|
+
[
|
|
4599
|
+
{ "attribute": "Longevity", "Dolce Shine": 4, "CK One": 3 },
|
|
4600
|
+
{ "attribute": "Sillage", "Dolce Shine": 3, "CK One": 4 },
|
|
4601
|
+
{ "attribute": "Freshness", "Dolce Shine": 5, "CK One": 4 }
|
|
4602
|
+
]
|
|
4603
|
+
- table: { "columns": string[], "rows": (string|number)[][] }
|
|
4604
|
+
- text: { "content": "<prose answer>" }
|
|
4605
|
+
|
|
4606
|
+
DECISION RULES (follow strictly):
|
|
4607
|
+
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.
|
|
4608
|
+
2. line_chart \u2192 trends or changes over time (dates, months, years, sequential events)
|
|
4609
|
+
3. pie_chart \u2192 proportional breakdown or percentage distribution
|
|
4610
|
+
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.
|
|
4611
|
+
5. table \u2192 multi-field structured records where each item has \u2265 3 attributes
|
|
4612
|
+
6. text \u2192 conversational or free-form answers where no chart adds value
|
|
4613
|
+
|
|
4614
|
+
IMPORTANT:
|
|
4615
|
+
- Aggregate numeric values from the raw data \u2014 do not pass raw object arrays as data.
|
|
4616
|
+
- Ensure all "value" fields are numbers, not strings.
|
|
4617
|
+
- For bar/line/pie, keep at most 12 data points for readability.
|
|
4618
|
+
- Never include nested objects or arrays inside bar_chart / line_chart / pie_chart data items.`;
|
|
4619
|
+
}
|
|
4620
|
+
/**
|
|
4621
|
+
* Serialize retrieved vector matches into a compact JSON context string.
|
|
4622
|
+
* Limits the total character count to avoid exceeding LLM context windows.
|
|
4623
|
+
*/
|
|
4624
|
+
static buildContextSummary(sources, maxChars = 6e3) {
|
|
4625
|
+
const items = sources.map((s, i) => {
|
|
4626
|
+
var _a, _b, _c, _d;
|
|
4627
|
+
return {
|
|
4628
|
+
index: i + 1,
|
|
4629
|
+
content: (_b = (_a = s.content) == null ? void 0 : _a.substring(0, 400)) != null ? _b : "",
|
|
4630
|
+
metadata: (_c = s.metadata) != null ? _c : {},
|
|
4631
|
+
score: (_d = s.score) != null ? _d : 0
|
|
4632
|
+
};
|
|
4633
|
+
});
|
|
4634
|
+
const full = JSON.stringify(items, null, 2);
|
|
4635
|
+
if (full.length <= maxChars) return full;
|
|
4636
|
+
const partial = [];
|
|
4637
|
+
let chars = 2;
|
|
4638
|
+
for (const item of items) {
|
|
4639
|
+
const chunk = JSON.stringify(item);
|
|
4640
|
+
if (chars + chunk.length + 2 > maxChars) break;
|
|
4641
|
+
partial.push(item);
|
|
4642
|
+
chars += chunk.length + 2;
|
|
4643
|
+
}
|
|
4644
|
+
return JSON.stringify(partial, null, 2) + "\n// ... truncated";
|
|
4645
|
+
}
|
|
4646
|
+
};
|
|
4647
|
+
/**
|
|
4648
|
+
* Central dictionary of common synonyms for UI properties.
|
|
4649
|
+
* This allows the system to be schema-agnostic by guessing field names.
|
|
4650
|
+
*/
|
|
4651
|
+
UITransformer.SYNONYMS = {
|
|
4652
|
+
name: ["product", "item", "title", "label", "heading", "subject"],
|
|
4653
|
+
price: ["cost", "amount", "msrp", "price", "rate", "value", "price_usd"],
|
|
4654
|
+
brand: ["manufacturer", "vendor", "make", "company", "brand_name", "supplier"],
|
|
4655
|
+
image: ["imageUrl", "thumbnail", "img", "url", "photo", "picture", "media", "image_url", "main_image", "product_image", "thumb"],
|
|
4656
|
+
stock: ["inventory", "quantity", "count", "availability", "stock_level", "inStock", "is_available"],
|
|
4657
|
+
description: ["summary", "content", "body", "text", "info", "details"]
|
|
4658
|
+
};
|
|
4659
|
+
|
|
4660
|
+
// src/utils/SchemaMapper.ts
|
|
4661
|
+
var SchemaMapper = class {
|
|
4662
|
+
/**
|
|
4663
|
+
* Trains the plugin on a set of keys.
|
|
4664
|
+
* This is done once per schema and cached.
|
|
4665
|
+
*/
|
|
4666
|
+
static async train(llm, projectId, keys) {
|
|
4667
|
+
const cacheKey = `${projectId}:${keys.sort().join(",")}`;
|
|
4668
|
+
if (this.cache.has(cacheKey)) {
|
|
4669
|
+
return this.cache.get(cacheKey);
|
|
4670
|
+
}
|
|
4671
|
+
console.log(`[SchemaMapper] \u{1F9E0} Training AI on new schema keys: ${keys.join(", ")}`);
|
|
4672
|
+
const propertyList = Object.entries(this.TARGET_PROPERTIES).map(([prop, desc]) => `- ${prop} (${desc})`).join("\n");
|
|
4673
|
+
const prompt = `
|
|
4674
|
+
Given these metadata keys from a database: [${keys.join(", ")}]
|
|
4675
|
+
|
|
4676
|
+
Identify which keys best correspond to these standard UI properties:
|
|
4677
|
+
${propertyList}
|
|
4678
|
+
|
|
4679
|
+
Return ONLY a JSON object where the keys are the UI properties and the values are the matching database keys.
|
|
4680
|
+
If no good match is found for a property, omit it.
|
|
4681
|
+
|
|
4682
|
+
Example:
|
|
4683
|
+
{
|
|
4684
|
+
"name": "Product_Title",
|
|
4685
|
+
"price": "MSRP_USD",
|
|
4686
|
+
"brand": "VendorName"
|
|
4687
|
+
}
|
|
4688
|
+
`;
|
|
4689
|
+
try {
|
|
4690
|
+
const response = await llm.chat(
|
|
4691
|
+
[
|
|
4692
|
+
{ role: "system", content: "You are a database schema expert. You ONLY respond with valid JSON." },
|
|
4693
|
+
{ role: "user", content: prompt }
|
|
4694
|
+
],
|
|
4695
|
+
""
|
|
4696
|
+
);
|
|
4697
|
+
const startIdx = response.indexOf("{");
|
|
4698
|
+
if (startIdx !== -1) {
|
|
4699
|
+
let braceCount = 0;
|
|
4700
|
+
let endIdx = -1;
|
|
4701
|
+
for (let i = startIdx; i < response.length; i++) {
|
|
4702
|
+
if (response[i] === "{") braceCount++;
|
|
4703
|
+
else if (response[i] === "}") {
|
|
4704
|
+
braceCount--;
|
|
4705
|
+
if (braceCount === 0) {
|
|
4706
|
+
endIdx = i;
|
|
4707
|
+
break;
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
if (endIdx !== -1) {
|
|
4712
|
+
const jsonContent = response.substring(startIdx, endIdx + 1);
|
|
4713
|
+
const cleanJson = this.sanitizeJson(jsonContent);
|
|
4714
|
+
const mapping = JSON.parse(cleanJson);
|
|
4715
|
+
this.cache.set(cacheKey, mapping);
|
|
4716
|
+
return mapping;
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
} catch (error) {
|
|
4720
|
+
console.warn("[SchemaMapper] AI training failed, falling back to heuristics:", error);
|
|
4721
|
+
}
|
|
4722
|
+
return {};
|
|
4723
|
+
}
|
|
4724
|
+
/**
|
|
4725
|
+
* Forgiving JSON parser that fixes common AI formatting mistakes.
|
|
4726
|
+
*/
|
|
4727
|
+
static sanitizeJson(s) {
|
|
4728
|
+
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();
|
|
4729
|
+
}
|
|
4730
|
+
static getCached(projectId, keys) {
|
|
4731
|
+
const cacheKey = `${projectId}:${keys.sort().join(",")}`;
|
|
4732
|
+
return this.cache.get(cacheKey);
|
|
4733
|
+
}
|
|
4734
|
+
};
|
|
4735
|
+
SchemaMapper.cache = /* @__PURE__ */ new Map();
|
|
4736
|
+
/**
|
|
4737
|
+
* Descriptions of standard UI properties to help the AI map fields accurately.
|
|
4738
|
+
*/
|
|
4739
|
+
SchemaMapper.TARGET_PROPERTIES = {
|
|
4740
|
+
name: "The primary title, name, or label of the item",
|
|
4741
|
+
price: "The numeric cost, price, MSRP, or amount",
|
|
4742
|
+
brand: "The manufacturer, vendor, brand, or maker",
|
|
4743
|
+
image: "The URL to an image, thumbnail, photo, or picture",
|
|
4744
|
+
stock: "The availability, inventory count, quantity, or stock level",
|
|
4745
|
+
description: "The detailed text content, summary, or body info",
|
|
4746
|
+
category: "The group, department, type, or category name"
|
|
4211
4747
|
};
|
|
4212
4748
|
|
|
4213
4749
|
// src/core/Pipeline.ts
|
|
@@ -4256,205 +4792,31 @@ var Pipeline = class {
|
|
|
4256
4792
|
}
|
|
4257
4793
|
this.reranker = new Reranker();
|
|
4258
4794
|
}
|
|
4795
|
+
/**
|
|
4796
|
+
* Expose the underlying LLM provider (set after initialize()).
|
|
4797
|
+
* Used by the stream handler to pass to UITransformer.analyzeAndDecide().
|
|
4798
|
+
*/
|
|
4799
|
+
getLLMProvider() {
|
|
4800
|
+
return this.initialised ? this.llmProvider : void 0;
|
|
4801
|
+
}
|
|
4259
4802
|
async initialize() {
|
|
4260
|
-
var _a
|
|
4803
|
+
var _a;
|
|
4261
4804
|
if (this.initialised) return;
|
|
4262
|
-
const CHART_MARKER = "<!-- UI_PROTOCOL_V7 -->";
|
|
4263
4805
|
const chartInstruction = `
|
|
4806
|
+
You are a helpful product assistant. Use the provided context to answer questions accurately.
|
|
4264
4807
|
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
---
|
|
4271
|
-
|
|
4272
|
-
## 1. INTENT CLASSIFICATION (MANDATORY)
|
|
4273
|
-
|
|
4274
|
-
Classify the query into ONE of the following intents:
|
|
4275
|
-
|
|
4276
|
-
- "explore" \u2192 user wants to browse items (e.g., "show me products")
|
|
4277
|
-
- "analyze" \u2192 user wants aggregation/distribution (e.g., "distribution of products")
|
|
4278
|
-
- "compare" \u2192 user wants side-by-side comparison
|
|
4279
|
-
- "detail" \u2192 user wants structured/tabular data
|
|
4808
|
+
### UI STYLE RULES (CRITICAL):
|
|
4809
|
+
- NEVER generate markdown tables. If you do, the UI will break.
|
|
4810
|
+
- NEVER generate HTML tags like <figure>, <tbody>, <tr>, etc.
|
|
4811
|
+
- NEVER generate text-based charts or graphs.
|
|
4812
|
+
- ONLY use plain text and bullet points.
|
|
4280
4813
|
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
| Intent | View |
|
|
4286
|
-
|----------|-----------|
|
|
4287
|
-
| explore | carousel |
|
|
4288
|
-
| analyze | chart |
|
|
4289
|
-
| compare | table |
|
|
4290
|
-
| detail | table |
|
|
4291
|
-
|
|
4292
|
-
\u26A0\uFE0F Override Rules:
|
|
4293
|
-
- If query contains "distribution", "percentage", "ratio" \u2192 ALWAYS chart
|
|
4294
|
-
- If query contains "show products", "list items" \u2192 ALWAYS carousel
|
|
4295
|
-
- NEVER mix views
|
|
4296
|
-
|
|
4297
|
-
---
|
|
4298
|
-
|
|
4299
|
-
## 3. RESPONSE FORMAT (STRICT JSON ONLY INSIDE BLOCK)
|
|
4300
|
-
|
|
4301
|
-
\`\`\`ui
|
|
4302
|
-
{
|
|
4303
|
-
"view": "carousel" | "chart" | "table",
|
|
4304
|
-
"title": "string",
|
|
4305
|
-
"description": "string",
|
|
4306
|
-
"metadata": {
|
|
4307
|
-
"intent": "explore" | "analyze" | "compare" | "detail",
|
|
4308
|
-
"confidence": 0.0-1.0
|
|
4309
|
-
},
|
|
4310
|
-
|
|
4311
|
-
// CHART ONLY
|
|
4312
|
-
"chart": {
|
|
4313
|
-
"type": "pie" | "bar" | "line",
|
|
4314
|
-
"xKey": "label",
|
|
4315
|
-
"yKeys": ["value"],
|
|
4316
|
-
"data": []
|
|
4317
|
-
},
|
|
4318
|
-
|
|
4319
|
-
// TABLE ONLY
|
|
4320
|
-
"table": {
|
|
4321
|
-
"columns": [],
|
|
4322
|
-
"rows": []
|
|
4323
|
-
},
|
|
4324
|
-
|
|
4325
|
-
// CAROUSEL ONLY
|
|
4326
|
-
"carousel": {
|
|
4327
|
-
"items": []
|
|
4328
|
-
},
|
|
4329
|
-
|
|
4330
|
-
"insights": []
|
|
4331
|
-
}
|
|
4332
|
-
\`\`\`
|
|
4333
|
-
|
|
4334
|
-
---
|
|
4335
|
-
|
|
4336
|
-
## 4. DATA CONTRACT (CRITICAL)
|
|
4337
|
-
|
|
4338
|
-
### \u{1F539} CAROUSEL ITEM FORMAT
|
|
4339
|
-
{
|
|
4340
|
-
"id": "string",
|
|
4341
|
-
"name": "string",
|
|
4342
|
-
"brand": "string",
|
|
4343
|
-
"price": number,
|
|
4344
|
-
"image": "url",
|
|
4345
|
-
"link": "url",
|
|
4346
|
-
"inStock": boolean
|
|
4347
|
-
}
|
|
4348
|
-
|
|
4349
|
-
### \u{1F539} CHART DATA FORMAT
|
|
4350
|
-
{
|
|
4351
|
-
"label": "string",
|
|
4352
|
-
"value": number,
|
|
4353
|
-
"inStock": boolean (optional)
|
|
4354
|
-
}
|
|
4355
|
-
|
|
4356
|
-
### \u{1F539} TABLE FORMAT
|
|
4357
|
-
- columns MUST match row keys
|
|
4358
|
-
- rows MUST be flat objects
|
|
4359
|
-
|
|
4360
|
-
---
|
|
4361
|
-
|
|
4362
|
-
## 5. DATA RULES
|
|
4363
|
-
|
|
4364
|
-
- NEVER hallucinate fields or data
|
|
4365
|
-
- ONLY use retrieved vector DB data
|
|
4366
|
-
- NO placeholders ("...", "N/A")
|
|
4367
|
-
- If image/link missing \u2192 REMOVE FIELD
|
|
4368
|
-
- Aggregate BEFORE rendering chart
|
|
4369
|
-
- IF NO DATA IS FOUND in the retrieved context:
|
|
4370
|
-
\u2192 DO NOT output a \`\`\`ui\`\`\` block
|
|
4371
|
-
\u2192 Instead, state clearly that no relevant products or data were found.
|
|
4372
|
-
|
|
4373
|
-
---
|
|
4374
|
-
|
|
4375
|
-
## 6. SMART RULES (IMPORTANT)
|
|
4376
|
-
|
|
4377
|
-
- If user asks BOTH:
|
|
4378
|
-
"show products AND distribution"
|
|
4379
|
-
\u2192 PRIORITIZE "explore" \u2192 carousel
|
|
4380
|
-
|
|
4381
|
-
- If dataset size > 20:
|
|
4382
|
-
\u2192 aggregate \u2192 chart or table
|
|
4383
|
-
|
|
4384
|
-
---
|
|
4385
|
-
|
|
4386
|
-
## 7. OUTPUT RULES
|
|
4387
|
-
|
|
4388
|
-
- ALWAYS return EXACTLY ONE \`\`\`ui\`\`\` block
|
|
4389
|
-
- JSON must be VALID
|
|
4390
|
-
- No explanation inside block
|
|
4391
|
-
- NEVER use ASCII charts, Markdown tables, or text-based drawings for data.
|
|
4392
|
-
- If data is tabular or statistical, ALWAYS use the \`\`\`ui\`\`\` block.
|
|
4393
|
-
|
|
4394
|
-
---
|
|
4395
|
-
|
|
4396
|
-
## 8. EXAMPLES
|
|
4397
|
-
|
|
4398
|
-
User: "show me beauty products"
|
|
4399
|
-
|
|
4400
|
-
\`\`\`ui
|
|
4401
|
-
{
|
|
4402
|
-
"view": "carousel",
|
|
4403
|
-
"title": "Beauty Products",
|
|
4404
|
-
"description": "Browse available beauty items",
|
|
4405
|
-
"metadata": {
|
|
4406
|
-
"intent": "explore",
|
|
4407
|
-
"confidence": 0.95
|
|
4408
|
-
},
|
|
4409
|
-
"carousel": {
|
|
4410
|
-
"items": [
|
|
4411
|
-
{
|
|
4412
|
-
"id": "1",
|
|
4413
|
-
"name": "Mascara",
|
|
4414
|
-
"brand": "Essence",
|
|
4415
|
-
"price": 9.99,
|
|
4416
|
-
"image": "https://example.com/mascara.jpg",
|
|
4417
|
-
"link": "https://example.com/p1",
|
|
4418
|
-
"inStock": true
|
|
4419
|
-
}
|
|
4420
|
-
]
|
|
4421
|
-
},
|
|
4422
|
-
"insights": ["Most products are affordable"]
|
|
4423
|
-
}
|
|
4424
|
-
\`\`\`
|
|
4425
|
-
|
|
4426
|
-
---
|
|
4427
|
-
|
|
4428
|
-
User: "distribution of products across categories"
|
|
4429
|
-
|
|
4430
|
-
\`\`\`ui
|
|
4431
|
-
{
|
|
4432
|
-
"view": "chart",
|
|
4433
|
-
"title": "Product Distribution",
|
|
4434
|
-
"description": "Category-wise product distribution",
|
|
4435
|
-
"metadata": {
|
|
4436
|
-
"intent": "analyze",
|
|
4437
|
-
"confidence": 0.98
|
|
4438
|
-
},
|
|
4439
|
-
"chart": {
|
|
4440
|
-
"type": "pie",
|
|
4441
|
-
"xKey": "label",
|
|
4442
|
-
"yKeys": ["value"],
|
|
4443
|
-
"data": [
|
|
4444
|
-
{ "label": "Beauty", "value": 15 },
|
|
4445
|
-
{ "label": "Electronics", "value": 10 }
|
|
4446
|
-
]
|
|
4447
|
-
},
|
|
4448
|
-
"insights": ["Beauty dominates inventory"]
|
|
4449
|
-
}
|
|
4450
|
-
\`\`\`
|
|
4814
|
+
### PRODUCT DISPLAY:
|
|
4815
|
+
- When recommending products, simply list their names, prices, and features in a friendly, conversational manner.
|
|
4816
|
+
- The UI will automatically detect these products and show high-quality product cards in a carousel below your message.
|
|
4817
|
+
- Do NOT try to format product lists as tables.
|
|
4451
4818
|
`;
|
|
4452
|
-
|
|
4453
|
-
let cleanPrompt = this.config.llm.systemPrompt || "";
|
|
4454
|
-
cleanPrompt = cleanPrompt.replace(/\n\n### UNIVERSAL UI PROTOCOL[\s\S]*?(?=\n\n|$)/g, "");
|
|
4455
|
-
cleanPrompt = cleanPrompt.replace(/<!-- UI_PROTOCOL_V\d+ -->[\s\S]*?(?=\n\n|$)/g, "");
|
|
4456
|
-
this.config.llm.systemPrompt = cleanPrompt.trim() + chartInstruction;
|
|
4457
|
-
}
|
|
4819
|
+
this.config.llm.systemPrompt = chartInstruction;
|
|
4458
4820
|
console.log(`[Pipeline] Initializing with provider: ${this.config.vectorDb.provider}...`);
|
|
4459
4821
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
4460
4822
|
const { llmProvider, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
@@ -4469,7 +4831,7 @@ User: "distribution of products across categories"
|
|
|
4469
4831
|
this.entityExtractor = new EntityExtractor(this.llmProvider);
|
|
4470
4832
|
}
|
|
4471
4833
|
await this.vectorDB.initialize();
|
|
4472
|
-
if (((
|
|
4834
|
+
if (((_a = this.config.rag) == null ? void 0 : _a.architecture) === "agentic") {
|
|
4473
4835
|
this.agent = new LangChainAgent(this, this.config);
|
|
4474
4836
|
await this.agent.initialize(this.llmProvider);
|
|
4475
4837
|
}
|
|
@@ -4606,14 +4968,16 @@ User: "distribution of products across categories"
|
|
|
4606
4968
|
let reply = "";
|
|
4607
4969
|
let sources = [];
|
|
4608
4970
|
let graphData;
|
|
4971
|
+
let uiTransformation;
|
|
4609
4972
|
try {
|
|
4610
4973
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
|
|
4611
4974
|
const chunk = temp.value;
|
|
4612
4975
|
if (typeof chunk === "string") {
|
|
4613
4976
|
reply += chunk;
|
|
4614
|
-
} else if ("
|
|
4615
|
-
sources = chunk.sources;
|
|
4616
|
-
graphData = chunk.graphData;
|
|
4977
|
+
} else if (typeof chunk === "object" && chunk !== null) {
|
|
4978
|
+
if ("sources" in chunk) sources = chunk.sources;
|
|
4979
|
+
if ("graphData" in chunk) graphData = chunk.graphData;
|
|
4980
|
+
if ("ui_transformation" in chunk) uiTransformation = chunk.ui_transformation;
|
|
4617
4981
|
}
|
|
4618
4982
|
}
|
|
4619
4983
|
} catch (temp) {
|
|
@@ -4626,7 +4990,7 @@ User: "distribution of products across categories"
|
|
|
4626
4990
|
throw error[0];
|
|
4627
4991
|
}
|
|
4628
4992
|
}
|
|
4629
|
-
return { reply, sources, graphData };
|
|
4993
|
+
return { reply, sources, graphData, ui_transformation: uiTransformation };
|
|
4630
4994
|
}
|
|
4631
4995
|
/**
|
|
4632
4996
|
* High-performance streaming RAG flow.
|
|
@@ -4670,7 +5034,12 @@ ${graphContext}
|
|
|
4670
5034
|
VECTOR CONTEXT:
|
|
4671
5035
|
${context}`;
|
|
4672
5036
|
}
|
|
4673
|
-
const
|
|
5037
|
+
const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
|
|
5038
|
+
const trainingPromise = allMetadataKeys.length > 0 ? SchemaMapper.train(this.llmProvider, ns, allMetadataKeys) : Promise.resolve(void 0);
|
|
5039
|
+
const restrictionSuffix = "\n\n(IMPORTANT: Use plain text only. NEVER generate tables, HTML figures, or text charts. If listing products, use simple bullet points.)";
|
|
5040
|
+
const hardenedHistory = [...history];
|
|
5041
|
+
const userQuestion = { role: "user", content: question + restrictionSuffix };
|
|
5042
|
+
const messages = [...hardenedHistory, userQuestion];
|
|
4674
5043
|
if (this.llmProvider.chatStream) {
|
|
4675
5044
|
const stream = this.llmProvider.chatStream(messages, context);
|
|
4676
5045
|
if (!stream) {
|
|
@@ -4695,7 +5064,8 @@ ${context}`;
|
|
|
4695
5064
|
const reply = yield new __await(this.llmProvider.chat(messages, context));
|
|
4696
5065
|
yield reply;
|
|
4697
5066
|
}
|
|
4698
|
-
const
|
|
5067
|
+
const trainedSchema = yield new __await(trainingPromise);
|
|
5068
|
+
const uiTransformation = yield new __await(this.generateUiTransformation(question, sources, context, trainedSchema));
|
|
4699
5069
|
yield {
|
|
4700
5070
|
reply: "",
|
|
4701
5071
|
sources,
|
|
@@ -4711,6 +5081,17 @@ ${context}`;
|
|
|
4711
5081
|
* Universal retrieval method combining all enabled providers.
|
|
4712
5082
|
* Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
|
|
4713
5083
|
*/
|
|
5084
|
+
async generateUiTransformation(question, sources, _context, trainedSchema) {
|
|
5085
|
+
if (!sources || sources.length === 0) {
|
|
5086
|
+
return UITransformer.transform(question, sources, this.config, trainedSchema);
|
|
5087
|
+
}
|
|
5088
|
+
try {
|
|
5089
|
+
return await UITransformer.analyzeAndDecide(question, sources, this.llmProvider);
|
|
5090
|
+
} catch (err) {
|
|
5091
|
+
console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
|
|
5092
|
+
return UITransformer.transform(question, sources, this.config, trainedSchema);
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
4714
5095
|
async retrieve(query, options) {
|
|
4715
5096
|
var _a, _b, _c;
|
|
4716
5097
|
const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
|
|
@@ -4913,6 +5294,14 @@ var VectorPlugin = class {
|
|
|
4913
5294
|
getConfig() {
|
|
4914
5295
|
return this.config;
|
|
4915
5296
|
}
|
|
5297
|
+
/**
|
|
5298
|
+
* Get the initialized LLM provider (available after the first request).
|
|
5299
|
+
* Used by handlers to pass to UITransformer.analyzeAndDecide() for
|
|
5300
|
+
* LLM-driven visualization decisions.
|
|
5301
|
+
*/
|
|
5302
|
+
getLLMProvider() {
|
|
5303
|
+
return this.pipeline.getLLMProvider();
|
|
5304
|
+
}
|
|
4916
5305
|
/**
|
|
4917
5306
|
* Perform pre-flight health checks on all configured providers.
|
|
4918
5307
|
* Useful to verify connectivity before running operations.
|
|
@@ -5192,199 +5581,189 @@ var DocumentParser = class {
|
|
|
5192
5581
|
// src/server.ts
|
|
5193
5582
|
init_BaseVectorProvider();
|
|
5194
5583
|
init_PineconeProvider();
|
|
5195
|
-
init_PostgreSQLProvider();
|
|
5196
5584
|
|
|
5197
|
-
// src/providers/vectordb/
|
|
5585
|
+
// src/providers/vectordb/PostgreSQLProvider.ts
|
|
5198
5586
|
var import_pg2 = require("pg");
|
|
5199
5587
|
init_BaseVectorProvider();
|
|
5200
|
-
var
|
|
5588
|
+
var PostgreSQLProvider = class extends BaseVectorProvider {
|
|
5201
5589
|
constructor(config) {
|
|
5202
|
-
var _a
|
|
5590
|
+
var _a;
|
|
5203
5591
|
super(config);
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
}
|
|
5592
|
+
this.tableName = this.indexName.replace(/[^a-z0-9_]/gi, "_");
|
|
5593
|
+
const opts = config.options;
|
|
5594
|
+
if (!opts.connectionString) throw new Error("[PostgreSQLProvider] options.connectionString is required");
|
|
5208
5595
|
this.connectionString = opts.connectionString;
|
|
5209
|
-
this.dimensions = (_a = opts.dimensions) != null ? _a :
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5596
|
+
this.dimensions = (_a = opts.dimensions) != null ? _a : 1536;
|
|
5597
|
+
}
|
|
5598
|
+
static getValidator() {
|
|
5599
|
+
return {
|
|
5600
|
+
validate(config) {
|
|
5601
|
+
const errors = [];
|
|
5602
|
+
const opts = config.options || {};
|
|
5603
|
+
if (!opts.connectionString) {
|
|
5604
|
+
errors.push({
|
|
5605
|
+
field: "vectorDb.options.connectionString",
|
|
5606
|
+
message: "PostgreSQL connection string is required",
|
|
5607
|
+
suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
|
|
5608
|
+
severity: "error"
|
|
5609
|
+
});
|
|
5610
|
+
}
|
|
5611
|
+
if (opts.tables && typeof opts.tables !== "string" && !Array.isArray(opts.tables)) {
|
|
5612
|
+
errors.push({
|
|
5613
|
+
field: "vectorDb.options.tables",
|
|
5614
|
+
message: "PostgreSQL tables must be a string or a string array",
|
|
5615
|
+
severity: "error"
|
|
5616
|
+
});
|
|
5617
|
+
}
|
|
5618
|
+
return errors;
|
|
5619
|
+
}
|
|
5620
|
+
};
|
|
5621
|
+
}
|
|
5622
|
+
static getHealthChecker() {
|
|
5623
|
+
return {
|
|
5624
|
+
async check(config) {
|
|
5625
|
+
const opts = config.options || {};
|
|
5626
|
+
const timestamp = Date.now();
|
|
5627
|
+
try {
|
|
5628
|
+
const { Client } = await import("pg");
|
|
5629
|
+
const client = new Client({ connectionString: opts.connectionString });
|
|
5630
|
+
await client.connect();
|
|
5631
|
+
const result = await client.query(`
|
|
5632
|
+
SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'vector');
|
|
5633
|
+
`);
|
|
5634
|
+
const hasVector = result.rows[0].exists;
|
|
5635
|
+
await client.end();
|
|
5636
|
+
return {
|
|
5637
|
+
healthy: true,
|
|
5638
|
+
provider: "postgresql",
|
|
5639
|
+
capabilities: { pgvectorInstalled: hasVector },
|
|
5640
|
+
timestamp
|
|
5641
|
+
};
|
|
5642
|
+
} catch (error) {
|
|
5643
|
+
return {
|
|
5644
|
+
healthy: false,
|
|
5645
|
+
provider: "postgresql",
|
|
5646
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
5647
|
+
timestamp
|
|
5648
|
+
};
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5651
|
+
};
|
|
5219
5652
|
}
|
|
5220
5653
|
async initialize() {
|
|
5221
5654
|
this.pool = new import_pg2.Pool({ connectionString: this.connectionString });
|
|
5222
5655
|
const client = await this.pool.connect();
|
|
5223
5656
|
try {
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5657
|
+
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
5658
|
+
await client.query(`
|
|
5659
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
5660
|
+
id TEXT PRIMARY KEY,
|
|
5661
|
+
namespace TEXT NOT NULL DEFAULT '',
|
|
5662
|
+
content TEXT NOT NULL,
|
|
5663
|
+
metadata JSONB,
|
|
5664
|
+
embedding VECTOR(${this.dimensions})
|
|
5665
|
+
)
|
|
5666
|
+
`);
|
|
5667
|
+
await client.query(`
|
|
5668
|
+
CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
|
|
5669
|
+
ON ${this.tableName}
|
|
5670
|
+
USING hnsw (embedding vector_cosine_ops)
|
|
5229
5671
|
`);
|
|
5230
|
-
const discoveredTables = res.rows.map((r) => r.table_name);
|
|
5231
|
-
if (this.tables.length === 0) {
|
|
5232
|
-
this.tables = discoveredTables;
|
|
5233
|
-
} else {
|
|
5234
|
-
const staticTables = [...this.tables];
|
|
5235
|
-
this.tables = staticTables.filter((t) => discoveredTables.includes(t));
|
|
5236
|
-
if (this.tables.length < staticTables.length) {
|
|
5237
|
-
const missing = staticTables.filter((t) => !discoveredTables.includes(t));
|
|
5238
|
-
console.warn(`[MultiTablePostgresProvider] Some configured tables were skipped (missing 'embedding' column): ${missing.join(", ")}`);
|
|
5239
|
-
}
|
|
5240
|
-
}
|
|
5241
|
-
if (this.tables.length === 0) {
|
|
5242
|
-
console.warn('[MultiTablePostgresProvider] No tables with "embedding" columns found in the database.');
|
|
5243
|
-
} else {
|
|
5244
|
-
console.log(
|
|
5245
|
-
`[MultiTablePostgresProvider] Connected. Searching across ${this.tables.length} table(s): ${this.tables.join(", ")}`
|
|
5246
|
-
);
|
|
5247
|
-
}
|
|
5248
5672
|
} finally {
|
|
5249
5673
|
client.release();
|
|
5250
5674
|
}
|
|
5251
5675
|
}
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
async batchUpsert(_docs, _namespace) {
|
|
5265
|
-
throw new Error(
|
|
5266
|
-
"[MultiTablePostgresProvider] batchUpsert() is not supported in multi-table mode."
|
|
5676
|
+
async upsert(doc, namespace = "") {
|
|
5677
|
+
var _a;
|
|
5678
|
+
const vectorLiteral = `[${doc.vector.join(",")}]`;
|
|
5679
|
+
await this.pool.query(
|
|
5680
|
+
`INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
|
|
5681
|
+
VALUES ($1, $2, $3, $4, $5::vector)
|
|
5682
|
+
ON CONFLICT (id) DO UPDATE
|
|
5683
|
+
SET namespace = EXCLUDED.namespace,
|
|
5684
|
+
content = EXCLUDED.content,
|
|
5685
|
+
metadata = EXCLUDED.metadata,
|
|
5686
|
+
embedding = EXCLUDED.embedding`,
|
|
5687
|
+
[doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), vectorLiteral]
|
|
5267
5688
|
);
|
|
5268
5689
|
}
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
};
|
|
5295
|
-
const dynamicKeywordQuery = getDynamicKeywordQuery();
|
|
5296
|
-
const queryPromises = this.tables.map(async (table) => {
|
|
5297
|
-
try {
|
|
5298
|
-
let sqlQuery = "";
|
|
5299
|
-
let params = [];
|
|
5300
|
-
if (queryText) {
|
|
5301
|
-
const hasEntityHints = entityHints.length > 0;
|
|
5302
|
-
const exactNameScoreExpr = hasEntityHints ? `+ (
|
|
5303
|
-
SELECT COALESCE(MAX(
|
|
5304
|
-
CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
|
|
5305
|
-
THEN 1.0 ELSE 0.0 END
|
|
5306
|
-
), 0)
|
|
5307
|
-
FROM jsonb_each_text(to_jsonb(t)) AS kv(key, val)
|
|
5308
|
-
WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
|
|
5309
|
-
) * 3.0` : "";
|
|
5310
|
-
sqlQuery = `
|
|
5311
|
-
SELECT *,
|
|
5312
|
-
(1 - (embedding <=> $1::vector)) AS vector_score,
|
|
5313
|
-
COALESCE(ts_rank(to_tsvector('english', (to_jsonb(t) - 'embedding')::text), to_tsquery('english', $2)), 0) AS keyword_score,
|
|
5314
|
-
((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
|
|
5315
|
-
FROM "${table}" t
|
|
5316
|
-
ORDER BY hybrid_score DESC
|
|
5317
|
-
LIMIT 50
|
|
5318
|
-
`;
|
|
5319
|
-
params = [vectorLiteral, dynamicKeywordQuery];
|
|
5320
|
-
} else {
|
|
5321
|
-
sqlQuery = `
|
|
5322
|
-
SELECT *,
|
|
5323
|
-
(1 - (embedding <=> $1::vector)) AS hybrid_score
|
|
5324
|
-
FROM "${table}" t
|
|
5325
|
-
ORDER BY hybrid_score DESC
|
|
5326
|
-
LIMIT 50
|
|
5327
|
-
`;
|
|
5328
|
-
params = [vectorLiteral];
|
|
5329
|
-
}
|
|
5330
|
-
const result = await this.pool.query(sqlQuery, params);
|
|
5331
|
-
if (result.rowCount && result.rowCount > 0) {
|
|
5332
|
-
console.log(` \u2705 Table "${table}": Found ${result.rowCount} potential matches. Top score: ${result.rows[0].hybrid_score.toFixed(4)}`);
|
|
5333
|
-
} else {
|
|
5334
|
-
console.log(` \u26AA Table "${table}": No relevant matches found.`);
|
|
5335
|
-
}
|
|
5336
|
-
const tableResults = [];
|
|
5337
|
-
for (const row of result.rows) {
|
|
5338
|
-
const _a2 = row, { hybrid_score, id } = _a2, rest = __objRest(_a2, ["hybrid_score", "id"]);
|
|
5339
|
-
delete rest.embedding;
|
|
5340
|
-
delete rest.vector_score;
|
|
5341
|
-
delete rest.keyword_score;
|
|
5342
|
-
delete rest.exact_name_score;
|
|
5343
|
-
const content = `[TYPE: ${table.replace(/s$/, "").toUpperCase()}]
|
|
5344
|
-
` + Object.entries(rest).filter(([k, v]) => v !== null && typeof v !== "object" && k !== "id").map(([k, v]) => `${k}: ${v}`).join("\n");
|
|
5345
|
-
tableResults.push({
|
|
5346
|
-
id: `${table}-${id}`,
|
|
5347
|
-
score: parseFloat(String(hybrid_score)),
|
|
5348
|
-
content,
|
|
5349
|
-
metadata: __spreadProps(__spreadValues({}, rest), { source_table: table })
|
|
5350
|
-
});
|
|
5351
|
-
}
|
|
5352
|
-
return tableResults;
|
|
5353
|
-
} catch (err) {
|
|
5354
|
-
console.error(
|
|
5355
|
-
`[MultiTablePostgresProvider] Error querying table "${table}":`,
|
|
5356
|
-
err.message
|
|
5357
|
-
);
|
|
5358
|
-
return [];
|
|
5690
|
+
async batchUpsert(docs, namespace = "") {
|
|
5691
|
+
if (docs.length === 0) return;
|
|
5692
|
+
const client = await this.pool.connect();
|
|
5693
|
+
try {
|
|
5694
|
+
await client.query("BEGIN");
|
|
5695
|
+
const BATCH_SIZE = 50;
|
|
5696
|
+
for (let i = 0; i < docs.length; i += BATCH_SIZE) {
|
|
5697
|
+
const batch = docs.slice(i, i + BATCH_SIZE);
|
|
5698
|
+
const values = [];
|
|
5699
|
+
const valuePlaceholders = batch.map((doc, idx) => {
|
|
5700
|
+
var _a;
|
|
5701
|
+
const offset = idx * 5;
|
|
5702
|
+
values.push(doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), `[${doc.vector.join(",")}]`);
|
|
5703
|
+
return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}::vector)`;
|
|
5704
|
+
}).join(", ");
|
|
5705
|
+
const query = `
|
|
5706
|
+
INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
|
|
5707
|
+
VALUES ${valuePlaceholders}
|
|
5708
|
+
ON CONFLICT (id) DO UPDATE
|
|
5709
|
+
SET namespace = EXCLUDED.namespace,
|
|
5710
|
+
content = EXCLUDED.content,
|
|
5711
|
+
metadata = EXCLUDED.metadata,
|
|
5712
|
+
embedding = EXCLUDED.embedding
|
|
5713
|
+
`;
|
|
5714
|
+
await client.query(query, values);
|
|
5359
5715
|
}
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
}
|
|
5365
|
-
|
|
5366
|
-
for (const res of allResults) {
|
|
5367
|
-
const table = (_a = res.metadata) == null ? void 0 : _a.source_table;
|
|
5368
|
-
if (!resultsByTable[table]) resultsByTable[table] = [];
|
|
5369
|
-
resultsByTable[table].push(res);
|
|
5716
|
+
await client.query("COMMIT");
|
|
5717
|
+
} catch (error) {
|
|
5718
|
+
await client.query("ROLLBACK");
|
|
5719
|
+
throw error;
|
|
5720
|
+
} finally {
|
|
5721
|
+
client.release();
|
|
5370
5722
|
}
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5723
|
+
}
|
|
5724
|
+
async query(vector, topK, namespace, filter) {
|
|
5725
|
+
const vectorLiteral = `[${vector.join(",")}]`;
|
|
5726
|
+
let whereClause = namespace ? `WHERE namespace = $3` : "";
|
|
5727
|
+
const params = [vectorLiteral, topK];
|
|
5728
|
+
if (namespace) params.push(namespace);
|
|
5729
|
+
const publicFilter = this.sanitizeFilter(filter);
|
|
5730
|
+
if (Object.keys(publicFilter).length > 0) {
|
|
5731
|
+
const filterConditions = Object.entries(publicFilter).map(([key, val]) => {
|
|
5732
|
+
const paramIdx = params.length + 1;
|
|
5733
|
+
params.push(JSON.stringify(val));
|
|
5734
|
+
return `metadata->>'${key}' = $${paramIdx}`;
|
|
5735
|
+
}).join(" AND ");
|
|
5736
|
+
whereClause = whereClause ? `${whereClause} AND ${filterConditions}` : `WHERE ${filterConditions}`;
|
|
5375
5737
|
}
|
|
5376
|
-
const
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5738
|
+
const client = await this.pool.connect();
|
|
5739
|
+
try {
|
|
5740
|
+
const efSearch = this.config.options.efSearch || Math.max(topK * 10, 40);
|
|
5741
|
+
await client.query(`SET LOCAL hnsw.ef_search = ${efSearch}`);
|
|
5742
|
+
const result = await client.query(
|
|
5743
|
+
`SELECT id, content, metadata, 1 - (embedding <=> $1::vector) AS score
|
|
5744
|
+
FROM ${this.tableName}
|
|
5745
|
+
${whereClause}
|
|
5746
|
+
ORDER BY embedding <=> $1::vector
|
|
5747
|
+
LIMIT $2`,
|
|
5748
|
+
params
|
|
5749
|
+
);
|
|
5750
|
+
return result.rows.map((row) => ({
|
|
5751
|
+
id: String(row["id"]),
|
|
5752
|
+
score: parseFloat(String(row["score"])),
|
|
5753
|
+
content: String(row["content"]),
|
|
5754
|
+
metadata: row["metadata"]
|
|
5755
|
+
}));
|
|
5756
|
+
} finally {
|
|
5757
|
+
client.release();
|
|
5380
5758
|
}
|
|
5381
|
-
return finalSorted.slice(0, Math.max(topK, 15));
|
|
5382
5759
|
}
|
|
5383
|
-
async delete(
|
|
5384
|
-
|
|
5760
|
+
async delete(id, namespace) {
|
|
5761
|
+
const where = namespace ? "WHERE id = $1 AND namespace = $2" : "WHERE id = $1";
|
|
5762
|
+
const params = namespace ? [id, namespace] : [id];
|
|
5763
|
+
await this.pool.query(`DELETE FROM ${this.tableName} ${where}`, params);
|
|
5385
5764
|
}
|
|
5386
|
-
async deleteNamespace(
|
|
5387
|
-
|
|
5765
|
+
async deleteNamespace(namespace) {
|
|
5766
|
+
await this.pool.query(`DELETE FROM ${this.tableName} WHERE namespace = $1`, [namespace]);
|
|
5388
5767
|
}
|
|
5389
5768
|
async ping() {
|
|
5390
5769
|
try {
|
|
@@ -5395,13 +5774,12 @@ var MultiTablePostgresProvider = class extends BaseVectorProvider {
|
|
|
5395
5774
|
}
|
|
5396
5775
|
}
|
|
5397
5776
|
async disconnect() {
|
|
5398
|
-
|
|
5399
|
-
await this.pool.end();
|
|
5400
|
-
}
|
|
5777
|
+
await this.pool.end();
|
|
5401
5778
|
}
|
|
5402
5779
|
};
|
|
5403
5780
|
|
|
5404
5781
|
// src/server.ts
|
|
5782
|
+
init_MultiTablePostgresProvider();
|
|
5405
5783
|
init_MongoDBProvider();
|
|
5406
5784
|
init_MilvusProvider();
|
|
5407
5785
|
init_QdrantProvider();
|
|
@@ -5428,7 +5806,7 @@ function sseMetaFrame(meta) {
|
|
|
5428
5806
|
`;
|
|
5429
5807
|
}
|
|
5430
5808
|
function sseUIFrame(uiTransformation) {
|
|
5431
|
-
return `data: ${JSON.stringify(
|
|
5809
|
+
return `data: ${JSON.stringify({ type: "ui_transformation", data: uiTransformation })}
|
|
5432
5810
|
|
|
5433
5811
|
`;
|
|
5434
5812
|
}
|
|
@@ -5483,6 +5861,7 @@ function createStreamHandler(configOrPlugin) {
|
|
|
5483
5861
|
const encoder = new TextEncoder();
|
|
5484
5862
|
const stream = new ReadableStream({
|
|
5485
5863
|
async start(controller) {
|
|
5864
|
+
var _a, _b;
|
|
5486
5865
|
const enqueue = (text) => controller.enqueue(encoder.encode(text));
|
|
5487
5866
|
try {
|
|
5488
5867
|
const pipelineStream = plugin.chatStream(message, history, namespace);
|
|
@@ -5493,13 +5872,22 @@ function createStreamHandler(configOrPlugin) {
|
|
|
5493
5872
|
enqueue(sseTextFrame(chunk));
|
|
5494
5873
|
} else {
|
|
5495
5874
|
enqueue(sseMetaFrame(chunk));
|
|
5496
|
-
const
|
|
5497
|
-
|
|
5875
|
+
const responseChunk = chunk;
|
|
5876
|
+
const sources = (responseChunk == null ? void 0 : responseChunk.sources) || [];
|
|
5877
|
+
if (sources.length > 0) {
|
|
5498
5878
|
try {
|
|
5499
|
-
const
|
|
5500
|
-
|
|
5879
|
+
const llmProvider = (_a = plugin.getLLMProvider) == null ? void 0 : _a.call(plugin);
|
|
5880
|
+
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());
|
|
5881
|
+
if (uiTransformation) {
|
|
5882
|
+
enqueue(sseUIFrame(uiTransformation));
|
|
5883
|
+
}
|
|
5501
5884
|
} catch (transformError) {
|
|
5502
5885
|
console.warn("[createStreamHandler] UI transformation warning:", transformError);
|
|
5886
|
+
try {
|
|
5887
|
+
const fallback = UITransformer.transform(message, sources, plugin.getConfig());
|
|
5888
|
+
if (fallback) enqueue(sseUIFrame(fallback));
|
|
5889
|
+
} catch (e) {
|
|
5890
|
+
}
|
|
5503
5891
|
}
|
|
5504
5892
|
}
|
|
5505
5893
|
}
|