Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b480c71f0f | |||
| 309c8c18a6 | |||
| f4d8647264 | |||
| f95c4cbbe5 | |||
| 00c7cc432c | |||
| 91abc03880 | |||
| 9100e95175 | |||
| cdb8dcdb9e | |||
| 2a79add7a5 |
@@ -13,7 +13,8 @@ install:
|
||||
install_all:
|
||||
poetry install
|
||||
poetry run pip install groq together boto3 litellm ollama chromadb weaviate weaviate-client sentence_transformers vertexai \
|
||||
google-generativeai elasticsearch opensearch-py vecs pinecone pinecone-text faiss-cpu langchain-community
|
||||
google-generativeai elasticsearch opensearch-py vecs pinecone pinecone-text faiss-cpu langchain-community \
|
||||
upstash-vector azure-search-documents
|
||||
|
||||
# Format code with ruff
|
||||
format:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'Get Memory Export'
|
||||
openapi: get /v1/exports/
|
||||
openapi: post /v1/exports/get
|
||||
---
|
||||
|
||||
Retrieve the latest structured memory export after submitting an export job. You can filter the export by `user_id`, `run_id`, `session_id`, or `app_id` to get the most recent export matching your filters.
|
||||
@@ -90,6 +90,11 @@ mode: "wide"
|
||||
|
||||
<Tab title="TypeScript">
|
||||
|
||||
<Update label="2025-04-09" description="v2.1.15">
|
||||
**Improvements:**
|
||||
- **Client:** Added support for Mem0 to work with Chrome Extensions
|
||||
</Update>
|
||||
|
||||
<Update label="2025-04-01" description="v2.1.14">
|
||||
**New Features:**
|
||||
- **Mastra Example:** Added Mastra example
|
||||
|
||||
@@ -8,7 +8,7 @@ iconType: "solid"
|
||||
|
||||
The `config` is defined as an object with two main keys:
|
||||
- `vector_store`: Specifies the vector database provider and its configuration
|
||||
- `provider`: The name of the vector database (e.g., "chroma", "pgvector", "qdrant", "milvus","azure_ai_search", "vertex_ai_vector_search")
|
||||
- `provider`: The name of the vector database (e.g., "chroma", "pgvector", "qdrant", "milvus", "upstash_vector", "azure_ai_search", "vertex_ai_vector_search")
|
||||
- `config`: A nested dictionary containing provider-specific settings
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
[Upstash Vector](https://upstash.com/docs/vector) is a serverless vector database with built-in embedding models.
|
||||
|
||||
### Usage with Upstash embeddings
|
||||
|
||||
You can enable the built-in embedding models by setting `enable_embeddings` to `True`. This allows you to use Upstash's embedding models for vectorization.
|
||||
|
||||
```python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["UPSTASH_VECTOR_REST_URL"] = "..."
|
||||
os.environ["UPSTASH_VECTOR_REST_TOKEN"] = "..."
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "upstash_vector",
|
||||
"enable_embeddings": True,
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
m.add("Likes to play cricket on weekends", user_id="alice", metadata={"category": "hobbies"})
|
||||
```
|
||||
|
||||
<Note>
|
||||
Setting `enable_embeddings` to `True` will bypass any external embedding provider you have configured.
|
||||
</Note>
|
||||
|
||||
### Usage with external embedding providers
|
||||
|
||||
```python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
os.environ["OPENAI_API_KEY"] = "..."
|
||||
os.environ["UPSTASH_VECTOR_REST_URL"] = "..."
|
||||
os.environ["UPSTASH_VECTOR_REST_TOKEN"] = "..."
|
||||
|
||||
config = {
|
||||
"vector_store": {
|
||||
"provider": "upstash_vector",
|
||||
},
|
||||
"embedder": {
|
||||
"provider": "openai",
|
||||
"config": {
|
||||
"model": "text-embedding-3-large"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
m = Memory.from_config(config)
|
||||
m.add("Likes to play cricket on weekends", user_id="alice", metadata={"category": "hobbies"})
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
Here are the parameters available for configuring Upstash Vector:
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| ------------------- | ---------------------------------- | ------------- |
|
||||
| `url` | URL for the Upstash Vector index | `None` |
|
||||
| `token` | Token for the Upstash Vector index | `None` |
|
||||
| `client` | An `upstash_vector.Index` instance | `None` |
|
||||
| `collection_name` | The default namespace used | `""` |
|
||||
| `enable_embeddings` | Whether to use Upstash embeddings | `False` |
|
||||
|
||||
<Note>
|
||||
When `url` and `token` are not provided, the `UPSTASH_VECTOR_REST_URL` and
|
||||
`UPSTASH_VECTOR_REST_TOKEN` environment variables are used.
|
||||
</Note>
|
||||
@@ -18,6 +18,7 @@ See the list of supported vector databases below.
|
||||
<Card title="Qdrant" href="/components/vectordbs/dbs/qdrant"></Card>
|
||||
<Card title="Chroma" href="/components/vectordbs/dbs/chroma"></Card>
|
||||
<Card title="Pgvector" href="/components/vectordbs/dbs/pgvector"></Card>
|
||||
<Card title="Upstash Vector" href="/components/vectordbs/dbs/upstash-vector"></Card>
|
||||
<Card title="Milvus" href="/components/vectordbs/dbs/milvus"></Card>
|
||||
<Card title="Pinecone" href="/components/vectordbs/dbs/pinecone"></Card>
|
||||
<Card title="Azure" href="/components/vectordbs/dbs/azure"></Card>
|
||||
|
||||
@@ -202,7 +202,6 @@
|
||||
"examples/mem0-agentic-tool",
|
||||
"examples/openai-inbuilt-tools",
|
||||
"examples/mem0-openai-voice-demo",
|
||||
"examples/mem0-livekit-voice-agent",
|
||||
"examples/email_processing"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -91,10 +91,17 @@ export_instructions = """
|
||||
5. Clearly distinguish between factual statements and inferences
|
||||
"""
|
||||
|
||||
# For create operation, using only user_id filter as requested
|
||||
filters = {
|
||||
"AND": [
|
||||
{"user_id": "alex"}
|
||||
]
|
||||
}
|
||||
|
||||
response = client.create_memory_export(
|
||||
schema=json_schema,
|
||||
user_id="alice",
|
||||
export_instructions=export_instructions
|
||||
filters=filters,
|
||||
export_instructions=export_instructions # Optional
|
||||
)
|
||||
|
||||
print(response)
|
||||
@@ -127,7 +134,15 @@ Once the export job is complete, you can retrieve the structured data:
|
||||
<CodeGroup>
|
||||
|
||||
```python Python
|
||||
response = client.get_memory_export(user_id="alice")
|
||||
# Corrected date range (assuming you meant July 10 to July 20)
|
||||
filters = {
|
||||
"AND": [
|
||||
{"created_at": {"gte": "2024-07-10", "lte": "2024-07-20"}},
|
||||
{"user_id": "alex"}
|
||||
]
|
||||
}
|
||||
|
||||
response = client.get_memory_export(filters=filters)
|
||||
print(response)
|
||||
```
|
||||
|
||||
@@ -157,6 +172,7 @@ You can apply various filters to customize which memories are included in the ex
|
||||
- `agent_id`: Filter memories by specific agent
|
||||
- `run_id`: Filter memories by specific run
|
||||
- `session_id`: Filter memories by specific session
|
||||
- `created_at`: Filter memories by date
|
||||
|
||||
<Note>
|
||||
The export process may take some time to complete, especially when dealing with a large number of memories or complex schemas.
|
||||
|
||||
+135
-155
@@ -379,7 +379,118 @@
|
||||
}
|
||||
},
|
||||
"/v1/exports/": {
|
||||
"get": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"exports"
|
||||
],
|
||||
"summary": "Create an export job with schema",
|
||||
"description": "Create a structured export of memories based on a provided schema.",
|
||||
"operationId": "exports_create",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["schema"],
|
||||
"properties": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"description": "Schema definition for the export"
|
||||
},
|
||||
"filters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string"},
|
||||
"agent_id": {"type": "string"},
|
||||
"app_id": {"type": "string"},
|
||||
"run_id": {"type": "string"}
|
||||
},
|
||||
"description": "Filters to apply while exporting memories. Available fields are: user_id, agent_id, app_id, run_id."
|
||||
},
|
||||
"org_id": {
|
||||
"type": "string",
|
||||
"description": "Filter exports by organization ID"
|
||||
},
|
||||
"project_id": {
|
||||
"type": "string",
|
||||
"description": "Filter exports by project ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Export created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Memory export request received. The export will be ready in a few seconds."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
},
|
||||
"required": ["message", "id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Schema is required and must be a valid object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\njson_schema = {pydantic_json_schema}\nfilters = {\n \"AND\": [\n {\"user_id\": \"alex\"}\n ]\n}\n\nresponse = client.create_memory_export(\n schema=json_schema,\n filters=filters\n)\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst jsonSchema = {pydantic_json_schema};\nconst filters = {\n AND: [\n {user_id: 'alex'}\n ]\n};\n\nclient.createMemoryExport({\n schema: jsonSchema,\n filters: filters\n})\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request POST \\\n --url 'https://api.mem0.ai/v1/exports/' \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"schema\": {pydantic_json_schema},\n \"filters\": {\n \"AND\": [\n {\"user_id\": \"alex\"}\n ]\n }\n }'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
"source": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\turl := \"https://api.mem0.ai/v1/exports/\"\n\n\tfilters := map[string]interface{}{\n\t\t\"AND\": []map[string]interface{}{\n\t\t\t{\"user_id\": \"alex\"},\n\t\t},\n\t}\n\n\tdata := map[string]interface{}{\n\t\t\"schema\": map[string]interface{}{}, // Your schema here\n\t\t\"filters\": filters,\n\t}\n\n\tjsonData, _ := json.Marshal(data)\n\n\treq, _ := http.NewRequest(\"POST\", url, bytes.NewBuffer(jsonData))\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(string(body))\n}"
|
||||
},
|
||||
{
|
||||
"lang": "PHP",
|
||||
"source": "<?php\n\n$curl = curl_init();\n\n$filters = [\n 'AND' => [\n ['user_id' => 'alex']\n ]\n];\n\n$data = array(\n \"schema\" => array(), // Your schema here\n \"filters\" => $filters\n);\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/v1/exports/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"POST\",\n CURLOPT_POSTFIELDS => json_encode($data),\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
},
|
||||
{
|
||||
"lang": "Java",
|
||||
"source": "import com.mashape.unirest.http.HttpResponse;\nimport com.mashape.unirest.http.JsonNode;\nimport com.mashape.unirest.http.Unirest;\nimport org.json.JSONObject;\nimport org.json.JSONArray;\n\nJSONObject filters = new JSONObject()\n .put(\"AND\", new JSONArray()\n .put(new JSONObject().put(\"user_id\", \"alex\")));\n\nJSONObject data = new JSONObject()\n .put(\"schema\", new JSONObject()) // Your schema here\n .put(\"filters\", filters);\n\nHttpResponse<JsonNode> response = Unirest.post(\"https://api.mem0.ai/v1/exports/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(data.toString())\n .asJson();"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/exports/get": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"exports"
|
||||
],
|
||||
@@ -388,36 +499,20 @@
|
||||
"operationId": "exports_list",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "user_id",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter exports by user ID"
|
||||
},
|
||||
{
|
||||
"name": "run_id",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter exports by run ID"
|
||||
},
|
||||
{
|
||||
"name": "session_id",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter exports by session ID"
|
||||
},
|
||||
{
|
||||
"name": "app_id",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Filter exports by app ID"
|
||||
"name": "filters",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_id": {"type": "string"},
|
||||
"agent_id": {"type": "string"},
|
||||
"app_id": {"type": "string"},
|
||||
"run_id": {"type": "string"},
|
||||
"created_at": {"type": "string"},
|
||||
"updated_at": {"type": "string"}
|
||||
},
|
||||
"description": "Filters to apply while exporting memories. Available fields are: user_id, agent_id, app_id, run_id, created_at, updated_at."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "org_id",
|
||||
@@ -484,144 +579,29 @@
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"project_id\")\n\nresponse = client.get_memory_export(user_id=\"your_user_id\")\nprint(response)"
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"project_id\")\n\nfilters = {\n \"AND\": [\n {\"created_at\": {\"gte\": \"2024-07-10\", \"lte\": \"2024-07-20\"}},\n {\"user_id\": \"alex\"}\n ]\n}\n\nresponse = client.get_memory_export(filters=filters)\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\n// Get memory export\nclient.getMemoryExport({ user_id: \"your_user_id\" })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst filters = {\n AND: [\n {created_at: {gte: \"2024-07-10\", lte: \"2024-07-20\"}},\n {user_id: \"alex\"}\n ]\n};\n\n// Get memory export\nclient.getMemoryExport({ filters })\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request GET \\\n --url 'https://api.mem0.ai/v1/exports/?user_id=your_user_id' \\\n --header 'Authorization: Token <api-key>'"
|
||||
"source": "curl --request GET \\\n --url 'https://api.mem0.ai/v1/exports/?filters={\"AND\":[{\"created_at\":{\"gte\":\"2024-07-10\",\"lte\":\"2024-07-20\"}},{\"user_id\":\"alex\"}]}' \\\n --header 'Authorization: Token <api-key>'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\turl := \"https://api.mem0.ai/v1/exports/?user_id=your_user_id\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(string(body))\n}"
|
||||
"source": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\tfilters := `{\"AND\":[{\"created_at\":{\"gte\":\"2024-07-10\",\"lte\":\"2024-07-20\"}},{\"user_id\":\"alex\"}]}`\n\turl := fmt.Sprintf(\"https://api.mem0.ai/v1/exports/?filters=%s\", filters)\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(string(body))\n}"
|
||||
},
|
||||
{
|
||||
"lang": "PHP",
|
||||
"source": "<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/v1/exports/?user_id=your_user_id\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"GET\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
"source": "<?php\n\n$curl = curl_init();\n\n$filters = urlencode('{\"AND\":[{\"created_at\":{\"gte\":\"2024-07-10\",\"lte\":\"2024-07-20\"}},{\"user_id\":\"alex\"}]}');\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/v1/exports/?filters=\" . $filters,\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"GET\",\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
},
|
||||
{
|
||||
"lang": "Java",
|
||||
"source": "HttpResponse<String> response = Unirest.get(\"https://api.mem0.ai/v1/exports/?user_id=your_user_id\")\n .header(\"Authorization\", \"Token <api-key>\")\n .asString();"
|
||||
"source": "String filters = \"{\\\"AND\\\":[{\\\"created_at\\\":{\\\"gte\\\":\\\"2024-07-10\\\",\\\"lte\\\":\\\"2024-07-20\\\"}},{\\\"user_id\\\":\\\"alex\\\"}]}\";\n\nHttpResponse<String> response = Unirest.get(\"https://api.mem0.ai/v1/exports/?filters=\" + filters)\n .header(\"Authorization\", \"Token <api-key>\")\n .asString();"
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"exports"
|
||||
],
|
||||
"summary": "Create an export job with schema",
|
||||
"description": "Create a structured export of memories based on a provided schema.",
|
||||
"operationId": "exports_create",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["schema"],
|
||||
"properties": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"description": "Schema definition for the export"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"description": "Filter exports by user ID"
|
||||
},
|
||||
"run_id": {
|
||||
"type": "string",
|
||||
"description": "Filter exports by run ID"
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"description": "Filter exports by session ID"
|
||||
},
|
||||
"app_id": {
|
||||
"type": "string",
|
||||
"description": "Filter exports by app ID"
|
||||
},
|
||||
"org_id": {
|
||||
"type": "string",
|
||||
"description": "Filter exports by organization ID"
|
||||
},
|
||||
"project_id": {
|
||||
"type": "string",
|
||||
"description": "Filter exports by project ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Export created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Memory export request received. The export will be ready in a few seconds."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"example": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
},
|
||||
"required": ["message", "id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Schema is required and must be a valid object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-code-samples": [
|
||||
{
|
||||
"lang": "Python",
|
||||
"source": "# To use the Python SDK, install the package:\n# pip install mem0ai\n\nfrom mem0 import MemoryClient\n\nclient = MemoryClient(api_key=\"your_api_key\", org_id=\"your_org_id\", project_id=\"your_project_id\")\n\njson_schema = {pydantic_json_schema}\n\nresponse = client.create_memory_export(\n schema=json_schema,\n user_id=\"your_user_id\"\n)\nprint(response)"
|
||||
},
|
||||
{
|
||||
"lang": "JavaScript",
|
||||
"source": "// To use the JavaScript SDK, install the package:\n// npm i mem0ai\n\nimport MemoryClient from 'mem0ai';\nconst client = new MemoryClient({ apiKey: \"your-api-key\" });\n\nconst jsonSchema = {pydantic_json_schema};\n\nclient.createMemoryExport({\n schema: jsonSchema,\n user_id: \"your_user_id\"\n})\n .then(result => console.log(result))\n .catch(error => console.error(error));"
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl --request POST \\\n --url 'https://api.mem0.ai/v1/exports/' \\\n --header 'Authorization: Token <api-key>' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"schema\": {pydantic_json_schema},\n \"user_id\": \"your_user_id\"\n }'"
|
||||
},
|
||||
{
|
||||
"lang": "Go",
|
||||
"source": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\turl := \"https://api.mem0.ai/v1/exports/\"\n\n\tdata := map[string]interface{}{\n\t\t\"schema\": map[string]interface{}{}, // Your schema here\n\t\t\"user_id\": \"user123\",\n\t}\n\n\tjsonData, _ := json.Marshal(data)\n\n\treq, _ := http.NewRequest(\"POST\", url, bytes.NewBuffer(jsonData))\n\n\treq.Header.Add(\"Authorization\", \"Token <api-key>\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(string(body))\n}"
|
||||
},
|
||||
{
|
||||
"lang": "PHP",
|
||||
"source": "<?php\n\n$curl = curl_init();\n\n$data = array(\n \"schema\" => array(), // Your schema here\n \"user_id\" => \"your_user_id\"\n);\n\ncurl_setopt_array($curl, [\n CURLOPT_URL => \"https://api.mem0.ai/v1/exports/\",\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_ENCODING => \"\",\n CURLOPT_MAXREDIRS => 10,\n CURLOPT_TIMEOUT => 30,\n CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n CURLOPT_CUSTOMREQUEST => \"POST\",\n CURLOPT_POSTFIELDS => json_encode($data),\n CURLOPT_HTTPHEADER => [\n \"Authorization: Token <api-key>\",\n \"Content-Type: application/json\"\n ],\n]);\n\n$response = curl_exec($curl);\n$err = curl_error($curl);\n\ncurl_close($curl);\n\nif ($err) {\n echo \"cURL Error #:\" . $err;\n} else {\n echo $response;\n}"
|
||||
},
|
||||
{
|
||||
"lang": "Java",
|
||||
"source": "import com.mashape.unirest.http.HttpResponse;\nimport com.mashape.unirest.http.JsonNode;\nimport com.mashape.unirest.http.Unirest;\nimport org.json.JSONObject;\n\nJSONObject data = new JSONObject()\n .put(\"schema\", new JSONObject()) // Your schema here\n .put(\"user_id\", \"your_user_id\");\n\nHttpResponse<JsonNode> response = Unirest.post(\"https://api.mem0.ai/v1/exports/\")\n .header(\"Authorization\", \"Token <api-key>\")\n .header(\"Content-Type\", \"application/json\")\n .body(data.toString())\n .asJson();"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/memories/": {
|
||||
@@ -4190,7 +4170,7 @@
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl -X PUT \"https://api.mem0.ai/v1/memories/batch/\" \\\n -H \"Authorization: Token your-api-key\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"memories\": [\n {\n \"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\",\n \"text\": \"Watches football\"\n },\n {\n \"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\",\n \"text\": \"Likes to travel\"\n }\n ]\n }'"
|
||||
"source": "curl -X PUT \"https://api.mem0.ai/v1/batch/\" \\\n -H \"Authorization: Token your-api-key\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"memories\": [\n {\n \"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\",\n \"text\": \"Watches football\"\n },\n {\n \"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\",\n \"text\": \"Likes to travel\"\n }\n ]\n }'"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -4267,7 +4247,7 @@
|
||||
},
|
||||
{
|
||||
"lang": "cURL",
|
||||
"source": "curl -X DELETE \"https://api.mem0.ai/v1/memories/batch/\" \\\n -H \"Authorization: Token your-api-key\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"delete_memories\": [\n {\n \"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\"\n },\n {\n \"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\"\n }\n ]\n }'"
|
||||
"source": "curl -X DELETE \"https://api.mem0.ai/v1/batch/\" \\\n -H \"Authorization: Token your-api-key\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"memories\": [\n {\n \"memory_id\": \"285ed74b-6e05-4043-b16b-3abd5b533496\"\n },\n {\n \"memory_id\": \"2c9bd859-d1b7-4d33-a6b8-94e0147c4f07\"\n }\n ]\n }'"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mem0ai",
|
||||
"version": "2.1.13",
|
||||
"version": "2.1.15",
|
||||
"description": "The Memory Layer For Your AI Apps",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -93,12 +93,14 @@
|
||||
"dependencies": {
|
||||
"axios": "1.7.7",
|
||||
"openai": "4.28.0",
|
||||
"redis": "^4.6.13",
|
||||
"uuid": "9.0.1",
|
||||
"zod": "3.22.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": "0.18.0",
|
||||
"@qdrant/js-client-rest": "1.13.0",
|
||||
"@google/genai": "^0.7.0",
|
||||
"@supabase/supabase-js": "^2.49.1",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/pg": "8.11.0",
|
||||
|
||||
Generated
+187
@@ -10,6 +10,9 @@ importers:
|
||||
"@anthropic-ai/sdk":
|
||||
specifier: 0.18.0
|
||||
version: 0.18.0(encoding@0.1.13)
|
||||
"@google/genai":
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0(encoding@0.1.13)
|
||||
"@qdrant/js-client-rest":
|
||||
specifier: 1.13.0
|
||||
version: 1.13.0(typescript@5.5.4)
|
||||
@@ -615,6 +618,13 @@ packages:
|
||||
integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==,
|
||||
}
|
||||
|
||||
"@google/genai@0.7.0":
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-r+Fwj/emnXZN5R+4JCxDXboY4AGTmTn7+Wnori5dgyJiStP0P82f9YYL0CVsCnDIumNY2i0UIcZ1zGZdtHJ34w==,
|
||||
}
|
||||
engines: { node: ">=18.0.0" }
|
||||
|
||||
"@isaacs/cliui@8.0.2":
|
||||
resolution:
|
||||
{
|
||||
@@ -1313,6 +1323,13 @@ packages:
|
||||
}
|
||||
engines: { node: ">= 6.0.0" }
|
||||
|
||||
agent-base@7.1.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==,
|
||||
}
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
agentkeepalive@4.6.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -1484,6 +1501,12 @@ packages:
|
||||
integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==,
|
||||
}
|
||||
|
||||
bignumber.js@9.2.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-JocpCSOixzy5XFJi2ub6IMmV/G9i8Lrm2lZvwBv9xPdglmZM0ufDVBbjbrfU/zuLvBfD7Bv2eYxz9i+OHTgkew==,
|
||||
}
|
||||
|
||||
binary-extensions@2.3.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -1543,6 +1566,12 @@ packages:
|
||||
integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==,
|
||||
}
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==,
|
||||
}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution:
|
||||
{
|
||||
@@ -1916,6 +1945,12 @@ packages:
|
||||
integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==,
|
||||
}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==,
|
||||
}
|
||||
|
||||
ejs@3.1.10:
|
||||
resolution:
|
||||
{
|
||||
@@ -2073,6 +2108,12 @@ packages:
|
||||
}
|
||||
engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 }
|
||||
|
||||
extend@3.0.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==,
|
||||
}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
resolution:
|
||||
{
|
||||
@@ -2222,6 +2263,20 @@ packages:
|
||||
engines: { node: ^12.13.0 || ^14.15.0 || >=16.0.0 }
|
||||
deprecated: This package is no longer supported.
|
||||
|
||||
gaxios@6.7.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==,
|
||||
}
|
||||
engines: { node: ">=14" }
|
||||
|
||||
gcp-metadata@6.1.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==,
|
||||
}
|
||||
engines: { node: ">=14" }
|
||||
|
||||
generic-pool@3.9.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2305,6 +2360,20 @@ packages:
|
||||
}
|
||||
engines: { node: ">=4" }
|
||||
|
||||
google-auth-library@9.15.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==,
|
||||
}
|
||||
engines: { node: ">=14" }
|
||||
|
||||
google-logging-utils@0.0.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==,
|
||||
}
|
||||
engines: { node: ">=14" }
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2324,6 +2393,13 @@ packages:
|
||||
integrity: sha512-Cdgjh4YoSBE2X4S9sxPGXaAy1dlN4bRtAaDZ3cnq+XsxhhN9WSBeHF64l7LWwuD5ntmw7YC5Vf4Ff1oHCg1LOg==,
|
||||
}
|
||||
|
||||
gtoken@7.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==,
|
||||
}
|
||||
engines: { node: ">=14.0.0" }
|
||||
|
||||
has-flag@3.0.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2398,6 +2474,13 @@ packages:
|
||||
}
|
||||
engines: { node: ">= 6" }
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==,
|
||||
}
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
human-signals@2.1.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2861,6 +2944,12 @@ packages:
|
||||
engines: { node: ">=6" }
|
||||
hasBin: true
|
||||
|
||||
json-bigint@1.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==,
|
||||
}
|
||||
|
||||
json-parse-even-better-errors@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -2882,6 +2971,18 @@ packages:
|
||||
engines: { node: ">=6" }
|
||||
hasBin: true
|
||||
|
||||
jwa@2.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==,
|
||||
}
|
||||
|
||||
jws@4.0.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==,
|
||||
}
|
||||
|
||||
kleur@3.0.3:
|
||||
resolution:
|
||||
{
|
||||
@@ -4913,6 +5014,16 @@ snapshots:
|
||||
"@gar/promisify@1.1.3":
|
||||
optional: true
|
||||
|
||||
"@google/genai@0.7.0(encoding@0.1.13)":
|
||||
dependencies:
|
||||
google-auth-library: 9.15.1(encoding@0.1.13)
|
||||
ws: 8.18.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- encoding
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
"@isaacs/cliui@8.0.2":
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
@@ -5406,6 +5517,8 @@ snapshots:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
agent-base@7.1.3: {}
|
||||
|
||||
agentkeepalive@4.6.0:
|
||||
dependencies:
|
||||
humanize-ms: 1.2.1
|
||||
@@ -5527,6 +5640,8 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
bignumber.js@9.2.0: {}
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
bindings@1.5.0:
|
||||
@@ -5567,6 +5682,8 @@ snapshots:
|
||||
dependencies:
|
||||
node-int64: 0.4.0
|
||||
|
||||
buffer-equal-constant-time@1.0.1: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer-writer@2.0.0: {}
|
||||
@@ -5766,6 +5883,10 @@ snapshots:
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
ejs@3.1.10:
|
||||
dependencies:
|
||||
jake: 10.9.2
|
||||
@@ -5872,6 +5993,8 @@ snapshots:
|
||||
jest-message-util: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
dependencies:
|
||||
"@nodelib/fs.stat": 2.0.5
|
||||
@@ -5962,6 +6085,26 @@ snapshots:
|
||||
wide-align: 1.1.5
|
||||
optional: true
|
||||
|
||||
gaxios@6.7.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
is-stream: 2.0.1
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
uuid: 9.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
gcp-metadata@6.1.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
gaxios: 6.7.1(encoding@0.1.13)
|
||||
google-logging-utils: 0.0.2
|
||||
json-bigint: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
generic-pool@3.9.0: {}
|
||||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
@@ -6016,6 +6159,20 @@ snapshots:
|
||||
|
||||
globals@11.12.0: {}
|
||||
|
||||
google-auth-library@9.15.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
gaxios: 6.7.1(encoding@0.1.13)
|
||||
gcp-metadata: 6.1.1(encoding@0.1.13)
|
||||
gtoken: 7.1.0(encoding@0.1.13)
|
||||
jws: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
google-logging-utils@0.0.2: {}
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
@@ -6034,6 +6191,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
gtoken@7.1.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
gaxios: 6.7.1(encoding@0.1.13)
|
||||
jws: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
has-flag@3.0.0: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
@@ -6077,6 +6242,13 @@ snapshots:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.3
|
||||
debug: 4.4.0(supports-color@5.5.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
humanize-ms@1.2.1:
|
||||
@@ -6528,12 +6700,27 @@ snapshots:
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
|
||||
json-bigint@1.0.0:
|
||||
dependencies:
|
||||
bignumber.js: 9.2.0
|
||||
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
|
||||
json-parse-even-better-errors@3.0.2: {}
|
||||
|
||||
json5@2.2.3: {}
|
||||
|
||||
jwa@2.0.0:
|
||||
dependencies:
|
||||
buffer-equal-constant-time: 1.0.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
jws@4.0.0:
|
||||
dependencies:
|
||||
jwa: 2.0.0
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
kleur@3.0.3: {}
|
||||
|
||||
kolorist@1.8.0: {}
|
||||
|
||||
@@ -4,7 +4,10 @@ import type { TelemetryClient, TelemetryOptions } from "./telemetry.types";
|
||||
let version = "2.1.12";
|
||||
|
||||
// Safely check for process.env in different environments
|
||||
const MEM0_TELEMETRY = process?.env?.MEM0_TELEMETRY === "false" ? false : true;
|
||||
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/";
|
||||
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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 };
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -448,7 +448,7 @@ class MemoryClient:
|
||||
Returns:
|
||||
Dict containing the exported data
|
||||
"""
|
||||
response = self.client.get("/v1/exports/", params=self._prepare_params(kwargs))
|
||||
response = self.client.post("/v1/exports/get/", json=self._prepare_params(kwargs))
|
||||
response.raise_for_status()
|
||||
capture_client_event("client.get_memory_export", self, {"keys": list(kwargs.keys())})
|
||||
return response.json()
|
||||
@@ -930,7 +930,7 @@ class AsyncMemoryClient:
|
||||
Returns:
|
||||
Dict containing the exported data
|
||||
"""
|
||||
response = await self.async_client.get("/v1/exports/", params=self._prepare_params(kwargs))
|
||||
response = await self.async_client.post("/v1/exports/get/", json=self._prepare_params(kwargs))
|
||||
response.raise_for_status()
|
||||
capture_client_event("async_client.get_memory_export", self.sync_client, {"keys": list(kwargs.keys())})
|
||||
return response.json()
|
||||
|
||||
@@ -12,6 +12,7 @@ class FAISSConfig(BaseModel):
|
||||
normalize_L2: bool = Field(
|
||||
False, description="Whether to normalize L2 vectors (only applicable for euclidean distance)"
|
||||
)
|
||||
embedding_model_dims: int = Field(1536, description="Dimension of the embedding vector")
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
from typing import Any, ClassVar, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
try:
|
||||
from upstash_vector import Index
|
||||
except ImportError:
|
||||
raise ImportError("The 'upstash_vector' library is required. Please install it using 'pip install upstash_vector'.")
|
||||
|
||||
|
||||
class UpstashVectorConfig(BaseModel):
|
||||
Index: ClassVar[type] = Index
|
||||
|
||||
url: Optional[str] = Field(None, description="URL for Upstash Vector index")
|
||||
token: Optional[str] = Field(None, description="Token for Upstash Vector index")
|
||||
client: Optional[Index] = Field(None, description="Existing `upstash_vector.Index` client instance")
|
||||
collection_name: str = Field("mem0", description="Namespace to use for the index")
|
||||
enable_embeddings: bool = Field(
|
||||
False, description="Whether to use built-in upstash embeddings or not. Default is True."
|
||||
)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def check_credentials_or_client(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
||||
client = values.get("client")
|
||||
url = values.get("url") or os.environ.get("UPSTASH_VECTOR_REST_URL")
|
||||
token = values.get("token") or os.environ.get("UPSTASH_VECTOR_REST_TOKEN")
|
||||
|
||||
if not client and not (url and token):
|
||||
raise ValueError("Either a client or URL and token must be provided.")
|
||||
return values
|
||||
|
||||
model_config = {
|
||||
"arbitrary_types_allowed": True,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
from typing import Literal, Optional
|
||||
|
||||
from mem0.embeddings.base import EmbeddingBase
|
||||
|
||||
|
||||
class MockEmbeddings(EmbeddingBase):
|
||||
def embed(self, text, memory_action: Optional[Literal["add", "search", "update"]] = None):
|
||||
"""
|
||||
Generate a mock embedding with dimension of 10.
|
||||
"""
|
||||
return [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
|
||||
+38
-18
@@ -12,12 +12,20 @@ from pydantic import ValidationError
|
||||
|
||||
from mem0.configs.base import MemoryConfig, MemoryItem
|
||||
from mem0.configs.enums import MemoryType
|
||||
from mem0.configs.prompts import PROCEDURAL_MEMORY_SYSTEM_PROMPT, get_update_memory_messages
|
||||
from mem0.configs.prompts import (
|
||||
PROCEDURAL_MEMORY_SYSTEM_PROMPT,
|
||||
get_update_memory_messages,
|
||||
)
|
||||
from mem0.memory.base import MemoryBase
|
||||
from mem0.memory.setup import setup_config
|
||||
from mem0.memory.storage import SQLiteManager
|
||||
from mem0.memory.telemetry import capture_event
|
||||
from mem0.memory.utils import get_fact_retrieval_messages, parse_messages, parse_vision_messages, remove_code_blocks
|
||||
from mem0.memory.utils import (
|
||||
get_fact_retrieval_messages,
|
||||
parse_messages,
|
||||
parse_vision_messages,
|
||||
remove_code_blocks,
|
||||
)
|
||||
from mem0.utils.factory import EmbedderFactory, LlmFactory, VectorStoreFactory
|
||||
|
||||
# Setup user config
|
||||
@@ -32,7 +40,11 @@ class Memory(MemoryBase):
|
||||
|
||||
self.custom_fact_extraction_prompt = self.config.custom_fact_extraction_prompt
|
||||
self.custom_update_memory_prompt = self.config.custom_update_memory_prompt
|
||||
self.embedding_model = EmbedderFactory.create(self.config.embedder.provider, self.config.embedder.config)
|
||||
self.embedding_model = EmbedderFactory.create(
|
||||
self.config.embedder.provider,
|
||||
self.config.embedder.config,
|
||||
self.config.vector_store.config,
|
||||
)
|
||||
self.vector_store = VectorStoreFactory.create(
|
||||
self.config.vector_store.provider, self.config.vector_store.config
|
||||
)
|
||||
@@ -87,7 +99,6 @@ class Memory(MemoryBase):
|
||||
infer=True,
|
||||
memory_type=None,
|
||||
prompt=None,
|
||||
llm=None,
|
||||
):
|
||||
"""
|
||||
Create a new memory.
|
||||
@@ -102,7 +113,6 @@ class Memory(MemoryBase):
|
||||
infer (bool, optional): Whether to infer the memories. Defaults to True.
|
||||
memory_type (str, optional): Type of memory to create. Defaults to None. By default, it creates the short term memories and long term (semantic and episodic) memories. Pass "procedural_memory" to create procedural memories.
|
||||
prompt (str, optional): Prompt to use for the memory creation. Defaults to None.
|
||||
llm (BaseChatModel, optional): LLM class to use for generating procedural memories. Defaults to None. Useful when user is using LangChain ChatModel.
|
||||
Returns:
|
||||
dict: A dictionary containing the result of the memory addition operation.
|
||||
result: dict of affected events with each dict has the following key:
|
||||
@@ -139,7 +149,7 @@ class Memory(MemoryBase):
|
||||
messages = [{"role": "user", "content": messages}]
|
||||
|
||||
if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value:
|
||||
results = self._create_procedural_memory(messages, metadata=metadata, llm=llm, prompt=prompt)
|
||||
results = self._create_procedural_memory(messages, metadata=metadata, prompt=prompt)
|
||||
return results
|
||||
|
||||
if self.config.llm.config.get("enable_vision"):
|
||||
@@ -262,7 +272,9 @@ class Memory(MemoryBase):
|
||||
continue
|
||||
elif resp.get("event") == "ADD":
|
||||
memory_id = self._create_memory(
|
||||
data=resp.get("text"), existing_embeddings=new_message_embeddings, metadata=metadata
|
||||
data=resp.get("text"),
|
||||
existing_embeddings=new_message_embeddings,
|
||||
metadata=metadata,
|
||||
)
|
||||
returned_memories.append(
|
||||
{
|
||||
@@ -302,7 +314,11 @@ class Memory(MemoryBase):
|
||||
except Exception as e:
|
||||
logging.error(f"Error in new_memories_with_actions: {e}")
|
||||
|
||||
capture_event("mem0.add", self, {"version": self.api_version, "keys": list(filters.keys())})
|
||||
capture_event(
|
||||
"mem0.add",
|
||||
self,
|
||||
{"version": self.api_version, "keys": list(filters.keys())},
|
||||
)
|
||||
|
||||
return returned_memories
|
||||
|
||||
@@ -344,7 +360,16 @@ class Memory(MemoryBase):
|
||||
).model_dump(exclude={"score"})
|
||||
|
||||
# Add metadata if there are additional keys
|
||||
excluded_keys = {"user_id", "agent_id", "run_id", "hash", "data", "created_at", "updated_at", "id"}
|
||||
excluded_keys = {
|
||||
"user_id",
|
||||
"agent_id",
|
||||
"run_id",
|
||||
"hash",
|
||||
"data",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"id",
|
||||
}
|
||||
additional_metadata = {k: v for k, v in memory.payload.items() if k not in excluded_keys}
|
||||
if additional_metadata:
|
||||
memory_item["metadata"] = additional_metadata
|
||||
@@ -632,20 +657,15 @@ class Memory(MemoryBase):
|
||||
metadata (dict): Metadata to create a procedural memory from.
|
||||
prompt (str, optional): Prompt to use for the procedural memory creation. Defaults to None.
|
||||
"""
|
||||
try:
|
||||
from langchain_core.messages.utils import convert_to_messages # type: ignore
|
||||
except Exception:
|
||||
logger.error(
|
||||
"Import error while loading langchain-core. Please install 'langchain-core' to use procedural memory."
|
||||
)
|
||||
raise
|
||||
|
||||
logger.info("Creating procedural memory")
|
||||
|
||||
parsed_messages = [
|
||||
{"role": "system", "content": prompt or PROCEDURAL_MEMORY_SYSTEM_PROMPT},
|
||||
*messages,
|
||||
{"role": "user", "content": "Create procedural memory of the above conversation."},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Create procedural memory of the above conversation.",
|
||||
},
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import importlib
|
||||
from typing import Optional
|
||||
|
||||
from mem0.configs.embeddings.base import BaseEmbedderConfig
|
||||
from mem0.configs.llms.base import BaseLlmConfig
|
||||
from mem0.embeddings.mock import MockEmbeddings
|
||||
|
||||
|
||||
def load_class(class_type):
|
||||
@@ -54,7 +56,9 @@ class EmbedderFactory:
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def create(cls, provider_name, config):
|
||||
def create(cls, provider_name, config, vector_config: Optional[dict]):
|
||||
if provider_name == "upstash_vector" and vector_config and vector_config.enable_embeddings:
|
||||
return MockEmbeddings()
|
||||
class_type = cls.provider_to_class.get(provider_name)
|
||||
if class_type:
|
||||
embedder_instance = load_class(class_type)
|
||||
@@ -70,6 +74,7 @@ class VectorStoreFactory:
|
||||
"chroma": "mem0.vector_stores.chroma.ChromaDB",
|
||||
"pgvector": "mem0.vector_stores.pgvector.PGVector",
|
||||
"milvus": "mem0.vector_stores.milvus.MilvusDB",
|
||||
"upstash_vector": "mem0.vector_stores.upstash_vector.UpstashVector",
|
||||
"azure_ai_search": "mem0.vector_stores.azure_ai_search.AzureAISearch",
|
||||
"pinecone": "mem0.vector_stores.pinecone.PineconeDB",
|
||||
"redis": "mem0.vector_stores.redis.RedisDB",
|
||||
|
||||
@@ -5,7 +5,7 @@ from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
class VectorStoreConfig(BaseModel):
|
||||
provider: str = Field(
|
||||
description="Provider of the vector store (e.g., 'qdrant', 'chroma')",
|
||||
description="Provider of the vector store (e.g., 'qdrant', 'chroma', 'upstash_vector')",
|
||||
default="qdrant",
|
||||
)
|
||||
config: Optional[Dict] = Field(description="Configuration for the specific vector store", default=None)
|
||||
@@ -16,6 +16,7 @@ class VectorStoreConfig(BaseModel):
|
||||
"pgvector": "PGVectorConfig",
|
||||
"pinecone": "PineconeConfig",
|
||||
"milvus": "MilvusDBConfig",
|
||||
"upstash_vector": "UpstashVectorConfig",
|
||||
"azure_ai_search": "AzureAISearchConfig",
|
||||
"redis": "RedisDBConfig",
|
||||
"elasticsearch": "ElasticsearchConfig",
|
||||
|
||||
@@ -35,6 +35,7 @@ class FAISS(VectorStoreBase):
|
||||
path: Optional[str] = None,
|
||||
distance_strategy: str = "euclidean",
|
||||
normalize_L2: bool = False,
|
||||
embedding_model_dims: int = 1536,
|
||||
):
|
||||
"""
|
||||
Initialize the FAISS vector store.
|
||||
@@ -51,6 +52,7 @@ class FAISS(VectorStoreBase):
|
||||
self.path = path or f"/tmp/faiss/{collection_name}"
|
||||
self.distance_strategy = distance_strategy
|
||||
self.normalize_L2 = normalize_L2
|
||||
self.embedding_model_dims = embedding_model_dims
|
||||
|
||||
# Initialize storage structures
|
||||
self.index = None
|
||||
@@ -145,13 +147,12 @@ class FAISS(VectorStoreBase):
|
||||
|
||||
return results
|
||||
|
||||
def create_col(self, name: str, vector_size: int = 1536, distance: str = None):
|
||||
def create_col(self, name: str, distance: str = None):
|
||||
"""
|
||||
Create a new collection.
|
||||
|
||||
Args:
|
||||
name (str): Name of the collection.
|
||||
vector_size (int, optional): Dimensionality of vectors. Defaults to 1536.
|
||||
distance (str, optional): Distance metric to use. Overrides the distance_strategy
|
||||
passed during initialization. Defaults to None.
|
||||
|
||||
@@ -162,9 +163,9 @@ class FAISS(VectorStoreBase):
|
||||
|
||||
# Create index based on distance strategy
|
||||
if distance_strategy.lower() == "inner_product" or distance_strategy.lower() == "cosine":
|
||||
self.index = faiss.IndexFlatIP(vector_size)
|
||||
self.index = faiss.IndexFlatIP(self.embedding_model_dims)
|
||||
else:
|
||||
self.index = faiss.IndexFlatL2(vector_size)
|
||||
self.index = faiss.IndexFlatL2(self.embedding_model_dims)
|
||||
|
||||
self.collection_name = name
|
||||
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
import logging
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mem0.vector_stores.base import VectorStoreBase
|
||||
|
||||
try:
|
||||
from upstash_vector import Index
|
||||
except ImportError:
|
||||
raise ImportError("The 'upstash_vector' library is required. Please install it using 'pip install upstash_vector'.")
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OutputData(BaseModel):
|
||||
id: Optional[str] # memory id
|
||||
score: Optional[float] # is None for `get` method
|
||||
payload: Optional[Dict] # metadata
|
||||
|
||||
|
||||
class UpstashVector(VectorStoreBase):
|
||||
def __init__(
|
||||
self,
|
||||
collection_name: str,
|
||||
url: Optional[str] = None,
|
||||
token: Optional[str] = None,
|
||||
client: Optional[Index] = None,
|
||||
enable_embeddings: bool = False,
|
||||
):
|
||||
"""
|
||||
Initialize the UpstashVector vector store.
|
||||
|
||||
Args:
|
||||
url (str, optional): URL for Upstash Vector index. Defaults to None.
|
||||
token (int, optional): Token for Upstash Vector index. Defaults to None.
|
||||
client (Index, optional): Existing `upstash_vector.Index` client instance. Defaults to None.
|
||||
namespace (str, optional): Default namespace for the index. Defaults to None.
|
||||
"""
|
||||
if client:
|
||||
self.client = client
|
||||
elif url and token:
|
||||
self.client = Index(url, token)
|
||||
else:
|
||||
raise ValueError("Either a client or URL and token must be provided.")
|
||||
|
||||
self.collection_name = collection_name
|
||||
|
||||
self.enable_embeddings = enable_embeddings
|
||||
|
||||
def insert(
|
||||
self,
|
||||
vectors: List[list],
|
||||
payloads: Optional[List[Dict]] = None,
|
||||
ids: Optional[List[str]] = None,
|
||||
):
|
||||
"""
|
||||
Insert vectors
|
||||
|
||||
Args:
|
||||
vectors (list): List of vectors to insert.
|
||||
payloads (list, optional): List of payloads corresponding to vectors. These will be passed as metadatas to the Upstash Vector client. Defaults to None.
|
||||
ids (list, optional): List of IDs corresponding to vectors. Defaults to None.
|
||||
"""
|
||||
logger.info(f"Inserting {len(vectors)} vectors into namespace {self.collection_name}")
|
||||
|
||||
if self.enable_embeddings:
|
||||
if not payloads or any("data" not in m or m["data"] is None for m in payloads):
|
||||
raise ValueError("When embeddings are enabled, all payloads must contain a 'data' field.")
|
||||
processed_vectors = [
|
||||
{
|
||||
"id": ids[i] if ids else None,
|
||||
"data": payloads[i]["data"],
|
||||
"metadata": payloads[i],
|
||||
}
|
||||
for i, v in enumerate(vectors)
|
||||
]
|
||||
else:
|
||||
processed_vectors = [
|
||||
{
|
||||
"id": ids[i] if ids else None,
|
||||
"vector": vectors[i],
|
||||
"metadata": payloads[i] if payloads else None,
|
||||
}
|
||||
for i, v in enumerate(vectors)
|
||||
]
|
||||
|
||||
self.client.upsert(
|
||||
vectors=processed_vectors,
|
||||
namespace=self.collection_name,
|
||||
)
|
||||
|
||||
def _stringify(self, x):
|
||||
return f'"{x}"' if isinstance(x, str) else x
|
||||
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
vectors: List[list],
|
||||
limit: int = 5,
|
||||
filters: Optional[Dict] = None,
|
||||
) -> List[OutputData]:
|
||||
"""
|
||||
Search for similar vectors.
|
||||
|
||||
Args:
|
||||
query (list): Query vector.
|
||||
limit (int, optional): Number of results to return. Defaults to 5.
|
||||
filters (Dict, optional): Filters to apply to the search.
|
||||
|
||||
Returns:
|
||||
List[OutputData]: Search results.
|
||||
"""
|
||||
|
||||
filters_str = " AND ".join([f"{k} = {self._stringify(v)}" for k, v in filters.items()]) if filters else None
|
||||
|
||||
response = []
|
||||
|
||||
if self.enable_embeddings:
|
||||
response = self.client.query(
|
||||
data=query,
|
||||
top_k=limit,
|
||||
filter=filters_str or "",
|
||||
include_metadata=True,
|
||||
namespace=self.collection_name,
|
||||
)
|
||||
else:
|
||||
queries = [
|
||||
{
|
||||
"vector": v,
|
||||
"top_k": limit,
|
||||
"filter": filters_str or "",
|
||||
"include_metadata": True,
|
||||
"namespace": self.collection_name,
|
||||
}
|
||||
for v in vectors
|
||||
]
|
||||
responses = self.client.query_many(queries=queries)
|
||||
# flatten
|
||||
response = [res for res_list in responses for res in res_list]
|
||||
|
||||
return [
|
||||
OutputData(
|
||||
id=res.id,
|
||||
score=res.score,
|
||||
payload=res.metadata,
|
||||
)
|
||||
for res in response
|
||||
]
|
||||
|
||||
def delete(self, vector_id: int):
|
||||
"""
|
||||
Delete a vector by ID.
|
||||
|
||||
Args:
|
||||
vector_id (int): ID of the vector to delete.
|
||||
"""
|
||||
self.client.delete(
|
||||
ids=[str(vector_id)],
|
||||
namespace=self.collection_name,
|
||||
)
|
||||
|
||||
def update(
|
||||
self,
|
||||
vector_id: int,
|
||||
vector: Optional[list] = None,
|
||||
payload: Optional[dict] = None,
|
||||
):
|
||||
"""
|
||||
Update a vector and its payload.
|
||||
|
||||
Args:
|
||||
vector_id (int): ID of the vector to update.
|
||||
vector (list, optional): Updated vector. Defaults to None.
|
||||
payload (dict, optional): Updated payload. Defaults to None.
|
||||
"""
|
||||
self.client.update(
|
||||
id=str(vector_id),
|
||||
vector=vector,
|
||||
data=payload.get("data") if payload else None,
|
||||
metadata=payload,
|
||||
namespace=self.collection_name,
|
||||
)
|
||||
|
||||
def get(self, vector_id: int) -> Optional[OutputData]:
|
||||
"""
|
||||
Retrieve a vector by ID.
|
||||
|
||||
Args:
|
||||
vector_id (int): ID of the vector to retrieve.
|
||||
|
||||
Returns:
|
||||
dict: Retrieved vector.
|
||||
"""
|
||||
response = self.client.fetch(
|
||||
ids=[str(vector_id)],
|
||||
namespace=self.collection_name,
|
||||
include_metadata=True,
|
||||
)
|
||||
if len(response) == 0:
|
||||
return None
|
||||
vector = response[0]
|
||||
if not vector:
|
||||
return None
|
||||
return OutputData(id=vector.id, score=None, payload=vector.metadata)
|
||||
|
||||
def list(self, filters: Optional[Dict] = None, limit: int = 100) -> List[List[OutputData]]:
|
||||
"""
|
||||
List all memories.
|
||||
Args:
|
||||
filters (Dict, optional): Filters to apply to the search. Defaults to None.
|
||||
limit (int, optional): Number of results to return. Defaults to 100.
|
||||
Returns:
|
||||
List[OutputData]: Search results.
|
||||
"""
|
||||
filters_str = " AND ".join([f"{k} = {self._stringify(v)}" for k, v in filters.items()]) if filters else None
|
||||
|
||||
info = self.client.info()
|
||||
ns_info = info.namespaces.get(self.collection_name)
|
||||
|
||||
if not ns_info or ns_info.vector_count == 0:
|
||||
return [[]]
|
||||
|
||||
random_vector = [1.0] * self.client.info().dimension
|
||||
|
||||
results, query = self.client.resumable_query(
|
||||
vector=random_vector,
|
||||
filter=filters_str or "",
|
||||
include_metadata=True,
|
||||
namespace=self.collection_name,
|
||||
top_k=100,
|
||||
)
|
||||
with query:
|
||||
while True:
|
||||
if len(results) >= limit:
|
||||
break
|
||||
res = query.fetch_next(100)
|
||||
if not res:
|
||||
break
|
||||
results.extend(res)
|
||||
|
||||
parsed_result = [
|
||||
OutputData(
|
||||
id=res.id,
|
||||
score=res.score,
|
||||
payload=res.metadata,
|
||||
)
|
||||
for res in results
|
||||
]
|
||||
return [parsed_result]
|
||||
|
||||
def create_col(self, name, vector_size, distance):
|
||||
"""
|
||||
Upstash Vector has namespaces instead of collections. A namespace is created when the first vector is inserted.
|
||||
|
||||
This method is a placeholder to maintain the interface.
|
||||
"""
|
||||
pass
|
||||
|
||||
def list_cols(self) -> List[str]:
|
||||
"""
|
||||
Lists all namespaces in the Upstash Vector index.
|
||||
Returns:
|
||||
List[str]: List of namespaces.
|
||||
"""
|
||||
return self.client.list_namespaces()
|
||||
|
||||
def delete_col(self):
|
||||
"""
|
||||
Delete the namespace and all vectors in it.
|
||||
"""
|
||||
self.client.reset(namespace=self.collection_name)
|
||||
pass
|
||||
|
||||
def col_info(self):
|
||||
"""
|
||||
Return general information about the Upstash Vector index.
|
||||
|
||||
- Total number of vectors across all namespaces
|
||||
- Total number of vectors waiting to be indexed across all namespaces
|
||||
- Total size of the index on disk in bytes
|
||||
- Vector dimension
|
||||
- Similarity function used
|
||||
- Per-namespace vector and pending vector counts
|
||||
"""
|
||||
return self.client.info()
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "mem0ai"
|
||||
version = "0.1.84"
|
||||
version = "0.1.87"
|
||||
description = "Long-term memory for AI Agents"
|
||||
authors = ["Mem0 <founders@mem0.ai>"]
|
||||
exclude = [
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional
|
||||
from unittest.mock import MagicMock, call, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mem0.vector_stores.upstash_vector import UpstashVector
|
||||
|
||||
|
||||
@dataclass
|
||||
class QueryResult:
|
||||
id: str
|
||||
score: Optional[float]
|
||||
vector: Optional[List[float]] = None
|
||||
metadata: Optional[Dict] = None
|
||||
data: Optional[str] = None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_index():
|
||||
with patch("upstash_vector.Index") as mock_index:
|
||||
yield mock_index
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def upstash_instance(mock_index):
|
||||
return UpstashVector(client=mock_index.return_value, collection_name="ns")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def upstash_instance_with_embeddings(mock_index):
|
||||
return UpstashVector(
|
||||
client=mock_index.return_value, collection_name="ns", enable_embeddings=True
|
||||
)
|
||||
|
||||
|
||||
def test_insert_vectors(upstash_instance, mock_index):
|
||||
vectors = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]
|
||||
payloads = [{"name": "vector1"}, {"name": "vector2"}]
|
||||
ids = ["id1", "id2"]
|
||||
|
||||
upstash_instance.insert(vectors=vectors, payloads=payloads, ids=ids)
|
||||
|
||||
upstash_instance.client.upsert.assert_called_once_with(
|
||||
vectors=[
|
||||
{"id": "id1", "vector": [0.1, 0.2, 0.3], "metadata": {"name": "vector1"}},
|
||||
{"id": "id2", "vector": [0.4, 0.5, 0.6], "metadata": {"name": "vector2"}},
|
||||
],
|
||||
namespace="ns",
|
||||
)
|
||||
|
||||
|
||||
def test_search_vectors(upstash_instance, mock_index):
|
||||
mock_result = [
|
||||
QueryResult(
|
||||
id="id1", score=0.1, vector=None, metadata={"name": "vector1"}, data=None
|
||||
),
|
||||
QueryResult(
|
||||
id="id2", score=0.2, vector=None, metadata={"name": "vector2"}, data=None
|
||||
),
|
||||
]
|
||||
|
||||
upstash_instance.client.query_many.return_value = [mock_result]
|
||||
|
||||
vectors = [[0.1, 0.2, 0.3]]
|
||||
results = upstash_instance.search(
|
||||
query="hello world",
|
||||
vectors=vectors,
|
||||
limit=2,
|
||||
filters={"age": 30, "name": "John"},
|
||||
)
|
||||
|
||||
upstash_instance.client.query_many.assert_called_once_with(
|
||||
queries=[
|
||||
{
|
||||
"vector": vectors[0],
|
||||
"top_k": 2,
|
||||
"namespace": "ns",
|
||||
"include_metadata": True,
|
||||
"filter": 'age = 30 AND name = "John"',
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
assert len(results) == 2
|
||||
assert results[0].id == "id1"
|
||||
assert results[0].score == 0.1
|
||||
assert results[0].payload == {"name": "vector1"}
|
||||
|
||||
|
||||
def test_delete_vector(upstash_instance):
|
||||
vector_id = "id1"
|
||||
|
||||
upstash_instance.delete(vector_id=vector_id)
|
||||
|
||||
upstash_instance.client.delete.assert_called_once_with(
|
||||
ids=[vector_id], namespace="ns"
|
||||
)
|
||||
|
||||
|
||||
def test_update_vector(upstash_instance):
|
||||
vector_id = "id1"
|
||||
new_vector = [0.7, 0.8, 0.9]
|
||||
new_payload = {"name": "updated_vector"}
|
||||
|
||||
upstash_instance.update(vector_id=vector_id, vector=new_vector, payload=new_payload)
|
||||
|
||||
upstash_instance.client.update.assert_called_once_with(
|
||||
id="id1",
|
||||
vector=new_vector,
|
||||
data=None,
|
||||
metadata={"name": "updated_vector"},
|
||||
namespace="ns",
|
||||
)
|
||||
|
||||
|
||||
def test_get_vector(upstash_instance):
|
||||
mock_result = [
|
||||
QueryResult(
|
||||
id="id1", score=None, vector=None, metadata={"name": "vector1"}, data=None
|
||||
)
|
||||
]
|
||||
upstash_instance.client.fetch.return_value = mock_result
|
||||
|
||||
result = upstash_instance.get(vector_id="id1")
|
||||
|
||||
upstash_instance.client.fetch.assert_called_once_with(
|
||||
ids=["id1"], namespace="ns", include_metadata=True
|
||||
)
|
||||
|
||||
assert result.id == "id1"
|
||||
assert result.payload == {"name": "vector1"}
|
||||
|
||||
|
||||
def test_list_vectors(upstash_instance):
|
||||
mock_result = [
|
||||
QueryResult(
|
||||
id="id1", score=None, vector=None, metadata={"name": "vector1"}, data=None
|
||||
),
|
||||
QueryResult(
|
||||
id="id2", score=None, vector=None, metadata={"name": "vector2"}, data=None
|
||||
),
|
||||
QueryResult(
|
||||
id="id3", score=None, vector=None, metadata={"name": "vector3"}, data=None
|
||||
),
|
||||
]
|
||||
handler = MagicMock()
|
||||
|
||||
upstash_instance.client.info.return_value.dimension = 10
|
||||
upstash_instance.client.resumable_query.return_value = (mock_result[0:1], handler)
|
||||
handler.fetch_next.side_effect = [mock_result[1:2], mock_result[2:3], []]
|
||||
|
||||
filters = {"age": 30, "name": "John"}
|
||||
print("filters", filters)
|
||||
[results] = upstash_instance.list(filters=filters, limit=15)
|
||||
|
||||
upstash_instance.client.info.return_value = {
|
||||
"dimension": 10,
|
||||
}
|
||||
|
||||
upstash_instance.client.resumable_query.assert_called_once_with(
|
||||
vector=[1.0] * 10,
|
||||
filter='age = 30 AND name = "John"',
|
||||
include_metadata=True,
|
||||
namespace="ns",
|
||||
top_k=100,
|
||||
)
|
||||
|
||||
handler.fetch_next.assert_has_calls([call(100), call(100), call(100)])
|
||||
handler.__exit__.assert_called_once()
|
||||
|
||||
assert len(results) == len(mock_result)
|
||||
assert results[0].id == "id1"
|
||||
assert results[0].payload == {"name": "vector1"}
|
||||
|
||||
|
||||
def test_insert_vectors_with_embeddings(upstash_instance_with_embeddings, mock_index):
|
||||
vectors = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]
|
||||
payloads = [
|
||||
{"name": "vector1", "data": "data1"},
|
||||
{"name": "vector2", "data": "data2"},
|
||||
]
|
||||
ids = ["id1", "id2"]
|
||||
|
||||
upstash_instance_with_embeddings.insert(vectors=vectors, payloads=payloads, ids=ids)
|
||||
|
||||
upstash_instance_with_embeddings.client.upsert.assert_called_once_with(
|
||||
vectors=[
|
||||
{
|
||||
"id": "id1",
|
||||
# Uses the data field instead of using vectors
|
||||
"data": "data1",
|
||||
"metadata": {"name": "vector1", "data": "data1"},
|
||||
},
|
||||
{
|
||||
"id": "id2",
|
||||
"data": "data2",
|
||||
"metadata": {"name": "vector2", "data": "data2"},
|
||||
},
|
||||
],
|
||||
namespace="ns",
|
||||
)
|
||||
|
||||
|
||||
def test_search_vectors_with_embeddings(upstash_instance_with_embeddings, mock_index):
|
||||
mock_result = [
|
||||
QueryResult(
|
||||
id="id1", score=0.1, vector=None, metadata={"name": "vector1"}, data="data1"
|
||||
),
|
||||
QueryResult(
|
||||
id="id2", score=0.2, vector=None, metadata={"name": "vector2"}, data="data2"
|
||||
),
|
||||
]
|
||||
|
||||
upstash_instance_with_embeddings.client.query.return_value = mock_result
|
||||
|
||||
results = upstash_instance_with_embeddings.search(
|
||||
query="hello world",
|
||||
vectors=[],
|
||||
limit=2,
|
||||
filters={"age": 30, "name": "John"},
|
||||
)
|
||||
|
||||
upstash_instance_with_embeddings.client.query.assert_called_once_with(
|
||||
# Uses the data field instead of using vectors
|
||||
data="hello world",
|
||||
top_k=2,
|
||||
filter='age = 30 AND name = "John"',
|
||||
include_metadata=True,
|
||||
namespace="ns",
|
||||
)
|
||||
|
||||
assert len(results) == 2
|
||||
assert results[0].id == "id1"
|
||||
assert results[0].score == 0.1
|
||||
assert results[0].payload == {"name": "vector1"}
|
||||
|
||||
|
||||
def test_update_vector_with_embeddings(upstash_instance_with_embeddings):
|
||||
vector_id = "id1"
|
||||
new_payload = {"name": "updated_vector", "data": "updated_data"}
|
||||
|
||||
upstash_instance_with_embeddings.update(vector_id=vector_id, payload=new_payload)
|
||||
|
||||
upstash_instance_with_embeddings.client.update.assert_called_once_with(
|
||||
id="id1",
|
||||
vector=None,
|
||||
data="updated_data",
|
||||
metadata={"name": "updated_vector", "data": "updated_data"},
|
||||
namespace="ns",
|
||||
)
|
||||
|
||||
|
||||
def test_insert_vectors_with_embeddings_missing_data(upstash_instance_with_embeddings):
|
||||
vectors = [[0.1, 0.2, 0.3]]
|
||||
payloads = [{"name": "vector1"}] # Missing data field
|
||||
ids = ["id1"]
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="When embeddings are enabled, all payloads must contain a 'data' field",
|
||||
):
|
||||
upstash_instance_with_embeddings.insert(
|
||||
vectors=vectors, payloads=payloads, ids=ids
|
||||
)
|
||||
|
||||
|
||||
def test_update_vector_with_embeddings_missing_data(upstash_instance_with_embeddings):
|
||||
# Should still work, data is not required for update
|
||||
vector_id = "id1"
|
||||
new_payload = {"name": "updated_vector"} # Missing data field
|
||||
|
||||
upstash_instance_with_embeddings.update(vector_id=vector_id, payload=new_payload)
|
||||
|
||||
upstash_instance_with_embeddings.client.update.assert_called_once_with(
|
||||
id="id1",
|
||||
vector=None,
|
||||
data=None,
|
||||
metadata={"name": "updated_vector"},
|
||||
namespace="ns",
|
||||
)
|
||||
|
||||
|
||||
def test_list_cols(upstash_instance):
|
||||
mock_namespaces = ["ns1", "ns2", "ns3"]
|
||||
upstash_instance.client.list_namespaces.return_value = mock_namespaces
|
||||
|
||||
result = upstash_instance.list_cols()
|
||||
|
||||
upstash_instance.client.list_namespaces.assert_called_once()
|
||||
assert result == mock_namespaces
|
||||
|
||||
|
||||
def test_delete_col(upstash_instance):
|
||||
upstash_instance.delete_col()
|
||||
upstash_instance.client.reset.assert_called_once_with(namespace="ns")
|
||||
|
||||
|
||||
def test_col_info(upstash_instance):
|
||||
mock_info = {
|
||||
"dimension": 10,
|
||||
"total_vectors": 100,
|
||||
"pending_vectors": 0,
|
||||
"disk_size": 1024,
|
||||
}
|
||||
upstash_instance.client.info.return_value = mock_info
|
||||
|
||||
result = upstash_instance.col_info()
|
||||
|
||||
upstash_instance.client.info.assert_called_once()
|
||||
assert result == mock_info
|
||||
|
||||
|
||||
def test_get_vector_not_found(upstash_instance):
|
||||
upstash_instance.client.fetch.return_value = []
|
||||
|
||||
result = upstash_instance.get(vector_id="nonexistent")
|
||||
|
||||
upstash_instance.client.fetch.assert_called_once_with(
|
||||
ids=["nonexistent"], namespace="ns", include_metadata=True
|
||||
)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_search_vectors_empty_filters(upstash_instance):
|
||||
mock_result = [
|
||||
QueryResult(
|
||||
id="id1", score=0.1, vector=None, metadata={"name": "vector1"}, data=None
|
||||
)
|
||||
]
|
||||
upstash_instance.client.query_many.return_value = [mock_result]
|
||||
|
||||
vectors = [[0.1, 0.2, 0.3]]
|
||||
results = upstash_instance.search(
|
||||
query="hello world",
|
||||
vectors=vectors,
|
||||
limit=1,
|
||||
filters=None,
|
||||
)
|
||||
|
||||
upstash_instance.client.query_many.assert_called_once_with(
|
||||
queries=[
|
||||
{
|
||||
"vector": vectors[0],
|
||||
"top_k": 1,
|
||||
"namespace": "ns",
|
||||
"include_metadata": True,
|
||||
"filter": "",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
assert len(results) == 1
|
||||
assert results[0].id == "id1"
|
||||
|
||||
|
||||
def test_insert_vectors_no_payloads(upstash_instance):
|
||||
vectors = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]
|
||||
ids = ["id1", "id2"]
|
||||
|
||||
upstash_instance.insert(vectors=vectors, ids=ids)
|
||||
|
||||
upstash_instance.client.upsert.assert_called_once_with(
|
||||
vectors=[
|
||||
{"id": "id1", "vector": [0.1, 0.2, 0.3], "metadata": None},
|
||||
{"id": "id2", "vector": [0.4, 0.5, 0.6], "metadata": None},
|
||||
],
|
||||
namespace="ns",
|
||||
)
|
||||
|
||||
|
||||
def test_insert_vectors_no_ids(upstash_instance):
|
||||
vectors = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]
|
||||
payloads = [{"name": "vector1"}, {"name": "vector2"}]
|
||||
|
||||
upstash_instance.insert(vectors=vectors, payloads=payloads)
|
||||
|
||||
upstash_instance.client.upsert.assert_called_once_with(
|
||||
vectors=[
|
||||
{"id": None, "vector": [0.1, 0.2, 0.3], "metadata": {"name": "vector1"}},
|
||||
{"id": None, "vector": [0.4, 0.5, 0.6], "metadata": {"name": "vector2"}},
|
||||
],
|
||||
namespace="ns",
|
||||
)
|
||||
Reference in New Issue
Block a user