@retrivora-ai/rag-engine 1.9.3 → 1.9.6
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/README.md +27 -0
- package/dist/{ILLMProvider-Bw2A28nU.d.mts → ILLMProvider-DNhyOYoK.d.mts} +41 -1
- package/dist/{ILLMProvider-Bw2A28nU.d.ts → ILLMProvider-DNhyOYoK.d.ts} +41 -1
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +552 -198
- package/dist/handlers/index.mjs +552 -198
- package/dist/index-C9v7-tWd.d.mts +183 -0
- package/dist/{index-B70ZLkfG.d.mts → index-CHL1jdYm.d.mts} +1 -1
- package/dist/index-CjQdL0cX.d.ts +183 -0
- package/dist/{index-DVu-mkAM.d.ts → index-Hgbwl9X4.d.ts} +1 -1
- package/dist/index.css +40 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +326 -158
- package/dist/index.mjs +312 -151
- package/dist/server.d.mts +15 -94
- package/dist/server.d.ts +15 -94
- package/dist/server.js +612 -198
- package/dist/server.mjs +604 -198
- package/package.json +1 -1
- package/src/app/api/chat/route.ts +2 -2
- package/src/app/api/health/route.ts +3 -2
- package/src/app/api/ingest/route.ts +3 -2
- package/src/app/api/suggestions/route.ts +3 -2
- package/src/app/api/upload/route.ts +3 -2
- package/src/app/constants.tsx +85 -30
- package/src/app/layout.tsx +18 -7
- package/src/components/MessageBubble.tsx +39 -2
- package/src/components/ThinkingBlock.tsx +75 -0
- package/src/components/VisualizationRenderer.tsx +27 -1
- package/src/config/RagConfig.ts +47 -0
- package/src/config/serverConfig.ts +25 -0
- package/src/core/ConfigResolver.ts +56 -4
- package/src/core/ConfigValidator.ts +2 -1
- package/src/core/Pipeline.ts +224 -67
- package/src/core/ProviderRegistry.ts +11 -2
- package/src/core/QueryProcessor.ts +38 -4
- package/src/core/Retrivora.ts +51 -0
- package/src/exceptions/index.ts +59 -0
- package/src/handlers/index.ts +2 -0
- package/src/hooks/useRagChat.ts +26 -4
- package/src/index.ts +25 -1
- package/src/lib/plugin.ts +24 -0
- package/src/llm/LLMFactory.ts +4 -1
- package/src/llm/providers/AnthropicProvider.ts +70 -20
- package/src/llm/providers/GeminiProvider.ts +14 -15
- package/src/llm/providers/OllamaProvider.ts +13 -16
- package/src/llm/providers/OpenAIProvider.ts +9 -14
- package/src/llm/providers/UniversalLLMAdapter.ts +5 -5
- package/src/llm/utils.ts +46 -0
- package/src/providers/vectordb/MongoDBProvider.ts +9 -4
- package/src/providers/vectordb/MultiTablePostgresProvider.ts +45 -13
- package/src/rag/EntityExtractor.ts +2 -2
- package/src/rag/Reranker.ts +9 -16
- package/src/server.ts +27 -1
- package/src/types/chat.ts +7 -0
- package/src/utils/UITransformer.ts +73 -4
- package/dist/DocumentChunker-Dh9TvmGG.d.mts +0 -45
- package/dist/DocumentChunker-Dh9TvmGG.d.ts +0 -45
package/dist/handlers/index.js
CHANGED
|
@@ -661,6 +661,11 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
661
661
|
CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
|
|
662
662
|
ON ${this.uploadTable}
|
|
663
663
|
USING hnsw (embedding vector_cosine_ops)
|
|
664
|
+
`);
|
|
665
|
+
await client.query(`
|
|
666
|
+
CREATE INDEX IF NOT EXISTS "${this.uploadTable}_fts_idx"
|
|
667
|
+
ON "${this.uploadTable}"
|
|
668
|
+
USING gin (to_tsvector('english', content))
|
|
664
669
|
`);
|
|
665
670
|
const res = await client.query(`
|
|
666
671
|
SELECT table_name
|
|
@@ -731,6 +736,11 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
731
736
|
CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
|
|
732
737
|
ON "${tableName}"
|
|
733
738
|
USING hnsw (embedding vector_cosine_ops)
|
|
739
|
+
`);
|
|
740
|
+
await client.query(`
|
|
741
|
+
CREATE INDEX IF NOT EXISTS "${tableName}_fts_idx"
|
|
742
|
+
ON "${tableName}"
|
|
743
|
+
USING gin (to_tsvector('english', content))
|
|
734
744
|
`);
|
|
735
745
|
if (!this.tables.includes(tableName)) {
|
|
736
746
|
this.tables.push(tableName);
|
|
@@ -822,8 +832,11 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
822
832
|
const paramIdx = baseOffset + filterParams.length;
|
|
823
833
|
const synonyms = (_a3 = FIELD_SYNONYMS[key]) != null ? _a3 : [];
|
|
824
834
|
const keysToCheck = [key, ...synonyms];
|
|
825
|
-
const coalesceExprs = keysToCheck.
|
|
826
|
-
|
|
835
|
+
const coalesceExprs = keysToCheck.flatMap((k) => [
|
|
836
|
+
`metadata->>'${k}'`,
|
|
837
|
+
`to_jsonb(t)->>'${k}'`
|
|
838
|
+
]);
|
|
839
|
+
return `COALESCE(${coalesceExprs.map((expr) => `LOWER(${expr})`).join(", ")}) = LOWER($${paramIdx})`;
|
|
827
840
|
});
|
|
828
841
|
whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
829
842
|
}
|
|
@@ -834,16 +847,35 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
834
847
|
CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
|
|
835
848
|
THEN 1.0 ELSE 0.0 END
|
|
836
849
|
), 0)
|
|
837
|
-
FROM jsonb_each_text(
|
|
850
|
+
FROM jsonb_each_text(COALESCE(v.metadata, k.metadata)) AS kv(key, val)
|
|
838
851
|
WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
|
|
839
852
|
) * 3.0` : "";
|
|
840
853
|
sqlQuery = `
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
854
|
+
WITH vector_search AS (
|
|
855
|
+
SELECT *, (1 - (embedding <=> $1::vector)) AS vector_score
|
|
856
|
+
FROM "${table}" t
|
|
857
|
+
${whereClause}
|
|
858
|
+
ORDER BY embedding <=> $1::vector ASC
|
|
859
|
+
LIMIT ${tableLimit}
|
|
860
|
+
),
|
|
861
|
+
keyword_search AS (
|
|
862
|
+
SELECT *, COALESCE(ts_rank(to_tsvector('english', content), to_tsquery('english', $2)), 0) AS keyword_score
|
|
863
|
+
FROM "${table}" t
|
|
864
|
+
WHERE ${whereClause ? whereClause.replace("WHERE", "") : "TRUE"}
|
|
865
|
+
AND to_tsvector('english', content) @@ to_tsquery('english', $2)
|
|
866
|
+
ORDER BY keyword_score DESC
|
|
867
|
+
LIMIT ${tableLimit}
|
|
868
|
+
)
|
|
869
|
+
SELECT
|
|
870
|
+
COALESCE(v.id, k.id) AS id,
|
|
871
|
+
COALESCE(v.namespace, k.namespace) AS namespace,
|
|
872
|
+
COALESCE(v.content, k.content) AS content,
|
|
873
|
+
COALESCE(v.metadata, k.metadata) AS metadata,
|
|
874
|
+
COALESCE(v.vector_score, 0) AS vector_score,
|
|
875
|
+
COALESCE(k.keyword_score, 0) AS keyword_score,
|
|
876
|
+
(COALESCE(v.vector_score, 0) + COALESCE(k.keyword_score, 0) * 2.0 ${exactNameScoreExpr}) AS hybrid_score
|
|
877
|
+
FROM vector_search v
|
|
878
|
+
FULL OUTER JOIN keyword_search k ON v.id = k.id
|
|
847
879
|
ORDER BY hybrid_score DESC
|
|
848
880
|
LIMIT ${tableLimit}
|
|
849
881
|
`;
|
|
@@ -854,7 +886,7 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
854
886
|
(1 - (embedding <=> $1::vector)) AS hybrid_score
|
|
855
887
|
FROM "${table}" t
|
|
856
888
|
${whereClause}
|
|
857
|
-
ORDER BY
|
|
889
|
+
ORDER BY embedding <=> $1::vector ASC
|
|
858
890
|
LIMIT ${tableLimit}
|
|
859
891
|
`;
|
|
860
892
|
params = [vectorLiteral, ...filterParams];
|
|
@@ -1060,7 +1092,11 @@ var init_MongoDBProvider = __esm({
|
|
|
1060
1092
|
if (key === "namespace") {
|
|
1061
1093
|
vectorSearchFilter.namespace = value;
|
|
1062
1094
|
} else {
|
|
1063
|
-
|
|
1095
|
+
if (typeof value === "string") {
|
|
1096
|
+
matchFilter[key] = { $regex: new RegExp(`^${value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "i") };
|
|
1097
|
+
} else {
|
|
1098
|
+
matchFilter[key] = value;
|
|
1099
|
+
}
|
|
1064
1100
|
}
|
|
1065
1101
|
}
|
|
1066
1102
|
const pipeline = [
|
|
@@ -2149,7 +2185,7 @@ function readEnum(env, name, fallback, allowed) {
|
|
|
2149
2185
|
throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
|
|
2150
2186
|
}
|
|
2151
2187
|
function getEnvConfig(env = process.env, base) {
|
|
2152
|
-
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, _Ga;
|
|
2188
|
+
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, _Ga, _Ha, _Ia, _Ja;
|
|
2153
2189
|
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__";
|
|
2154
2190
|
const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
|
|
2155
2191
|
const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
|
|
@@ -2219,7 +2255,7 @@ function getEnvConfig(env = process.env, base) {
|
|
|
2219
2255
|
rest: "default",
|
|
2220
2256
|
custom: "default"
|
|
2221
2257
|
};
|
|
2222
|
-
return {
|
|
2258
|
+
return __spreadValues({
|
|
2223
2259
|
projectId,
|
|
2224
2260
|
vectorDb: {
|
|
2225
2261
|
provider: vectorProvider,
|
|
@@ -2235,7 +2271,9 @@ function getEnvConfig(env = process.env, base) {
|
|
|
2235
2271
|
maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
|
|
2236
2272
|
temperature: readNumber(env, "LLM_TEMPERATURE", 0.7),
|
|
2237
2273
|
options: {
|
|
2238
|
-
profile: readString(env, "LLM_UNIVERSAL_PROFILE")
|
|
2274
|
+
profile: readString(env, "LLM_UNIVERSAL_PROFILE"),
|
|
2275
|
+
thinking: readString(env, "LLM_THINKING") === "true" || readString(env, "LLM_THINKING") === "enabled" ? true : readString(env, "LLM_THINKING") === "false" ? false : void 0,
|
|
2276
|
+
thinkingBudget: env.LLM_THINKING_BUDGET ? parseInt(env.LLM_THINKING_BUDGET, 10) : void 0
|
|
2239
2277
|
}
|
|
2240
2278
|
},
|
|
2241
2279
|
embedding: {
|
|
@@ -2267,11 +2305,56 @@ function getEnvConfig(env = process.env, base) {
|
|
|
2267
2305
|
topK: readNumber(env, "RAG_TOP_K", 5),
|
|
2268
2306
|
scoreThreshold: readNumber(env, "RAG_SCORE_THRESHOLD", 0),
|
|
2269
2307
|
chunkSize: readNumber(env, "RAG_CHUNK_SIZE", 1e3),
|
|
2270
|
-
chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200)
|
|
2308
|
+
chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200),
|
|
2309
|
+
filterableFields: (_Ha = readString(env, "RAG_FILTERABLE_FIELDS")) == null ? void 0 : _Ha.split(",").map((f) => f.trim()),
|
|
2310
|
+
// Query pipeline toggles — read from .env.local
|
|
2311
|
+
useQueryTransformation: readString(env, "RAG_USE_QUERY_TRANSFORMATION") === "true",
|
|
2312
|
+
useReranking: readString(env, "RAG_USE_RERANKING") === "true",
|
|
2313
|
+
useGraphRetrieval: readString(env, "RAG_USE_GRAPH_RETRIEVAL") === "true",
|
|
2314
|
+
architecture: (_Ia = readString(env, "RAG_ARCHITECTURE")) != null ? _Ia : "simple",
|
|
2315
|
+
chunkingStrategy: (_Ja = readString(env, "RAG_CHUNKING_STRATEGY")) != null ? _Ja : "recursive",
|
|
2316
|
+
uiMapping: (() => {
|
|
2317
|
+
const raw = readString(env, "RAG_UI_MAPPING");
|
|
2318
|
+
if (!raw) return void 0;
|
|
2319
|
+
try {
|
|
2320
|
+
return JSON.parse(raw);
|
|
2321
|
+
} catch (e) {
|
|
2322
|
+
return void 0;
|
|
2323
|
+
}
|
|
2324
|
+
})()
|
|
2271
2325
|
}
|
|
2272
|
-
}
|
|
2326
|
+
}, readString(env, "GRAPH_DB_PROVIDER") ? {
|
|
2327
|
+
graphDb: {
|
|
2328
|
+
provider: readString(env, "GRAPH_DB_PROVIDER"),
|
|
2329
|
+
options: {
|
|
2330
|
+
uri: readString(env, "GRAPH_DB_URI"),
|
|
2331
|
+
username: readString(env, "GRAPH_DB_USERNAME"),
|
|
2332
|
+
password: readString(env, "GRAPH_DB_PASSWORD")
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
} : {});
|
|
2273
2336
|
}
|
|
2274
2337
|
|
|
2338
|
+
// src/exceptions/index.ts
|
|
2339
|
+
var RetrivoraError = class extends Error {
|
|
2340
|
+
constructor(message, code, details) {
|
|
2341
|
+
super(message);
|
|
2342
|
+
this.name = new.target.name;
|
|
2343
|
+
this.code = code;
|
|
2344
|
+
this.details = details;
|
|
2345
|
+
}
|
|
2346
|
+
};
|
|
2347
|
+
var ProviderNotFoundException = class extends RetrivoraError {
|
|
2348
|
+
constructor(providerType, provider, details) {
|
|
2349
|
+
super(`Unsupported ${providerType} provider: ${provider}`, "PROVIDER_NOT_FOUND", details);
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2352
|
+
var ConfigurationException = class extends RetrivoraError {
|
|
2353
|
+
constructor(message, details) {
|
|
2354
|
+
super(message, "CONFIGURATION_ERROR", details);
|
|
2355
|
+
}
|
|
2356
|
+
};
|
|
2357
|
+
|
|
2275
2358
|
// src/core/ConfigResolver.ts
|
|
2276
2359
|
var ConfigResolver = class {
|
|
2277
2360
|
/**
|
|
@@ -2298,24 +2381,81 @@ var ConfigResolver = class {
|
|
|
2298
2381
|
rag: hostConfig.rag ? __spreadValues(__spreadValues({}, envConfig.rag), hostConfig.rag) : envConfig.rag
|
|
2299
2382
|
});
|
|
2300
2383
|
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Resolves the public SDK config shape used by `new Retrivora({...})`.
|
|
2386
|
+
* Supports aliases from the product prompt while preserving existing env
|
|
2387
|
+
* fallback behavior.
|
|
2388
|
+
*/
|
|
2389
|
+
static resolveUniversal(hostConfig, env = process.env) {
|
|
2390
|
+
var _a;
|
|
2391
|
+
if (!hostConfig) return this.resolve(void 0, env);
|
|
2392
|
+
const normalized = __spreadProps(__spreadValues({}, hostConfig), {
|
|
2393
|
+
vectorDb: (_a = hostConfig.vectorDb) != null ? _a : hostConfig.vectorDatabase,
|
|
2394
|
+
rag: this.mergeRetrievalWorkflow(hostConfig.rag, hostConfig.retrieval, hostConfig.workflow)
|
|
2395
|
+
});
|
|
2396
|
+
return this.resolve(normalized, env);
|
|
2397
|
+
}
|
|
2301
2398
|
/**
|
|
2302
2399
|
* Validates the configuration for required fields.
|
|
2303
2400
|
*/
|
|
2304
2401
|
static validate(config) {
|
|
2305
2402
|
if (!config.projectId) {
|
|
2306
|
-
throw new
|
|
2403
|
+
throw new ConfigurationException("[ConfigResolver] projectId is required");
|
|
2307
2404
|
}
|
|
2308
2405
|
if (!config.vectorDb.provider) {
|
|
2309
|
-
throw new
|
|
2406
|
+
throw new ConfigurationException("[ConfigResolver] vectorDb.provider is required");
|
|
2310
2407
|
}
|
|
2311
2408
|
if (!config.llm.provider) {
|
|
2312
|
-
throw new
|
|
2409
|
+
throw new ConfigurationException("[ConfigResolver] llm.provider is required");
|
|
2313
2410
|
}
|
|
2314
2411
|
}
|
|
2412
|
+
static mergeRetrievalWorkflow(rag, retrieval, workflow) {
|
|
2413
|
+
var _a, _b, _c, _d, _e;
|
|
2414
|
+
if (!rag && !retrieval && !workflow) return void 0;
|
|
2415
|
+
const normalized = __spreadValues({}, rag != null ? rag : {});
|
|
2416
|
+
if (retrieval) {
|
|
2417
|
+
normalized.topK = (_a = retrieval.topK) != null ? _a : normalized.topK;
|
|
2418
|
+
normalized.scoreThreshold = (_b = retrieval.scoreThreshold) != null ? _b : normalized.scoreThreshold;
|
|
2419
|
+
normalized.useReranking = (_c = retrieval.useReranking) != null ? _c : normalized.useReranking;
|
|
2420
|
+
if (retrieval.strategy === "hybrid") normalized.architecture = (_d = normalized.architecture) != null ? _d : "hybrid";
|
|
2421
|
+
if (retrieval.strategy === "agentic") normalized.architecture = "agentic";
|
|
2422
|
+
if (retrieval.strategy === "contextual-compression") normalized.useReranking = true;
|
|
2423
|
+
}
|
|
2424
|
+
if (workflow == null ? void 0 : workflow.type) {
|
|
2425
|
+
const type = workflow.type;
|
|
2426
|
+
if (type === "agentic" || type === "agentic-rag") normalized.architecture = "agentic";
|
|
2427
|
+
else if (type === "hybrid-rag") normalized.architecture = "hybrid";
|
|
2428
|
+
else if (type === "graph-rag") {
|
|
2429
|
+
normalized.architecture = "graph";
|
|
2430
|
+
normalized.useGraphRetrieval = true;
|
|
2431
|
+
} else if (type === "simple-rag" || type === "rag") {
|
|
2432
|
+
normalized.architecture = (_e = normalized.architecture) != null ? _e : "simple";
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return normalized;
|
|
2436
|
+
}
|
|
2315
2437
|
};
|
|
2316
2438
|
|
|
2317
2439
|
// src/llm/providers/OpenAIProvider.ts
|
|
2318
2440
|
var import_openai = __toESM(require("openai"));
|
|
2441
|
+
|
|
2442
|
+
// src/llm/utils.ts
|
|
2443
|
+
function buildSystemContent(systemPrompt, context) {
|
|
2444
|
+
const noContext = !context || context.trim() === "" || context.trim() === "No relevant context found.";
|
|
2445
|
+
if (systemPrompt.includes("{{context}}")) {
|
|
2446
|
+
return systemPrompt.replace("{{context}}", noContext ? "" : context);
|
|
2447
|
+
}
|
|
2448
|
+
if (noContext) {
|
|
2449
|
+
return systemPrompt;
|
|
2450
|
+
}
|
|
2451
|
+
return `${systemPrompt}
|
|
2452
|
+
|
|
2453
|
+
---
|
|
2454
|
+
Context:
|
|
2455
|
+
${context}`;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
// src/llm/providers/OpenAIProvider.ts
|
|
2319
2459
|
var OpenAIProvider = class {
|
|
2320
2460
|
constructor(llmConfig, embeddingConfig) {
|
|
2321
2461
|
if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
|
|
@@ -2379,16 +2519,10 @@ var OpenAIProvider = class {
|
|
|
2379
2519
|
}
|
|
2380
2520
|
async chat(messages, context, options) {
|
|
2381
2521
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2382
|
-
const
|
|
2383
|
-
|
|
2384
|
-
Context:
|
|
2385
|
-
${context}`;
|
|
2522
|
+
const basePrompt = (_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.";
|
|
2386
2523
|
const systemMessage = {
|
|
2387
2524
|
role: "system",
|
|
2388
|
-
content:
|
|
2389
|
-
|
|
2390
|
-
Context:
|
|
2391
|
-
${context}`
|
|
2525
|
+
content: buildSystemContent(basePrompt, context)
|
|
2392
2526
|
};
|
|
2393
2527
|
const formattedMessages = [
|
|
2394
2528
|
systemMessage,
|
|
@@ -2409,16 +2543,10 @@ ${context}`
|
|
|
2409
2543
|
chatStream(messages, context, options) {
|
|
2410
2544
|
return __asyncGenerator(this, null, function* () {
|
|
2411
2545
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2412
|
-
const
|
|
2413
|
-
|
|
2414
|
-
Context:
|
|
2415
|
-
${context}`;
|
|
2546
|
+
const basePrompt = (_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.";
|
|
2416
2547
|
const systemMessage = {
|
|
2417
2548
|
role: "system",
|
|
2418
|
-
content:
|
|
2419
|
-
|
|
2420
|
-
Context:
|
|
2421
|
-
${context}`
|
|
2549
|
+
content: buildSystemContent(basePrompt, context)
|
|
2422
2550
|
};
|
|
2423
2551
|
const formattedMessages = [
|
|
2424
2552
|
systemMessage,
|
|
@@ -2539,58 +2667,87 @@ var AnthropicProvider = class {
|
|
|
2539
2667
|
};
|
|
2540
2668
|
}
|
|
2541
2669
|
async chat(messages, context, options) {
|
|
2542
|
-
var _a, _b, _c, _d;
|
|
2543
|
-
const
|
|
2544
|
-
|
|
2545
|
-
Context:
|
|
2546
|
-
${context}`;
|
|
2547
|
-
const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
|
|
2548
|
-
|
|
2549
|
-
Context:
|
|
2550
|
-
${context}`;
|
|
2670
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2671
|
+
const basePrompt = (_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.";
|
|
2672
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2551
2673
|
const anthropicMessages = messages.map((m) => ({
|
|
2552
2674
|
role: m.role === "assistant" ? "assistant" : "user",
|
|
2553
2675
|
content: m.content
|
|
2554
2676
|
}));
|
|
2555
|
-
const
|
|
2677
|
+
const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
|
|
2678
|
+
const isThinkingEnabled = ((_c = this.llmConfig.options) == null ? void 0 : _c.thinking) === true || ((_d = this.llmConfig.options) == null ? void 0 : _d.thinking) === "enabled" || isClaude37 && ((_e = this.llmConfig.options) == null ? void 0 : _e.thinking) !== false;
|
|
2679
|
+
const extraParams = {};
|
|
2680
|
+
if (isThinkingEnabled && isClaude37) {
|
|
2681
|
+
extraParams.betas = ["interleaved-thinking-2025-05-14"];
|
|
2682
|
+
const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
|
|
2683
|
+
const budget = Math.min(
|
|
2684
|
+
typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
|
|
2685
|
+
maxTokens - 1
|
|
2686
|
+
);
|
|
2687
|
+
extraParams.thinking = {
|
|
2688
|
+
type: "enabled",
|
|
2689
|
+
budget_tokens: budget
|
|
2690
|
+
};
|
|
2691
|
+
extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
|
|
2692
|
+
}
|
|
2693
|
+
const response = await this.client.messages.create(__spreadValues({
|
|
2556
2694
|
model: this.llmConfig.model,
|
|
2557
|
-
max_tokens: (
|
|
2695
|
+
max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
|
|
2558
2696
|
system,
|
|
2559
2697
|
messages: anthropicMessages
|
|
2560
|
-
});
|
|
2561
|
-
|
|
2562
|
-
|
|
2698
|
+
}, extraParams));
|
|
2699
|
+
let reply = "";
|
|
2700
|
+
for (const block of response.content) {
|
|
2701
|
+
if (block.type === "text") {
|
|
2702
|
+
reply += block.text;
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
return reply;
|
|
2563
2706
|
}
|
|
2564
2707
|
chatStream(messages, context, options) {
|
|
2565
2708
|
return __asyncGenerator(this, null, function* () {
|
|
2566
|
-
var _a, _b, _c, _d;
|
|
2567
|
-
const
|
|
2568
|
-
|
|
2569
|
-
Context:
|
|
2570
|
-
${context}`;
|
|
2571
|
-
const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
|
|
2572
|
-
|
|
2573
|
-
Context:
|
|
2574
|
-
${context}`;
|
|
2709
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2710
|
+
const basePrompt = (_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.";
|
|
2711
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2575
2712
|
const anthropicMessages = messages.map((m) => ({
|
|
2576
2713
|
role: m.role === "assistant" ? "assistant" : "user",
|
|
2577
2714
|
content: m.content
|
|
2578
2715
|
}));
|
|
2579
|
-
const
|
|
2716
|
+
const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
|
|
2717
|
+
const isThinkingEnabled = ((_c = this.llmConfig.options) == null ? void 0 : _c.thinking) === true || ((_d = this.llmConfig.options) == null ? void 0 : _d.thinking) === "enabled" || isClaude37 && ((_e = this.llmConfig.options) == null ? void 0 : _e.thinking) !== false;
|
|
2718
|
+
const extraParams = {};
|
|
2719
|
+
if (isThinkingEnabled && isClaude37) {
|
|
2720
|
+
extraParams.betas = ["interleaved-thinking-2025-05-14"];
|
|
2721
|
+
const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
|
|
2722
|
+
const budget = Math.min(
|
|
2723
|
+
typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
|
|
2724
|
+
maxTokens - 1
|
|
2725
|
+
);
|
|
2726
|
+
extraParams.thinking = {
|
|
2727
|
+
type: "enabled",
|
|
2728
|
+
budget_tokens: budget
|
|
2729
|
+
};
|
|
2730
|
+
extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
|
|
2731
|
+
}
|
|
2732
|
+
const stream = yield new __await(this.client.messages.create(__spreadValues({
|
|
2580
2733
|
model: this.llmConfig.model,
|
|
2581
|
-
max_tokens: (
|
|
2734
|
+
max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
|
|
2582
2735
|
system,
|
|
2583
2736
|
messages: anthropicMessages,
|
|
2584
2737
|
stream: true
|
|
2585
|
-
}));
|
|
2738
|
+
}, extraParams)));
|
|
2586
2739
|
if (!stream) {
|
|
2587
2740
|
throw new Error("[AnthropicProvider] messages.create stream is undefined");
|
|
2588
2741
|
}
|
|
2589
2742
|
try {
|
|
2590
2743
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2591
2744
|
const chunk = temp.value;
|
|
2592
|
-
if (chunk.type === "content_block_delta"
|
|
2593
|
-
|
|
2745
|
+
if (chunk.type === "content_block_delta") {
|
|
2746
|
+
if (chunk.delta.type === "text_delta") {
|
|
2747
|
+
yield chunk.delta.text;
|
|
2748
|
+
} else if (chunk.delta.type === "thinking_delta") {
|
|
2749
|
+
yield `\0THINKING\0${chunk.delta.thinking}`;
|
|
2750
|
+
}
|
|
2594
2751
|
}
|
|
2595
2752
|
}
|
|
2596
2753
|
} catch (temp) {
|
|
@@ -2634,9 +2791,10 @@ ${context}`;
|
|
|
2634
2791
|
var import_axios = __toESM(require("axios"));
|
|
2635
2792
|
var OllamaProvider = class {
|
|
2636
2793
|
constructor(llmConfig, embeddingConfig) {
|
|
2637
|
-
var _a;
|
|
2794
|
+
var _a, _b;
|
|
2638
2795
|
const baseURL = (_a = llmConfig.baseUrl) != null ? _a : "http://localhost:11434";
|
|
2639
|
-
|
|
2796
|
+
const timeout = Number((_b = llmConfig.options) == null ? void 0 : _b.timeout) || 3e5;
|
|
2797
|
+
this.http = import_axios.default.create({ baseURL, timeout });
|
|
2640
2798
|
this.llmConfig = llmConfig;
|
|
2641
2799
|
this.embeddingConfig = embeddingConfig;
|
|
2642
2800
|
}
|
|
@@ -2692,14 +2850,15 @@ var OllamaProvider = class {
|
|
|
2692
2850
|
};
|
|
2693
2851
|
}
|
|
2694
2852
|
async chat(messages, context, options) {
|
|
2695
|
-
var _a, _b, _c, _d;
|
|
2696
|
-
const
|
|
2853
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2854
|
+
const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Use the provided context to answer the user's question.";
|
|
2855
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2697
2856
|
const { data } = await this.http.post("/api/chat", {
|
|
2698
2857
|
model: this.llmConfig.model,
|
|
2699
2858
|
stream: false,
|
|
2700
2859
|
options: {
|
|
2701
|
-
temperature: (
|
|
2702
|
-
num_predict: (
|
|
2860
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
|
|
2861
|
+
num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
|
|
2703
2862
|
},
|
|
2704
2863
|
messages: [
|
|
2705
2864
|
{ role: "system", content: system },
|
|
@@ -2710,14 +2869,15 @@ var OllamaProvider = class {
|
|
|
2710
2869
|
}
|
|
2711
2870
|
chatStream(messages, context, options) {
|
|
2712
2871
|
return __asyncGenerator(this, null, function* () {
|
|
2713
|
-
var _a, _b, _c, _d, _e, _f;
|
|
2714
|
-
const
|
|
2872
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2873
|
+
const basePrompt = (_b = (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : this.llmConfig.systemPrompt) != null ? _b : "You are a helpful assistant. Use the provided context to answer the user's question.";
|
|
2874
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2715
2875
|
const response = yield new __await(this.http.post("/api/chat", {
|
|
2716
2876
|
model: this.llmConfig.model,
|
|
2717
2877
|
stream: true,
|
|
2718
2878
|
options: {
|
|
2719
|
-
temperature: (
|
|
2720
|
-
num_predict: (
|
|
2879
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
|
|
2880
|
+
num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
|
|
2721
2881
|
},
|
|
2722
2882
|
messages: [
|
|
2723
2883
|
{ role: "system", content: system },
|
|
@@ -2739,7 +2899,7 @@ var OllamaProvider = class {
|
|
|
2739
2899
|
if (!line.trim()) continue;
|
|
2740
2900
|
try {
|
|
2741
2901
|
const json = JSON.parse(line);
|
|
2742
|
-
if ((
|
|
2902
|
+
if ((_g = json.message) == null ? void 0 : _g.content) {
|
|
2743
2903
|
yield json.message.content;
|
|
2744
2904
|
}
|
|
2745
2905
|
if (json.done) return;
|
|
@@ -2761,26 +2921,12 @@ var OllamaProvider = class {
|
|
|
2761
2921
|
if (lineBuffer.trim()) {
|
|
2762
2922
|
try {
|
|
2763
2923
|
const json = JSON.parse(lineBuffer);
|
|
2764
|
-
if ((
|
|
2924
|
+
if ((_h = json.message) == null ? void 0 : _h.content) yield json.message.content;
|
|
2765
2925
|
} catch (e) {
|
|
2766
2926
|
}
|
|
2767
2927
|
}
|
|
2768
2928
|
});
|
|
2769
2929
|
}
|
|
2770
|
-
buildSystemPrompt(context, overridePrompt) {
|
|
2771
|
-
var _a;
|
|
2772
|
-
if (overridePrompt) {
|
|
2773
|
-
return overridePrompt;
|
|
2774
|
-
}
|
|
2775
|
-
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
|
|
2776
|
-
|
|
2777
|
-
Context:
|
|
2778
|
-
${context}`;
|
|
2779
|
-
return systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2780
|
-
|
|
2781
|
-
Context:
|
|
2782
|
-
${context}`;
|
|
2783
|
-
}
|
|
2784
2930
|
async embed(text, options) {
|
|
2785
2931
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2786
2932
|
const model = (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "nomic-embed-text";
|
|
@@ -2927,21 +3073,6 @@ var GeminiProvider = class {
|
|
|
2927
3073
|
var _a, _b;
|
|
2928
3074
|
return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
|
|
2929
3075
|
}
|
|
2930
|
-
/**
|
|
2931
|
-
* Build the system instruction string, inserting the RAG context either via
|
|
2932
|
-
* the `{{context}}` placeholder or by appending it.
|
|
2933
|
-
*/
|
|
2934
|
-
buildSystemInstruction(context) {
|
|
2935
|
-
var _a;
|
|
2936
|
-
const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
2937
|
-
|
|
2938
|
-
Context:
|
|
2939
|
-
${context}`;
|
|
2940
|
-
return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
|
|
2941
|
-
|
|
2942
|
-
Context:
|
|
2943
|
-
${context}`;
|
|
2944
|
-
}
|
|
2945
3076
|
/**
|
|
2946
3077
|
* Convert ChatMessage[] to the Gemini contents format.
|
|
2947
3078
|
*
|
|
@@ -2960,16 +3091,17 @@ ${context}`;
|
|
|
2960
3091
|
// ILLMProvider — chat
|
|
2961
3092
|
// -------------------------------------------------------------------------
|
|
2962
3093
|
async chat(messages, context, options) {
|
|
2963
|
-
var _a, _b, _c, _d;
|
|
3094
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3095
|
+
const basePrompt = (_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.";
|
|
2964
3096
|
const model = this.client.getGenerativeModel({
|
|
2965
3097
|
model: this.llmConfig.model,
|
|
2966
|
-
systemInstruction:
|
|
3098
|
+
systemInstruction: buildSystemContent(basePrompt, context)
|
|
2967
3099
|
});
|
|
2968
3100
|
const result = await model.generateContent({
|
|
2969
3101
|
contents: this.buildGeminiContents(messages),
|
|
2970
3102
|
generationConfig: {
|
|
2971
|
-
temperature: (
|
|
2972
|
-
maxOutputTokens: (
|
|
3103
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
|
|
3104
|
+
maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
|
|
2973
3105
|
stopSequences: options == null ? void 0 : options.stop
|
|
2974
3106
|
}
|
|
2975
3107
|
});
|
|
@@ -2977,16 +3109,17 @@ ${context}`;
|
|
|
2977
3109
|
}
|
|
2978
3110
|
chatStream(messages, context, options) {
|
|
2979
3111
|
return __asyncGenerator(this, null, function* () {
|
|
2980
|
-
var _a, _b, _c, _d;
|
|
3112
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3113
|
+
const basePrompt = (_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.";
|
|
2981
3114
|
const model = this.client.getGenerativeModel({
|
|
2982
3115
|
model: this.llmConfig.model,
|
|
2983
|
-
systemInstruction:
|
|
3116
|
+
systemInstruction: buildSystemContent(basePrompt, context)
|
|
2984
3117
|
});
|
|
2985
3118
|
const result = yield new __await(model.generateContentStream({
|
|
2986
3119
|
contents: this.buildGeminiContents(messages),
|
|
2987
3120
|
generationConfig: {
|
|
2988
|
-
temperature: (
|
|
2989
|
-
maxOutputTokens: (
|
|
3121
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
|
|
3122
|
+
maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
|
|
2990
3123
|
stopSequences: options == null ? void 0 : options.stop
|
|
2991
3124
|
}
|
|
2992
3125
|
}));
|
|
@@ -3382,7 +3515,9 @@ var LLMFactory = class _LLMFactory {
|
|
|
3382
3515
|
if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
|
|
3383
3516
|
return new UniversalLLMAdapter(llmConfig);
|
|
3384
3517
|
}
|
|
3385
|
-
throw new
|
|
3518
|
+
throw new ProviderNotFoundException(
|
|
3519
|
+
"LLM",
|
|
3520
|
+
String(llmConfig.provider),
|
|
3386
3521
|
`[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
|
|
3387
3522
|
);
|
|
3388
3523
|
}
|
|
@@ -3514,7 +3649,7 @@ var ProviderRegistry = class {
|
|
|
3514
3649
|
return UniversalVectorProvider2;
|
|
3515
3650
|
}
|
|
3516
3651
|
default:
|
|
3517
|
-
throw new
|
|
3652
|
+
throw new ProviderNotFoundException("vector", provider);
|
|
3518
3653
|
}
|
|
3519
3654
|
}
|
|
3520
3655
|
static async createVectorProvider(config) {
|
|
@@ -3532,12 +3667,18 @@ var ProviderRegistry = class {
|
|
|
3532
3667
|
return new SimpleGraphProvider2(config);
|
|
3533
3668
|
}
|
|
3534
3669
|
default:
|
|
3535
|
-
throw new
|
|
3670
|
+
throw new ProviderNotFoundException("graph", provider);
|
|
3536
3671
|
}
|
|
3537
3672
|
}
|
|
3538
3673
|
static createLLMProvider(llmConfig, embeddingConfig) {
|
|
3539
3674
|
return LLMFactory.create(llmConfig, embeddingConfig);
|
|
3540
3675
|
}
|
|
3676
|
+
static registerLLMProvider(name, factory) {
|
|
3677
|
+
LLMFactory.register(name, factory);
|
|
3678
|
+
}
|
|
3679
|
+
static createEmbeddingProvider(embeddingConfig) {
|
|
3680
|
+
return LLMFactory.createEmbeddingProvider(embeddingConfig);
|
|
3681
|
+
}
|
|
3541
3682
|
};
|
|
3542
3683
|
ProviderRegistry.vectorProviders = {};
|
|
3543
3684
|
ProviderRegistry.graphProviders = {};
|
|
@@ -3684,8 +3825,8 @@ var ConfigValidator = class {
|
|
|
3684
3825
|
const errorItems = errors.filter((e) => e.severity === "error");
|
|
3685
3826
|
if (errorItems.length > 0) {
|
|
3686
3827
|
const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
|
|
3687
|
-
throw new
|
|
3688
|
-
${message}
|
|
3828
|
+
throw new ConfigurationException(`[ConfigValidator] Configuration validation failed:
|
|
3829
|
+
${message}`, errorItems);
|
|
3689
3830
|
}
|
|
3690
3831
|
}
|
|
3691
3832
|
};
|
|
@@ -3872,20 +4013,17 @@ var Reranker = class {
|
|
|
3872
4013
|
}
|
|
3873
4014
|
try {
|
|
3874
4015
|
const topN = matches.slice(0, 10);
|
|
3875
|
-
const
|
|
3876
|
-
|
|
3877
|
-
Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
|
|
3878
|
-
Use the indices provided in brackets like [0], [1], etc.
|
|
3879
|
-
Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
|
|
3880
|
-
|
|
3881
|
-
Query: "${query}"
|
|
4016
|
+
const response = await llm.chat(
|
|
4017
|
+
[{ role: "user", content: `Query: "${query}"
|
|
3882
4018
|
|
|
3883
4019
|
Documents:
|
|
3884
|
-
${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}
|
|
3885
|
-
const response = await llm.chat(
|
|
3886
|
-
[{ role: "user", content: prompt }],
|
|
4020
|
+
${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}` }],
|
|
3887
4021
|
"",
|
|
3888
|
-
{
|
|
4022
|
+
{
|
|
4023
|
+
systemPrompt: 'You are a relevance ranking expert. Given the user query and a list of retrieved document chunks, rank the chunks by relevance to the query. Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant. Use the indices provided in brackets like [0], [1], etc. Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.',
|
|
4024
|
+
temperature: 0,
|
|
4025
|
+
maxTokens: 50
|
|
4026
|
+
}
|
|
3889
4027
|
);
|
|
3890
4028
|
const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
|
|
3891
4029
|
const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
|
|
@@ -4391,6 +4529,18 @@ var QueryProcessor = class {
|
|
|
4391
4529
|
}, normalizedField ? { field: normalizedField } : {}));
|
|
4392
4530
|
}
|
|
4393
4531
|
};
|
|
4532
|
+
const activeValidFields = validFields.length > 0 ? validFields : ["brand", "category", "price", "rating", "stock", "stock_quantity", "status", "name", "title"];
|
|
4533
|
+
const resolveValidField = (fieldStr) => {
|
|
4534
|
+
const cleaned = this.normalizeHintValue(fieldStr).replace(/\b(?:provide|show|get|give|list|all|the|a|an|of|organizations?|companies?|records?|items?|whose|with|having|where|that|which|have|has|is|are|was|were)\b/gi, " ").replace(/\s+/g, " ").trim();
|
|
4535
|
+
const comparable = (v) => v.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4536
|
+
const cleanedComparable = comparable(cleaned);
|
|
4537
|
+
if (!cleanedComparable) return null;
|
|
4538
|
+
const matchedField = activeValidFields.find((fieldName) => {
|
|
4539
|
+
const candidate = comparable(fieldName);
|
|
4540
|
+
return candidate === cleanedComparable || candidate.includes(cleanedComparable) || cleanedComparable.includes(candidate);
|
|
4541
|
+
});
|
|
4542
|
+
return matchedField != null ? matchedField : null;
|
|
4543
|
+
};
|
|
4394
4544
|
for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
|
|
4395
4545
|
addHint(match[1]);
|
|
4396
4546
|
}
|
|
@@ -4415,7 +4565,13 @@ var QueryProcessor = class {
|
|
|
4415
4565
|
const field = match[1];
|
|
4416
4566
|
const value = match[2];
|
|
4417
4567
|
if (field && !this.isLikelyPromptPhrase(field)) {
|
|
4418
|
-
|
|
4568
|
+
const resolvedField = resolveValidField(field);
|
|
4569
|
+
if (resolvedField) {
|
|
4570
|
+
addHint(value, resolvedField);
|
|
4571
|
+
} else {
|
|
4572
|
+
addHint(value);
|
|
4573
|
+
addHint(field);
|
|
4574
|
+
}
|
|
4419
4575
|
}
|
|
4420
4576
|
}
|
|
4421
4577
|
if (validFields.length > 0) {
|
|
@@ -4461,12 +4617,16 @@ var QueryProcessor = class {
|
|
|
4461
4617
|
];
|
|
4462
4618
|
for (const pattern of fieldValuePatterns) {
|
|
4463
4619
|
for (const match of question.matchAll(pattern)) {
|
|
4464
|
-
const field =
|
|
4620
|
+
const field = (_c = match[1]) != null ? _c : "";
|
|
4465
4621
|
const value = (_d = match[2]) != null ? _d : "";
|
|
4466
|
-
|
|
4467
|
-
|
|
4622
|
+
const resolvedField = resolveValidField(field);
|
|
4623
|
+
if (resolvedField) {
|
|
4624
|
+
addHint(value, resolvedField);
|
|
4468
4625
|
} else {
|
|
4469
4626
|
addHint(value);
|
|
4627
|
+
if (field && !this.isLikelyPromptPhrase(field)) {
|
|
4628
|
+
addHint(field);
|
|
4629
|
+
}
|
|
4470
4630
|
}
|
|
4471
4631
|
}
|
|
4472
4632
|
}
|
|
@@ -4693,7 +4853,7 @@ var LLMRouter = class {
|
|
|
4693
4853
|
|
|
4694
4854
|
// src/utils/UITransformer.ts
|
|
4695
4855
|
init_synonyms();
|
|
4696
|
-
var UITransformer = class {
|
|
4856
|
+
var UITransformer = class _UITransformer {
|
|
4697
4857
|
// ─── Public Entry Points ─────────────────────────────────────────────────
|
|
4698
4858
|
/**
|
|
4699
4859
|
* Heuristic-only transform (no LLM required).
|
|
@@ -4755,7 +4915,7 @@ var UITransformer = class {
|
|
|
4755
4915
|
static async analyzeAndDecide(query, sources, llm) {
|
|
4756
4916
|
let intent;
|
|
4757
4917
|
try {
|
|
4758
|
-
intent = await this.detectIntent(query, llm);
|
|
4918
|
+
intent = await this.detectIntent(query, llm, sources);
|
|
4759
4919
|
console.debug("[UITransformer] Detected intent:", intent);
|
|
4760
4920
|
} catch (err) {
|
|
4761
4921
|
console.warn("[UITransformer] Intent detection failed; using heuristic.", err);
|
|
@@ -4773,11 +4933,23 @@ var UITransformer = class {
|
|
|
4773
4933
|
try {
|
|
4774
4934
|
const context = this.buildContextSummary(sources);
|
|
4775
4935
|
const systemPrompt = this.buildVisualizationSystemPrompt();
|
|
4936
|
+
const profile = this.profileData(sources);
|
|
4937
|
+
const schemaContext = `
|
|
4938
|
+
RETRIEVED DATA SCHEMA:
|
|
4939
|
+
- Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4940
|
+
- Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4941
|
+
- Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4942
|
+
- Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4943
|
+
- Text/Other fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
|
|
4944
|
+
`;
|
|
4776
4945
|
const userPrompt = [
|
|
4777
4946
|
`USER QUESTION: ${query}`,
|
|
4778
4947
|
"",
|
|
4779
4948
|
`DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
|
|
4780
4949
|
"",
|
|
4950
|
+
`RETRIEVED DATA SCHEMA:`,
|
|
4951
|
+
schemaContext,
|
|
4952
|
+
"",
|
|
4781
4953
|
"RETRIEVED DATA (JSON):",
|
|
4782
4954
|
context
|
|
4783
4955
|
].join("\n");
|
|
@@ -4788,6 +4960,20 @@ var UITransformer = class {
|
|
|
4788
4960
|
);
|
|
4789
4961
|
const parsed = this.parseTransformationResponse(rawResponse);
|
|
4790
4962
|
if (parsed) {
|
|
4963
|
+
let isCompatible = true;
|
|
4964
|
+
if (parsed.type === "line_chart" && (profile.dateFields.length === 0 || profile.numericFields.length === 0)) {
|
|
4965
|
+
isCompatible = false;
|
|
4966
|
+
} else if (["bar_chart", "horizontal_bar", "pie_chart", "donut_chart"].includes(parsed.type) && (profile.categoricalFields.length === 0 && profile.numericFields.length === 0)) {
|
|
4967
|
+
isCompatible = false;
|
|
4968
|
+
} else if (parsed.type === "scatter_plot" && profile.numericFields.length < 2) {
|
|
4969
|
+
isCompatible = false;
|
|
4970
|
+
} else if (parsed.type === "metric_card" && profile.numericFields.length === 0) {
|
|
4971
|
+
isCompatible = false;
|
|
4972
|
+
}
|
|
4973
|
+
if (!isCompatible) {
|
|
4974
|
+
console.warn(`[UITransformer] LLM chose incompatible visualization type "${parsed.type}" for retrieved schema. Falling back to heuristic.`);
|
|
4975
|
+
return this.transform(query, sources, void 0, void 0, intent);
|
|
4976
|
+
}
|
|
4791
4977
|
const intentAllowsTable = intent.wantsExplicitTable || ["tabular", "table", "geographic"].includes(intent.visualizationHint);
|
|
4792
4978
|
const intentWantsPieLikeChart = ["pie_chart", "donut_chart"].includes(intent.recommendedChart) || ["composition", "category_breakdown"].includes(intent.visualizationHint);
|
|
4793
4979
|
if (parsed.type === "table" && !intentAllowsTable) {
|
|
@@ -4817,7 +5003,26 @@ var UITransformer = class {
|
|
|
4817
5003
|
* - The intent object can be reused across both the heuristic and LLM paths.
|
|
4818
5004
|
* - It is easy to unit-test intent detection in isolation.
|
|
4819
5005
|
*/
|
|
4820
|
-
static async detectIntent(query, llm) {
|
|
5006
|
+
static async detectIntent(query, llm, sources) {
|
|
5007
|
+
let schemaProfileText = "";
|
|
5008
|
+
if (sources && sources.length > 0) {
|
|
5009
|
+
const profile = this.profileData(sources);
|
|
5010
|
+
const hasProducts = sources.some((item) => this.isProductData(item));
|
|
5011
|
+
schemaProfileText = `
|
|
5012
|
+
RETRIEVED DATA SCHEMA:
|
|
5013
|
+
- Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
5014
|
+
- Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
5015
|
+
- Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
5016
|
+
- Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
5017
|
+
- Text fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
|
|
5018
|
+
|
|
5019
|
+
Please choose visualizationHint and recommendedChart dynamically based on the available schema.
|
|
5020
|
+
- Do NOT recommend "trend" or "line_chart" if there are no Date fields.
|
|
5021
|
+
- Do NOT recommend numeric-dependent visualizations (like bar_chart, line_chart, pie_chart, scatter_plot, histogram, metric_card) if there are no Numeric fields. Fall back to tabular/table or text.
|
|
5022
|
+
- If there are multiple metadata fields but no clear categories or numbers, "tabular"/"table" is a highly accurate default.
|
|
5023
|
+
${hasProducts ? `- Note: The retrieved data matches product/catalog structure. If the user query is looking for, exploring, searching, browsing, or listing products/items, you MUST recommend visualizationHint: "product_browse" and recommendedChart: "text".` : ""}
|
|
5024
|
+
`;
|
|
5025
|
+
}
|
|
4821
5026
|
const systemPrompt = `You are an intent classifier for a product-search RAG system.
|
|
4822
5027
|
Given a user query, return ONLY a valid JSON object with this exact shape \u2014 no prose, no markdown:
|
|
4823
5028
|
|
|
@@ -4853,8 +5058,11 @@ RULES:
|
|
|
4853
5058
|
- isTemporal: true if time words appear (trend, historical, over time, last year, monthly, etc.)
|
|
4854
5059
|
- isComparison: true if user compares, ranks, or contrasts entities or categories.
|
|
4855
5060
|
- language: detect from the query text itself; default "en" if uncertain.`;
|
|
5061
|
+
const userPrompt = `QUERY: ${query}${schemaProfileText ? `
|
|
5062
|
+
|
|
5063
|
+
${schemaProfileText}` : ""}`;
|
|
4856
5064
|
const rawResponse = await llm.chat(
|
|
4857
|
-
[{ role: "user", content:
|
|
5065
|
+
[{ role: "user", content: userPrompt }],
|
|
4858
5066
|
"",
|
|
4859
5067
|
{ systemPrompt, temperature: 0 }
|
|
4860
5068
|
);
|
|
@@ -5297,10 +5505,21 @@ RULES:
|
|
|
5297
5505
|
const hasEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
|
|
5298
5506
|
return asksForRecords && (hasNumericPredicate || hasEntityTerms);
|
|
5299
5507
|
}
|
|
5508
|
+
/** @internal kept private for transform() internal logic */
|
|
5300
5509
|
static isProductQuery(query) {
|
|
5510
|
+
return _UITransformer._productQueryTest(query);
|
|
5511
|
+
}
|
|
5512
|
+
/**
|
|
5513
|
+
* Public entry-point for external callers (e.g. Pipeline)
|
|
5514
|
+
* to check whether a query maps to product_browse without triggering an LLM call.
|
|
5515
|
+
*/
|
|
5516
|
+
static isProductQueryPublic(query) {
|
|
5517
|
+
return _UITransformer._productQueryTest(query);
|
|
5518
|
+
}
|
|
5519
|
+
static _productQueryTest(query) {
|
|
5301
5520
|
const q = query.toLowerCase();
|
|
5302
5521
|
const productTerms = /\b(product|products|item|items|sku|catalog|catalogue|price|prices|brand|model|stock|inventory|in stock|out of stock|buy|shop)\b/.test(q);
|
|
5303
|
-
const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list)\b/.test(q);
|
|
5522
|
+
const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list|explore|exploring)\b/.test(q);
|
|
5304
5523
|
const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
|
|
5305
5524
|
return productTerms && productAction && !nonProductEntityTerms;
|
|
5306
5525
|
}
|
|
@@ -6073,23 +6292,19 @@ function estimateCostUsd(promptTokens, completionTokens, model) {
|
|
|
6073
6292
|
async function scoreHallucination(llm, answer, context) {
|
|
6074
6293
|
const maxContextChars = 3e3;
|
|
6075
6294
|
const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
CONTEXT:
|
|
6295
|
+
try {
|
|
6296
|
+
const raw = await llm.chat(
|
|
6297
|
+
[{ role: "user", content: `CONTEXT:
|
|
6079
6298
|
${truncatedContext}
|
|
6080
6299
|
|
|
6081
6300
|
ANSWER:
|
|
6082
|
-
${answer}
|
|
6083
|
-
|
|
6084
|
-
Return ONLY a valid JSON object with no markdown fences:
|
|
6085
|
-
{"score": <float 0-1>, "reason": "<one sentence>"}
|
|
6086
|
-
|
|
6087
|
-
Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
|
|
6088
|
-
try {
|
|
6089
|
-
const raw = await llm.chat(
|
|
6090
|
-
[{ role: "user", content: prompt }],
|
|
6301
|
+
${answer}` }],
|
|
6091
6302
|
"",
|
|
6092
|
-
{
|
|
6303
|
+
{
|
|
6304
|
+
systemPrompt: 'You are an AI quality checker. Given the CONTEXT retrieved from a knowledge base and the ANSWER generated from it, rate how well-grounded the answer is. Return ONLY a valid JSON object with no markdown fences: {"score": <float 0-1>, "reason": "<one sentence>"} where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.',
|
|
6305
|
+
temperature: 0,
|
|
6306
|
+
maxTokens: 120
|
|
6307
|
+
}
|
|
6093
6308
|
);
|
|
6094
6309
|
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
6095
6310
|
if (!jsonMatch) return void 0;
|
|
@@ -6149,7 +6364,10 @@ var Pipeline = class {
|
|
|
6149
6364
|
- For product descriptions, summarize customer-facing details only. Do NOT list internal catalog fields such as Handle, Body (HTML), Vendor, Type, Tags, Published, Option, Variant, SKU, or raw metadata labels.
|
|
6150
6365
|
- Do NOT try to format product lists as tables.
|
|
6151
6366
|
`;
|
|
6152
|
-
this.config.llm.systemPrompt
|
|
6367
|
+
const userPromptPrefix = this.config.llm.systemPrompt ? `${this.config.llm.systemPrompt}
|
|
6368
|
+
|
|
6369
|
+
` : "";
|
|
6370
|
+
this.config.llm.systemPrompt = `${userPromptPrefix}${chartInstruction}`;
|
|
6153
6371
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
6154
6372
|
this.llmRouter = new LLMRouter(this.config);
|
|
6155
6373
|
const { llmProvider: resolvedLLM, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
@@ -6327,11 +6545,11 @@ var Pipeline = class {
|
|
|
6327
6545
|
*/
|
|
6328
6546
|
askStream(_0) {
|
|
6329
6547
|
return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
|
|
6330
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
6548
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
6331
6549
|
yield new __await(this.initialize());
|
|
6332
6550
|
const ns = namespace != null ? namespace : this.config.projectId;
|
|
6333
6551
|
const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
|
|
6334
|
-
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
|
|
6552
|
+
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0.3;
|
|
6335
6553
|
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
6336
6554
|
const requestStart = performance.now();
|
|
6337
6555
|
try {
|
|
@@ -6350,12 +6568,13 @@ var Pipeline = class {
|
|
|
6350
6568
|
const embedStart = performance.now();
|
|
6351
6569
|
const cacheKey = `${ns}::${searchQuery}`;
|
|
6352
6570
|
const cachedVector = this.embeddingCache.get(cacheKey);
|
|
6571
|
+
const useGraph = this.graphDB && ((_h = this.config.rag) == null ? void 0 : _h.useGraphRetrieval);
|
|
6353
6572
|
const [strategyResult, embeddedVector] = yield new __await(Promise.all([
|
|
6354
6573
|
QueryProcessor.determineRetrievalStrategy(
|
|
6355
6574
|
searchQuery,
|
|
6356
|
-
this.llmRouter.get("fast"),
|
|
6357
|
-
(
|
|
6358
|
-
(
|
|
6575
|
+
useGraph ? this.llmRouter.get("fast") : void 0,
|
|
6576
|
+
(_i = this.config.rag) == null ? void 0 : _i.graphKeywords,
|
|
6577
|
+
(_j = this.config.rag) == null ? void 0 : _j.vectorKeywords
|
|
6359
6578
|
),
|
|
6360
6579
|
// Embed immediately regardless of strategy — costs nothing if cached.
|
|
6361
6580
|
// If strategy turns out to be 'graph'-only we just won't use the vector.
|
|
@@ -6365,7 +6584,7 @@ var Pipeline = class {
|
|
|
6365
6584
|
if (!cachedVector && queryVector.length > 0) {
|
|
6366
6585
|
this.embeddingCache.set(cacheKey, queryVector);
|
|
6367
6586
|
}
|
|
6368
|
-
const graphData = (strategyResult === "graph" || strategyResult === "both") && this.graphDB && ((
|
|
6587
|
+
const graphData = (strategyResult === "graph" || strategyResult === "both") && this.graphDB && ((_k = this.config.rag) == null ? void 0 : _k.useGraphRetrieval) ? yield new __await(this.graphDB.query(searchQuery)) : void 0;
|
|
6369
6588
|
const hasMetadataFilter = filter.metadata && Object.keys(filter.metadata).length > 0;
|
|
6370
6589
|
const hasNumericPredicates = Array.isArray(filter.__numericPredicates) && filter.__numericPredicates.length > 0;
|
|
6371
6590
|
const wantsExhaustiveList = hasNumericPredicates || hasMetadataFilter || /\b(list|all|show|provide|give|get|browse|find|search|display)\b/i.test(question);
|
|
@@ -6378,7 +6597,7 @@ var Pipeline = class {
|
|
|
6378
6597
|
const structuredSources = this.applyStructuredFilters(rawSources, filter);
|
|
6379
6598
|
let fullSources = hasNumericPredicates ? structuredSources : structuredSources.filter((m) => m.score >= scoreThreshold);
|
|
6380
6599
|
const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
|
|
6381
|
-
if (!hasNumericPredicates && ((
|
|
6600
|
+
if (!hasNumericPredicates && ((_l = this.config.rag) == null ? void 0 : _l.useReranking)) {
|
|
6382
6601
|
fullSources = yield new __await(this.reranker.rerank(fullSources, question, rerankLimit));
|
|
6383
6602
|
} else if (!wantsExhaustiveList) {
|
|
6384
6603
|
fullSources = fullSources.slice(0, topK);
|
|
@@ -6409,34 +6628,117 @@ VECTOR CONTEXT:
|
|
|
6409
6628
|
${context}`;
|
|
6410
6629
|
}
|
|
6411
6630
|
const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
|
|
6412
|
-
const
|
|
6413
|
-
|
|
6414
|
-
(
|
|
6631
|
+
const cachedSchema = allMetadataKeys.length > 0 ? SchemaMapper.getCached(ns, allMetadataKeys) : void 0;
|
|
6632
|
+
if (allMetadataKeys.length > 0 && !cachedSchema) {
|
|
6633
|
+
SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => {
|
|
6634
|
+
});
|
|
6635
|
+
}
|
|
6636
|
+
const isProductQ = UITransformer.isProductQueryPublic(question);
|
|
6637
|
+
const uiTransformationPromise = isProductQ ? Promise.resolve(
|
|
6638
|
+
UITransformer.transform(
|
|
6415
6639
|
question,
|
|
6416
6640
|
sources,
|
|
6417
|
-
|
|
6418
|
-
|
|
6641
|
+
this.config,
|
|
6642
|
+
cachedSchema,
|
|
6643
|
+
{ visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
|
|
6419
6644
|
)
|
|
6645
|
+
) : this.generateUiTransformation(
|
|
6646
|
+
question,
|
|
6647
|
+
sources,
|
|
6648
|
+
cachedSchema,
|
|
6649
|
+
hasNumericPredicates
|
|
6420
6650
|
).catch((uiError) => {
|
|
6421
6651
|
console.warn("[Pipeline] UI transformation failed concurrently:", uiError);
|
|
6422
|
-
return UITransformer.transform(question, sources, this.config);
|
|
6652
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6423
6653
|
});
|
|
6654
|
+
let hallucinationScoringPromise = Promise.resolve(void 0);
|
|
6424
6655
|
const restrictionSuffix = "\n\n(IMPORTANT: Format your response beautifully using rich Markdown! Use bolding for emphasis, headings (##) for structure, bullet points for lists, and proper line breaks between paragraphs to make it highly readable. However, NEVER generate Markdown tables, HTML figures, or text charts (the UI renders requested charts separately, so NEVER say you cannot create, display, draw, render, or provide a chart/visualization). If listing products, use simple markdown bullet points or comma-separated names. NEVER use plus signs (+) to separate product names, category names, or list items. For product description/detail questions, provide customer-facing prose only and do NOT list internal catalog fields such as Handle, Body (HTML), Vendor, Type, Tags, Published, Option, Variant, SKU, or raw metadata labels. You CAN use numbers for years/counts like 2006 or 5800, but NEVER put a dollar sign ($) before them.)";
|
|
6656
|
+
const isClaude37 = this.config.llm.model.includes("claude-3-7-sonnet");
|
|
6657
|
+
const isNativeThinking = isClaude37 && (((_m = this.config.llm.options) == null ? void 0 : _m.thinking) === true || ((_n = this.config.llm.options) == null ? void 0 : _n.thinking) === "enabled" || ((_o = this.config.llm.options) == null ? void 0 : _o.thinking) !== false);
|
|
6658
|
+
const modelNameLower = this.config.llm.model.toLowerCase();
|
|
6659
|
+
const isReasoningModel = modelNameLower.includes("-r1") || modelNameLower.includes("deepseek-r1") || modelNameLower.includes("reasoning") || modelNameLower.includes("thinking");
|
|
6660
|
+
const isSimulatedThinking = !isNativeThinking && (((_p = this.config.llm.options) == null ? void 0 : _p.thinking) === true || ((_q = this.config.llm.options) == null ? void 0 : _q.thinking) === "enabled" || isReasoningModel && ((_r = this.config.llm.options) == null ? void 0 : _r.thinking) !== false);
|
|
6661
|
+
let finalRestrictionSuffix = restrictionSuffix;
|
|
6662
|
+
if (isSimulatedThinking) {
|
|
6663
|
+
finalRestrictionSuffix += "\n\n(IMPORTANT: You must think step-by-step before answering. Write your thinking process inside a `<think>...</think>` block. Write the `<think>` block first, then follow it with your final response. Keep the thinking process thorough and detailed. Example: `<think>Thinking details here...</think>Final answer text here.`)";
|
|
6664
|
+
}
|
|
6425
6665
|
const hardenedHistory = [...history];
|
|
6426
|
-
const userQuestion = { role: "user", content: question +
|
|
6666
|
+
const userQuestion = { role: "user", content: question + finalRestrictionSuffix };
|
|
6427
6667
|
const messages = [...hardenedHistory, userQuestion];
|
|
6428
|
-
const systemPrompt = (
|
|
6668
|
+
const systemPrompt = (_s = this.config.llm.systemPrompt) != null ? _s : "";
|
|
6429
6669
|
const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
6430
6670
|
let fullReply = "";
|
|
6671
|
+
let thinkingText = "";
|
|
6672
|
+
let thinkingStartMs = performance.now();
|
|
6673
|
+
let thinkingDurationMs = 0;
|
|
6431
6674
|
const generateStart = performance.now();
|
|
6432
6675
|
if (this.llmProvider.chatStream) {
|
|
6433
6676
|
const stream = this.llmProvider.chatStream(messages, context);
|
|
6434
6677
|
if (!stream) throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
|
|
6678
|
+
let inThinking = false;
|
|
6679
|
+
let textBuffer = "";
|
|
6435
6680
|
try {
|
|
6436
6681
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
6437
6682
|
const chunk = temp.value;
|
|
6438
|
-
|
|
6439
|
-
|
|
6683
|
+
if (chunk.startsWith("\0THINKING\0")) {
|
|
6684
|
+
const thinkingDelta = chunk.slice("\0THINKING\0".length);
|
|
6685
|
+
thinkingText += thinkingDelta;
|
|
6686
|
+
thinkingDurationMs = performance.now() - thinkingStartMs;
|
|
6687
|
+
yield { type: "thinking", text: thinkingDelta };
|
|
6688
|
+
continue;
|
|
6689
|
+
}
|
|
6690
|
+
if (isSimulatedThinking) {
|
|
6691
|
+
textBuffer += chunk;
|
|
6692
|
+
while (textBuffer.length > 0) {
|
|
6693
|
+
if (!inThinking) {
|
|
6694
|
+
const thinkIndex = textBuffer.indexOf("<think>");
|
|
6695
|
+
if (thinkIndex !== -1) {
|
|
6696
|
+
const before = textBuffer.slice(0, thinkIndex);
|
|
6697
|
+
if (before) {
|
|
6698
|
+
fullReply += before;
|
|
6699
|
+
yield before;
|
|
6700
|
+
}
|
|
6701
|
+
inThinking = true;
|
|
6702
|
+
thinkingStartMs = performance.now();
|
|
6703
|
+
textBuffer = textBuffer.slice(thinkIndex + "<think>".length);
|
|
6704
|
+
} else {
|
|
6705
|
+
const maxSafeLength = textBuffer.length - "<think>".length;
|
|
6706
|
+
if (maxSafeLength > 0) {
|
|
6707
|
+
const safeText = textBuffer.slice(0, maxSafeLength);
|
|
6708
|
+
fullReply += safeText;
|
|
6709
|
+
yield safeText;
|
|
6710
|
+
textBuffer = textBuffer.slice(maxSafeLength);
|
|
6711
|
+
}
|
|
6712
|
+
break;
|
|
6713
|
+
}
|
|
6714
|
+
} else {
|
|
6715
|
+
const endThinkIndex = textBuffer.indexOf("</think>");
|
|
6716
|
+
if (endThinkIndex !== -1) {
|
|
6717
|
+
const thinkingDelta = textBuffer.slice(0, endThinkIndex);
|
|
6718
|
+
if (thinkingDelta) {
|
|
6719
|
+
thinkingText += thinkingDelta;
|
|
6720
|
+
thinkingDurationMs = performance.now() - thinkingStartMs;
|
|
6721
|
+
yield { type: "thinking", text: thinkingDelta };
|
|
6722
|
+
}
|
|
6723
|
+
inThinking = false;
|
|
6724
|
+
textBuffer = textBuffer.slice(endThinkIndex + "</think>".length);
|
|
6725
|
+
} else {
|
|
6726
|
+
const maxSafeLength = textBuffer.length - "</think>".length;
|
|
6727
|
+
if (maxSafeLength > 0) {
|
|
6728
|
+
const thinkingDelta = textBuffer.slice(0, maxSafeLength);
|
|
6729
|
+
thinkingText += thinkingDelta;
|
|
6730
|
+
thinkingDurationMs = performance.now() - thinkingStartMs;
|
|
6731
|
+
yield { type: "thinking", text: thinkingDelta };
|
|
6732
|
+
textBuffer = textBuffer.slice(maxSafeLength);
|
|
6733
|
+
}
|
|
6734
|
+
break;
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
} else {
|
|
6739
|
+
fullReply += chunk;
|
|
6740
|
+
yield chunk;
|
|
6741
|
+
}
|
|
6440
6742
|
}
|
|
6441
6743
|
} catch (temp) {
|
|
6442
6744
|
error = [temp];
|
|
@@ -6448,17 +6750,42 @@ ${context}`;
|
|
|
6448
6750
|
throw error[0];
|
|
6449
6751
|
}
|
|
6450
6752
|
}
|
|
6753
|
+
if (isSimulatedThinking && textBuffer.length > 0) {
|
|
6754
|
+
if (inThinking) {
|
|
6755
|
+
thinkingText += textBuffer;
|
|
6756
|
+
yield { type: "thinking", text: textBuffer };
|
|
6757
|
+
} else {
|
|
6758
|
+
fullReply += textBuffer;
|
|
6759
|
+
yield textBuffer;
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6451
6762
|
} else {
|
|
6452
6763
|
const reply = yield new __await(this.llmProvider.chat(messages, context));
|
|
6453
|
-
|
|
6454
|
-
|
|
6764
|
+
if (isSimulatedThinking) {
|
|
6765
|
+
const thinkStart = reply.indexOf("<think>");
|
|
6766
|
+
const thinkEnd = reply.indexOf("</think>");
|
|
6767
|
+
if (thinkStart !== -1 && thinkEnd !== -1 && thinkEnd > thinkStart) {
|
|
6768
|
+
thinkingText = reply.slice(thinkStart + "<think>".length, thinkEnd);
|
|
6769
|
+
fullReply = reply.slice(0, thinkStart) + reply.slice(thinkEnd + "</think>".length);
|
|
6770
|
+
thinkingDurationMs = 100;
|
|
6771
|
+
} else {
|
|
6772
|
+
fullReply = reply;
|
|
6773
|
+
}
|
|
6774
|
+
} else {
|
|
6775
|
+
fullReply = reply;
|
|
6776
|
+
}
|
|
6777
|
+
yield fullReply;
|
|
6778
|
+
}
|
|
6779
|
+
const runHallucination = ((_t = this.config.llm.options) == null ? void 0 : _t.hallucinationScoring) === true || this.config.llm.provider !== "ollama" && ((_u = this.config.llm.options) == null ? void 0 : _u.hallucinationScoring) !== false;
|
|
6780
|
+
if (runHallucination) {
|
|
6781
|
+
hallucinationScoringPromise = scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0);
|
|
6455
6782
|
}
|
|
6456
6783
|
const generateMs = performance.now() - generateStart;
|
|
6457
6784
|
const totalMs = performance.now() - requestStart;
|
|
6458
6785
|
const latency = {
|
|
6459
6786
|
embedMs: Math.round(embedMs),
|
|
6460
6787
|
retrieveMs: Math.round(retrieveMs),
|
|
6461
|
-
rerankMs: ((
|
|
6788
|
+
rerankMs: ((_v = this.config.rag) == null ? void 0 : _v.useReranking) ? Math.round(rerankMs) : void 0,
|
|
6462
6789
|
generateMs: Math.round(generateMs),
|
|
6463
6790
|
totalMs: Math.round(totalMs)
|
|
6464
6791
|
};
|
|
@@ -6471,12 +6798,16 @@ ${context}`;
|
|
|
6471
6798
|
totalTokens: promptTokens + completionTokens,
|
|
6472
6799
|
estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
|
|
6473
6800
|
};
|
|
6474
|
-
const
|
|
6801
|
+
const [uiTransformation, hallucinationResult] = yield new __await(Promise.all([
|
|
6802
|
+
uiTransformationPromise,
|
|
6803
|
+
hallucinationScoringPromise
|
|
6804
|
+
]));
|
|
6805
|
+
const trace = __spreadValues({
|
|
6475
6806
|
requestId,
|
|
6476
6807
|
query: question,
|
|
6477
6808
|
rewrittenQuery,
|
|
6478
6809
|
systemPrompt,
|
|
6479
|
-
userPrompt: question +
|
|
6810
|
+
userPrompt: question + finalRestrictionSuffix,
|
|
6480
6811
|
chunks: sources.map((s) => {
|
|
6481
6812
|
var _a2;
|
|
6482
6813
|
return {
|
|
@@ -6490,22 +6821,19 @@ ${context}`;
|
|
|
6490
6821
|
latency,
|
|
6491
6822
|
tokens,
|
|
6492
6823
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6493
|
-
}
|
|
6494
|
-
|
|
6824
|
+
}, hallucinationResult ? {
|
|
6825
|
+
hallucinationScore: hallucinationResult.score,
|
|
6826
|
+
hallucinationReason: hallucinationResult.reason
|
|
6827
|
+
} : {});
|
|
6495
6828
|
yield {
|
|
6496
6829
|
reply: "",
|
|
6497
6830
|
sources,
|
|
6498
6831
|
graphData,
|
|
6499
6832
|
ui_transformation: uiTransformation,
|
|
6500
|
-
trace
|
|
6833
|
+
trace,
|
|
6834
|
+
thinking: thinkingText || void 0,
|
|
6835
|
+
thinkingMs: thinkingDurationMs > 0 ? Math.round(thinkingDurationMs) : void 0
|
|
6501
6836
|
};
|
|
6502
|
-
scoreHallucination(this.llmProvider, fullReply, context).then((hallucinationResult) => {
|
|
6503
|
-
if (hallucinationResult) {
|
|
6504
|
-
trace.hallucinationScore = hallucinationResult.score;
|
|
6505
|
-
trace.hallucinationReason = hallucinationResult.reason;
|
|
6506
|
-
}
|
|
6507
|
-
}).catch(() => {
|
|
6508
|
-
});
|
|
6509
6837
|
} catch (error2) {
|
|
6510
6838
|
throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
6511
6839
|
}
|
|
@@ -6515,18 +6843,30 @@ ${context}`;
|
|
|
6515
6843
|
* Universal retrieval method combining all enabled providers.
|
|
6516
6844
|
* Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
|
|
6517
6845
|
*/
|
|
6518
|
-
async generateUiTransformation(question, sources,
|
|
6846
|
+
async generateUiTransformation(question, sources, cachedSchema, forceDeterministic = false) {
|
|
6847
|
+
var _a;
|
|
6519
6848
|
if (!sources || sources.length === 0) {
|
|
6520
|
-
return UITransformer.transform(question, sources, this.config,
|
|
6849
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6521
6850
|
}
|
|
6522
|
-
if (
|
|
6523
|
-
return UITransformer.transform(
|
|
6851
|
+
if (UITransformer.isProductQueryPublic(question)) {
|
|
6852
|
+
return UITransformer.transform(
|
|
6853
|
+
question,
|
|
6854
|
+
sources,
|
|
6855
|
+
this.config,
|
|
6856
|
+
cachedSchema,
|
|
6857
|
+
{ visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
|
|
6858
|
+
);
|
|
6859
|
+
}
|
|
6860
|
+
const isLocalProvider = this.config.llm.provider === "ollama";
|
|
6861
|
+
const disableLlmUiTransform = ((_a = this.config.llm.options) == null ? void 0 : _a.disableLlmUiTransform) === true;
|
|
6862
|
+
if (forceDeterministic || isLocalProvider || disableLlmUiTransform) {
|
|
6863
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6524
6864
|
}
|
|
6525
6865
|
try {
|
|
6526
6866
|
return await UITransformer.analyzeAndDecide(question, sources, this.llmRouter.get("fast"));
|
|
6527
6867
|
} catch (err) {
|
|
6528
6868
|
console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
|
|
6529
|
-
return UITransformer.transform(question, sources, this.config,
|
|
6869
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6530
6870
|
}
|
|
6531
6871
|
}
|
|
6532
6872
|
applyStructuredFilters(sources, filter) {
|
|
@@ -6610,25 +6950,29 @@ ${context}`;
|
|
|
6610
6950
|
return Number.isFinite(numeric) ? numeric : null;
|
|
6611
6951
|
}
|
|
6612
6952
|
async retrieve(query, options) {
|
|
6613
|
-
var _a, _b, _c, _d, _e, _f;
|
|
6953
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
6614
6954
|
const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
|
|
6615
6955
|
const topK = (_b = options.topK) != null ? _b : 5;
|
|
6616
6956
|
const cacheKey = `${ns}::${query}`;
|
|
6617
6957
|
let queryVector = this.embeddingCache.get(cacheKey);
|
|
6618
|
-
const
|
|
6958
|
+
const useGraph = this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval);
|
|
6959
|
+
const strategy = await QueryProcessor.determineRetrievalStrategy(
|
|
6960
|
+
query,
|
|
6961
|
+
useGraph ? this.llmRouter.get("fast") : void 0
|
|
6962
|
+
);
|
|
6619
6963
|
console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
|
|
6620
6964
|
const [retrievedVector, graphData] = await Promise.all([
|
|
6621
6965
|
// Only embed if we need vector search (strategy is 'vector' or 'both')
|
|
6622
6966
|
strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
|
|
6623
6967
|
// Only query graph if we need graph search (strategy is 'graph' or 'both')
|
|
6624
|
-
(strategy === "graph" || strategy === "both") && this.graphDB && ((
|
|
6968
|
+
(strategy === "graph" || strategy === "both") && this.graphDB && ((_d = this.config.rag) == null ? void 0 : _d.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
|
|
6625
6969
|
]);
|
|
6626
6970
|
if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
|
|
6627
6971
|
this.embeddingCache.set(cacheKey, retrievedVector);
|
|
6628
6972
|
queryVector = retrievedVector;
|
|
6629
6973
|
}
|
|
6630
|
-
const baseFilter = __spreadProps(__spreadValues({}, (
|
|
6631
|
-
const numericPredicates = QueryProcessor.extractNumericPredicates(query, (
|
|
6974
|
+
const baseFilter = __spreadProps(__spreadValues({}, (_e = options.filter) != null ? _e : {}), { queryText: query });
|
|
6975
|
+
const numericPredicates = QueryProcessor.extractNumericPredicates(query, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
|
|
6632
6976
|
if (numericPredicates.length > 0) {
|
|
6633
6977
|
baseFilter.__numericPredicates = numericPredicates;
|
|
6634
6978
|
}
|
|
@@ -6639,7 +6983,7 @@ ${context}`;
|
|
|
6639
6983
|
) : [];
|
|
6640
6984
|
const resolvedSources = [];
|
|
6641
6985
|
for (const source of sources) {
|
|
6642
|
-
const parentId = (
|
|
6986
|
+
const parentId = (_g = source.metadata) == null ? void 0 : _g.parent_id;
|
|
6643
6987
|
if (parentId) {
|
|
6644
6988
|
console.log(`[Pipeline] Multi-Vector: Found child chunk. Parent ID: ${parentId}`);
|
|
6645
6989
|
resolvedSources.push(source);
|
|
@@ -6662,10 +7006,13 @@ New Question: ${question}
|
|
|
6662
7006
|
Optimized Search Query:`;
|
|
6663
7007
|
const rewrite = await this.llmProvider.chat(
|
|
6664
7008
|
[
|
|
6665
|
-
{ role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
|
|
6666
7009
|
{ role: "user", content: prompt }
|
|
6667
7010
|
],
|
|
6668
|
-
""
|
|
7011
|
+
"",
|
|
7012
|
+
{
|
|
7013
|
+
systemPrompt: "You are an assistant that optimizes search queries for RAG systems. Focus on extracting the core intent and entities. Do not answer the question, just rewrite it.",
|
|
7014
|
+
temperature: 0
|
|
7015
|
+
}
|
|
6669
7016
|
);
|
|
6670
7017
|
return rewrite.trim() || question;
|
|
6671
7018
|
}
|
|
@@ -6689,10 +7036,13 @@ ${context}
|
|
|
6689
7036
|
Suggestions:`;
|
|
6690
7037
|
const response = await this.llmProvider.chat(
|
|
6691
7038
|
[
|
|
6692
|
-
{ role: "system", content: "You are a helpful assistant that generates search suggestions." },
|
|
6693
7039
|
{ role: "user", content: prompt }
|
|
6694
7040
|
],
|
|
6695
|
-
""
|
|
7041
|
+
"",
|
|
7042
|
+
{
|
|
7043
|
+
systemPrompt: "You are a helpful assistant that generates search suggestions based on the provided snippets. Focus on questions that can be answered by the context, keep each under 10 words, and return ONLY a JSON array of strings.",
|
|
7044
|
+
temperature: 0
|
|
7045
|
+
}
|
|
6696
7046
|
);
|
|
6697
7047
|
const match = response.match(/\[[\s\S]*\]/);
|
|
6698
7048
|
if (match) {
|
|
@@ -7031,6 +7381,10 @@ function createStreamHandler(configOrPlugin) {
|
|
|
7031
7381
|
if (!isActive) break;
|
|
7032
7382
|
if (typeof chunk === "string") {
|
|
7033
7383
|
enqueue(sseTextFrame(chunk));
|
|
7384
|
+
} else if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "thinking") {
|
|
7385
|
+
enqueue(`data: ${JSON.stringify(chunk)}
|
|
7386
|
+
|
|
7387
|
+
`);
|
|
7034
7388
|
} else {
|
|
7035
7389
|
enqueue(sseMetaFrame(chunk));
|
|
7036
7390
|
const responseChunk = chunk;
|