Add user_id in TS OSS SDK (#2514)
This commit is contained in:
@@ -35,7 +35,9 @@ export class GoogleLLM implements LLM {
|
||||
// },
|
||||
});
|
||||
|
||||
const text = completion.text?.replace(/^```json\n/, "").replace(/\n```$/, "");
|
||||
const text = completion.text
|
||||
?.replace(/^```json\n/, "")
|
||||
.replace(/\n```$/, "");
|
||||
|
||||
return text || "";
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import {
|
||||
} from "./memory.types";
|
||||
import { parse_vision_messages } from "../utils/memory";
|
||||
import { HistoryManager } from "../storage/base";
|
||||
import { captureClientEvent } from "../utils/telemetry";
|
||||
|
||||
export class Memory {
|
||||
private config: MemoryConfig;
|
||||
private customPrompt: string | undefined;
|
||||
@@ -45,6 +47,7 @@ export class Memory {
|
||||
private apiVersion: string;
|
||||
private graphMemory?: MemoryGraph;
|
||||
private enableGraph: boolean;
|
||||
telemetryId: string;
|
||||
|
||||
constructor(config: Partial<MemoryConfig> = {}) {
|
||||
// Merge and validate config
|
||||
@@ -85,11 +88,58 @@ export class Memory {
|
||||
this.collectionName = this.config.vectorStore.config.collectionName;
|
||||
this.apiVersion = this.config.version || "v1.0";
|
||||
this.enableGraph = this.config.enableGraph || false;
|
||||
this.telemetryId = "anonymous";
|
||||
|
||||
// Initialize graph memory if configured
|
||||
if (this.enableGraph && this.config.graphStore) {
|
||||
this.graphMemory = new MemoryGraph(this.config);
|
||||
}
|
||||
|
||||
// Initialize telemetry if vector store is initialized
|
||||
this._initializeTelemetry();
|
||||
}
|
||||
|
||||
private async _initializeTelemetry() {
|
||||
try {
|
||||
await this._getTelemetryId();
|
||||
|
||||
// Capture initialization event
|
||||
await captureClientEvent("init", this, {
|
||||
api_version: this.apiVersion,
|
||||
client_type: "Memory",
|
||||
collection_name: this.collectionName,
|
||||
enable_graph: this.enableGraph,
|
||||
});
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
private async _getTelemetryId() {
|
||||
try {
|
||||
if (
|
||||
!this.telemetryId ||
|
||||
this.telemetryId === "anonymous" ||
|
||||
this.telemetryId === "anonymous-supabase"
|
||||
) {
|
||||
this.telemetryId = await this.vectorStore.getUserId();
|
||||
}
|
||||
return this.telemetryId;
|
||||
} catch (error) {
|
||||
this.telemetryId = "anonymous";
|
||||
return this.telemetryId;
|
||||
}
|
||||
}
|
||||
|
||||
private async _captureEvent(methodName: string, additionalData = {}) {
|
||||
try {
|
||||
await this._getTelemetryId();
|
||||
await captureClientEvent(methodName, this, {
|
||||
...additionalData,
|
||||
api_version: this.apiVersion,
|
||||
collection_name: this.collectionName,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to capture ${methodName} event:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
static fromConfig(configDict: Record<string, any>): Memory {
|
||||
@@ -106,6 +156,12 @@ export class Memory {
|
||||
messages: string | Message[],
|
||||
config: AddMemoryOptions,
|
||||
): Promise<SearchResult> {
|
||||
await this._captureEvent("add", {
|
||||
message_count: Array.isArray(messages) ? messages.length : 1,
|
||||
has_metadata: !!config.metadata,
|
||||
has_filters: !!config.filters,
|
||||
infer: config.infer,
|
||||
});
|
||||
const {
|
||||
userId,
|
||||
agentId,
|
||||
@@ -341,6 +397,11 @@ export class Memory {
|
||||
query: string,
|
||||
config: SearchMemoryOptions,
|
||||
): Promise<SearchResult> {
|
||||
await this._captureEvent("search", {
|
||||
query_length: query.length,
|
||||
limit: config.limit,
|
||||
has_filters: !!config.filters,
|
||||
});
|
||||
const { userId, agentId, runId, limit = 100, filters = {} } = config;
|
||||
|
||||
if (userId) filters.userId = userId;
|
||||
@@ -402,12 +463,14 @@ export class Memory {
|
||||
}
|
||||
|
||||
async update(memoryId: string, data: string): Promise<{ message: string }> {
|
||||
await this._captureEvent("update", { memory_id: memoryId });
|
||||
const embedding = await this.embedder.embed(data);
|
||||
await this.updateMemory(memoryId, data, { [data]: embedding });
|
||||
return { message: "Memory updated successfully!" };
|
||||
}
|
||||
|
||||
async delete(memoryId: string): Promise<{ message: string }> {
|
||||
await this._captureEvent("delete", { memory_id: memoryId });
|
||||
await this.deleteMemory(memoryId);
|
||||
return { message: "Memory deleted successfully!" };
|
||||
}
|
||||
@@ -415,6 +478,11 @@ export class Memory {
|
||||
async deleteAll(
|
||||
config: DeleteAllMemoryOptions,
|
||||
): Promise<{ message: string }> {
|
||||
await this._captureEvent("delete_all", {
|
||||
has_user_id: !!config.userId,
|
||||
has_agent_id: !!config.agentId,
|
||||
has_run_id: !!config.runId,
|
||||
});
|
||||
const { userId, agentId, runId } = config;
|
||||
|
||||
const filters: SearchFilters = {};
|
||||
@@ -441,6 +509,7 @@ export class Memory {
|
||||
}
|
||||
|
||||
async reset(): Promise<void> {
|
||||
await this._captureEvent("reset");
|
||||
await this.db.reset();
|
||||
await this.vectorStore.deleteCol();
|
||||
if (this.graphMemory) {
|
||||
@@ -453,6 +522,12 @@ export class Memory {
|
||||
}
|
||||
|
||||
async getAll(config: GetAllMemoryOptions): Promise<SearchResult> {
|
||||
await this._captureEvent("get_all", {
|
||||
limit: config.limit,
|
||||
has_user_id: !!config.userId,
|
||||
has_agent_id: !!config.agentId,
|
||||
has_run_id: !!config.runId,
|
||||
});
|
||||
const { userId, agentId, runId, limit = 100 } = config;
|
||||
|
||||
const filters: SearchFilters = {};
|
||||
|
||||
98
mem0-ts/src/oss/src/utils/telemetry.ts
Normal file
98
mem0-ts/src/oss/src/utils/telemetry.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import type {
|
||||
TelemetryClient,
|
||||
TelemetryInstance,
|
||||
TelemetryEventData,
|
||||
} from "./telemetry.types";
|
||||
|
||||
let version = "2.1.15";
|
||||
|
||||
// Safely check for process.env in different environments
|
||||
let MEM0_TELEMETRY = true;
|
||||
try {
|
||||
MEM0_TELEMETRY = process?.env?.MEM0_TELEMETRY === "false" ? false : true;
|
||||
} catch (error) {}
|
||||
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
|
||||
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
|
||||
|
||||
class UnifiedTelemetry implements TelemetryClient {
|
||||
private apiKey: string;
|
||||
private host: string;
|
||||
|
||||
constructor(projectApiKey: string, host: string) {
|
||||
this.apiKey = projectApiKey;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
async captureEvent(distinctId: string, eventName: string, properties = {}) {
|
||||
if (!MEM0_TELEMETRY) return;
|
||||
|
||||
const eventProperties = {
|
||||
client_version: version,
|
||||
timestamp: new Date().toISOString(),
|
||||
...properties,
|
||||
$process_person_profile:
|
||||
distinctId === "anonymous" || distinctId === "anonymous-supabase"
|
||||
? false
|
||||
: true,
|
||||
$lib: "posthog-node",
|
||||
};
|
||||
|
||||
const payload = {
|
||||
api_key: this.apiKey,
|
||||
distinct_id: distinctId,
|
||||
event: eventName,
|
||||
properties: eventProperties,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(this.host, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Telemetry event capture failed:", await response.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Telemetry event capture failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async shutdown() {
|
||||
// No shutdown needed for direct API calls
|
||||
}
|
||||
}
|
||||
|
||||
const telemetry = new UnifiedTelemetry(POSTHOG_API_KEY, POSTHOG_HOST);
|
||||
|
||||
async function captureClientEvent(
|
||||
eventName: string,
|
||||
instance: TelemetryInstance,
|
||||
additionalData: Record<string, any> = {},
|
||||
) {
|
||||
if (!instance.telemetryId) {
|
||||
console.warn("No telemetry ID found for instance");
|
||||
return;
|
||||
}
|
||||
|
||||
const eventData: TelemetryEventData = {
|
||||
function: `${instance.constructor.name}`,
|
||||
method: eventName,
|
||||
api_host: instance.host,
|
||||
timestamp: new Date().toISOString(),
|
||||
client_version: version,
|
||||
client_source: "nodejs",
|
||||
...additionalData,
|
||||
};
|
||||
|
||||
await telemetry.captureEvent(
|
||||
instance.telemetryId,
|
||||
`mem0.${eventName}`,
|
||||
eventData,
|
||||
);
|
||||
}
|
||||
|
||||
export { telemetry, captureClientEvent };
|
||||
34
mem0-ts/src/oss/src/utils/telemetry.types.ts
Normal file
34
mem0-ts/src/oss/src/utils/telemetry.types.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export interface TelemetryClient {
|
||||
captureEvent(
|
||||
distinctId: string,
|
||||
eventName: string,
|
||||
properties?: Record<string, any>,
|
||||
): Promise<void>;
|
||||
shutdown(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface TelemetryInstance {
|
||||
telemetryId: string;
|
||||
constructor: {
|
||||
name: string;
|
||||
};
|
||||
host?: string;
|
||||
apiKey?: string;
|
||||
}
|
||||
|
||||
export interface TelemetryEventData {
|
||||
function: string;
|
||||
method: string;
|
||||
api_host?: string;
|
||||
timestamp?: string;
|
||||
client_source: "browser" | "nodejs";
|
||||
client_version: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface TelemetryOptions {
|
||||
enabled?: boolean;
|
||||
apiKey?: string;
|
||||
host?: string;
|
||||
version?: string;
|
||||
}
|
||||
@@ -23,4 +23,7 @@ export interface VectorStore {
|
||||
filters?: SearchFilters,
|
||||
limit?: number,
|
||||
): Promise<[VectorStoreResult[], number]>;
|
||||
getUserId(): Promise<string>;
|
||||
setUserId(userId: string): Promise<void>;
|
||||
initialize(): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,13 @@ export class MemoryVectorStore implements VectorStore {
|
||||
payload TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
await this.run(`
|
||||
CREATE TABLE IF NOT EXISTS memory_migrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL UNIQUE
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
private async run(sql: string, params: any[] = []): Promise<void> {
|
||||
@@ -201,4 +208,33 @@ export class MemoryVectorStore implements VectorStore {
|
||||
|
||||
return [results.slice(0, limit), results.length];
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
const row = await this.getOne(
|
||||
`SELECT user_id FROM memory_migrations LIMIT 1`,
|
||||
);
|
||||
if (row) {
|
||||
return row.user_id;
|
||||
}
|
||||
|
||||
// Generate a random user_id if none exists
|
||||
const randomUserId =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
await this.run(`INSERT INTO memory_migrations (user_id) VALUES (?)`, [
|
||||
randomUserId,
|
||||
]);
|
||||
return randomUserId;
|
||||
}
|
||||
|
||||
async setUserId(userId: string): Promise<void> {
|
||||
await this.run(`DELETE FROM memory_migrations`);
|
||||
await this.run(`INSERT INTO memory_migrations (user_id) VALUES (?)`, [
|
||||
userId,
|
||||
]);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
await this.init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@ export class PGVector implements VectorStore {
|
||||
private useDiskann: boolean;
|
||||
private useHnsw: boolean;
|
||||
private readonly dbName: string;
|
||||
private config: PGVectorConfig;
|
||||
|
||||
constructor(config: PGVectorConfig) {
|
||||
this.collectionName = config.collectionName;
|
||||
this.useDiskann = config.diskann || false;
|
||||
this.useHnsw = config.hnsw || false;
|
||||
this.dbName = config.dbname || "vector_store";
|
||||
this.config = config;
|
||||
|
||||
this.client = new Client({
|
||||
database: "postgres", // Initially connect to default postgres database
|
||||
@@ -33,14 +35,9 @@ export class PGVector implements VectorStore {
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
});
|
||||
|
||||
this.initialize(config, config.embeddingModelDims);
|
||||
}
|
||||
|
||||
private async initialize(
|
||||
config: PGVectorConfig,
|
||||
embeddingModelDims: number,
|
||||
): Promise<void> {
|
||||
async initialize(): Promise<void> {
|
||||
try {
|
||||
await this.client.connect();
|
||||
|
||||
@@ -56,20 +53,28 @@ export class PGVector implements VectorStore {
|
||||
// Connect to the target database
|
||||
this.client = new Client({
|
||||
database: this.dbName,
|
||||
user: config.user,
|
||||
password: config.password,
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
user: this.config.user,
|
||||
password: this.config.password,
|
||||
host: this.config.host,
|
||||
port: this.config.port,
|
||||
});
|
||||
await this.client.connect();
|
||||
|
||||
// Create vector extension
|
||||
await this.client.query("CREATE EXTENSION IF NOT EXISTS vector");
|
||||
|
||||
// Create memory_migrations table
|
||||
await this.client.query(`
|
||||
CREATE TABLE IF NOT EXISTS memory_migrations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id TEXT NOT NULL UNIQUE
|
||||
)
|
||||
`);
|
||||
|
||||
// Check if the collection exists
|
||||
const collections = await this.listCols();
|
||||
if (!collections.includes(this.collectionName)) {
|
||||
await this.createCol(embeddingModelDims);
|
||||
await this.createCol(this.config.embeddingModelDims);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during initialization:", error);
|
||||
@@ -296,4 +301,32 @@ export class PGVector implements VectorStore {
|
||||
async close(): Promise<void> {
|
||||
await this.client.end();
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
const result = await this.client.query(
|
||||
"SELECT user_id FROM memory_migrations LIMIT 1",
|
||||
);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
return result.rows[0].user_id;
|
||||
}
|
||||
|
||||
// Generate a random user_id if none exists
|
||||
const randomUserId =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
await this.client.query(
|
||||
"INSERT INTO memory_migrations (user_id) VALUES ($1)",
|
||||
[randomUserId],
|
||||
);
|
||||
return randomUserId;
|
||||
}
|
||||
|
||||
async setUserId(userId: string): Promise<void> {
|
||||
await this.client.query("DELETE FROM memory_migrations");
|
||||
await this.client.query(
|
||||
"INSERT INTO memory_migrations (user_id) VALUES ($1)",
|
||||
[userId],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,33 +13,7 @@ interface QdrantConfig extends VectorStoreConfig {
|
||||
onDisk?: boolean;
|
||||
collectionName: string;
|
||||
embeddingModelDims: number;
|
||||
}
|
||||
|
||||
type DistanceType = "Cosine" | "Euclid" | "Dot";
|
||||
|
||||
interface QdrantPoint {
|
||||
id: string | number;
|
||||
vector: { name: string; vector: number[] };
|
||||
payload?: Record<string, unknown> | { [key: string]: unknown } | null;
|
||||
shard_key?: string;
|
||||
version?: number;
|
||||
}
|
||||
|
||||
interface QdrantScoredPoint extends QdrantPoint {
|
||||
score: number;
|
||||
version: number;
|
||||
}
|
||||
|
||||
interface QdrantNamedVector {
|
||||
name: string;
|
||||
vector: number[];
|
||||
}
|
||||
|
||||
interface QdrantSearchRequest {
|
||||
vector: { name: string; vector: number[] };
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
filter?: QdrantFilter;
|
||||
dimension?: number;
|
||||
}
|
||||
|
||||
interface QdrantFilter {
|
||||
@@ -54,27 +28,10 @@ interface QdrantCondition {
|
||||
range?: { gte?: number; gt?: number; lte?: number; lt?: number };
|
||||
}
|
||||
|
||||
interface QdrantVectorParams {
|
||||
size: number;
|
||||
distance: "Cosine" | "Euclid" | "Dot" | "Manhattan";
|
||||
on_disk?: boolean;
|
||||
}
|
||||
|
||||
interface QdrantCollectionInfo {
|
||||
config?: {
|
||||
params?: {
|
||||
vectors?: {
|
||||
size: number;
|
||||
distance: "Cosine" | "Euclid" | "Dot" | "Manhattan";
|
||||
on_disk?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export class Qdrant implements VectorStore {
|
||||
private client: QdrantClient;
|
||||
private readonly collectionName: string;
|
||||
private dimension: number;
|
||||
|
||||
constructor(config: QdrantConfig) {
|
||||
if (config.client) {
|
||||
@@ -107,61 +64,8 @@ export class Qdrant implements VectorStore {
|
||||
}
|
||||
|
||||
this.collectionName = config.collectionName;
|
||||
this.createCol(config.embeddingModelDims, config.onDisk || false);
|
||||
}
|
||||
|
||||
private async createCol(
|
||||
vectorSize: number,
|
||||
onDisk: boolean,
|
||||
distance: DistanceType = "Cosine",
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Check if collection exists
|
||||
const collections = await this.client.getCollections();
|
||||
const exists = collections.collections.some(
|
||||
(col: { name: string }) => col.name === this.collectionName,
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
const vectorParams: QdrantVectorParams = {
|
||||
size: vectorSize,
|
||||
distance: distance as "Cosine" | "Euclid" | "Dot" | "Manhattan",
|
||||
on_disk: onDisk,
|
||||
};
|
||||
|
||||
try {
|
||||
await this.client.createCollection(this.collectionName, {
|
||||
vectors: vectorParams,
|
||||
});
|
||||
} catch (error: any) {
|
||||
// Handle case where collection was created between our check and create
|
||||
if (error?.status === 409) {
|
||||
// Collection already exists - verify it has the correct configuration
|
||||
const collectionInfo = (await this.client.getCollection(
|
||||
this.collectionName,
|
||||
)) as QdrantCollectionInfo;
|
||||
const vectorConfig = collectionInfo.config?.params?.vectors;
|
||||
|
||||
if (!vectorConfig || vectorConfig.size !== vectorSize) {
|
||||
throw new Error(
|
||||
`Collection ${this.collectionName} exists but has wrong configuration. ` +
|
||||
`Expected vector size: ${vectorSize}, got: ${vectorConfig?.size}`,
|
||||
);
|
||||
}
|
||||
// Collection exists with correct configuration - we can proceed
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error("Error creating/verifying collection:", error.message);
|
||||
} else {
|
||||
console.error("Error creating/verifying collection:", error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
this.dimension = config.dimension || 1536; // Default OpenAI dimension
|
||||
this.initialize().catch(console.error);
|
||||
}
|
||||
|
||||
private createFilter(filters?: SearchFilters): QdrantFilter | undefined {
|
||||
@@ -293,4 +197,158 @@ export class Qdrant implements VectorStore {
|
||||
|
||||
return [results, response.points.length];
|
||||
}
|
||||
|
||||
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 {
|
||||
// First check if the collection exists
|
||||
const collections = await this.client.getCollections();
|
||||
const userCollectionExists = collections.collections.some(
|
||||
(col: { name: string }) => col.name === "memory_migrations",
|
||||
);
|
||||
|
||||
if (!userCollectionExists) {
|
||||
// Create the collection if it doesn't exist
|
||||
await this.client.createCollection("memory_migrations", {
|
||||
vectors: {
|
||||
size: 1,
|
||||
distance: "Cosine",
|
||||
on_disk: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Now try to get the user ID
|
||||
const result = await this.client.scroll("memory_migrations", {
|
||||
limit: 1,
|
||||
with_payload: true,
|
||||
});
|
||||
|
||||
if (result.points.length > 0) {
|
||||
return result.points[0].payload?.user_id as string;
|
||||
}
|
||||
|
||||
// Generate a random user_id if none exists
|
||||
const randomUserId =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
|
||||
await this.client.upsert("memory_migrations", {
|
||||
points: [
|
||||
{
|
||||
id: this.generateUUID(),
|
||||
vector: [0],
|
||||
payload: { user_id: randomUserId },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return randomUserId;
|
||||
} catch (error) {
|
||||
console.error("Error getting user ID:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async setUserId(userId: string): Promise<void> {
|
||||
try {
|
||||
// Get existing point ID
|
||||
const result = await this.client.scroll("memory_migrations", {
|
||||
limit: 1,
|
||||
with_payload: true,
|
||||
});
|
||||
|
||||
const pointId =
|
||||
result.points.length > 0 ? result.points[0].id : this.generateUUID();
|
||||
|
||||
await this.client.upsert("memory_migrations", {
|
||||
points: [
|
||||
{
|
||||
id: pointId,
|
||||
vector: [0],
|
||||
payload: { user_id: userId },
|
||||
},
|
||||
],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error setting user ID:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
try {
|
||||
// Create collection if it doesn't exist
|
||||
const collections = await this.client.getCollections();
|
||||
const exists = collections.collections.some(
|
||||
(c) => c.name === this.collectionName,
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
try {
|
||||
await this.client.createCollection(this.collectionName, {
|
||||
vectors: {
|
||||
size: this.dimension,
|
||||
distance: "Cosine",
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
// Handle case where collection was created between our check and create
|
||||
if (error?.status === 409) {
|
||||
// Collection already exists - verify it has the correct configuration
|
||||
const collectionInfo = await this.client.getCollection(
|
||||
this.collectionName,
|
||||
);
|
||||
const vectorConfig = collectionInfo.config?.params?.vectors;
|
||||
|
||||
if (!vectorConfig || vectorConfig.size !== this.dimension) {
|
||||
throw new Error(
|
||||
`Collection ${this.collectionName} exists but has wrong configuration. ` +
|
||||
`Expected vector size: ${this.dimension}, got: ${vectorConfig?.size}`,
|
||||
);
|
||||
}
|
||||
// Collection exists with correct configuration - we can proceed
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create memory_migrations collection if it doesn't exist
|
||||
const userExists = collections.collections.some(
|
||||
(c) => c.name === "memory_migrations",
|
||||
);
|
||||
|
||||
if (!userExists) {
|
||||
try {
|
||||
await this.client.createCollection("memory_migrations", {
|
||||
vectors: {
|
||||
size: 1, // Minimal size since we only store user_id
|
||||
distance: "Cosine",
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
// Handle case where collection was created between our check and create
|
||||
if (error?.status === 409) {
|
||||
// Collection already exists - we can proceed
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error initializing Qdrant:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,57 +187,6 @@ export class RedisDB implements VectorStore {
|
||||
});
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
try {
|
||||
await this.client.connect();
|
||||
console.log("Connected to Redis");
|
||||
|
||||
// Check if Redis Stack modules are loaded
|
||||
const modulesResponse =
|
||||
(await this.client.moduleList()) as unknown as any[];
|
||||
|
||||
// Parse module list to find search module
|
||||
const hasSearch = modulesResponse.some((module: any[]) => {
|
||||
const moduleMap = new Map();
|
||||
for (let i = 0; i < module.length; i += 2) {
|
||||
moduleMap.set(module[i], module[i + 1]);
|
||||
}
|
||||
return moduleMap.get("name")?.toLowerCase() === "search";
|
||||
});
|
||||
|
||||
if (!hasSearch) {
|
||||
throw new Error(
|
||||
"RediSearch module is not loaded. Please ensure Redis Stack is properly installed and running.",
|
||||
);
|
||||
}
|
||||
|
||||
// Create index with retries
|
||||
let retries = 0;
|
||||
const maxRetries = 3;
|
||||
while (retries < maxRetries) {
|
||||
try {
|
||||
await this.createIndex();
|
||||
console.log("Redis index created successfully");
|
||||
break;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error creating index (attempt ${retries + 1}/${maxRetries}):`,
|
||||
error,
|
||||
);
|
||||
retries++;
|
||||
if (retries === maxRetries) {
|
||||
throw error;
|
||||
}
|
||||
// Wait before retrying
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during Redis initialization:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async createIndex(): Promise<void> {
|
||||
try {
|
||||
// Drop existing index if it exists
|
||||
@@ -290,6 +239,61 @@ export class RedisDB implements VectorStore {
|
||||
}
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
try {
|
||||
await this.client.connect();
|
||||
console.log("Connected to Redis");
|
||||
|
||||
// Check if Redis Stack modules are loaded
|
||||
const modulesResponse =
|
||||
(await this.client.moduleList()) as unknown as any[];
|
||||
|
||||
// Parse module list to find search module
|
||||
const hasSearch = modulesResponse.some((module: any[]) => {
|
||||
const moduleMap = new Map();
|
||||
for (let i = 0; i < module.length; i += 2) {
|
||||
moduleMap.set(module[i], module[i + 1]);
|
||||
}
|
||||
return moduleMap.get("name")?.toLowerCase() === "search";
|
||||
});
|
||||
|
||||
if (!hasSearch) {
|
||||
throw new Error(
|
||||
"RediSearch module is not loaded. Please ensure Redis Stack is properly installed and running.",
|
||||
);
|
||||
}
|
||||
|
||||
// Create index with retries
|
||||
let retries = 0;
|
||||
const maxRetries = 3;
|
||||
while (retries < maxRetries) {
|
||||
try {
|
||||
await this.createIndex();
|
||||
console.log("Redis index created successfully");
|
||||
break;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error creating index (attempt ${retries + 1}/${maxRetries}):`,
|
||||
error,
|
||||
);
|
||||
retries++;
|
||||
if (retries === maxRetries) {
|
||||
throw error;
|
||||
}
|
||||
// Wait before retrying
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error("Error initializing Redis:", error.message);
|
||||
} else {
|
||||
console.error("Error initializing Redis:", error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async insert(
|
||||
vectors: number[][],
|
||||
ids: string[],
|
||||
@@ -629,4 +633,35 @@ export class RedisDB implements VectorStore {
|
||||
async close(): Promise<void> {
|
||||
await this.client.quit();
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
try {
|
||||
// Check if the user ID exists in Redis
|
||||
const userId = await this.client.get("memory_migrations:1");
|
||||
if (userId) {
|
||||
return userId;
|
||||
}
|
||||
|
||||
// Generate a random user_id if none exists
|
||||
const randomUserId =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
|
||||
// Store the user ID
|
||||
await this.client.set("memory_migrations:1", randomUserId);
|
||||
return randomUserId;
|
||||
} catch (error) {
|
||||
console.error("Error getting user ID:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async setUserId(userId: string): Promise<void> {
|
||||
try {
|
||||
await this.client.set("memory_migrations:1", userId);
|
||||
} catch (error) {
|
||||
console.error("Error setting user ID:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@ create table if not exists memories (
|
||||
updated_at timestamp with time zone default timezone('utc', now())
|
||||
);
|
||||
|
||||
-- Create the memory migrations table
|
||||
create table if not exists memory_migrations (
|
||||
user_id text primary key,
|
||||
created_at timestamp with time zone default timezone('utc', now())
|
||||
);
|
||||
|
||||
-- Create the vector similarity search function
|
||||
create or replace function match_vectors(
|
||||
query_embedding vector(1536),
|
||||
@@ -93,7 +99,7 @@ export class SupabaseDB implements VectorStore {
|
||||
});
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
async initialize(): Promise<void> {
|
||||
try {
|
||||
// Verify table exists and vector operations work by attempting a test insert
|
||||
const testVector = Array(1536).fill(0);
|
||||
@@ -133,6 +139,12 @@ create table if not exists memories (
|
||||
updated_at timestamp with time zone default timezone('utc', now())
|
||||
);
|
||||
|
||||
-- Create the memory migrations table
|
||||
create table if not exists memory_migrations (
|
||||
user_id text primary key,
|
||||
created_at timestamp with time zone default timezone('utc', now())
|
||||
);
|
||||
|
||||
-- Create the vector similarity search function
|
||||
create or replace function match_vectors(
|
||||
query_embedding vector(1536),
|
||||
@@ -336,4 +348,74 @@ See the SQL migration instructions in the code comments.`,
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
try {
|
||||
// First check if the table exists
|
||||
const { data: tableExists } = await this.client
|
||||
.from("memory_migrations")
|
||||
.select("user_id")
|
||||
.limit(1);
|
||||
|
||||
if (!tableExists || tableExists.length === 0) {
|
||||
// Generate a random user_id
|
||||
const randomUserId =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
|
||||
// Insert the new user_id
|
||||
const { error: insertError } = await this.client
|
||||
.from("memory_migrations")
|
||||
.insert({ user_id: randomUserId });
|
||||
|
||||
if (insertError) throw insertError;
|
||||
return randomUserId;
|
||||
}
|
||||
|
||||
// Get the first user_id
|
||||
const { data, error } = await this.client
|
||||
.from("memory_migrations")
|
||||
.select("user_id")
|
||||
.limit(1);
|
||||
|
||||
if (error) throw error;
|
||||
if (!data || data.length === 0) {
|
||||
// Generate a random user_id if no data found
|
||||
const randomUserId =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
|
||||
const { error: insertError } = await this.client
|
||||
.from("memory_migrations")
|
||||
.insert({ user_id: randomUserId });
|
||||
|
||||
if (insertError) throw insertError;
|
||||
return randomUserId;
|
||||
}
|
||||
|
||||
return data[0].user_id;
|
||||
} catch (error) {
|
||||
console.error("Error getting user ID:", error);
|
||||
return "anonymous-supabase";
|
||||
}
|
||||
}
|
||||
|
||||
async setUserId(userId: string): Promise<void> {
|
||||
try {
|
||||
const { error: deleteError } = await this.client
|
||||
.from("memory_migrations")
|
||||
.delete()
|
||||
.neq("user_id", "");
|
||||
|
||||
if (deleteError) throw deleteError;
|
||||
|
||||
const { error: insertError } = await this.client
|
||||
.from("memory_migrations")
|
||||
.insert({ user_id: userId });
|
||||
|
||||
if (insertError) throw insertError;
|
||||
} catch (error) {
|
||||
console.error("Error setting user ID:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user