@radaros/core 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1407 -0
- package/dist/index.js +5269 -0
- package/package.json +6 -2
- package/src/a2a/a2a-remote-agent.ts +0 -270
- package/src/a2a/types.ts +0 -142
- package/src/agent/agent.ts +0 -417
- package/src/agent/llm-loop.ts +0 -290
- package/src/agent/run-context.ts +0 -35
- package/src/agent/types.ts +0 -89
- package/src/events/event-bus.ts +0 -45
- package/src/events/types.ts +0 -16
- package/src/guardrails/types.ts +0 -5
- package/src/hooks/types.ts +0 -6
- package/src/index.ts +0 -157
- package/src/knowledge/knowledge-base.ts +0 -146
- package/src/logger/logger.ts +0 -249
- package/src/mcp/mcp-client.ts +0 -264
- package/src/memory/memory.ts +0 -87
- package/src/memory/types.ts +0 -13
- package/src/memory/user-memory.ts +0 -211
- package/src/models/provider.ts +0 -22
- package/src/models/providers/anthropic.ts +0 -360
- package/src/models/providers/google.ts +0 -386
- package/src/models/providers/ollama.ts +0 -211
- package/src/models/providers/openai.ts +0 -345
- package/src/models/providers/vertex.ts +0 -427
- package/src/models/registry.ts +0 -107
- package/src/models/types.ts +0 -124
- package/src/session/session-manager.ts +0 -75
- package/src/session/types.ts +0 -10
- package/src/storage/driver.ts +0 -10
- package/src/storage/in-memory.ts +0 -44
- package/src/storage/mongodb.ts +0 -70
- package/src/storage/postgres.ts +0 -81
- package/src/storage/sqlite.ts +0 -81
- package/src/team/modes.ts +0 -1
- package/src/team/team.ts +0 -323
- package/src/team/types.ts +0 -26
- package/src/toolkits/base.ts +0 -15
- package/src/toolkits/duckduckgo.ts +0 -256
- package/src/toolkits/gmail.ts +0 -226
- package/src/toolkits/hackernews.ts +0 -121
- package/src/toolkits/websearch.ts +0 -158
- package/src/toolkits/whatsapp.ts +0 -209
- package/src/tools/define-tool.ts +0 -22
- package/src/tools/tool-executor.ts +0 -221
- package/src/tools/types.ts +0 -36
- package/src/utils/retry.ts +0 -56
- package/src/vector/base.ts +0 -44
- package/src/vector/embeddings/google.ts +0 -64
- package/src/vector/embeddings/openai.ts +0 -66
- package/src/vector/in-memory.ts +0 -115
- package/src/vector/mongodb.ts +0 -241
- package/src/vector/pgvector.ts +0 -169
- package/src/vector/qdrant.ts +0 -203
- package/src/vector/types.ts +0 -55
- package/src/workflow/step-runner.ts +0 -303
- package/src/workflow/types.ts +0 -55
- package/src/workflow/workflow.ts +0 -68
- package/tsconfig.json +0 -8
package/src/vector/base.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
VectorStore,
|
|
3
|
-
VectorDocument,
|
|
4
|
-
VectorSearchResult,
|
|
5
|
-
VectorSearchOptions,
|
|
6
|
-
EmbeddingProvider,
|
|
7
|
-
} from "./types.js";
|
|
8
|
-
|
|
9
|
-
export abstract class BaseVectorStore implements VectorStore {
|
|
10
|
-
constructor(protected embedder?: EmbeddingProvider) {}
|
|
11
|
-
|
|
12
|
-
protected async ensureEmbedding(doc: VectorDocument): Promise<number[]> {
|
|
13
|
-
if (doc.embedding) return doc.embedding;
|
|
14
|
-
if (!this.embedder) {
|
|
15
|
-
throw new Error(
|
|
16
|
-
"No embedding provided on document and no EmbeddingProvider configured"
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
return this.embedder.embed(doc.content);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
protected async ensureQueryVector(query: number[] | string): Promise<number[]> {
|
|
23
|
-
if (Array.isArray(query)) return query;
|
|
24
|
-
if (!this.embedder) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
"String query requires an EmbeddingProvider to be configured"
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
return this.embedder.embed(query);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
abstract initialize(): Promise<void>;
|
|
33
|
-
abstract upsert(collection: string, doc: VectorDocument): Promise<void>;
|
|
34
|
-
abstract upsertBatch(collection: string, docs: VectorDocument[]): Promise<void>;
|
|
35
|
-
abstract search(
|
|
36
|
-
collection: string,
|
|
37
|
-
query: number[] | string,
|
|
38
|
-
options?: VectorSearchOptions
|
|
39
|
-
): Promise<VectorSearchResult[]>;
|
|
40
|
-
abstract delete(collection: string, id: string): Promise<void>;
|
|
41
|
-
abstract get(collection: string, id: string): Promise<VectorDocument | null>;
|
|
42
|
-
abstract dropCollection(collection: string): Promise<void>;
|
|
43
|
-
abstract close(): Promise<void>;
|
|
44
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import type { EmbeddingProvider } from "../types.js";
|
|
3
|
-
|
|
4
|
-
const _require = createRequire(import.meta.url);
|
|
5
|
-
|
|
6
|
-
export interface GoogleEmbeddingConfig {
|
|
7
|
-
apiKey?: string;
|
|
8
|
-
model?: string;
|
|
9
|
-
dimensions?: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const MODEL_DIMENSIONS: Record<string, number> = {
|
|
13
|
-
"text-embedding-004": 768,
|
|
14
|
-
"embedding-001": 768,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export class GoogleEmbedding implements EmbeddingProvider {
|
|
18
|
-
readonly dimensions: number;
|
|
19
|
-
private ai: any;
|
|
20
|
-
private model: string;
|
|
21
|
-
|
|
22
|
-
constructor(config: GoogleEmbeddingConfig = {}) {
|
|
23
|
-
this.model = config.model ?? "text-embedding-004";
|
|
24
|
-
this.dimensions =
|
|
25
|
-
config.dimensions ?? MODEL_DIMENSIONS[this.model] ?? 768;
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const { GoogleGenAI } = _require("@google/genai");
|
|
29
|
-
this.ai = new GoogleGenAI({
|
|
30
|
-
apiKey: config.apiKey ?? process.env.GOOGLE_API_KEY,
|
|
31
|
-
});
|
|
32
|
-
} catch {
|
|
33
|
-
throw new Error(
|
|
34
|
-
"@google/genai is required for GoogleEmbedding. Install it: npm install @google/genai"
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async embed(text: string): Promise<number[]> {
|
|
40
|
-
const result = await this.ai.models.embedContent({
|
|
41
|
-
model: this.model,
|
|
42
|
-
contents: text,
|
|
43
|
-
...(this.dimensions !== MODEL_DIMENSIONS[this.model]
|
|
44
|
-
? { config: { outputDimensionality: this.dimensions } }
|
|
45
|
-
: {}),
|
|
46
|
-
});
|
|
47
|
-
return result.embeddings[0].values;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async embedBatch(texts: string[]): Promise<number[][]> {
|
|
51
|
-
const results = await Promise.all(
|
|
52
|
-
texts.map((text) =>
|
|
53
|
-
this.ai.models.embedContent({
|
|
54
|
-
model: this.model,
|
|
55
|
-
contents: text,
|
|
56
|
-
...(this.dimensions !== MODEL_DIMENSIONS[this.model]
|
|
57
|
-
? { config: { outputDimensionality: this.dimensions } }
|
|
58
|
-
: {}),
|
|
59
|
-
})
|
|
60
|
-
)
|
|
61
|
-
);
|
|
62
|
-
return results.map((r: any) => r.embeddings[0].values);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import type { EmbeddingProvider } from "../types.js";
|
|
3
|
-
|
|
4
|
-
const _require = createRequire(import.meta.url);
|
|
5
|
-
|
|
6
|
-
export interface OpenAIEmbeddingConfig {
|
|
7
|
-
apiKey?: string;
|
|
8
|
-
baseURL?: string;
|
|
9
|
-
model?: string;
|
|
10
|
-
dimensions?: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const MODEL_DIMENSIONS: Record<string, number> = {
|
|
14
|
-
"text-embedding-3-small": 1536,
|
|
15
|
-
"text-embedding-3-large": 3072,
|
|
16
|
-
"text-embedding-ada-002": 1536,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export class OpenAIEmbedding implements EmbeddingProvider {
|
|
20
|
-
readonly dimensions: number;
|
|
21
|
-
private client: any;
|
|
22
|
-
private model: string;
|
|
23
|
-
|
|
24
|
-
constructor(config: OpenAIEmbeddingConfig = {}) {
|
|
25
|
-
this.model = config.model ?? "text-embedding-3-small";
|
|
26
|
-
this.dimensions =
|
|
27
|
-
config.dimensions ?? MODEL_DIMENSIONS[this.model] ?? 1536;
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const mod = _require("openai");
|
|
31
|
-
const OpenAI = mod.default ?? mod;
|
|
32
|
-
this.client = new OpenAI({
|
|
33
|
-
apiKey: config.apiKey ?? process.env.OPENAI_API_KEY,
|
|
34
|
-
baseURL: config.baseURL,
|
|
35
|
-
});
|
|
36
|
-
} catch {
|
|
37
|
-
throw new Error(
|
|
38
|
-
"openai package is required for OpenAIEmbedding. Install it: npm install openai"
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async embed(text: string): Promise<number[]> {
|
|
44
|
-
const response = await this.client.embeddings.create({
|
|
45
|
-
model: this.model,
|
|
46
|
-
input: text,
|
|
47
|
-
...(this.dimensions !== MODEL_DIMENSIONS[this.model]
|
|
48
|
-
? { dimensions: this.dimensions }
|
|
49
|
-
: {}),
|
|
50
|
-
});
|
|
51
|
-
return response.data[0].embedding;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async embedBatch(texts: string[]): Promise<number[][]> {
|
|
55
|
-
const response = await this.client.embeddings.create({
|
|
56
|
-
model: this.model,
|
|
57
|
-
input: texts,
|
|
58
|
-
...(this.dimensions !== MODEL_DIMENSIONS[this.model]
|
|
59
|
-
? { dimensions: this.dimensions }
|
|
60
|
-
: {}),
|
|
61
|
-
});
|
|
62
|
-
return response.data
|
|
63
|
-
.sort((a: any, b: any) => a.index - b.index)
|
|
64
|
-
.map((d: any) => d.embedding);
|
|
65
|
-
}
|
|
66
|
-
}
|
package/src/vector/in-memory.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { BaseVectorStore } from "./base.js";
|
|
2
|
-
import type {
|
|
3
|
-
VectorDocument,
|
|
4
|
-
VectorSearchResult,
|
|
5
|
-
VectorSearchOptions,
|
|
6
|
-
EmbeddingProvider,
|
|
7
|
-
} from "./types.js";
|
|
8
|
-
|
|
9
|
-
interface StoredDoc {
|
|
10
|
-
id: string;
|
|
11
|
-
content: string;
|
|
12
|
-
embedding: number[];
|
|
13
|
-
metadata: Record<string, unknown>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class InMemoryVectorStore extends BaseVectorStore {
|
|
17
|
-
private collections = new Map<string, Map<string, StoredDoc>>();
|
|
18
|
-
|
|
19
|
-
constructor(embedder?: EmbeddingProvider) {
|
|
20
|
-
super(embedder);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async initialize(): Promise<void> {}
|
|
24
|
-
|
|
25
|
-
private getCol(collection: string): Map<string, StoredDoc> {
|
|
26
|
-
let col = this.collections.get(collection);
|
|
27
|
-
if (!col) {
|
|
28
|
-
col = new Map();
|
|
29
|
-
this.collections.set(collection, col);
|
|
30
|
-
}
|
|
31
|
-
return col;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async upsert(collection: string, doc: VectorDocument): Promise<void> {
|
|
35
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
36
|
-
this.getCol(collection).set(doc.id, {
|
|
37
|
-
id: doc.id,
|
|
38
|
-
content: doc.content,
|
|
39
|
-
embedding,
|
|
40
|
-
metadata: doc.metadata ?? {},
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async upsertBatch(collection: string, docs: VectorDocument[]): Promise<void> {
|
|
45
|
-
for (const doc of docs) {
|
|
46
|
-
await this.upsert(collection, doc);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async search(
|
|
51
|
-
collection: string,
|
|
52
|
-
query: number[] | string,
|
|
53
|
-
options?: VectorSearchOptions
|
|
54
|
-
): Promise<VectorSearchResult[]> {
|
|
55
|
-
const vec = await this.ensureQueryVector(query);
|
|
56
|
-
const topK = options?.topK ?? 10;
|
|
57
|
-
const col = this.getCol(collection);
|
|
58
|
-
|
|
59
|
-
const scored: VectorSearchResult[] = [];
|
|
60
|
-
for (const doc of col.values()) {
|
|
61
|
-
const score = this.cosineSimilarity(vec, doc.embedding);
|
|
62
|
-
if (options?.minScore != null && score < options.minScore) continue;
|
|
63
|
-
if (options?.filter) {
|
|
64
|
-
let match = true;
|
|
65
|
-
for (const [k, v] of Object.entries(options.filter)) {
|
|
66
|
-
if (doc.metadata[k] !== v) {
|
|
67
|
-
match = false;
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (!match) continue;
|
|
72
|
-
}
|
|
73
|
-
scored.push({
|
|
74
|
-
id: doc.id,
|
|
75
|
-
content: doc.content,
|
|
76
|
-
score,
|
|
77
|
-
metadata: doc.metadata,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
scored.sort((a, b) => b.score - a.score);
|
|
82
|
-
return scored.slice(0, topK);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
private cosineSimilarity(a: number[], b: number[]): number {
|
|
86
|
-
let dot = 0;
|
|
87
|
-
let normA = 0;
|
|
88
|
-
let normB = 0;
|
|
89
|
-
for (let i = 0; i < a.length; i++) {
|
|
90
|
-
dot += a[i] * b[i];
|
|
91
|
-
normA += a[i] * a[i];
|
|
92
|
-
normB += b[i] * b[i];
|
|
93
|
-
}
|
|
94
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
95
|
-
return denom === 0 ? 0 : dot / denom;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async delete(collection: string, id: string): Promise<void> {
|
|
99
|
-
this.getCol(collection).delete(id);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async get(collection: string, id: string): Promise<VectorDocument | null> {
|
|
103
|
-
const doc = this.getCol(collection).get(id);
|
|
104
|
-
if (!doc) return null;
|
|
105
|
-
return { id: doc.id, content: doc.content, metadata: doc.metadata };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async dropCollection(collection: string): Promise<void> {
|
|
109
|
-
this.collections.delete(collection);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async close(): Promise<void> {
|
|
113
|
-
this.collections.clear();
|
|
114
|
-
}
|
|
115
|
-
}
|
package/src/vector/mongodb.ts
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { BaseVectorStore } from "./base.js";
|
|
3
|
-
import type {
|
|
4
|
-
VectorDocument,
|
|
5
|
-
VectorSearchResult,
|
|
6
|
-
VectorSearchOptions,
|
|
7
|
-
EmbeddingProvider,
|
|
8
|
-
} from "./types.js";
|
|
9
|
-
|
|
10
|
-
const _require = createRequire(import.meta.url);
|
|
11
|
-
|
|
12
|
-
export interface MongoDBVectorConfig {
|
|
13
|
-
uri: string;
|
|
14
|
-
dbName?: string;
|
|
15
|
-
/** Atlas Search index name (must be pre-created for $vectorSearch). Defaults to "vector_index". */
|
|
16
|
-
indexName?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class MongoDBVectorStore extends BaseVectorStore {
|
|
20
|
-
private client: any;
|
|
21
|
-
private db: any;
|
|
22
|
-
private indexName: string;
|
|
23
|
-
private dbName: string;
|
|
24
|
-
private useAtlas: boolean | null = null;
|
|
25
|
-
|
|
26
|
-
constructor(config: MongoDBVectorConfig, embedder?: EmbeddingProvider) {
|
|
27
|
-
super(embedder);
|
|
28
|
-
this.indexName = config.indexName ?? "vector_index";
|
|
29
|
-
this.dbName = config.dbName ?? "radaros_vectors";
|
|
30
|
-
try {
|
|
31
|
-
const { MongoClient } = _require("mongodb");
|
|
32
|
-
this.client = new MongoClient(config.uri);
|
|
33
|
-
} catch {
|
|
34
|
-
throw new Error(
|
|
35
|
-
"mongodb is required for MongoDBVectorStore. Install it: npm install mongodb"
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async initialize(): Promise<void> {
|
|
41
|
-
await this.client.connect();
|
|
42
|
-
this.db = this.client.db(this.dbName);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private col(collection: string) {
|
|
46
|
-
return this.db.collection(collection);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async upsert(collection: string, doc: VectorDocument): Promise<void> {
|
|
50
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
51
|
-
await this.col(collection).updateOne(
|
|
52
|
-
{ _id: doc.id },
|
|
53
|
-
{
|
|
54
|
-
$set: {
|
|
55
|
-
content: doc.content,
|
|
56
|
-
embedding,
|
|
57
|
-
metadata: doc.metadata ?? {},
|
|
58
|
-
updatedAt: new Date(),
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
{ upsert: true }
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async upsertBatch(collection: string, docs: VectorDocument[]): Promise<void> {
|
|
66
|
-
const ops = await Promise.all(
|
|
67
|
-
docs.map(async (doc) => {
|
|
68
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
69
|
-
return {
|
|
70
|
-
updateOne: {
|
|
71
|
-
filter: { _id: doc.id },
|
|
72
|
-
update: {
|
|
73
|
-
$set: {
|
|
74
|
-
content: doc.content,
|
|
75
|
-
embedding,
|
|
76
|
-
metadata: doc.metadata ?? {},
|
|
77
|
-
updatedAt: new Date(),
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
upsert: true,
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
})
|
|
84
|
-
);
|
|
85
|
-
if (ops.length > 0) {
|
|
86
|
-
await this.col(collection).bulkWrite(ops);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async search(
|
|
91
|
-
collection: string,
|
|
92
|
-
query: number[] | string,
|
|
93
|
-
options?: VectorSearchOptions
|
|
94
|
-
): Promise<VectorSearchResult[]> {
|
|
95
|
-
const vec = await this.ensureQueryVector(query);
|
|
96
|
-
|
|
97
|
-
if (this.useAtlas === true) {
|
|
98
|
-
return this.atlasSearch(collection, vec, options);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (this.useAtlas === false) {
|
|
102
|
-
return this.localSearch(collection, vec, options);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// First call: auto-detect Atlas support
|
|
106
|
-
try {
|
|
107
|
-
const results = await this.atlasSearch(collection, vec, options);
|
|
108
|
-
this.useAtlas = true;
|
|
109
|
-
return results;
|
|
110
|
-
} catch {
|
|
111
|
-
this.useAtlas = false;
|
|
112
|
-
return this.localSearch(collection, vec, options);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private async atlasSearch(
|
|
117
|
-
collection: string,
|
|
118
|
-
vec: number[],
|
|
119
|
-
options?: VectorSearchOptions
|
|
120
|
-
): Promise<VectorSearchResult[]> {
|
|
121
|
-
const topK = options?.topK ?? 10;
|
|
122
|
-
|
|
123
|
-
const pipeline: Record<string, unknown>[] = [
|
|
124
|
-
{
|
|
125
|
-
$vectorSearch: {
|
|
126
|
-
index: this.indexName,
|
|
127
|
-
path: "embedding",
|
|
128
|
-
queryVector: vec,
|
|
129
|
-
numCandidates: topK * 10,
|
|
130
|
-
limit: topK,
|
|
131
|
-
...(options?.filter ? { filter: this.buildFilter(options.filter) } : {}),
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
$addFields: {
|
|
136
|
-
score: { $meta: "vectorSearchScore" },
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
];
|
|
140
|
-
|
|
141
|
-
if (options?.minScore != null) {
|
|
142
|
-
pipeline.push({ $match: { score: { $gte: options.minScore } } });
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
pipeline.push({
|
|
146
|
-
$project: { _id: 1, content: 1, score: 1, metadata: 1 },
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const results = await this.col(collection).aggregate(pipeline).toArray();
|
|
150
|
-
|
|
151
|
-
return results.map((r: any) => ({
|
|
152
|
-
id: String(r._id),
|
|
153
|
-
content: r.content ?? "",
|
|
154
|
-
score: r.score,
|
|
155
|
-
metadata: r.metadata,
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private async localSearch(
|
|
160
|
-
collection: string,
|
|
161
|
-
vec: number[],
|
|
162
|
-
options?: VectorSearchOptions
|
|
163
|
-
): Promise<VectorSearchResult[]> {
|
|
164
|
-
const topK = options?.topK ?? 10;
|
|
165
|
-
const filter: Record<string, unknown> = {};
|
|
166
|
-
if (options?.filter) {
|
|
167
|
-
for (const [k, v] of Object.entries(options.filter)) {
|
|
168
|
-
filter[`metadata.${k}`] = v;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const docs = await this.col(collection)
|
|
173
|
-
.find(filter, { projection: { _id: 1, content: 1, embedding: 1, metadata: 1 } })
|
|
174
|
-
.toArray();
|
|
175
|
-
|
|
176
|
-
const scored: VectorSearchResult[] = [];
|
|
177
|
-
for (const doc of docs) {
|
|
178
|
-
if (!doc.embedding) continue;
|
|
179
|
-
const score = cosine(vec, doc.embedding);
|
|
180
|
-
if (options?.minScore != null && score < options.minScore) continue;
|
|
181
|
-
scored.push({
|
|
182
|
-
id: String(doc._id),
|
|
183
|
-
content: doc.content ?? "",
|
|
184
|
-
score,
|
|
185
|
-
metadata: doc.metadata,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
scored.sort((a, b) => b.score - a.score);
|
|
190
|
-
return scored.slice(0, topK);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private buildFilter(
|
|
194
|
-
filter: Record<string, unknown>
|
|
195
|
-
): Record<string, unknown> {
|
|
196
|
-
const conditions: Record<string, unknown> = {};
|
|
197
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
198
|
-
conditions[`metadata.${key}`] = value;
|
|
199
|
-
}
|
|
200
|
-
return conditions;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async delete(collection: string, id: string): Promise<void> {
|
|
204
|
-
await this.col(collection).deleteOne({ _id: id });
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async get(collection: string, id: string): Promise<VectorDocument | null> {
|
|
208
|
-
const doc = await this.col(collection).findOne({ _id: id });
|
|
209
|
-
if (!doc) return null;
|
|
210
|
-
return {
|
|
211
|
-
id: String(doc._id),
|
|
212
|
-
content: doc.content,
|
|
213
|
-
metadata: doc.metadata,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async dropCollection(collection: string): Promise<void> {
|
|
218
|
-
try {
|
|
219
|
-
await this.col(collection).drop();
|
|
220
|
-
} catch {
|
|
221
|
-
// collection may not exist
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async close(): Promise<void> {
|
|
226
|
-
await this.client.close();
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function cosine(a: number[], b: number[]): number {
|
|
231
|
-
let dot = 0;
|
|
232
|
-
let normA = 0;
|
|
233
|
-
let normB = 0;
|
|
234
|
-
for (let i = 0; i < a.length; i++) {
|
|
235
|
-
dot += a[i] * b[i];
|
|
236
|
-
normA += a[i] * a[i];
|
|
237
|
-
normB += b[i] * b[i];
|
|
238
|
-
}
|
|
239
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
240
|
-
return denom === 0 ? 0 : dot / denom;
|
|
241
|
-
}
|
package/src/vector/pgvector.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { BaseVectorStore } from "./base.js";
|
|
3
|
-
import type {
|
|
4
|
-
VectorDocument,
|
|
5
|
-
VectorSearchResult,
|
|
6
|
-
VectorSearchOptions,
|
|
7
|
-
EmbeddingProvider,
|
|
8
|
-
} from "./types.js";
|
|
9
|
-
|
|
10
|
-
const _require = createRequire(import.meta.url);
|
|
11
|
-
|
|
12
|
-
export interface PgVectorConfig {
|
|
13
|
-
connectionString: string;
|
|
14
|
-
dimensions?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class PgVectorStore extends BaseVectorStore {
|
|
18
|
-
private pool: any;
|
|
19
|
-
private dimensions: number;
|
|
20
|
-
private initializedCollections = new Set<string>();
|
|
21
|
-
|
|
22
|
-
constructor(config: PgVectorConfig, embedder?: EmbeddingProvider) {
|
|
23
|
-
super(embedder);
|
|
24
|
-
this.dimensions = config.dimensions ?? embedder?.dimensions ?? 1536;
|
|
25
|
-
try {
|
|
26
|
-
const { Pool } = _require("pg");
|
|
27
|
-
this.pool = new Pool({ connectionString: config.connectionString });
|
|
28
|
-
} catch {
|
|
29
|
-
throw new Error(
|
|
30
|
-
"pg is required for PgVectorStore. Install it: npm install pg"
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async initialize(): Promise<void> {
|
|
36
|
-
await this.pool.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private async ensureCollection(collection: string): Promise<void> {
|
|
40
|
-
if (this.initializedCollections.has(collection)) return;
|
|
41
|
-
const table = this.sanitize(collection);
|
|
42
|
-
await this.pool.query(`
|
|
43
|
-
CREATE TABLE IF NOT EXISTS ${table} (
|
|
44
|
-
id TEXT PRIMARY KEY,
|
|
45
|
-
content TEXT NOT NULL,
|
|
46
|
-
embedding vector(${this.dimensions}),
|
|
47
|
-
metadata JSONB DEFAULT '{}'::jsonb,
|
|
48
|
-
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
49
|
-
)
|
|
50
|
-
`);
|
|
51
|
-
await this.pool.query(`
|
|
52
|
-
CREATE INDEX IF NOT EXISTS ${table}_embedding_idx
|
|
53
|
-
ON ${table} USING ivfflat (embedding vector_cosine_ops)
|
|
54
|
-
WITH (lists = 100)
|
|
55
|
-
`).catch(() => {
|
|
56
|
-
// IVFFlat index needs enough rows; hnsw fallback
|
|
57
|
-
});
|
|
58
|
-
this.initializedCollections.add(collection);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private sanitize(name: string): string {
|
|
62
|
-
return name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private toSql(vec: number[]): string {
|
|
66
|
-
return `[${vec.join(",")}]`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async upsert(collection: string, doc: VectorDocument): Promise<void> {
|
|
70
|
-
await this.ensureCollection(collection);
|
|
71
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
72
|
-
const table = this.sanitize(collection);
|
|
73
|
-
await this.pool.query(
|
|
74
|
-
`INSERT INTO ${table} (id, content, embedding, metadata)
|
|
75
|
-
VALUES ($1, $2, $3::vector, $4::jsonb)
|
|
76
|
-
ON CONFLICT (id)
|
|
77
|
-
DO UPDATE SET content = EXCLUDED.content,
|
|
78
|
-
embedding = EXCLUDED.embedding,
|
|
79
|
-
metadata = EXCLUDED.metadata`,
|
|
80
|
-
[doc.id, doc.content, this.toSql(embedding), JSON.stringify(doc.metadata ?? {})]
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async upsertBatch(collection: string, docs: VectorDocument[]): Promise<void> {
|
|
85
|
-
for (const doc of docs) {
|
|
86
|
-
await this.upsert(collection, doc);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async search(
|
|
91
|
-
collection: string,
|
|
92
|
-
query: number[] | string,
|
|
93
|
-
options?: VectorSearchOptions
|
|
94
|
-
): Promise<VectorSearchResult[]> {
|
|
95
|
-
await this.ensureCollection(collection);
|
|
96
|
-
const vec = await this.ensureQueryVector(query);
|
|
97
|
-
const topK = options?.topK ?? 10;
|
|
98
|
-
const table = this.sanitize(collection);
|
|
99
|
-
|
|
100
|
-
let filterClause = "";
|
|
101
|
-
const params: unknown[] = [this.toSql(vec), topK];
|
|
102
|
-
|
|
103
|
-
if (options?.filter) {
|
|
104
|
-
const conditions = Object.entries(options.filter).map(([k, v], i) => {
|
|
105
|
-
params.push(JSON.stringify(v));
|
|
106
|
-
return `metadata->>'${k.replace(/'/g, "''")}' = $${i + 3}`;
|
|
107
|
-
});
|
|
108
|
-
if (conditions.length > 0) {
|
|
109
|
-
filterClause = `WHERE ${conditions.join(" AND ")}`;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const result = await this.pool.query(
|
|
114
|
-
`SELECT id, content, metadata,
|
|
115
|
-
1 - (embedding <=> $1::vector) AS score
|
|
116
|
-
FROM ${table}
|
|
117
|
-
${filterClause}
|
|
118
|
-
ORDER BY embedding <=> $1::vector
|
|
119
|
-
LIMIT $2`,
|
|
120
|
-
params
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
let rows = result.rows as Array<{
|
|
124
|
-
id: string;
|
|
125
|
-
content: string;
|
|
126
|
-
score: number;
|
|
127
|
-
metadata: Record<string, unknown>;
|
|
128
|
-
}>;
|
|
129
|
-
|
|
130
|
-
if (options?.minScore != null) {
|
|
131
|
-
rows = rows.filter((r) => r.score >= options.minScore!);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return rows.map((r) => ({
|
|
135
|
-
id: r.id,
|
|
136
|
-
content: r.content,
|
|
137
|
-
score: r.score,
|
|
138
|
-
metadata: r.metadata,
|
|
139
|
-
}));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async delete(collection: string, id: string): Promise<void> {
|
|
143
|
-
await this.ensureCollection(collection);
|
|
144
|
-
const table = this.sanitize(collection);
|
|
145
|
-
await this.pool.query(`DELETE FROM ${table} WHERE id = $1`, [id]);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async get(collection: string, id: string): Promise<VectorDocument | null> {
|
|
149
|
-
await this.ensureCollection(collection);
|
|
150
|
-
const table = this.sanitize(collection);
|
|
151
|
-
const result = await this.pool.query(
|
|
152
|
-
`SELECT id, content, metadata FROM ${table} WHERE id = $1`,
|
|
153
|
-
[id]
|
|
154
|
-
);
|
|
155
|
-
if (result.rows.length === 0) return null;
|
|
156
|
-
const row = result.rows[0];
|
|
157
|
-
return { id: row.id, content: row.content, metadata: row.metadata };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async dropCollection(collection: string): Promise<void> {
|
|
161
|
-
const table = this.sanitize(collection);
|
|
162
|
-
await this.pool.query(`DROP TABLE IF EXISTS ${table}`);
|
|
163
|
-
this.initializedCollections.delete(collection);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async close(): Promise<void> {
|
|
167
|
-
await this.pool.end();
|
|
168
|
-
}
|
|
169
|
-
}
|