@retrivora-ai/rag-engine 1.9.3 → 1.9.7
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 +59 -7
- package/dist/{ILLMProvider-Bw2A28nU.d.mts → ILLMProvider-Bhk6zJOK.d.mts} +43 -7
- package/dist/{ILLMProvider-Bw2A28nU.d.ts → ILLMProvider-Bhk6zJOK.d.ts} +43 -7
- package/dist/handlers/index.d.mts +2 -2
- package/dist/handlers/index.d.ts +2 -2
- package/dist/handlers/index.js +737 -237
- package/dist/handlers/index.mjs +736 -237
- package/dist/index-B9J_XEh0.d.ts +187 -0
- package/dist/index-BJ4cd-t5.d.mts +187 -0
- package/dist/{index-B70ZLkfG.d.mts → index-Bu7T6xgr.d.ts} +20 -3
- package/dist/{index-DVu-mkAM.d.ts → index-C3SVtPYg.d.mts} +20 -3
- package/dist/index.css +237 -10
- package/dist/index.d.mts +13 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +365 -164
- package/dist/index.mjs +350 -158
- package/dist/server.d.mts +15 -94
- package/dist/server.d.ts +15 -94
- package/dist/server.js +850 -239
- package/dist/server.mjs +839 -238
- package/package.json +2 -4
- 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 +168 -148
- package/src/app/layout.tsx +5 -18
- package/src/app/types.ts +17 -17
- package/src/components/ChatWidget.tsx +3 -1
- package/src/components/ChatWindow.tsx +5 -1
- package/src/components/DocViewer.tsx +71 -5
- package/src/components/Documentation.tsx +74 -11
- package/src/components/MessageBubble.tsx +39 -2
- package/src/components/ThinkingBlock.tsx +75 -0
- package/src/components/VisualizationRenderer.tsx +27 -1
- package/src/components/constants.tsx +275 -0
- package/src/config/RagConfig.ts +47 -0
- package/src/config/constants.ts +1 -0
- package/src/config/serverConfig.ts +25 -0
- package/src/core/ConfigResolver.ts +73 -22
- package/src/core/ConfigValidator.ts +2 -1
- package/src/core/Pipeline.ts +226 -68
- package/src/core/ProviderRegistry.ts +16 -7
- package/src/core/QueryProcessor.ts +38 -4
- package/src/core/Retrivora.ts +91 -0
- package/src/core/VectorPlugin.ts +62 -8
- package/src/exceptions/index.ts +111 -0
- package/src/handlers/index.ts +73 -0
- package/src/hooks/useRagChat.ts +30 -5
- package/src/index.ts +27 -1
- package/src/lib/plugin.ts +24 -0
- package/src/llm/LLMFactory.ts +8 -4
- 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 +30 -1
- package/src/types/chat.ts +9 -0
- package/src/types/props.ts +38 -1
- 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
|
@@ -6,9 +6,6 @@ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
7
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
8
8
|
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
9
|
-
var __typeError = (msg) => {
|
|
10
|
-
throw TypeError(msg);
|
|
11
|
-
};
|
|
12
9
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
10
|
var __spreadValues = (a, b) => {
|
|
14
11
|
for (var prop in b || (b = {}))
|
|
@@ -56,34 +53,6 @@ var __asyncGenerator = (__this, __arguments, generator) => {
|
|
|
56
53
|
}, method = (k, call, wait, clear) => it[k] = (x) => (call = new Promise((yes, no, run) => (run = () => resume(k, x, yes, no), q ? q.then(run) : run())), clear = () => q === wait && (q = 0), q = wait = call.then(clear, clear), call), q, it = {};
|
|
57
54
|
return generator = generator.apply(__this, __arguments), it[__knownSymbol("asyncIterator")] = () => it, method("next"), method("throw"), method("return"), it;
|
|
58
55
|
};
|
|
59
|
-
var __yieldStar = (value) => {
|
|
60
|
-
var obj = value[__knownSymbol("asyncIterator")], isAwait = false, method, it = {};
|
|
61
|
-
if (obj == null) {
|
|
62
|
-
obj = value[__knownSymbol("iterator")]();
|
|
63
|
-
method = (k) => it[k] = (x) => obj[k](x);
|
|
64
|
-
} else {
|
|
65
|
-
obj = obj.call(value);
|
|
66
|
-
method = (k) => it[k] = (v) => {
|
|
67
|
-
if (isAwait) {
|
|
68
|
-
isAwait = false;
|
|
69
|
-
if (k === "throw") throw v;
|
|
70
|
-
return v;
|
|
71
|
-
}
|
|
72
|
-
isAwait = true;
|
|
73
|
-
return {
|
|
74
|
-
done: false,
|
|
75
|
-
value: new __await(new Promise((resolve) => {
|
|
76
|
-
var x = obj[k](v);
|
|
77
|
-
if (!(x instanceof Object)) __typeError("Object expected");
|
|
78
|
-
resolve(x);
|
|
79
|
-
}), 1)
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
return it[__knownSymbol("iterator")] = () => it, method("next"), "throw" in obj ? method("throw") : it.throw = (x) => {
|
|
84
|
-
throw x;
|
|
85
|
-
}, "return" in obj && method("return"), it;
|
|
86
|
-
};
|
|
87
56
|
var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it);
|
|
88
57
|
|
|
89
58
|
// src/utils/templateUtils.ts
|
|
@@ -640,6 +609,11 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
640
609
|
CREATE INDEX IF NOT EXISTS ${this.uploadTable}_embedding_idx
|
|
641
610
|
ON ${this.uploadTable}
|
|
642
611
|
USING hnsw (embedding vector_cosine_ops)
|
|
612
|
+
`);
|
|
613
|
+
await client.query(`
|
|
614
|
+
CREATE INDEX IF NOT EXISTS "${this.uploadTable}_fts_idx"
|
|
615
|
+
ON "${this.uploadTable}"
|
|
616
|
+
USING gin (to_tsvector('english', content))
|
|
643
617
|
`);
|
|
644
618
|
const res = await client.query(`
|
|
645
619
|
SELECT table_name
|
|
@@ -710,6 +684,11 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
710
684
|
CREATE INDEX IF NOT EXISTS "${tableName}_embedding_idx"
|
|
711
685
|
ON "${tableName}"
|
|
712
686
|
USING hnsw (embedding vector_cosine_ops)
|
|
687
|
+
`);
|
|
688
|
+
await client.query(`
|
|
689
|
+
CREATE INDEX IF NOT EXISTS "${tableName}_fts_idx"
|
|
690
|
+
ON "${tableName}"
|
|
691
|
+
USING gin (to_tsvector('english', content))
|
|
713
692
|
`);
|
|
714
693
|
if (!this.tables.includes(tableName)) {
|
|
715
694
|
this.tables.push(tableName);
|
|
@@ -801,8 +780,11 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
801
780
|
const paramIdx = baseOffset + filterParams.length;
|
|
802
781
|
const synonyms = (_a3 = FIELD_SYNONYMS[key]) != null ? _a3 : [];
|
|
803
782
|
const keysToCheck = [key, ...synonyms];
|
|
804
|
-
const coalesceExprs = keysToCheck.
|
|
805
|
-
|
|
783
|
+
const coalesceExprs = keysToCheck.flatMap((k) => [
|
|
784
|
+
`metadata->>'${k}'`,
|
|
785
|
+
`to_jsonb(t)->>'${k}'`
|
|
786
|
+
]);
|
|
787
|
+
return `COALESCE(${coalesceExprs.map((expr) => `LOWER(${expr})`).join(", ")}) = LOWER($${paramIdx})`;
|
|
806
788
|
});
|
|
807
789
|
whereClause = `WHERE ${conditions.join(" AND ")}`;
|
|
808
790
|
}
|
|
@@ -813,16 +795,35 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
813
795
|
CASE WHEN LOWER(val) IN (${entityHints.map((h) => `'${h.replace(/'/g, "''")}'`).join(", ")})
|
|
814
796
|
THEN 1.0 ELSE 0.0 END
|
|
815
797
|
), 0)
|
|
816
|
-
FROM jsonb_each_text(
|
|
798
|
+
FROM jsonb_each_text(COALESCE(v.metadata, k.metadata)) AS kv(key, val)
|
|
817
799
|
WHERE key IN (${this.searchFields.map((f) => `'${f}'`).join(", ")})
|
|
818
800
|
) * 3.0` : "";
|
|
819
801
|
sqlQuery = `
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
802
|
+
WITH vector_search AS (
|
|
803
|
+
SELECT *, (1 - (embedding <=> $1::vector)) AS vector_score
|
|
804
|
+
FROM "${table}" t
|
|
805
|
+
${whereClause}
|
|
806
|
+
ORDER BY embedding <=> $1::vector ASC
|
|
807
|
+
LIMIT ${tableLimit}
|
|
808
|
+
),
|
|
809
|
+
keyword_search AS (
|
|
810
|
+
SELECT *, COALESCE(ts_rank(to_tsvector('english', content), to_tsquery('english', $2)), 0) AS keyword_score
|
|
811
|
+
FROM "${table}" t
|
|
812
|
+
WHERE ${whereClause ? whereClause.replace("WHERE", "") : "TRUE"}
|
|
813
|
+
AND to_tsvector('english', content) @@ to_tsquery('english', $2)
|
|
814
|
+
ORDER BY keyword_score DESC
|
|
815
|
+
LIMIT ${tableLimit}
|
|
816
|
+
)
|
|
817
|
+
SELECT
|
|
818
|
+
COALESCE(v.id, k.id) AS id,
|
|
819
|
+
COALESCE(v.namespace, k.namespace) AS namespace,
|
|
820
|
+
COALESCE(v.content, k.content) AS content,
|
|
821
|
+
COALESCE(v.metadata, k.metadata) AS metadata,
|
|
822
|
+
COALESCE(v.vector_score, 0) AS vector_score,
|
|
823
|
+
COALESCE(k.keyword_score, 0) AS keyword_score,
|
|
824
|
+
(COALESCE(v.vector_score, 0) + COALESCE(k.keyword_score, 0) * 2.0 ${exactNameScoreExpr}) AS hybrid_score
|
|
825
|
+
FROM vector_search v
|
|
826
|
+
FULL OUTER JOIN keyword_search k ON v.id = k.id
|
|
826
827
|
ORDER BY hybrid_score DESC
|
|
827
828
|
LIMIT ${tableLimit}
|
|
828
829
|
`;
|
|
@@ -833,7 +834,7 @@ var init_MultiTablePostgresProvider = __esm({
|
|
|
833
834
|
(1 - (embedding <=> $1::vector)) AS hybrid_score
|
|
834
835
|
FROM "${table}" t
|
|
835
836
|
${whereClause}
|
|
836
|
-
ORDER BY
|
|
837
|
+
ORDER BY embedding <=> $1::vector ASC
|
|
837
838
|
LIMIT ${tableLimit}
|
|
838
839
|
`;
|
|
839
840
|
params = [vectorLiteral, ...filterParams];
|
|
@@ -1039,7 +1040,11 @@ var init_MongoDBProvider = __esm({
|
|
|
1039
1040
|
if (key === "namespace") {
|
|
1040
1041
|
vectorSearchFilter.namespace = value;
|
|
1041
1042
|
} else {
|
|
1042
|
-
|
|
1043
|
+
if (typeof value === "string") {
|
|
1044
|
+
matchFilter[key] = { $regex: new RegExp(`^${value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "i") };
|
|
1045
|
+
} else {
|
|
1046
|
+
matchFilter[key] = value;
|
|
1047
|
+
}
|
|
1043
1048
|
}
|
|
1044
1049
|
}
|
|
1045
1050
|
const pipeline = [
|
|
@@ -2112,7 +2117,7 @@ function readEnum(env, name, fallback, allowed) {
|
|
|
2112
2117
|
throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
|
|
2113
2118
|
}
|
|
2114
2119
|
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;
|
|
2120
|
+
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
2121
|
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
2122
|
const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
|
|
2118
2123
|
const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
|
|
@@ -2182,7 +2187,7 @@ function getEnvConfig(env = process.env, base) {
|
|
|
2182
2187
|
rest: "default",
|
|
2183
2188
|
custom: "default"
|
|
2184
2189
|
};
|
|
2185
|
-
return {
|
|
2190
|
+
return __spreadValues({
|
|
2186
2191
|
projectId,
|
|
2187
2192
|
vectorDb: {
|
|
2188
2193
|
provider: vectorProvider,
|
|
@@ -2198,7 +2203,9 @@ function getEnvConfig(env = process.env, base) {
|
|
|
2198
2203
|
maxTokens: readNumber(env, "LLM_MAX_TOKENS", 4096),
|
|
2199
2204
|
temperature: readNumber(env, "LLM_TEMPERATURE", 0.7),
|
|
2200
2205
|
options: {
|
|
2201
|
-
profile: readString(env, "LLM_UNIVERSAL_PROFILE")
|
|
2206
|
+
profile: readString(env, "LLM_UNIVERSAL_PROFILE"),
|
|
2207
|
+
thinking: readString(env, "LLM_THINKING") === "true" || readString(env, "LLM_THINKING") === "enabled" ? true : readString(env, "LLM_THINKING") === "false" ? false : void 0,
|
|
2208
|
+
thinkingBudget: env.LLM_THINKING_BUDGET ? parseInt(env.LLM_THINKING_BUDGET, 10) : void 0
|
|
2202
2209
|
}
|
|
2203
2210
|
},
|
|
2204
2211
|
embedding: {
|
|
@@ -2230,9 +2237,34 @@ function getEnvConfig(env = process.env, base) {
|
|
|
2230
2237
|
topK: readNumber(env, "RAG_TOP_K", 5),
|
|
2231
2238
|
scoreThreshold: readNumber(env, "RAG_SCORE_THRESHOLD", 0),
|
|
2232
2239
|
chunkSize: readNumber(env, "RAG_CHUNK_SIZE", 1e3),
|
|
2233
|
-
chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200)
|
|
2240
|
+
chunkOverlap: readNumber(env, "RAG_CHUNK_OVERLAP", 200),
|
|
2241
|
+
filterableFields: (_Ha = readString(env, "RAG_FILTERABLE_FIELDS")) == null ? void 0 : _Ha.split(",").map((f) => f.trim()),
|
|
2242
|
+
// Query pipeline toggles — read from .env.local
|
|
2243
|
+
useQueryTransformation: readString(env, "RAG_USE_QUERY_TRANSFORMATION") === "true",
|
|
2244
|
+
useReranking: readString(env, "RAG_USE_RERANKING") === "true",
|
|
2245
|
+
useGraphRetrieval: readString(env, "RAG_USE_GRAPH_RETRIEVAL") === "true",
|
|
2246
|
+
architecture: (_Ia = readString(env, "RAG_ARCHITECTURE")) != null ? _Ia : "simple",
|
|
2247
|
+
chunkingStrategy: (_Ja = readString(env, "RAG_CHUNKING_STRATEGY")) != null ? _Ja : "recursive",
|
|
2248
|
+
uiMapping: (() => {
|
|
2249
|
+
const raw = readString(env, "RAG_UI_MAPPING");
|
|
2250
|
+
if (!raw) return void 0;
|
|
2251
|
+
try {
|
|
2252
|
+
return JSON.parse(raw);
|
|
2253
|
+
} catch (e) {
|
|
2254
|
+
return void 0;
|
|
2255
|
+
}
|
|
2256
|
+
})()
|
|
2234
2257
|
}
|
|
2235
|
-
}
|
|
2258
|
+
}, readString(env, "GRAPH_DB_PROVIDER") ? {
|
|
2259
|
+
graphDb: {
|
|
2260
|
+
provider: readString(env, "GRAPH_DB_PROVIDER"),
|
|
2261
|
+
options: {
|
|
2262
|
+
uri: readString(env, "GRAPH_DB_URI"),
|
|
2263
|
+
username: readString(env, "GRAPH_DB_USERNAME"),
|
|
2264
|
+
password: readString(env, "GRAPH_DB_PASSWORD")
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
} : {});
|
|
2236
2268
|
}
|
|
2237
2269
|
|
|
2238
2270
|
// src/core/ConfigResolver.ts
|
|
@@ -2261,6 +2293,20 @@ var ConfigResolver = class {
|
|
|
2261
2293
|
rag: hostConfig.rag ? __spreadValues(__spreadValues({}, envConfig.rag), hostConfig.rag) : envConfig.rag
|
|
2262
2294
|
});
|
|
2263
2295
|
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Resolves the public SDK config shape used by `new Retrivora({...})`.
|
|
2298
|
+
* Supports aliases from the product prompt while preserving existing env
|
|
2299
|
+
* fallback behavior.
|
|
2300
|
+
*/
|
|
2301
|
+
static resolveUniversal(hostConfig, env = process.env) {
|
|
2302
|
+
var _a;
|
|
2303
|
+
if (!hostConfig) return this.resolve(void 0, env);
|
|
2304
|
+
const normalized = __spreadProps(__spreadValues({}, hostConfig), {
|
|
2305
|
+
vectorDb: (_a = hostConfig.vectorDb) != null ? _a : hostConfig.vectorDatabase,
|
|
2306
|
+
rag: this.mergeRetrievalWorkflow(hostConfig.rag, hostConfig.retrieval, hostConfig.workflow)
|
|
2307
|
+
});
|
|
2308
|
+
return this.resolve(normalized, env);
|
|
2309
|
+
}
|
|
2264
2310
|
/**
|
|
2265
2311
|
* Validates the configuration for required fields.
|
|
2266
2312
|
*/
|
|
@@ -2275,10 +2321,53 @@ var ConfigResolver = class {
|
|
|
2275
2321
|
throw new Error("[ConfigResolver] llm.provider is required");
|
|
2276
2322
|
}
|
|
2277
2323
|
}
|
|
2324
|
+
static mergeRetrievalWorkflow(rag, retrieval, workflow) {
|
|
2325
|
+
var _a, _b, _c, _d, _e;
|
|
2326
|
+
if (!rag && !retrieval && !workflow) return void 0;
|
|
2327
|
+
const normalized = __spreadValues({}, rag != null ? rag : {});
|
|
2328
|
+
if (retrieval) {
|
|
2329
|
+
normalized.topK = (_a = retrieval.topK) != null ? _a : normalized.topK;
|
|
2330
|
+
normalized.scoreThreshold = (_b = retrieval.scoreThreshold) != null ? _b : normalized.scoreThreshold;
|
|
2331
|
+
normalized.useReranking = (_c = retrieval.useReranking) != null ? _c : normalized.useReranking;
|
|
2332
|
+
if (retrieval.strategy === "hybrid") normalized.architecture = (_d = normalized.architecture) != null ? _d : "hybrid";
|
|
2333
|
+
if (retrieval.strategy === "agentic") normalized.architecture = "agentic";
|
|
2334
|
+
if (retrieval.strategy === "contextual-compression") normalized.useReranking = true;
|
|
2335
|
+
}
|
|
2336
|
+
if (workflow == null ? void 0 : workflow.type) {
|
|
2337
|
+
const type = workflow.type;
|
|
2338
|
+
if (type === "agentic" || type === "agentic-rag") normalized.architecture = "agentic";
|
|
2339
|
+
else if (type === "hybrid-rag") normalized.architecture = "hybrid";
|
|
2340
|
+
else if (type === "graph-rag") {
|
|
2341
|
+
normalized.architecture = "graph";
|
|
2342
|
+
normalized.useGraphRetrieval = true;
|
|
2343
|
+
} else if (type === "simple-rag" || type === "rag") {
|
|
2344
|
+
normalized.architecture = (_e = normalized.architecture) != null ? _e : "simple";
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return normalized;
|
|
2348
|
+
}
|
|
2278
2349
|
};
|
|
2279
2350
|
|
|
2280
2351
|
// src/llm/providers/OpenAIProvider.ts
|
|
2281
2352
|
import OpenAI from "openai";
|
|
2353
|
+
|
|
2354
|
+
// src/llm/utils.ts
|
|
2355
|
+
function buildSystemContent(systemPrompt, context) {
|
|
2356
|
+
const noContext = !context || context.trim() === "" || context.trim() === "No relevant context found.";
|
|
2357
|
+
if (systemPrompt.includes("{{context}}")) {
|
|
2358
|
+
return systemPrompt.replace("{{context}}", noContext ? "" : context);
|
|
2359
|
+
}
|
|
2360
|
+
if (noContext) {
|
|
2361
|
+
return systemPrompt;
|
|
2362
|
+
}
|
|
2363
|
+
return `${systemPrompt}
|
|
2364
|
+
|
|
2365
|
+
---
|
|
2366
|
+
Context:
|
|
2367
|
+
${context}`;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
// src/llm/providers/OpenAIProvider.ts
|
|
2282
2371
|
var OpenAIProvider = class {
|
|
2283
2372
|
constructor(llmConfig, embeddingConfig) {
|
|
2284
2373
|
if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
|
|
@@ -2342,16 +2431,10 @@ var OpenAIProvider = class {
|
|
|
2342
2431
|
}
|
|
2343
2432
|
async chat(messages, context, options) {
|
|
2344
2433
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2345
|
-
const
|
|
2346
|
-
|
|
2347
|
-
Context:
|
|
2348
|
-
${context}`;
|
|
2434
|
+
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
2435
|
const systemMessage = {
|
|
2350
2436
|
role: "system",
|
|
2351
|
-
content:
|
|
2352
|
-
|
|
2353
|
-
Context:
|
|
2354
|
-
${context}`
|
|
2437
|
+
content: buildSystemContent(basePrompt, context)
|
|
2355
2438
|
};
|
|
2356
2439
|
const formattedMessages = [
|
|
2357
2440
|
systemMessage,
|
|
@@ -2372,16 +2455,10 @@ ${context}`
|
|
|
2372
2455
|
chatStream(messages, context, options) {
|
|
2373
2456
|
return __asyncGenerator(this, null, function* () {
|
|
2374
2457
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2375
|
-
const
|
|
2376
|
-
|
|
2377
|
-
Context:
|
|
2378
|
-
${context}`;
|
|
2458
|
+
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
2459
|
const systemMessage = {
|
|
2380
2460
|
role: "system",
|
|
2381
|
-
content:
|
|
2382
|
-
|
|
2383
|
-
Context:
|
|
2384
|
-
${context}`
|
|
2461
|
+
content: buildSystemContent(basePrompt, context)
|
|
2385
2462
|
};
|
|
2386
2463
|
const formattedMessages = [
|
|
2387
2464
|
systemMessage,
|
|
@@ -2502,58 +2579,87 @@ var AnthropicProvider = class {
|
|
|
2502
2579
|
};
|
|
2503
2580
|
}
|
|
2504
2581
|
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}`;
|
|
2582
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2583
|
+
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.";
|
|
2584
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2514
2585
|
const anthropicMessages = messages.map((m) => ({
|
|
2515
2586
|
role: m.role === "assistant" ? "assistant" : "user",
|
|
2516
2587
|
content: m.content
|
|
2517
2588
|
}));
|
|
2518
|
-
const
|
|
2589
|
+
const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
|
|
2590
|
+
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;
|
|
2591
|
+
const extraParams = {};
|
|
2592
|
+
if (isThinkingEnabled && isClaude37) {
|
|
2593
|
+
extraParams.betas = ["interleaved-thinking-2025-05-14"];
|
|
2594
|
+
const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
|
|
2595
|
+
const budget = Math.min(
|
|
2596
|
+
typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
|
|
2597
|
+
maxTokens - 1
|
|
2598
|
+
);
|
|
2599
|
+
extraParams.thinking = {
|
|
2600
|
+
type: "enabled",
|
|
2601
|
+
budget_tokens: budget
|
|
2602
|
+
};
|
|
2603
|
+
extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
|
|
2604
|
+
}
|
|
2605
|
+
const response = await this.client.messages.create(__spreadValues({
|
|
2519
2606
|
model: this.llmConfig.model,
|
|
2520
|
-
max_tokens: (
|
|
2607
|
+
max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
|
|
2521
2608
|
system,
|
|
2522
2609
|
messages: anthropicMessages
|
|
2523
|
-
});
|
|
2524
|
-
|
|
2525
|
-
|
|
2610
|
+
}, extraParams));
|
|
2611
|
+
let reply = "";
|
|
2612
|
+
for (const block of response.content) {
|
|
2613
|
+
if (block.type === "text") {
|
|
2614
|
+
reply += block.text;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
return reply;
|
|
2526
2618
|
}
|
|
2527
2619
|
chatStream(messages, context, options) {
|
|
2528
2620
|
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}`;
|
|
2621
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2622
|
+
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.";
|
|
2623
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2538
2624
|
const anthropicMessages = messages.map((m) => ({
|
|
2539
2625
|
role: m.role === "assistant" ? "assistant" : "user",
|
|
2540
2626
|
content: m.content
|
|
2541
2627
|
}));
|
|
2542
|
-
const
|
|
2628
|
+
const isClaude37 = this.llmConfig.model.includes("claude-3-7-sonnet");
|
|
2629
|
+
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;
|
|
2630
|
+
const extraParams = {};
|
|
2631
|
+
if (isThinkingEnabled && isClaude37) {
|
|
2632
|
+
extraParams.betas = ["interleaved-thinking-2025-05-14"];
|
|
2633
|
+
const maxTokens = (_g = (_f = options == null ? void 0 : options.maxTokens) != null ? _f : this.llmConfig.maxTokens) != null ? _g : 4096;
|
|
2634
|
+
const budget = Math.min(
|
|
2635
|
+
typeof ((_h = this.llmConfig.options) == null ? void 0 : _h.thinkingBudget) === "number" ? (_i = this.llmConfig.options) == null ? void 0 : _i.thinkingBudget : 2048,
|
|
2636
|
+
maxTokens - 1
|
|
2637
|
+
);
|
|
2638
|
+
extraParams.thinking = {
|
|
2639
|
+
type: "enabled",
|
|
2640
|
+
budget_tokens: budget
|
|
2641
|
+
};
|
|
2642
|
+
extraParams.max_tokens = Math.max(maxTokens, budget + 1024);
|
|
2643
|
+
}
|
|
2644
|
+
const stream = yield new __await(this.client.messages.create(__spreadValues({
|
|
2543
2645
|
model: this.llmConfig.model,
|
|
2544
|
-
max_tokens: (
|
|
2646
|
+
max_tokens: (_k = (_j = options == null ? void 0 : options.maxTokens) != null ? _j : this.llmConfig.maxTokens) != null ? _k : 1024,
|
|
2545
2647
|
system,
|
|
2546
2648
|
messages: anthropicMessages,
|
|
2547
2649
|
stream: true
|
|
2548
|
-
}));
|
|
2650
|
+
}, extraParams)));
|
|
2549
2651
|
if (!stream) {
|
|
2550
2652
|
throw new Error("[AnthropicProvider] messages.create stream is undefined");
|
|
2551
2653
|
}
|
|
2552
2654
|
try {
|
|
2553
2655
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2554
2656
|
const chunk = temp.value;
|
|
2555
|
-
if (chunk.type === "content_block_delta"
|
|
2556
|
-
|
|
2657
|
+
if (chunk.type === "content_block_delta") {
|
|
2658
|
+
if (chunk.delta.type === "text_delta") {
|
|
2659
|
+
yield chunk.delta.text;
|
|
2660
|
+
} else if (chunk.delta.type === "thinking_delta") {
|
|
2661
|
+
yield `\0THINKING\0${chunk.delta.thinking}`;
|
|
2662
|
+
}
|
|
2557
2663
|
}
|
|
2558
2664
|
}
|
|
2559
2665
|
} catch (temp) {
|
|
@@ -2597,9 +2703,10 @@ ${context}`;
|
|
|
2597
2703
|
import axios from "axios";
|
|
2598
2704
|
var OllamaProvider = class {
|
|
2599
2705
|
constructor(llmConfig, embeddingConfig) {
|
|
2600
|
-
var _a;
|
|
2706
|
+
var _a, _b;
|
|
2601
2707
|
const baseURL = (_a = llmConfig.baseUrl) != null ? _a : "http://localhost:11434";
|
|
2602
|
-
|
|
2708
|
+
const timeout = Number((_b = llmConfig.options) == null ? void 0 : _b.timeout) || 3e5;
|
|
2709
|
+
this.http = axios.create({ baseURL, timeout });
|
|
2603
2710
|
this.llmConfig = llmConfig;
|
|
2604
2711
|
this.embeddingConfig = embeddingConfig;
|
|
2605
2712
|
}
|
|
@@ -2655,14 +2762,15 @@ var OllamaProvider = class {
|
|
|
2655
2762
|
};
|
|
2656
2763
|
}
|
|
2657
2764
|
async chat(messages, context, options) {
|
|
2658
|
-
var _a, _b, _c, _d;
|
|
2659
|
-
const
|
|
2765
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2766
|
+
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.";
|
|
2767
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2660
2768
|
const { data } = await this.http.post("/api/chat", {
|
|
2661
2769
|
model: this.llmConfig.model,
|
|
2662
2770
|
stream: false,
|
|
2663
2771
|
options: {
|
|
2664
|
-
temperature: (
|
|
2665
|
-
num_predict: (
|
|
2772
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
|
|
2773
|
+
num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
|
|
2666
2774
|
},
|
|
2667
2775
|
messages: [
|
|
2668
2776
|
{ role: "system", content: system },
|
|
@@ -2673,14 +2781,15 @@ var OllamaProvider = class {
|
|
|
2673
2781
|
}
|
|
2674
2782
|
chatStream(messages, context, options) {
|
|
2675
2783
|
return __asyncGenerator(this, null, function* () {
|
|
2676
|
-
var _a, _b, _c, _d, _e, _f;
|
|
2677
|
-
const
|
|
2784
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2785
|
+
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.";
|
|
2786
|
+
const system = buildSystemContent(basePrompt, context);
|
|
2678
2787
|
const response = yield new __await(this.http.post("/api/chat", {
|
|
2679
2788
|
model: this.llmConfig.model,
|
|
2680
2789
|
stream: true,
|
|
2681
2790
|
options: {
|
|
2682
|
-
temperature: (
|
|
2683
|
-
num_predict: (
|
|
2791
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : 0.7,
|
|
2792
|
+
num_predict: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : 1024
|
|
2684
2793
|
},
|
|
2685
2794
|
messages: [
|
|
2686
2795
|
{ role: "system", content: system },
|
|
@@ -2702,7 +2811,7 @@ var OllamaProvider = class {
|
|
|
2702
2811
|
if (!line.trim()) continue;
|
|
2703
2812
|
try {
|
|
2704
2813
|
const json = JSON.parse(line);
|
|
2705
|
-
if ((
|
|
2814
|
+
if ((_g = json.message) == null ? void 0 : _g.content) {
|
|
2706
2815
|
yield json.message.content;
|
|
2707
2816
|
}
|
|
2708
2817
|
if (json.done) return;
|
|
@@ -2724,26 +2833,12 @@ var OllamaProvider = class {
|
|
|
2724
2833
|
if (lineBuffer.trim()) {
|
|
2725
2834
|
try {
|
|
2726
2835
|
const json = JSON.parse(lineBuffer);
|
|
2727
|
-
if ((
|
|
2836
|
+
if ((_h = json.message) == null ? void 0 : _h.content) yield json.message.content;
|
|
2728
2837
|
} catch (e) {
|
|
2729
2838
|
}
|
|
2730
2839
|
}
|
|
2731
2840
|
});
|
|
2732
2841
|
}
|
|
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
2842
|
async embed(text, options) {
|
|
2748
2843
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2749
2844
|
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 +2985,6 @@ var GeminiProvider = class {
|
|
|
2890
2985
|
var _a, _b;
|
|
2891
2986
|
return sanitizeModel((_b = optionsModel != null ? optionsModel : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _b : DEFAULT_EMBEDDING_MODEL);
|
|
2892
2987
|
}
|
|
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
2988
|
/**
|
|
2909
2989
|
* Convert ChatMessage[] to the Gemini contents format.
|
|
2910
2990
|
*
|
|
@@ -2923,16 +3003,17 @@ ${context}`;
|
|
|
2923
3003
|
// ILLMProvider — chat
|
|
2924
3004
|
// -------------------------------------------------------------------------
|
|
2925
3005
|
async chat(messages, context, options) {
|
|
2926
|
-
var _a, _b, _c, _d;
|
|
3006
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3007
|
+
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
3008
|
const model = this.client.getGenerativeModel({
|
|
2928
3009
|
model: this.llmConfig.model,
|
|
2929
|
-
systemInstruction:
|
|
3010
|
+
systemInstruction: buildSystemContent(basePrompt, context)
|
|
2930
3011
|
});
|
|
2931
3012
|
const result = await model.generateContent({
|
|
2932
3013
|
contents: this.buildGeminiContents(messages),
|
|
2933
3014
|
generationConfig: {
|
|
2934
|
-
temperature: (
|
|
2935
|
-
maxOutputTokens: (
|
|
3015
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
|
|
3016
|
+
maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
|
|
2936
3017
|
stopSequences: options == null ? void 0 : options.stop
|
|
2937
3018
|
}
|
|
2938
3019
|
});
|
|
@@ -2940,16 +3021,17 @@ ${context}`;
|
|
|
2940
3021
|
}
|
|
2941
3022
|
chatStream(messages, context, options) {
|
|
2942
3023
|
return __asyncGenerator(this, null, function* () {
|
|
2943
|
-
var _a, _b, _c, _d;
|
|
3024
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3025
|
+
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
3026
|
const model = this.client.getGenerativeModel({
|
|
2945
3027
|
model: this.llmConfig.model,
|
|
2946
|
-
systemInstruction:
|
|
3028
|
+
systemInstruction: buildSystemContent(basePrompt, context)
|
|
2947
3029
|
});
|
|
2948
3030
|
const result = yield new __await(model.generateContentStream({
|
|
2949
3031
|
contents: this.buildGeminiContents(messages),
|
|
2950
3032
|
generationConfig: {
|
|
2951
|
-
temperature: (
|
|
2952
|
-
maxOutputTokens: (
|
|
3033
|
+
temperature: (_d = (_c = options == null ? void 0 : options.temperature) != null ? _c : this.llmConfig.temperature) != null ? _d : DEFAULT_TEMPERATURE,
|
|
3034
|
+
maxOutputTokens: (_f = (_e = options == null ? void 0 : options.maxTokens) != null ? _e : this.llmConfig.maxTokens) != null ? _f : DEFAULT_MAX_TOKENS,
|
|
2953
3035
|
stopSequences: options == null ? void 0 : options.stop
|
|
2954
3036
|
}
|
|
2955
3037
|
}));
|
|
@@ -3277,6 +3359,78 @@ ${context != null ? context : "None"}` },
|
|
|
3277
3359
|
}
|
|
3278
3360
|
};
|
|
3279
3361
|
|
|
3362
|
+
// src/exceptions/index.ts
|
|
3363
|
+
var RetrivoraError = class extends Error {
|
|
3364
|
+
constructor(message, code, details) {
|
|
3365
|
+
super(message);
|
|
3366
|
+
this.name = new.target.name;
|
|
3367
|
+
this.code = code;
|
|
3368
|
+
this.details = details;
|
|
3369
|
+
}
|
|
3370
|
+
};
|
|
3371
|
+
var ProviderNotFoundException = class extends RetrivoraError {
|
|
3372
|
+
constructor(providerType, provider, details) {
|
|
3373
|
+
super(`Unsupported ${providerType} provider: ${provider}`, "PROVIDER_NOT_FOUND", details);
|
|
3374
|
+
}
|
|
3375
|
+
};
|
|
3376
|
+
var EmbeddingFailedException = class extends RetrivoraError {
|
|
3377
|
+
constructor(message = "Embedding generation failed", details) {
|
|
3378
|
+
super(message, "EMBEDDING_FAILED", details);
|
|
3379
|
+
}
|
|
3380
|
+
};
|
|
3381
|
+
var RetrievalException = class extends RetrivoraError {
|
|
3382
|
+
constructor(message = "Retrieval failed", details) {
|
|
3383
|
+
super(message, "RETRIEVAL_FAILED", details);
|
|
3384
|
+
}
|
|
3385
|
+
};
|
|
3386
|
+
var RateLimitException = class extends RetrivoraError {
|
|
3387
|
+
constructor(message = "Provider rate limit exceeded", details) {
|
|
3388
|
+
super(message, "RATE_LIMITED", details);
|
|
3389
|
+
}
|
|
3390
|
+
};
|
|
3391
|
+
var ConfigurationException = class extends RetrivoraError {
|
|
3392
|
+
constructor(message, details) {
|
|
3393
|
+
super(message, "CONFIGURATION_ERROR", details);
|
|
3394
|
+
}
|
|
3395
|
+
};
|
|
3396
|
+
var AuthenticationException = class extends RetrivoraError {
|
|
3397
|
+
constructor(message = "Provider authentication failed", details) {
|
|
3398
|
+
super(message, "AUTHENTICATION_ERROR", details);
|
|
3399
|
+
}
|
|
3400
|
+
};
|
|
3401
|
+
function wrapError(err, defaultCode, defaultMessage) {
|
|
3402
|
+
var _a;
|
|
3403
|
+
if (err instanceof RetrivoraError) {
|
|
3404
|
+
return err;
|
|
3405
|
+
}
|
|
3406
|
+
const error = err;
|
|
3407
|
+
const message = (error == null ? void 0 : error.message) || defaultMessage || String(err);
|
|
3408
|
+
const status = (error == null ? void 0 : error.status) || (error == null ? void 0 : error.statusCode) || ((_a = error == null ? void 0 : error.response) == null ? void 0 : _a.status);
|
|
3409
|
+
const code = error == null ? void 0 : error.code;
|
|
3410
|
+
if (status === 429 || /rate[- ]?limit/i.test(message) || code === "RATE_LIMIT_EXCEEDED") {
|
|
3411
|
+
return new RateLimitException(message, err);
|
|
3412
|
+
}
|
|
3413
|
+
if (status === 401 || status === 403 || /unauthorized|auth|api[- ]?key/i.test(message) || code === "INVALID_API_KEY") {
|
|
3414
|
+
return new AuthenticationException(message, err);
|
|
3415
|
+
}
|
|
3416
|
+
switch (defaultCode) {
|
|
3417
|
+
case "PROVIDER_NOT_FOUND":
|
|
3418
|
+
return new ProviderNotFoundException("provider", message, err);
|
|
3419
|
+
case "EMBEDDING_FAILED":
|
|
3420
|
+
return new EmbeddingFailedException(message, err);
|
|
3421
|
+
case "RETRIEVAL_FAILED":
|
|
3422
|
+
return new RetrievalException(message, err);
|
|
3423
|
+
case "RATE_LIMITED":
|
|
3424
|
+
return new RateLimitException(message, err);
|
|
3425
|
+
case "CONFIGURATION_ERROR":
|
|
3426
|
+
return new ConfigurationException(message, err);
|
|
3427
|
+
case "AUTHENTICATION_ERROR":
|
|
3428
|
+
return new AuthenticationException(message, err);
|
|
3429
|
+
default:
|
|
3430
|
+
return new RetrivoraError(message, defaultCode, err);
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3280
3434
|
// src/llm/LLMFactory.ts
|
|
3281
3435
|
var customProviders = /* @__PURE__ */ new Map();
|
|
3282
3436
|
var LLMFactory = class _LLMFactory {
|
|
@@ -3322,7 +3476,7 @@ var LLMFactory = class _LLMFactory {
|
|
|
3322
3476
|
];
|
|
3323
3477
|
}
|
|
3324
3478
|
static create(llmConfig, embeddingConfig) {
|
|
3325
|
-
var _a, _b;
|
|
3479
|
+
var _a, _b, _c;
|
|
3326
3480
|
switch (llmConfig.provider) {
|
|
3327
3481
|
case "openai":
|
|
3328
3482
|
return new OpenAIProvider(llmConfig, embeddingConfig);
|
|
@@ -3345,8 +3499,13 @@ var LLMFactory = class _LLMFactory {
|
|
|
3345
3499
|
if (llmConfig.baseUrl || ((_b = llmConfig.options) == null ? void 0 : _b.baseUrl)) {
|
|
3346
3500
|
return new UniversalLLMAdapter(llmConfig);
|
|
3347
3501
|
}
|
|
3348
|
-
throw new
|
|
3349
|
-
|
|
3502
|
+
throw new ProviderNotFoundException(
|
|
3503
|
+
"llm",
|
|
3504
|
+
(_c = llmConfig.provider) != null ? _c : "undefined",
|
|
3505
|
+
{
|
|
3506
|
+
message: `Unknown provider "${llmConfig.provider}". Register a custom provider with LLMFactory.register().`,
|
|
3507
|
+
available: _LLMFactory.listProviders()
|
|
3508
|
+
}
|
|
3350
3509
|
);
|
|
3351
3510
|
}
|
|
3352
3511
|
}
|
|
@@ -3477,7 +3636,7 @@ var ProviderRegistry = class {
|
|
|
3477
3636
|
return UniversalVectorProvider2;
|
|
3478
3637
|
}
|
|
3479
3638
|
default:
|
|
3480
|
-
throw new
|
|
3639
|
+
throw new ProviderNotFoundException("vector", provider);
|
|
3481
3640
|
}
|
|
3482
3641
|
}
|
|
3483
3642
|
static async createVectorProvider(config) {
|
|
@@ -3495,12 +3654,18 @@ var ProviderRegistry = class {
|
|
|
3495
3654
|
return new SimpleGraphProvider2(config);
|
|
3496
3655
|
}
|
|
3497
3656
|
default:
|
|
3498
|
-
throw new
|
|
3657
|
+
throw new ProviderNotFoundException("graph", provider);
|
|
3499
3658
|
}
|
|
3500
3659
|
}
|
|
3501
3660
|
static createLLMProvider(llmConfig, embeddingConfig) {
|
|
3502
3661
|
return LLMFactory.create(llmConfig, embeddingConfig);
|
|
3503
3662
|
}
|
|
3663
|
+
static registerLLMProvider(name, factory) {
|
|
3664
|
+
LLMFactory.register(name, factory);
|
|
3665
|
+
}
|
|
3666
|
+
static createEmbeddingProvider(embeddingConfig) {
|
|
3667
|
+
return LLMFactory.createEmbeddingProvider(embeddingConfig);
|
|
3668
|
+
}
|
|
3504
3669
|
};
|
|
3505
3670
|
ProviderRegistry.vectorProviders = {};
|
|
3506
3671
|
ProviderRegistry.graphProviders = {};
|
|
@@ -3647,8 +3812,8 @@ var ConfigValidator = class {
|
|
|
3647
3812
|
const errorItems = errors.filter((e) => e.severity === "error");
|
|
3648
3813
|
if (errorItems.length > 0) {
|
|
3649
3814
|
const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
|
|
3650
|
-
throw new
|
|
3651
|
-
${message}
|
|
3815
|
+
throw new ConfigurationException(`[ConfigValidator] Configuration validation failed:
|
|
3816
|
+
${message}`, errorItems);
|
|
3652
3817
|
}
|
|
3653
3818
|
}
|
|
3654
3819
|
};
|
|
@@ -3835,20 +4000,17 @@ var Reranker = class {
|
|
|
3835
4000
|
}
|
|
3836
4001
|
try {
|
|
3837
4002
|
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}"
|
|
4003
|
+
const response = await llm.chat(
|
|
4004
|
+
[{ role: "user", content: `Query: "${query}"
|
|
3845
4005
|
|
|
3846
4006
|
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 }],
|
|
4007
|
+
${topN.map((m, i) => `[${i}] ${m.content.replace(/\n/g, " ")}`).join("\n")}` }],
|
|
3850
4008
|
"",
|
|
3851
|
-
{
|
|
4009
|
+
{
|
|
4010
|
+
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.',
|
|
4011
|
+
temperature: 0,
|
|
4012
|
+
maxTokens: 50
|
|
4013
|
+
}
|
|
3852
4014
|
);
|
|
3853
4015
|
const cleanedResponse = response.trim().replace(/[\[\]\s]/g, "");
|
|
3854
4016
|
const rankedIndices = cleanedResponse.split(",").map(Number).filter((n) => !isNaN(n));
|
|
@@ -4354,6 +4516,18 @@ var QueryProcessor = class {
|
|
|
4354
4516
|
}, normalizedField ? { field: normalizedField } : {}));
|
|
4355
4517
|
}
|
|
4356
4518
|
};
|
|
4519
|
+
const activeValidFields = validFields.length > 0 ? validFields : ["brand", "category", "price", "rating", "stock", "stock_quantity", "status", "name", "title"];
|
|
4520
|
+
const resolveValidField = (fieldStr) => {
|
|
4521
|
+
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();
|
|
4522
|
+
const comparable = (v) => v.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4523
|
+
const cleanedComparable = comparable(cleaned);
|
|
4524
|
+
if (!cleanedComparable) return null;
|
|
4525
|
+
const matchedField = activeValidFields.find((fieldName) => {
|
|
4526
|
+
const candidate = comparable(fieldName);
|
|
4527
|
+
return candidate === cleanedComparable || candidate.includes(cleanedComparable) || cleanedComparable.includes(candidate);
|
|
4528
|
+
});
|
|
4529
|
+
return matchedField != null ? matchedField : null;
|
|
4530
|
+
};
|
|
4357
4531
|
for (const match of question.matchAll(/["']([^"']{2,100})["']/g)) {
|
|
4358
4532
|
addHint(match[1]);
|
|
4359
4533
|
}
|
|
@@ -4378,7 +4552,13 @@ var QueryProcessor = class {
|
|
|
4378
4552
|
const field = match[1];
|
|
4379
4553
|
const value = match[2];
|
|
4380
4554
|
if (field && !this.isLikelyPromptPhrase(field)) {
|
|
4381
|
-
|
|
4555
|
+
const resolvedField = resolveValidField(field);
|
|
4556
|
+
if (resolvedField) {
|
|
4557
|
+
addHint(value, resolvedField);
|
|
4558
|
+
} else {
|
|
4559
|
+
addHint(value);
|
|
4560
|
+
addHint(field);
|
|
4561
|
+
}
|
|
4382
4562
|
}
|
|
4383
4563
|
}
|
|
4384
4564
|
if (validFields.length > 0) {
|
|
@@ -4424,12 +4604,16 @@ var QueryProcessor = class {
|
|
|
4424
4604
|
];
|
|
4425
4605
|
for (const pattern of fieldValuePatterns) {
|
|
4426
4606
|
for (const match of question.matchAll(pattern)) {
|
|
4427
|
-
const field =
|
|
4607
|
+
const field = (_c = match[1]) != null ? _c : "";
|
|
4428
4608
|
const value = (_d = match[2]) != null ? _d : "";
|
|
4429
|
-
|
|
4430
|
-
|
|
4609
|
+
const resolvedField = resolveValidField(field);
|
|
4610
|
+
if (resolvedField) {
|
|
4611
|
+
addHint(value, resolvedField);
|
|
4431
4612
|
} else {
|
|
4432
4613
|
addHint(value);
|
|
4614
|
+
if (field && !this.isLikelyPromptPhrase(field)) {
|
|
4615
|
+
addHint(field);
|
|
4616
|
+
}
|
|
4433
4617
|
}
|
|
4434
4618
|
}
|
|
4435
4619
|
}
|
|
@@ -4656,7 +4840,7 @@ var LLMRouter = class {
|
|
|
4656
4840
|
|
|
4657
4841
|
// src/utils/UITransformer.ts
|
|
4658
4842
|
init_synonyms();
|
|
4659
|
-
var UITransformer = class {
|
|
4843
|
+
var UITransformer = class _UITransformer {
|
|
4660
4844
|
// ─── Public Entry Points ─────────────────────────────────────────────────
|
|
4661
4845
|
/**
|
|
4662
4846
|
* Heuristic-only transform (no LLM required).
|
|
@@ -4718,7 +4902,7 @@ var UITransformer = class {
|
|
|
4718
4902
|
static async analyzeAndDecide(query, sources, llm) {
|
|
4719
4903
|
let intent;
|
|
4720
4904
|
try {
|
|
4721
|
-
intent = await this.detectIntent(query, llm);
|
|
4905
|
+
intent = await this.detectIntent(query, llm, sources);
|
|
4722
4906
|
console.debug("[UITransformer] Detected intent:", intent);
|
|
4723
4907
|
} catch (err) {
|
|
4724
4908
|
console.warn("[UITransformer] Intent detection failed; using heuristic.", err);
|
|
@@ -4736,11 +4920,23 @@ var UITransformer = class {
|
|
|
4736
4920
|
try {
|
|
4737
4921
|
const context = this.buildContextSummary(sources);
|
|
4738
4922
|
const systemPrompt = this.buildVisualizationSystemPrompt();
|
|
4923
|
+
const profile = this.profileData(sources);
|
|
4924
|
+
const schemaContext = `
|
|
4925
|
+
RETRIEVED DATA SCHEMA:
|
|
4926
|
+
- Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4927
|
+
- Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4928
|
+
- Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4929
|
+
- Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
4930
|
+
- Text/Other fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
|
|
4931
|
+
`;
|
|
4739
4932
|
const userPrompt = [
|
|
4740
4933
|
`USER QUESTION: ${query}`,
|
|
4741
4934
|
"",
|
|
4742
4935
|
`DETECTED INTENT (JSON): ${JSON.stringify(intent)}`,
|
|
4743
4936
|
"",
|
|
4937
|
+
`RETRIEVED DATA SCHEMA:`,
|
|
4938
|
+
schemaContext,
|
|
4939
|
+
"",
|
|
4744
4940
|
"RETRIEVED DATA (JSON):",
|
|
4745
4941
|
context
|
|
4746
4942
|
].join("\n");
|
|
@@ -4751,6 +4947,20 @@ var UITransformer = class {
|
|
|
4751
4947
|
);
|
|
4752
4948
|
const parsed = this.parseTransformationResponse(rawResponse);
|
|
4753
4949
|
if (parsed) {
|
|
4950
|
+
let isCompatible = true;
|
|
4951
|
+
if (parsed.type === "line_chart" && (profile.dateFields.length === 0 || profile.numericFields.length === 0)) {
|
|
4952
|
+
isCompatible = false;
|
|
4953
|
+
} else if (["bar_chart", "horizontal_bar", "pie_chart", "donut_chart"].includes(parsed.type) && (profile.categoricalFields.length === 0 && profile.numericFields.length === 0)) {
|
|
4954
|
+
isCompatible = false;
|
|
4955
|
+
} else if (parsed.type === "scatter_plot" && profile.numericFields.length < 2) {
|
|
4956
|
+
isCompatible = false;
|
|
4957
|
+
} else if (parsed.type === "metric_card" && profile.numericFields.length === 0) {
|
|
4958
|
+
isCompatible = false;
|
|
4959
|
+
}
|
|
4960
|
+
if (!isCompatible) {
|
|
4961
|
+
console.warn(`[UITransformer] LLM chose incompatible visualization type "${parsed.type}" for retrieved schema. Falling back to heuristic.`);
|
|
4962
|
+
return this.transform(query, sources, void 0, void 0, intent);
|
|
4963
|
+
}
|
|
4754
4964
|
const intentAllowsTable = intent.wantsExplicitTable || ["tabular", "table", "geographic"].includes(intent.visualizationHint);
|
|
4755
4965
|
const intentWantsPieLikeChart = ["pie_chart", "donut_chart"].includes(intent.recommendedChart) || ["composition", "category_breakdown"].includes(intent.visualizationHint);
|
|
4756
4966
|
if (parsed.type === "table" && !intentAllowsTable) {
|
|
@@ -4780,7 +4990,26 @@ var UITransformer = class {
|
|
|
4780
4990
|
* - The intent object can be reused across both the heuristic and LLM paths.
|
|
4781
4991
|
* - It is easy to unit-test intent detection in isolation.
|
|
4782
4992
|
*/
|
|
4783
|
-
static async detectIntent(query, llm) {
|
|
4993
|
+
static async detectIntent(query, llm, sources) {
|
|
4994
|
+
let schemaProfileText = "";
|
|
4995
|
+
if (sources && sources.length > 0) {
|
|
4996
|
+
const profile = this.profileData(sources);
|
|
4997
|
+
const hasProducts = sources.some((item) => this.isProductData(item));
|
|
4998
|
+
schemaProfileText = `
|
|
4999
|
+
RETRIEVED DATA SCHEMA:
|
|
5000
|
+
- Numeric fields (measures): ${profile.numericFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
5001
|
+
- Category fields (dimensions): ${profile.categoricalFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
5002
|
+
- Date fields (temporal): ${profile.dateFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
5003
|
+
- Boolean fields: ${profile.booleanFields.map((f) => `${f.key} (unique values: ${f.uniqueCount})`).join(", ") || "None"}
|
|
5004
|
+
- Text fields: ${profile.fields.filter((f) => f.kind === "text").map((f) => f.key).join(", ") || "None"}
|
|
5005
|
+
|
|
5006
|
+
Please choose visualizationHint and recommendedChart dynamically based on the available schema.
|
|
5007
|
+
- Do NOT recommend "trend" or "line_chart" if there are no Date fields.
|
|
5008
|
+
- 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.
|
|
5009
|
+
- If there are multiple metadata fields but no clear categories or numbers, "tabular"/"table" is a highly accurate default.
|
|
5010
|
+
${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".` : ""}
|
|
5011
|
+
`;
|
|
5012
|
+
}
|
|
4784
5013
|
const systemPrompt = `You are an intent classifier for a product-search RAG system.
|
|
4785
5014
|
Given a user query, return ONLY a valid JSON object with this exact shape \u2014 no prose, no markdown:
|
|
4786
5015
|
|
|
@@ -4816,8 +5045,11 @@ RULES:
|
|
|
4816
5045
|
- isTemporal: true if time words appear (trend, historical, over time, last year, monthly, etc.)
|
|
4817
5046
|
- isComparison: true if user compares, ranks, or contrasts entities or categories.
|
|
4818
5047
|
- language: detect from the query text itself; default "en" if uncertain.`;
|
|
5048
|
+
const userPrompt = `QUERY: ${query}${schemaProfileText ? `
|
|
5049
|
+
|
|
5050
|
+
${schemaProfileText}` : ""}`;
|
|
4819
5051
|
const rawResponse = await llm.chat(
|
|
4820
|
-
[{ role: "user", content:
|
|
5052
|
+
[{ role: "user", content: userPrompt }],
|
|
4821
5053
|
"",
|
|
4822
5054
|
{ systemPrompt, temperature: 0 }
|
|
4823
5055
|
);
|
|
@@ -5260,10 +5492,21 @@ RULES:
|
|
|
5260
5492
|
const hasEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
|
|
5261
5493
|
return asksForRecords && (hasNumericPredicate || hasEntityTerms);
|
|
5262
5494
|
}
|
|
5495
|
+
/** @internal kept private for transform() internal logic */
|
|
5263
5496
|
static isProductQuery(query) {
|
|
5497
|
+
return _UITransformer._productQueryTest(query);
|
|
5498
|
+
}
|
|
5499
|
+
/**
|
|
5500
|
+
* Public entry-point for external callers (e.g. Pipeline)
|
|
5501
|
+
* to check whether a query maps to product_browse without triggering an LLM call.
|
|
5502
|
+
*/
|
|
5503
|
+
static isProductQueryPublic(query) {
|
|
5504
|
+
return _UITransformer._productQueryTest(query);
|
|
5505
|
+
}
|
|
5506
|
+
static _productQueryTest(query) {
|
|
5264
5507
|
const q = query.toLowerCase();
|
|
5265
5508
|
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);
|
|
5509
|
+
const productAction = /\b(recommend|suggest|describe|description|detail|details|about|show|find|search|browse|list|explore|exploring)\b/.test(q);
|
|
5267
5510
|
const nonProductEntityTerms = /\b(organization|organisations?|organizations?|company|companies|employee|employees|staff|headcount|workforce)\b/.test(q);
|
|
5268
5511
|
return productTerms && productAction && !nonProductEntityTerms;
|
|
5269
5512
|
}
|
|
@@ -6036,23 +6279,19 @@ function estimateCostUsd(promptTokens, completionTokens, model) {
|
|
|
6036
6279
|
async function scoreHallucination(llm, answer, context) {
|
|
6037
6280
|
const maxContextChars = 3e3;
|
|
6038
6281
|
const truncatedContext = context.length > maxContextChars ? context.slice(0, maxContextChars) + "\n...[truncated]" : context;
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
CONTEXT:
|
|
6282
|
+
try {
|
|
6283
|
+
const raw = await llm.chat(
|
|
6284
|
+
[{ role: "user", content: `CONTEXT:
|
|
6042
6285
|
${truncatedContext}
|
|
6043
6286
|
|
|
6044
6287
|
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 }],
|
|
6288
|
+
${answer}` }],
|
|
6054
6289
|
"",
|
|
6055
|
-
{
|
|
6290
|
+
{
|
|
6291
|
+
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.',
|
|
6292
|
+
temperature: 0,
|
|
6293
|
+
maxTokens: 120
|
|
6294
|
+
}
|
|
6056
6295
|
);
|
|
6057
6296
|
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
6058
6297
|
if (!jsonMatch) return void 0;
|
|
@@ -6112,7 +6351,10 @@ var Pipeline = class {
|
|
|
6112
6351
|
- 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
6352
|
- Do NOT try to format product lists as tables.
|
|
6114
6353
|
`;
|
|
6115
|
-
this.config.llm.systemPrompt
|
|
6354
|
+
const userPromptPrefix = this.config.llm.systemPrompt ? `${this.config.llm.systemPrompt}
|
|
6355
|
+
|
|
6356
|
+
` : "";
|
|
6357
|
+
this.config.llm.systemPrompt = `${userPromptPrefix}${chartInstruction}`;
|
|
6116
6358
|
this.vectorDB = await ProviderRegistry.createVectorProvider(this.config.vectorDb);
|
|
6117
6359
|
this.llmRouter = new LLMRouter(this.config);
|
|
6118
6360
|
const { llmProvider: resolvedLLM, embeddingProvider } = await EmbeddingStrategyResolver.resolve(
|
|
@@ -6192,7 +6434,7 @@ var Pipeline = class {
|
|
|
6192
6434
|
embedBatchOptions
|
|
6193
6435
|
);
|
|
6194
6436
|
if (vectors.length !== chunks.length) {
|
|
6195
|
-
throw new
|
|
6437
|
+
throw new EmbeddingFailedException(
|
|
6196
6438
|
`[Pipeline] Embedding mismatch: got ${vectors.length} vectors for ${chunks.length} chunks. Check embedding provider logs for individual chunk failures.`
|
|
6197
6439
|
);
|
|
6198
6440
|
}
|
|
@@ -6290,11 +6532,11 @@ var Pipeline = class {
|
|
|
6290
6532
|
*/
|
|
6291
6533
|
askStream(_0) {
|
|
6292
6534
|
return __asyncGenerator(this, arguments, function* (question, history = [], namespace) {
|
|
6293
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
6535
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
6294
6536
|
yield new __await(this.initialize());
|
|
6295
6537
|
const ns = namespace != null ? namespace : this.config.projectId;
|
|
6296
6538
|
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;
|
|
6539
|
+
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0.3;
|
|
6298
6540
|
const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
6299
6541
|
const requestStart = performance.now();
|
|
6300
6542
|
try {
|
|
@@ -6313,12 +6555,13 @@ var Pipeline = class {
|
|
|
6313
6555
|
const embedStart = performance.now();
|
|
6314
6556
|
const cacheKey = `${ns}::${searchQuery}`;
|
|
6315
6557
|
const cachedVector = this.embeddingCache.get(cacheKey);
|
|
6558
|
+
const useGraph = this.graphDB && ((_h = this.config.rag) == null ? void 0 : _h.useGraphRetrieval);
|
|
6316
6559
|
const [strategyResult, embeddedVector] = yield new __await(Promise.all([
|
|
6317
6560
|
QueryProcessor.determineRetrievalStrategy(
|
|
6318
6561
|
searchQuery,
|
|
6319
|
-
this.llmRouter.get("fast"),
|
|
6320
|
-
(
|
|
6321
|
-
(
|
|
6562
|
+
useGraph ? this.llmRouter.get("fast") : void 0,
|
|
6563
|
+
(_i = this.config.rag) == null ? void 0 : _i.graphKeywords,
|
|
6564
|
+
(_j = this.config.rag) == null ? void 0 : _j.vectorKeywords
|
|
6322
6565
|
),
|
|
6323
6566
|
// Embed immediately regardless of strategy — costs nothing if cached.
|
|
6324
6567
|
// If strategy turns out to be 'graph'-only we just won't use the vector.
|
|
@@ -6328,7 +6571,7 @@ var Pipeline = class {
|
|
|
6328
6571
|
if (!cachedVector && queryVector.length > 0) {
|
|
6329
6572
|
this.embeddingCache.set(cacheKey, queryVector);
|
|
6330
6573
|
}
|
|
6331
|
-
const graphData = (strategyResult === "graph" || strategyResult === "both") && this.graphDB && ((
|
|
6574
|
+
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
6575
|
const hasMetadataFilter = filter.metadata && Object.keys(filter.metadata).length > 0;
|
|
6333
6576
|
const hasNumericPredicates = Array.isArray(filter.__numericPredicates) && filter.__numericPredicates.length > 0;
|
|
6334
6577
|
const wantsExhaustiveList = hasNumericPredicates || hasMetadataFilter || /\b(list|all|show|provide|give|get|browse|find|search|display)\b/i.test(question);
|
|
@@ -6341,7 +6584,7 @@ var Pipeline = class {
|
|
|
6341
6584
|
const structuredSources = this.applyStructuredFilters(rawSources, filter);
|
|
6342
6585
|
let fullSources = hasNumericPredicates ? structuredSources : structuredSources.filter((m) => m.score >= scoreThreshold);
|
|
6343
6586
|
const rerankLimit = wantsExhaustiveList ? retrievalLimit : topK;
|
|
6344
|
-
if (!hasNumericPredicates && ((
|
|
6587
|
+
if (!hasNumericPredicates && ((_l = this.config.rag) == null ? void 0 : _l.useReranking)) {
|
|
6345
6588
|
fullSources = yield new __await(this.reranker.rerank(fullSources, question, rerankLimit));
|
|
6346
6589
|
} else if (!wantsExhaustiveList) {
|
|
6347
6590
|
fullSources = fullSources.slice(0, topK);
|
|
@@ -6372,34 +6615,117 @@ VECTOR CONTEXT:
|
|
|
6372
6615
|
${context}`;
|
|
6373
6616
|
}
|
|
6374
6617
|
const allMetadataKeys = Array.from(new Set(sources.flatMap((s) => Object.keys(s.metadata || {}))));
|
|
6375
|
-
const
|
|
6376
|
-
|
|
6377
|
-
(
|
|
6618
|
+
const cachedSchema = allMetadataKeys.length > 0 ? SchemaMapper.getCached(ns, allMetadataKeys) : void 0;
|
|
6619
|
+
if (allMetadataKeys.length > 0 && !cachedSchema) {
|
|
6620
|
+
SchemaMapper.train(this.llmProvider, ns, allMetadataKeys).catch(() => {
|
|
6621
|
+
});
|
|
6622
|
+
}
|
|
6623
|
+
const isProductQ = UITransformer.isProductQueryPublic(question);
|
|
6624
|
+
const uiTransformationPromise = isProductQ ? Promise.resolve(
|
|
6625
|
+
UITransformer.transform(
|
|
6378
6626
|
question,
|
|
6379
6627
|
sources,
|
|
6380
|
-
|
|
6381
|
-
|
|
6628
|
+
this.config,
|
|
6629
|
+
cachedSchema,
|
|
6630
|
+
{ visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
|
|
6382
6631
|
)
|
|
6632
|
+
) : this.generateUiTransformation(
|
|
6633
|
+
question,
|
|
6634
|
+
sources,
|
|
6635
|
+
cachedSchema,
|
|
6636
|
+
hasNumericPredicates
|
|
6383
6637
|
).catch((uiError) => {
|
|
6384
6638
|
console.warn("[Pipeline] UI transformation failed concurrently:", uiError);
|
|
6385
|
-
return UITransformer.transform(question, sources, this.config);
|
|
6639
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6386
6640
|
});
|
|
6641
|
+
let hallucinationScoringPromise = Promise.resolve(void 0);
|
|
6387
6642
|
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.)";
|
|
6643
|
+
const isClaude37 = this.config.llm.model.includes("claude-3-7-sonnet");
|
|
6644
|
+
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);
|
|
6645
|
+
const modelNameLower = this.config.llm.model.toLowerCase();
|
|
6646
|
+
const isReasoningModel = modelNameLower.includes("-r1") || modelNameLower.includes("deepseek-r1") || modelNameLower.includes("reasoning") || modelNameLower.includes("thinking");
|
|
6647
|
+
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);
|
|
6648
|
+
let finalRestrictionSuffix = restrictionSuffix;
|
|
6649
|
+
if (isSimulatedThinking) {
|
|
6650
|
+
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.`)";
|
|
6651
|
+
}
|
|
6388
6652
|
const hardenedHistory = [...history];
|
|
6389
|
-
const userQuestion = { role: "user", content: question +
|
|
6653
|
+
const userQuestion = { role: "user", content: question + finalRestrictionSuffix };
|
|
6390
6654
|
const messages = [...hardenedHistory, userQuestion];
|
|
6391
|
-
const systemPrompt = (
|
|
6655
|
+
const systemPrompt = (_s = this.config.llm.systemPrompt) != null ? _s : "";
|
|
6392
6656
|
const userPrompt = messages.map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
6393
6657
|
let fullReply = "";
|
|
6658
|
+
let thinkingText = "";
|
|
6659
|
+
let thinkingStartMs = performance.now();
|
|
6660
|
+
let thinkingDurationMs = 0;
|
|
6394
6661
|
const generateStart = performance.now();
|
|
6395
6662
|
if (this.llmProvider.chatStream) {
|
|
6396
6663
|
const stream = this.llmProvider.chatStream(messages, context);
|
|
6397
6664
|
if (!stream) throw new Error(`[Pipeline] ${this.config.llm.provider} chatStream returned undefined`);
|
|
6665
|
+
let inThinking = false;
|
|
6666
|
+
let textBuffer = "";
|
|
6398
6667
|
try {
|
|
6399
6668
|
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
6400
6669
|
const chunk = temp.value;
|
|
6401
|
-
|
|
6402
|
-
|
|
6670
|
+
if (chunk.startsWith("\0THINKING\0")) {
|
|
6671
|
+
const thinkingDelta = chunk.slice("\0THINKING\0".length);
|
|
6672
|
+
thinkingText += thinkingDelta;
|
|
6673
|
+
thinkingDurationMs = performance.now() - thinkingStartMs;
|
|
6674
|
+
yield { type: "thinking", text: thinkingDelta };
|
|
6675
|
+
continue;
|
|
6676
|
+
}
|
|
6677
|
+
if (isSimulatedThinking) {
|
|
6678
|
+
textBuffer += chunk;
|
|
6679
|
+
while (textBuffer.length > 0) {
|
|
6680
|
+
if (!inThinking) {
|
|
6681
|
+
const thinkIndex = textBuffer.indexOf("<think>");
|
|
6682
|
+
if (thinkIndex !== -1) {
|
|
6683
|
+
const before = textBuffer.slice(0, thinkIndex);
|
|
6684
|
+
if (before) {
|
|
6685
|
+
fullReply += before;
|
|
6686
|
+
yield before;
|
|
6687
|
+
}
|
|
6688
|
+
inThinking = true;
|
|
6689
|
+
thinkingStartMs = performance.now();
|
|
6690
|
+
textBuffer = textBuffer.slice(thinkIndex + "<think>".length);
|
|
6691
|
+
} else {
|
|
6692
|
+
const maxSafeLength = textBuffer.length - "<think>".length;
|
|
6693
|
+
if (maxSafeLength > 0) {
|
|
6694
|
+
const safeText = textBuffer.slice(0, maxSafeLength);
|
|
6695
|
+
fullReply += safeText;
|
|
6696
|
+
yield safeText;
|
|
6697
|
+
textBuffer = textBuffer.slice(maxSafeLength);
|
|
6698
|
+
}
|
|
6699
|
+
break;
|
|
6700
|
+
}
|
|
6701
|
+
} else {
|
|
6702
|
+
const endThinkIndex = textBuffer.indexOf("</think>");
|
|
6703
|
+
if (endThinkIndex !== -1) {
|
|
6704
|
+
const thinkingDelta = textBuffer.slice(0, endThinkIndex);
|
|
6705
|
+
if (thinkingDelta) {
|
|
6706
|
+
thinkingText += thinkingDelta;
|
|
6707
|
+
thinkingDurationMs = performance.now() - thinkingStartMs;
|
|
6708
|
+
yield { type: "thinking", text: thinkingDelta };
|
|
6709
|
+
}
|
|
6710
|
+
inThinking = false;
|
|
6711
|
+
textBuffer = textBuffer.slice(endThinkIndex + "</think>".length);
|
|
6712
|
+
} else {
|
|
6713
|
+
const maxSafeLength = textBuffer.length - "</think>".length;
|
|
6714
|
+
if (maxSafeLength > 0) {
|
|
6715
|
+
const thinkingDelta = textBuffer.slice(0, maxSafeLength);
|
|
6716
|
+
thinkingText += thinkingDelta;
|
|
6717
|
+
thinkingDurationMs = performance.now() - thinkingStartMs;
|
|
6718
|
+
yield { type: "thinking", text: thinkingDelta };
|
|
6719
|
+
textBuffer = textBuffer.slice(maxSafeLength);
|
|
6720
|
+
}
|
|
6721
|
+
break;
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
} else {
|
|
6726
|
+
fullReply += chunk;
|
|
6727
|
+
yield chunk;
|
|
6728
|
+
}
|
|
6403
6729
|
}
|
|
6404
6730
|
} catch (temp) {
|
|
6405
6731
|
error = [temp];
|
|
@@ -6411,17 +6737,42 @@ ${context}`;
|
|
|
6411
6737
|
throw error[0];
|
|
6412
6738
|
}
|
|
6413
6739
|
}
|
|
6740
|
+
if (isSimulatedThinking && textBuffer.length > 0) {
|
|
6741
|
+
if (inThinking) {
|
|
6742
|
+
thinkingText += textBuffer;
|
|
6743
|
+
yield { type: "thinking", text: textBuffer };
|
|
6744
|
+
} else {
|
|
6745
|
+
fullReply += textBuffer;
|
|
6746
|
+
yield textBuffer;
|
|
6747
|
+
}
|
|
6748
|
+
}
|
|
6414
6749
|
} else {
|
|
6415
6750
|
const reply = yield new __await(this.llmProvider.chat(messages, context));
|
|
6416
|
-
|
|
6417
|
-
|
|
6751
|
+
if (isSimulatedThinking) {
|
|
6752
|
+
const thinkStart = reply.indexOf("<think>");
|
|
6753
|
+
const thinkEnd = reply.indexOf("</think>");
|
|
6754
|
+
if (thinkStart !== -1 && thinkEnd !== -1 && thinkEnd > thinkStart) {
|
|
6755
|
+
thinkingText = reply.slice(thinkStart + "<think>".length, thinkEnd);
|
|
6756
|
+
fullReply = reply.slice(0, thinkStart) + reply.slice(thinkEnd + "</think>".length);
|
|
6757
|
+
thinkingDurationMs = 100;
|
|
6758
|
+
} else {
|
|
6759
|
+
fullReply = reply;
|
|
6760
|
+
}
|
|
6761
|
+
} else {
|
|
6762
|
+
fullReply = reply;
|
|
6763
|
+
}
|
|
6764
|
+
yield fullReply;
|
|
6765
|
+
}
|
|
6766
|
+
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;
|
|
6767
|
+
if (runHallucination) {
|
|
6768
|
+
hallucinationScoringPromise = scoreHallucination(this.llmProvider, fullReply, context).catch(() => void 0);
|
|
6418
6769
|
}
|
|
6419
6770
|
const generateMs = performance.now() - generateStart;
|
|
6420
6771
|
const totalMs = performance.now() - requestStart;
|
|
6421
6772
|
const latency = {
|
|
6422
6773
|
embedMs: Math.round(embedMs),
|
|
6423
6774
|
retrieveMs: Math.round(retrieveMs),
|
|
6424
|
-
rerankMs: ((
|
|
6775
|
+
rerankMs: ((_v = this.config.rag) == null ? void 0 : _v.useReranking) ? Math.round(rerankMs) : void 0,
|
|
6425
6776
|
generateMs: Math.round(generateMs),
|
|
6426
6777
|
totalMs: Math.round(totalMs)
|
|
6427
6778
|
};
|
|
@@ -6434,12 +6785,16 @@ ${context}`;
|
|
|
6434
6785
|
totalTokens: promptTokens + completionTokens,
|
|
6435
6786
|
estimatedCostUsd: estimateCostUsd(promptTokens, completionTokens, this.config.llm.model)
|
|
6436
6787
|
};
|
|
6437
|
-
const
|
|
6788
|
+
const [uiTransformation, hallucinationResult] = yield new __await(Promise.all([
|
|
6789
|
+
uiTransformationPromise,
|
|
6790
|
+
hallucinationScoringPromise
|
|
6791
|
+
]));
|
|
6792
|
+
const trace = __spreadValues({
|
|
6438
6793
|
requestId,
|
|
6439
6794
|
query: question,
|
|
6440
6795
|
rewrittenQuery,
|
|
6441
6796
|
systemPrompt,
|
|
6442
|
-
userPrompt: question +
|
|
6797
|
+
userPrompt: question + finalRestrictionSuffix,
|
|
6443
6798
|
chunks: sources.map((s) => {
|
|
6444
6799
|
var _a2;
|
|
6445
6800
|
return {
|
|
@@ -6453,22 +6808,19 @@ ${context}`;
|
|
|
6453
6808
|
latency,
|
|
6454
6809
|
tokens,
|
|
6455
6810
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6456
|
-
}
|
|
6457
|
-
|
|
6811
|
+
}, hallucinationResult ? {
|
|
6812
|
+
hallucinationScore: hallucinationResult.score,
|
|
6813
|
+
hallucinationReason: hallucinationResult.reason
|
|
6814
|
+
} : {});
|
|
6458
6815
|
yield {
|
|
6459
6816
|
reply: "",
|
|
6460
6817
|
sources,
|
|
6461
6818
|
graphData,
|
|
6462
6819
|
ui_transformation: uiTransformation,
|
|
6463
|
-
trace
|
|
6820
|
+
trace,
|
|
6821
|
+
thinking: thinkingText || void 0,
|
|
6822
|
+
thinkingMs: thinkingDurationMs > 0 ? Math.round(thinkingDurationMs) : void 0
|
|
6464
6823
|
};
|
|
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
6824
|
} catch (error2) {
|
|
6473
6825
|
throw new Error(`[Pipeline] Stream failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
6474
6826
|
}
|
|
@@ -6478,18 +6830,30 @@ ${context}`;
|
|
|
6478
6830
|
* Universal retrieval method combining all enabled providers.
|
|
6479
6831
|
* Uses an LRU-bounded embedding cache to avoid re-embedding the same query.
|
|
6480
6832
|
*/
|
|
6481
|
-
async generateUiTransformation(question, sources,
|
|
6833
|
+
async generateUiTransformation(question, sources, cachedSchema, forceDeterministic = false) {
|
|
6834
|
+
var _a;
|
|
6482
6835
|
if (!sources || sources.length === 0) {
|
|
6483
|
-
return UITransformer.transform(question, sources, this.config,
|
|
6836
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6837
|
+
}
|
|
6838
|
+
if (UITransformer.isProductQueryPublic(question)) {
|
|
6839
|
+
return UITransformer.transform(
|
|
6840
|
+
question,
|
|
6841
|
+
sources,
|
|
6842
|
+
this.config,
|
|
6843
|
+
cachedSchema,
|
|
6844
|
+
{ visualizationHint: "product_browse", recommendedChart: "text", filterInStockOnly: false, wantsExplicitTable: false, isTemporal: false, isComparison: false, language: "en" }
|
|
6845
|
+
);
|
|
6484
6846
|
}
|
|
6485
|
-
|
|
6486
|
-
|
|
6847
|
+
const isLocalProvider = this.config.llm.provider === "ollama";
|
|
6848
|
+
const disableLlmUiTransform = ((_a = this.config.llm.options) == null ? void 0 : _a.disableLlmUiTransform) === true;
|
|
6849
|
+
if (forceDeterministic || isLocalProvider || disableLlmUiTransform) {
|
|
6850
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6487
6851
|
}
|
|
6488
6852
|
try {
|
|
6489
6853
|
return await UITransformer.analyzeAndDecide(question, sources, this.llmRouter.get("fast"));
|
|
6490
6854
|
} catch (err) {
|
|
6491
6855
|
console.warn("[Pipeline] generateUiTransformation failed, using heuristic fallback:", err);
|
|
6492
|
-
return UITransformer.transform(question, sources, this.config,
|
|
6856
|
+
return UITransformer.transform(question, sources, this.config, cachedSchema);
|
|
6493
6857
|
}
|
|
6494
6858
|
}
|
|
6495
6859
|
applyStructuredFilters(sources, filter) {
|
|
@@ -6573,25 +6937,29 @@ ${context}`;
|
|
|
6573
6937
|
return Number.isFinite(numeric) ? numeric : null;
|
|
6574
6938
|
}
|
|
6575
6939
|
async retrieve(query, options) {
|
|
6576
|
-
var _a, _b, _c, _d, _e, _f;
|
|
6940
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
6577
6941
|
const ns = (_a = options.namespace) != null ? _a : this.config.projectId;
|
|
6578
6942
|
const topK = (_b = options.topK) != null ? _b : 5;
|
|
6579
6943
|
const cacheKey = `${ns}::${query}`;
|
|
6580
6944
|
let queryVector = this.embeddingCache.get(cacheKey);
|
|
6581
|
-
const
|
|
6945
|
+
const useGraph = this.graphDB && ((_c = this.config.rag) == null ? void 0 : _c.useGraphRetrieval);
|
|
6946
|
+
const strategy = await QueryProcessor.determineRetrievalStrategy(
|
|
6947
|
+
query,
|
|
6948
|
+
useGraph ? this.llmRouter.get("fast") : void 0
|
|
6949
|
+
);
|
|
6582
6950
|
console.debug(`[Pipeline] Determined retrieval strategy: ${strategy}`);
|
|
6583
6951
|
const [retrievedVector, graphData] = await Promise.all([
|
|
6584
6952
|
// Only embed if we need vector search (strategy is 'vector' or 'both')
|
|
6585
6953
|
strategy === "vector" || strategy === "both" ? queryVector ? Promise.resolve(queryVector) : this.embeddingProvider.embed(query, { taskType: "query" }) : Promise.resolve([]),
|
|
6586
6954
|
// Only query graph if we need graph search (strategy is 'graph' or 'both')
|
|
6587
|
-
(strategy === "graph" || strategy === "both") && this.graphDB && ((
|
|
6955
|
+
(strategy === "graph" || strategy === "both") && this.graphDB && ((_d = this.config.rag) == null ? void 0 : _d.useGraphRetrieval) ? this.graphDB.query(query) : Promise.resolve(void 0)
|
|
6588
6956
|
]);
|
|
6589
6957
|
if ((strategy === "vector" || strategy === "both") && !queryVector && retrievedVector && retrievedVector.length > 0) {
|
|
6590
6958
|
this.embeddingCache.set(cacheKey, retrievedVector);
|
|
6591
6959
|
queryVector = retrievedVector;
|
|
6592
6960
|
}
|
|
6593
|
-
const baseFilter = __spreadProps(__spreadValues({}, (
|
|
6594
|
-
const numericPredicates = QueryProcessor.extractNumericPredicates(query, (
|
|
6961
|
+
const baseFilter = __spreadProps(__spreadValues({}, (_e = options.filter) != null ? _e : {}), { queryText: query });
|
|
6962
|
+
const numericPredicates = QueryProcessor.extractNumericPredicates(query, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
|
|
6595
6963
|
if (numericPredicates.length > 0) {
|
|
6596
6964
|
baseFilter.__numericPredicates = numericPredicates;
|
|
6597
6965
|
}
|
|
@@ -6602,7 +6970,7 @@ ${context}`;
|
|
|
6602
6970
|
) : [];
|
|
6603
6971
|
const resolvedSources = [];
|
|
6604
6972
|
for (const source of sources) {
|
|
6605
|
-
const parentId = (
|
|
6973
|
+
const parentId = (_g = source.metadata) == null ? void 0 : _g.parent_id;
|
|
6606
6974
|
if (parentId) {
|
|
6607
6975
|
console.log(`[Pipeline] Multi-Vector: Found child chunk. Parent ID: ${parentId}`);
|
|
6608
6976
|
resolvedSources.push(source);
|
|
@@ -6625,10 +6993,13 @@ New Question: ${question}
|
|
|
6625
6993
|
Optimized Search Query:`;
|
|
6626
6994
|
const rewrite = await this.llmProvider.chat(
|
|
6627
6995
|
[
|
|
6628
|
-
{ role: "system", content: "You are an assistant that optimizes search queries for RAG systems." },
|
|
6629
6996
|
{ role: "user", content: prompt }
|
|
6630
6997
|
],
|
|
6631
|
-
""
|
|
6998
|
+
"",
|
|
6999
|
+
{
|
|
7000
|
+
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.",
|
|
7001
|
+
temperature: 0
|
|
7002
|
+
}
|
|
6632
7003
|
);
|
|
6633
7004
|
return rewrite.trim() || question;
|
|
6634
7005
|
}
|
|
@@ -6652,10 +7023,13 @@ ${context}
|
|
|
6652
7023
|
Suggestions:`;
|
|
6653
7024
|
const response = await this.llmProvider.chat(
|
|
6654
7025
|
[
|
|
6655
|
-
{ role: "system", content: "You are a helpful assistant that generates search suggestions." },
|
|
6656
7026
|
{ role: "user", content: prompt }
|
|
6657
7027
|
],
|
|
6658
|
-
""
|
|
7028
|
+
"",
|
|
7029
|
+
{
|
|
7030
|
+
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.",
|
|
7031
|
+
temperature: 0
|
|
7032
|
+
}
|
|
6659
7033
|
);
|
|
6660
7034
|
const match = response.match(/\[[\s\S]*\]/);
|
|
6661
7035
|
if (match) {
|
|
@@ -6779,6 +7153,8 @@ var VectorPlugin = class {
|
|
|
6779
7153
|
constructor(hostConfig) {
|
|
6780
7154
|
this.config = ConfigResolver.resolve(hostConfig);
|
|
6781
7155
|
this.validationPromise = ConfigValidator.validateAndThrow(this.config);
|
|
7156
|
+
this.validationPromise.catch(() => {
|
|
7157
|
+
});
|
|
6782
7158
|
this.pipeline = new Pipeline(this.config);
|
|
6783
7159
|
}
|
|
6784
7160
|
/**
|
|
@@ -6812,31 +7188,93 @@ var VectorPlugin = class {
|
|
|
6812
7188
|
* Run a chat query.
|
|
6813
7189
|
*/
|
|
6814
7190
|
async chat(message, history = [], namespace) {
|
|
6815
|
-
|
|
6816
|
-
|
|
7191
|
+
try {
|
|
7192
|
+
await this.validationPromise;
|
|
7193
|
+
} catch (err) {
|
|
7194
|
+
throw wrapError(err, "CONFIGURATION_ERROR");
|
|
7195
|
+
}
|
|
7196
|
+
try {
|
|
7197
|
+
return await this.pipeline.ask(message, history, namespace);
|
|
7198
|
+
} catch (err) {
|
|
7199
|
+
const msg = String(err);
|
|
7200
|
+
let defaultCode = "RETRIEVAL_FAILED";
|
|
7201
|
+
if (msg.includes("Embed") || msg.includes("embed")) {
|
|
7202
|
+
defaultCode = "EMBEDDING_FAILED";
|
|
7203
|
+
}
|
|
7204
|
+
throw wrapError(err, defaultCode);
|
|
7205
|
+
}
|
|
6817
7206
|
}
|
|
6818
7207
|
/**
|
|
6819
7208
|
* Run a streaming chat query.
|
|
6820
7209
|
*/
|
|
6821
7210
|
chatStream(_0) {
|
|
6822
7211
|
return __asyncGenerator(this, arguments, function* (message, history = [], namespace) {
|
|
6823
|
-
|
|
6824
|
-
|
|
7212
|
+
try {
|
|
7213
|
+
yield new __await(this.validationPromise);
|
|
7214
|
+
} catch (err) {
|
|
7215
|
+
throw wrapError(err, "CONFIGURATION_ERROR");
|
|
7216
|
+
}
|
|
7217
|
+
try {
|
|
7218
|
+
const stream = this.pipeline.askStream(message, history, namespace);
|
|
7219
|
+
try {
|
|
7220
|
+
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
7221
|
+
const chunk = temp.value;
|
|
7222
|
+
yield chunk;
|
|
7223
|
+
}
|
|
7224
|
+
} catch (temp) {
|
|
7225
|
+
error = [temp];
|
|
7226
|
+
} finally {
|
|
7227
|
+
try {
|
|
7228
|
+
more && (temp = iter.return) && (yield new __await(temp.call(iter)));
|
|
7229
|
+
} finally {
|
|
7230
|
+
if (error)
|
|
7231
|
+
throw error[0];
|
|
7232
|
+
}
|
|
7233
|
+
}
|
|
7234
|
+
} catch (err) {
|
|
7235
|
+
const msg = String(err);
|
|
7236
|
+
let defaultCode = "RETRIEVAL_FAILED";
|
|
7237
|
+
if (msg.includes("Embed") || msg.includes("embed")) {
|
|
7238
|
+
defaultCode = "EMBEDDING_FAILED";
|
|
7239
|
+
}
|
|
7240
|
+
throw wrapError(err, defaultCode);
|
|
7241
|
+
}
|
|
6825
7242
|
});
|
|
6826
7243
|
}
|
|
6827
7244
|
/**
|
|
6828
7245
|
* Ingest documents into the vector database.
|
|
6829
7246
|
*/
|
|
6830
7247
|
async ingest(documents, namespace) {
|
|
6831
|
-
|
|
6832
|
-
|
|
7248
|
+
try {
|
|
7249
|
+
await this.validationPromise;
|
|
7250
|
+
} catch (err) {
|
|
7251
|
+
throw wrapError(err, "CONFIGURATION_ERROR");
|
|
7252
|
+
}
|
|
7253
|
+
try {
|
|
7254
|
+
return await this.pipeline.ingest(documents, namespace);
|
|
7255
|
+
} catch (err) {
|
|
7256
|
+
const msg = String(err);
|
|
7257
|
+
let defaultCode = "RETRIEVAL_FAILED";
|
|
7258
|
+
if (msg.includes("Embed") || msg.includes("embed")) {
|
|
7259
|
+
defaultCode = "EMBEDDING_FAILED";
|
|
7260
|
+
}
|
|
7261
|
+
throw wrapError(err, defaultCode);
|
|
7262
|
+
}
|
|
6833
7263
|
}
|
|
6834
7264
|
/**
|
|
6835
7265
|
* Get auto-suggestions based on a query prefix.
|
|
6836
7266
|
*/
|
|
6837
7267
|
async getSuggestions(query, namespace) {
|
|
6838
|
-
|
|
6839
|
-
|
|
7268
|
+
try {
|
|
7269
|
+
await this.validationPromise;
|
|
7270
|
+
} catch (err) {
|
|
7271
|
+
throw wrapError(err, "CONFIGURATION_ERROR");
|
|
7272
|
+
}
|
|
7273
|
+
try {
|
|
7274
|
+
return await this.pipeline.getSuggestions(query, namespace);
|
|
7275
|
+
} catch (err) {
|
|
7276
|
+
throw wrapError(err, "RETRIEVAL_FAILED");
|
|
7277
|
+
}
|
|
6840
7278
|
}
|
|
6841
7279
|
};
|
|
6842
7280
|
|
|
@@ -6994,6 +7432,10 @@ function createStreamHandler(configOrPlugin) {
|
|
|
6994
7432
|
if (!isActive) break;
|
|
6995
7433
|
if (typeof chunk === "string") {
|
|
6996
7434
|
enqueue(sseTextFrame(chunk));
|
|
7435
|
+
} else if (chunk && typeof chunk === "object" && "type" in chunk && chunk.type === "thinking") {
|
|
7436
|
+
enqueue(`data: ${JSON.stringify(chunk)}
|
|
7437
|
+
|
|
7438
|
+
`);
|
|
6997
7439
|
} else {
|
|
6998
7440
|
enqueue(sseMetaFrame(chunk));
|
|
6999
7441
|
const responseChunk = chunk;
|
|
@@ -7182,10 +7624,67 @@ function createSuggestionsHandler(configOrPlugin) {
|
|
|
7182
7624
|
}
|
|
7183
7625
|
};
|
|
7184
7626
|
}
|
|
7627
|
+
function createRagHandler(configOrPlugin) {
|
|
7628
|
+
const plugin = configOrPlugin instanceof VectorPlugin ? configOrPlugin : new VectorPlugin(configOrPlugin);
|
|
7629
|
+
const chatHandler = createChatHandler(plugin);
|
|
7630
|
+
const streamHandler = createStreamHandler(plugin);
|
|
7631
|
+
const uploadHandler = createUploadHandler(plugin);
|
|
7632
|
+
const healthHandler = createHealthHandler(plugin);
|
|
7633
|
+
const suggestionsHandler = createSuggestionsHandler(plugin);
|
|
7634
|
+
async function routePostRequest(req, segment) {
|
|
7635
|
+
switch (segment) {
|
|
7636
|
+
case "chat":
|
|
7637
|
+
return streamHandler(req);
|
|
7638
|
+
case "chat-sync":
|
|
7639
|
+
return chatHandler(req);
|
|
7640
|
+
case "upload":
|
|
7641
|
+
return uploadHandler(req);
|
|
7642
|
+
case "suggestions":
|
|
7643
|
+
return suggestionsHandler(req);
|
|
7644
|
+
case "health":
|
|
7645
|
+
return healthHandler();
|
|
7646
|
+
default:
|
|
7647
|
+
return NextResponse.json({ error: `Not Found: POST segment "${segment}" not supported.` }, { status: 404 });
|
|
7648
|
+
}
|
|
7649
|
+
}
|
|
7650
|
+
async function routeGetRequest(req, segment) {
|
|
7651
|
+
if (segment === "health") {
|
|
7652
|
+
return healthHandler();
|
|
7653
|
+
}
|
|
7654
|
+
return NextResponse.json({ error: `Method Not Allowed: GET is only supported for "health" segment.` }, { status: 405 });
|
|
7655
|
+
}
|
|
7656
|
+
async function getSegment(context) {
|
|
7657
|
+
var _a;
|
|
7658
|
+
const resolvedParams = typeof ((_a = context == null ? void 0 : context.params) == null ? void 0 : _a.then) === "function" ? await context.params : context == null ? void 0 : context.params;
|
|
7659
|
+
const segments = (resolvedParams == null ? void 0 : resolvedParams.retrivora) || [];
|
|
7660
|
+
return segments[0] || "chat";
|
|
7661
|
+
}
|
|
7662
|
+
return {
|
|
7663
|
+
GET: async (req, context) => {
|
|
7664
|
+
try {
|
|
7665
|
+
const segment = await getSegment(context);
|
|
7666
|
+
return await routeGetRequest(req, segment);
|
|
7667
|
+
} catch (err) {
|
|
7668
|
+
const msg = err instanceof Error ? err.message : "GET Routing failed";
|
|
7669
|
+
return NextResponse.json({ error: msg }, { status: 500 });
|
|
7670
|
+
}
|
|
7671
|
+
},
|
|
7672
|
+
POST: async (req, context) => {
|
|
7673
|
+
try {
|
|
7674
|
+
const segment = await getSegment(context);
|
|
7675
|
+
return await routePostRequest(req, segment);
|
|
7676
|
+
} catch (err) {
|
|
7677
|
+
const msg = err instanceof Error ? err.message : "POST Routing failed";
|
|
7678
|
+
return NextResponse.json({ error: msg }, { status: 500 });
|
|
7679
|
+
}
|
|
7680
|
+
}
|
|
7681
|
+
};
|
|
7682
|
+
}
|
|
7185
7683
|
export {
|
|
7186
7684
|
createChatHandler,
|
|
7187
7685
|
createHealthHandler,
|
|
7188
7686
|
createIngestHandler,
|
|
7687
|
+
createRagHandler,
|
|
7189
7688
|
createStreamHandler,
|
|
7190
7689
|
createSuggestionsHandler,
|
|
7191
7690
|
createUploadHandler,
|