@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,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pgVector Adapter
|
|
3
|
+
*
|
|
4
|
+
* Stores embeddings in a PostgreSQL table using the pgvector extension.
|
|
5
|
+
* Required options in VectorDBConfig.options:
|
|
6
|
+
* - connectionString: string – PostgreSQL connection string
|
|
7
|
+
* - dimensions?: number – vector dimensions (default 1536 for OpenAI ada-002)
|
|
8
|
+
*
|
|
9
|
+
* The adapter auto-creates the table and index on initialize().
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Pool, PoolClient } from 'pg';
|
|
13
|
+
import { IVectorDB, VectorMatch, UpsertDocument } from '../IVectorDB';
|
|
14
|
+
import { VectorDBConfig } from '../../config/RagConfig';
|
|
15
|
+
|
|
16
|
+
export class PgVectorAdapter implements IVectorDB {
|
|
17
|
+
private pool!: Pool;
|
|
18
|
+
private readonly tableName: string;
|
|
19
|
+
private readonly dimensions: number;
|
|
20
|
+
private readonly connectionString: string;
|
|
21
|
+
|
|
22
|
+
constructor(config: VectorDBConfig) {
|
|
23
|
+
this.tableName = config.indexName.replace(/[^a-z0-9_]/gi, '_'); // sanitize
|
|
24
|
+
const opts = config.options as Record<string, unknown>;
|
|
25
|
+
if (!opts.connectionString) {
|
|
26
|
+
throw new Error('[PgVectorAdapter] options.connectionString is required');
|
|
27
|
+
}
|
|
28
|
+
this.connectionString = opts.connectionString as string;
|
|
29
|
+
this.dimensions = (opts.dimensions as number) ?? 1536;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async initialize(): Promise<void> {
|
|
33
|
+
this.pool = new Pool({ connectionString: this.connectionString });
|
|
34
|
+
|
|
35
|
+
const client: PoolClient = await this.pool.connect();
|
|
36
|
+
try {
|
|
37
|
+
// Enable pgvector extension
|
|
38
|
+
await client.query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
39
|
+
|
|
40
|
+
// Create the chunks table if it doesn't exist
|
|
41
|
+
await client.query(`
|
|
42
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
43
|
+
id TEXT PRIMARY KEY,
|
|
44
|
+
namespace TEXT NOT NULL DEFAULT '',
|
|
45
|
+
content TEXT NOT NULL,
|
|
46
|
+
metadata JSONB,
|
|
47
|
+
embedding VECTOR(${this.dimensions})
|
|
48
|
+
)
|
|
49
|
+
`);
|
|
50
|
+
|
|
51
|
+
// Create HNSW index for fast approximate nearest-neighbour search
|
|
52
|
+
await client.query(`
|
|
53
|
+
CREATE INDEX IF NOT EXISTS ${this.tableName}_embedding_idx
|
|
54
|
+
ON ${this.tableName}
|
|
55
|
+
USING hnsw (embedding vector_cosine_ops)
|
|
56
|
+
`);
|
|
57
|
+
} finally {
|
|
58
|
+
client.release();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async upsert(doc: UpsertDocument, namespace = ''): Promise<void> {
|
|
63
|
+
const vectorLiteral = `[${doc.vector.join(',')}]`;
|
|
64
|
+
await this.pool.query(
|
|
65
|
+
`INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
|
|
66
|
+
VALUES ($1, $2, $3, $4, $5::vector)
|
|
67
|
+
ON CONFLICT (id) DO UPDATE
|
|
68
|
+
SET namespace = EXCLUDED.namespace,
|
|
69
|
+
content = EXCLUDED.content,
|
|
70
|
+
metadata = EXCLUDED.metadata,
|
|
71
|
+
embedding = EXCLUDED.embedding`,
|
|
72
|
+
[doc.id, namespace, doc.content, JSON.stringify(doc.metadata ?? {}), vectorLiteral]
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async batchUpsert(docs: UpsertDocument[], namespace = ''): Promise<void> {
|
|
77
|
+
// Sequential for simplicity; swap for COPY or multi-row INSERT for perf
|
|
78
|
+
for (const doc of docs) {
|
|
79
|
+
await this.upsert(doc, namespace);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async query(
|
|
84
|
+
vector: number[],
|
|
85
|
+
topK: number,
|
|
86
|
+
namespace?: string,
|
|
87
|
+
filter?: Record<string, unknown>
|
|
88
|
+
): Promise<VectorMatch[]> {
|
|
89
|
+
const vectorLiteral = `[${vector.join(',')}]`;
|
|
90
|
+
|
|
91
|
+
let whereClause = namespace ? `WHERE namespace = $3` : '';
|
|
92
|
+
const params: unknown[] = [vectorLiteral, topK];
|
|
93
|
+
if (namespace) params.push(namespace);
|
|
94
|
+
|
|
95
|
+
// Append metadata filter conditions if provided
|
|
96
|
+
if (filter && Object.keys(filter).length > 0) {
|
|
97
|
+
const filterConditions = Object.entries(filter)
|
|
98
|
+
.map(([key, val]) => {
|
|
99
|
+
const paramIdx = params.length + 1;
|
|
100
|
+
params.push(JSON.stringify(val));
|
|
101
|
+
return `metadata->>'${key}' = $${paramIdx}`;
|
|
102
|
+
})
|
|
103
|
+
.join(' AND ');
|
|
104
|
+
whereClause = whereClause
|
|
105
|
+
? `${whereClause} AND ${filterConditions}`
|
|
106
|
+
: `WHERE ${filterConditions}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result = await this.pool.query(
|
|
110
|
+
`SELECT id,
|
|
111
|
+
content,
|
|
112
|
+
metadata,
|
|
113
|
+
1 - (embedding <=> $1::vector) AS score
|
|
114
|
+
FROM ${this.tableName}
|
|
115
|
+
${whereClause}
|
|
116
|
+
ORDER BY embedding <=> $1::vector
|
|
117
|
+
LIMIT $2`,
|
|
118
|
+
params
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return result.rows.map((row: Record<string, unknown>) => ({
|
|
122
|
+
id: String(row.id),
|
|
123
|
+
score: parseFloat(String(row.score)),
|
|
124
|
+
content: String(row.content),
|
|
125
|
+
metadata: row.metadata as Record<string, unknown>,
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async delete(id: string, namespace?: string): Promise<void> {
|
|
130
|
+
const where = namespace ? 'WHERE id = $1 AND namespace = $2' : 'WHERE id = $1';
|
|
131
|
+
const params = namespace ? [id, namespace] : [id];
|
|
132
|
+
await this.pool.query(`DELETE FROM ${this.tableName} ${where}`, params);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async deleteNamespace(namespace: string): Promise<void> {
|
|
136
|
+
await this.pool.query(
|
|
137
|
+
`DELETE FROM ${this.tableName} WHERE namespace = $1`,
|
|
138
|
+
[namespace]
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async ping(): Promise<boolean> {
|
|
143
|
+
try {
|
|
144
|
+
await this.pool.query('SELECT 1');
|
|
145
|
+
return true;
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.error('[PgVectorAdapter] Ping failed:', err);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async disconnect(): Promise<void> {
|
|
153
|
+
try {
|
|
154
|
+
await this.pool.end();
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error('[PgVectorAdapter] Failed to disconnect:', err);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinecone Vector DB Adapter
|
|
3
|
+
*
|
|
4
|
+
* Required options in VectorDBConfig.options:
|
|
5
|
+
* - apiKey: string – Pinecone API key
|
|
6
|
+
* - environment?: string – Pinecone environment (legacy, optional for new projects)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Pinecone, RecordMetadata } from '@pinecone-database/pinecone';
|
|
10
|
+
import { IVectorDB, VectorMatch, UpsertDocument } from '../IVectorDB';
|
|
11
|
+
import { VectorDBConfig } from '../../config/RagConfig';
|
|
12
|
+
|
|
13
|
+
export class PineconeAdapter implements IVectorDB {
|
|
14
|
+
private client!: Pinecone;
|
|
15
|
+
private readonly indexName: string;
|
|
16
|
+
private readonly apiKey: string;
|
|
17
|
+
|
|
18
|
+
constructor(config: VectorDBConfig) {
|
|
19
|
+
this.indexName = config.indexName;
|
|
20
|
+
const opts = config.options as Record<string, string>;
|
|
21
|
+
if (!opts.apiKey) throw new Error('[PineconeAdapter] options.apiKey is required');
|
|
22
|
+
this.apiKey = opts.apiKey;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async initialize(): Promise<void> {
|
|
26
|
+
this.client = new Pinecone({ apiKey: this.apiKey });
|
|
27
|
+
// Verify index exists
|
|
28
|
+
const indexes = await this.client.listIndexes();
|
|
29
|
+
const names = indexes.indexes?.map((i) => i.name) ?? [];
|
|
30
|
+
if (!names.includes(this.indexName)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`[PineconeAdapter] Index "${this.indexName}" not found. ` +
|
|
33
|
+
`Available: ${names.join(', ')}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private index(namespace?: string) {
|
|
39
|
+
const idx = this.client.index(this.indexName);
|
|
40
|
+
return namespace ? idx.namespace(namespace) : idx.namespace('');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async upsert(doc: UpsertDocument, namespace?: string): Promise<void> {
|
|
44
|
+
await this.index(namespace).upsert({
|
|
45
|
+
records: [
|
|
46
|
+
{
|
|
47
|
+
id: doc.id,
|
|
48
|
+
values: doc.vector,
|
|
49
|
+
metadata: {
|
|
50
|
+
content: doc.content,
|
|
51
|
+
...(doc.metadata ?? {}),
|
|
52
|
+
} as RecordMetadata,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
59
|
+
const BATCH = 100;
|
|
60
|
+
for (let i = 0; i < docs.length; i += BATCH) {
|
|
61
|
+
const records = docs.slice(i, i + BATCH).map((d) => ({
|
|
62
|
+
id: d.id,
|
|
63
|
+
values: d.vector,
|
|
64
|
+
metadata: {
|
|
65
|
+
content: d.content,
|
|
66
|
+
...(d.metadata ?? {}),
|
|
67
|
+
} as RecordMetadata,
|
|
68
|
+
}));
|
|
69
|
+
await this.index(namespace).upsert({ records });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async query(
|
|
74
|
+
vector: number[],
|
|
75
|
+
topK: number,
|
|
76
|
+
namespace?: string,
|
|
77
|
+
filter?: Record<string, unknown>
|
|
78
|
+
): Promise<VectorMatch[]> {
|
|
79
|
+
const result = await this.index(namespace).query({
|
|
80
|
+
vector,
|
|
81
|
+
topK,
|
|
82
|
+
includeMetadata: true,
|
|
83
|
+
...(filter ? { filter } : {}),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return (result.matches ?? []).map((m) => ({
|
|
87
|
+
id: m.id,
|
|
88
|
+
score: m.score ?? 0,
|
|
89
|
+
content: (m.metadata?.content as string) ?? '',
|
|
90
|
+
metadata: m.metadata as Record<string, unknown>,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async delete(id: string, namespace?: string): Promise<void> {
|
|
95
|
+
await this.index(namespace).deleteOne({ id });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async deleteNamespace(namespace: string): Promise<void> {
|
|
99
|
+
await this.client.index(this.indexName).namespace(namespace).deleteAll();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async ping(): Promise<boolean> {
|
|
103
|
+
try {
|
|
104
|
+
await this.client.listIndexes();
|
|
105
|
+
return true;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error('[PineconeAdapter] Ping failed:', err);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async disconnect(): Promise<void> {
|
|
113
|
+
// Pinecone SDK is stateless REST, no persistent connection to close.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import { IVectorDB, VectorMatch, UpsertDocument } from '../IVectorDB';
|
|
3
|
+
import { VectorDBConfig } from '../../config/RagConfig';
|
|
4
|
+
import { resolvePath, buildPayload } from '../../utils/templateUtils';
|
|
5
|
+
import { VECTOR_PROFILES } from '../../config/UniversalProfiles';
|
|
6
|
+
|
|
7
|
+
interface UniversalVectorDBOptions {
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
queryPath?: string;
|
|
11
|
+
upsertPath?: string;
|
|
12
|
+
deletePath?: string;
|
|
13
|
+
deleteNamespacePath?: string;
|
|
14
|
+
pingPath?: string;
|
|
15
|
+
queryPayloadTemplate?: string;
|
|
16
|
+
upsertPayloadTemplate?: string;
|
|
17
|
+
batchUpsertPayloadTemplate?: string;
|
|
18
|
+
responseExtractPath?: string;
|
|
19
|
+
idPath?: string;
|
|
20
|
+
scorePath?: string;
|
|
21
|
+
contentPath?: string;
|
|
22
|
+
metadataPath?: string;
|
|
23
|
+
timeout?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class UniversalVectorDBAdapter implements IVectorDB {
|
|
27
|
+
private readonly http: AxiosInstance;
|
|
28
|
+
private readonly indexName: string;
|
|
29
|
+
private readonly opts: UniversalVectorDBOptions;
|
|
30
|
+
|
|
31
|
+
constructor(config: VectorDBConfig) {
|
|
32
|
+
this.indexName = config.indexName;
|
|
33
|
+
|
|
34
|
+
// Merge profile defaults if specified
|
|
35
|
+
const options = (config.options as Record<string, unknown>) ?? {};
|
|
36
|
+
let profile: Record<string, unknown> = {};
|
|
37
|
+
|
|
38
|
+
if (typeof options.profile === 'string') {
|
|
39
|
+
profile = (VECTOR_PROFILES as Record<string, Record<string, unknown>>)[options.profile] || {};
|
|
40
|
+
} else if (typeof options.profile === 'object' && options.profile !== null) {
|
|
41
|
+
profile = options.profile as Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.opts = { ...profile, ...(config.options ?? {}) } as UniversalVectorDBOptions;
|
|
45
|
+
|
|
46
|
+
if (!this.opts.baseUrl) {
|
|
47
|
+
throw new Error('[UniversalVectorDBAdapter] options.baseUrl is required');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.http = axios.create({
|
|
51
|
+
baseURL: this.opts.baseUrl,
|
|
52
|
+
headers: {
|
|
53
|
+
'Content-Type': 'application/json',
|
|
54
|
+
...(this.opts.headers || {}),
|
|
55
|
+
},
|
|
56
|
+
timeout: this.opts.timeout ?? 30_000,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async initialize(): Promise<void> {
|
|
61
|
+
if (this.opts.pingPath) {
|
|
62
|
+
const ok = await this.ping();
|
|
63
|
+
if (!ok) {
|
|
64
|
+
throw new Error(`[UniversalVectorDBAdapter] Could not reach health endpoint: ${this.opts.pingPath}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async upsert(doc: UpsertDocument, namespace?: string): Promise<void> {
|
|
70
|
+
const path = this.opts.upsertPath ?? '/upsert';
|
|
71
|
+
|
|
72
|
+
if (this.opts.upsertPayloadTemplate) {
|
|
73
|
+
const payload = buildPayload(this.opts.upsertPayloadTemplate, {
|
|
74
|
+
id: doc.id,
|
|
75
|
+
vector: doc.vector,
|
|
76
|
+
content: doc.content,
|
|
77
|
+
metadata: doc.metadata ?? {},
|
|
78
|
+
namespace: namespace ?? '',
|
|
79
|
+
index: this.indexName,
|
|
80
|
+
});
|
|
81
|
+
await this.http.post(path, payload);
|
|
82
|
+
} else {
|
|
83
|
+
// Fallback to standard generic shape
|
|
84
|
+
await this.http.post(path, {
|
|
85
|
+
index: this.indexName,
|
|
86
|
+
namespace,
|
|
87
|
+
...doc,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
|
|
93
|
+
const path = this.opts.upsertPath ?? '/upsert';
|
|
94
|
+
|
|
95
|
+
// For batch upsert with templating, if the user provided batchUpsertPayloadTemplate
|
|
96
|
+
if (this.opts.batchUpsertPayloadTemplate) {
|
|
97
|
+
const payload = buildPayload(this.opts.batchUpsertPayloadTemplate, {
|
|
98
|
+
vectors: docs,
|
|
99
|
+
namespace: namespace ?? '',
|
|
100
|
+
index: this.indexName,
|
|
101
|
+
});
|
|
102
|
+
await this.http.post(path, payload);
|
|
103
|
+
} else {
|
|
104
|
+
// Fallback to standard loop or generic shape
|
|
105
|
+
await Promise.all(docs.map((doc) => this.upsert(doc, namespace)));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async query(
|
|
110
|
+
vector: number[],
|
|
111
|
+
topK: number,
|
|
112
|
+
namespace?: string,
|
|
113
|
+
filter?: Record<string, unknown>
|
|
114
|
+
): Promise<VectorMatch[]> {
|
|
115
|
+
const path = this.opts.queryPath ?? '/query';
|
|
116
|
+
|
|
117
|
+
let payload: unknown;
|
|
118
|
+
if (this.opts.queryPayloadTemplate) {
|
|
119
|
+
payload = buildPayload(this.opts.queryPayloadTemplate, {
|
|
120
|
+
vector,
|
|
121
|
+
topK,
|
|
122
|
+
namespace: namespace ?? '',
|
|
123
|
+
index: this.indexName,
|
|
124
|
+
filter: filter ?? {},
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
payload = { index: this.indexName, namespace, vector, topK, filter };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const { data } = await this.http.post(path, payload);
|
|
131
|
+
|
|
132
|
+
const extractPath = this.opts.responseExtractPath ?? 'matches';
|
|
133
|
+
const matchesRaw = resolvePath(data, extractPath);
|
|
134
|
+
|
|
135
|
+
if (!Array.isArray(matchesRaw)) {
|
|
136
|
+
throw new Error(`[UniversalVectorDBAdapter] Expected an array at responseExtractPath '${extractPath}', but got ${typeof matchesRaw}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const idPath = this.opts.idPath ?? 'id';
|
|
140
|
+
const scorePath = this.opts.scorePath ?? 'score';
|
|
141
|
+
const contentPath = this.opts.contentPath ?? 'content';
|
|
142
|
+
const metadataPath = this.opts.metadataPath ?? 'metadata';
|
|
143
|
+
|
|
144
|
+
return matchesRaw.map((match) => ({
|
|
145
|
+
id: String(resolvePath(match, idPath) || ''),
|
|
146
|
+
score: Number(resolvePath(match, scorePath) || 0),
|
|
147
|
+
content: String(resolvePath(match, contentPath) || ''),
|
|
148
|
+
metadata: (resolvePath(match, metadataPath) as Record<string, unknown> | undefined) || {},
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async delete(id: string, namespace?: string): Promise<void> {
|
|
153
|
+
const path = this.opts.deletePath ?? '/delete';
|
|
154
|
+
await this.http.post(path, { index: this.indexName, namespace, id });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async deleteNamespace(namespace: string): Promise<void> {
|
|
158
|
+
const path = this.opts.deleteNamespacePath ?? '/delete-namespace';
|
|
159
|
+
await this.http.post(path, { index: this.indexName, namespace });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async ping(): Promise<boolean> {
|
|
163
|
+
try {
|
|
164
|
+
if (this.opts.pingPath) {
|
|
165
|
+
await this.http.get(this.opts.pingPath);
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error('[UniversalVectorDBAdapter] Ping failed:', err);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async disconnect(): Promise<void> {
|
|
175
|
+
// Axios based adapter is stateless per request.
|
|
176
|
+
}
|
|
177
|
+
}
|