Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a1225f8e9 | |||
| 1b51ad5f89 |
@@ -5,6 +5,12 @@ description: "Use pgvector as a vector store in Mem0 for PostgreSQL-based vector
|
||||
|
||||
[pgvector](https://github.com/pgvector/pgvector) is an open-source vector similarity search extension for Postgres. After connecting to Postgres, run `CREATE EXTENSION IF NOT EXISTS vector;` to create the vector extension.
|
||||
|
||||
The TypeScript SDK loads the `pg` driver only when you use this store, so install it alongside `mem0ai`:
|
||||
|
||||
```bash
|
||||
npm install pg
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
+13
-4
@@ -100,7 +100,8 @@
|
||||
"tsup": "^8.3.0",
|
||||
"typescript": "5.5.4",
|
||||
"iovalkey": "^0.3.3",
|
||||
"@mochow/mochow-sdk-node": "^2.1.5"
|
||||
"@mochow/mochow-sdk-node": "^2.1.5",
|
||||
"@types/jest": "^29.5.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.18.0",
|
||||
@@ -127,8 +128,7 @@
|
||||
"@qdrant/js-client-rest": "^1.18.0",
|
||||
"@supabase/supabase-js": "^2.49.1",
|
||||
"@turbopuffer/turbopuffer": "^2.0.0",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/pg": "8.11.0",
|
||||
"@types/pg": "^8.11.0",
|
||||
"@upstash/vector": "^1.2.3",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"cassandra-driver": "4.8.0",
|
||||
@@ -141,7 +141,7 @@
|
||||
"weaviate-client": "^3.0.0",
|
||||
"ollama": "^0.5.14",
|
||||
"oracledb": "^6.5.0 || ^7.0.0",
|
||||
"pg": "8.11.3",
|
||||
"pg": "^8.11.3",
|
||||
"redis": "^4.6.13",
|
||||
"@elastic/elasticsearch": "^9.0.0",
|
||||
"iovalkey": "^0.3.3",
|
||||
@@ -257,6 +257,15 @@
|
||||
},
|
||||
"oracledb": {
|
||||
"optional": true
|
||||
},
|
||||
"pg": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/pg": {
|
||||
"optional": true
|
||||
},
|
||||
"natural": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
||||
Generated
+6
-42
@@ -109,11 +109,8 @@ importers:
|
||||
'@turbopuffer/turbopuffer':
|
||||
specifier: ^2.0.0
|
||||
version: 2.5.0
|
||||
'@types/jest':
|
||||
specifier: 29.5.14
|
||||
version: 29.5.14
|
||||
'@types/pg':
|
||||
specifier: 8.11.0
|
||||
specifier: ^8.11.0
|
||||
version: 8.11.0
|
||||
'@upstash/vector':
|
||||
specifier: ^1.2.3
|
||||
@@ -167,8 +164,8 @@ importers:
|
||||
specifier: ^6.5.0 || ^7.0.0
|
||||
version: 7.0.1
|
||||
pg:
|
||||
specifier: 8.11.3
|
||||
version: 8.11.3
|
||||
specifier: ^8.11.3
|
||||
version: 8.21.0
|
||||
redis:
|
||||
specifier: ^4.6.13
|
||||
version: 4.7.1
|
||||
@@ -191,6 +188,9 @@ importers:
|
||||
'@types/better-sqlite3':
|
||||
specifier: ^7.6.13
|
||||
version: 7.6.13
|
||||
'@types/jest':
|
||||
specifier: ^29.5.14
|
||||
version: 29.5.14
|
||||
'@types/node':
|
||||
specifier: ^22.7.6
|
||||
version: 22.19.21
|
||||
@@ -2228,10 +2228,6 @@ packages:
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
buffer-writer@2.0.0:
|
||||
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
@@ -3846,9 +3842,6 @@ packages:
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
|
||||
packet-reader@1.0.0:
|
||||
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
|
||||
|
||||
pad-left@2.1.0:
|
||||
resolution: {integrity: sha512-HJxs9K9AztdIQIAIa/OIazRAUW/L6B9hbQDxO4X07roW3eo9XqZc2ur9bn1StH9CnbbI9EgvejHQX7CBpCF1QA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -3920,15 +3913,6 @@ packages:
|
||||
resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
pg@8.11.3:
|
||||
resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
peerDependencies:
|
||||
pg-native: '>=3.0.1'
|
||||
peerDependenciesMeta:
|
||||
pg-native:
|
||||
optional: true
|
||||
|
||||
pg@8.21.0:
|
||||
resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==}
|
||||
engines: {node: '>= 16.0.0'}
|
||||
@@ -7593,8 +7577,6 @@ snapshots:
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer-writer@2.0.0: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
@@ -9409,8 +9391,6 @@ snapshots:
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
packet-reader@1.0.0: {}
|
||||
|
||||
pad-left@2.1.0:
|
||||
dependencies:
|
||||
repeat-string: 1.6.1
|
||||
@@ -9458,10 +9438,6 @@ snapshots:
|
||||
|
||||
pg-numeric@1.0.2: {}
|
||||
|
||||
pg-pool@3.14.0(pg@8.11.3):
|
||||
dependencies:
|
||||
pg: 8.11.3
|
||||
|
||||
pg-pool@3.14.0(pg@8.21.0):
|
||||
dependencies:
|
||||
pg: 8.21.0
|
||||
@@ -9486,18 +9462,6 @@ snapshots:
|
||||
postgres-interval: 3.0.0
|
||||
postgres-range: 1.1.4
|
||||
|
||||
pg@8.11.3:
|
||||
dependencies:
|
||||
buffer-writer: 2.0.0
|
||||
packet-reader: 1.0.0
|
||||
pg-connection-string: 2.13.0
|
||||
pg-pool: 3.14.0(pg@8.11.3)
|
||||
pg-protocol: 1.14.0
|
||||
pg-types: 2.2.0
|
||||
pgpass: 1.0.5
|
||||
optionalDependencies:
|
||||
pg-cloudflare: 1.4.0
|
||||
|
||||
pg@8.21.0:
|
||||
dependencies:
|
||||
pg-connection-string: 2.13.0
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import type { Client as ClientType, ClientConfig } from "pg";
|
||||
import pkg from "pg";
|
||||
const { Client, escapeIdentifier } = pkg;
|
||||
import { VectorStore } from "./base";
|
||||
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
|
||||
import { loadPeer } from "../utils/load_peer";
|
||||
|
||||
function escapeIdentifier(name: string): string {
|
||||
return `"${name.replace(/"/g, '""')}"`;
|
||||
}
|
||||
|
||||
const SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,127}$/;
|
||||
|
||||
@@ -212,7 +215,7 @@ function buildClientConfig(
|
||||
}
|
||||
|
||||
export class PGVector implements VectorStore {
|
||||
private client: ClientType;
|
||||
private client!: ClientType;
|
||||
private collectionName: string;
|
||||
private useDiskann: boolean;
|
||||
private useHnsw: boolean;
|
||||
@@ -234,13 +237,6 @@ export class PGVector implements VectorStore {
|
||||
? ""
|
||||
: validateIdentifier(config.dbname || "vector_store", "dbname");
|
||||
this.config = config;
|
||||
|
||||
this.client = new Client(
|
||||
buildClientConfig(
|
||||
config,
|
||||
this.useDirectConnection ? undefined : "postgres",
|
||||
),
|
||||
);
|
||||
this.initialize().catch(console.error);
|
||||
}
|
||||
|
||||
@@ -257,6 +253,18 @@ export class PGVector implements VectorStore {
|
||||
|
||||
private async _doInitialize(): Promise<void> {
|
||||
try {
|
||||
const pg = await loadPeer(
|
||||
"pg",
|
||||
"PGVector vector store",
|
||||
() => import("pg"),
|
||||
);
|
||||
const { Client } = pg.default ?? pg;
|
||||
this.client = new Client(
|
||||
buildClientConfig(
|
||||
this.config,
|
||||
this.useDirectConnection ? undefined : "postgres",
|
||||
),
|
||||
);
|
||||
await this.client.connect();
|
||||
|
||||
if (!this.useDirectConnection) {
|
||||
@@ -345,6 +353,7 @@ export class PGVector implements VectorStore {
|
||||
ids: string[],
|
||||
payloads: Record<string, any>[],
|
||||
): Promise<void> {
|
||||
await this.initialize();
|
||||
const values = vectors.map((vector, i) => ({
|
||||
id: ids[i],
|
||||
vector: `[${vector.join(",")}]`,
|
||||
@@ -368,6 +377,7 @@ export class PGVector implements VectorStore {
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[] | null> {
|
||||
await this.initialize();
|
||||
try {
|
||||
const {
|
||||
conditions,
|
||||
@@ -406,6 +416,7 @@ export class PGVector implements VectorStore {
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
await this.initialize();
|
||||
const queryVector = `[${query.join(",")}]`;
|
||||
const {
|
||||
conditions,
|
||||
@@ -435,6 +446,7 @@ export class PGVector implements VectorStore {
|
||||
}
|
||||
|
||||
async get(vectorId: string): Promise<VectorStoreResult | null> {
|
||||
await this.initialize();
|
||||
const result = await this.client.query(
|
||||
`SELECT id, payload FROM ${this.col()} WHERE id = $1`,
|
||||
[vectorId],
|
||||
@@ -453,6 +465,7 @@ export class PGVector implements VectorStore {
|
||||
vector: number[],
|
||||
payload: Record<string, any>,
|
||||
): Promise<void> {
|
||||
await this.initialize();
|
||||
const vectorStr = `[${vector.join(",")}]`;
|
||||
await this.client.query(
|
||||
`
|
||||
@@ -465,12 +478,14 @@ export class PGVector implements VectorStore {
|
||||
}
|
||||
|
||||
async delete(vectorId: string): Promise<void> {
|
||||
await this.initialize();
|
||||
await this.client.query(`DELETE FROM ${this.col()} WHERE id = $1`, [
|
||||
vectorId,
|
||||
]);
|
||||
}
|
||||
|
||||
async deleteCol(): Promise<void> {
|
||||
await this.initialize();
|
||||
await this.client.query(`DROP TABLE IF EXISTS ${this.col()}`);
|
||||
}
|
||||
|
||||
@@ -487,6 +502,7 @@ export class PGVector implements VectorStore {
|
||||
filters?: SearchFilters,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
await this.initialize();
|
||||
const {
|
||||
conditions,
|
||||
values: filterValues,
|
||||
@@ -525,10 +541,11 @@ export class PGVector implements VectorStore {
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this.client.end();
|
||||
await this.client?.end();
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
await this.initialize();
|
||||
const result = await this.client.query(
|
||||
"SELECT user_id FROM memory_migrations LIMIT 1",
|
||||
);
|
||||
@@ -549,6 +566,7 @@ export class PGVector implements VectorStore {
|
||||
}
|
||||
|
||||
async setUserId(userId: string): Promise<void> {
|
||||
await this.initialize();
|
||||
await this.client.query("DELETE FROM memory_migrations");
|
||||
await this.client.query(
|
||||
"INSERT INTO memory_migrations (user_id) VALUES ($1)",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
jest.mock("pg", () => {
|
||||
throw new Error("Cannot find module 'pg'");
|
||||
});
|
||||
|
||||
jest.mock("natural", () => {
|
||||
throw new Error("Cannot find module 'natural'");
|
||||
});
|
||||
|
||||
describe("mem0ai/oss without pg or natural installed", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, "error").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test("mem0ai/oss loads", async () => {
|
||||
await expect(import("../src")).resolves.toHaveProperty("Memory");
|
||||
});
|
||||
|
||||
test("PGVector explains how to install pg", async () => {
|
||||
const { PGVector } = await import("../src/vector_stores/pgvector");
|
||||
const store = new PGVector({
|
||||
connectionString: "postgresql://localhost:5432/db",
|
||||
embeddingModelDims: 3,
|
||||
} as any);
|
||||
|
||||
await expect(store.initialize()).rejects.toThrow(
|
||||
"The 'pg' package is required to use the PGVector vector store. Install it with: npm install pg",
|
||||
);
|
||||
});
|
||||
|
||||
test("BM25 lemmatization falls back to the built-in stemmer", async () => {
|
||||
const { lemmatizeForBm25 } = await import("../src/utils/lemmatization");
|
||||
|
||||
expect(lemmatizeForBm25("The dogs were running")).toBe("dog runn running");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user