Added cloudflare vector-store (#2607)
This commit is contained in:
45
docs/components/vectordbs/dbs/vectorize.mdx
Normal file
45
docs/components/vectordbs/dbs/vectorize.mdx
Normal file
@@ -0,0 +1,45 @@
|
||||
[Cloudflare Vectorize](https://developers.cloudflare.com/vectorize/) is a vector database offering from Cloudflare, allowing you to build AI-powered applications with vector embeddings.
|
||||
|
||||
### Usage
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
import { Memory } from 'mem0ai/oss';
|
||||
|
||||
const config = {
|
||||
vectorStore: {
|
||||
provider: 'vectorize',
|
||||
config: {
|
||||
indexName: 'my-memory-index',
|
||||
accountId: 'your-cloudflare-account-id',
|
||||
apiKey: 'your-cloudflare-api-key',
|
||||
dimension: 1536, // Optional: defaults to 1536
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const memory = new Memory(config);
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm looking for a good book to read."},
|
||||
{"role": "assistant", "content": "Sure, what genre are you interested in?"},
|
||||
{"role": "user", "content": "I enjoy fantasy novels with strong world-building."},
|
||||
{"role": "assistant", "content": "Great! I'll keep that in mind for future recommendations."}
|
||||
]
|
||||
await memory.add(messages, { userId: "bob", metadata: { interest: "books" } });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Config
|
||||
|
||||
Let's see the available parameters for the `vectorize` config:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="TypeScript">
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
| `indexName` | The name of the Vectorize index | `None` (Required) |
|
||||
| `accountId` | Your Cloudflare account ID | `None` (Required) |
|
||||
| `apiKey` | Your Cloudflare API token | `None` (Required) |
|
||||
| `dimension` | Dimensions of the embedding model | `1536` |
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@@ -13,7 +13,7 @@ Mem0 includes built-in support for various popular databases. Memory can utilize
|
||||
See the list of supported vector databases below.
|
||||
|
||||
<Note>
|
||||
The following vector databases are supported in the Python implementation. The TypeScript implementation currently only supports Qdrant, Redis and in-memory vector database.
|
||||
The following vector databases are supported in the Python implementation. The TypeScript implementation currently only supports Qdrant, Redis,Vectorize and in-memory vector database.
|
||||
</Note>
|
||||
|
||||
<CardGroup cols={3}>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@types/redis": "^4.0.10",
|
||||
"@types/sqlite3": "^3.1.11",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"cloudflare": "^4.2.0",
|
||||
"dotenv": "^16.4.4",
|
||||
"groq-sdk": "^0.3.0",
|
||||
"openai": "^4.28.0",
|
||||
@@ -31,6 +32,7 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20250504.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "^5.0.5",
|
||||
|
||||
@@ -22,4 +22,5 @@ export * from "./vector_stores/qdrant";
|
||||
export * from "./vector_stores/redis";
|
||||
export * from "./vector_stores/supabase";
|
||||
export * from "./vector_stores/langchain";
|
||||
export * from "./vector_stores/vectorize";
|
||||
export * from "./utils/factory";
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Embedder } from "../embeddings/base";
|
||||
import { LLM } from "../llms/base";
|
||||
import { VectorStore } from "../vector_stores/base";
|
||||
import { Qdrant } from "../vector_stores/qdrant";
|
||||
import { VectorizeDB } from "../vector_stores/vectorize";
|
||||
import { RedisDB } from "../vector_stores/redis";
|
||||
import { OllamaLLM } from "../llms/ollama";
|
||||
import { SupabaseDB } from "../vector_stores/supabase";
|
||||
@@ -90,6 +91,8 @@ export class VectorStoreFactory {
|
||||
return new SupabaseDB(config as any);
|
||||
case "langchain":
|
||||
return new LangchainVectorStore(config as any);
|
||||
case "vectorize":
|
||||
return new VectorizeDB(config as any);
|
||||
default:
|
||||
throw new Error(`Unsupported vector store provider: ${provider}`);
|
||||
}
|
||||
|
||||
436
mem0-ts/src/oss/src/vector_stores/vectorize.ts
Normal file
436
mem0-ts/src/oss/src/vector_stores/vectorize.ts
Normal file
@@ -0,0 +1,436 @@
|
||||
import Cloudflare from "cloudflare";
|
||||
import type { Vectorize, VectorizeVector } from "@cloudflare/workers-types";
|
||||
import { VectorStore } from "./base";
|
||||
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
|
||||
|
||||
interface VectorizeConfig extends VectorStoreConfig {
|
||||
apiKey?: string;
|
||||
indexName: string;
|
||||
accountId: string;
|
||||
}
|
||||
|
||||
interface CloudflareVector {
|
||||
id: string;
|
||||
values: number[];
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export class VectorizeDB implements VectorStore {
|
||||
private client: Cloudflare | null = null;
|
||||
private dimensions: number;
|
||||
private indexName: string;
|
||||
private accountId: string;
|
||||
|
||||
constructor(config: VectorizeConfig) {
|
||||
this.client = new Cloudflare({ apiToken: config.apiKey });
|
||||
this.dimensions = config.dimension || 1536;
|
||||
this.indexName = config.indexName;
|
||||
this.accountId = config.accountId;
|
||||
this.initialize().catch(console.error);
|
||||
}
|
||||
|
||||
async insert(
|
||||
vectors: number[][],
|
||||
ids: string[],
|
||||
payloads: Record<string, any>[]
|
||||
): Promise<void> {
|
||||
try {
|
||||
const vectorObjects: CloudflareVector[] = vectors.map(
|
||||
(vector, index) => ({
|
||||
id: ids[index],
|
||||
values: vector,
|
||||
metadata: payloads[index] || {},
|
||||
})
|
||||
);
|
||||
|
||||
const ndjsonPayload = vectorObjects
|
||||
.map((v) => JSON.stringify(v))
|
||||
.join("\n");
|
||||
|
||||
const response = await fetch(
|
||||
`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/vectorize/v2/indexes/${this.indexName}/insert`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-ndjson",
|
||||
Authorization: `Bearer ${this.client?.apiToken}`,
|
||||
},
|
||||
body: ndjsonPayload,
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(
|
||||
`Failed to insert vectors: ${response.status} ${errorText}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error inserting vectors:", error);
|
||||
throw new Error(
|
||||
`Failed to insert vectors: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
limit: number = 5,
|
||||
filters?: SearchFilters
|
||||
): Promise<VectorStoreResult[]> {
|
||||
try {
|
||||
const result = await this.client?.vectorize.indexes.query(
|
||||
this.indexName,
|
||||
{
|
||||
account_id: this.accountId,
|
||||
vector: query,
|
||||
filter: filters,
|
||||
returnMetadata: "all",
|
||||
topK: limit,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
(result?.matches?.map((match) => ({
|
||||
id: match.id,
|
||||
payload: match.metadata,
|
||||
score: match.score,
|
||||
})) as VectorStoreResult[]) || []
|
||||
); // Return empty array if result or matches is null/undefined
|
||||
} catch (error) {
|
||||
console.error("Error searching vectors:", error);
|
||||
throw new Error(
|
||||
`Failed to search vectors: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async get(vectorId: string): Promise<VectorStoreResult | null> {
|
||||
try {
|
||||
const result = (await this.client?.vectorize.indexes.getByIds(
|
||||
this.indexName,
|
||||
{
|
||||
account_id: this.accountId,
|
||||
ids: [vectorId],
|
||||
}
|
||||
)) as any;
|
||||
|
||||
if (!result?.length) return null;
|
||||
|
||||
return {
|
||||
id: vectorId,
|
||||
payload: result[0].metadata,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error getting vector:", error);
|
||||
throw new Error(
|
||||
`Failed to get vector: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async update(
|
||||
vectorId: string,
|
||||
vector: number[],
|
||||
payload: Record<string, any>
|
||||
): Promise<void> {
|
||||
try {
|
||||
const data: VectorizeVector = {
|
||||
id: vectorId,
|
||||
values: vector,
|
||||
metadata: payload,
|
||||
};
|
||||
|
||||
const response = await fetch(
|
||||
`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/vectorize/v2/indexes/${this.indexName}/upsert`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-ndjson",
|
||||
Authorization: `Bearer ${this.client?.apiToken}`,
|
||||
},
|
||||
body: JSON.stringify(data) + "\n", // ndjson format
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(
|
||||
`Failed to update vector: ${response.status} ${errorText}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating vector:", error);
|
||||
throw new Error(
|
||||
`Failed to update vector: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(vectorId: string): Promise<void> {
|
||||
try {
|
||||
await this.client?.vectorize.indexes.deleteByIds(this.indexName, {
|
||||
account_id: this.accountId,
|
||||
ids: [vectorId],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting vector:", error);
|
||||
throw new Error(
|
||||
`Failed to delete vector: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteCol(): Promise<void> {
|
||||
try {
|
||||
await this.client?.vectorize.indexes.delete(this.indexName, {
|
||||
account_id: this.accountId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting collection:", error);
|
||||
throw new Error(
|
||||
`Failed to delete collection: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
limit: number = 20
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
try {
|
||||
const result = await this.client?.vectorize.indexes.query(
|
||||
this.indexName,
|
||||
{
|
||||
account_id: this.accountId,
|
||||
vector: Array(this.dimensions).fill(0), // Dummy vector for listing
|
||||
filter: filters,
|
||||
topK: limit,
|
||||
returnMetadata: "all",
|
||||
}
|
||||
);
|
||||
|
||||
const matches =
|
||||
(result?.matches?.map((match) => ({
|
||||
id: match.id,
|
||||
payload: match.metadata,
|
||||
score: match.score,
|
||||
})) as VectorStoreResult[]) || [];
|
||||
|
||||
return [matches, matches.length];
|
||||
} catch (error) {
|
||||
console.error("Error listing vectors:", error);
|
||||
throw new Error(
|
||||
`Failed to list vectors: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private generateUUID(): string {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
|
||||
/[xy]/g,
|
||||
function (c) {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
try {
|
||||
let found = false;
|
||||
for await (const index of this.client!.vectorize.indexes.list({
|
||||
account_id: this.accountId,
|
||||
})) {
|
||||
if (index.name === "memory_migrations") {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
await this.client?.vectorize.indexes.create({
|
||||
account_id: this.accountId,
|
||||
name: "memory_migrations",
|
||||
config: {
|
||||
dimensions: 1,
|
||||
metric: "cosine",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Now try to get the userId
|
||||
const result: any = await this.client?.vectorize.indexes.query(
|
||||
"memory_migrations",
|
||||
{
|
||||
account_id: this.accountId,
|
||||
vector: [0],
|
||||
topK: 1,
|
||||
returnMetadata: "all",
|
||||
}
|
||||
);
|
||||
if (result.matches.length > 0) {
|
||||
return result.matches[0].metadata.userId as string;
|
||||
}
|
||||
|
||||
// Generate a random userId if none exists
|
||||
const randomUserId =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
const data: VectorizeVector = {
|
||||
id: this.generateUUID(),
|
||||
values: [0],
|
||||
metadata: { userId: randomUserId },
|
||||
};
|
||||
|
||||
await fetch(
|
||||
`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/vectorize/v2/indexes/memory_migrations/upsert`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-ndjson",
|
||||
Authorization: `Bearer ${this.client?.apiToken}`,
|
||||
},
|
||||
body: JSON.stringify(data) + "\n", // ndjson format
|
||||
}
|
||||
);
|
||||
return randomUserId;
|
||||
} catch (error) {
|
||||
console.error("Error getting user ID:", error);
|
||||
throw new Error(
|
||||
`Failed to get user ID: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async setUserId(userId: string): Promise<void> {
|
||||
try {
|
||||
// Get existing point ID
|
||||
const result: any = await this.client?.vectorize.indexes.query(
|
||||
"memory_migrations",
|
||||
{
|
||||
account_id: this.accountId,
|
||||
vector: [0],
|
||||
topK: 1,
|
||||
returnMetadata: "all",
|
||||
}
|
||||
);
|
||||
const pointId =
|
||||
result.matches.length > 0 ? result.matches[0].id : this.generateUUID();
|
||||
|
||||
const data: VectorizeVector = {
|
||||
id: pointId,
|
||||
values: [0],
|
||||
metadata: { userId },
|
||||
};
|
||||
await fetch(
|
||||
`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/vectorize/v2/indexes/memory_migrations/upsert`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-ndjson",
|
||||
Authorization: `Bearer ${this.client?.apiToken}`,
|
||||
},
|
||||
body: JSON.stringify(data) + "\n", // ndjson format
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error setting user ID:", error);
|
||||
throw new Error(
|
||||
`Failed to set user ID: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
try {
|
||||
// Check if the index already exists
|
||||
let indexFound = false;
|
||||
for await (const idx of this.client!.vectorize.indexes.list({
|
||||
account_id: this.accountId,
|
||||
})) {
|
||||
if (idx.name === this.indexName) {
|
||||
indexFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If the index doesn't exist, create it
|
||||
if (!indexFound) {
|
||||
try {
|
||||
await this.client?.vectorize.indexes.create({
|
||||
account_id: this.accountId,
|
||||
name: this.indexName,
|
||||
config: {
|
||||
dimensions: this.dimensions,
|
||||
metric: "cosine",
|
||||
},
|
||||
});
|
||||
|
||||
const properties = ["userId", "agentId", "runId"];
|
||||
|
||||
for (const propertyName of properties) {
|
||||
await this.client?.vectorize.indexes.metadataIndex.create(
|
||||
this.indexName,
|
||||
{
|
||||
account_id: this.accountId,
|
||||
indexType: "string",
|
||||
propertyName,
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (err: any) {
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// check for metadata index
|
||||
const metadataIndexes =
|
||||
await this.client?.vectorize.indexes.metadataIndex.list(
|
||||
this.indexName,
|
||||
{
|
||||
account_id: this.accountId,
|
||||
}
|
||||
);
|
||||
const existingMetadataIndexes = new Set<string>();
|
||||
for (const metadataIndex of metadataIndexes?.metadataIndexes || []) {
|
||||
existingMetadataIndexes.add(metadataIndex.propertyName!);
|
||||
}
|
||||
const properties = ["userId", "agentId", "runId"];
|
||||
for (const propertyName of properties) {
|
||||
if (!existingMetadataIndexes.has(propertyName)) {
|
||||
await this.client?.vectorize.indexes.metadataIndex.create(
|
||||
this.indexName,
|
||||
{
|
||||
account_id: this.accountId,
|
||||
indexType: "string",
|
||||
propertyName,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
// Create memory_migrations collection if it doesn't exist
|
||||
let found = false;
|
||||
for await (const index of this.client!.vectorize.indexes.list({
|
||||
account_id: this.accountId,
|
||||
})) {
|
||||
if (index.name === "memory_migrations") {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
await this.client?.vectorize.indexes.create({
|
||||
account_id: this.accountId,
|
||||
name: "memory_migrations",
|
||||
config: {
|
||||
dimensions: 1,
|
||||
metric: "cosine",
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user