mem0ai
Version:
The Memory Layer For Your AI Apps
1,652 lines (1,629 loc) • 143 kB
JavaScript
// src/oss/src/memory/index.ts
import { v4 as uuidv43 } from "uuid";
import { createHash } from "crypto";
// src/oss/src/types/index.ts
import { z } from "zod";
var MemoryConfigSchema = z.object({
version: z.string().optional(),
embedder: z.object({
provider: z.string(),
config: z.object({
modelProperties: z.record(z.string(), z.any()).optional(),
apiKey: z.string().optional(),
model: z.union([z.string(), z.any()]).optional()
})
}),
vectorStore: z.object({
provider: z.string(),
config: z.object({
collectionName: z.string().optional(),
dimension: z.number().optional(),
client: z.any().optional()
}).passthrough()
}),
llm: z.object({
provider: z.string(),
config: z.object({
apiKey: z.string().optional(),
model: z.union([z.string(), z.any()]).optional(),
modelProperties: z.record(z.string(), z.any()).optional()
})
}),
historyDbPath: z.string().optional(),
customPrompt: z.string().optional(),
enableGraph: z.boolean().optional(),
graphStore: z.object({
provider: z.string(),
config: z.object({
url: z.string(),
username: z.string(),
password: z.string()
}),
llm: z.object({
provider: z.string(),
config: z.record(z.string(), z.any())
}).optional(),
customPrompt: z.string().optional()
}).optional(),
historyStore: z.object({
provider: z.string(),
config: z.record(z.string(), z.any())
}).optional(),
disableHistory: z.boolean().optional()
});
// src/oss/src/embeddings/openai.ts
import OpenAI from "openai";
var OpenAIEmbedder = class {
constructor(config) {
this.openai = new OpenAI({ apiKey: config.apiKey });
this.model = config.model || "text-embedding-3-small";
}
async embed(text) {
const response = await this.openai.embeddings.create({
model: this.model,
input: text
});
return response.data[0].embedding;
}
async embedBatch(texts) {
const response = await this.openai.embeddings.create({
model: this.model,
input: texts
});
return response.data.map((item) => item.embedding);
}
};
// src/oss/src/embeddings/ollama.ts
import { Ollama } from "ollama";
// src/oss/src/utils/logger.ts
var logger = {
info: (message) => console.log(`[INFO] ${message}`),
error: (message) => console.error(`[ERROR] ${message}`),
debug: (message) => console.debug(`[DEBUG] ${message}`),
warn: (message) => console.warn(`[WARN] ${message}`)
};
// src/oss/src/embeddings/ollama.ts
var OllamaEmbedder = class {
constructor(config) {
// Using this variable to avoid calling the Ollama server multiple times
this.initialized = false;
this.ollama = new Ollama({
host: config.url || "http://localhost:11434"
});
this.model = config.model || "nomic-embed-text:latest";
this.ensureModelExists().catch((err) => {
logger.error(`Error ensuring model exists: ${err}`);
});
}
async embed(text) {
try {
await this.ensureModelExists();
} catch (err) {
logger.error(`Error ensuring model exists: ${err}`);
}
const response = await this.ollama.embeddings({
model: this.model,
prompt: text
});
return response.embedding;
}
async embedBatch(texts) {
const response = await Promise.all(texts.map((text) => this.embed(text)));
return response;
}
async ensureModelExists() {
if (this.initialized) {
return true;
}
const local_models = await this.ollama.list();
if (!local_models.models.find((m) => m.name === this.model)) {
logger.info(`Pulling model ${this.model}...`);
await this.ollama.pull({ model: this.model });
}
this.initialized = true;
return true;
}
};
// src/oss/src/llms/openai.ts
import OpenAI2 from "openai";
var OpenAILLM = class {
constructor(config) {
this.openai = new OpenAI2({ apiKey: config.apiKey });
this.model = config.model || "gpt-4o-mini";
}
async generateResponse(messages, responseFormat, tools) {
const completion = await this.openai.chat.completions.create({
messages: messages.map((msg) => {
const role = msg.role;
return {
role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
};
}),
model: this.model,
response_format: responseFormat,
...tools && { tools, tool_choice: "auto" }
});
const response = completion.choices[0].message;
if (response.tool_calls) {
return {
content: response.content || "",
role: response.role,
toolCalls: response.tool_calls.map((call) => ({
name: call.function.name,
arguments: call.function.arguments
}))
};
}
return response.content || "";
}
async generateChat(messages) {
const completion = await this.openai.chat.completions.create({
messages: messages.map((msg) => {
const role = msg.role;
return {
role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
};
}),
model: this.model
});
const response = completion.choices[0].message;
return {
content: response.content || "",
role: response.role
};
}
};
// src/oss/src/llms/openai_structured.ts
import OpenAI3 from "openai";
var OpenAIStructuredLLM = class {
constructor(config) {
this.openai = new OpenAI3({ apiKey: config.apiKey });
this.model = config.model || "gpt-4-turbo-preview";
}
async generateResponse(messages, responseFormat, tools) {
const completion = await this.openai.chat.completions.create({
messages: messages.map((msg) => ({
role: msg.role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
})),
model: this.model,
...tools ? {
tools: tools.map((tool) => ({
type: "function",
function: {
name: tool.function.name,
description: tool.function.description,
parameters: tool.function.parameters
}
})),
tool_choice: "auto"
} : responseFormat ? {
response_format: {
type: responseFormat.type
}
} : {}
});
const response = completion.choices[0].message;
if (response.tool_calls) {
return {
content: response.content || "",
role: response.role,
toolCalls: response.tool_calls.map((call) => ({
name: call.function.name,
arguments: call.function.arguments
}))
};
}
return response.content || "";
}
async generateChat(messages) {
const completion = await this.openai.chat.completions.create({
messages: messages.map((msg) => ({
role: msg.role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
})),
model: this.model
});
const response = completion.choices[0].message;
return {
content: response.content || "",
role: response.role
};
}
};
// src/oss/src/llms/anthropic.ts
import Anthropic from "@anthropic-ai/sdk";
var AnthropicLLM = class {
constructor(config) {
const apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
throw new Error("Anthropic API key is required");
}
this.client = new Anthropic({ apiKey });
this.model = config.model || "claude-3-sonnet-20240229";
}
async generateResponse(messages, responseFormat) {
const systemMessage = messages.find((msg) => msg.role === "system");
const otherMessages = messages.filter((msg) => msg.role !== "system");
const response = await this.client.messages.create({
model: this.model,
messages: otherMessages.map((msg) => ({
role: msg.role,
content: typeof msg.content === "string" ? msg.content : msg.content.image_url.url
})),
system: typeof (systemMessage == null ? void 0 : systemMessage.content) === "string" ? systemMessage.content : void 0,
max_tokens: 4096
});
return response.content[0].text;
}
async generateChat(messages) {
const response = await this.generateResponse(messages);
return {
content: response,
role: "assistant"
};
}
};
// src/oss/src/llms/groq.ts
import { Groq } from "groq-sdk";
var GroqLLM = class {
constructor(config) {
const apiKey = config.apiKey || process.env.GROQ_API_KEY;
if (!apiKey) {
throw new Error("Groq API key is required");
}
this.client = new Groq({ apiKey });
this.model = config.model || "llama3-70b-8192";
}
async generateResponse(messages, responseFormat) {
const response = await this.client.chat.completions.create({
model: this.model,
messages: messages.map((msg) => ({
role: msg.role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
})),
response_format: responseFormat
});
return response.choices[0].message.content || "";
}
async generateChat(messages) {
const response = await this.client.chat.completions.create({
model: this.model,
messages: messages.map((msg) => ({
role: msg.role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
}))
});
const message = response.choices[0].message;
return {
content: message.content || "",
role: message.role
};
}
};
// src/oss/src/llms/mistral.ts
import { Mistral } from "@mistralai/mistralai";
var MistralLLM = class {
constructor(config) {
if (!config.apiKey) {
throw new Error("Mistral API key is required");
}
this.client = new Mistral({
apiKey: config.apiKey
});
this.model = config.model || "mistral-tiny-latest";
}
// Helper function to convert content to string
contentToString(content) {
if (typeof content === "string") {
return content;
}
if (Array.isArray(content)) {
return content.map((chunk) => {
if (chunk.type === "text") {
return chunk.text;
} else {
return JSON.stringify(chunk);
}
}).join("");
}
return String(content || "");
}
async generateResponse(messages, responseFormat, tools) {
const response = await this.client.chat.complete({
model: this.model,
messages: messages.map((msg) => ({
role: msg.role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
})),
...tools && { tools },
...responseFormat && { response_format: responseFormat }
});
if (!response || !response.choices || response.choices.length === 0) {
return "";
}
const message = response.choices[0].message;
if (!message) {
return "";
}
if (message.toolCalls && message.toolCalls.length > 0) {
return {
content: this.contentToString(message.content),
role: message.role || "assistant",
toolCalls: message.toolCalls.map((call) => ({
name: call.function.name,
arguments: typeof call.function.arguments === "string" ? call.function.arguments : JSON.stringify(call.function.arguments)
}))
};
}
return this.contentToString(message.content);
}
async generateChat(messages) {
const formattedMessages = messages.map((msg) => ({
role: msg.role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
}));
const response = await this.client.chat.complete({
model: this.model,
messages: formattedMessages
});
if (!response || !response.choices || response.choices.length === 0) {
return {
content: "",
role: "assistant"
};
}
const message = response.choices[0].message;
return {
content: this.contentToString(message.content),
role: message.role || "assistant"
};
}
};
// src/oss/src/vector_stores/memory.ts
import sqlite3 from "sqlite3";
import path from "path";
var MemoryVectorStore = class {
constructor(config) {
this.dimension = config.dimension || 1536;
this.dbPath = path.join(process.cwd(), "vector_store.db");
if (config.dbPath) {
this.dbPath = config.dbPath;
}
this.db = new sqlite3.Database(this.dbPath);
this.init().catch(console.error);
}
async init() {
await this.run(`
CREATE TABLE IF NOT EXISTS vectors (
id TEXT PRIMARY KEY,
vector BLOB NOT NULL,
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
)
`);
}
async run(sql, params = []) {
return new Promise((resolve, reject) => {
this.db.run(sql, params, (err) => {
if (err) reject(err);
else resolve();
});
});
}
async all(sql, params = []) {
return new Promise((resolve, reject) => {
this.db.all(sql, params, (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
}
async getOne(sql, params = []) {
return new Promise((resolve, reject) => {
this.db.get(sql, params, (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
}
cosineSimilarity(a, b) {
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
filterVector(vector, filters) {
if (!filters) return true;
return Object.entries(filters).every(
([key, value]) => vector.payload[key] === value
);
}
async insert(vectors, ids, payloads) {
for (let i = 0; i < vectors.length; i++) {
if (vectors[i].length !== this.dimension) {
throw new Error(
`Vector dimension mismatch. Expected ${this.dimension}, got ${vectors[i].length}`
);
}
const vectorBuffer = Buffer.from(new Float32Array(vectors[i]).buffer);
await this.run(
`INSERT OR REPLACE INTO vectors (id, vector, payload) VALUES (?, ?, ?)`,
[ids[i], vectorBuffer, JSON.stringify(payloads[i])]
);
}
}
async search(query, limit = 10, filters) {
if (query.length !== this.dimension) {
throw new Error(
`Query dimension mismatch. Expected ${this.dimension}, got ${query.length}`
);
}
const rows = await this.all(`SELECT * FROM vectors`);
const results = [];
for (const row of rows) {
const vector = new Float32Array(row.vector.buffer);
const payload = JSON.parse(row.payload);
const memoryVector = {
id: row.id,
vector: Array.from(vector),
payload
};
if (this.filterVector(memoryVector, filters)) {
const score = this.cosineSimilarity(query, Array.from(vector));
results.push({
id: memoryVector.id,
payload: memoryVector.payload,
score
});
}
}
results.sort((a, b) => (b.score || 0) - (a.score || 0));
return results.slice(0, limit);
}
async get(vectorId) {
const row = await this.getOne(`SELECT * FROM vectors WHERE id = ?`, [
vectorId
]);
if (!row) return null;
const payload = JSON.parse(row.payload);
return {
id: row.id,
payload
};
}
async update(vectorId, vector, payload) {
if (vector.length !== this.dimension) {
throw new Error(
`Vector dimension mismatch. Expected ${this.dimension}, got ${vector.length}`
);
}
const vectorBuffer = Buffer.from(new Float32Array(vector).buffer);
await this.run(`UPDATE vectors SET vector = ?, payload = ? WHERE id = ?`, [
vectorBuffer,
JSON.stringify(payload),
vectorId
]);
}
async delete(vectorId) {
await this.run(`DELETE FROM vectors WHERE id = ?`, [vectorId]);
}
async deleteCol() {
await this.run(`DROP TABLE IF EXISTS vectors`);
await this.init();
}
async list(filters, limit = 100) {
const rows = await this.all(`SELECT * FROM vectors`);
const results = [];
for (const row of rows) {
const payload = JSON.parse(row.payload);
const memoryVector = {
id: row.id,
vector: Array.from(new Float32Array(row.vector.buffer)),
payload
};
if (this.filterVector(memoryVector, filters)) {
results.push({
id: memoryVector.id,
payload: memoryVector.payload
});
}
}
return [results.slice(0, limit), results.length];
}
async getUserId() {
const row = await this.getOne(
`SELECT user_id FROM memory_migrations LIMIT 1`
);
if (row) {
return row.user_id;
}
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) {
await this.run(`DELETE FROM memory_migrations`);
await this.run(`INSERT INTO memory_migrations (user_id) VALUES (?)`, [
userId
]);
}
async initialize() {
await this.init();
}
};
// src/oss/src/vector_stores/qdrant.ts
import { QdrantClient } from "@qdrant/js-client-rest";
import * as fs from "fs";
var Qdrant = class {
constructor(config) {
if (config.client) {
this.client = config.client;
} else {
const params = {};
if (config.apiKey) {
params.apiKey = config.apiKey;
}
if (config.url) {
params.url = config.url;
}
if (config.host && config.port) {
params.host = config.host;
params.port = config.port;
}
if (!Object.keys(params).length) {
params.path = config.path;
if (!config.onDisk && config.path) {
if (fs.existsSync(config.path) && fs.statSync(config.path).isDirectory()) {
fs.rmSync(config.path, { recursive: true });
}
}
}
this.client = new QdrantClient(params);
}
this.collectionName = config.collectionName;
this.dimension = config.dimension || 1536;
this.initialize().catch(console.error);
}
createFilter(filters) {
if (!filters) return void 0;
const conditions = [];
for (const [key, value] of Object.entries(filters)) {
if (typeof value === "object" && value !== null && "gte" in value && "lte" in value) {
conditions.push({
key,
range: {
gte: value.gte,
lte: value.lte
}
});
} else {
conditions.push({
key,
match: {
value
}
});
}
}
return conditions.length ? { must: conditions } : void 0;
}
async insert(vectors, ids, payloads) {
const points = vectors.map((vector, idx) => ({
id: ids[idx],
vector,
payload: payloads[idx] || {}
}));
await this.client.upsert(this.collectionName, {
points
});
}
async search(query, limit = 5, filters) {
const queryFilter = this.createFilter(filters);
const results = await this.client.search(this.collectionName, {
vector: query,
filter: queryFilter,
limit
});
return results.map((hit) => ({
id: String(hit.id),
payload: hit.payload || {},
score: hit.score
}));
}
async get(vectorId) {
const results = await this.client.retrieve(this.collectionName, {
ids: [vectorId],
with_payload: true
});
if (!results.length) return null;
return {
id: vectorId,
payload: results[0].payload || {}
};
}
async update(vectorId, vector, payload) {
const point = {
id: vectorId,
vector,
payload
};
await this.client.upsert(this.collectionName, {
points: [point]
});
}
async delete(vectorId) {
await this.client.delete(this.collectionName, {
points: [vectorId]
});
}
async deleteCol() {
await this.client.deleteCollection(this.collectionName);
}
async list(filters, limit = 100) {
const scrollRequest = {
limit,
filter: this.createFilter(filters),
with_payload: true,
with_vectors: false
};
const response = await this.client.scroll(
this.collectionName,
scrollRequest
);
const results = response.points.map((point) => ({
id: String(point.id),
payload: point.payload || {}
}));
return [results, response.points.length];
}
generateUUID() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function(c) {
const r = Math.random() * 16 | 0;
const v = c === "x" ? r : r & 3 | 8;
return v.toString(16);
}
);
}
async getUserId() {
var _a2;
try {
const collections = await this.client.getCollections();
const userCollectionExists = collections.collections.some(
(col) => col.name === "memory_migrations"
);
if (!userCollectionExists) {
await this.client.createCollection("memory_migrations", {
vectors: {
size: 1,
distance: "Cosine",
on_disk: false
}
});
}
const result = await this.client.scroll("memory_migrations", {
limit: 1,
with_payload: true
});
if (result.points.length > 0) {
return (_a2 = result.points[0].payload) == null ? void 0 : _a2.user_id;
}
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) {
try {
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() {
var _a2, _b;
try {
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) {
if ((error == null ? void 0 : error.status) === 409) {
const collectionInfo = await this.client.getCollection(
this.collectionName
);
const vectorConfig = (_b = (_a2 = collectionInfo.config) == null ? void 0 : _a2.params) == null ? void 0 : _b.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 == null ? void 0 : vectorConfig.size}`
);
}
} else {
throw error;
}
}
}
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) {
if ((error == null ? void 0 : error.status) === 409) {
} else {
throw error;
}
}
}
} catch (error) {
console.error("Error initializing Qdrant:", error);
throw error;
}
}
};
// src/oss/src/vector_stores/redis.ts
import { createClient } from "redis";
var DEFAULT_FIELDS = [
{ name: "memory_id", type: "tag" },
{ name: "hash", type: "tag" },
{ name: "agent_id", type: "tag" },
{ name: "run_id", type: "tag" },
{ name: "user_id", type: "tag" },
{ name: "memory", type: "text" },
{ name: "metadata", type: "text" },
{ name: "created_at", type: "numeric" },
{ name: "updated_at", type: "numeric" },
{
name: "embedding",
type: "vector",
attrs: {
algorithm: "flat",
distance_metric: "cosine",
datatype: "float32",
dims: 0
// Will be set in constructor
}
}
];
var EXCLUDED_KEYS = /* @__PURE__ */ new Set([
"user_id",
"agent_id",
"run_id",
"hash",
"data",
"created_at",
"updated_at"
]);
function toSnakeCase(obj) {
if (typeof obj !== "object" || obj === null) return obj;
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`),
value
])
);
}
function toCamelCase(obj) {
if (typeof obj !== "object" || obj === null) return obj;
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
value
])
);
}
var RedisDB = class {
constructor(config) {
this.indexName = config.collectionName;
this.indexPrefix = `mem0:${config.collectionName}`;
this.schema = {
index: {
name: this.indexName,
prefix: this.indexPrefix
},
fields: DEFAULT_FIELDS.map((field) => {
if (field.name === "embedding" && field.attrs) {
return {
...field,
attrs: {
...field.attrs,
dims: config.embeddingModelDims
}
};
}
return field;
})
};
this.client = createClient({
url: config.redisUrl,
username: config.username,
password: config.password,
socket: {
reconnectStrategy: (retries) => {
if (retries > 10) {
console.error("Max reconnection attempts reached");
return new Error("Max reconnection attempts reached");
}
return Math.min(retries * 100, 3e3);
}
}
});
this.client.on("error", (err) => console.error("Redis Client Error:", err));
this.client.on("connect", () => console.log("Redis Client Connected"));
this.initialize().catch((err) => {
console.error("Failed to initialize Redis:", err);
throw err;
});
}
async createIndex() {
try {
try {
await this.client.ft.dropIndex(this.indexName);
} catch (error) {
}
const schema = {};
for (const field of this.schema.fields) {
if (field.type === "vector") {
schema[field.name] = {
type: "VECTOR",
ALGORITHM: "FLAT",
TYPE: "FLOAT32",
DIM: field.attrs.dims,
DISTANCE_METRIC: "COSINE",
INITIAL_CAP: 1e3
};
} else if (field.type === "numeric") {
schema[field.name] = {
type: "NUMERIC",
SORTABLE: true
};
} else if (field.type === "tag") {
schema[field.name] = {
type: "TAG",
SEPARATOR: "|"
};
} else if (field.type === "text") {
schema[field.name] = {
type: "TEXT",
WEIGHT: 1
};
}
}
await this.client.ft.create(this.indexName, schema, {
ON: "HASH",
PREFIX: this.indexPrefix + ":",
STOPWORDS: []
});
} catch (error) {
console.error("Error creating Redis index:", error);
throw error;
}
}
async initialize() {
try {
await this.client.connect();
console.log("Connected to Redis");
const modulesResponse = await this.client.moduleList();
const hasSearch = modulesResponse.some((module) => {
var _a2;
const moduleMap = /* @__PURE__ */ new Map();
for (let i = 0; i < module.length; i += 2) {
moduleMap.set(module[i], module[i + 1]);
}
return ((_a2 = moduleMap.get("name")) == null ? void 0 : _a2.toLowerCase()) === "search";
});
if (!hasSearch) {
throw new Error(
"RediSearch module is not loaded. Please ensure Redis Stack is properly installed and running."
);
}
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;
}
await new Promise((resolve) => setTimeout(resolve, 1e3));
}
}
} 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, ids, payloads) {
const data = vectors.map((vector, idx) => {
const payload = toSnakeCase(payloads[idx]);
const id = ids[idx];
const entry = {
memory_id: id,
hash: payload.hash,
memory: payload.data,
created_at: new Date(payload.created_at).getTime(),
embedding: new Float32Array(vector).buffer
};
["agent_id", "run_id", "user_id"].forEach((field) => {
if (field in payload) {
entry[field] = payload[field];
}
});
entry.metadata = JSON.stringify(
Object.fromEntries(
Object.entries(payload).filter(([key]) => !EXCLUDED_KEYS.has(key))
)
);
return entry;
});
try {
await Promise.all(
data.map(
(entry) => this.client.hSet(`${this.indexPrefix}:${entry.memory_id}`, {
...entry,
embedding: Buffer.from(entry.embedding)
})
)
);
} catch (error) {
console.error("Error during vector insert:", error);
throw error;
}
}
async search(query, limit = 5, filters) {
const snakeFilters = filters ? toSnakeCase(filters) : void 0;
const filterExpr = snakeFilters ? Object.entries(snakeFilters).filter(([_, value]) => value !== null).map(([key, value]) => `@${key}:{${value}}`).join(" ") : "*";
const queryVector = new Float32Array(query).buffer;
const searchOptions = {
PARAMS: {
vec: Buffer.from(queryVector)
},
RETURN: [
"memory_id",
"hash",
"agent_id",
"run_id",
"user_id",
"memory",
"metadata",
"created_at",
"__vector_score"
],
SORTBY: "__vector_score",
DIALECT: 2,
LIMIT: {
from: 0,
size: limit
}
};
try {
const results = await this.client.ft.search(
this.indexName,
`${filterExpr} =>[KNN ${limit} @embedding $vec AS __vector_score]`,
searchOptions
);
return results.documents.map((doc) => {
var _a2;
const resultPayload = {
hash: doc.value.hash,
data: doc.value.memory,
created_at: new Date(parseInt(doc.value.created_at)).toISOString(),
...doc.value.updated_at && {
updated_at: new Date(parseInt(doc.value.updated_at)).toISOString()
},
...doc.value.agent_id && { agent_id: doc.value.agent_id },
...doc.value.run_id && { run_id: doc.value.run_id },
...doc.value.user_id && { user_id: doc.value.user_id },
...JSON.parse(doc.value.metadata || "{}")
};
return {
id: doc.value.memory_id,
payload: toCamelCase(resultPayload),
score: (_a2 = Number(doc.value.__vector_score)) != null ? _a2 : 0
};
});
} catch (error) {
console.error("Error during vector search:", error);
throw error;
}
}
async get(vectorId) {
try {
const exists = await this.client.exists(
`${this.indexPrefix}:${vectorId}`
);
if (!exists) {
console.warn(`Memory with ID ${vectorId} does not exist`);
return null;
}
const result = await this.client.hGetAll(
`${this.indexPrefix}:${vectorId}`
);
if (!Object.keys(result).length) return null;
const doc = {
memory_id: result.memory_id,
hash: result.hash,
memory: result.memory,
created_at: result.created_at,
updated_at: result.updated_at,
agent_id: result.agent_id,
run_id: result.run_id,
user_id: result.user_id,
metadata: result.metadata
};
let created_at;
try {
if (!result.created_at) {
created_at = /* @__PURE__ */ new Date();
} else {
const timestamp = Number(result.created_at);
if (timestamp.toString().length === 10) {
created_at = new Date(timestamp * 1e3);
} else {
created_at = new Date(timestamp);
}
if (isNaN(created_at.getTime())) {
console.warn(
`Invalid created_at timestamp: ${result.created_at}, using current date`
);
created_at = /* @__PURE__ */ new Date();
}
}
} catch (error) {
console.warn(
`Error parsing created_at timestamp: ${result.created_at}, using current date`
);
created_at = /* @__PURE__ */ new Date();
}
let updated_at;
try {
if (result.updated_at) {
const timestamp = Number(result.updated_at);
if (timestamp.toString().length === 10) {
updated_at = new Date(timestamp * 1e3);
} else {
updated_at = new Date(timestamp);
}
if (isNaN(updated_at.getTime())) {
console.warn(
`Invalid updated_at timestamp: ${result.updated_at}, setting to undefined`
);
updated_at = void 0;
}
}
} catch (error) {
console.warn(
`Error parsing updated_at timestamp: ${result.updated_at}, setting to undefined`
);
updated_at = void 0;
}
const payload = {
hash: doc.hash,
data: doc.memory,
created_at: created_at.toISOString(),
...updated_at && { updated_at: updated_at.toISOString() },
...doc.agent_id && { agent_id: doc.agent_id },
...doc.run_id && { run_id: doc.run_id },
...doc.user_id && { user_id: doc.user_id },
...JSON.parse(doc.metadata || "{}")
};
return {
id: vectorId,
payload
};
} catch (error) {
console.error("Error getting vector:", error);
throw error;
}
}
async update(vectorId, vector, payload) {
const snakePayload = toSnakeCase(payload);
const entry = {
memory_id: vectorId,
hash: snakePayload.hash,
memory: snakePayload.data,
created_at: new Date(snakePayload.created_at).getTime(),
updated_at: new Date(snakePayload.updated_at).getTime(),
embedding: Buffer.from(new Float32Array(vector).buffer)
};
["agent_id", "run_id", "user_id"].forEach((field) => {
if (field in snakePayload) {
entry[field] = snakePayload[field];
}
});
entry.metadata = JSON.stringify(
Object.fromEntries(
Object.entries(snakePayload).filter(([key]) => !EXCLUDED_KEYS.has(key))
)
);
try {
await this.client.hSet(`${this.indexPrefix}:${vectorId}`, entry);
} catch (error) {
console.error("Error during vector update:", error);
throw error;
}
}
async delete(vectorId) {
try {
const key = `${this.indexPrefix}:${vectorId}`;
const exists = await this.client.exists(key);
if (!exists) {
console.warn(`Memory with ID ${vectorId} does not exist`);
return;
}
const result = await this.client.del(key);
if (!result) {
throw new Error(`Failed to delete memory with ID ${vectorId}`);
}
console.log(`Successfully deleted memory with ID ${vectorId}`);
} catch (error) {
console.error("Error deleting memory:", error);
throw error;
}
}
async deleteCol() {
await this.client.ft.dropIndex(this.indexName);
}
async list(filters, limit = 100) {
const snakeFilters = filters ? toSnakeCase(filters) : void 0;
const filterExpr = snakeFilters ? Object.entries(snakeFilters).filter(([_, value]) => value !== null).map(([key, value]) => `@${key}:{${value}}`).join(" ") : "*";
const searchOptions = {
SORTBY: "created_at",
SORTDIR: "DESC",
LIMIT: {
from: 0,
size: limit
}
};
const results = await this.client.ft.search(
this.indexName,
filterExpr,
searchOptions
);
const items = results.documents.map((doc) => ({
id: doc.value.memory_id,
payload: toCamelCase({
hash: doc.value.hash,
data: doc.value.memory,
created_at: new Date(parseInt(doc.value.created_at)).toISOString(),
...doc.value.updated_at && {
updated_at: new Date(parseInt(doc.value.updated_at)).toISOString()
},
...doc.value.agent_id && { agent_id: doc.value.agent_id },
...doc.value.run_id && { run_id: doc.value.run_id },
...doc.value.user_id && { user_id: doc.value.user_id },
...JSON.parse(doc.value.metadata || "{}")
})
}));
return [items, results.total];
}
async close() {
await this.client.quit();
}
async getUserId() {
try {
const userId = await this.client.get("memory_migrations:1");
if (userId) {
return userId;
}
const randomUserId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
await this.client.set("memory_migrations:1", randomUserId);
return randomUserId;
} catch (error) {
console.error("Error getting user ID:", error);
throw error;
}
}
async setUserId(userId) {
try {
await this.client.set("memory_migrations:1", userId);
} catch (error) {
console.error("Error setting user ID:", error);
throw error;
}
}
};
// src/oss/src/llms/ollama.ts
import { Ollama as Ollama2 } from "ollama";
var OllamaLLM = class {
constructor(config) {
// Using this variable to avoid calling the Ollama server multiple times
this.initialized = false;
var _a2;
this.ollama = new Ollama2({
host: ((_a2 = config.config) == null ? void 0 : _a2.url) || "http://localhost:11434"
});
this.model = config.model || "llama3.1:8b";
this.ensureModelExists().catch((err) => {
logger.error(`Error ensuring model exists: ${err}`);
});
}
async generateResponse(messages, responseFormat, tools) {
try {
await this.ensureModelExists();
} catch (err) {
logger.error(`Error ensuring model exists: ${err}`);
}
const completion = await this.ollama.chat({
model: this.model,
messages: messages.map((msg) => {
const role = msg.role;
return {
role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
};
}),
...(responseFormat == null ? void 0 : responseFormat.type) === "json_object" && { format: "json" },
...tools && { tools, tool_choice: "auto" }
});
const response = completion.message;
if (response.tool_calls) {
return {
content: response.content || "",
role: response.role,
toolCalls: response.tool_calls.map((call) => ({
name: call.function.name,
arguments: JSON.stringify(call.function.arguments)
}))
};
}
return response.content || "";
}
async generateChat(messages) {
try {
await this.ensureModelExists();
} catch (err) {
logger.error(`Error ensuring model exists: ${err}`);
}
const completion = await this.ollama.chat({
messages: messages.map((msg) => {
const role = msg.role;
return {
role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
};
}),
model: this.model
});
const response = completion.message;
return {
content: response.content || "",
role: response.role
};
}
async ensureModelExists() {
if (this.initialized) {
return true;
}
const local_models = await this.ollama.list();
if (!local_models.models.find((m) => m.name === this.model)) {
logger.info(`Pulling model ${this.model}...`);
await this.ollama.pull({ model: this.model });
}
this.initialized = true;
return true;
}
};
// src/oss/src/vector_stores/supabase.ts
import { createClient as createClient2 } from "@supabase/supabase-js";
var SupabaseDB = class {
constructor(config) {
this.client = createClient2(config.supabaseUrl, config.supabaseKey);
this.tableName = config.tableName;
this.embeddingColumnName = config.embeddingColumnName || "embedding";
this.metadataColumnName = config.metadataColumnName || "metadata";
this.initialize().catch((err) => {
console.error("Failed to initialize Supabase:", err);
throw err;
});
}
async initialize() {
try {
const testVector = Array(1536).fill(0);
try {
await this.client.from(this.tableName).delete().eq("id", "test_vector");
} catch (e) {
}
const { error: insertError } = await this.client.from(this.tableName).insert({
id: "test_vector",
[this.embeddingColumnName]: testVector,
[this.metadataColumnName]: {}
}).select();
if (insertError && insertError.code !== "23505") {
console.error("Test insert error:", insertError);
throw new Error(
`Vector operations failed. Please ensure:
1. The vector extension is enabled
2. The table "${this.tableName}" exists with correct schema
3. The match_vectors function is created
RUN THE FOLLOWING SQL IN YOUR SUPABASE SQL EDITOR:
-- Enable the vector extension
create extension if not exists vector;
-- Create the memories table
create table if not exists memories (
id text primary key,
embedding vector(1536),
metadata jsonb,
created_at timestamp with time zone default timezone('utc', now()),
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),
match_count int,
filter jsonb default '{}'::jsonb
)
returns table (
id text,
similarity float,
metadata jsonb
)
language plpgsql
as $$
begin
return query
select
t.id::text,
1 - (t.embedding <=> query_embedding) as similarity,
t.metadata
from memories t
where case
when filter::text = '{}'::text then true
else t.metadata @> filter
end
order by t.embedding <=> query_embedding
limit match_count;
end;
$$;
See the SQL migration instructions in the code comments.`
);
}
try {
await this.client.from(this.tableName).delete().eq("id", "test_vector");
} catch (e) {
}
console.log("Connected to Supabase successfully");
} catch (error) {
console.error("Error during Supabase initialization:", error);
throw error;
}
}
async insert(vectors, ids, payloads) {
try {
const data = vectors.map((vector, idx) => ({
id: ids[idx],
[this.embeddingColumnName]: vector,
[this.metadataColumnName]: {
...payloads[idx],
created_at: (/* @__PURE__ */ new Date()).toISOString()
}
}));
const { error } = await this.client.from(this.tableName).insert(data);
if (error) throw error;
} catch (error) {
console.error("Error during vector insert:", error);
throw error;
}
}
async search(query, limit = 5, filters) {
try {
const rpcQuery = {
query_embedding: query,
match_count: limit
};
if (filters) {
rpcQuery.filter = filters;
}
const { data, error } = await this.client.rpc("match_vectors", rpcQuery);
if (error) throw error;
if (!data) return [];
const results = data;
return results.map((result) => ({
id: result.id,
payload: result.metadata,
score: result.similarity
}));
} catch (error) {
console.error("Error during vector search:", error);
throw error;
}
}
async get(vectorId) {
try {
const { data, error } = await this.client.from(this.tableName).select("*").eq("id", vectorId).single();
if (error) throw error;
if (!data) return null;
return {
id: data.id,
payload: data[this.metadataColumnName]
};
} catch (error) {
console.error("Error getting vector:", error);
throw error;
}
}
async update(vectorId, vector, payload) {
try {
const { error } = await this.client.from(this.tableName).update({
[this.embeddingColumnName]: vector,
[this.metadataColumnName]: {
...payload,
updated_at: (/* @__PURE__ */ new Date()).toISOString()
}
}).eq("id", vectorId);
if (error) throw error;
} catch (error) {
console.error("Error during vector update:", error);
throw error;
}
}
async delete(vectorId) {
try {
const { error } = await this.client.from(this.tableName).delete().eq("id", vectorId);
if (error) throw error;
} catch (error) {
console.error("Error deleting vector:", error);
throw error;
}
}
async deleteCol() {
try {
const { error } = await this.client.from(this.tableName).delete().neq("id", "");
if (error) throw error;
} catch (error) {
console.error("Error deleting collection:", error);
throw error;
}
}
async list(filters, limit = 100) {
try {
let query = this.client.from(this.tableName).select("*", { count: "exact" }).limit(limit);
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
query = query.eq(`${this.metadataColumnName}->>${key}`, value);
});
}
const { data, error, count } = await query;
if (error) throw error;
const results = data.map((item) => ({
id: item.id,
payload: item[this.metadataColumnName]
}));
return [results, count || 0];
} catch (error) {
console.error("Error listing vectors:", error);
throw error;
}
}
async getUserId() {
try {
const { data: tableExists } = await this.client.from("memory_migrations").select("user_id").limit(1);
if (!tableExists || tableExists.length === 0) {
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;
}
const { data, error } = await this.client.from("memory_migrations").select("user_id").limit(1);
if (error) throw error;
if (!data || data.length === 0) {
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) {
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);
}
}
};
// src/oss/src/storage/SQLiteManager.ts
import sqlite32 from "sqlite3";
var SQLiteManager = class {
constructor(dbPath) {
this.db = new sqlite32.Database(dbPath);
this.init().catch(console.error);
}
async init() {
await this.run(`
CREATE TABLE IF NOT EXISTS memory_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
memory_id TEXT NOT NULL,
previous_value TEXT,
new_value TEXT,
action TEXT NOT NULL,
created_at TEXT,
updated_at TEXT,
is_deleted INTEGER DEFAULT 0
)
`);
}
async run(sql, params = []) {
return new Promise((resolve, reject) => {
this.db.run(sql, params, (err) => {
if (err) reject(err);