@retrivora-ai/rag-engine 1.0.9 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{QdrantProvider-NGFXVDCF.mjs → QdrantProvider-NMXOULCU.mjs} +1 -1
- package/dist/chunk-63HITIWC.mjs +245 -0
- package/dist/{chunk-7S2SRQGL.mjs → chunk-JZ4H7EP6.mjs} +147 -3
- package/dist/handlers/index.js +263 -48
- package/dist/handlers/index.mjs +1 -1
- package/dist/server.d.mts +12 -6
- package/dist/server.d.ts +12 -6
- package/dist/server.js +263 -48
- package/dist/server.mjs +2 -2
- package/package.json +1 -1
- package/src/core/Pipeline.ts +2 -0
- package/src/llm/providers/AnthropicProvider.ts +29 -0
- package/src/llm/providers/GeminiProvider.ts +31 -0
- package/src/llm/providers/OllamaProvider.ts +21 -2
- package/src/llm/providers/OpenAIProvider.ts +35 -0
- package/src/providers/vectordb/QdrantProvider.ts +154 -60
- package/dist/chunk-NT5VP7MT.mjs +0 -174
package/dist/server.js
CHANGED
|
@@ -854,20 +854,76 @@ var init_QdrantProvider = __esm({
|
|
|
854
854
|
QdrantProvider = class extends BaseVectorProvider {
|
|
855
855
|
constructor(config) {
|
|
856
856
|
super(config);
|
|
857
|
+
this.schemaDiscovered = false;
|
|
857
858
|
const opts = config.options;
|
|
858
859
|
const baseUrl = opts.baseUrl;
|
|
859
860
|
if (!baseUrl) throw new Error("[QdrantProvider] baseUrl is required");
|
|
861
|
+
this.contentField = opts.contentField || "content";
|
|
862
|
+
this.metadataField = opts.metadataField || "metadata";
|
|
863
|
+
this.isFlatPayload = !!opts.flatPayload;
|
|
860
864
|
this.http = import_axios4.default.create({
|
|
861
865
|
baseURL: baseUrl,
|
|
866
|
+
timeout: 15e3,
|
|
867
|
+
// 15s timeout for vector DB operations
|
|
862
868
|
headers: __spreadValues({
|
|
863
869
|
"Content-Type": "application/json"
|
|
864
870
|
}, opts.apiKey ? { "api-key": opts.apiKey } : {})
|
|
865
871
|
});
|
|
866
872
|
}
|
|
867
873
|
async initialize() {
|
|
874
|
+
if (this.schemaDiscovered) return;
|
|
868
875
|
await this.ping();
|
|
869
876
|
await this.ensureCollection();
|
|
870
|
-
|
|
877
|
+
console.log(`[QdrantProvider] \u{1F50D} Discovering schema for collection "${this.indexName}"...`);
|
|
878
|
+
const discoveredFields = await this.discoverSchema();
|
|
879
|
+
const opts = this.config.options;
|
|
880
|
+
const configFields = opts.filterableFields || [];
|
|
881
|
+
const allFields = [.../* @__PURE__ */ new Set([...discoveredFields, ...configFields])];
|
|
882
|
+
if (allFields.length > 0) {
|
|
883
|
+
console.log(`[QdrantProvider] \u{1F6E0} Ensuring indexes for ${allFields.length} discovered fields...`);
|
|
884
|
+
await Promise.all(
|
|
885
|
+
allFields.map(async (fieldInfo) => {
|
|
886
|
+
const [fieldName, schemaType] = fieldInfo.split(":");
|
|
887
|
+
return this.ensureIndex(fieldName, schemaType || "keyword");
|
|
888
|
+
})
|
|
889
|
+
);
|
|
890
|
+
} else {
|
|
891
|
+
console.log(`[QdrantProvider] \u2139\uFE0F No fields discovered for indexing.`);
|
|
892
|
+
}
|
|
893
|
+
this.schemaDiscovered = true;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Samples points from the collection to discover available payload fields.
|
|
897
|
+
*/
|
|
898
|
+
async discoverSchema() {
|
|
899
|
+
var _a;
|
|
900
|
+
try {
|
|
901
|
+
const { data } = await this.http.post(`/collections/${this.indexName}/points/scroll`, {
|
|
902
|
+
limit: 20,
|
|
903
|
+
with_payload: true,
|
|
904
|
+
with_vector: false
|
|
905
|
+
});
|
|
906
|
+
const points = ((_a = data.result) == null ? void 0 : _a.points) || [];
|
|
907
|
+
const keys = /* @__PURE__ */ new Set();
|
|
908
|
+
for (const point of points) {
|
|
909
|
+
const payload = point.payload || {};
|
|
910
|
+
Object.keys(payload).forEach((k) => {
|
|
911
|
+
if (k !== "namespace" && k !== this.contentField && k !== this.metadataField) {
|
|
912
|
+
keys.add(k);
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
if (!this.isFlatPayload && payload[this.metadataField]) {
|
|
916
|
+
const meta = payload[this.metadataField];
|
|
917
|
+
if (typeof meta === "object" && meta !== null) {
|
|
918
|
+
Object.keys(meta).forEach((k) => keys.add(k));
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return Array.from(keys);
|
|
923
|
+
} catch (err) {
|
|
924
|
+
console.warn(`[QdrantProvider] \u26A0\uFE0F Schema discovery failed:`, err instanceof Error ? err.message : String(err));
|
|
925
|
+
return [];
|
|
926
|
+
}
|
|
871
927
|
}
|
|
872
928
|
/**
|
|
873
929
|
* Ensures the collection exists. Creates it if missing.
|
|
@@ -895,29 +951,25 @@ var init_QdrantProvider = __esm({
|
|
|
895
951
|
}
|
|
896
952
|
}
|
|
897
953
|
/**
|
|
898
|
-
* Ensures that
|
|
899
|
-
* Qdrant requires this for search filters to work in many configurations.
|
|
954
|
+
* Ensures that a payload field has an index.
|
|
900
955
|
*/
|
|
901
|
-
async ensureIndex() {
|
|
902
|
-
var _a
|
|
956
|
+
async ensureIndex(fieldName, schema = "keyword") {
|
|
957
|
+
var _a;
|
|
958
|
+
let fullPath = fieldName;
|
|
959
|
+
if (!this.isFlatPayload && !fieldName.includes(".") && fieldName !== "namespace" && fieldName !== this.contentField) {
|
|
960
|
+
fullPath = `${this.metadataField}.${fieldName}`;
|
|
961
|
+
}
|
|
903
962
|
try {
|
|
904
963
|
await this.http.put(`/collections/${this.indexName}/index`, {
|
|
905
|
-
field_name:
|
|
906
|
-
field_schema:
|
|
964
|
+
field_name: fullPath,
|
|
965
|
+
field_schema: schema
|
|
907
966
|
});
|
|
908
|
-
console.log(`[QdrantProvider] \u2705 Ensured
|
|
967
|
+
console.log(`[QdrantProvider] \u2705 Ensured ${schema} index for "${fullPath}"`);
|
|
909
968
|
} catch (err) {
|
|
910
969
|
let status;
|
|
911
|
-
|
|
912
|
-
if (
|
|
913
|
-
|
|
914
|
-
data = (_b = err.response) == null ? void 0 : _b.data;
|
|
915
|
-
}
|
|
916
|
-
if (status === 409) {
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
920
|
-
console.warn(`[QdrantProvider] \u26A0\uFE0F Could not ensure namespace index (Status: ${status}):`, JSON.stringify(data || errorMessage));
|
|
970
|
+
if (import_axios4.default.isAxiosError(err)) status = (_a = err.response) == null ? void 0 : _a.status;
|
|
971
|
+
if (status === 409) return;
|
|
972
|
+
console.warn(`[QdrantProvider] \u26A0\uFE0F Could not ensure index for "${fullPath}":`, err instanceof Error ? err.message : String(err));
|
|
921
973
|
}
|
|
922
974
|
}
|
|
923
975
|
async upsert(doc, namespace) {
|
|
@@ -925,14 +977,21 @@ var init_QdrantProvider = __esm({
|
|
|
925
977
|
}
|
|
926
978
|
async batchUpsert(docs, namespace) {
|
|
927
979
|
const payload = {
|
|
928
|
-
points: docs.map((doc) =>
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
|
|
980
|
+
points: docs.map((doc) => {
|
|
981
|
+
const pointPayload = __spreadValues({
|
|
982
|
+
[this.contentField]: doc.content
|
|
983
|
+
}, namespace ? { namespace } : {});
|
|
984
|
+
if (this.isFlatPayload) {
|
|
985
|
+
Object.assign(pointPayload, doc.metadata || {});
|
|
986
|
+
} else {
|
|
987
|
+
pointPayload[this.metadataField] = doc.metadata || {};
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
id: this.normalizeId(doc.id),
|
|
991
|
+
vector: doc.vector,
|
|
992
|
+
payload: pointPayload
|
|
993
|
+
};
|
|
994
|
+
})
|
|
936
995
|
};
|
|
937
996
|
await this.http.put(`/collections/${this.indexName}/points`, payload);
|
|
938
997
|
}
|
|
@@ -943,7 +1002,15 @@ var init_QdrantProvider = __esm({
|
|
|
943
1002
|
}
|
|
944
1003
|
const sanitizedFilter = this.sanitizeFilter(_filter);
|
|
945
1004
|
for (const [key, value] of Object.entries(sanitizedFilter)) {
|
|
946
|
-
|
|
1005
|
+
let filterKey = key;
|
|
1006
|
+
if (!this.isFlatPayload && !key.includes(".") && key !== "namespace" && key !== this.contentField) {
|
|
1007
|
+
filterKey = `${this.metadataField}.${key}`;
|
|
1008
|
+
}
|
|
1009
|
+
if (Array.isArray(value)) {
|
|
1010
|
+
must.push({ key: filterKey, match: { any: value } });
|
|
1011
|
+
} else {
|
|
1012
|
+
must.push({ key: filterKey, match: { value } });
|
|
1013
|
+
}
|
|
947
1014
|
}
|
|
948
1015
|
const payload = {
|
|
949
1016
|
vector,
|
|
@@ -955,28 +1022,36 @@ var init_QdrantProvider = __esm({
|
|
|
955
1022
|
},
|
|
956
1023
|
filter: must.length > 0 ? { must } : void 0
|
|
957
1024
|
};
|
|
958
|
-
console.log(`[QdrantProvider] \u{1F50D} Searching "${this.indexName}" | Namespace: ${namespace || "default"} | TopK: ${topK}`);
|
|
959
|
-
if (must.length > (namespace ? 1 : 0)) {
|
|
960
|
-
console.log(`[QdrantProvider] \u{1F6E0} Filters:`, JSON.stringify(must.filter((m) => m.key !== "namespace")));
|
|
961
|
-
}
|
|
962
1025
|
const { data } = await this.http.post(`/collections/${this.indexName}/points/search`, payload);
|
|
963
1026
|
const results = (data.result || []).map((res) => {
|
|
964
|
-
|
|
1027
|
+
const p = res.payload || {};
|
|
1028
|
+
let content = p[this.contentField] || "";
|
|
1029
|
+
if (!content) {
|
|
1030
|
+
const stringFields = Object.entries(p).filter(([_, v]) => typeof v === "string").map(([k, v]) => ({ key: k, val: v }));
|
|
1031
|
+
if (stringFields.length > 0) {
|
|
1032
|
+
const bestMatch = stringFields.sort((a, b) => b.val.length - a.val.length)[0];
|
|
1033
|
+
content = bestMatch.val;
|
|
1034
|
+
} else {
|
|
1035
|
+
content = JSON.stringify(p);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
let metadata = {};
|
|
1039
|
+
if (this.isFlatPayload) {
|
|
1040
|
+
metadata = __spreadValues({}, p);
|
|
1041
|
+
delete metadata[this.contentField];
|
|
1042
|
+
delete metadata["namespace"];
|
|
1043
|
+
} else {
|
|
1044
|
+
metadata = p[this.metadataField] || p;
|
|
1045
|
+
}
|
|
965
1046
|
return {
|
|
966
|
-
id: res
|
|
967
|
-
score: res
|
|
968
|
-
content
|
|
969
|
-
metadata
|
|
1047
|
+
id: res.id,
|
|
1048
|
+
score: res.score,
|
|
1049
|
+
content,
|
|
1050
|
+
metadata
|
|
970
1051
|
};
|
|
971
1052
|
});
|
|
972
|
-
if (results.length === 0) {
|
|
973
|
-
console.warn(`[QdrantProvider] \u26A0\uFE0F Retrieval returned 0 results. Check if data exists in namespace "${namespace}"`);
|
|
974
|
-
} else {
|
|
975
|
-
console.log(`[QdrantProvider] \u2705 Found ${results.length} results (best score: ${results[0].score.toFixed(4)})`);
|
|
976
|
-
}
|
|
977
1053
|
return results;
|
|
978
1054
|
}
|
|
979
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
980
1055
|
async delete(id, _namespace) {
|
|
981
1056
|
await this.http.post(`/collections/${this.indexName}/points/delete`, {
|
|
982
1057
|
points: [this.normalizeId(id)]
|
|
@@ -997,10 +1072,6 @@ var init_QdrantProvider = __esm({
|
|
|
997
1072
|
return false;
|
|
998
1073
|
}
|
|
999
1074
|
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Normalizes an ID into a format Qdrant accepts (UUID or integer).
|
|
1002
|
-
* For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
|
|
1003
|
-
*/
|
|
1004
1075
|
normalizeId(id) {
|
|
1005
1076
|
if (typeof id === "number") return id;
|
|
1006
1077
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -1916,6 +1987,53 @@ ${context}`
|
|
|
1916
1987
|
});
|
|
1917
1988
|
return (_h = (_g = (_f = completion.choices[0]) == null ? void 0 : _f.message) == null ? void 0 : _g.content) != null ? _h : "";
|
|
1918
1989
|
}
|
|
1990
|
+
chatStream(messages, context, options) {
|
|
1991
|
+
return __asyncGenerator(this, null, function* () {
|
|
1992
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1993
|
+
const systemContent = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
1994
|
+
|
|
1995
|
+
Context:
|
|
1996
|
+
${context}`;
|
|
1997
|
+
const systemMessage = {
|
|
1998
|
+
role: "system",
|
|
1999
|
+
content: systemContent.includes("{{context}}") ? systemContent.replace("{{context}}", context) : `${systemContent}
|
|
2000
|
+
|
|
2001
|
+
Context:
|
|
2002
|
+
${context}`
|
|
2003
|
+
};
|
|
2004
|
+
const formattedMessages = [
|
|
2005
|
+
systemMessage,
|
|
2006
|
+
...messages.map((m) => ({
|
|
2007
|
+
role: m.role,
|
|
2008
|
+
content: m.content
|
|
2009
|
+
}))
|
|
2010
|
+
];
|
|
2011
|
+
const stream = yield new __await(this.client.chat.completions.create({
|
|
2012
|
+
model: this.llmConfig.model,
|
|
2013
|
+
messages: formattedMessages,
|
|
2014
|
+
max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
|
|
2015
|
+
temperature: (_e = (_d = options == null ? void 0 : options.temperature) != null ? _d : this.llmConfig.temperature) != null ? _e : 0.7,
|
|
2016
|
+
stop: options == null ? void 0 : options.stop,
|
|
2017
|
+
stream: true
|
|
2018
|
+
}));
|
|
2019
|
+
try {
|
|
2020
|
+
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2021
|
+
const chunk = temp.value;
|
|
2022
|
+
const content = ((_g = (_f = chunk.choices[0]) == null ? void 0 : _f.delta) == null ? void 0 : _g.content) || "";
|
|
2023
|
+
if (content) yield content;
|
|
2024
|
+
}
|
|
2025
|
+
} catch (temp) {
|
|
2026
|
+
error = [temp];
|
|
2027
|
+
} finally {
|
|
2028
|
+
try {
|
|
2029
|
+
more && (temp = iter.return) && (yield new __await(temp.call(iter)));
|
|
2030
|
+
} finally {
|
|
2031
|
+
if (error)
|
|
2032
|
+
throw error[0];
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
1919
2037
|
async embed(text, options) {
|
|
1920
2038
|
const results = await this.batchEmbed([text], options);
|
|
1921
2039
|
return results[0];
|
|
@@ -2021,6 +2139,47 @@ ${context}`;
|
|
|
2021
2139
|
const block = response.content[0];
|
|
2022
2140
|
return block.type === "text" ? block.text : "";
|
|
2023
2141
|
}
|
|
2142
|
+
chatStream(messages, context, options) {
|
|
2143
|
+
return __asyncGenerator(this, null, function* () {
|
|
2144
|
+
var _a, _b, _c;
|
|
2145
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
|
|
2146
|
+
|
|
2147
|
+
Context:
|
|
2148
|
+
${context}`;
|
|
2149
|
+
const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2150
|
+
|
|
2151
|
+
Context:
|
|
2152
|
+
${context}`;
|
|
2153
|
+
const anthropicMessages = messages.map((m) => ({
|
|
2154
|
+
role: m.role === "assistant" ? "assistant" : "user",
|
|
2155
|
+
content: m.content
|
|
2156
|
+
}));
|
|
2157
|
+
const stream = yield new __await(this.client.messages.create({
|
|
2158
|
+
model: this.llmConfig.model,
|
|
2159
|
+
max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
|
|
2160
|
+
system,
|
|
2161
|
+
messages: anthropicMessages,
|
|
2162
|
+
stream: true
|
|
2163
|
+
}));
|
|
2164
|
+
try {
|
|
2165
|
+
for (var iter = __forAwait(stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2166
|
+
const chunk = temp.value;
|
|
2167
|
+
if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
|
|
2168
|
+
yield chunk.delta.text;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
} catch (temp) {
|
|
2172
|
+
error = [temp];
|
|
2173
|
+
} finally {
|
|
2174
|
+
try {
|
|
2175
|
+
more && (temp = iter.return) && (yield new __await(temp.call(iter)));
|
|
2176
|
+
} finally {
|
|
2177
|
+
if (error)
|
|
2178
|
+
throw error[0];
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2024
2183
|
async embed(text, options) {
|
|
2025
2184
|
void text;
|
|
2026
2185
|
void options;
|
|
@@ -2126,7 +2285,7 @@ var OllamaProvider = class {
|
|
|
2126
2285
|
}
|
|
2127
2286
|
chatStream(messages, context, options) {
|
|
2128
2287
|
return __asyncGenerator(this, null, function* () {
|
|
2129
|
-
var _a, _b, _c, _d, _e;
|
|
2288
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2130
2289
|
const system = this.buildSystemPrompt(context);
|
|
2131
2290
|
const response = yield new __await(this.http.post("/api/chat", {
|
|
2132
2291
|
model: this.llmConfig.model,
|
|
@@ -2140,11 +2299,15 @@ var OllamaProvider = class {
|
|
|
2140
2299
|
...messages.map((m) => ({ role: m.role, content: m.content }))
|
|
2141
2300
|
]
|
|
2142
2301
|
}, { responseType: "stream" }));
|
|
2302
|
+
let lineBuffer = "";
|
|
2143
2303
|
try {
|
|
2144
2304
|
for (var iter = __forAwait(response.data), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2145
2305
|
const chunk = temp.value;
|
|
2146
|
-
|
|
2306
|
+
lineBuffer += chunk.toString();
|
|
2307
|
+
const lines = lineBuffer.split("\n");
|
|
2308
|
+
lineBuffer = lines.pop() || "";
|
|
2147
2309
|
for (const line of lines) {
|
|
2310
|
+
if (!line.trim()) continue;
|
|
2148
2311
|
try {
|
|
2149
2312
|
const json = JSON.parse(line);
|
|
2150
2313
|
if ((_e = json.message) == null ? void 0 : _e.content) {
|
|
@@ -2152,6 +2315,7 @@ var OllamaProvider = class {
|
|
|
2152
2315
|
}
|
|
2153
2316
|
if (json.done) return;
|
|
2154
2317
|
} catch (e) {
|
|
2318
|
+
console.warn("[OllamaProvider] Failed to parse streaming line:", line);
|
|
2155
2319
|
}
|
|
2156
2320
|
}
|
|
2157
2321
|
}
|
|
@@ -2165,6 +2329,13 @@ var OllamaProvider = class {
|
|
|
2165
2329
|
throw error[0];
|
|
2166
2330
|
}
|
|
2167
2331
|
}
|
|
2332
|
+
if (lineBuffer.trim()) {
|
|
2333
|
+
try {
|
|
2334
|
+
const json = JSON.parse(lineBuffer);
|
|
2335
|
+
if ((_f = json.message) == null ? void 0 : _f.content) yield json.message.content;
|
|
2336
|
+
} catch (e) {
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2168
2339
|
});
|
|
2169
2340
|
}
|
|
2170
2341
|
buildSystemPrompt(context) {
|
|
@@ -2310,6 +2481,49 @@ ${context}`;
|
|
|
2310
2481
|
});
|
|
2311
2482
|
return (_f = response.text) != null ? _f : "";
|
|
2312
2483
|
}
|
|
2484
|
+
chatStream(messages, context, options) {
|
|
2485
|
+
return __asyncGenerator(this, null, function* () {
|
|
2486
|
+
var _a, _b, _c, _d, _e;
|
|
2487
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
2488
|
+
|
|
2489
|
+
Context:
|
|
2490
|
+
${context}`;
|
|
2491
|
+
const systemInstruction = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
2492
|
+
|
|
2493
|
+
Context:
|
|
2494
|
+
${context}`;
|
|
2495
|
+
const geminiMessages = messages.map((m) => ({
|
|
2496
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
2497
|
+
parts: [{ text: m.content }]
|
|
2498
|
+
}));
|
|
2499
|
+
const result = yield new __await(this.client.models.generateContentStream({
|
|
2500
|
+
model: this.llmConfig.model,
|
|
2501
|
+
contents: geminiMessages,
|
|
2502
|
+
config: {
|
|
2503
|
+
systemInstruction,
|
|
2504
|
+
temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
|
|
2505
|
+
maxOutputTokens: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024,
|
|
2506
|
+
stopSequences: options == null ? void 0 : options.stop
|
|
2507
|
+
}
|
|
2508
|
+
}));
|
|
2509
|
+
try {
|
|
2510
|
+
for (var iter = __forAwait(result.stream), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) {
|
|
2511
|
+
const chunk = temp.value;
|
|
2512
|
+
const text = chunk.text;
|
|
2513
|
+
if (text) yield text;
|
|
2514
|
+
}
|
|
2515
|
+
} catch (temp) {
|
|
2516
|
+
error = [temp];
|
|
2517
|
+
} finally {
|
|
2518
|
+
try {
|
|
2519
|
+
more && (temp = iter.return) && (yield new __await(temp.call(iter)));
|
|
2520
|
+
} finally {
|
|
2521
|
+
if (error)
|
|
2522
|
+
throw error[0];
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2313
2527
|
async embed(text, options) {
|
|
2314
2528
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2315
2529
|
const model = this.sanitizeModel(
|
|
@@ -3857,6 +4071,7 @@ var Pipeline = class {
|
|
|
3857
4071
|
}
|
|
3858
4072
|
const hints = QueryProcessor.extractQueryFieldHints(question, (_f = this.config.rag) == null ? void 0 : _f.filterableFields);
|
|
3859
4073
|
const filter = QueryProcessor.buildQueryFilter(question, hints);
|
|
4074
|
+
console.log(`[Pipeline] \u{1F9E9} Extracted filters:`, JSON.stringify(filter));
|
|
3860
4075
|
const { sources: rawSources, graphData } = yield new __await(this.retrieve(searchQuery, {
|
|
3861
4076
|
namespace: ns,
|
|
3862
4077
|
topK: topK * 2,
|
package/dist/server.mjs
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
sseFrame,
|
|
41
41
|
sseMetaFrame,
|
|
42
42
|
sseTextFrame
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-JZ4H7EP6.mjs";
|
|
44
44
|
import "./chunk-YLTMFW4M.mjs";
|
|
45
45
|
import {
|
|
46
46
|
PineconeProvider
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
} from "./chunk-U55XRW3U.mjs";
|
|
57
57
|
import {
|
|
58
58
|
QdrantProvider
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-63HITIWC.mjs";
|
|
60
60
|
import {
|
|
61
61
|
BaseVectorProvider
|
|
62
62
|
} from "./chunk-IMP6FUCY.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retrivora-ai/rag-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
|
|
5
5
|
"author": "Abhinav Alkuchi",
|
|
6
6
|
"license": "MIT",
|
package/src/core/Pipeline.ts
CHANGED
|
@@ -317,6 +317,8 @@ export class Pipeline {
|
|
|
317
317
|
// 2. Parallel Retrieval
|
|
318
318
|
const hints = QueryProcessor.extractQueryFieldHints(question, this.config.rag?.filterableFields);
|
|
319
319
|
const filter = QueryProcessor.buildQueryFilter(question, hints);
|
|
320
|
+
|
|
321
|
+
console.log(`[Pipeline] 🧩 Extracted filters:`, JSON.stringify(filter));
|
|
320
322
|
|
|
321
323
|
const { sources: rawSources, graphData } = await this.retrieve(searchQuery, {
|
|
322
324
|
namespace: ns,
|
|
@@ -98,6 +98,35 @@ export class AnthropicProvider implements ILLMProvider {
|
|
|
98
98
|
return block.type === 'text' ? block.text : '';
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
async *chatStream(messages: ChatMessage[], context: string, options?: ChatOptions): AsyncIterable<string> {
|
|
102
|
+
const systemPrompt =
|
|
103
|
+
this.llmConfig.systemPrompt ??
|
|
104
|
+
`You are a helpful assistant. Use the context below to answer the user's question accurately.\n\nContext:\n${context}`;
|
|
105
|
+
|
|
106
|
+
const system = systemPrompt.includes('{{context}}')
|
|
107
|
+
? systemPrompt.replace('{{context}}', context)
|
|
108
|
+
: `${systemPrompt}\n\nContext:\n${context}`;
|
|
109
|
+
|
|
110
|
+
const anthropicMessages: Anthropic.MessageParam[] = messages.map((m) => ({
|
|
111
|
+
role: m.role === 'assistant' ? 'assistant' : 'user',
|
|
112
|
+
content: m.content,
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
const stream = await this.client.messages.create({
|
|
116
|
+
model: this.llmConfig.model,
|
|
117
|
+
max_tokens: options?.maxTokens ?? this.llmConfig.maxTokens ?? 1024,
|
|
118
|
+
system,
|
|
119
|
+
messages: anthropicMessages,
|
|
120
|
+
stream: true,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
for await (const chunk of stream) {
|
|
124
|
+
if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') {
|
|
125
|
+
yield chunk.delta.text;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
101
130
|
async embed(text: string, options?: EmbedOptions): Promise<number[]> {
|
|
102
131
|
void text;
|
|
103
132
|
void options;
|
|
@@ -130,6 +130,37 @@ export class GeminiProvider implements ILLMProvider {
|
|
|
130
130
|
return response.text ?? '';
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
async *chatStream(messages: ChatMessage[], context: string, options?: ChatOptions): AsyncIterable<string> {
|
|
134
|
+
const systemPrompt =
|
|
135
|
+
this.llmConfig.systemPrompt ??
|
|
136
|
+
`You are a helpful assistant. Answer questions based on the provided context.\n\nContext:\n${context}`;
|
|
137
|
+
|
|
138
|
+
const systemInstruction = systemPrompt.includes('{{context}}')
|
|
139
|
+
? systemPrompt.replace('{{context}}', context)
|
|
140
|
+
: `${systemPrompt}\n\nContext:\n${context}`;
|
|
141
|
+
|
|
142
|
+
const geminiMessages = messages.map((m) => ({
|
|
143
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
144
|
+
parts: [{ text: m.content }],
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
const result = await this.client.models.generateContentStream({
|
|
148
|
+
model: this.llmConfig.model,
|
|
149
|
+
contents: geminiMessages,
|
|
150
|
+
config: {
|
|
151
|
+
systemInstruction,
|
|
152
|
+
temperature: options?.temperature ?? this.llmConfig.temperature ?? 0.7,
|
|
153
|
+
maxOutputTokens: options?.maxTokens ?? this.llmConfig.maxTokens ?? 1024,
|
|
154
|
+
stopSequences: options?.stop,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
for await (const chunk of result.stream) {
|
|
159
|
+
const text = chunk.text;
|
|
160
|
+
if (text) yield text;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
133
164
|
async embed(text: string, options?: EmbedOptions): Promise<number[]> {
|
|
134
165
|
const model = this.sanitizeModel(
|
|
135
166
|
options?.model ??
|
|
@@ -118,9 +118,16 @@ export class OllamaProvider implements ILLMProvider {
|
|
|
118
118
|
],
|
|
119
119
|
}, { responseType: 'stream' });
|
|
120
120
|
|
|
121
|
+
let lineBuffer = '';
|
|
121
122
|
for await (const chunk of response.data) {
|
|
122
|
-
|
|
123
|
+
lineBuffer += chunk.toString();
|
|
124
|
+
const lines = lineBuffer.split('\n');
|
|
125
|
+
|
|
126
|
+
// Keep the last partial line in the buffer
|
|
127
|
+
lineBuffer = lines.pop() || '';
|
|
128
|
+
|
|
123
129
|
for (const line of lines) {
|
|
130
|
+
if (!line.trim()) continue;
|
|
124
131
|
try {
|
|
125
132
|
const json = JSON.parse(line);
|
|
126
133
|
if (json.message?.content) {
|
|
@@ -128,10 +135,22 @@ export class OllamaProvider implements ILLMProvider {
|
|
|
128
135
|
}
|
|
129
136
|
if (json.done) return;
|
|
130
137
|
} catch {
|
|
131
|
-
//
|
|
138
|
+
// If parsing fails, it might be a malformed line or something else
|
|
139
|
+
// But with line buffering, this should be rare
|
|
140
|
+
console.warn('[OllamaProvider] Failed to parse streaming line:', line);
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
143
|
}
|
|
144
|
+
|
|
145
|
+
// Process anything left in the buffer at the end
|
|
146
|
+
if (lineBuffer.trim()) {
|
|
147
|
+
try {
|
|
148
|
+
const json = JSON.parse(lineBuffer);
|
|
149
|
+
if (json.message?.content) yield json.message.content;
|
|
150
|
+
} catch {
|
|
151
|
+
// Final cleanup
|
|
152
|
+
}
|
|
153
|
+
}
|
|
135
154
|
}
|
|
136
155
|
|
|
137
156
|
private buildSystemPrompt(context: string): string {
|
|
@@ -108,6 +108,41 @@ export class OpenAIProvider implements ILLMProvider {
|
|
|
108
108
|
|
|
109
109
|
return completion.choices[0]?.message?.content ?? '';
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
async *chatStream(messages: ChatMessage[], context: string, options?: ChatOptions): AsyncIterable<string> {
|
|
113
|
+
const systemContent =
|
|
114
|
+
this.llmConfig.systemPrompt ??
|
|
115
|
+
`You are a helpful assistant. Answer questions based on the provided context.\n\nContext:\n${context}`;
|
|
116
|
+
|
|
117
|
+
const systemMessage: OpenAI.Chat.ChatCompletionSystemMessageParam = {
|
|
118
|
+
role: 'system',
|
|
119
|
+
content: systemContent.includes('{{context}}')
|
|
120
|
+
? systemContent.replace('{{context}}', context)
|
|
121
|
+
: `${systemContent}\n\nContext:\n${context}`,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const formattedMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
|
125
|
+
systemMessage,
|
|
126
|
+
...messages.map((m) => ({
|
|
127
|
+
role: m.role as 'user' | 'assistant',
|
|
128
|
+
content: m.content,
|
|
129
|
+
})),
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
const stream = await this.client.chat.completions.create({
|
|
133
|
+
model: this.llmConfig.model,
|
|
134
|
+
messages: formattedMessages,
|
|
135
|
+
max_tokens: options?.maxTokens ?? this.llmConfig.maxTokens ?? 1024,
|
|
136
|
+
temperature: options?.temperature ?? this.llmConfig.temperature ?? 0.7,
|
|
137
|
+
stop: options?.stop,
|
|
138
|
+
stream: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
for await (const chunk of stream) {
|
|
142
|
+
const content = chunk.choices[0]?.delta?.content || '';
|
|
143
|
+
if (content) yield content;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
111
146
|
|
|
112
147
|
async embed(text: string, options?: EmbedOptions): Promise<number[]> {
|
|
113
148
|
const results = await this.batchEmbed([text], options);
|