@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.mjs
CHANGED
|
@@ -640,6 +640,11 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
640
640
|
CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
|
|
641
641
|
ON ${this.uploadTable}
|
|
642
642
|
USING hnsw (embedding vector_cosine_ops)
|
|
643
|
+
`);
|
|
644
|
+
await client.query(`
|
|
645
|
+
CREATE INDEX IF NOT EXISTS "${this.uploadTable}_fts_idx"
|
|
646
|
+
ON "${this.uploadTable}"
|
|
647
|
+
USING gin (to_tsvector('english', content))
|
|
643
648
|
`);
|
|
644
649
|
const res = await client.query(`
|
|
645
650
|
SELECT table_name
|
|
@@ -710,6 +715,11 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
710
715
|
CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
|
|
711
716
|
ON "${tableName}"
|
|
712
717
|
USING hnsw (embedding vector_cosine_ops)
|
|
718
|
+
`);
|
|
719
|
+
await client.query(`
|
|
720
|
+
CREATE INDEX IF NOT EXISTS "${tableName}_fts_idx"
|
|
721
|
+
ON "${tableName}"
|
|
722
|
+
USING gin (to_tsvector('english', content))
|
|
713
723
|
`);
|
|
714
724
|
if (!this.tables.includes(tableName)) {
|
|
715
725
|
this.tables.push(tableName);
|
|
@@ -801,8 +811,11 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
801
811
|
const paramIdx = baseOffset + filterParams.length;
|
|
802
812
|
const synonyms = (_a3 = FIELD_SYNONYMS[key]) != null ? _a3 : [];
|
|
803
813
|
const keysToCheck = [key, ...synonyms];
|
|
804
|
-
const coalesceExprs = keysToCheck.
|
|
805
|
-
|
|
814
|
+
const coalesceExprs = keysToCheck.flatMap((k) => [
|
|
815
|
+
`metadata->>'${k}'`,
|
|
816
|
+
`to_jsonb(t)->>'${k}'`
|
|
817
|
+
]);
|
|
818
|
+
return `COALESCE(${coalesceExprs.map((expr) => `LOWER(${expr})`).join(", ")}) = LOWER($${paramIdx})`;
|
|
806
819
|
});
|
|
807
820
|
whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
808
821
|
}
|
|
@@ -813,16 +826,35 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
813
826
|
CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
|
|
814
827
|
THEN 1.0 ELSE 0.0 END
|
|
815
828
|
), 0)
|
|
816
|
-
FROM jsonb_each_text(
|
|
829
|
+
FROM jsonb_each_text(COALESCE(v.metadata, k.metadata)) AS kv(key, val)
|
|
817
830
|
WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
|
|
818
831
|
) * 3.0` : "";
|
|
819
832
|
sqlQuery = `
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
833
|
+
WITH vector_search AS (
|
|
834
|
+
SELECT *, (1 - (embedding <=> $1::vector)) AS vector_score
|
|
835
|
+
FROM "${table}" t
|
|
836
|
+
${whereClause}
|
|
837
|
+
ORDER BY embedding <=> $1::vector ASC
|
|
838
|
+
LIMIT ${tableLimit}
|
|
839
|
+
),
|
|
840
|
+
keyword_search AS (
|
|
841
|
+
SELECT *, COALESCE(ts_rank(to_tsvector('english', content), to_tsquery('english', $2)), 0) AS keyword_score
|
|
842
|
+
FROM "${table}" t
|
|
843
|
+
WHERE ${whereClause ? whereClause.replace("WHERE", "") : "TRUE"}
|
|
844
|
+
AND to_tsvector('english', content) @@ to_tsquery('english', $2)
|
|
845
|
+
ORDER BY keyword_score DESC
|
|
846
|
+
LIMIT ${tableLimit}
|
|
847
|
+
)
|
|
848
|
+
SELECT
|
|
849
|
+
COALESCE(v.id, k.id) AS id,
|
|
850
|
+
COALESCE(v.namespace, k.namespace) AS namespace,
|
|
851
|
+
COALESCE(v.content, k.content) AS content,
|
|
852
|
+
COALESCE(v.metadata, k.metadata) AS metadata,
|
|
853
|
+
COALESCE(v.vector_score, 0) AS vector_score,
|
|
854
|
+
COALESCE(k.keyword_score, 0) AS keyword_score,
|
|
855
|
+
(COALESCE(v.vector_score, 0) + COALESCE(k.keyword_score, 0) * 2.0 ${exactNameScoreExpr}) AS hybrid_score
|
|
856
|
+
FROM vector_search v
|
|
857
|
+
FULL OUTER JOIN keyword_search k ON v.id = k.id
|
|
826
858
|
ORDER BY hybrid_score DESC
|
|
827
859
|
LIMIT ${tableLimit}
|
|
828
860
|
`;
|
|
@@ -833,7 +865,7 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
833
865
|
(1 - (embedding <=> $1::vector)) AS hybrid_score
|
|
834
866
|
FROM "${table}" t
|
|
835
867
|
${whereClause}
|
|
836
|
-
ORDER BY
|
|
868
|
+
ORDER BY embedding <=> $1::vector ASC
|
|
837
869
|
LIMIT ${tableLimit}
|
|
838
870
|
`;
|
|
839
871
|
params = [vectorLiteral, ...filterParams];
|
|
@@ -1039,7 +1071,11 @@ var init_MongoDBProvider = __esm({
|
|
|
1039
1071
|
if (key === "namespace") {
|
|
1040
1072
|
vectorSearchFilter.namespace = value;
|
|
1041
1073
|
} else {
|
|
1042
|
-
|
|
1074
|
+
if (typeof value === "string") {
|
|
1075
|
+
matchFilter[key] = { $regex: new RegExp(`^${value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "i") };
|
|
1076
|
+
} else {
|
|
1077
|
+
matchFilter[key] = value;
|
|
1078
|
+
}
|
|
1043
1079
|
}
|
|
1044
1080
|
}
|
|
1045
1081
|
const pipeline = [
|
|
@@ -2112,7 +2148,7 @@ function readEnum(env, name, fallback, allowed) {
|
|
|
2112
2148
|
throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
|
|
2113
2149
|
}
|
|
2114
2150
|
function getEnvConfig(env = process.env, base) {
|
|
2115
|
-
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;
|
|
2151
|
+
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;
|
|
2116
2152
|
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__";
|
|
2117
2153
|
const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
|
|
2118
2154
|
const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
|
|
@@ -2182,7 +2218,7 @@ function getEnvConfig(env = process.env, base) {
|
|
|
2182
2218
|
rest: "default",
|
|
2183
2219
|
custom: "default"
|
|
2184
2220
|
};
|
|
2185
|
-
return {
|
|
2221
|
+
return __spreadValues({
|
|
2186
2222
|
projectId,
|
|
2187
2223
|
vectorDb: {
|
|
2188
2224
|
provider: vectorProvider,
|
|
@@ -2198,7 +2234,9 @@ function getEnvConfig(env = process.env, base) {
|
|
|
2198
2234
|
maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
|
|
2199
2235
|
temperature: readNumber(env, "LLM_TEMPERATURE", 0.7),
|
|
2200
2236
|
options: {
|
|
2201
|
-
profile: readString(env, "LLM_UNIVERSAL_PROFILE")
|
|
2237
|
+
profile: readString(env, "LLM_UNIVERSAL_PROFILE"),
|
|
2238
|
+
thinking: readString(env, "LLM_THINKING") === "true" || readString(env, "LLM_THINKING") === "enabled" ? true : readString(env, "LLM_THINKING") === "false" ? false : void 0,
|
|
2239
|
+
thinkingBudget: env.LLM_THINKING_BUDGET ? parseInt(env.LLM_THINKING_BUDGET, 10) : void 0
|
|
2202
2240
|
}
|
|
2203
2241
|
},
|
|
2204
2242
|
embedding: {
|
|
@@ -2230,11 +2268,56 @@ function getEnvConfig(env = process.env, base) {
|
|
|
2230
2268
|
topK: readNumber(env, "RAG_TOP_K", 5),
|
|
2231
2269
|
scoreThreshold: readNumber(env, "RAG_SCORE_THRESHOLD", 0),
|
|
2232
2270
|
chunkSize: readNumber(env, "RAG_CHUNK_SIZE", 1e3),
|
|
2233
|
-
chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200)
|
|
2271
|
+
chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200),
|
|
2272
|
+
filterableFields: (_Ha = readString(env, "RAG_FILTERABLE_FIELDS")) == null ? void 0 : _Ha.split(",").map((f) => f.trim()),
|
|
2273
|
+
// Query pipeline toggles — read from .env.local
|
|
2274
|
+
useQueryTransformation: readString(env, "RAG_USE_QUERY_TRANSFORMATION") === "true",
|
|
2275
|
+
useReranking: readString(env, "RAG_USE_RERANKING") === "true",
|
|
2276
|
+
useGraphRetrieval: readString(env, "RAG_USE_GRAPH_RETRIEVAL") === "true",
|
|
2277
|
+
architecture: (_Ia = readString(env, "RAG_ARCHITECTURE")) != null ? _Ia : "simple",
|
|
2278
|
+
chunkingStrategy: (_Ja = readString(env, "RAG_CHUNKING_STRATEGY")) != null ? _Ja : "recursive",
|
|
2279
|
+
uiMapping: (() => {
|
|
2280
|
+
const raw = readString(env, "RAG_UI_MAPPING");
|
|
2281
|
+
if (!raw) return void 0;
|
|
2282
|
+
try {
|
|
2283
|
+
return JSON.parse(raw);
|
|
2284
|
+
} catch (e) {
|
|
2285
|
+
return void 0;
|
|
2286
|
+
}
|
|
2287
|
+
})()
|
|
2234
2288
|
}
|
|
2235
|
-
}
|
|
2289
|
+
}, readString(env, "GRAPH_DB_PROVIDER") ? {
|
|
2290
|
+
graphDb: {
|
|
2291
|
+
provider: readString(env, "GRAPH_DB_PROVIDER"),
|
|
2292
|
+
options: {
|
|
2293
|
+
uri: readString(env, "GRAPH_DB_URI"),
|
|
2294
|
+
username: readString(env, "GRAPH_DB_USERNAME"),
|
|
2295
|
+
password: readString(env, "GRAPH_DB_PASSWORD")
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
} : {});
|
|
2236
2299
|
}
|
|
2237
2300
|
|
|
2301
|
+
// src/exceptions/index.ts
|
|
2302
|
+
var RetrivoraError = class extends Error {
|
|
2303
|
+
constructor(message, code, details) {
|
|
2304
|
+
super(message);
|
|
2305
|
+
this.name = new.target.name;
|
|
2306
|
+
this.code = code;
|
|
2307
|
+
this.details = details;
|
|
2308
|
+
}
|
|
2309
|
+
};
|
|
2310
|
+
var ProviderNotFoundException = class extends RetrivoraError {
|
|
2311
|
+
constructor(providerType, provider, details) {
|
|
2312
|
+
super(`Unsupported ${providerType} provider: ${provider}`, "PROVIDER_NOT_FOUND", details);
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
var ConfigurationException = class extends RetrivoraError {
|
|
2316
|
+
constructor(message, details) {
|
|
2317
|
+
super(message, "CONFIGURATION_ERROR", details);
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
|
|
2238
2321
|
// src/core/ConfigResolver.ts
|
|
2239
2322
|
var ConfigResolver = class {
|
|
2240
2323
|
/**
|
|
@@ -2261,24 +2344,81 @@ var ConfigResolver = class {
|
|
|
2261
2344
|
rag: hostConfig.rag ? __spreadValues(__spreadValues({}, envConfig.rag), hostConfig.rag) : envConfig.rag
|
|
2262
2345
|
});
|
|
2263
2346
|
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Resolves the public SDK config shape used by `new Retrivora({...})`.
|
|
2349
|
+
* Supports aliases from the product prompt while preserving existing env
|
|
2350
|
+
* fallback behavior.
|
|
2351
|
+
*/
|
|
2352
|
+
static resolveUniversal(hostConfig, env = process.env) {
|
|
2353
|
+
var _a;
|
|
2354
|
+
if (!hostConfig) return this.resolve(void 0, env);
|
|
2355
|
+
const normalized = __spreadProps(__spreadValues({}, hostConfig), {
|
|
2356
|
+
vectorDb: (_a = hostConfig.vectorDb) != null ? _a : hostConfig.vectorDatabase,
|
|
2357
|
+
rag: this.mergeRetrievalWorkflow(hostConfig.rag, hostConfig.retrieval, hostConfig.workflow)
|
|
2358
|
+
});
|
|
2359
|
+
return this.resolve(normalized, env);
|
|
2360
|
+
}
|
|
2264
2361
|
/**
|
|
2265
2362
|
* Validates the configuration for required fields.
|
|
2266
2363
|
*/
|
|
2267
2364
|
static validate(config) {
|
|
2268
2365
|
if (!config.projectId) {
|
|
2269
|
-
throw new
|
|
2366
|
+
throw new ConfigurationException("[ConfigResolver] projectId is required");
|
|
2270
2367
|
}
|
|
2271
2368
|
if (!config.vectorDb.provider) {
|
|
2272
|
-
throw new
|
|
2369
|
+
throw new ConfigurationException("[ConfigResolver] vectorDb.provider is required");
|
|
2273
2370
|
}
|
|
2274
2371
|
if (!config.llm.provider) {
|
|
2275
|
-
throw new
|
|
2372
|
+
throw new ConfigurationException("[ConfigResolver] llm.provider is required");
|
|
2276
2373
|
}
|
|
2277
2374
|
}
|
|
2375
|
+
static mergeRetrievalWorkflow(rag, retrieval, workflow) {
|
|
2376
|
+
var _a, _b, _c, _d, _e;
|
|
2377
|
+
if (!rag && !retrieval && !workflow) return void 0;
|
|
2378
|
+
const normalized = __spreadValues({}, rag != null ? rag : {});
|
|
2379
|
+
if (retrieval) {
|
|
2380
|
+
normalized.topK = (_a = retrieval.topK) != null ? _a : normalized.topK;
|
|
2381
|
+
normalized.scoreThreshold = (_b = retrieval.scoreThreshold) != null ? _b : normalized.scoreThreshold;
|
|
2382
|
+
normalized.useReranking = (_c = retrieval.useReranking) != null ? _c : normalized.useReranking;
|
|
2383
|
+
if (retrieval.strategy === "hybrid") normalized.architecture = (_d = normalized.architecture) != null ? _d : "hybrid";
|
|
2384
|
+
if (retrieval.strategy === "agentic") normalized.architecture = "agentic";
|
|
2385
|
+
if (retrieval.strategy === "contextual-compression") normalized.useReranking = true;
|
|
2386
|
+
}
|
|
2387
|
+
if (workflow == null ? void 0 : workflow.type) {
|
|
2388
|
+
const type = workflow.type;
|
|
2389
|
+
if (type === "agentic" || type === "agentic-rag") normalized.architecture = "agentic";
|
|
2390
|
+
else if (type === "hybrid-rag") normalized.architecture = "hybrid";
|
|
2391
|
+
else if (type === "graph-rag") {
|
|
2392
|
+
normalized.architecture = "graph";
|
|
2393
|
+
normalized.useGraphRetrieval = true;
|
|
2394
|
+
} else if (type === "simple-rag" || type === "rag") {
|
|
2395
|
+
normalized.architecture = (_e = normalized.architecture) != null ? _e : "simple";
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
return normalized;
|
|
2399
|
+
}
|
|
2278
2400
|
};
|
|
2279
2401
|
|
|
2280
2402
|
// src/llm/providers/OpenAIProvider.ts
|
|
2281
2403
|
import OpenAI from "openai";
|
|
2404
|
+
|
|
2405
|
+
// src/llm/utils.ts
|
|
2406
|
+
function buildSystemContent(systemPrompt, context) {
|
|
2407
|
+
const noContext = !context || context.trim() === "" || context.trim() === "No relevant context found.";
|
|
2408
|
+
if (systemPrompt.includes("{{context}}")) {
|
|
2409
|
+
return systemPrompt.replace("{{context}}", noContext ? "" : context);
|
|
2410
|
+
}
|
|
2411
|
+
if (noContext) {
|
|
2412
|
+
return systemPrompt;
|
|
2413
|
+
}
|
|
2414
|
+
return `${systemPrompt}
|
|
2415
|
+
|
|
2416
|
+
---
|
|
2417
|
+
Context:
|
|
2418
|
+
${context}`;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// src/llm/providers/OpenAIProvider.ts
|
|
2282
2422
|
var OpenAIProvider = class {
|
|
2283
2423
|
constructor(llmConfig, embeddingConfig) {
|
|
2284
2424
|
if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
|
|
@@ -2342,16 +2482,10 @@ var OpenAIProvider = class {
|
|
|
2342
2482
|
}
|
|
2343
2483
|
async chat(messages, context, options) {
|
|
2344
2484
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2345
|
-
const
|
|
2346
|
-
|
|
2347
|
-
Context:
|
|
2348
|
-
${context}`;
|
|
2485
|
+
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.";
|
|
2349
2486
|
const systemMessage = {
|
|
2350
2487
|
role: "system",
|
|
2351
|
-
content:
|
|
2352
|
-
|
|
2353
|
-
Context:
|
|
2354
|
-
${context}`
|
|
2488
|
+
content: buildSystemContent(basePrompt, context)
|
|
2355
2489
|
};
|
|
2356
2490
|
const formattedMessages = [
|
|
2357
2491
|
systemMessage,
|
|
@@ -2372,16 +2506,10 @@ ${context}`
|
|
|
2372
2506
|
chatStream(messages, context, options) {
|
|
2373
2507
|
return __asyncGenerator(this, null, function* () {
|
|
2374
2508
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2375
|
-
const
|
|
2376
|
-
|
|
2377
|
-
Context:
|
|
2378
|
-
${context}`;
|
|
2509
|
+
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.";
|
|
2379
2510
|
const systemMessage = {
|
|
2380
2511
|
role: "system",
|
|
2381
|
-
content:
|
|
2382
|
-
|
|
2383
|
-
Context:
|
|
2384
|
-
${context}`
|
|
2512
|
+
content: buildSystemContent(basePrompt, context)
|
|
2385
2513
|
};
|
|
2386
2514
|
const formattedMessages = [
|
|
2387
2515
|
systemMessage,
|
|
@@ -2502,58 +2630,87 @@ var AnthropicProvider = class {
|
|
|
2502
2630
|
};
|
|
2503
2631
|
}
|
|
2504
2632
|
async chat(messages, context, options) {
|
|
2505
|
-
var _a, _b, _c, _d;
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
Context:
|
|
2509
|
-
${context}`;
|
|
2510
|
-
const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
|
|
2511
|
-
|
|
2512
|
-
Context:
|
|
2513
|
-
${context}`;
|
|
2633
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2634
|
+
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.";
|
|
2635
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2514
2636
|
const anthropicMessages = messages.map((m) => ({
|
|
2515
2637
|
role: m.role === "assistant" ? "assistant" : "user",
|
|
2516
2638
|
content: m.content
|
|
2517
2639
|
}));
|
|
2518
|
-
const
|
|
2640
|
+
const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
|
|
2641
|
+
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;
|
|
2642
|
+
const extraParams = {};
|
|
2643
|
+
if (isThinkingEnabled && isClaude37) {
|
|
2644
|
+
extraParams.betas = ["interleaved-thinking-2025-05-14"];
|
|
2645
|
+
const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
|
|
2646
|
+
const budget = Math.min(
|
|
2647
|
+
typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
|
|
2648
|
+
maxTokens - 1
|
|
2649
|
+
);
|
|
2650
|
+
extraParams.thinking = {
|
|
2651
|
+
type: "enabled",
|
|
2652
|
+
budget_tokens: budget
|
|
2653
|
+
};
|
|
2654
|
+
extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
|
|
2655
|
+
}
|
|
2656
|
+
const response = await this.client.messages.create(__spreadValues({
|
|
2519
2657
|
model: this.llmConfig.model,
|
|
2520
|
-
max_tokens: (
|
|
2658
|
+
max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
|
|
2521
2659
|
system,
|
|
2522
2660
|
messages: anthropicMessages
|
|
2523
|
-
});
|
|
2524
|
-
|
|
2525
|
-
|
|
2661
|
+
}, extraParams));
|
|
2662
|
+
let reply = "";
|
|
2663
|
+
for (const block of response.content) {
|
|
2664
|
+
if (block.type === "text") {
|
|
2665
|
+
reply += block.text;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return reply;
|
|
2526
2669
|
}
|
|
2527
2670
|
chatStream(messages, context, options) {
|
|
2528
2671
|
return __asyncGenerator(this, null, function* () {
|
|
2529
|
-
var _a, _b, _c, _d;
|
|
2530
|
-
const
|
|
2531
|
-
|
|
2532
|
-
Context:
|
|
2533
|
-
${context}`;
|
|
2534
|
-
const system = resolvedPrompt.includes("{{context}}") ? resolvedPrompt.replace("{{context}}", context) : (options == null ? void 0 : options.systemPrompt) ? resolvedPrompt : `${resolvedPrompt}
|
|
2535
|
-
|
|
2536
|
-
Context:
|
|
2537
|
-
${context}`;
|
|
2672
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2673
|
+
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.";
|
|
2674
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2538
2675
|
const anthropicMessages = messages.map((m) => ({
|
|
2539
2676
|
role: m.role === "assistant" ? "assistant" : "user",
|
|
2540
2677
|
content: m.content
|
|
2541
2678
|
}));
|
|
2542
|
-
const
|
|
2679
|
+
const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
|
|
2680
|
+
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;
|
|
2681
|
+
const extraParams = {};
|
|
2682
|
+
if (isThinkingEnabled && isClaude37) {
|
|
2683
|
+
extraParams.betas = ["interleaved-thinking-2025-05-14"];
|
|
2684
|
+
const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
|
|
2685
|
+
const budget = Math.min(
|
|
2686
|
+
typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
|
|
2687
|
+
maxTokens - 1
|
|
2688
|
+
);
|
|
2689
|
+
extraParams.thinking = {
|
|
2690
|
+
type: "enabled",
|
|
2691
|
+
budget_tokens: budget
|
|
2692
|
+
};
|
|
2693
|
+
extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
|
|
2694
|
+
}
|
|
2695
|
+
const stream = yield new __await(this.client.messages.create(__spreadValues({
|
|
2543
2696
|
model: this.llmConfig.model,
|
|
2544
|
-
max_tokens: (
|
|
2697
|
+
max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
|
|
2545
2698
|
system,
|
|
2546
2699
|
messages: anthropicMessages,
|
|
2547
2700
|
stream: true
|
|
2548
|
-
}));
|
|
2701
|
+
}, extraParams)));
|
|
2549
2702
|
if (!stream) {
|
|
2550
2703
|
throw new Error("[AnthropicProvider] messages.create stream is undefined");
|
|
2551
2704
|
}
|
|
2552
2705
|
try {
|
|
2553
2706
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2554
2707
|
const chunk = temp.value;
|
|
2555
|
-
if (chunk.type === "content_block_delta"
|
|
2556
|
-
|
|
2708
|
+
if (chunk.type === "content_block_delta") {
|
|
2709
|
+
if (chunk.delta.type === "text_delta") {
|
|
2710
|
+
yield chunk.delta.text;
|
|
2711
|
+
} else if (chunk.delta.type === "thinking_delta") {
|
|
2712
|
+
yield `\0THINKING\0${chunk.delta.thinking}`;
|
|
2713
|
+
}
|
|
2557
2714
|
}
|
|
2558
2715
|
}
|
|
2559
2716
|
} catch (temp) {
|
|
@@ -2597,9 +2754,10 @@ ${context}`;
|
|
|
2597
2754
|
import axios from "axios";
|
|
2598
2755
|
var OllamaProvider = class {
|
|
2599
2756
|
constructor(llmConfig, embeddingConfig) {
|
|
2600
|
-
var _a;
|
|
2757
|
+
var _a, _b;
|
|
2601
2758
|
const baseURL = (_a = llmConfig.baseUrl) != null ? _a : "http://localhost:11434";
|
|
2602
|
-
|
|
2759
|
+
const timeout = Number((_b = llmConfig.options) == null ? void 0 : _b.timeout) || 3e5;
|
|
2760
|
+
this.http = axios.create({ baseURL, timeout });
|
|
2603
2761
|
this.llmConfig = llmConfig;
|
|
2604
2762
|
this.embeddingConfig = embeddingConfig;
|
|
2605
2763
|
}
|
|
@@ -2655,14 +2813,15 @@ var OllamaProvider = class {
|
|
|
2655
2813
|
};
|
|
2656
2814
|
}
|
|
2657
2815
|
async chat(messages, context, options) {
|
|
2658
|
-
var _a, _b, _c, _d;
|
|
2659
|
-
const
|
|
2816
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2817
|
+
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.";
|
|
2818
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2660
2819
|
const { data } = await this.http.post("/api/chat", {
|
|
2661
2820
|
model: this.llmConfig.model,
|
|
2662
2821
|
stream: false,
|
|
2663
2822
|
options: {
|
|
2664
|
-
temperature: (
|
|
2665
|
-
num_predict: (
|
|
2823
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
|
|
2824
|
+
num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
|
|
2666
2825
|
},
|
|
2667
2826
|
messages: [
|
|
2668
2827
|
{ role: "system", content: system },
|
|
@@ -2673,14 +2832,15 @@ var OllamaProvider = class {
|
|
|
2673
2832
|
}
|
|
2674
2833
|
chatStream(messages, context, options) {
|
|
2675
2834
|
return __asyncGenerator(this, null, function* () {
|
|
2676
|
-
var _a, _b, _c, _d, _e, _f;
|
|
2677
|
-
const
|
|
2835
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2836
|
+
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.";
|
|
2837
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2678
2838
|
const response = yield new __await(this.http.post("/api/chat", {
|
|
2679
2839
|
model: this.llmConfig.model,
|
|
2680
2840
|
stream: true,
|
|
2681
2841
|
options: {
|
|
2682
|
-
temperature: (
|
|
2683
|
-
num_predict: (
|
|
2842
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
|
|
2843
|
+
num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
|
|
2684
2844
|
},
|
|
2685
2845
|
messages: [
|
|
2686
2846
|
{ role: "system", content: system },
|
|
@@ -2702,7 +2862,7 @@ var OllamaProvider = class {
|
|
|
2702
2862
|
if (!line.trim()) continue;
|
|
2703
2863
|
try {
|
|
2704
2864
|
const json = JSON.parse(line);
|
|
2705
|
-
if ((
|
|
2865
|
+
if ((_g = json.message) == null ? void 0 : _g.content) {
|
|
2706
2866
|
yield json.message.content;
|
|
2707
2867
|
}
|
|
2708
2868
|
if (json.done) return;
|
|
@@ -2724,26 +2884,12 @@ var OllamaProvider = class {
|
|
|
2724
2884
|
if (lineBuffer.trim()) {
|
|
2725
2885
|
try {
|
|
2726
2886
|
const json = JSON.parse(lineBuffer);
|
|
2727
|
-
if ((
|
|
2887
|
+
if ((_h = json.message) == null ? void 0 : _h.content) yield json.message.content;
|
|
2728
2888
|
} catch (e) {
|
|
2729
2889
|
}
|
|
2730
2890
|
}
|
|
2731
2891
|
});
|
|
2732
2892
|
}
|
|
2733
|
-
buildSystemPrompt(context, overridePrompt) {
|
|
2734
|
-
var _a;
|
|
2735
|
-
if (overridePrompt) {
|
|
2736
|
-
return overridePrompt;
|
|
2737
|
-
}
|
|
2738
|
-
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
|
|
2739
|
-
|
|
2740
|
-
Context:
|
|
2741
|
-
${context}`;
|
|
2742
|
-
return systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2743
|
-
|
|
2744
|
-
Context:
|
|
2745
|
-
${context}`;
|
|
2746
|
-
}
|
|
2747
2893
|
async embed(text, options) {
|
|
2748
2894
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2749
2895
|
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";
|
|
@@ -2890,21 +3036,6 @@ var GeminiProvider = class {
|
|
|
2890
3036
|
var _a, _b;
|
|
2891
3037
|
return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
|
|
2892
3038
|
}
|
|
2893
|
-
/**
|
|
2894
|
-
* Build the system instruction string, inserting the RAG context either via
|
|
2895
|
-
* the `{{context}}` placeholder or by appending it.
|
|
2896
|
-
*/
|
|
2897
|
-
buildSystemInstruction(context) {
|
|
2898
|
-
var _a;
|
|
2899
|
-
const base = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
2900
|
-
|
|
2901
|
-
Context:
|
|
2902
|
-
${context}`;
|
|
2903
|
-
return base.includes("{{context}}") ? base.replace("{{context}}", context) : `${base}
|
|
2904
|
-
|
|
2905
|
-
Context:
|
|
2906
|
-
${context}`;
|
|
2907
|
-
}
|
|
2908
3039
|
/**
|
|
2909
3040
|
* Convert ChatMessage[] to the Gemini contents format.
|
|
2910
3041
|
*
|
|
@@ -2923,16 +3054,17 @@ ${context}`;
|
|
|
2923
3054
|
// ILLMProvider — chat
|
|
2924
3055
|
// -------------------------------------------------------------------------
|
|
2925
3056
|
async chat(messages, context, options) {
|
|
2926
|
-
var _a, _b, _c, _d;
|
|
3057
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3058
|
+
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.";
|
|
2927
3059
|
const model = this.client.getGenerativeModel({
|
|
2928
3060
|
model: this.llmConfig.model,
|
|
2929
|
-
systemInstruction:
|
|
3061
|
+
systemInstruction: buildSystemContent(basePrompt, context)
|
|
2930
3062
|
});
|
|
2931
3063
|
const result = await model.generateContent({
|
|
2932
3064
|
contents: this.buildGeminiContents(messages),
|
|
2933
3065
|
generationConfig: {
|
|
2934
|
-
temperature: (
|
|
2935
|
-
maxOutputTokens: (
|
|
3066
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
|
|
3067
|
+
maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
|
|
2936
3068
|
stopSequences: options == null ? void 0 : options.stop
|
|
2937
3069
|
}
|
|
2938
3070
|
});
|
|
@@ -2940,16 +3072,17 @@ ${context}`;
|
|
|
2940
3072
|
}
|
|
2941
3073
|
chatStream(messages, context, options) {
|
|
2942
3074
|
return __asyncGenerator(this, null, function* () {
|
|
2943
|
-
var _a, _b, _c, _d;
|
|
3075
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3076
|
+
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.";
|
|
2944
3077
|
const model = this.client.getGenerativeModel({
|
|
2945
3078
|
model: this.llmConfig.model,
|
|
2946
|
-
systemInstruction:
|
|
3079
|
+
systemInstruction: buildSystemContent(basePrompt, context)
|
|
2947
3080
|
});
|
|
2948
3081
|
const result = yield new __await(model.generateContentStream({
|
|
2949
3082
|
contents: this.buildGeminiContents(messages),
|
|
2950
3083
|
generationConfig: {
|
|
2951
|
-
temperature: (
|
|
2952
|
-
maxOutputTokens: (
|
|
3084
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
|
|
3085
|
+
maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
|
|
2953
3086
|
stopSequences: options == null ? void 0 : options.stop
|
|
2954
3087
|
}
|
|
2955
3088
|
}));
|
|
@@ -3345,7 +3478,9 @@ var LLMFactory = class _LLMFactory {
|
|
|
3345
3478
|
if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
|
|
3346
3479
|
return new UniversalLLMAdapter(llmConfig);
|
|
3347
3480
|
}
|
|
3348
|
-
throw new
|
|
3481
|
+
throw new ProviderNotFoundException(
|
|
3482
|
+
"LLM",
|
|
3483
|
+
String(llmConfig.provider),
|
|
3349
3484
|
`[LLMFactory] Unknown provider "${llmConfig.provider}". Built-in providers: ${_LLMFactory.listProviders().join(", ")}. Register a custom provider with LLMFactory.register().`
|
|
3350
3485
|
);
|
|
3351
3486
|
}
|
|
@@ -3477,7 +3612,7 @@ var ProviderRegistry = class {
|
|
|
3477
3612
|
return UniversalVectorProvider2;
|
|
3478
3613
|
}
|
|
3479
3614
|
default:
|
|
3480
|
-
throw new
|
|
3615
|
+
throw new ProviderNotFoundException("vector", provider);
|
|
3481
3616
|
}
|
|
3482
3617
|
}
|
|
3483
3618
|
static async createVectorProvider(config) {
|
|
@@ -3495,12 +3630,18 @@ var ProviderRegistry = class {
|
|
|
3495
3630
|
return new SimpleGraphProvider2(config);
|
|
3496
3631
|
}
|
|
3497
3632
|
default:
|
|
3498
|
-
throw new
|
|
3633
|
+
throw new ProviderNotFoundException("graph", provider);
|
|
3499
3634
|
}
|
|
3500
3635
|
}
|
|
3501
3636
|
static createLLMProvider(llmConfig, embeddingConfig) {
|
|
3502
3637
|
return LLMFactory.create(llmConfig, embeddingConfig);
|
|
3503
3638
|
}
|
|
3639
|
+
static registerLLMProvider(name, factory) {
|
|
3640
|
+
LLMFactory.register(name, factory);
|
|
3641
|
+
}
|
|
3642
|
+
static createEmbeddingProvider(embeddingConfig) {
|
|
3643
|
+
return LLMFactory.createEmbeddingProvider(embeddingConfig);
|
|
3644
|
+
}
|
|
3504
3645
|
};
|
|
3505
3646
|
ProviderRegistry.vectorProviders = {};
|
|
3506
3647
|
ProviderRegistry.graphProviders = {};
|
|
@@ -3647,8 +3788,8 @@ var ConfigValidator = class {
|
|
|
3647
3788
|
const errorItems = errors.filter((e) => e.severity === "error");
|
|
3648
3789
|
if (errorItems.length > 0) {
|
|
3649
3790
|
const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
|
|
3650
|
-
throw new
|
|
3651
|
-
${message}
|
|
3791
|
+
throw new ConfigurationException(`[ConfigValidator] Configuration validation failed:
|
|
3792
|
+
${message}`, errorItems);
|
|
3652
3793
|
}
|
|
3653
3794
|
}
|
|
3654
3795
|
};
|
|
@@ -3835,20 +3976,17 @@ var Reranker = class {
|
|
|
3835
3976
|
}
|
|
3836
3977
|
try {
|
|
3837
3978
|
const topN = matches.slice(0, 10);
|
|
3838
|
-
const
|
|
3839
|
-
|
|
3840
|
-
Return the result as a comma-separated list of document indices in order of relevance, from most relevant to least relevant.
|
|
3841
|
-
Use the indices provided in brackets like [0], [1], etc.
|
|
3842
|
-
Only return the indices (e.g., "2,0,1"), nothing else. Do not include brackets in the output.
|
|
3843
|
-
|
|
3844
|
-
Query: "${query}"
|
|
3979
|
+
const response = await llm.chat(
|
|
3980
|
+
[{ role: "user", content: `Query: "${query}"
|
|
3845
3981
|
|
|
3846
3982
|
Documents:
|
|
3847
|
-
${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}
|
|
3848
|
-
const response = await llm.chat(
|
|
3849
|
-
[{ role: "user", content: prompt }],
|
|
3983
|
+
${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}` }],
|
|
3850
3984
|
"",
|
|
3851
|
-
{
|
|
3985
|
+
{
|
|
3986
|
+
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.',
|
|
3987
|
+
temperature: 0,
|
|
3988
|
+
maxTokens: 50
|
|
3989
|
+
}
|
|
3852
3990
|
);
|
|
3853
3991
|
const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
|
|
3854
3992
|
const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
|
|
@@ -4354,6 +4492,18 @@ var QueryProcessor = class {
|
|
|
4354
4492
|
}, normalizedField ? { field: normalizedField } : {}));
|
|
4355
4493
|
}
|
|
4356
4494
|
};
|
|
4495
|
+
const activeValidFields = validFields.length > 0 ? validFields : ["brand", "category", "price", "rating", "stock", "stock_quantity", "status", "name", "title"];
|
|
4496
|
+
const resolveValidField = (fieldStr) => {
|
|
4497
|
+
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();
|
|
4498
|
+
const comparable = (v) => v.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4499
|
+
const cleanedComparable = comparable(cleaned);
|
|
4500
|
+
if (!cleanedComparable) return null;
|
|
4501
|
+
const matchedField = activeValidFields.find((fieldName) => {
|
|
4502
|
+
const candidate = comparable(fieldName);
|
|
4503
|
+
return candidate === cleanedComparable || candidate.includes(cleanedComparable) || cleanedComparable.includes(candidate);
|
|
4504
|
+
});
|
|
4505
|
+
return matchedField != null ? matchedField : null;
|
|
4506
|
+
};
|
|
4357
4507
|
for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
|
|
4358
4508
|
addHint(match[1]);
|
|
4359
4509
|
}
|
|
@@ -4378,7 +4528,13 @@ var QueryProcessor = class {
|
|
|
4378
4528
|
const field = match[1];
|
|
4379
4529
|
const value = match[2];
|
|
4380
4530
|
if (field && !this.isLikelyPromptPhrase(field)) {
|
|
4381
|
-
|
|
4531
|
+
const resolvedField = resolveValidField(field);
|
|
4532
|
+
if (resolvedField) {
|
|
4533
|
+
addHint(value, resolvedField);
|
|
4534
|
+
} else {
|
|
4535
|
+
addHint(value);
|
|
4536
|
+
addHint(field);
|
|
4537
|
+
}
|
|
4382
4538
|
}
|
|
4383
4539
|
}
|
|
4384
4540
|
if (validFields.length > 0) {
|
|
@@ -4424,12 +4580,16 @@ var QueryProcessor = class {
|
|
|
4424
4580
|
];
|
|
4425
4581
|
for (const pattern of fieldValuePatterns) {
|
|
4426
4582
|
for (const match of question.matchAll(pattern)) {
|
|
4427
|
-
const field =
|
|
4583
|
+
const field = (_c = match[1]) != null ? _c : "";
|
|
4428
4584
|
const value = (_d = match[2]) != null ? _d : "";
|
|
4429
|
-
|
|
4430
|
-
|
|
4585
|
+
const resolvedField = resolveValidField(field);
|
|
4586
|
+
if (resolvedField) {
|
|
4587
|
+
addHint(value, resolvedField);
|
|
4431
4588
|
} else {
|
|
4432
4589
|
addHint(value);
|
|
4590
|
+
if (field && !this.isLikelyPromptPhrase(field)) {
|
|
4591
|
+
addHint(field);
|
|
4592
|
+
}
|
|
4433
4593
|
}
|
|
4434
4594
|
}
|
|
4435
4595
|
}
|
|
@@ -4656,7 +4816,7 @@ var LLMRouter = class {
|
|
|
4656
4816
|
|
|
4657
4817
|
// src/utils/UITransformer.ts
|
|
4658
4818
|
init_synonyms();
|
|
4659
|
-
var UITransformer = class {
|
|
4819
|
+
var UITransformer = class _UITransformer {
|
|
4660
4820
|
// ─── Public Entry Points ─────────────────────────────────────────────────
|
|
4661
4821
|
/**
|
|
4662
4822
|
* Heuristic-only transform (no LLM required).
|
|
@@ -4718,7 +4878,7 @@ var UITransformer = class {
|
|
|
4718
4878
|
static async analyzeAndDecide(query, sources, llm) {
|
|
4719
4879
|
let intent;
|
|
4720
4880
|
try {
|
|
4721
|
-
intent = await this.detectIntent(query, llm);
|
|
4881
|
+
intent = await this.detectIntent(query, llm, sources);
|
|
4722
4882
|
console.debug("[UITransformer] Detected intent:", intent);
|
|
4723
4883
|
} catch (err) {
|
|
4724
4884
|
console.warn("[UITransformer] Intent detection failed; using heuristic.", err);
|
|
@@ -4736,11 +4896,23 @@ var UITransformer = class {
|
|
|
4736
4896
|
try {
|
|
4737
4897
|
const context = this.buildContextSummary(sources);
|
|
4738
4898
|
const systemPrompt = this.buildVisualizationSystemPrompt();
|
|
4899
|
+
const profile = this.profileData(sources);
|
|
4900
|
+
const schemaContext = `
|
|
4901
|
+
RETRIEVED DATA SCHEMA:
|
|
4902
|
+
- Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4903
|
+
- Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4904
|
+
- Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4905
|
+
- Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4906
|
+
- Text/Other fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
|
|
4907
|
+
`;
|
|
4739
4908
|
const userPrompt = [
|
|
4740
4909
|
`USER QUESTION: ${query}`,
|
|
4741
4910
|
"",
|
|
4742
4911
|
`DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
|
|
4743
4912
|
"",
|
|
4913
|
+
`RETRIEVED DATA SCHEMA:`,
|
|
4914
|
+
schemaContext,
|
|
4915
|
+
"",
|
|
4744
4916
|
"RETRIEVED DATA (JSON):",
|
|
4745
4917
|
context
|
|
4746
4918
|
].join("\n");
|
|
@@ -4751,6 +4923,20 @@ var UITransformer = class {
|
|
|
4751
4923
|
);
|
|
4752
4924
|
const parsed = this.parseTransformationResponse(rawResponse);
|
|
4753
4925
|
if (parsed) {
|
|
4926
|
+
let isCompatible = true;
|
|
4927
|
+
if (parsed.type === "line_chart" && (profile.dateFields.length === 0 || profile.numericFields.length === 0)) {
|
|
4928
|
+
isCompatible = false;
|
|
4929
|
+
} else if (["bar_chart", "horizontal_bar", "pie_chart", "donut_chart"].includes(parsed.type) && (profile.categoricalFields.length === 0 && profile.numericFields.length === 0)) {
|
|
4930
|
+
isCompatible = false;
|
|
4931
|
+
} else if (parsed.type === "scatter_plot" && profile.numericFields.length < 2) {
|
|
4932
|
+
isCompatible = false;
|
|
4933
|
+
} else if (parsed.type === "metric_card" && profile.numericFields.length === 0) {
|
|
4934
|
+
isCompatible = false;
|
|
4935
|
+
}
|
|
4936
|
+
if (!isCompatible) {
|
|
4937
|
+
console.warn(`[UITransformer] LLM chose incompatible visualization type "${parsed.type}" for retrieved schema. Falling back to heuristic.`);
|
|
4938
|
+
return this.transform(query, sources, void 0, void 0, intent);
|
|
4939
|
+
}
|
|
4754
4940
|
const intentAllowsTable = intent.wantsExplicitTable || ["tabular", "table", "geographic"].includes(intent.visualizationHint);
|
|
4755
4941
|
const intentWantsPieLikeChart = ["pie_chart", "donut_chart"].includes(intent.recommendedChart) || ["composition", "category_breakdown"].includes(intent.visualizationHint);
|
|
4756
4942
|
if (parsed.type === "table" && !intentAllowsTable) {
|
|
@@ -4780,7 +4966,26 @@ var UITransformer = class {
|
|
|
4780
4966
|
* - The intent object can be reused across both the heuristic and LLM paths.
|
|
4781
4967
|
* - It is easy to unit-test intent detection in isolation.
|
|
4782
4968
|
*/
|
|
4783
|
-
static async detectIntent(query, llm) {
|
|
4969
|
+
static async detectIntent(query, llm, sources) {
|
|
4970
|
+
let schemaProfileText = "";
|
|
4971
|
+
if (sources && sources.length > 0) {
|
|
4972
|
+
const profile = this.profileData(sources);
|
|
4973
|
+
const hasProducts = sources.some((item) => this.isProductData(item));
|
|
4974
|
+
schemaProfileText = `
|
|
4975
|
+
RETRIEVED DATA SCHEMA:
|
|
4976
|
+
- Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4977
|
+
- Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4978
|
+
- Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4979
|
+
- Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4980
|
+
- Text fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
|
|
4981
|
+
|
|
4982
|
+
Please choose visualizationHint and recommendedChart dynamically based on the available schema.
|
|
4983
|
+
- Do NOT recommend "trend" or "line_chart" if there are no Date fields.
|
|
4984
|
+
- 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.
|
|
4985
|
+
- If there are multiple metadata fields but no clear categories or numbers, "tabular"/"table" is a highly accurate default.
|
|
4986
|
+
${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".` : ""}
|
|
4987
|
+
`;
|
|
4988
|
+
}
|
|
4784
4989
|
const systemPrompt = `You are an intent classifier for a product-search RAG system.
|
|
4785
4990
|
Given a user query, return ONLY a valid JSON object with this exact shape \u2014 no prose, no markdown:
|
|
4786
4991
|
|
|
@@ -4816,8 +5021,11 @@ RULES:
|
|
|
4816
5021
|
- isTemporal: true if time words appear (trend, historical, over time, last year, monthly, etc.)
|
|
4817
5022
|
- isComparison: true if user compares, ranks, or contrasts entities or categories.
|
|
4818
5023
|
- language: detect from the query text itself; default "en" if uncertain.`;
|
|
5024
|
+
const userPrompt = `QUERY: ${query}${schemaProfileText ? `
|
|
5025
|
+
|
|
5026
|
+
${schemaProfileText}` : ""}`;
|
|
4819
5027
|
const rawResponse = await llm.chat(
|
|
4820
|
-
[{ role: "user", content:
|
|
5028
|
+
[{ role: "user", content: userPrompt }],
|
|
4821
5029
|
"",
|
|
4822
5030
|
{ systemPrompt, temperature: 0 }
|
|
4823
5031
|
);
|
|
@@ -5260,10 +5468,21 @@ RULES:
|
|
|
5260
5468
|
const hasEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
|
|
5261
5469
|
return asksForRecords && (hasNumericPredicate || hasEntityTerms);
|
|
5262
5470
|
}
|
|
5471
|
+
/** @internal kept private for transform() internal logic */
|
|
5263
5472
|
static isProductQuery(query) {
|
|
5473
|
+
return _UITransformer._productQueryTest(query);
|
|
5474
|
+
}
|
|
5475
|
+
/**
|
|
5476
|
+
* Public entry-point for external callers (e.g. Pipeline)
|
|
5477
|
+
* to check whether a query maps to product_browse without triggering an LLM call.
|
|
5478
|
+
*/
|
|
5479
|
+
static isProductQueryPublic(query) {
|
|
5480
|
+
return _UITransformer._productQueryTest(query);
|
|
5481
|
+
}
|
|
5482
|
+
static _productQueryTest(query) {
|
|
5264
5483
|
const q = query.toLowerCase();
|
|
5265
5484
|
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);
|
|
5266
|
-
const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list)\b/.test(q);
|
|
5485
|
+
const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list|explore|exploring)\b/.test(q);
|
|
5267
5486
|
const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
|
|
5268
5487
|
return productTerms && productAction && !nonProductEntityTerms;
|
|
5269
5488
|
}
|
|
@@ -6036,23 +6255,19 @@ function estimateCostUsd(promptTokens, completionTokens, model) {
|
|
|
6036
6255
|
async function scoreHallucination(llm, answer, context) {
|
|
6037
6256
|
const maxContextChars = 3e3;
|
|
6038
6257
|
const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
CONTEXT:
|
|
6258
|
+
try {
|
|
6259
|
+
const raw = await llm.chat(
|
|
6260
|
+
[{ role: "user", content: `CONTEXT:
|
|
6042
6261
|
${truncatedContext}
|
|
6043
6262
|
|
|
6044
6263
|
ANSWER:
|
|
6045
|
-
${answer}
|
|
6046
|
-
|
|
6047
|
-
Return ONLY a valid JSON object with no markdown fences:
|
|
6048
|
-
{"score": <float 0-1>, "reason": "<one sentence>"}
|
|
6049
|
-
|
|
6050
|
-
Where score 0 = fully grounded in context, score 1 = likely hallucinated or unsupported.`;
|
|
6051
|
-
try {
|
|
6052
|
-
const raw = await llm.chat(
|
|
6053
|
-
[{ role: "user", content: prompt }],
|
|
6264
|
+
${answer}` }],
|
|
6054
6265
|
"",
|
|
6055
|
-
{
|
|
6266
|
+
{
|
|
6267
|
+
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.',
|
|
6268
|
+
temperature: 0,
|
|
6269
|
+
maxTokens: 120
|
|
6270
|
+
}
|
|
6056
6271
|
);
|
|
6057
6272
|
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
6058
6273
|
if (!jsonMatch) return void 0;
|
|
@@ -6112,7 +6327,10 @@ var Pipeline = class {
|
|
|
6112
6327
|
- 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.
|
|
6113
6328
|
- Do NOT try to format product lists as tables.
|
|
6114
6329
|
`;
|
|
6115
|
-
this.config.llm.systemPrompt
|
|
6330
|
+
const userPromptPrefix = this.config.llm.systemPrompt ? `${this.config.llm.systemPrompt}
|
|
6331
|
+
|
|
6332
|
+
` : "";
|
|
6333
|
+
this.config.llm.systemPrompt = `${userPromptPrefix}${chartInstruction}`;
|
|
6116
6334
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
6117
6335
|
this.llmRouter = new LLMRouter(this.config);
|
|
6118
6336
|
const { llmProvider: resolvedLLM, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
@@ -6290,11 +6508,11 @@ var Pipeline = class {
|
|
|
6290
6508
|
*/
|
|
6291
6509
|
askStream(_0) {
|
|
6292
6510
|
return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
|
|
6293
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
6511
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
6294
6512
|
yield new __await(this.initialize());
|
|
6295
6513
|
const ns = namespace != null ? namespace : this.config.projectId;
|
|
6296
6514
|
const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
|
|
6297
|
-
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
|
|
6515
|
+
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0.3;
|
|
6298
6516
|
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
6299
6517
|
const requestStart = performance.now();
|
|
6300
6518
|
try {
|
|
@@ -6313,12 +6531,13 @@ var Pipeline = class {
|
|
|
6313
6531
|
const embedStart = performance.now();
|
|
6314
6532
|
const cacheKey = `${ns}::${searchQuery}`;
|
|
6315
6533
|
const cachedVector = this.embeddingCache.get(cacheKey);
|
|
6534
|
+
const useGraph = this.graphDB && ((_h = this.config.rag) == null ? void 0 : _h.useGraphRetrieval);
|
|
6316
6535
|
const [strategyResult, embeddedVector] = yield new __await(Promise.all([
|
|
6317
6536
|
QueryProcessor.determineRetrievalStrategy(
|
|
6318
6537
|
searchQuery,
|
|
6319
|
-
this.llmRouter.get("fast"),
|
|
6320
|
-
(
|
|
6321
|
-
(
|
|
6538
|
+
useGraph ? this.llmRouter.get("fast") : void 0,
|
|
6539
|
+
(_i = this.config.rag) == null ? void 0 : _i.graphKeywords,
|
|
6540
|
+
(_j = this.config.rag) == null ? void 0 : _j.vectorKeywords
|
|
6322
6541
|
),
|
|
6323
6542
|
// Embed immediately regardless of strategy — costs nothing if cached.
|
|
6324
6543
|
// If strategy turns out to be 'graph'-only we just won't use the vector.
|
|
@@ -6328,7 +6547,7 @@ var Pipeline = class {
|
|
|
6328
6547
|
if (!cachedVector && queryVector.length > 0) {
|
|
6329
6548
|
this.embeddingCache.set(cacheKey, queryVector);
|
|
6330
6549
|
}
|
|
6331
|
-
const graphData = (strategyResult === "graph" || strategyResult === "both") && this.graphDB && ((
|
|
6550
|
+
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;
|
|
6332
6551
|
const hasMetadataFilter = filter.metadata && Object.keys(filter.metadata).length > 0;
|
|
6333
6552
|
const hasNumericPredicates = Array.isArray(filter.__numericPredicates) && filter.__numericPredicates.length > 0;
|
|
6334
6553
|
const wantsExhaustiveList = hasNumericPredicates || hasMetadataFilter || /\b(list|all|show|provide|give|get|browse|find|search|display)\b/i.test(question);
|
|
@@ -6341,7 +6560,7 @@ var Pipeline = class {
|
|
|
6341
6560
|
const structuredSources = this.applyStructuredFilters(rawSources, filter);
|
|
6342
6561
|
let fullSources = hasNumericPredicates ? structuredSources : structuredSources.filter((m) => m.score >= scoreThreshold);
|
|
6343
6562
|
const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
|
|
6344
|
-
if (!hasNumericPredicates && ((
|
|
6563
|
+
if (!hasNumericPredicates && ((_l = this.config.rag) == null ? void 0 : _l.useReranking)) {
|
|
6345
6564
|
fullSources = yield new __await(this.reranker.rerank(fullSources, question, rerankLimit));
|
|
6346
6565
|
} else if (!wantsExhaustiveList) {
|
|
6347
6566
|
fullSources = fullSources.slice(0, topK);
|
|
@@ -6372,34 +6591,117 @@ VECTOR CONTEXT:
|
|
|
6372
6591
|
${context}`;
|
|
6373
6592
|
}
|
|
6374
6593
|
const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
|
|
6375
|
-
const
|
|
6376
|
-
|
|
6377
|
-
(
|
|
6594
|
+
const cachedSchema = allMetadataKeys.length > 0 ? SchemaMapper.getCached(ns, allMetadataKeys) : void 0;
|
|
6595
|
+
if (allMetadataKeys.length > 0 && !cachedSchema) {
|
|
6596
|
+
SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => {
|
|
6597
|
+
});
|
|
6598
|
+
}
|
|
6599
|
+
const isProductQ = UITransformer.isProductQueryPublic(question);
|
|
6600
|
+
const uiTransformationPromise = isProductQ ? Promise.resolve(
|
|
6601
|
+
UITransformer.transform(
|
|
6378
6602
|
question,
|
|
6379
6603
|
sources,
|
|
6380
|
-
|
|
6381
|
-
|
|
6604
|
+
this.config,
|
|
6605
|
+
cachedSchema,
|
|
6606
|
+
{ visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
|
|
6382
6607
|
)
|
|
6608
|
+
) : this.generateUiTransformation(
|
|
6609
|
+
question,
|
|
6610
|
+
sources,
|
|
6611
|
+
cachedSchema,
|
|
6612
|
+
hasNumericPredicates
|
|
6383
6613
|
).catch((uiError) => {
|
|
6384
6614
|
console.warn("[Pipeline] UI transformation failed concurrently:", uiError);
|
|
6385
|
-
return UITransformer.transform(question, sources, this.config);
|
|
6615
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6386
6616
|
});
|
|
6617
|
+
let hallucinationScoringPromise = Promise.resolve(void 0);
|
|
6387
6618
|
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.)";
|
|
6619
|
+
const isClaude37 = this.config.llm.model.includes("claude-3-7-sonnet");
|
|
6620
|
+
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);
|
|
6621
|
+
const modelNameLower = this.config.llm.model.toLowerCase();
|
|
6622
|
+
const isReasoningModel = modelNameLower.includes("-r1") || modelNameLower.includes("deepseek-r1") || modelNameLower.includes("reasoning") || modelNameLower.includes("thinking");
|
|
6623
|
+
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);
|
|
6624
|
+
let finalRestrictionSuffix = restrictionSuffix;
|
|
6625
|
+
if (isSimulatedThinking) {
|
|
6626
|
+
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.`)";
|
|
6627
|
+
}
|
|
6388
6628
|
const hardenedHistory = [...history];
|
|
6389
|
-
const userQuestion = { role: "user", content: question +
|
|
6629
|
+
const userQuestion = { role: "user", content: question + finalRestrictionSuffix };
|
|
6390
6630
|
const messages = [...hardenedHistory, userQuestion];
|
|
6391
|
-
const systemPrompt = (
|
|
6631
|
+
const systemPrompt = (_s = this.config.llm.systemPrompt) != null ? _s : "";
|
|
6392
6632
|
const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
6393
6633
|
let fullReply = "";
|
|
6634
|
+
let thinkingText = "";
|
|
6635
|
+
let thinkingStartMs = performance.now();
|
|
6636
|
+
let thinkingDurationMs = 0;
|
|
6394
6637
|
const generateStart = performance.now();
|
|
6395
6638
|
if (this.llmProvider.chatStream) {
|
|
6396
6639
|
const stream = this.llmProvider.chatStream(messages, context);
|
|
6397
6640
|
if (!stream) throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
|
|
6641
|
+
let inThinking = false;
|
|
6642
|
+
let textBuffer = "";
|
|
6398
6643
|
try {
|
|
6399
6644
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
6400
6645
|
const chunk = temp.value;
|
|
6401
|
-
|
|
6402
|
-
|
|
6646
|
+
if (chunk.startsWith("\0THINKING\0")) {
|
|
6647
|
+
const thinkingDelta = chunk.slice("\0THINKING\0".length);
|
|
6648
|
+
thinkingText += thinkingDelta;
|
|
6649
|
+
thinkingDurationMs = performance.now() - thinkingStartMs;
|
|
6650
|
+
yield { type: "thinking", text: thinkingDelta };
|
|
6651
|
+
continue;
|
|
6652
|
+
}
|
|
6653
|
+
if (isSimulatedThinking) {
|
|
6654
|
+
textBuffer += chunk;
|
|
6655
|
+
while (textBuffer.length > 0) {
|
|
6656
|
+
if (!inThinking) {
|
|
6657
|
+
const thinkIndex = textBuffer.indexOf("<think>");
|
|
6658
|
+
if (thinkIndex !== -1) {
|
|
6659
|
+
const before = textBuffer.slice(0, thinkIndex);
|
|
6660
|
+
if (before) {
|
|
6661
|
+
fullReply += before;
|
|
6662
|
+
yield before;
|
|
6663
|
+
}
|
|
6664
|
+
inThinking = true;
|
|
6665
|
+
thinkingStartMs = performance.now();
|
|
6666
|
+
textBuffer = textBuffer.slice(thinkIndex + "<think>".length);
|
|
6667
|
+
} else {
|
|
6668
|
+
const maxSafeLength = textBuffer.length - "<think>".length;
|
|
6669
|
+
if (maxSafeLength > 0) {
|
|
6670
|
+
const safeText = textBuffer.slice(0, maxSafeLength);
|
|
6671
|
+
fullReply += safeText;
|
|
6672
|
+
yield safeText;
|
|
6673
|
+
textBuffer = textBuffer.slice(maxSafeLength);
|
|
6674
|
+
}
|
|
6675
|
+
break;
|
|
6676
|
+
}
|
|
6677
|
+
} else {
|
|
6678
|
+
const endThinkIndex = textBuffer.indexOf("</think>");
|
|
6679
|
+
if (endThinkIndex !== -1) {
|
|
6680
|
+
const thinkingDelta = textBuffer.slice(0, endThinkIndex);
|
|
6681
|
+
if (thinkingDelta) {
|
|
6682
|
+
thinkingText += thinkingDelta;
|
|
6683
|
+
thinkingDurationMs = performance.now() - thinkingStartMs;
|
|
6684
|
+
yield { type: "thinking", text: thinkingDelta };
|
|
6685
|
+
}
|
|
6686
|
+
inThinking = false;
|
|
6687
|
+
textBuffer = textBuffer.slice(endThinkIndex + "</think>".length);
|
|
6688
|
+
} else {
|
|
6689
|
+
const maxSafeLength = textBuffer.length - "</think>".length;
|
|
6690
|
+
if (maxSafeLength > 0) {
|
|
6691
|
+
const thinkingDelta = textBuffer.slice(0, maxSafeLength);
|
|
6692
|
+
thinkingText += thinkingDelta;
|
|
6693
|
+
thinkingDurationMs = performance.now() - thinkingStartMs;
|
|
6694
|
+
yield { type: "thinking", text: thinkingDelta };
|
|
6695
|
+
textBuffer = textBuffer.slice(maxSafeLength);
|
|
6696
|
+
}
|
|
6697
|
+
break;
|
|
6698
|
+
}
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
} else {
|
|
6702
|
+
fullReply += chunk;
|
|
6703
|
+
yield chunk;
|
|
6704
|
+
}
|
|
6403
6705
|
}
|
|
6404
6706
|
} catch (temp) {
|
|
6405
6707
|
error = [temp];
|
|
@@ -6411,17 +6713,42 @@ ${context}`;
|
|
|
6411
6713
|
throw error[0];
|
|
6412
6714
|
}
|
|
6413
6715
|
}
|
|
6716
|
+
if (isSimulatedThinking && textBuffer.length > 0) {
|
|
6717
|
+
if (inThinking) {
|
|
6718
|
+
thinkingText += textBuffer;
|
|
6719
|
+
yield { type: "thinking", text: textBuffer };
|
|
6720
|
+
} else {
|
|
6721
|
+
fullReply += textBuffer;
|
|
6722
|
+
yield textBuffer;
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6414
6725
|
} else {
|
|
6415
6726
|
const reply = yield new __await(this.llmProvider.chat(messages, context));
|
|
6416
|
-
|
|
6417
|
-
|
|
6727
|
+
if (isSimulatedThinking) {
|
|
6728
|
+
const thinkStart = reply.indexOf("<think>");
|
|
6729
|
+
const thinkEnd = reply.indexOf("</think>");
|
|
6730
|
+
if (thinkStart !== -1 && thinkEnd !== -1 && thinkEnd > thinkStart) {
|
|
6731
|
+
thinkingText = reply.slice(thinkStart + "<think>".length, thinkEnd);
|
|
6732
|
+
fullReply = reply.slice(0, thinkStart) + reply.slice(thinkEnd + "</think>".length);
|
|
6733
|
+
thinkingDurationMs = 100;
|
|
6734
|
+
} else {
|
|
6735
|
+
fullReply = reply;
|
|
6736
|
+
}
|
|
6737
|
+
} else {
|
|
6738
|
+
fullReply = reply;
|
|
6739
|
+
}
|
|
6740
|
+
yield fullReply;
|
|
6741
|
+
}
|
|
6742
|
+
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;
|
|
6743
|
+
if (runHallucination) {
|
|
6744
|
+
hallucinationScoringPromise = scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0);
|
|
6418
6745
|
}
|
|
6419
6746
|
const generateMs = performance.now() - generateStart;
|
|
6420
6747
|
const totalMs = performance.now() - requestStart;
|
|
6421
6748
|
const latency = {
|
|
6422
6749
|
embedMs: Math.round(embedMs),
|
|
6423
6750
|
retrieveMs: Math.round(retrieveMs),
|
|
6424
|
-
rerankMs: ((
|
|
6751
|
+
rerankMs: ((_v = this.config.rag) == null ? void 0 : _v.useReranking) ? Math.round(rerankMs) : void 0,
|
|
6425
6752
|
generateMs: Math.round(generateMs),
|
|
6426
6753
|
totalMs: Math.round(totalMs)
|
|
6427
6754
|
};
|
|
@@ -6434,12 +6761,16 @@ ${context}`;
|
|
|
6434
6761
|
totalTokens: promptTokens + completionTokens,
|
|
6435
6762
|
estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
|
|
6436
6763
|
};
|
|
6437
|
-
const
|
|
6764
|
+
const [uiTransformation, hallucinationResult] = yield new __await(Promise.all([
|
|
6765
|
+
uiTransformationPromise,
|
|
6766
|
+
hallucinationScoringPromise
|
|
6767
|
+
]));
|
|
6768
|
+
const trace = __spreadValues({
|
|
6438
6769
|
requestId,
|
|
6439
6770
|
query: question,
|
|
6440
6771
|
rewrittenQuery,
|
|
6441
6772
|
systemPrompt,
|
|
6442
|
-
userPrompt: question +
|
|
6773
|
+
userPrompt: question + finalRestrictionSuffix,
|
|
6443
6774
|
chunks: sources.map((s) => {
|
|
6444
6775
|
var _a2;
|
|
6445
6776
|
return {
|
|
@@ -6453,22 +6784,19 @@ ${context}`;
|
|
|
6453
6784
|
latency,
|
|
6454
6785
|
tokens,
|
|
6455
6786
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6456
|
-
}
|
|
6457
|
-
|
|
6787
|
+
}, hallucinationResult ? {
|
|
6788
|
+
hallucinationScore: hallucinationResult.score,
|
|
6789
|
+
hallucinationReason: hallucinationResult.reason
|
|
6790
|
+
} : {});
|
|
6458
6791
|
yield {
|
|
6459
6792
|
reply: "",
|
|
6460
6793
|
sources,
|
|
6461
6794
|
graphData,
|
|
6462
6795
|
ui_transformation: uiTransformation,
|
|
6463
|
-
trace
|
|
6796
|
+
trace,
|
|
6797
|
+
thinking: thinkingText || void 0,
|
|
6798
|
+
thinkingMs: thinkingDurationMs > 0 ? Math.round(thinkingDurationMs) : void 0
|
|
6464
6799
|
};
|
|
6465
|
-
scoreHallucination(this.llmProvider, fullReply, context).then((hallucinationResult) => {
|
|
6466
|
-
if (hallucinationResult) {
|
|
6467
|
-
trace.hallucinationScore = hallucinationResult.score;
|
|
6468
|
-
trace.hallucinationReason = hallucinationResult.reason;
|
|
6469
|
-
}
|
|
6470
|
-
}).catch(() => {
|
|
6471
|
-
});
|
|
6472
6800
|
} catch (error2) {
|
|
6473
6801
|
throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
6474
6802
|
}
|
|
@@ -6478,18 +6806,30 @@ ${context}`;
|
|
|
6478
6806
|
* Universal retrieval method combining all enabled providers.
|
|
6479
6807
|
* Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
|
|
6480
6808
|
*/
|
|
6481
|
-
async generateUiTransformation(question, sources,
|
|
6809
|
+
async generateUiTransformation(question, sources, cachedSchema, forceDeterministic = false) {
|
|
6810
|
+
var _a;
|
|
6482
6811
|
if (!sources || sources.length === 0) {
|
|
6483
|
-
return UITransformer.transform(question, sources, this.config,
|
|
6812
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6484
6813
|
}
|
|
6485
|
-
if (
|
|
6486
|
-
return UITransformer.transform(
|
|
6814
|
+
if (UITransformer.isProductQueryPublic(question)) {
|
|
6815
|
+
return UITransformer.transform(
|
|
6816
|
+
question,
|
|
6817
|
+
sources,
|
|
6818
|
+
this.config,
|
|
6819
|
+
cachedSchema,
|
|
6820
|
+
{ visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
|
|
6821
|
+
);
|
|
6822
|
+
}
|
|
6823
|
+
const isLocalProvider = this.config.llm.provider === "ollama";
|
|
6824
|
+
const disableLlmUiTransform = ((_a = this.config.llm.options) == null ? void 0 : _a.disableLlmUiTransform) === true;
|
|
6825
|
+
if (forceDeterministic || isLocalProvider || disableLlmUiTransform) {
|
|
6826
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6487
6827
|
}
|
|
6488
6828
|
try {
|
|
6489
6829
|
return await UITransformer.analyzeAndDecide(question, sources, this.llmRouter.get("fast"));
|
|
6490
6830
|
} catch (err) {
|
|
6491
6831
|
console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
|
|
6492
|
-
return UITransformer.transform(question, sources, this.config,
|
|
6832
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6493
6833
|
}
|
|
6494
6834
|
}
|
|
6495
6835
|
applyStructuredFilters(sources, filter) {
|
|
@@ -6573,25 +6913,29 @@ ${context}`;
|
|
|
6573
6913
|
return Number.isFinite(numeric) ? numeric : null;
|
|
6574
6914
|
}
|
|
6575
6915
|
async retrieve(query, options) {
|
|
6576
|
-
var _a, _b, _c, _d, _e, _f;
|
|
6916
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
6577
6917
|
const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
|
|
6578
6918
|
const topK = (_b = options.topK) != null ? _b : 5;
|
|
6579
6919
|
const cacheKey = `${ns}::${query}`;
|
|
6580
6920
|
let queryVector = this.embeddingCache.get(cacheKey);
|
|
6581
|
-
const
|
|
6921
|
+
const useGraph = this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval);
|
|
6922
|
+
const strategy = await QueryProcessor.determineRetrievalStrategy(
|
|
6923
|
+
query,
|
|
6924
|
+
useGraph ? this.llmRouter.get("fast") : void 0
|
|
6925
|
+
);
|
|
6582
6926
|
console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
|
|
6583
6927
|
const [retrievedVector, graphData] = await Promise.all([
|
|
6584
6928
|
// Only embed if we need vector search (strategy is 'vector' or 'both')
|
|
6585
6929
|
strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
|
|
6586
6930
|
// Only query graph if we need graph search (strategy is 'graph' or 'both')
|
|
6587
|
-
(strategy === "graph" || strategy === "both") && this.graphDB && ((
|
|
6931
|
+
(strategy === "graph" || strategy === "both") && this.graphDB && ((_d = this.config.rag) == null ? void 0 : _d.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
|
|
6588
6932
|
]);
|
|
6589
6933
|
if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
|
|
6590
6934
|
this.embeddingCache.set(cacheKey, retrievedVector);
|
|
6591
6935
|
queryVector = retrievedVector;
|
|
6592
6936
|
}
|
|
6593
|
-
const baseFilter = __spreadProps(__spreadValues({}, (
|
|
6594
|
-
const numericPredicates = QueryProcessor.extractNumericPredicates(query, (
|
|
6937
|
+
const baseFilter = __spreadProps(__spreadValues({}, (_e = options.filter) != null ? _e : {}), { queryText: query });
|
|
6938
|
+
const numericPredicates = QueryProcessor.extractNumericPredicates(query, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
|
|
6595
6939
|
if (numericPredicates.length > 0) {
|
|
6596
6940
|
baseFilter.__numericPredicates = numericPredicates;
|
|
6597
6941
|
}
|
|
@@ -6602,7 +6946,7 @@ ${context}`;
|
|
|
6602
6946
|
) : [];
|
|
6603
6947
|
const resolvedSources = [];
|
|
6604
6948
|
for (const source of sources) {
|
|
6605
|
-
const parentId = (
|
|
6949
|
+
const parentId = (_g = source.metadata) == null ? void 0 : _g.parent_id;
|
|
6606
6950
|
if (parentId) {
|
|
6607
6951
|
console.log(`[Pipeline] Multi-Vector: Found child chunk. Parent ID: ${parentId}`);
|
|
6608
6952
|
resolvedSources.push(source);
|
|
@@ -6625,10 +6969,13 @@ New Question: ${question}
|
|
|
6625
6969
|
Optimized Search Query:`;
|
|
6626
6970
|
const rewrite = await this.llmProvider.chat(
|
|
6627
6971
|
[
|
|
6628
|
-
{ role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
|
|
6629
6972
|
{ role: "user", content: prompt }
|
|
6630
6973
|
],
|
|
6631
|
-
""
|
|
6974
|
+
"",
|
|
6975
|
+
{
|
|
6976
|
+
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.",
|
|
6977
|
+
temperature: 0
|
|
6978
|
+
}
|
|
6632
6979
|
);
|
|
6633
6980
|
return rewrite.trim() || question;
|
|
6634
6981
|
}
|
|
@@ -6652,10 +6999,13 @@ ${context}
|
|
|
6652
6999
|
Suggestions:`;
|
|
6653
7000
|
const response = await this.llmProvider.chat(
|
|
6654
7001
|
[
|
|
6655
|
-
{ role: "system", content: "You are a helpful assistant that generates search suggestions." },
|
|
6656
7002
|
{ role: "user", content: prompt }
|
|
6657
7003
|
],
|
|
6658
|
-
""
|
|
7004
|
+
"",
|
|
7005
|
+
{
|
|
7006
|
+
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.",
|
|
7007
|
+
temperature: 0
|
|
7008
|
+
}
|
|
6659
7009
|
);
|
|
6660
7010
|
const match = response.match(/\[[\s\S]*\]/);
|
|
6661
7011
|
if (match) {
|
|
@@ -6994,6 +7344,10 @@ function createStreamHandler(configOrPlugin) {
|
|
|
6994
7344
|
if (!isActive) break;
|
|
6995
7345
|
if (typeof chunk === "string") {
|
|
6996
7346
|
enqueue(sseTextFrame(chunk));
|
|
7347
|
+
} else if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "thinking") {
|
|
7348
|
+
enqueue(`data: ${JSON.stringify(chunk)}
|
|
7349
|
+
|
|
7350
|
+
`);
|
|
6997
7351
|
} else {
|
|
6998
7352
|
enqueue(sseMetaFrame(chunk));
|
|
6999
7353
|
const responseChunk = chunk;
|