@retrivora-ai/rag-engine 0.1.0
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/.env.example +77 -0
- package/README.md +149 -0
- package/dist/DocumentChunker-BUrIrcPk.d.mts +43 -0
- package/dist/DocumentChunker-BUrIrcPk.d.ts +43 -0
- package/dist/RAGPipeline-BmkIv1HD.d.mts +298 -0
- package/dist/RAGPipeline-BmkIv1HD.d.ts +298 -0
- package/dist/chunk-NCG2JKXB.mjs +1254 -0
- package/dist/chunk-ZPXLQR5Q.mjs +67 -0
- package/dist/handlers/index.d.mts +68 -0
- package/dist/handlers/index.d.ts +68 -0
- package/dist/handlers/index.js +1319 -0
- package/dist/handlers/index.mjs +13 -0
- package/dist/index.d.mts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +612 -0
- package/dist/index.mjs +551 -0
- package/dist/server.d.mts +211 -0
- package/dist/server.d.ts +211 -0
- package/dist/server.js +1457 -0
- package/dist/server.mjs +148 -0
- package/package.json +90 -0
- package/src/app/api/chat/route.ts +4 -0
- package/src/app/api/health/route.ts +4 -0
- package/src/app/api/ingest/route.ts +26 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +163 -0
- package/src/app/layout.tsx +35 -0
- package/src/app/page.tsx +506 -0
- package/src/components/ChatWidget.tsx +91 -0
- package/src/components/ChatWindow.tsx +248 -0
- package/src/components/ConfigProvider.tsx +56 -0
- package/src/components/MessageBubble.tsx +99 -0
- package/src/components/SourceCard.tsx +66 -0
- package/src/components/ThemeProvider.tsx +8 -0
- package/src/components/ThemeToggle.tsx +29 -0
- package/src/config/RagConfig.ts +159 -0
- package/src/config/UniversalProfiles.ts +83 -0
- package/src/config/serverConfig.ts +142 -0
- package/src/handlers/index.ts +202 -0
- package/src/hooks/useHydrated.ts +13 -0
- package/src/hooks/useRagChat.ts +167 -0
- package/src/hooks/useStoredMessages.ts +53 -0
- package/src/index.ts +27 -0
- package/src/llm/ILLMProvider.ts +60 -0
- package/src/llm/LLMFactory.ts +54 -0
- package/src/llm/providers/AnthropicProvider.ts +87 -0
- package/src/llm/providers/OllamaProvider.ts +102 -0
- package/src/llm/providers/OpenAIProvider.ts +92 -0
- package/src/llm/providers/UniversalLLMAdapter.ts +154 -0
- package/src/rag/DocumentChunker.ts +105 -0
- package/src/rag/RAGPipeline.ts +196 -0
- package/src/server.ts +30 -0
- package/src/types/pdf-parse.d.ts +13 -0
- package/src/utils/DocumentParser.ts +75 -0
- package/src/utils/templateUtils.ts +78 -0
- package/src/vectordb/IVectorDB.ts +75 -0
- package/src/vectordb/VectorDBFactory.ts +41 -0
- package/src/vectordb/adapters/MongoDbAdapter.ts +175 -0
- package/src/vectordb/adapters/PgVectorAdapter.ts +159 -0
- package/src/vectordb/adapters/PineconeAdapter.ts +115 -0
- package/src/vectordb/adapters/UniversalVectorDBAdapter.ts +177 -0
|
@@ -0,0 +1,1319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __spreadValues = (a, b) => {
|
|
14
|
+
for (var prop in b || (b = {}))
|
|
15
|
+
if (__hasOwnProp.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
if (__getOwnPropSymbols)
|
|
18
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
19
|
+
if (__propIsEnum.call(b, prop))
|
|
20
|
+
__defNormalProp(a, prop, b[prop]);
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __export = (target, all) => {
|
|
26
|
+
for (var name in all)
|
|
27
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
28
|
+
};
|
|
29
|
+
var __copyProps = (to, from, except, desc) => {
|
|
30
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
31
|
+
for (let key of __getOwnPropNames(from))
|
|
32
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
33
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
34
|
+
}
|
|
35
|
+
return to;
|
|
36
|
+
};
|
|
37
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
38
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
39
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
40
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
41
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
42
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
43
|
+
mod
|
|
44
|
+
));
|
|
45
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
46
|
+
|
|
47
|
+
// src/handlers/index.ts
|
|
48
|
+
var handlers_exports = {};
|
|
49
|
+
__export(handlers_exports, {
|
|
50
|
+
createChatHandler: () => createChatHandler,
|
|
51
|
+
createHealthHandler: () => createHealthHandler,
|
|
52
|
+
createIngestHandler: () => createIngestHandler,
|
|
53
|
+
createUploadHandler: () => createUploadHandler
|
|
54
|
+
});
|
|
55
|
+
module.exports = __toCommonJS(handlers_exports);
|
|
56
|
+
var import_server = require("next/server");
|
|
57
|
+
|
|
58
|
+
// src/vectordb/adapters/PineconeAdapter.ts
|
|
59
|
+
var import_pinecone = require("@pinecone-database/pinecone");
|
|
60
|
+
var PineconeAdapter = class {
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.indexName = config.indexName;
|
|
63
|
+
const opts = config.options;
|
|
64
|
+
if (!opts.apiKey) throw new Error("[PineconeAdapter] options.apiKey is required");
|
|
65
|
+
this.apiKey = opts.apiKey;
|
|
66
|
+
}
|
|
67
|
+
async initialize() {
|
|
68
|
+
var _a, _b;
|
|
69
|
+
this.client = new import_pinecone.Pinecone({ apiKey: this.apiKey });
|
|
70
|
+
const indexes = await this.client.listIndexes();
|
|
71
|
+
const names = (_b = (_a = indexes.indexes) == null ? void 0 : _a.map((i) => i.name)) != null ? _b : [];
|
|
72
|
+
if (!names.includes(this.indexName)) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`[PineconeAdapter] Index "${this.indexName}" not found. Available: ${names.join(", ")}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
index(namespace) {
|
|
79
|
+
const idx = this.client.index(this.indexName);
|
|
80
|
+
return namespace ? idx.namespace(namespace) : idx.namespace("");
|
|
81
|
+
}
|
|
82
|
+
async upsert(doc, namespace) {
|
|
83
|
+
var _a;
|
|
84
|
+
await this.index(namespace).upsert({
|
|
85
|
+
records: [
|
|
86
|
+
{
|
|
87
|
+
id: doc.id,
|
|
88
|
+
values: doc.vector,
|
|
89
|
+
metadata: __spreadValues({
|
|
90
|
+
content: doc.content
|
|
91
|
+
}, (_a = doc.metadata) != null ? _a : {})
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async batchUpsert(docs, namespace) {
|
|
97
|
+
const BATCH = 100;
|
|
98
|
+
for (let i = 0; i < docs.length; i += BATCH) {
|
|
99
|
+
const records = docs.slice(i, i + BATCH).map((d) => {
|
|
100
|
+
var _a;
|
|
101
|
+
return {
|
|
102
|
+
id: d.id,
|
|
103
|
+
values: d.vector,
|
|
104
|
+
metadata: __spreadValues({
|
|
105
|
+
content: d.content
|
|
106
|
+
}, (_a = d.metadata) != null ? _a : {})
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
await this.index(namespace).upsert({ records });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async query(vector, topK, namespace, filter) {
|
|
113
|
+
var _a;
|
|
114
|
+
const result = await this.index(namespace).query(__spreadValues({
|
|
115
|
+
vector,
|
|
116
|
+
topK,
|
|
117
|
+
includeMetadata: true
|
|
118
|
+
}, filter ? { filter } : {}));
|
|
119
|
+
return ((_a = result.matches) != null ? _a : []).map((m) => {
|
|
120
|
+
var _a2, _b, _c;
|
|
121
|
+
return {
|
|
122
|
+
id: m.id,
|
|
123
|
+
score: (_a2 = m.score) != null ? _a2 : 0,
|
|
124
|
+
content: (_c = (_b = m.metadata) == null ? void 0 : _b.content) != null ? _c : "",
|
|
125
|
+
metadata: m.metadata
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async delete(id, namespace) {
|
|
130
|
+
await this.index(namespace).deleteOne({ id });
|
|
131
|
+
}
|
|
132
|
+
async deleteNamespace(namespace) {
|
|
133
|
+
await this.client.index(this.indexName).namespace(namespace).deleteAll();
|
|
134
|
+
}
|
|
135
|
+
async ping() {
|
|
136
|
+
try {
|
|
137
|
+
await this.client.listIndexes();
|
|
138
|
+
return true;
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error("[PineconeAdapter] Ping failed:", err);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async disconnect() {
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/vectordb/adapters/PgVectorAdapter.ts
|
|
149
|
+
var import_pg = require("pg");
|
|
150
|
+
var PgVectorAdapter = class {
|
|
151
|
+
constructor(config) {
|
|
152
|
+
var _a;
|
|
153
|
+
this.tableName = config.indexName.replace(/[^a-z0-9_]/gi, "_");
|
|
154
|
+
const opts = config.options;
|
|
155
|
+
if (!opts.connectionString) {
|
|
156
|
+
throw new Error("[PgVectorAdapter] options.connectionString is required");
|
|
157
|
+
}
|
|
158
|
+
this.connectionString = opts.connectionString;
|
|
159
|
+
this.dimensions = (_a = opts.dimensions) != null ? _a : 1536;
|
|
160
|
+
}
|
|
161
|
+
async initialize() {
|
|
162
|
+
this.pool = new import_pg.Pool({ connectionString: this.connectionString });
|
|
163
|
+
const client = await this.pool.connect();
|
|
164
|
+
try {
|
|
165
|
+
await client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
166
|
+
await client.query(`
|
|
167
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
168
|
+
id TEXT PRIMARY KEY,
|
|
169
|
+
namespace TEXT NOT NULL DEFAULT '',
|
|
170
|
+
content TEXT NOT NULL,
|
|
171
|
+
metadata JSONB,
|
|
172
|
+
embedding VECTOR(${this.dimensions})
|
|
173
|
+
)
|
|
174
|
+
`);
|
|
175
|
+
await client.query(`
|
|
176
|
+
CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
|
|
177
|
+
ON ${this.tableName}
|
|
178
|
+
USING hnsw (embedding vector_cosine_ops)
|
|
179
|
+
`);
|
|
180
|
+
} finally {
|
|
181
|
+
client.release();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async upsert(doc, namespace = "") {
|
|
185
|
+
var _a;
|
|
186
|
+
const vectorLiteral = `[${doc.vector.join(",")}]`;
|
|
187
|
+
await this.pool.query(
|
|
188
|
+
`INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
|
|
189
|
+
VALUES ($1, $2, $3, $4, $5::vector)
|
|
190
|
+
ON CONFLICT (id) DO UPDATE
|
|
191
|
+
SET namespace = EXCLUDED.namespace,
|
|
192
|
+
content = EXCLUDED.content,
|
|
193
|
+
metadata = EXCLUDED.metadata,
|
|
194
|
+
embedding = EXCLUDED.embedding`,
|
|
195
|
+
[doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), vectorLiteral]
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
async batchUpsert(docs, namespace = "") {
|
|
199
|
+
for (const doc of docs) {
|
|
200
|
+
await this.upsert(doc, namespace);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async query(vector, topK, namespace, filter) {
|
|
204
|
+
const vectorLiteral = `[${vector.join(",")}]`;
|
|
205
|
+
let whereClause = namespace ? `WHERE namespace = $3` : "";
|
|
206
|
+
const params = [vectorLiteral, topK];
|
|
207
|
+
if (namespace) params.push(namespace);
|
|
208
|
+
if (filter && Object.keys(filter).length > 0) {
|
|
209
|
+
const filterConditions = Object.entries(filter).map(([key, val]) => {
|
|
210
|
+
const paramIdx = params.length + 1;
|
|
211
|
+
params.push(JSON.stringify(val));
|
|
212
|
+
return `metadata->>'${key}' = $${paramIdx}`;
|
|
213
|
+
}).join(" AND ");
|
|
214
|
+
whereClause = whereClause ? `${whereClause} AND ${filterConditions}` : `WHERE ${filterConditions}`;
|
|
215
|
+
}
|
|
216
|
+
const result = await this.pool.query(
|
|
217
|
+
`SELECT id,
|
|
218
|
+
content,
|
|
219
|
+
metadata,
|
|
220
|
+
1 - (embedding <=> $1::vector) AS score
|
|
221
|
+
FROM ${this.tableName}
|
|
222
|
+
${whereClause}
|
|
223
|
+
ORDER BY embedding <=> $1::vector
|
|
224
|
+
LIMIT $2`,
|
|
225
|
+
params
|
|
226
|
+
);
|
|
227
|
+
return result.rows.map((row) => ({
|
|
228
|
+
id: String(row.id),
|
|
229
|
+
score: parseFloat(String(row.score)),
|
|
230
|
+
content: String(row.content),
|
|
231
|
+
metadata: row.metadata
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
async delete(id, namespace) {
|
|
235
|
+
const where = namespace ? "WHERE id = $1 AND namespace = $2" : "WHERE id = $1";
|
|
236
|
+
const params = namespace ? [id, namespace] : [id];
|
|
237
|
+
await this.pool.query(`DELETE FROM ${this.tableName} ${where}`, params);
|
|
238
|
+
}
|
|
239
|
+
async deleteNamespace(namespace) {
|
|
240
|
+
await this.pool.query(
|
|
241
|
+
`DELETE FROM ${this.tableName} WHERE namespace = $1`,
|
|
242
|
+
[namespace]
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
async ping() {
|
|
246
|
+
try {
|
|
247
|
+
await this.pool.query("SELECT 1");
|
|
248
|
+
return true;
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error("[PgVectorAdapter] Ping failed:", err);
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async disconnect() {
|
|
255
|
+
try {
|
|
256
|
+
await this.pool.end();
|
|
257
|
+
} catch (err) {
|
|
258
|
+
console.error("[PgVectorAdapter] Failed to disconnect:", err);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/vectordb/adapters/UniversalVectorDBAdapter.ts
|
|
264
|
+
var import_axios = __toESM(require("axios"));
|
|
265
|
+
|
|
266
|
+
// src/utils/templateUtils.ts
|
|
267
|
+
function isRecord(value) {
|
|
268
|
+
return typeof value === "object" && value !== null;
|
|
269
|
+
}
|
|
270
|
+
function resolvePath(obj, path) {
|
|
271
|
+
if (!path || !obj) return void 0;
|
|
272
|
+
return path.replace(/\[(\w+)\]/g, ".$1").replace(/^\./, "").split(".").reduce((current, segment) => {
|
|
273
|
+
if (!isRecord(current) && !Array.isArray(current)) {
|
|
274
|
+
return void 0;
|
|
275
|
+
}
|
|
276
|
+
return current[segment];
|
|
277
|
+
}, obj);
|
|
278
|
+
}
|
|
279
|
+
function buildPayload(template, vars) {
|
|
280
|
+
let raw = template;
|
|
281
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
282
|
+
const stringifiedVal = val === void 0 ? "null" : JSON.stringify(val);
|
|
283
|
+
raw = raw.replace(new RegExp(`"\\{\\{${key}\\}\\}"`, "g"), stringifiedVal);
|
|
284
|
+
raw = raw.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), stringifiedVal);
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
return JSON.parse(raw);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
throw new Error(`Failed to parse payload template: ${err.message}
|
|
290
|
+
Raw payload: ${raw}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/config/UniversalProfiles.ts
|
|
295
|
+
var OPENAI_BASE = {
|
|
296
|
+
chatPath: "/chat/completions",
|
|
297
|
+
embedPath: "/embeddings",
|
|
298
|
+
responseExtractPath: "choices[0].message.content",
|
|
299
|
+
embedExtractPath: "data[0].embedding",
|
|
300
|
+
chatPayloadTemplate: '{"model":"{{model}}","messages":{{messages}},"max_tokens":{{maxTokens}},"temperature":{{temperature}}}'
|
|
301
|
+
};
|
|
302
|
+
var LLM_PROFILES = {
|
|
303
|
+
"openai-compatible": OPENAI_BASE,
|
|
304
|
+
"litellm": __spreadProps(__spreadValues({}, OPENAI_BASE), {
|
|
305
|
+
chatPayloadTemplate: '{"model":"{{model}}","messages":{{messages}},"max_tokens":{{maxTokens}},"temperature":{{temperature}},"stream":false}'
|
|
306
|
+
}),
|
|
307
|
+
"anthropic-claude": {
|
|
308
|
+
chatPath: "/v1/messages",
|
|
309
|
+
responseExtractPath: "content[0].text",
|
|
310
|
+
headers: { "anthropic-version": "2023-06-01" },
|
|
311
|
+
chatPayloadTemplate: '{"model":"{{model}}","messages":{{messages}},"max_tokens":{{maxTokens}}}'
|
|
312
|
+
},
|
|
313
|
+
"google-gemini": {
|
|
314
|
+
chatPath: "/v1beta/models/{{model}}:generateContent",
|
|
315
|
+
responseExtractPath: "candidates[0].content.parts[0].text",
|
|
316
|
+
chatPayloadTemplate: '{"contents":[{"parts":[{"text":"{{messages}}"}]}]}'
|
|
317
|
+
},
|
|
318
|
+
"github-copilot": __spreadProps(__spreadValues({}, OPENAI_BASE), {
|
|
319
|
+
chatPayloadTemplate: '{"model":"{{model}}","messages":{{messages}},"temperature":{{temperature}}}'
|
|
320
|
+
}),
|
|
321
|
+
"ollama-standard": {
|
|
322
|
+
chatPath: "/api/chat",
|
|
323
|
+
embedPath: "/api/embeddings",
|
|
324
|
+
responseExtractPath: "message.content",
|
|
325
|
+
embedExtractPath: "embedding",
|
|
326
|
+
chatPayloadTemplate: '{"model":"{{model}}","messages":{{messages}},"stream":false}',
|
|
327
|
+
embedPayloadTemplate: '{"model":"{{model}}","prompt":"{{input}}"}'
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var VECTOR_PROFILES = {
|
|
331
|
+
"pinecone-rest": {
|
|
332
|
+
queryPath: "/query",
|
|
333
|
+
upsertPath: "/vectors/upsert",
|
|
334
|
+
responseExtractPath: "matches",
|
|
335
|
+
idPath: "id",
|
|
336
|
+
scorePath: "score",
|
|
337
|
+
contentPath: "metadata.content",
|
|
338
|
+
metadataPath: "metadata",
|
|
339
|
+
queryPayloadTemplate: '{"vector":{{vector}},"topK":{{topK}},"includeMetadata":true,"namespace":"{{namespace}}"}'
|
|
340
|
+
},
|
|
341
|
+
"mongodb-atlas": {
|
|
342
|
+
queryPath: "/action/aggregate",
|
|
343
|
+
responseExtractPath: "documents",
|
|
344
|
+
idPath: "_id",
|
|
345
|
+
scorePath: "score",
|
|
346
|
+
contentPath: "content",
|
|
347
|
+
metadataPath: "metadata",
|
|
348
|
+
queryPayloadTemplate: '{"collection":"{{index}}","database":"ai_db","dataSource":"Cluster0","pipeline":[{"$vectorSearch":{"index":"vector_index","path":"embedding","queryVector":{{vector}},"numCandidates":100,"limit":{{topK}}}},{"$project":{"_id":1,"content":1,"metadata":1,"score":{"$meta":"vectorSearchScore"}}}]}'
|
|
349
|
+
},
|
|
350
|
+
"chromadb": {
|
|
351
|
+
queryPath: "/api/v1/collections/{{index}}/query",
|
|
352
|
+
upsertPath: "/api/v1/collections/{{index}}/add",
|
|
353
|
+
responseExtractPath: "matches",
|
|
354
|
+
idPath: "id",
|
|
355
|
+
scorePath: "distance",
|
|
356
|
+
contentPath: "document",
|
|
357
|
+
metadataPath: "metadata"
|
|
358
|
+
},
|
|
359
|
+
"qdrant": {
|
|
360
|
+
queryPath: "/collections/{{index}}/points/search",
|
|
361
|
+
upsertPath: "/collections/{{index}}/points",
|
|
362
|
+
responseExtractPath: "result",
|
|
363
|
+
idPath: "id",
|
|
364
|
+
scorePath: "score",
|
|
365
|
+
contentPath: "payload.content",
|
|
366
|
+
metadataPath: "payload"
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/vectordb/adapters/UniversalVectorDBAdapter.ts
|
|
371
|
+
var UniversalVectorDBAdapter = class {
|
|
372
|
+
constructor(config) {
|
|
373
|
+
var _a, _b, _c;
|
|
374
|
+
this.indexName = config.indexName;
|
|
375
|
+
const options = (_a = config.options) != null ? _a : {};
|
|
376
|
+
let profile = {};
|
|
377
|
+
if (typeof options.profile === "string") {
|
|
378
|
+
profile = VECTOR_PROFILES[options.profile] || {};
|
|
379
|
+
} else if (typeof options.profile === "object" && options.profile !== null) {
|
|
380
|
+
profile = options.profile;
|
|
381
|
+
}
|
|
382
|
+
this.opts = __spreadValues(__spreadValues({}, profile), (_b = config.options) != null ? _b : {});
|
|
383
|
+
if (!this.opts.baseUrl) {
|
|
384
|
+
throw new Error("[UniversalVectorDBAdapter] options.baseUrl is required");
|
|
385
|
+
}
|
|
386
|
+
this.http = import_axios.default.create({
|
|
387
|
+
baseURL: this.opts.baseUrl,
|
|
388
|
+
headers: __spreadValues({
|
|
389
|
+
"Content-Type": "application/json"
|
|
390
|
+
}, this.opts.headers || {}),
|
|
391
|
+
timeout: (_c = this.opts.timeout) != null ? _c : 3e4
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
async initialize() {
|
|
395
|
+
if (this.opts.pingPath) {
|
|
396
|
+
const ok = await this.ping();
|
|
397
|
+
if (!ok) {
|
|
398
|
+
throw new Error(`[UniversalVectorDBAdapter] Could not reach health endpoint: ${this.opts.pingPath}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async upsert(doc, namespace) {
|
|
403
|
+
var _a, _b;
|
|
404
|
+
const path = (_a = this.opts.upsertPath) != null ? _a : "/upsert";
|
|
405
|
+
if (this.opts.upsertPayloadTemplate) {
|
|
406
|
+
const payload = buildPayload(this.opts.upsertPayloadTemplate, {
|
|
407
|
+
id: doc.id,
|
|
408
|
+
vector: doc.vector,
|
|
409
|
+
content: doc.content,
|
|
410
|
+
metadata: (_b = doc.metadata) != null ? _b : {},
|
|
411
|
+
namespace: namespace != null ? namespace : "",
|
|
412
|
+
index: this.indexName
|
|
413
|
+
});
|
|
414
|
+
await this.http.post(path, payload);
|
|
415
|
+
} else {
|
|
416
|
+
await this.http.post(path, __spreadValues({
|
|
417
|
+
index: this.indexName,
|
|
418
|
+
namespace
|
|
419
|
+
}, doc));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
async batchUpsert(docs, namespace) {
|
|
423
|
+
var _a;
|
|
424
|
+
const path = (_a = this.opts.upsertPath) != null ? _a : "/upsert";
|
|
425
|
+
if (this.opts.batchUpsertPayloadTemplate) {
|
|
426
|
+
const payload = buildPayload(this.opts.batchUpsertPayloadTemplate, {
|
|
427
|
+
vectors: docs,
|
|
428
|
+
namespace: namespace != null ? namespace : "",
|
|
429
|
+
index: this.indexName
|
|
430
|
+
});
|
|
431
|
+
await this.http.post(path, payload);
|
|
432
|
+
} else {
|
|
433
|
+
await Promise.all(docs.map((doc) => this.upsert(doc, namespace)));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async query(vector, topK, namespace, filter) {
|
|
437
|
+
var _a, _b, _c, _d, _e, _f;
|
|
438
|
+
const path = (_a = this.opts.queryPath) != null ? _a : "/query";
|
|
439
|
+
let payload;
|
|
440
|
+
if (this.opts.queryPayloadTemplate) {
|
|
441
|
+
payload = buildPayload(this.opts.queryPayloadTemplate, {
|
|
442
|
+
vector,
|
|
443
|
+
topK,
|
|
444
|
+
namespace: namespace != null ? namespace : "",
|
|
445
|
+
index: this.indexName,
|
|
446
|
+
filter: filter != null ? filter : {}
|
|
447
|
+
});
|
|
448
|
+
} else {
|
|
449
|
+
payload = { index: this.indexName, namespace, vector, topK, filter };
|
|
450
|
+
}
|
|
451
|
+
const { data } = await this.http.post(path, payload);
|
|
452
|
+
const extractPath = (_b = this.opts.responseExtractPath) != null ? _b : "matches";
|
|
453
|
+
const matchesRaw = resolvePath(data, extractPath);
|
|
454
|
+
if (!Array.isArray(matchesRaw)) {
|
|
455
|
+
throw new Error(`[UniversalVectorDBAdapter] Expected an array at responseExtractPath '${extractPath}', but got ${typeof matchesRaw}`);
|
|
456
|
+
}
|
|
457
|
+
const idPath = (_c = this.opts.idPath) != null ? _c : "id";
|
|
458
|
+
const scorePath = (_d = this.opts.scorePath) != null ? _d : "score";
|
|
459
|
+
const contentPath = (_e = this.opts.contentPath) != null ? _e : "content";
|
|
460
|
+
const metadataPath = (_f = this.opts.metadataPath) != null ? _f : "metadata";
|
|
461
|
+
return matchesRaw.map((match) => ({
|
|
462
|
+
id: String(resolvePath(match, idPath) || ""),
|
|
463
|
+
score: Number(resolvePath(match, scorePath) || 0),
|
|
464
|
+
content: String(resolvePath(match, contentPath) || ""),
|
|
465
|
+
metadata: resolvePath(match, metadataPath) || {}
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
468
|
+
async delete(id, namespace) {
|
|
469
|
+
var _a;
|
|
470
|
+
const path = (_a = this.opts.deletePath) != null ? _a : "/delete";
|
|
471
|
+
await this.http.post(path, { index: this.indexName, namespace, id });
|
|
472
|
+
}
|
|
473
|
+
async deleteNamespace(namespace) {
|
|
474
|
+
var _a;
|
|
475
|
+
const path = (_a = this.opts.deleteNamespacePath) != null ? _a : "/delete-namespace";
|
|
476
|
+
await this.http.post(path, { index: this.indexName, namespace });
|
|
477
|
+
}
|
|
478
|
+
async ping() {
|
|
479
|
+
try {
|
|
480
|
+
if (this.opts.pingPath) {
|
|
481
|
+
await this.http.get(this.opts.pingPath);
|
|
482
|
+
}
|
|
483
|
+
return true;
|
|
484
|
+
} catch (err) {
|
|
485
|
+
console.error("[UniversalVectorDBAdapter] Ping failed:", err);
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async disconnect() {
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// src/vectordb/adapters/MongoDbAdapter.ts
|
|
494
|
+
var import_mongodb = require("mongodb");
|
|
495
|
+
var MongoDbAdapter = class {
|
|
496
|
+
constructor(options) {
|
|
497
|
+
this.uri = options.uri;
|
|
498
|
+
this.dbName = options.database;
|
|
499
|
+
this.collectionName = options.collection;
|
|
500
|
+
this.indexName = options.indexName || "vector_index";
|
|
501
|
+
this.embeddingKey = options.embeddingKey || "embedding";
|
|
502
|
+
this.contentKey = options.contentKey || "content";
|
|
503
|
+
this.metadataKey = options.metadataKey || "metadata";
|
|
504
|
+
if (!this.uri || !this.dbName || !this.collectionName) {
|
|
505
|
+
throw new Error("MongoDbAdapter requires uri, database, and collection options.");
|
|
506
|
+
}
|
|
507
|
+
this.client = new import_mongodb.MongoClient(this.uri);
|
|
508
|
+
}
|
|
509
|
+
async initialize() {
|
|
510
|
+
await this.client.connect();
|
|
511
|
+
this.db = this.client.db(this.dbName);
|
|
512
|
+
this.collection = this.db.collection(this.collectionName);
|
|
513
|
+
}
|
|
514
|
+
async upsert(doc, namespace) {
|
|
515
|
+
if (!this.collection) await this.initialize();
|
|
516
|
+
const document = __spreadValues({
|
|
517
|
+
_id: doc.id,
|
|
518
|
+
[this.embeddingKey]: doc.vector,
|
|
519
|
+
[this.contentKey]: doc.content,
|
|
520
|
+
[this.metadataKey]: doc.metadata || {}
|
|
521
|
+
}, namespace ? { namespace } : {});
|
|
522
|
+
await this.collection.updateOne(
|
|
523
|
+
{ _id: doc.id },
|
|
524
|
+
{ $set: document },
|
|
525
|
+
{ upsert: true }
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
async batchUpsert(docs, namespace) {
|
|
529
|
+
if (!this.collection) await this.initialize();
|
|
530
|
+
const operations = docs.map((doc) => ({
|
|
531
|
+
updateOne: {
|
|
532
|
+
filter: { _id: doc.id },
|
|
533
|
+
update: {
|
|
534
|
+
$set: __spreadValues({
|
|
535
|
+
_id: doc.id,
|
|
536
|
+
[this.embeddingKey]: doc.vector,
|
|
537
|
+
[this.contentKey]: doc.content,
|
|
538
|
+
[this.metadataKey]: doc.metadata || {}
|
|
539
|
+
}, namespace ? { namespace } : {})
|
|
540
|
+
},
|
|
541
|
+
upsert: true
|
|
542
|
+
}
|
|
543
|
+
}));
|
|
544
|
+
await this.collection.bulkWrite(operations);
|
|
545
|
+
}
|
|
546
|
+
async query(vector, topK, namespace, filter) {
|
|
547
|
+
if (!this.collection) await this.initialize();
|
|
548
|
+
const pipeline = [
|
|
549
|
+
{
|
|
550
|
+
$vectorSearch: __spreadValues({
|
|
551
|
+
index: this.indexName,
|
|
552
|
+
path: this.embeddingKey,
|
|
553
|
+
queryVector: vector,
|
|
554
|
+
numCandidates: Math.max(topK * 10, 100),
|
|
555
|
+
limit: topK
|
|
556
|
+
}, filter || namespace ? {
|
|
557
|
+
filter: __spreadValues(__spreadValues({}, filter || {}), namespace ? { namespace } : {})
|
|
558
|
+
} : {})
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
$project: {
|
|
562
|
+
score: { $meta: "vectorSearchScore" },
|
|
563
|
+
content: `$${this.contentKey}`,
|
|
564
|
+
metadata: `$${this.metadataKey}`
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
];
|
|
568
|
+
const results = await this.collection.aggregate(pipeline).toArray();
|
|
569
|
+
return results.map((res) => ({
|
|
570
|
+
id: res._id.toString(),
|
|
571
|
+
score: res.score,
|
|
572
|
+
content: res.content,
|
|
573
|
+
metadata: res.metadata
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
async delete(id, namespace) {
|
|
577
|
+
if (!this.collection) await this.initialize();
|
|
578
|
+
const query = __spreadValues({ _id: id }, namespace ? { namespace } : {});
|
|
579
|
+
await this.collection.deleteOne(query);
|
|
580
|
+
}
|
|
581
|
+
async deleteNamespace(namespace) {
|
|
582
|
+
if (!this.collection) await this.initialize();
|
|
583
|
+
await this.collection.deleteMany({ namespace });
|
|
584
|
+
}
|
|
585
|
+
async ping() {
|
|
586
|
+
try {
|
|
587
|
+
if (!this.collection) await this.initialize();
|
|
588
|
+
await this.db.command({ ping: 1 });
|
|
589
|
+
return true;
|
|
590
|
+
} catch (err) {
|
|
591
|
+
console.error("[MongoDbAdapter] Ping failed:", err);
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async disconnect() {
|
|
596
|
+
try {
|
|
597
|
+
await this.client.close();
|
|
598
|
+
this.db = void 0;
|
|
599
|
+
this.collection = void 0;
|
|
600
|
+
} catch (err) {
|
|
601
|
+
console.error("[MongoDbAdapter] Failed to disconnect:", err);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
// src/vectordb/VectorDBFactory.ts
|
|
607
|
+
var VectorDBFactory = class {
|
|
608
|
+
static create(config) {
|
|
609
|
+
var _a;
|
|
610
|
+
switch (config.provider) {
|
|
611
|
+
case "pinecone":
|
|
612
|
+
return new PineconeAdapter(config);
|
|
613
|
+
case "pgvector":
|
|
614
|
+
return new PgVectorAdapter(config);
|
|
615
|
+
case "mongodb":
|
|
616
|
+
return new MongoDbAdapter(config.options);
|
|
617
|
+
case "rest":
|
|
618
|
+
case "universal_rest":
|
|
619
|
+
case "custom":
|
|
620
|
+
return new UniversalVectorDBAdapter(config);
|
|
621
|
+
default:
|
|
622
|
+
if ((_a = config.options) == null ? void 0 : _a.baseUrl) {
|
|
623
|
+
return new UniversalVectorDBAdapter(config);
|
|
624
|
+
}
|
|
625
|
+
throw new Error(
|
|
626
|
+
`[VectorDBFactory] Unknown provider "${config.provider}". Supported: pinecone | pgvector | mongodb | rest`
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// src/llm/providers/OpenAIProvider.ts
|
|
633
|
+
var import_openai = __toESM(require("openai"));
|
|
634
|
+
var OpenAIProvider = class {
|
|
635
|
+
constructor(llmConfig, embeddingConfig) {
|
|
636
|
+
if (!llmConfig.apiKey) throw new Error("[OpenAIProvider] llmConfig.apiKey is required");
|
|
637
|
+
this.client = new import_openai.default({ apiKey: llmConfig.apiKey });
|
|
638
|
+
this.llmConfig = llmConfig;
|
|
639
|
+
this.embeddingConfig = embeddingConfig;
|
|
640
|
+
}
|
|
641
|
+
async chat(messages, context, options) {
|
|
642
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
643
|
+
const systemContent = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Answer questions based on the provided context.
|
|
644
|
+
|
|
645
|
+
Context:
|
|
646
|
+
${context}`;
|
|
647
|
+
const systemMessage = {
|
|
648
|
+
role: "system",
|
|
649
|
+
content: systemContent.includes("{{context}}") ? systemContent.replace("{{context}}", context) : `${systemContent}
|
|
650
|
+
|
|
651
|
+
Context:
|
|
652
|
+
${context}`
|
|
653
|
+
};
|
|
654
|
+
const formattedMessages = [
|
|
655
|
+
systemMessage,
|
|
656
|
+
...messages.map((m) => ({
|
|
657
|
+
role: m.role,
|
|
658
|
+
content: m.content
|
|
659
|
+
}))
|
|
660
|
+
];
|
|
661
|
+
const completion = await this.client.chat.completions.create({
|
|
662
|
+
model: this.llmConfig.model,
|
|
663
|
+
messages: formattedMessages,
|
|
664
|
+
max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
|
|
665
|
+
temperature: (_e = (_d = options == null ? void 0 : options.temperature) != null ? _d : this.llmConfig.temperature) != null ? _e : 0.7,
|
|
666
|
+
stop: options == null ? void 0 : options.stop
|
|
667
|
+
});
|
|
668
|
+
return (_h = (_g = (_f = completion.choices[0]) == null ? void 0 : _f.message) == null ? void 0 : _g.content) != null ? _h : "";
|
|
669
|
+
}
|
|
670
|
+
async embed(text, options) {
|
|
671
|
+
const results = await this.batchEmbed([text], options);
|
|
672
|
+
return results[0];
|
|
673
|
+
}
|
|
674
|
+
async batchEmbed(texts, options) {
|
|
675
|
+
var _a, _b, _c, _d, _e;
|
|
676
|
+
const model = (_c = (_b = options == null ? void 0 : options.model) != null ? _b : (_a = this.embeddingConfig) == null ? void 0 : _a.model) != null ? _c : "text-embedding-3-small";
|
|
677
|
+
const apiKey = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.apiKey) != null ? _e : this.llmConfig.apiKey;
|
|
678
|
+
const client = apiKey !== this.llmConfig.apiKey ? new import_openai.default({ apiKey }) : this.client;
|
|
679
|
+
const response = await client.embeddings.create({ model, input: texts });
|
|
680
|
+
return response.data.map((d) => d.embedding);
|
|
681
|
+
}
|
|
682
|
+
async ping() {
|
|
683
|
+
try {
|
|
684
|
+
await this.client.models.list();
|
|
685
|
+
return true;
|
|
686
|
+
} catch (err) {
|
|
687
|
+
console.error("[OpenAIProvider] Ping failed:", err);
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// src/llm/providers/AnthropicProvider.ts
|
|
694
|
+
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
695
|
+
var AnthropicProvider = class {
|
|
696
|
+
constructor(llmConfig, embeddingConfig) {
|
|
697
|
+
if (!llmConfig.apiKey) throw new Error("[AnthropicProvider] llmConfig.apiKey is required");
|
|
698
|
+
this.client = new import_sdk.default({ apiKey: llmConfig.apiKey });
|
|
699
|
+
this.llmConfig = llmConfig;
|
|
700
|
+
this.embeddingConfig = embeddingConfig;
|
|
701
|
+
}
|
|
702
|
+
async chat(messages, context, options) {
|
|
703
|
+
var _a, _b, _c;
|
|
704
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the context below to answer the user's question accurately.
|
|
705
|
+
|
|
706
|
+
Context:
|
|
707
|
+
${context}`;
|
|
708
|
+
const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
709
|
+
|
|
710
|
+
Context:
|
|
711
|
+
${context}`;
|
|
712
|
+
const anthropicMessages = messages.map((m) => ({
|
|
713
|
+
role: m.role === "assistant" ? "assistant" : "user",
|
|
714
|
+
content: m.content
|
|
715
|
+
}));
|
|
716
|
+
const response = await this.client.messages.create({
|
|
717
|
+
model: this.llmConfig.model,
|
|
718
|
+
max_tokens: (_c = (_b = options == null ? void 0 : options.maxTokens) != null ? _b : this.llmConfig.maxTokens) != null ? _c : 1024,
|
|
719
|
+
system,
|
|
720
|
+
messages: anthropicMessages
|
|
721
|
+
});
|
|
722
|
+
const block = response.content[0];
|
|
723
|
+
return block.type === "text" ? block.text : "";
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Anthropic does not offer an embedding API.
|
|
727
|
+
* This method throws with a clear error so developers know to configure
|
|
728
|
+
* a separate embedding provider (OpenAI or Ollama).
|
|
729
|
+
*/
|
|
730
|
+
async embed(text, options) {
|
|
731
|
+
void text;
|
|
732
|
+
void options;
|
|
733
|
+
throw new Error(
|
|
734
|
+
'[AnthropicProvider] Anthropic does not provide an embedding API. Set embedding.provider to "openai" or "ollama" in your RagConfig.'
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
async batchEmbed(texts, options) {
|
|
738
|
+
void texts;
|
|
739
|
+
void options;
|
|
740
|
+
throw new Error(
|
|
741
|
+
"[AnthropicProvider] Anthropic does not provide an embedding API."
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
async ping() {
|
|
745
|
+
try {
|
|
746
|
+
await this.client.models.list();
|
|
747
|
+
return true;
|
|
748
|
+
} catch (err) {
|
|
749
|
+
console.error("[AnthropicProvider] Ping failed:", err);
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// src/llm/providers/OllamaProvider.ts
|
|
756
|
+
var import_axios2 = __toESM(require("axios"));
|
|
757
|
+
var OllamaProvider = class {
|
|
758
|
+
constructor(llmConfig, embeddingConfig) {
|
|
759
|
+
var _a;
|
|
760
|
+
const baseURL = (_a = llmConfig.baseUrl) != null ? _a : "http://localhost:11434";
|
|
761
|
+
this.http = import_axios2.default.create({ baseURL, timeout: 12e4 });
|
|
762
|
+
this.llmConfig = llmConfig;
|
|
763
|
+
this.embeddingConfig = embeddingConfig;
|
|
764
|
+
}
|
|
765
|
+
async chat(messages, context, options) {
|
|
766
|
+
var _a, _b, _c, _d, _e;
|
|
767
|
+
const systemPrompt = (_a = this.llmConfig.systemPrompt) != null ? _a : `You are a helpful assistant. Use the provided context to answer the user's question.
|
|
768
|
+
|
|
769
|
+
Context:
|
|
770
|
+
${context}`;
|
|
771
|
+
const system = systemPrompt.includes("{{context}}") ? systemPrompt.replace("{{context}}", context) : `${systemPrompt}
|
|
772
|
+
|
|
773
|
+
Context:
|
|
774
|
+
${context}`;
|
|
775
|
+
const { data } = await this.http.post("/api/chat", {
|
|
776
|
+
model: this.llmConfig.model,
|
|
777
|
+
stream: false,
|
|
778
|
+
options: {
|
|
779
|
+
temperature: (_c = (_b = options == null ? void 0 : options.temperature) != null ? _b : this.llmConfig.temperature) != null ? _c : 0.7,
|
|
780
|
+
num_predict: (_e = (_d = options == null ? void 0 : options.maxTokens) != null ? _d : this.llmConfig.maxTokens) != null ? _e : 1024
|
|
781
|
+
},
|
|
782
|
+
messages: [
|
|
783
|
+
{ role: "system", content: system },
|
|
784
|
+
...messages.map((m) => ({ role: m.role, content: m.content }))
|
|
785
|
+
]
|
|
786
|
+
});
|
|
787
|
+
return data.message.content;
|
|
788
|
+
}
|
|
789
|
+
async embed(text, options) {
|
|
790
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
791
|
+
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";
|
|
792
|
+
const baseURL = (_f = (_e = (_d = this.embeddingConfig) == null ? void 0 : _d.baseUrl) != null ? _e : this.llmConfig.baseUrl) != null ? _f : "http://localhost:11434";
|
|
793
|
+
const client = baseURL !== ((_g = this.llmConfig.baseUrl) != null ? _g : "http://localhost:11434") ? import_axios2.default.create({ baseURL, timeout: 6e4 }) : this.http;
|
|
794
|
+
const { data } = await client.post("/api/embeddings", {
|
|
795
|
+
model,
|
|
796
|
+
prompt: text
|
|
797
|
+
});
|
|
798
|
+
return data.embedding;
|
|
799
|
+
}
|
|
800
|
+
async batchEmbed(texts, options) {
|
|
801
|
+
const vectors = [];
|
|
802
|
+
for (const text of texts) {
|
|
803
|
+
vectors.push(await this.embed(text, options));
|
|
804
|
+
}
|
|
805
|
+
return vectors;
|
|
806
|
+
}
|
|
807
|
+
async ping() {
|
|
808
|
+
try {
|
|
809
|
+
await this.http.get("/api/tags");
|
|
810
|
+
return true;
|
|
811
|
+
} catch (err) {
|
|
812
|
+
console.error("[OllamaProvider] Ping failed:", err);
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
// src/llm/providers/UniversalLLMAdapter.ts
|
|
819
|
+
var import_axios3 = __toESM(require("axios"));
|
|
820
|
+
var UniversalLLMAdapter = class {
|
|
821
|
+
constructor(config) {
|
|
822
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
823
|
+
this.model = config.model;
|
|
824
|
+
const llmConfig = config;
|
|
825
|
+
const options = (_a = llmConfig.options) != null ? _a : {};
|
|
826
|
+
let profile = {};
|
|
827
|
+
if (typeof options.profile === "string") {
|
|
828
|
+
profile = LLM_PROFILES[options.profile] || {};
|
|
829
|
+
} else if (typeof options.profile === "object" && options.profile !== null) {
|
|
830
|
+
profile = options.profile;
|
|
831
|
+
}
|
|
832
|
+
this.opts = __spreadValues(__spreadValues({}, profile), (_b = llmConfig.options) != null ? _b : {});
|
|
833
|
+
this.systemPrompt = (_c = llmConfig.systemPrompt) != null ? _c : "You are a helpful AI assistant. Use the provided context to answer the user.";
|
|
834
|
+
this.maxTokens = (_d = llmConfig.maxTokens) != null ? _d : 1024;
|
|
835
|
+
this.temperature = (_e = llmConfig.temperature) != null ? _e : 0;
|
|
836
|
+
const baseUrl = (_f = llmConfig.baseUrl) != null ? _f : this.opts.baseUrl;
|
|
837
|
+
if (!baseUrl) {
|
|
838
|
+
throw new Error("[UniversalLLMAdapter] baseUrl is required in config or config.options");
|
|
839
|
+
}
|
|
840
|
+
this.http = import_axios3.default.create({
|
|
841
|
+
baseURL: baseUrl,
|
|
842
|
+
headers: __spreadValues(__spreadValues({
|
|
843
|
+
"Content-Type": "application/json"
|
|
844
|
+
}, config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}), this.opts.headers || {}),
|
|
845
|
+
timeout: (_g = this.opts.timeout) != null ? _g : 6e4
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
async chat(messages, context) {
|
|
849
|
+
var _a, _b;
|
|
850
|
+
const path = (_a = this.opts.chatPath) != null ? _a : "/chat/completions";
|
|
851
|
+
const formattedMessages = [
|
|
852
|
+
{ role: "system", content: `${this.systemPrompt}
|
|
853
|
+
|
|
854
|
+
Context:
|
|
855
|
+
${context != null ? context : "None"}` },
|
|
856
|
+
...messages
|
|
857
|
+
];
|
|
858
|
+
let payload;
|
|
859
|
+
if (this.opts.chatPayloadTemplate) {
|
|
860
|
+
payload = buildPayload(this.opts.chatPayloadTemplate, {
|
|
861
|
+
model: this.model,
|
|
862
|
+
messages: formattedMessages,
|
|
863
|
+
maxTokens: this.maxTokens,
|
|
864
|
+
temperature: this.temperature
|
|
865
|
+
});
|
|
866
|
+
} else {
|
|
867
|
+
payload = {
|
|
868
|
+
model: this.model,
|
|
869
|
+
messages: formattedMessages,
|
|
870
|
+
max_tokens: this.maxTokens,
|
|
871
|
+
temperature: this.temperature
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
const { data } = await this.http.post(path, payload);
|
|
875
|
+
const extractPath = (_b = this.opts.responseExtractPath) != null ? _b : "choices[0].message.content";
|
|
876
|
+
const result = resolvePath(data, extractPath);
|
|
877
|
+
if (result === void 0) {
|
|
878
|
+
throw new Error(`[UniversalLLMAdapter] Could not extract text from path '${extractPath}' in response.`);
|
|
879
|
+
}
|
|
880
|
+
return String(result);
|
|
881
|
+
}
|
|
882
|
+
async embed(text) {
|
|
883
|
+
var _a, _b;
|
|
884
|
+
const path = (_a = this.opts.embedPath) != null ? _a : "/embeddings";
|
|
885
|
+
let payload;
|
|
886
|
+
if (this.opts.embedPayloadTemplate) {
|
|
887
|
+
payload = buildPayload(this.opts.embedPayloadTemplate, {
|
|
888
|
+
model: this.model,
|
|
889
|
+
input: text
|
|
890
|
+
});
|
|
891
|
+
} else {
|
|
892
|
+
payload = {
|
|
893
|
+
model: this.model,
|
|
894
|
+
input: text
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
const { data } = await this.http.post(path, payload);
|
|
898
|
+
const extractPath = (_b = this.opts.embedExtractPath) != null ? _b : "data[0].embedding";
|
|
899
|
+
const vector = resolvePath(data, extractPath);
|
|
900
|
+
if (!Array.isArray(vector)) {
|
|
901
|
+
throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
|
|
902
|
+
}
|
|
903
|
+
return vector;
|
|
904
|
+
}
|
|
905
|
+
async batchEmbed(texts) {
|
|
906
|
+
const vectors = [];
|
|
907
|
+
for (const text of texts) {
|
|
908
|
+
vectors.push(await this.embed(text));
|
|
909
|
+
}
|
|
910
|
+
return vectors;
|
|
911
|
+
}
|
|
912
|
+
async ping() {
|
|
913
|
+
try {
|
|
914
|
+
if (this.opts.pingPath) {
|
|
915
|
+
await this.http.get(this.opts.pingPath);
|
|
916
|
+
}
|
|
917
|
+
return true;
|
|
918
|
+
} catch (err) {
|
|
919
|
+
console.error("[UniversalLLMAdapter] Ping failed:", err);
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/llm/LLMFactory.ts
|
|
926
|
+
var LLMFactory = class _LLMFactory {
|
|
927
|
+
static create(llmConfig, embeddingConfig) {
|
|
928
|
+
var _a;
|
|
929
|
+
switch (llmConfig.provider) {
|
|
930
|
+
case "openai":
|
|
931
|
+
return new OpenAIProvider(llmConfig, embeddingConfig);
|
|
932
|
+
case "anthropic":
|
|
933
|
+
return new AnthropicProvider(llmConfig, embeddingConfig);
|
|
934
|
+
case "ollama":
|
|
935
|
+
return new OllamaProvider(llmConfig, embeddingConfig);
|
|
936
|
+
case "rest":
|
|
937
|
+
case "universal_rest":
|
|
938
|
+
case "custom":
|
|
939
|
+
return new UniversalLLMAdapter(llmConfig);
|
|
940
|
+
default:
|
|
941
|
+
if (llmConfig.baseUrl || ((_a = llmConfig.options) == null ? void 0 : _a.baseUrl)) {
|
|
942
|
+
return new UniversalLLMAdapter(llmConfig);
|
|
943
|
+
}
|
|
944
|
+
throw new Error(
|
|
945
|
+
`[LLMFactory] Unknown provider "${llmConfig.provider}". Supported: openai | anthropic | ollama | rest | custom`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Creates a dedicated embedding-only provider.
|
|
951
|
+
* Useful when the LLM provider (e.g. Anthropic) doesn't support embeddings.
|
|
952
|
+
*/
|
|
953
|
+
static createEmbeddingProvider(embeddingConfig) {
|
|
954
|
+
const fakeLLMConfig = {
|
|
955
|
+
provider: embeddingConfig.provider,
|
|
956
|
+
model: embeddingConfig.model,
|
|
957
|
+
apiKey: embeddingConfig.apiKey,
|
|
958
|
+
baseUrl: embeddingConfig.baseUrl,
|
|
959
|
+
options: embeddingConfig.options
|
|
960
|
+
};
|
|
961
|
+
return _LLMFactory.create(fakeLLMConfig, embeddingConfig);
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// src/rag/DocumentChunker.ts
|
|
966
|
+
var DocumentChunker = class {
|
|
967
|
+
constructor(chunkSize = 1e3, chunkOverlap = 200) {
|
|
968
|
+
this.chunkSize = chunkSize;
|
|
969
|
+
this.chunkOverlap = chunkOverlap;
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Split a single text string into overlapping chunks.
|
|
973
|
+
*/
|
|
974
|
+
chunk(text, options = {}) {
|
|
975
|
+
const {
|
|
976
|
+
chunkSize = this.chunkSize,
|
|
977
|
+
chunkOverlap = this.chunkOverlap,
|
|
978
|
+
docId = `doc_${Date.now()}`,
|
|
979
|
+
metadata = {}
|
|
980
|
+
} = options;
|
|
981
|
+
const cleaned = text.replace(/\r\n/g, "\n").trim();
|
|
982
|
+
if (!cleaned) return [];
|
|
983
|
+
const sentences = cleaned.split(new RegExp("(?<=[.!?])\\s+|\\n{2,}")).map((s) => s.trim()).filter(Boolean);
|
|
984
|
+
const chunks = [];
|
|
985
|
+
let current = "";
|
|
986
|
+
let chunkIndex = 0;
|
|
987
|
+
for (const sentence of sentences) {
|
|
988
|
+
if ((current + " " + sentence).trim().length <= chunkSize) {
|
|
989
|
+
current = current ? `${current} ${sentence}` : sentence;
|
|
990
|
+
} else {
|
|
991
|
+
if (current) {
|
|
992
|
+
chunks.push({
|
|
993
|
+
id: `${docId}_chunk_${chunkIndex++}`,
|
|
994
|
+
content: current.trim(),
|
|
995
|
+
metadata: __spreadProps(__spreadValues({}, metadata), { docId, chunkIndex: chunkIndex - 1 })
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
if (chunkOverlap > 0 && current.length > chunkOverlap) {
|
|
999
|
+
current = current.slice(-chunkOverlap) + " " + sentence;
|
|
1000
|
+
} else {
|
|
1001
|
+
current = sentence;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (current.trim()) {
|
|
1006
|
+
chunks.push({
|
|
1007
|
+
id: `${docId}_chunk_${chunkIndex}`,
|
|
1008
|
+
content: current.trim(),
|
|
1009
|
+
metadata: __spreadProps(__spreadValues({}, metadata), { docId, chunkIndex })
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
return chunks;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Chunk multiple documents at once.
|
|
1016
|
+
*/
|
|
1017
|
+
chunkMany(documents) {
|
|
1018
|
+
return documents.flatMap(
|
|
1019
|
+
(doc) => this.chunk(doc.content, { docId: doc.docId, metadata: doc.metadata })
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
// src/rag/RAGPipeline.ts
|
|
1025
|
+
var RAGPipeline = class {
|
|
1026
|
+
constructor(config, adapters) {
|
|
1027
|
+
this.initialised = false;
|
|
1028
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1029
|
+
this.config = config;
|
|
1030
|
+
this.vectorDB = (_a = adapters == null ? void 0 : adapters.vectorDB) != null ? _a : VectorDBFactory.create(config.vectorDb);
|
|
1031
|
+
this.llmProvider = (_b = adapters == null ? void 0 : adapters.llmProvider) != null ? _b : LLMFactory.create(config.llm, config.embedding);
|
|
1032
|
+
if (adapters == null ? void 0 : adapters.embeddingProvider) {
|
|
1033
|
+
this.embeddingProvider = adapters.embeddingProvider;
|
|
1034
|
+
} else if (config.llm.provider === "anthropic") {
|
|
1035
|
+
this.embeddingProvider = LLMFactory.createEmbeddingProvider(config.embedding);
|
|
1036
|
+
} else {
|
|
1037
|
+
this.embeddingProvider = this.llmProvider;
|
|
1038
|
+
}
|
|
1039
|
+
this.chunker = new DocumentChunker(
|
|
1040
|
+
(_d = (_c = config.rag) == null ? void 0 : _c.chunkSize) != null ? _d : 1e3,
|
|
1041
|
+
(_f = (_e = config.rag) == null ? void 0 : _e.chunkOverlap) != null ? _f : 200
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
/** Lazily initialise the vector DB connection */
|
|
1045
|
+
async init() {
|
|
1046
|
+
if (!this.initialised) {
|
|
1047
|
+
await this.vectorDB.initialize();
|
|
1048
|
+
this.initialised = true;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
// ---------------------------------------------------------------------------
|
|
1052
|
+
// INGEST
|
|
1053
|
+
// ---------------------------------------------------------------------------
|
|
1054
|
+
/**
|
|
1055
|
+
* Ingest one or more documents into the vector DB.
|
|
1056
|
+
* Automatically chunks, embeds, and upserts each chunk.
|
|
1057
|
+
*/
|
|
1058
|
+
async ingest(documents, namespace) {
|
|
1059
|
+
await this.init();
|
|
1060
|
+
const ns = namespace != null ? namespace : this.config.projectId;
|
|
1061
|
+
const results = [];
|
|
1062
|
+
for (const doc of documents) {
|
|
1063
|
+
const chunks = this.chunker.chunk(doc.content, {
|
|
1064
|
+
docId: doc.docId,
|
|
1065
|
+
metadata: doc.metadata
|
|
1066
|
+
});
|
|
1067
|
+
const chunkTexts = chunks.map((c) => c.content);
|
|
1068
|
+
const vectors = await this.embeddingProvider.batchEmbed(chunkTexts);
|
|
1069
|
+
const upsertDocs = chunks.map((chunk, i) => ({
|
|
1070
|
+
id: chunk.id,
|
|
1071
|
+
vector: vectors[i],
|
|
1072
|
+
content: chunk.content,
|
|
1073
|
+
metadata: chunk.metadata
|
|
1074
|
+
}));
|
|
1075
|
+
await this.vectorDB.batchUpsert(upsertDocs, ns);
|
|
1076
|
+
results.push({ docId: doc.docId, chunksIngested: chunks.length });
|
|
1077
|
+
console.log(`[RAGPipeline] Ingested ${chunks.length} chunks for doc: ${doc.docId}`);
|
|
1078
|
+
}
|
|
1079
|
+
return results;
|
|
1080
|
+
}
|
|
1081
|
+
// ---------------------------------------------------------------------------
|
|
1082
|
+
// ASK
|
|
1083
|
+
// ---------------------------------------------------------------------------
|
|
1084
|
+
/**
|
|
1085
|
+
* Run a full RAG query:
|
|
1086
|
+
* 1. Embed the user question
|
|
1087
|
+
* 2. Retrieve top-K relevant chunks from the vector DB
|
|
1088
|
+
* 3. Filter by score threshold
|
|
1089
|
+
* 4. Build context string
|
|
1090
|
+
* 5. Call the LLM with chat history + context
|
|
1091
|
+
*/
|
|
1092
|
+
async ask(question, history = [], namespace) {
|
|
1093
|
+
var _a, _b, _c, _d;
|
|
1094
|
+
await this.init();
|
|
1095
|
+
const ns = namespace != null ? namespace : this.config.projectId;
|
|
1096
|
+
const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
|
|
1097
|
+
const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
|
|
1098
|
+
const queryVector = await this.embeddingProvider.embed(question);
|
|
1099
|
+
const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
|
|
1100
|
+
const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
|
|
1101
|
+
const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
|
|
1102
|
+
${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
|
|
1103
|
+
const messages = [
|
|
1104
|
+
...history,
|
|
1105
|
+
{ role: "user", content: question }
|
|
1106
|
+
];
|
|
1107
|
+
const reply = await this.llmProvider.chat(messages, context);
|
|
1108
|
+
return { reply, sources };
|
|
1109
|
+
}
|
|
1110
|
+
// ---------------------------------------------------------------------------
|
|
1111
|
+
// HEALTH
|
|
1112
|
+
// ---------------------------------------------------------------------------
|
|
1113
|
+
async health() {
|
|
1114
|
+
const [vectorDB, llm] = await Promise.all([
|
|
1115
|
+
this.vectorDB.ping().catch(() => false),
|
|
1116
|
+
this.llmProvider.ping().catch(() => false)
|
|
1117
|
+
]);
|
|
1118
|
+
return { vectorDB, llm };
|
|
1119
|
+
}
|
|
1120
|
+
// ---------------------------------------------------------------------------
|
|
1121
|
+
// DATA MANAGEMENT
|
|
1122
|
+
// ---------------------------------------------------------------------------
|
|
1123
|
+
async deleteDocument(docId, namespace) {
|
|
1124
|
+
await this.init();
|
|
1125
|
+
const ns = namespace != null ? namespace : this.config.projectId;
|
|
1126
|
+
await this.vectorDB.delete(docId, ns);
|
|
1127
|
+
}
|
|
1128
|
+
async deleteProject(namespace) {
|
|
1129
|
+
await this.init();
|
|
1130
|
+
const ns = namespace != null ? namespace : this.config.projectId;
|
|
1131
|
+
await this.vectorDB.deleteNamespace(ns);
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
// src/utils/DocumentParser.ts
|
|
1136
|
+
var DocumentParser = class {
|
|
1137
|
+
/**
|
|
1138
|
+
* Extract text from a File or Buffer based on its type.
|
|
1139
|
+
*/
|
|
1140
|
+
static async parse(file, fileName, mimeType) {
|
|
1141
|
+
var _a;
|
|
1142
|
+
const extension = (_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
1143
|
+
if (extension === "txt" || extension === "md" || mimeType === "text/plain" || mimeType === "text/markdown") {
|
|
1144
|
+
return this.readAsText(file);
|
|
1145
|
+
}
|
|
1146
|
+
if (extension === "json" || mimeType === "application/json") {
|
|
1147
|
+
const text = await this.readAsText(file);
|
|
1148
|
+
try {
|
|
1149
|
+
const obj = JSON.parse(text);
|
|
1150
|
+
return JSON.stringify(obj, null, 2);
|
|
1151
|
+
} catch (e) {
|
|
1152
|
+
return text;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (extension === "csv" || mimeType === "text/csv") {
|
|
1156
|
+
return this.readAsText(file);
|
|
1157
|
+
}
|
|
1158
|
+
if (extension === "pdf" || mimeType === "application/pdf") {
|
|
1159
|
+
try {
|
|
1160
|
+
const pdf = await import("pdf-parse/lib/pdf-parse.js");
|
|
1161
|
+
const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
|
|
1162
|
+
const data = await pdf.default(buffer);
|
|
1163
|
+
return data.text;
|
|
1164
|
+
} catch (err) {
|
|
1165
|
+
console.warn('[DocumentParser] PDF parsing failed. Make sure "pdf-parse" is installed.', err);
|
|
1166
|
+
return `[PDF Parsing Error for ${fileName}]`;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (extension === "docx" || mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
|
|
1170
|
+
try {
|
|
1171
|
+
const mammoth = await import("mammoth");
|
|
1172
|
+
const buffer = Buffer.isBuffer(file) ? file : Buffer.from(await file.arrayBuffer());
|
|
1173
|
+
const result = await mammoth.extractRawText({ buffer });
|
|
1174
|
+
return result.value;
|
|
1175
|
+
} catch (err) {
|
|
1176
|
+
console.warn('[DocumentParser] DOCX parsing failed. Make sure "mammoth" is installed.', err);
|
|
1177
|
+
return `[DOCX Parsing Error for ${fileName}]`;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
try {
|
|
1181
|
+
return await this.readAsText(file);
|
|
1182
|
+
} catch (e) {
|
|
1183
|
+
throw new Error(`[DocumentParser] Unsupported file format: ${fileName} (${mimeType})`);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
static async readAsText(file) {
|
|
1187
|
+
if (Buffer.isBuffer(file)) {
|
|
1188
|
+
return file.toString("utf-8");
|
|
1189
|
+
}
|
|
1190
|
+
return await file.text();
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
// src/handlers/index.ts
|
|
1195
|
+
function createChatHandler(config, adapters) {
|
|
1196
|
+
let pipeline = null;
|
|
1197
|
+
function getPipeline() {
|
|
1198
|
+
if (!pipeline) pipeline = new RAGPipeline(config, adapters);
|
|
1199
|
+
return pipeline;
|
|
1200
|
+
}
|
|
1201
|
+
return async function POST(req) {
|
|
1202
|
+
try {
|
|
1203
|
+
const body = await req.json();
|
|
1204
|
+
const { message, history = [], namespace } = body;
|
|
1205
|
+
if (!(message == null ? void 0 : message.trim())) {
|
|
1206
|
+
return import_server.NextResponse.json({ error: "message is required" }, { status: 400 });
|
|
1207
|
+
}
|
|
1208
|
+
const rag = getPipeline();
|
|
1209
|
+
const result = await rag.ask(message, history, namespace);
|
|
1210
|
+
return import_server.NextResponse.json(result);
|
|
1211
|
+
} catch (err) {
|
|
1212
|
+
const message = err instanceof Error ? err.message : "Internal server error";
|
|
1213
|
+
return import_server.NextResponse.json({ error: message }, { status: 500 });
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
function createIngestHandler(config, adapters) {
|
|
1218
|
+
let pipeline = null;
|
|
1219
|
+
function getPipeline() {
|
|
1220
|
+
if (!pipeline) pipeline = new RAGPipeline(config, adapters);
|
|
1221
|
+
return pipeline;
|
|
1222
|
+
}
|
|
1223
|
+
return async function POST(req) {
|
|
1224
|
+
try {
|
|
1225
|
+
const body = await req.json();
|
|
1226
|
+
const { documents, namespace } = body;
|
|
1227
|
+
if (!Array.isArray(documents) || documents.length === 0) {
|
|
1228
|
+
return import_server.NextResponse.json(
|
|
1229
|
+
{ error: "documents array is required and must not be empty" },
|
|
1230
|
+
{ status: 400 }
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
const rag = getPipeline();
|
|
1234
|
+
const results = await rag.ingest(documents, namespace);
|
|
1235
|
+
const totalChunks = results.reduce((sum, r) => sum + r.chunksIngested, 0);
|
|
1236
|
+
return import_server.NextResponse.json({ results, totalChunks });
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
const message = err instanceof Error ? err.message : "Internal server error";
|
|
1239
|
+
return import_server.NextResponse.json({ error: message }, { status: 500 });
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function createHealthHandler(config, adapters) {
|
|
1244
|
+
let pipeline = null;
|
|
1245
|
+
function getPipeline() {
|
|
1246
|
+
if (!pipeline) pipeline = new RAGPipeline(config, adapters);
|
|
1247
|
+
return pipeline;
|
|
1248
|
+
}
|
|
1249
|
+
return async function GET() {
|
|
1250
|
+
try {
|
|
1251
|
+
const rag = getPipeline();
|
|
1252
|
+
const health = await rag.health();
|
|
1253
|
+
const status = health.vectorDB && health.llm ? "ok" : "degraded";
|
|
1254
|
+
return import_server.NextResponse.json(__spreadProps(__spreadValues({
|
|
1255
|
+
status
|
|
1256
|
+
}, health), {
|
|
1257
|
+
project: config.projectId,
|
|
1258
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1259
|
+
}));
|
|
1260
|
+
} catch (err) {
|
|
1261
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1262
|
+
return import_server.NextResponse.json(
|
|
1263
|
+
{ status: "error", error: message, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
1264
|
+
{ status: 500 }
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
function createUploadHandler(config, adapters) {
|
|
1270
|
+
let pipeline = null;
|
|
1271
|
+
function getPipeline() {
|
|
1272
|
+
if (!pipeline) pipeline = new RAGPipeline(config, adapters);
|
|
1273
|
+
return pipeline;
|
|
1274
|
+
}
|
|
1275
|
+
return async function POST(req) {
|
|
1276
|
+
try {
|
|
1277
|
+
const formData = await req.formData();
|
|
1278
|
+
const files = formData.getAll("files");
|
|
1279
|
+
const namespace = formData.get("namespace") || void 0;
|
|
1280
|
+
if (!files || files.length === 0) {
|
|
1281
|
+
return import_server.NextResponse.json({ error: "No files provided" }, { status: 400 });
|
|
1282
|
+
}
|
|
1283
|
+
const documents = await Promise.all(
|
|
1284
|
+
files.map(async (file) => {
|
|
1285
|
+
const content = await DocumentParser.parse(file, file.name, file.type);
|
|
1286
|
+
return {
|
|
1287
|
+
docId: file.name,
|
|
1288
|
+
content,
|
|
1289
|
+
metadata: {
|
|
1290
|
+
fileName: file.name,
|
|
1291
|
+
fileSize: file.size,
|
|
1292
|
+
fileType: file.type,
|
|
1293
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
})
|
|
1297
|
+
);
|
|
1298
|
+
const rag = getPipeline();
|
|
1299
|
+
const results = await rag.ingest(documents, namespace);
|
|
1300
|
+
const totalChunks = results.reduce((sum, r) => sum + r.chunksIngested, 0);
|
|
1301
|
+
return import_server.NextResponse.json({
|
|
1302
|
+
message: "Upload successful",
|
|
1303
|
+
filesProcessed: files.length,
|
|
1304
|
+
totalChunks,
|
|
1305
|
+
results
|
|
1306
|
+
});
|
|
1307
|
+
} catch (err) {
|
|
1308
|
+
const message = err instanceof Error ? err.message : "Upload failed";
|
|
1309
|
+
return import_server.NextResponse.json({ error: message }, { status: 500 });
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1314
|
+
0 && (module.exports = {
|
|
1315
|
+
createChatHandler,
|
|
1316
|
+
createHealthHandler,
|
|
1317
|
+
createIngestHandler,
|
|
1318
|
+
createUploadHandler
|
|
1319
|
+
});
|