Compare commits

..

53 Commits

Author SHA1 Message Date
Dev Khant cd5c3035ab version bump -> 0.1.93 (#2576) 2025-04-21 09:25:00 +05:30
Dev Khant 09ac3618a8 Doc: fix agno link (#2573) 2025-04-19 10:59:50 +05:30
Dev Khant 3ee4768c14 Init embedding_model_dims in all vectordbs (#2572) 2025-04-19 10:53:01 +05:30
Prateek Chhikara 78912928bc Changes to client (#2562) 2025-04-17 11:28:39 -07:00
Dev Khant 8bd0d2dc24 Doc: fix curl for v2 get_all (#2567) 2025-04-17 23:10:54 +05:30
Saket Aryan f0bdd2c341 Add Support for Custom Instructions (#2565) 2025-04-17 14:38:07 +05:30
Antaripa Saha bf0c4adc0c Fitness Checker powered by memory (#2561) 2025-04-16 08:37:23 -07:00
Dev Khant 2cca50db80 Doc: update changelog (#2559) 2025-04-16 16:43:15 +05:30
Dev Khant b8e4d0980a Memory Reset (#2558) 2025-04-16 16:36:45 +05:30
Dev Khant 3613e2f14a Fix user_id functionality (#2548) 2025-04-16 13:32:33 +05:30
Dev Khant 541030d69c Update capture_event (#2527) 2025-04-16 10:42:36 +05:30
Dev Khant f77a084d1b silence faiss info logs (#2557) 2025-04-16 09:57:32 +05:30
Saket Aryan 33abf772ce Adds Azure OpenAI Embedding Model (#2545) 2025-04-15 22:02:30 +05:30
Saket Aryan c3c9205ffa TypeScript OSS: Langchain Integration (#2556) 2025-04-15 20:08:41 +05:30
Gábor Tóth 9f204dc557 Update openai.mdx (#2503) 2025-04-14 21:08:49 +05:30
Antaripa Saha 0e98773efb Voice Assistant using Elevenlabs (#2555) 2025-04-14 20:48:10 +05:30
Dev Khant 4431bd7d51 Doc: update changelog (#2553) 2025-04-14 15:58:16 +05:30
Dev Khant 0354ab0d6b Doc: reformat navbar page URLs (#2551) 2025-04-14 06:09:12 +05:30
Antaripa Saha 6dfc193296 movie recommendation using grok3 (#2547) 2025-04-12 08:53:18 -07:00
Dev Khant 9be6850b9d Doc: Add keywords AI (#2546) 2025-04-12 17:49:59 +05:30
Deshraj Yadav d33547a77a User/dyadav/fix telemetry issue (#2541) 2025-04-11 13:36:26 -07:00
Vir Kothari d77bed2d5d Update YT chrome extension example doc (#2540) 2025-04-11 22:21:24 +05:30
Dev Khant 9c89e0ec95 Doc; Update xAI doc (#2539) 2025-04-11 21:37:18 +05:30
Saket Aryan ca1ee2d2d7 Patch to fix Azure OpenAI (#2538) 2025-04-11 21:28:38 +05:30
Saket Aryan 05f9607282 Adds Azure OpenAI LLM to Mem0 TS SDK (#2536) 2025-04-11 20:09:20 +05:30
Achraf Dev d9236de4ed feat: add mistral AI as LLM provider (#2496)
Co-authored-by: Saket Aryan <94069182+whysosaket@users.noreply.github.com>
2025-04-11 20:02:44 +05:30
Dev Khant 942727fec6 Fix EmbedderFactory.create() in GraphMemory (#2535) 2025-04-11 13:56:05 +05:30
Manthan Gupta 72396e307d Fix: memory exclusion example in doc (#2520) 2025-04-11 13:50:48 +05:30
Dev Khant 881cf5b5a6 update changelog (#2534) 2025-04-11 13:45:34 +05:30
Dev Khant 5327d6e50d version bump -> 0.1.89 (#2533) 2025-04-11 13:40:47 +05:30
Dev Khant 15a3e20371 Store user_id in vectordb (#2466) 2025-04-11 13:37:34 +05:30
Dev Khant 19d7beef43 Add support for Langchain VectorStores (#2518) 2025-04-11 13:37:18 +05:30
Vir Kothari 8b789adb15 Add YT assistant chrome extension (#2485) 2025-04-10 22:14:57 +05:30
Antaripa Saha fd065fe9cc Personal Study Buddy (#2531) 2025-04-10 08:12:39 -07:00
Antaripa Saha b5127f7c62 personal assistant (#2530) 2025-04-10 20:24:18 +05:30
Dev-Khant 37d9fed690 doc: update agno 2025-04-10 15:49:36 +05:30
Dev Khant 31861e9acb Doc: Add agno example (#2529) 2025-04-10 15:46:46 +05:30
Dev Khant 07462adc9a Formatting (#2526) 2025-04-10 11:42:25 +05:30
Dev Khant 616313b8b5 Add async support (#2492)
Co-authored-by: Deshraj Yadav <deshrajdry@gmail.com>
2025-04-10 11:16:44 +05:30
Dev Khant 44f2490667 Doc: modify 2 examples to show OpenAIResponses API (#2525) 2025-04-10 10:24:10 +05:30
Dev Khant 3cc794fb98 version bump -> 0.1.88 (#2524) 2025-04-10 01:02:05 +05:30
Dev Khant ff6b251c66 Handle HF logging (#2523) 2025-04-10 01:01:00 +05:30
Sergio Toro 55df395fd6 fix: extract entities tool_calls some times is an array (#2481) 2025-04-10 00:03:52 +05:30
Dev Khant 244e60cea6 update python changelog (#2522) 2025-04-09 23:38:10 +05:30
Dev Khant b480c71f0f version bump -> 0.1.87 (#2521) 2025-04-09 23:34:15 +05:30
Saket Aryan 309c8c18a6 Add user_id in TS OSS SDK (#2514) 2025-04-09 10:24:56 -07:00
Dev Khant f4d8647264 Doc: update memory export (#2519) 2025-04-09 17:34:16 +05:30
Dev Khant f95c4cbbe5 Update MAKEFILE (#2517) 2025-04-09 12:04:39 +05:30
Dev Khant 00c7cc432c Remove redundant lines (#2516) 2025-04-09 11:02:37 +05:30
ytkimirti 91abc03880 Add Upstash Vector support (#2493) 2025-04-09 10:06:07 +05:30
Saket Aryan 9100e95175 Fix Batch API docs (#2512) 2025-04-07 23:54:16 +05:30
Dev Khant cdb8dcdb9e Add embeding_dims param to FAISS (#2513) 2025-04-07 23:29:55 +05:30
Dev-Khant 2a79add7a5 hotfix: _create_procedural_memory 2025-04-07 16:07:26 +05:30
109 changed files with 9861 additions and 742 deletions
+2 -1
View File
@@ -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.
+328
View File
@@ -0,0 +1,328 @@
---
title: "Product Updates"
mode: "wide"
---
<Tabs>
<Tab title="Python">
<Update label="2025-04-16" description="v0.1.91">
**New Features:**
- **LLM Integrations:** Added Azure OpenAI Embedding Model
- **Examples:**
- Added movie recommendation using grok3
- Added Voice Assistant using Elevenlabs
**Improvements:**
- **Documentation:**
- Added keywords AI
- Reformatted navbar page URLs
- Updated changelog
- Updated openai.mdx
- **FAISS:** Silenced FAISS info logs
</Update>
<Update label="2025-04-11" description="v0.1.90">
**New Features:**
- **LLM Integrations:** Added Mistral AI as LLM provider
**Improvements:**
- **Documentation:**
- Updated changelog
- Fixed memory exclusion example
- Updated xAI documentation
- Updated YouTube Chrome extension example documentation
**Bug Fixes:**
- **Core:** Fixed EmbedderFactory.create() in GraphMemory
- **Azure OpenAI:** Added patch to fix Azure OpenAI
- **Telemetry:** Fixed telemetry issue
</Update>
<Update label="2025-04-11" description="v0.1.89">
**New Features:**
- **Langchain Integration:** Added support for Langchain VectorStores
- **Examples:**
- Added personal assistant example
- Added personal study buddy example
- Added YouTube assistant Chrome extension example
- Added agno example
- Updated OpenAI Responses API examples
- **Vector Store:** Added capability to store user_id in vector database
- **Async Memory:** Added async support for OSS
**Improvements:**
- **Documentation:** Updated formatting and examples
</Update>
<Update label="2025-04-09" description="v0.1.87">
**New Features:**
- **Upstash Vector:** Added support for Upstash Vector store
**Improvements:**
- **Code Quality:** Removed redundant code lines
- **Build:** Updated MAKEFILE
- **Documentation:** Updated memory export documentation
</Update>
<Update label="2025-04-07" description="v0.1.86">
**Improvements:**
- **FAISS:** Added embedding_dims parameter to FAISS vector store
</Update>
<Update label="2025-04-07" description="v0.1.84">
**New Features:**
- **Langchain Embedder:** Added Langchain embedder integration
**Improvements:**
- **Langchain LLM:** Updated Langchain LLM integration to directly pass the Langchain object LLM
</Update>
<Update label="2025-04-07" description="v0.1.83">
**Bug Fixes:**
- **Langchain LLM:** Fixed issues with Langchain LLM integration
</Update>
<Update label="2025-04-07" description="v0.1.82">
**New Features:**
- **LLM Integrations:** Added support for Langchain LLMs, Google as new LLM and embedder
- **Development:** Added development docker compose
**Improvements:**
- **Output Format:** Set output_format='v1.1' and updated documentation
**Documentation:**
- **Integrations:** Added LMStudio and Together.ai documentation
- **API Reference:** Updated output_format documentation
- **Integrations:** Added PipeCat integration documentation
- **Integrations:** Added Flowise integration documentation for Mem0 memory setup
**Bug Fixes:**
- **Tests:** Fixed failing unit tests
</Update>
<Update label="2025-04-02" description="v0.1.79">
**New Features:**
- **FAISS Support:** Added FAISS vector store support
</Update>
<Update label="2025-04-02" description="v0.1.78">
**New Features:**
- **Livekit Integration:** Added Mem0 livekit example
- **Evaluation:** Added evaluation framework and tools
**Documentation:**
- **Multimodal:** Updated multimodal documentation
- **Examples:** Added examples for email processing
- **API Reference:** Updated API reference section
- **Elevenlabs:** Added Elevenlabs integration example
**Bug Fixes:**
- **OpenAI Environment Variables:** Fixed issues with OpenAI environment variables
- **Deployment Errors:** Added `package.json` file to fix deployment errors
- **Tools:** Fixed tools issues and improved formatting
- **Docs:** Updated API reference section for `expiration date`
</Update>
<Update label="2025-03-26" description="v0.1.77">
**Bug Fixes:**
- **OpenAI Environment Variables:** Fixed issues with OpenAI environment variables
- **Deployment Errors:** Added `package.json` file to fix deployment errors
- **Tools:** Fixed tools issues and improved formatting
- **Docs:** Updated API reference section for `expiration date`
</Update>
<Update label="2025-03-19" description="v0.1.76">
**New Features:**
- **Supabase Vector Store:** Added support for Supabase Vector Store
- **Supabase History DB:** Added Supabase History DB to run Mem0 OSS on Serverless
- **Feedback Method:** Added feedback method to client
**Bug Fixes:**
- **Azure OpenAI:** Fixed issues with Azure OpenAI
- **Azure AI Search:** Fixed test cases for Azure AI Search
</Update>
</Tab>
<Tab title="TypeScript">
<Update label="2025-04-17" description="v2.1.18">
**Improvements:**
- **Client:** Added support for custom instructions
</Update>
<Update label="2025-04-15" description="v2.1.17">
**New Features:**
- **OSS SDK:** Added support for Langchain LLM
- **OSS SDK:** Added support for Langchain Embedder
- **OSS SDK:** Added support for Langchain Vector Store
- **OSS SDK:** Added support for Azure OpenAI Embedder
**Improvements:**
- **OSS SDK:** Changed `model` in LLM and Embedder to use type any from `string` to use langchain llm models
- **OSS SDK:** Added client to vector store config for langchain vector store
- **OSS SDK:** - Updated Azure OpenAI to use new OpenAI SDK
</Update>
<Update label="2025-04-11" description="v2.1.16-patch.1">
**Bug Fixes:**
- **Azure OpenAI:** Fixed issues with Azure OpenAI
</Update>
<Update label="2025-04-11" description="v2.1.16">
**New Features:**
- **Azure OpenAI:** Added support for Azure OpenAI
- **Mistral LLM:** Added Mistral LLM integration in OSS
**Improvements:**
- **Zod:** Updated Zod to 3.24.1 to avoid conflicts with other packages
</Update>
<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
- **Integrations:** Added Flowise integration documentation for Mem0 memory setup
**Improvements:**
- **Demo:** Updated Demo Mem0AI
- **Client:** Enhanced Ping method in Mem0 Client
- **AI SDK:** Updated AI SDK implementation
</Update>
<Update label="2025-03-29" description="v2.1.13">
**Improvements:**
- **Introuced `ping` method to check if API key is valid and populate org/project id**
</Update>
<Update label="2025-03-29" description="AI SDK v1.0.0">
**New Features:**
- **Vercel AI SDK Update:** Support threshold and rerank
**Improvements:**
- **Made add calls async to avoid blocking**
- **Bump `mem0ai` to use `2.1.12`**
</Update>
<Update label="2025-03-26" description="v2.1.12">
**New Features:**
- **Mem0 OSS:** Support infer param
**Improvements:**
- **Updated Supabase TS Docs**
- **Made package size smaller**
</Update>
<Update label="2025-03-19" description="v2.1.11">
**New Features:**
- **Supabase Vector Store Integration**
- **Feedback Method**
</Update>
</Tab>
<Tab title="Platform">
<Update label="2025-04-12" description="">
**New Features:**
- **Memory Management:**
- Added ability to delete memories from Project level with filters
- Added delete memories capability on Memories Page
- **Memory Visualization:** Released V1 Graph Memory Visualization
- **Graph Playground:** Enabled for @mem0.ai users
- **Notifications:** Added email alerts to organization owners when new members join
- **Memory Export:** Added date support for filtering memory exports
**Improvements:**
- **Performance:**
- Optimized graph for better performance
- Optimized database calls in ADD method
- **Analytics:** Added flagging of paid users in Posthog
- **CI/CD:** Improved CI pipeline and fixed lint issues
</Update>
<Update label="2025-04-10" description="">
**New Features:**
- **Notifications:** Implemented email notifications for organization owners when new members join
**Improvements:**
- **CI/CD:** Fixed Dockerfile for CI tests
</Update>
<Update label="2025-04-09" description="">
**Improvements:**
- **Integrations:** Updated chat model for Together Qwen
- **Platform:** Removed older platforms
- **Bug Fixes:** Fixed FILTER_MAPPING
</Update>
<Update label="2025-04-03" description="">
**New Features:**
- **Memory:** Added implicit memory capabilities
- **API:** Improved implicit lambda and get_all v2 functionality
</Update>
<Update label="2025-04-02" description="">
**New Features:**
- **Integrations:** Added Clay integration
**Improvements:**
- **Integrations:** Removed deepseek coder from Together
- **API:** Added custom instructions for add v2
</Update>
<Update label="2025-03-31" description="">
**Security:**
- **Validation:** Added key validation in messages
</Update>
<Update label="2025-03-28" description="">
- **Updated Playground Prompt**
- **Send Email on User Addition to Org/Proj**
- **Fix Search Entity**
</Update>
<Update label="2025-03-19" description="">
- **General Stability & Performance Improvements**
</Update>
</Tab>
</Tabs>
-151
View File
@@ -1,151 +0,0 @@
---
title: "Product Updates"
mode: "wide"
---
<Tabs>
<Tab title="Python">
<Update label="2025-04-07" description="v0.1.84">
**New Features:**
- **Langchain Embedder:** Added Langchain embedder integration
**Improvements:**
- **Langchain LLM:** Updated Langchain LLM integration to directly pass the Langchain object LLM
</Update>
<Update label="2025-04-07" description="v0.1.83">
**Bug Fixes:**
- **Langchain LLM:** Fixed issues with Langchain LLM integration
</Update>
<Update label="2025-04-07" description="v0.1.82">
**New Features:**
- **LLM Integrations:** Added support for Langchain LLMs, Google as new LLM and embedder
- **Development:** Added development docker compose
**Improvements:**
- **Output Format:** Set output_format='v1.1' and updated documentation
**Documentation:**
- **Integrations:** Added LMStudio and Together.ai documentation
- **API Reference:** Updated output_format documentation
- **Integrations:** Added PipeCat integration documentation
- **Integrations:** Added Flowise integration documentation for Mem0 memory setup
**Bug Fixes:**
- **Tests:** Fixed failing unit tests
</Update>
<Update label="2025-04-02" description="v0.1.79">
**New Features:**
- **FAISS Support:** Added FAISS vector store support
</Update>
<Update label="2025-04-02" description="v0.1.78">
**New Features:**
- **Livekit Integration:** Added Mem0 livekit example
- **Evaluation:** Added evaluation framework and tools
**Documentation:**
- **Multimodal:** Updated multimodal documentation
- **Examples:** Added examples for email processing
- **API Reference:** Updated API reference section
- **Elevenlabs:** Added Elevenlabs integration example
**Bug Fixes:**
- **OpenAI Environment Variables:** Fixed issues with OpenAI environment variables
- **Deployment Errors:** Added `package.json` file to fix deployment errors
- **Tools:** Fixed tools issues and improved formatting
- **Docs:** Updated API reference section for `expiration date`
</Update>
<Update label="2025-03-26" description="v0.1.77">
**Bug Fixes:**
- **OpenAI Environment Variables:** Fixed issues with OpenAI environment variables
- **Deployment Errors:** Added `package.json` file to fix deployment errors
- **Tools:** Fixed tools issues and improved formatting
- **Docs:** Updated API reference section for `expiration date`
</Update>
<Update label="2025-03-19" description="v0.1.76">
**New Features:**
- **Supabase Vector Store:** Added support for Supabase Vector Store
- **Supabase History DB:** Added Supabase History DB to run Mem0 OSS on Serverless
- **Feedback Method:** Added feedback method to client
**Bug Fixes:**
- **Azure OpenAI:** Fixed issues with Azure OpenAI
- **Azure AI Search:** Fixed test cases for Azure AI Search
</Update>
</Tab>
<Tab title="TypeScript">
<Update label="2025-04-01" description="v2.1.14">
**New Features:**
- **Mastra Example:** Added Mastra example
- **Integrations:** Added Flowise integration documentation for Mem0 memory setup
**Improvements:**
- **Demo:** Updated Demo Mem0AI
- **Client:** Enhanced Ping method in Mem0 Client
- **AI SDK:** Updated AI SDK implementation
</Update>
<Update label="2025-03-29" description="v2.1.13">
**Improvements:**
- **Introuced `ping` method to check if API key is valid and populate org/project id**
</Update>
<Update label="2025-03-29" description="AI SDK v1.0.0">
**New Features:**
- **Vercel AI SDK Update:** Support threshold and rerank
**Improvements:**
- **Made add calls async to avoid blocking**
- **Bump `mem0ai` to use `2.1.12`**
</Update>
<Update label="2025-03-26" description="v2.1.12">
**New Features:**
- **Mem0 OSS:** Support infer param
**Improvements:**
- **Updated Supabase TS Docs**
- **Made package size smaller**
</Update>
<Update label="2025-03-19" description="v2.1.11">
**New Features:**
- **Supabase Vector Store Integration**
- **Feedback Method**
</Update>
</Tab>
<Tab title="Platform">
<Update label="2025-03-28" description="">
- **Updated Playground Prompt**
- **Send Email on User Addition to Org/Proj**
- **Fix Search Entity**
</Update>
<Update label="2025-03-19" description="">
- **General Stability & Performance Improvements**
</Update>
</Tab>
</Tabs>
@@ -6,7 +6,8 @@ To use Azure OpenAI embedding models, set the `EMBEDDING_AZURE_OPENAI_API_KEY`,
### Usage
```python
<CodeGroup>
```python Python
import os
from mem0 import Memory
@@ -46,6 +47,36 @@ messages = [
m.add(messages, user_id="john")
```
```typescript TypeScript
import { Memory } from 'mem0ai/oss';
const config = {
embedder: {
provider: "azure_openai",
config: {
model: "text-embedding-3-large",
modelProperties: {
endpoint: "your-api-base-url",
deployment: "your-deployment-name",
apiVersion: "version-to-use",
}
}
}
}
const memory = new Memory(config);
const messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
{"role": "user", "content": "I’m not a big fan of thriller movies but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
]
await memory.add(messages, { userId: "john" });
```
</CodeGroup>
### Config
Here are the parameters available for configuring Azure OpenAI embedder:
@@ -42,6 +42,32 @@ messages = [
]
m.add(messages, user_id="alice", metadata={"category": "movies"})
```
```typescript TypeScript
import { Memory } from "mem0ai";
import { OpenAIEmbeddings } from "@langchain/openai";
const embeddings = new OpenAIEmbeddings();
const config = {
"embedder": {
"provider": "langchain",
"config": {
"model": embeddings
}
}
}
const memory = new Memory(config);
const messages = [
{ role: "user", content: "I'm planning to watch a movie tonight. Any recommendations?" },
{ role: "assistant", content: "How about a thriller movies? They can be quite engaging." },
{ role: "user", content: "I'm not a big fan of thriller movies but I love sci-fi movies." },
{ role: "assistant", content: "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future." }
]
memory.add(messages, user_id="alice", metadata={"category": "movies"})
```
</CodeGroup>
## Supported LangChain Embedding Providers
+36 -2
View File
@@ -2,6 +2,8 @@
title: Azure OpenAI
---
<Note> Mem0 Now Supports Azure OpenAI Models in TypeScript SDK </Note>
To use Azure OpenAI models, you have to set the `LLM_AZURE_OPENAI_API_KEY`, `LLM_AZURE_ENDPOINT`, `LLM_AZURE_DEPLOYMENT` and `LLM_AZURE_API_VERSION` environment variables. You can obtain the Azure API key from the [Azure](https://azure.microsoft.com/).
> **Note**: The following are currently unsupported with reasoning models `Parallel tool calling`,`temperature`, `top_p`, `presence_penalty`, `frequency_penalty`, `logprobs`, `top_logprobs`, `logit_bias`, `max_tokens`
@@ -9,7 +11,8 @@ To use Azure OpenAI models, you have to set the `LLM_AZURE_OPENAI_API_KEY`, `LLM
## Usage
```python
<CodeGroup>
```python Python
import os
from mem0 import Memory
@@ -48,7 +51,38 @@ messages = [
m.add(messages, user_id="alice", metadata={"category": "movies"})
```
We also support the new [OpenAI structured-outputs](https://platform.openai.com/docs/guides/structured-outputs/introduction) model.
```typescript TypeScript
import { Memory } from 'mem0ai/oss';
const config = {
llm: {
provider: 'azure_openai',
config: {
apiKey: process.env.AZURE_OPENAI_API_KEY || '',
modelProperties: {
endpoint: 'https://your-api-base-url',
deployment: 'your-deployment-name',
modelName: 'your-model-name',
apiVersion: 'version-to-use',
// Any other parameters you want to pass to the Azure OpenAI API
},
},
},
};
const memory = new Memory(config);
const messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
{"role": "user", "content": "I’m not a big fan of thriller movies but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
]
await memory.add(messages, { userId: "alice", metadata: { category: "movies" } });
```
</CodeGroup>
We also support the new [OpenAI structured-outputs](https://platform.openai.com/docs/guides/structured-outputs/introduction) model. Typescript SDK does not support the `azure_openai_structured` model yet.
```python
import os
+31
View File
@@ -43,6 +43,37 @@ messages = [
]
m.add(messages, user_id="alice", metadata={"category": "movies"})
```
```typescript TypeScript
import { Memory } from "mem0ai";
import { ChatOpenAI } from "@langchain/openai";
const openai_model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0.2,
max_tokens: 2000
})
const config = {
"llm": {
"provider": "langchain",
"config": {
"model": openai_model
}
}
}
const memory = new Memory(config);
const messages = [
{ role: "user", content: "I'm planning to watch a movie tonight. Any recommendations?" },
{ role: "assistant", content: "How about a thriller movies? They can be quite engaging." },
{ role: "user", content: "I'm not a big fan of thriller movies but I love sci-fi movies." },
{ role: "assistant", content: "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future." }
]
memory.add(messages, user_id="alice", metadata={"category": "movies"})
```
</CodeGroup>
## Supported LangChain Providers
+29 -2
View File
@@ -2,11 +2,12 @@
title: Mistral AI
---
To use mistral's models, please Obtain the Mistral AI api key from their [console](https://console.mistral.ai/). Set the `MISTRAL_API_KEY` environment variable to use the model as given below in the example.
To use mistral's models, please obtain the Mistral AI api key from their [console](https://console.mistral.ai/). Set the `MISTRAL_API_KEY` environment variable to use the model as given below in the example.
## Usage
```python
<CodeGroup>
```python Python
import os
from mem0 import Memory
@@ -34,6 +35,32 @@ messages = [
m.add(messages, user_id="alice", metadata={"category": "movies"})
```
```typescript TypeScript
import { Memory } from 'mem0ai/oss';
const config = {
llm: {
provider: 'mistral',
config: {
apiKey: process.env.MISTRAL_API_KEY || '',
model: 'mistral-tiny-latest', // Or 'mistral-small-latest', 'mistral-medium-latest', etc.
temperature: 0.1,
maxTokens: 2000,
},
},
};
const memory = new Memory(config);
const messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
]
await memory.add(messages, { userId: "alice", metadata: { category: "movies" } });
```
</CodeGroup>
## Config
All available parameters for the `litellm` config are present in [Master List of All Params in Config](../config).
+1 -5
View File
@@ -92,10 +92,6 @@ config = {
m = Memory.from_config(config)
```
<Note>
OpenAI structured-outputs is currently only available in the Python implementation.
</Note>
## Config
All available parameters for the `openai` config are present in [Master List of All Params in Config](../config).
All available parameters for the `openai` config are present in [Master List of All Params in Config](../config).
+1 -1
View File
@@ -19,7 +19,7 @@ config = {
"llm": {
"provider": "xai",
"config": {
"model": "grok-2-latest",
"model": "grok-3-beta",
"temperature": 0.1,
"max_tokens": 2000,
}
+1 -1
View File
@@ -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
+112
View File
@@ -0,0 +1,112 @@
---
title: LangChain
---
Mem0 supports LangChain as a provider for vector store integration. LangChain provides a unified interface to various vector databases, making it easy to integrate different vector store providers through a consistent API.
<Note>
When using LangChain as your vector store provider, you must set the collection name to "mem0". This is a required configuration for proper integration with Mem0.
</Note>
## Usage
<CodeGroup>
```python Python
import os
from mem0 import Memory
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
# Initialize a LangChain vector store
embeddings = OpenAIEmbeddings()
vector_store = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings,
collection_name="mem0" # Required collection name
)
# Pass the initialized vector store to the config
config = {
"vector_store": {
"provider": "langchain",
"config": {
"client": vector_store
}
}
}
m = Memory.from_config(config)
messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
]
m.add(messages, user_id="alice", metadata={"category": "movies"})
```
```typescript TypeScript
import { Memory } from "mem0ai";
import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore as LangchainMemoryStore } from "langchain/vectorstores/memory";
const embeddings = new OpenAIEmbeddings();
const vectorStore = new LangchainVectorStore(embeddings);
const config = {
"vector_store": {
"provider": "langchain",
"config": { "client": vectorStore }
}
}
const memory = new Memory(config);
const messages = [
{ role: "user", content: "I'm planning to watch a movie tonight. Any recommendations?" },
{ role: "assistant", content: "How about a thriller movies? They can be quite engaging." },
{ role: "user", content: "I'm not a big fan of thriller movies but I love sci-fi movies." },
{ role: "assistant", content: "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future." }
]
memory.add(messages, user_id="alice", metadata={"category": "movies"})
```
</CodeGroup>
## Supported LangChain Vector Stores
LangChain supports a wide range of vector store providers, including:
- Chroma
- FAISS
- Pinecone
- Weaviate
- Milvus
- Qdrant
- And many more
You can use any of these vector store instances directly in your configuration. For a complete and up-to-date list of available providers, refer to the [LangChain Vector Stores documentation](https://python.langchain.com/docs/integrations/vectorstores).
## Limitations
When using LangChain as a vector store provider, there are some limitations to be aware of:
1. **Bulk Operations**: The `get_all` and `delete_all` operations are not supported when using LangChain as the vector store provider. This is because LangChain's vector store interface doesn't provide standardized methods for these bulk operations across all providers.
2. **Provider-Specific Features**: Some advanced features may not be available depending on the specific vector store implementation you're using through LangChain.
## Provider-Specific Configuration
When using LangChain as a vector store provider, you'll need to:
1. Set the appropriate environment variables for your chosen vector store provider
2. Import and initialize the specific vector store class you want to use
3. Pass the initialized vector store instance to the config
<Note>
Make sure to install the necessary LangChain packages and any provider-specific dependencies.
</Note>
## Config
All available parameters for the `langchain` vector store config are present in [Master List of All Params in Config](../config).
@@ -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>
+2
View File
@@ -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>
@@ -28,6 +29,7 @@ See the list of supported vector databases below.
<Card title="Vertex AI" href="/components/vectordbs/dbs/vertex_ai"></Card>
<Card title="Weaviate" href="/components/vectordbs/dbs/weaviate"></Card>
<Card title="FAISS" href="/components/vectordbs/dbs/faiss"></Card>
<Card title="LangChain" href="/components/vectordbs/dbs/langchain"></Card>
</CardGroup>
## Usage
+12 -8
View File
@@ -73,6 +73,7 @@
"group": "Features",
"icon": "wrench",
"pages": [
"open-source/features/async-memory",
"features/openai_compatibility",
"features/custom-fact-extraction-prompt",
"features/custom-update-memory-prompt",
@@ -139,7 +140,8 @@
"components/vectordbs/dbs/supabase",
"components/vectordbs/dbs/vertex_ai",
"components/vectordbs/dbs/weaviate",
"components/vectordbs/dbs/faiss"
"components/vectordbs/dbs/faiss",
"components/vectordbs/dbs/langchain"
]
}
]
@@ -186,7 +188,7 @@
"group": "💡 Examples",
"icon": "lightbulb",
"pages": [
"examples/overview",
"examples",
"examples/mem0-demo",
"examples/ai_companion_js",
"examples/mem0-mastra",
@@ -202,8 +204,8 @@
"examples/mem0-agentic-tool",
"examples/openai-inbuilt-tools",
"examples/mem0-openai-voice-demo",
"examples/mem0-livekit-voice-agent",
"examples/email_processing"
"examples/email_processing",
"examples/youtube-assistant"
]
}
]
@@ -215,7 +217,7 @@
"group": "Integrations",
"icon": "plug",
"pages": [
"integrations/overview",
"integrations",
"integrations/vercel-ai-sdk",
"integrations/flowise",
"integrations/crewai",
@@ -228,7 +230,9 @@
"integrations/mcp-server",
"integrations/livekit",
"integrations/elevenlabs",
"integrations/pipecat"
"integrations/pipecat",
"integrations/agno",
"integrations/keywords"
]
}
]
@@ -241,7 +245,7 @@
"group": "API Reference",
"icon": "terminal",
"pages": [
"api-reference/overview",
"api-reference",
{
"group": "Memory APIs",
"icon": "microchip",
@@ -317,7 +321,7 @@
"group": "Product Updates",
"icon": "rocket",
"pages": [
"changelog/overview"
"changelog"
]
}
]
@@ -47,6 +47,10 @@ Explore how **Mem0** can power real-world applications and bring personalized, i
Add **long-term memory** to ChatGPT, Claude, or Perplexity via the **Mem0 Chrome Extension** — personalize your AI chats anywhere.
</Card>
<Card title="YouTube Assistant" icon="puzzle-piece" href="/examples/youtube-assistant">
Integrate **Mem0** into **YouTube's** native UI, providing personalized responses with video context.
</Card>
<Card title="Document Writing Assistant" icon="pen" href="/examples/document-writing">
Create a **Writing Assistant** that understands and adapts to your unique style, improving consistency and productivity.
</Card>
+11 -11
View File
@@ -20,6 +20,7 @@ pip install openai mem0ai
Below is the complete code to create and interact with a Personalized AI Tutor using Mem0:
```python
import os
from openai import OpenAI
from mem0 import Memory
@@ -54,22 +55,21 @@ class PersonalAITutor:
:param question: The question to ask the AI.
:param user_id: Optional user ID to associate with the memory.
"""
# Start a streaming chat completion request to the AI
stream = self.client.chat.completions.create(
model="gpt-4",
stream=True,
messages=[
{"role": "system", "content": "You are a personal AI Tutor."},
{"role": "user", "content": question}
]
# Start a streaming response request to the AI
response = self.client.responses.create(
model="gpt-4o",
instructions="You are a personal AI Tutor.",
input=question,
stream=True
)
# Store the question in memory
self.memory.add(question, user_id=user_id, metadata={"app_id": self.app_id})
# Print the response from the AI in real-time
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
for event in response:
if event.type == "response.output_text.delta":
print(event.delta, end="")
def get_memories(self, user_id=None):
"""
+14 -9
View File
@@ -63,18 +63,23 @@ class PersonalTravelAssistant:
def ask_question(self, question, user_id):
# Fetch previous related memories
previous_memories = self.search_memories(question, user_id=user_id)
prompt = question
if previous_memories:
prompt = f"User input: {question}\n Previous memories: {previous_memories}"
self.messages.append({"role": "user", "content": prompt})
# Generate response using GPT-4o
response = self.client.chat.completions.create(
# Build the prompt
system_message = "You are a personal AI Assistant."
if previous_memories:
prompt = f"{system_message}\n\nUser input: {question}\nPrevious memories: {', '.join(previous_memories)}"
else:
prompt = f"{system_message}\n\nUser input: {question}"
# Generate response using Responses API
response = self.client.responses.create(
model="gpt-4o",
messages=self.messages
input=prompt
)
answer = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": answer})
# Extract answer from the response
answer = response.output[0].content[0].text
# Store the question in memory
self.memory.add(question, user_id=user_id)
+56
View File
@@ -0,0 +1,56 @@
---
title: YouTube Assistant Extension
---
Enhance your YouTube experience with Mem0's **YouTube Assistant**, a Chrome extension that brings AI-powered chat directly to your YouTube videos. Get instant, personalized answers about video content while leveraging your own knowledge and memories - all without leaving the page.
## Features
- **Contextual AI Chat**: Ask questions about videos you're watching
- **Seamless Integration**: Chat interface sits alongside YouTube's native UI
- **Memory Integration**: Personalized responses based on your knowledge through Mem0
- **Real-Time Memory**: Memories are updated in real-time based on your interactions
## Demo Video
<video
autoPlay
muted
loop
playsInline
width="700"
height="400"
src="https://github.com/user-attachments/assets/c0334ccd-311b-4dd7-8034-ef88204fc751"
></video>
## Installation
This extension is not available on the Chrome Web Store yet. You can install it manually using below method:
### Manual Installation (Developer Mode)
1. **Download the Extension**: Clone or download the extension files from the [Mem0 GitHub repository](https://github.com/mem0ai/mem0/tree/main/examples).
2. **Build**: Run `npm install` followed by `npm run build` to install the dependencies and build the extension.
3. **Access Chrome Extensions**: Open Google Chrome and navigate to `chrome://extensions`.
4. **Enable Developer Mode**: Toggle the "Developer mode" switch in the top right corner.
5. **Load Unpacked Extension**: Click "Load unpacked" and select the directory containing the extension files.
6. **Confirm Installation**: The Mem0 YouTube Assistant Extension should now appear in your Chrome toolbar.
## Setup
1. **Configure API Settings**: Click the extension icon and enter your OpenAI API key (required to use the extension)
2. **Customize Settings**: Configure additional settings such as model, temperature, and memory settings
3. **Navigate to YouTube**: Start using the assistant on any YouTube video
4. **Memories**: Enter your Mem0 API key to enable personalized responses, and feed initial memories from settings
## Example Prompts
- "Can you summarize the main points of this video?"
- "Explain the concept they just mentioned"
- "How does this relate to what I already know?"
- "What are some practical applications of this topic related to my work?"
## Privacy and Data Security
Your API keys are stored locally in your browser. Your messages are sent to the Mem0 API for extracting and retrieving memories. Mem0 is committed to ensuring your data's privacy and security.
+19 -3
View File
@@ -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.
+1 -1
View File
@@ -93,7 +93,7 @@ messages = [
<CodeGroup>
```python Code
client.add(messages, user_id="alice", includes=includes)
client.add(messages, user_id="alice", excludes=excludes)
```
```json Stored Memories
@@ -286,4 +286,38 @@ Here are the available integrations for Mem0:
>
Build conversational AI agents with memory using Pipecat.
</Card>
<Card
title="Agno"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path d="M8 4h8v12h8" stroke="currentColor" strokeWidth="2" fill="none" transform="rotate(15, 12, 12)"/>
</svg>
}
href="/integrations/agno"
>
Build autonomous agents with memory using Agno framework.
</Card>
<Card
title="Keywords AI"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.07513 1.1863C9.21663 1.07722 9.39144 1.01009 9.56624 1.01009C9.83261 1.01009 10.0823 1.12756 10.2405 1.33734L15.0101 7.4964V12.4136L16.4335 13.8401C16.7582 14.1673 16.7582 14.7043 16.4335 15.0316C16.1089 15.3588 15.5762 15.3588 15.2515 15.0316L13.3453 13.1016V8.07538L8.92529 2.36944V2.36105C8.64228 2.00024 8.70887 1.4716 9.07513 1.1863ZM18.976 14.4133C18.8344 14.3778 18.7003 14.3042 18.5894 14.1925L16.9163 12.5059C16.7249 12.3129 16.6416 12.0528 16.6749 11.8094V6.88385H16.6499L11.8553 0.691225C11.7282 0.529117 11.6716 0.333133 11.6803 0.140562C11.134 0.0481292 10.5726 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20C13.9387 20 17.3456 17.7229 18.976 14.4133Z" fill="currentColor"></path>
</svg>
}
href="/integrations/keywords"
>
Build AI applications with persistent memory and comprehensive LLM observability.
</Card>
</CardGroup>
+173
View File
@@ -0,0 +1,173 @@
---
title: Agno
---
Integrate [**Mem0**](https://github.com/mem0ai/mem0) with [Agno](https://github.com/agno-agi/agno), a Python framework for building autonomous agents. This integration enables Agno agents to access persistent memory across conversations, enhancing context retention and personalization.
## Overview
1. 🧠 Store and retrieve memories from Mem0 within Agno agents
2. 🖼️ Support for multimodal interactions (text and images)
3. 🔍 Semantic search for relevant past conversations
4. 🌐 Personalized responses based on user history
## Prerequisites
Before setting up Mem0 with Agno, ensure you have:
1. Installed the required packages:
```bash
pip install agno-ai mem0ai
```
2. Valid API keys:
- [Mem0 API Key](https://app.mem0.ai/dashboard/api-keys)
- OpenAI API Key (for the agent model)
## Integration Example
The following example demonstrates how to create an Agno agent with Mem0 memory integration, including support for image processing:
```python
import base64
from pathlib import Path
from typing import Optional
from agno.agent import Agent
from agno.media import Image
from agno.models.openai import OpenAIChat
from mem0 import MemoryClient
# Initialize the Mem0 client
client = MemoryClient()
# Define the agent
agent = Agent(
name="Personal Agent",
model=OpenAIChat(id="gpt-4"),
description="You are a helpful personal agent that helps me with day to day activities."
"You can process both text and images.",
markdown=True
)
def chat_user(
user_input: Optional[str] = None,
user_id: str = "user_123",
image_path: Optional[str] = None
) -> str:
"""
Handle user input with memory integration, supporting both text and images.
Args:
user_input: The user's text input
user_id: Unique identifier for the user
image_path: Path to an image file if provided
Returns:
The agent's response as a string
"""
if image_path:
# Convert image to base64
with open(image_path, "rb") as image_file:
base64_image = base64.b64encode(image_file.read()).decode("utf-8")
# Create message objects for text and image
messages = []
if user_input:
messages.append({
"role": "user",
"content": user_input
})
messages.append({
"role": "user",
"content": {
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
})
# Store messages in memory
client.add(messages, user_id=user_id)
print("✅ Image and text stored in memory.")
if user_input:
# Search for relevant memories
memories = client.search(user_input, user_id=user_id)
memory_context = "\n".join(f"- {m['memory']}" for m in memories)
# Construct the prompt
prompt = f"""
You are a helpful personal assistant who helps users with their day-to-day activities and keeps track of everything.
Your task is to:
1. Analyze the given image (if present) and extract meaningful details to answer the user's question.
2. Use your past memory of the user to personalize your answer.
3. Combine the image content and memory to generate a helpful, context-aware response.
Here is what I remember about the user:
{memory_context}
User question:
{user_input}
"""
# Get response from agent
if image_path:
response = agent.run(prompt, images=[Image(filepath=Path(image_path))])
else:
response = agent.run(prompt)
# Store the interaction in memory
client.add(f"User: {user_input}\nAssistant: {response.content}", user_id=user_id)
return response.content
return "No user input or image provided."
# Example Usage
if __name__ == "__main__":
response = chat_user(
"This is the picture of what I brought with me in the trip to Bahamas",
image_path="travel_items.jpeg",
user_id="user_123"
)
print(response)
```
## Key Features
### 1. Multimodal Memory Storage
The integration supports storing both text and image data:
- **Text Storage**: Conversation history is saved in a structured format
- **Image Analysis**: Agents can analyze images and store visual information
- **Combined Context**: Memory retrieval combines both text and visual data
### 2. Personalized Agent Responses
Improve your agent's context awareness:
- **Memory Retrieval**: Semantic search finds relevant past interactions
- **User Preferences**: Personalize responses based on stored user information
- **Continuity**: Maintain conversation threads across multiple sessions
### 3. Flexible Configuration
Customize the integration to your needs:
- **User Identification**: Organize memories by user ID
- **Memory Search**: Configure search relevance and result count
- **Memory Formatting**: Support for various OpenAI message formats
## Help & Resources
- [Agno Documentation](https://docs.agno.com/introduction)
- [Mem0 Platform](https://app.mem0.ai/)
<Snippet file="get-help.mdx" />
+140
View File
@@ -0,0 +1,140 @@
---
title: Keywords AI
---
Build AI applications with persistent memory and comprehensive LLM observability by integrating Mem0 with Keywords AI.
## Overview
Mem0 is a self-improving memory layer for LLM applications, enabling personalized AI experiences that save costs and delight users. Keywords AI provides complete LLM observability.
Combining Mem0 with Keywords AI allows you to:
1. Add persistent memory to your AI applications
2. Track interactions across sessions
3. Monitor memory usage and retrieval with Keywords AI observability
4. Optimize token usage and reduce costs
<Note>
You can get your Mem0 API key, user_id, and org_id from the [Mem0 dashboard](https://app.mem0.ai/). These are required for proper integration.
</Note>
## Setup and Configuration
Install the necessary libraries:
```bash
pip install mem0 keywordsai-sdk
```
Set up your environment variables:
```python
import os
# Set your API keys
os.environ["MEM0_API_KEY"] = "your-mem0-api-key"
os.environ["KEYWORDSAI_API_KEY"] = "your-keywords-api-key"
os.environ["KEYWORDSAI_BASE_URL"] = "https://api.keywordsai.co/api/"
```
## Basic Integration Example
Here's a simple example of using Mem0 with Keywords AI:
```python
from mem0 import Memory
import os
# Configuration
api_key = os.getenv("MEM0_API_KEY")
keywordsai_api_key = os.getenv("KEYWORDSAI_API_KEY")
base_url = os.getenv("KEYWORDSAI_BASE_URL") # "https://api.keywordsai.co/api/"
# Set up Mem0 with Keywords AI as the LLM provider
config = {
"llm": {
"provider": "openai",
"config": {
"model": "gpt-4o-mini",
"temperature": 0.0,
"api_key": keywordsai_api_key,
"openai_base_url": base_url,
},
}
}
# Initialize Memory
memory = Memory.from_config(config_dict=config)
# Add a memory
result = memory.add(
"I like to take long walks on weekends.",
user_id="alice",
metadata={"category": "hobbies"},
)
print(result)
```
## Advanced Integration with OpenAI SDK
For more advanced use cases, you can integrate Keywords AI with Mem0 through the OpenAI SDK:
```python
from openai import OpenAI
import os
import json
# Initialize client
client = OpenAI(
api_key=os.environ.get("KEYWORDSAI_API_KEY"),
base_url=os.environ.get("KEYWORDSAI_BASE_URL"),
)
# Sample conversation messages
messages = [
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
{"role": "assistant", "content": "How about a thriller movies? They can be quite engaging."},
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
]
# Add memory and generate a response
response = client.chat.completions.create(
model="openai/gpt-4o",
messages=messages,
extra_body={
"mem0_params": {
"user_id": "test_user",
"org_id": "org_1",
"api_key": os.environ.get("MEM0_API_KEY"),
"add_memories": {
"messages": messages,
},
}
},
)
print(json.dumps(response.model_dump(), indent=4))
```
For detailed information on this integration, refer to the official [Keywords AI Mem0 integration documentation](https://docs.keywordsai.co/integration/development-frameworks/mem0).
## Key Features
1. **Memory Integration**: Store and retrieve relevant information from past interactions
2. **LLM Observability**: Track memory usage and retrieval patterns with Keywords AI
3. **Session Persistence**: Maintain context across multiple user sessions
4. **Cost Optimization**: Reduce token usage through efficient memory retrieval
## Conclusion
Integrating Mem0 with Keywords AI provides a powerful combination for building AI applications with persistent memory and comprehensive observability. This integration enables more personalized user experiences while providing insights into your application's memory usage.
## Help
For more information on using Mem0 and Keywords AI together, refer to:
- [Mem0 Documentation](https://docs.mem0.ai)
- [Keywords AI Documentation](https://docs.keywordsai.co)
<Snippet file="get-help.mdx" />
+169
View File
@@ -0,0 +1,169 @@
---
title: Async Memory
description: 'Asynchronous memory for Mem0'
icon: "bolt"
iconType: "solid"
---
## AsyncMemory
The `AsyncMemory` class is a direct asynchronous interface to Mem0's in-process memory operations. Unlike the memory, which interacts with an API, `AsyncMemory` works directly with the underlying storage systems. This makes it ideal for applications where you want to embed Mem0 directly into your codebase.
### Initialization
To use `AsyncMemory`, import it from the `mem0.memory` module:
```python Python
import asyncio
from mem0 import AsyncMemory
# Initialize with default configuration
memory = AsyncMemory()
# Or initialize with custom configuration
from mem0.configs.base import MemoryConfig
custom_config = MemoryConfig(
# Your custom configuration here
)
memory = AsyncMemory(config=custom_config)
```
### Key Features
1. **Non-blocking Operations** - All memory operations use `asyncio` to avoid blocking the event loop
2. **Concurrent Processing** - Parallel execution of vector store and graph operations
3. **Efficient Resource Utilization** - Better handling of I/O bound operations
4. **Compatible with Async Frameworks** - Seamless integration with FastAPI, aiohttp, and other async frameworks
### Methods
All methods in `AsyncMemory` have the same parameters as the synchronous `Memory` class but are designed to be used with `async/await`.
#### Create memories
Add a new memory asynchronously:
```python Python
await memory.add(
messages=[
{"role": "user", "content": "I'm travelling to SF"},
{"role": "assistant", "content": "That's great to hear!"}
],
user_id="alice"
)
```
#### Retrieve memories
Retrieve memories related to a query:
```python Python
await memory.search(
query="Where am I travelling?",
user_id="alice"
)
```
#### List memories
List all memories for a `user_id`, `agent_id`, or `run_id`:
```python Python
await memory.get_all(user_id="alice")
```
#### Get specific memory
Retrieve a specific memory by its ID:
```python Python
await memory.get(memory_id="memory-id-here")
```
#### Update memory
Update an existing memory by ID:
```python Python
await memory.update(
memory_id="memory-id-here",
data="I'm travelling to Seattle"
)
```
#### Delete memory
Delete a specific memory by ID:
```python Python
await memory.delete(memory_id="memory-id-here")
```
#### Delete all memories
Delete all memories for a specific user, agent, or run:
```python Python
await memory.delete_all(user_id="alice")
```
Note: At least one filter (user_id, agent_id, or run_id) is required when using delete_all.
#### Memory History
Get the history of changes for a specific memory:
```python Python
await memory.history(memory_id="memory-id-here")
```
### Example: Concurrent Usage with Other APIs
`AsyncMemory` can be effectively combined with other async operations. Here's an example showing how to use it alongside OpenAI API calls in separate threads:
```python Python
import asyncio
from openai import AsyncOpenAI
from mem0 import AsyncMemory
async_openai_client = AsyncOpenAI()
async_memory = AsyncMemory()
async def chat_with_memories(message: str, user_id: str = "default_user") -> str:
# Retrieve relevant memories
search_result = await async_memory.search(query=message, user_id=user_id, limit=3)
relevant_memories = search_result["results"]
memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_memories)
# Generate Assistant response
system_prompt = f"You are a helpful AI. Answer the question based on query and memories.\nUser Memories:\n{memories_str}"
messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": message}]
response = await async_openai_client.chat.completions.create(model="gpt-4o-mini", messages=messages)
assistant_response = response.choices[0].message.content
# Create new memories from the conversation
messages.append({"role": "assistant", "content": assistant_response})
await async_memory.add(messages, user_id=user_id)
return assistant_response
async def async_main():
print("Chat with AI (type 'exit' to quit)")
while True:
user_input = input("You: ").strip()
if user_input.lower() == 'exit':
print("Goodbye!")
break
response = await chat_with_memories(user_input)
print(f"AI: {response}")
def main():
asyncio.run(async_main())
if __name__ == "__main__":
main()
```
If you have any questions or need further assistance, please don't hesitate to reach out:
<Snippet file="get-help.mdx" />
+10
View File
@@ -28,6 +28,16 @@ from mem0 import Memory
os.environ["OPENAI_API_KEY"] = "your-api-key"
m = Memory()
```
</Tab>
<Tab title="Async">
```python
import os
from mem0 import AsyncMemory
os.environ["OPENAI_API_KEY"] = "your-api-key"
m = AsyncMemory()
```
</Tab>
<Tab title="Advanced">
+136 -156
View File
@@ -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/": {
@@ -1249,7 +1229,7 @@
},
{
"lang": "cURL",
"source": "curl -X POST 'https://api.mem0.ai/v2/memories/' \\\n-H 'Authorization: Token your-api-key' \\\n-H 'Content-Type: application/json' \\\n-d '{\n \"filters\": {\n \"AND\": [\n { \"user_id\": \"alex\" },\n { \"created_at\": { \"gte\": \"2024-07-01\", \"lte\": \"2024-07-31\" } }\n ]\n }\n}'"
"source": "curl -X POST 'https://api.mem0.ai/v2/memories/' \\\n-H 'Authorization: Token your-api-key' \\\n-H 'Content-Type: application/json' \\\n-d '{\n \"filters\": {\n \"AND\": [\n { \"user_id\": \"alex\" },\n { \"created_at\": { \"gte\": \"2024-07-01\", \"lte\": \"2024-07-31\" } }\n ]\n },\n \"org_id\": \"your-org-id\",\n \"project_id\": \"your-project-id\"\n}'"
},
{
"lang": "Go",
@@ -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 -1
View File
@@ -5,7 +5,7 @@ iconType: "solid"
---
<Note type="info">
🎉 We now support [Claude 3.7 Sonnet](components/llms/models/anthropic)! Enhance your AI assistants with the latest and most capable language model from Anthropic.
🎉 We now support [Grok 3](components/llms/models/xAI)! Enhance your AI assistants with the latest and most capable language model from xAI.
</Note>
# Introduction
+164
View File
@@ -0,0 +1,164 @@
"""
Simple Fitness Memory Tracker that tracks your fitness progress and knows your health priorities.
Uses Mem0 for memory and GPT-4o for image understanding.
In order to run this file, you need to set up your Mem0 API at Mem0 platform and also need an OpenAI API key.
export OPENAI_API_KEY="your_openai_api_key"
export MEM0_API_KEY="your_mem0_api_key"
"""
from mem0 import MemoryClient
from agno.agent import Agent
from agno.models.openai import OpenAIChat
# Initialize memory
memory_client = MemoryClient(api_key="your-mem0-api-key")
USER_ID = "Anish"
agent = Agent(
name="Fitness Agent",
model=OpenAIChat(id="gpt-4o"),
description="You are a helpful fitness assistant who remembers past logs and gives personalized suggestions for Anish's training and diet.",
markdown=True
)
# Store user preferences as memory
def store_user_preferences(conversation: list, user_id: str = USER_ID):
"""Store user preferences from conversation history"""
memory_client.add(conversation, user_id=user_id, output_format='v1.1')
# Memory-aware assistant function
def fitness_coach(user_input: str, user_id: str = USER_ID):
memories = memory_client.search(user_input, user_id=user_id) # Search relevant memories bases on user query
memory_context = "\n".join(f"- {m['memory']}" for m in memories)
prompt = f"""You are a fitness assistant who helps Anish with his training, recovery, and diet. You have long-term memory of his health, routines, preferences, and past conversations.
Use your memory to personalize suggestions — consider his constraints, goals, patterns, and lifestyle when responding.
Here is what you remember about {user_id}:
{memory_context}
User query:
{user_input}"""
response = agent.run(prompt)
memory_client.add(f"User: {user_input}\nAssistant: {response.content}", user_id=user_id)
return response.content
# --------------------------------------------------
# Store user preferences and memories
messages = [
{
"role": "user",
"content": "Hi, I’m Anish. I'm 26 years old, 5'10\", and weigh 72kg. I started working out 6 months ago with the goal of building lean muscle."
},
{
"role": "assistant",
"content": "Got it — you're 26, 5'10\", 72kg, and on a lean muscle journey. Started gym 6 months ago."
},
{
"role": "user",
"content": "I follow a push-pull-legs routine and train 5 times a week. My rest days are Wednesday and Sunday."
},
{
"role": "assistant",
"content": "Understood — push-pull-legs split, training 5x/week with rest on Wednesdays and Sundays."
},
{
"role": "user",
"content": "After push days, I usually eat high-protein and moderate-carb meals to recover."
},
{
"role": "assistant",
"content": "Noted — high-protein, moderate-carb meals after push workouts."
},
{
"role": "user",
"content": "For pull days, I take whey protein and eat a banana after training."
},
{
"role": "assistant",
"content": "Logged — whey protein and banana post pull workouts."
},
{
"role": "user",
"content": "On leg days, I make sure to have complex carbs like rice or oats."
},
{
"role": "assistant",
"content": "Noted — complex carbs like rice and oats are part of your leg day meals."
},
{
"role": "user",
"content": "I often feel sore after leg days, so I use turmeric milk and magnesium to help with recovery."
},
{
"role": "assistant",
"content": "I'll remember turmeric milk and magnesium as part of your leg day recovery."
},
{
"role": "user",
"content": "Last push day, I did 3x8 bench press at 60kg, 4x12 overhead press, and dips. Felt fatigued after."
},
{
"role": "assistant",
"content": "Push day logged — 60kg bench, overhead press, dips. You felt fatigued afterward."
},
{
"role": "user",
"content": "I prefer light dinners post-workout like tofu, soup, and vegetables."
},
{
"role": "assistant",
"content": "Got it — light dinners post-workout: tofu, soup, and veggies."
},
{
"role": "user",
"content": "I have mild lactose intolerance, so I avoid dairy. I use almond milk or lactose-free whey."
},
{
"role": "assistant",
"content": "Understood — avoiding regular dairy, using almond milk and lactose-free whey."
},
{
"role": "user",
"content": "I get occasional knee pain, so I avoid deep squats and do more hamstring curls and glute bridges on leg days."
},
{
"role": "assistant",
"content": "Noted — due to knee discomfort, you substitute deep squats with curls and glute bridges."
},
{
"role": "user",
"content": "I track sleep and notice poor performance when I sleep less than 6 hours."
},
{
"role": "assistant",
"content": "Logged — performance drops when you get under 6 hours of sleep."
},
{
"role": "user",
"content": "I take magnesium supplements to help with muscle recovery and sleep quality."
},
{
"role": "assistant",
"content": "Remembered — magnesium helps you with recovery and sleep."
},
{
"role": "user",
"content": "I avoid caffeine after 4 PM because it affects my sleep."
},
{
"role": "assistant",
"content": "Got it — you avoid caffeine post-4 PM to protect your sleep."
},
]
store_user_preferences(messages)
# Example usage with fitness coach
fitness_coach("How much was I lifting for bench press a month ago?")
# OUTPUT: A month ago, you were lifting 55kg for your bench press as part of your push day routine. It looks like you've increased your bench press weight by 5kg since then! Keep up the good work on your journey to gain lean muscle.
fitness_coach("Suggest a post-workout meal, but I’ve had poor sleep last night.")
# OUTPUT: Anish, since you had poor sleep, focus on a recovery-friendly, lactose-free meal: tofu or chicken for protein, paired with quinoa or brown rice for lasting energy. Turmeric almond milk will help with inflammation. Based on your past leg day recovery, continue magnesium, stay well-hydrated, and avoid caffeine after 4PM. Aim for 7–8 hours of sleep, and consider light stretching or a warm bath to ease soreness.
@@ -0,0 +1,88 @@
"""
Memory-Powered Movie Recommendation Assistant (Grok 3 + Mem0)
This script builds a personalized movie recommender that remembers your preferences
(e.g. dislikes horror, loves romcoms) using Mem0 as a memory layer and Grok 3 for responses.
In order to run this file, you need to set up your Mem0 API at Mem0 platform and also need an XAI API key.
export XAI_API_KEY="your_xai_api_key"
export MEM0_API_KEY="your_mem0_api_key"
"""
from mem0 import Memory
from openai import OpenAI
# Configure Mem0 with Grok 3 and Qdrant
config = {
"vector_store": {
"provider": "qdrant",
"config": {
"embedding_model_dims": 384
}
},
"llm": {
"provider": "xai",
"config": {
"model": "grok-3-beta",
"temperature": 0.1,
"max_tokens": 2000,
}
},
"embedder": {
"provider": "huggingface",
"config": {
"model": "all-MiniLM-L6-v2" # open embedding model
}
}
}
# Instantiate memory layer
memory = Memory.from_config(config)
# Initialize Grok 3 client
grok_client = OpenAI(
api_key=XAI_API_KEY,
base_url="https://api.x.ai/v1",
)
def recommend_movie_with_memory(user_id: str, user_query: str):
# Retrieve prior memory about movies
past_memories = memory.search("movie preferences", user_id=user_id)
prompt = user_query
if past_memories:
prompt += f"\nPreviously, the user mentioned: {past_memories}"
# Generate movie recommendation using Grok 3
response = grok_client.chat.completions.create(
model="grok-3-beta",
messages=[
{"role": "user", "content": prompt}
]
)
recommendation = response.choices[0].message.content
# Store conversation in memory
memory.add(
[{"role": "user", "content": user_query},
{"role": "assistant", "content": recommendation}],
user_id=user_id,
metadata={"category": "movie"}
)
return recommendation
# Example Usage
if __name__ == "__main__":
user_id = "arshi"
recommend_movie_with_memory(user_id, "I'm looking for a movie to watch tonight. Any suggestions?")
# OUTPUT: You have watched Intersteller last weekend and you don't like horror movies, maybe you can watch "Purple Hearts" today.
recommend_movie_with_memory(user_id, "Can we skip the tearjerkers? I really enjoyed Notting Hill and Crazy Rich Asians.")
# OUTPUT: Got it — no sad endings! You might enjoy "The Proposal" or "Love, Rosie". They’re both light-hearted romcoms with happy vibes.
recommend_movie_with_memory(user_id, "Any light-hearted movie I can watch after work today?")
# OUTPUT: Since you liked Crazy Rich Asians and The Proposal, how about "The Intern" or "Isn’t It Romantic"? Both are upbeat, funny, and perfect for relaxing.
recommend_movie_with_memory(user_id, "I’ve already watched The Intern. Something new maybe?")
# OUTPUT: No problem! Try "Your Place or Mine" - romcoms that match your taste and are tear-free!
+101
View File
@@ -0,0 +1,101 @@
"""
Create your personal AI Assistant powered by memory that supports both text and images and remembers your preferences
In order to run this file, you need to set up your Mem0 API at Mem0 platform and also need a OpenAI API key.
export OPENAI_API_KEY="your_openai_api_key"
export MEM0_API_KEY="your_mem0_api_key"
"""
import base64
from pathlib import Path
from agno.agent import Agent
from agno.media import Image
from agno.models.openai import OpenAIChat
from mem0 import MemoryClient
# Initialize the Mem0 client
client = MemoryClient()
# Define the agent
agent = Agent(
name="Personal Agent",
model=OpenAIChat(id="gpt-4o"),
description="You are a helpful personal agent that helps me with day to day activities."
"You can process both text and images.",
markdown=True
)
# Function to handle user input with memory integration with support for images
def chat_user(user_input: str = None, user_id: str = "user_123", image_path: str = None):
if image_path:
with open(image_path, "rb") as image_file:
base64_image = base64.b64encode(image_file.read()).decode("utf-8")
# First: the text message
text_msg = {
"role": "user",
"content": user_input
}
# Second: the image message
image_msg = {
"role": "user",
"content": {
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
}
# Send both as separate message objects
client.add([text_msg, image_msg], user_id=user_id, output_format='v1.1')
print("✅ Image uploaded and stored in memory.")
if user_input:
memories = client.search(user_input, user_id=user_id)
memory_context = "\n".join(f"- {m['memory']}" for m in memories)
prompt = f"""
You are a helpful personal assistant who helps user with his day-to-day activities and keep track of everything.
Your task is to:
1. Analyze the given image (if present) and extract meaningful details to answer the user's question.
2. Use your past memory of the user to personalize your answer.
3. Combine the image content and memory to generate a helpful, context-aware response.
Here is what remember about the user:
{memory_context}
User question:
{user_input}
"""
if image_path:
response = agent.run(prompt, images=[Image(filepath=Path(image_path))])
else:
response = agent.run(prompt)
client.add(f"User: {user_input}\nAssistant: {response.content}", user_id=user_id)
return response.content
return "No user input or image provided."
# Example Usage
user_id = "user_123"
print(chat_user("What did I ask you to remind me about?", user_id))
# # OUTPUT: You asked me to remind you to call your mom tomorrow. 📞
#
print(chat_user("When is my test?", user_id=user_id))
# OUTPUT: Your pilot's test is on your birthday, which is in five days. You're turning 25!
# Good luck with your preparations, and remember to take some time to relax amidst the studying.
print(chat_user("This is the picture of what I brought with me in the trip to Bahamas",
image_path="travel_items.jpeg", # this will be added to Mem0 memory
user_id=user_id))
print(chat_user("hey can you quickly tell me if brought my sunglasses to my trip, not able to find",
user_id=user_id))
# OUTPUT: Yes, you did bring your sunglasses on your trip to the Bahamas along with your laptop, face masks and other items..
# Since you can't find them now, perhaps check the pockets of jackets you wore or in your luggage compartments.
+84
View File
@@ -0,0 +1,84 @@
"""
Create your personal AI Study Buddy that remembers what you’ve studied (and where you struggled),
helps with spaced repetition and topic review, personalizes responses using your past interactions.
Supports both text and PDF/image inputs.
In order to run this file, you need to set up your Mem0 API at Mem0 platform and also need a OpenAI API key.
export OPENAI_API_KEY="your_openai_api_key"
export MEM0_API_KEY="your_mem0_api_key"
"""
import asyncio
from mem0 import MemoryClient
from agents import Agent, Runner
client = MemoryClient()
# Define your study buddy agent
study_agent = Agent(
name="StudyBuddy",
instructions="""You are a helpful study coach. You:
- Track what the user has studied before
- Identify topics the user has struggled with (e.g., "I'm confused", "this is hard")
- Help with spaced repetition by suggesting topics to revisit based on last review time
- Personalize answers using stored memories
- Summarize PDFs or notes the user uploads""")
# Upload and store PDF to Mem0
def upload_pdf(pdf_url: str, user_id: str):
pdf_message = {
"role": "user",
"content": {
"type": "pdf_url",
"pdf_url": {"url": pdf_url}
}
}
client.add([pdf_message], user_id=user_id)
print("✅ PDF uploaded and processed into memory.")
# Main interaction loop with your personal study buddy
async def study_buddy(user_id: str, topic: str, user_input: str):
memories = client.search(f"{topic}", user_id=user_id)
memory_context = "n".join(f"- {m['memory']}" for m in memories)
prompt = f"""
You are helping the user study the topic: {topic}.
Here are past memories from previous sessions:
{memory_context}
Now respond to the user's new question or comment:
{user_input}
"""
result = await Runner.run(study_agent, prompt)
response = result.final_output
client.add([
{"role": "user", "content": f'''Topic: {topic}nUser: {user_input}nnStudy Assistant: {response}'''}
], user_id=user_id, metadata={"topic": topic})
return response
# Example usage
async def main():
user_id = "Ajay"
pdf_url = "https://pages.physics.ua.edu/staff/fabi/ph101/classnotes/8RotD101.pdf"
upload_pdf(pdf_url, user_id) # Upload a relevant lecture PDF to memory
topic = "Lagrangian Mechanics"
# Demonstrate tracking previously learned topics
print(await study_buddy(user_id, topic, "Can you remind me of what we discussed about generalized coordinates?"))
# Demonstrate weakness detection
print(await study_buddy(user_id, topic, "I still don’t get what frequency domain really means."))
# Demonstrate spaced repetition prompting
topic = "Momentum Conservation"
print(await study_buddy(user_id, topic, "I think we covered this last week. Is it time to review momentum conservation again?"))
if __name__ == "__main__":
asyncio.run(main())
+240
View File
@@ -0,0 +1,240 @@
"""
Personal Voice Assistant with Memory (Whisper + CrewAI + Mem0 + ElevenLabs)
This script creates a personalized AI assistant that can:
- Understand voice commands using Whisper (OpenAI STT)
- Respond intelligently using CrewAI Agent and LLMs
- Remember user preferences and facts using Mem0 memory
- Speak responses back using ElevenLabs text-to-speech
Initial user memory is bootstrapped from predefined preferences, and the assistant can remember new context dynamically over time.
To run this file, you need to set the following environment variables:
export OPENAI_API_KEY="your_openai_api_key"
export MEM0_API_KEY="your_mem0_api_key"
export ELEVENLABS_API_KEY="your_elevenlabs_api_key"
You must also have:
- A working microphone setup (pyaudio)
- A valid ElevenLabs voice ID
- Python packages: openai, elevenlabs, crewai, mem0ai, pyaudio
"""
import tempfile
import wave
import pyaudio
from elevenlabs.client import ElevenLabs
from elevenlabs import play
from crewai import Agent, Task, Crew, Process
from openai import OpenAI
from mem0 import MemoryClient
# ------------------ SETUP ------------------
USER_ID = "Alex"
openai_client = OpenAI()
tts_client = ElevenLabs()
memory_client = MemoryClient()
# Function to store user preferences in memory
def store_user_preferences(user_id: str, conversation: list):
"""Store user preferences from conversation history"""
memory_client.add(conversation, user_id=user_id)
# Initialize memory with some basic preferences
def initialize_memory():
# Example conversation storage with voice assistant relevant preferences
messages = [
{
"role": "user",
"content": "Hi, my name is Alex Thompson. I'm 32 years old and work as a software engineer at TechCorp.",
},
{
"role": "assistant",
"content": "Hello Alex Thompson! Nice to meet you. I've noted that you're 32 and work as a software engineer at TechCorp. How can I help you today?",
},
{
"role": "user",
"content": "I prefer brief and concise responses without unnecessary explanations. I get frustrated when assistants are too wordy or repeat information I already know."
},
{
"role": "assistant",
"content": "Got it. I'll keep my responses short, direct, and without redundancy.",
},
{
"role": "user",
"content": "I like to listen to jazz music when I'm working, especially artists like Miles Davis and John Coltrane. I find it helps me focus and be more productive."
},
{
"role": "assistant",
"content": "I'll remember your preference for jazz while working, particularly Miles Davis and John Coltrane. It's great for focus.",
},
{
"role": "user",
"content": "I usually wake up at 7 AM and prefer reminders for meetings 30 minutes in advance. My most productive hours are between 9 AM and noon, so I try to schedule important tasks during that time."
},
{
"role": "assistant",
"content": "Noted. You wake up at 7 AM, need meeting reminders 30 minutes ahead, and are most productive between 9 AM and noon for important tasks.",
},
{
"role": "user",
"content": "My favorite color is navy blue, and I prefer dark mode in all my apps. I'm allergic to peanuts, so please remind me to check ingredients when I ask about recipes or restaurants."
},
{
"role": "assistant",
"content": "I've noted that you prefer navy blue and dark mode interfaces. I'll also help you remember to check for peanuts in food recommendations due to your allergy.",
},
{
"role": "user",
"content": "My partner's name is Jamie, and we have a golden retriever named Max who is 3 years old. My parents live in Chicago, and I try to visit them once every two months."
},
{
"role": "assistant",
"content": "I'll remember that your partner is Jamie, your dog Max is a 3-year-old golden retriever, and your parents live in Chicago whom you visit bimonthly.",
},
]
# Store the initial preferences
store_user_preferences(USER_ID, messages)
print("✅ Memory initialized with user preferences")
voice_agent = Agent(
role="Memory-based Voice Assistant",
goal="Help the user with day-to-day tasks and remember their preferences over time.",
backstory="You are a voice assistant who understands the user well and converse with them.",
verbose=True,
memory=True,
memory_config={
"provider": "mem0",
"config": {"user_id": USER_ID},
},
)
# ------------------ AUDIO RECORDING ------------------
def record_audio(filename="input.wav", record_seconds=5):
print("🎙️ Recording (speak now)...")
chunk = 1024
fmt = pyaudio.paInt16
channels = 1
rate = 44100
p = pyaudio.PyAudio()
stream = p.open(format=fmt, channels=channels, rate=rate, input=True, frames_per_buffer=chunk)
frames = []
for _ in range(0, int(rate / chunk * record_seconds)):
data = stream.read(chunk)
frames.append(data)
stream.stop_stream()
stream.close()
p.terminate()
with wave.open(filename, 'wb') as wf:
wf.setnchannels(channels)
wf.setsampwidth(p.get_sample_size(fmt))
wf.setframerate(rate)
wf.writeframes(b''.join(frames))
# ------------------ STT USING WHISPER ------------------
def transcribe_whisper(audio_path):
print("🔎 Transcribing with Whisper...")
try:
with open(audio_path, "rb") as audio_file:
transcript = openai_client.audio.transcriptions.create(
model="whisper-1",
file=audio_file
)
print(f"🗣️ You said: {transcript.text}")
return transcript.text
except Exception as e:
print(f"Error during transcription: {e}")
return ""
# ------------------ AGENT RESPONSE ------------------
def get_agent_response(user_input):
if not user_input:
return "I didn't catch that. Could you please repeat?"
try:
task = Task(
description=f"Respond to: {user_input}",
expected_output="A short and relevant reply.",
agent=voice_agent
)
crew = Crew(
agents=[voice_agent],
tasks=[task],
process=Process.sequential,
verbose=True,
memory=True,
memory_config={
"provider": "mem0",
"config": {"user_id": USER_ID}
}
)
result = crew.kickoff()
# Extract the text response from the complex result object
if hasattr(result, 'raw'):
return result.raw
elif isinstance(result, dict) and 'raw' in result:
return result['raw']
elif isinstance(result, dict) and 'tasks_output' in result:
outputs = result['tasks_output']
if outputs and isinstance(outputs, list) and len(outputs) > 0:
return outputs[0].get('raw', str(result))
# Fallback to string representation if we can't extract the raw response
return str(result)
except Exception as e:
print(f"Error getting agent response: {e}")
return "I'm having trouble processing that request. Can we try again?"
# ------------------ SPEAK WITH ELEVENLABS ------------------
def speak_response(text):
print(f"🤖 Agent: {text}")
audio = tts_client.text_to_speech.convert(
text=text,
voice_id="JBFqnCBsd6RMkjVDRZzb",
model_id="eleven_multilingual_v2",
output_format="mp3_44100_128"
)
play(audio)
# ------------------ MAIN LOOP ------------------
def run_voice_agent():
print("🧠 Voice agent (Whisper + Mem0 + ElevenLabs) is ready! Say something.")
while True:
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_audio:
record_audio(tmp_audio.name)
try:
user_text = transcribe_whisper(tmp_audio.name)
if user_text.lower() in ['exit', 'quit', 'stop']:
print("👋 Exiting.")
break
response = get_agent_response(user_text)
speak_response(response)
except Exception as e:
print(f"❌ Error: {e}")
if __name__ == "__main__":
try:
# Initialize memory with user preferences before starting the voice agent (this can be done once)
initialize_memory()
# Run the voice assistant
run_voice_agent()
except KeyboardInterrupt:
print("\n👋 Program interrupted. Exiting.")
except Exception as e:
print(f"❌ Fatal error: {e}")
+4
View File
@@ -0,0 +1,4 @@
node_modules
.env*
dist
package-lock.json
+88
View File
@@ -0,0 +1,88 @@
# Mem0 Assistant Chrome Extension
A powerful Chrome extension that combines AI chat with your personal knowledge base through mem0. Get instant, personalized answers about video content while leveraging your own knowledge and memories - all without leaving the page.
## Development
1. Install dependencies:
```bash
npm install
```
2. Start development mode:
```bash
npm run watch
```
3. Build for production:
```bash
npm run build
```
## Features
- AI-powered chat interface directly in YouTube
- Memory capabilities powered by Mem0
- Dark mode support
- Customizable options
## Permissions
- activeTab: For accessing the current tab
- storage: For saving user preferences
- scripting: For injecting content scripts
## Host Permissions
- youtube.com
- openai.com
- mem0.ai
## Features
- **Contextual AI Chat**: Ask questions about videos you're watching
- **Seamless Integration**: Chat interface sits alongside YouTube's native UI
- **OpenAI-Powered**: Uses GPT models for intelligent responses
- **Customizable**: Configure model settings, appearance, and behavior
- **Future mem0 Integration**: Personalized responses based on your knowledge (coming soon)
## Installation
### From Source (Developer Mode)
1. Download or clone this repository
2. Open Chrome and navigate to `chrome://extensions/`
3. Enable "Developer mode" (toggle in the top-right corner)
4. Click "Load unpacked" and select the extension directory
5. The extension should now be installed and visible in your toolbar
### Setup
1. Click the extension icon in your toolbar
2. Enter your OpenAI API key (required to use the extension)
3. Configure additional settings if desired
4. Navigate to YouTube to start using the assistant
## Usage
1. Visit any YouTube video
2. Click the AI assistant icon in the corner of the page to open the chat interface
3. Ask questions about the video content
4. The AI will respond with contextual information
### Example Prompts
- "Can you summarize the main points of this video?"
- "What is the speaker explaining at 5:23?"
- "Explain the concept they just mentioned"
- "How does this relate to [topic I'm learning about]?"
- "What are some practical applications of what's being discussed?"
- **API Settings**: Change model, adjust tokens, modify temperature
- **Interface Settings**: Control where and how the chat appears
- **Behavior Settings**: Configure auto-context extraction
## Privacy & Data
- Your API keys are stored locally in your browser
- Video context and transcript is processed locally and only sent to OpenAI when you ask questions
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,45 @@
{
"manifest_version": 3,
"name": "YouTube Assistant powered by Mem0",
"version": "1.0",
"description": "An AI-powered YouTube assistant with memory capabilities from Mem0",
"permissions": [
"activeTab",
"storage",
"scripting"
],
"host_permissions": [
"https://*.youtube.com/*",
"https://*.openai.com/*",
"https://*.mem0.ai/*"
],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'",
"sandbox": "sandbox allow-scripts; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self'"
},
"action": {
"default_popup": "public/popup.html"
},
"options_page": "public/options.html",
"content_scripts": [
{
"matches": ["https://*.youtube.com/*"],
"js": ["dist/content.bundle.js"],
"css": ["styles/content.css"]
}
],
"background": {
"service_worker": "src/background.js"
},
"web_accessible_resources": [
{
"resources": [
"assets/*",
"dist/*",
"styles/*",
"node_modules/mem0ai/dist/*"
],
"matches": ["https://*.youtube.com/*"]
}
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"name": "mem0-assistant",
"version": "1.0.0",
"description": "A Chrome extension that integrates AI chat functionality directly into YouTube and other sites. Get instant answers about video content without leaving the page.",
"main": "background.js",
"scripts": {
"build": "webpack --config webpack.config.js",
"watch": "webpack --config webpack.config.js --watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.22.0",
"@babel/preset-env": "^7.22.0",
"babel-loader": "^9.1.2",
"css-loader": "^7.1.2",
"style-loader": "^4.0.0",
"webpack": "^5.85.0",
"webpack-cli": "^5.1.1",
"youtube-transcript": "^1.0.6"
},
"dependencies": {
"mem0ai": "^2.1.15"
}
}
@@ -0,0 +1,196 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>YouTube Assistant powered by Mem0</title>
<link rel="stylesheet" href="../styles/options.css">
</head>
<body>
<div class="main-content">
<header>
<div class="title-container">
<h1>YouTube Assistant</h1>
<div class="branding-container">
<span class="powered-by">powered by</span>
<a href="https://mem0.ai" target="_blank">
<img src="../assets/dark.svg" alt="Mem0 Logo" class="logo-img">
</a>
</div>
</div>
<div class="description">
Configure your YouTube Assistant preferences.
</div>
</header>
<div id="status-container"></div>
<div class="section">
<h2>Model Settings</h2>
<div class="form-group">
<label for="model">OpenAI Model</label>
<select id="model">
<option value="o3">o3</option>
<option value="o1">o1</option>
<option value="o1-mini">o1-mini</option>
<option value="o1-pro">o1-pro</option>
<option value="gpt-4o">GPT-4o</option>
<option value="gpt-4o-mini">GPT-4o mini</option>
</select>
<div class="description" style="margin-top: 8px; font-size: 13px">
Choose the OpenAI model to use depending on your needs.
</div>
</div>
<div class="form-group">
<label for="max-tokens">Maximum Response Length</label>
<input
type="number"
id="max-tokens"
min="50"
max="4000"
value="2000"
/>
<div class="description" style="margin-top: 8px; font-size: 13px">
Maximum number of tokens in the AI's response. Higher values allow
for longer responses but may increase processing time.
</div>
</div>
<div class="form-group">
<label for="temperature">Response Creativity</label>
<input
type="range"
id="temperature"
min="0"
max="1"
step="0.1"
value="0.7"
/>
<div
id="temperature-value"
style="display: inline-block; margin-left: 10px"
>
0.7
</div>
<div class="description" style="margin-top: 8px; font-size: 13px">
Controls response randomness. Lower values (0.1-0.3) are more
focused and deterministic, higher values (0.7-0.9) are more creative
and diverse.
</div>
</div>
</div>
<div class="section">
<h2>Create Memories</h2>
<div class="description">
Add information about yourself that you want the AI to remember. This
information will be used to provide more personalized responses.
</div>
<div class="form-group">
<label for="memory-input">Your Information</label>
<textarea
id="memory-input"
class="memory-input"
placeholder="Enter information about yourself that you want the AI to remember..."
></textarea>
</div>
<div class="actions">
<button id="add-memory" class="primary">
<span class="button-text">Add Memory</span>
</button>
</div>
<div id="memory-result" class="memory-result"></div>
</div>
<div class="actions">
<button id="reset-defaults" class="secondary-button">
Reset to Defaults
</button>
<button id="save-options">Save Changes</button>
</div>
</div>
<!-- Memories Sidebar -->
<div class="memories-sidebar" id="memories-sidebar">
<div class="memories-header">
<h2 class="memories-title">Your Memories</h2>
<div class="memories-actions">
<button
id="refresh-memories"
class="memory-action-btn"
title="Refresh Memories"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M23 4v6h-6"></path>
<path d="M1 20v-6h6"></path>
<path
d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"
></path>
</svg>
</button>
<button
id="delete-all-memories"
class="memory-action-btn delete"
title="Delete All Memories"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M3 6h18"></path>
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
</svg>
</button>
</div>
</div>
<div class="memories-list" id="memories-list">
<!-- Memories will be populated here -->
</div>
</div>
<!-- Edit Memory Modal -->
<div class="edit-memory-modal" id="edit-memory-modal">
<div class="edit-memory-content">
<div class="edit-memory-header">
<h3 class="edit-memory-title">Edit Memory</h3>
<button class="edit-memory-close" id="close-edit-modal">
&times;
</button>
</div>
<textarea class="edit-memory-textarea" id="edit-memory-text"></textarea>
<div class="edit-memory-actions">
<button class="memory-action-btn delete" id="delete-memory">
Delete
</button>
<button class="memory-action-btn" id="save-memory">
Save Changes
</button>
</div>
</div>
</div>
<script src="../dist/options.bundle.js"></script>
</body>
</html>
@@ -0,0 +1,165 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>YouTube Assistant powered by Mem0</title>
<link rel="stylesheet" href="../styles/popup.css">
</head>
<body>
<header>
<h1>YouTube Assistant</h1>
<div class="branding-container">
<span class="powered-by">powered by</span>
<a href="https://mem0.ai" target="_blank">
<img src="../assets/dark.svg" alt="Mem0 Logo" class="logo-img">
</a>
</div>
</header>
<div class="content">
<!-- Status area -->
<div id="status-container"></div>
<!-- API key input, only shown if not set -->
<div id="api-key-section" class="api-key-section">
<label for="api-key">OpenAI API Key</label>
<div class="api-key-input-wrapper">
<input type="password" id="api-key" placeholder="sk-..." />
<button class="toggle-password" id="toggle-openai-key">
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</button>
</div>
<button id="save-api-key" class="save-button">
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"
></path>
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
</svg>
Save OpenAI Key
</button>
</div>
<!-- mem0 API key input -->
<div id="mem0-api-key-section" class="api-key-section">
<label for="mem0-api-key">Mem0 API Key</label>
<div class="api-key-input-wrapper">
<input
type="password"
id="mem0-api-key"
placeholder="Enter your mem0 API key"
/>
<button class="toggle-password" id="toggle-mem0-key">
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</button>
</div>
<div class="api-key-actions">
<p>Get your API key from <a href="https://mem0.ai" target="_blank" class="get-key-link">mem0.ai</a> to integrate memory features in the chat.</p>
<button id="save-mem0-api-key" class="save-button">
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"
></path>
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
</svg>
Save Mem0 Key
</button>
</div>
</div>
<!-- Action buttons -->
<div class="actions">
<button id="toggle-chat">
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
></path>
</svg>
Chat
</button>
<button id="open-options">
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="3"></circle>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
></path>
</svg>
Settings
</button>
</div>
<!-- Future mem0 integration status -->
<div class="mem0-status">
<p>
Mem0 integration:
<span id="mem0-status-text">Not configured</span>
</p>
</div>
</div>
<script src="../src/popup.js"></script>
</body>
</html>
@@ -0,0 +1,255 @@
// Background script to handle API calls to OpenAI and manage extension state
// Configuration (will be stored in sync storage eventually)
let config = {
apiKey: "", // Will be set by user in options
mem0ApiKey: "", // Will be set by user in options
model: "gpt-4",
maxTokens: 2000,
temperature: 0.7,
enabledSites: ["youtube.com"],
};
// Track if config is loaded
let isConfigLoaded = false;
// Initialize configuration from storage
chrome.storage.sync.get(
["apiKey", "mem0ApiKey", "model", "maxTokens", "temperature", "enabledSites"],
(result) => {
if (result.apiKey) config.apiKey = result.apiKey;
if (result.mem0ApiKey) config.mem0ApiKey = result.mem0ApiKey;
if (result.model) config.model = result.model;
if (result.maxTokens) config.maxTokens = result.maxTokens;
if (result.temperature) config.temperature = result.temperature;
if (result.enabledSites) config.enabledSites = result.enabledSites;
isConfigLoaded = true;
}
);
// Listen for messages from content script or popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
// Handle different message types
switch (request.action) {
case "sendChatRequest":
sendChatRequest(request.messages, request.model || config.model)
.then((response) => sendResponse(response))
.catch((error) => sendResponse({ error: error.message }));
return true; // Required for async response
case "saveConfig":
saveConfig(request.config)
.then(() => sendResponse({ success: true }))
.catch((error) => sendResponse({ error: error.message }));
return true;
case "getConfig":
// If config isn't loaded yet, load it first
if (!isConfigLoaded) {
chrome.storage.sync.get(
[
"apiKey",
"mem0ApiKey",
"model",
"maxTokens",
"temperature",
"enabledSites",
],
(result) => {
if (result.apiKey) config.apiKey = result.apiKey;
if (result.mem0ApiKey) config.mem0ApiKey = result.mem0ApiKey;
if (result.model) config.model = result.model;
if (result.maxTokens) config.maxTokens = result.maxTokens;
if (result.temperature) config.temperature = result.temperature;
if (result.enabledSites) config.enabledSites = result.enabledSites;
isConfigLoaded = true;
sendResponse({ config });
}
);
return true;
}
sendResponse({ config });
return false;
case "openOptions":
// Open options page
chrome.runtime.openOptionsPage(() => {
if (chrome.runtime.lastError) {
console.error(
"Error opening options page:",
chrome.runtime.lastError
);
// Fallback: Try to open directly in a new tab
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") });
}
sendResponse({ success: true });
});
return true;
case "toggleChat":
// Forward the toggle request to the active tab
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]) {
chrome.tabs
.sendMessage(tabs[0].id, { action: "toggleChat" })
.then((response) => sendResponse(response))
.catch((error) => sendResponse({ error: error.message }));
} else {
sendResponse({ error: "No active tab found" });
}
});
return true;
}
});
// Handle extension icon click - toggle chat visibility
chrome.action.onClicked.addListener((tab) => {
chrome.tabs
.sendMessage(tab.id, { action: "toggleChat" })
.catch((error) => console.error("Error toggling chat:", error));
});
// Save configuration to sync storage
async function saveConfig(newConfig) {
// Validate API key if provided
if (newConfig.apiKey) {
try {
const isValid = await validateApiKey(newConfig.apiKey);
if (!isValid) {
throw new Error("Invalid API key");
}
} catch (error) {
throw new Error(`API key validation failed: ${error.message}`);
}
}
// Update local config
config = { ...config, ...newConfig };
// Save to sync storage
return chrome.storage.sync.set(newConfig);
}
// Validate OpenAI API key with a simple request
async function validateApiKey(apiKey) {
try {
const response = await fetch("https://api.openai.com/v1/models", {
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`API returned ${response.status}`);
}
return true;
} catch (error) {
console.error("API key validation error:", error);
return false;
}
}
// Send a chat request to OpenAI API
async function sendChatRequest(messages, model) {
// Check if API key is set
if (!config.apiKey) {
return {
error:
"API key not configured. Please set your OpenAI API key in the extension options.",
};
}
try {
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${config.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: model || config.model,
messages: messages.map((msg) => ({
role: msg.role,
content: msg.content,
})),
max_tokens: config.maxTokens,
temperature: config.temperature,
stream: true, // Enable streaming
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.error?.message || `API returned ${response.status}`
);
}
// Create a ReadableStream from the response
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
// Process the stream
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Decode the chunk and add to buffer
buffer += decoder.decode(value, { stream: true });
// Process complete lines
const lines = buffer.split("\n");
buffer = lines.pop() || ""; // Keep the last incomplete line in the buffer
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") {
// Stream complete
return { done: true };
}
try {
const parsed = JSON.parse(data);
if (parsed.choices[0].delta.content) {
// Send the chunk to the content script
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]) {
chrome.tabs.sendMessage(tabs[0].id, {
action: "streamChunk",
chunk: parsed.choices[0].delta.content,
});
}
});
}
} catch (e) {
console.error("Error parsing chunk:", e);
}
}
}
}
return { done: true };
} catch (error) {
console.error("Error sending chat request:", error);
return { error: error.message };
}
}
// Future: Add mem0 integration functions here
// When ready, replace with actual implementation
function mem0Integration() {
// Placeholder for future mem0 integration
return {
getUserMemories: async (userId) => {
return { memories: [] };
},
saveMemory: async (userId, memory) => {
return { success: true };
},
};
}
+657
View File
@@ -0,0 +1,657 @@
// Main content script that injects the AI chat into YouTube
import { YoutubeTranscript } from "youtube-transcript";
import { MemoryClient } from "mem0ai";
// Configuration
const config = {
apiEndpoint: "https://api.openai.com/v1/chat/completions",
model: "gpt-4o",
chatPosition: "right", // Where to display the chat panel
autoExtract: true, // Automatically extract video context
mem0ApiKey: "", // Will be set through extension options
};
// Initialize Mem0AI - will be initialized properly when API key is available
let mem0client = null;
let mem0Initializing = false;
// Function to initialize Mem0AI with API key from storage
async function initializeMem0AI() {
if (mem0Initializing) return; // Prevent multiple simultaneous initialization attempts
mem0Initializing = true;
try {
// Get API key from storage
const items = await chrome.storage.sync.get(["mem0ApiKey"]);
if (items.mem0ApiKey) {
try {
// Create new client instance with v2.1.11 configuration
mem0client = new MemoryClient({
apiKey: items.mem0ApiKey,
projectId: "youtube-assistant", // Add a project ID for organization
isExtension: true,
});
// Set up custom instructions for the YouTube educational assistant
await mem0client.updateProject({
custom_instructions: `Your task: Create memories for a YouTube AI assistant. Focus on capturing:
1. User's Knowledge & Experience:
- Direct statements about their skills, knowledge, or experience
- Their level of expertise in specific areas
- Technologies, frameworks, or tools they work with
- Their learning journey or background
2. User's Interests & Goals:
- What they're trying to learn or understand (user messages may include the video title)
- Their specific questions or areas of confusion
- Their learning objectives or career goals
- Topics they want to explore further
3. Personal Context:
- Their current role or position
- Their learning style or preferences
- Their experience level in the video's topic
- Any challenges or difficulties they're facing
4. Video Engagement:
- Their reactions to the content
- Points they agree or disagree with
- Areas they want to discuss further
- Connections they make to other topics
For each message:
- Extract both explicit statements and implicit knowledge
- Capture both video-related and personal context
- Note any relationships between user's knowledge and video content
Remember: The goal is to build a comprehensive understanding of both the user's knowledge and their learning journey through YouTube.`,
});
return true;
} catch (error) {
console.error("Error initializing Mem0AI:", error);
return false;
}
} else {
console.log("No Mem0AI API key found in storage");
return false;
}
} catch (error) {
console.error("Error accessing storage:", error);
return false;
} finally {
mem0Initializing = false;
}
}
// Global state
let chatState = {
messages: [],
isVisible: false,
isLoading: false,
videoContext: null,
transcript: null, // Add transcript to state
userMemories: null, // Will store retrieved memories
currentStreamingMessage: null, // Track the current streaming message
};
// Function to extract video ID from YouTube URL
function getYouTubeVideoId(url) {
const urlObj = new URL(url);
const searchParams = new URLSearchParams(urlObj.search);
return searchParams.get("v");
}
// Function to fetch and log transcript
async function fetchAndLogTranscript() {
try {
// Check if we're on a YouTube video page
if (
window.location.hostname.includes("youtube.com") &&
window.location.pathname.includes("/watch")
) {
const videoId = getYouTubeVideoId(window.location.href);
if (videoId) {
// Fetch transcript using youtube-transcript package
const transcript = await YoutubeTranscript.fetchTranscript(videoId);
// Decode HTML entities in transcript text
const decodedTranscript = transcript.map((entry) => ({
...entry,
text: entry.text
.replace(/&amp;#39;/g, "'")
.replace(/&amp;quot;/g, '"')
.replace(/&amp;lt;/g, "<")
.replace(/&amp;gt;/g, ">")
.replace(/&amp;amp;/g, "&"),
}));
// Store transcript in state
chatState.transcript = decodedTranscript;
} else {
return;
}
}
} catch (error) {
console.error("Error fetching transcript:", error);
chatState.transcript = null;
}
}
// Initialize when the DOM is fully loaded
document.addEventListener("DOMContentLoaded", async () => {
init();
fetchAndLogTranscript();
await initializeMem0AI(); // Initialize Mem0AI
});
// Also attempt to initialize on window load to handle YouTube's SPA behavior
window.addEventListener("load", async () => {
init();
fetchAndLogTranscript();
await initializeMem0AI(); // Initialize Mem0AI
});
// Add another listener for YouTube's navigation events
window.addEventListener("yt-navigate-finish", () => {
init();
fetchAndLogTranscript();
});
// Main initialization function
function init() {
// Check if we're on a YouTube page
if (
!window.location.hostname.includes("youtube.com") ||
!window.location.pathname.includes("/watch")
) {
return;
}
// Give YouTube's DOM a moment to settle
setTimeout(() => {
// Only inject if not already present
if (!document.getElementById("ai-chat-assistant-container")) {
injectChatInterface();
setupEventListeners();
extractVideoContext();
}
}, 1500);
}
// Extract context from the current YouTube video
function extractVideoContext() {
if (!config.autoExtract) return;
try {
const videoTitle =
document.querySelector(
"h1.title.style-scope.ytd-video-primary-info-renderer"
)?.textContent ||
document.querySelector("h1.title")?.textContent ||
"Unknown Video";
const channelName =
document.querySelector("ytd-channel-name yt-formatted-string")
?.textContent ||
document.querySelector("ytd-channel-name")?.textContent ||
"Unknown Channel";
// Video ID from URL
const videoId = new URLSearchParams(window.location.search).get("v");
// Update state with basic video context first
chatState.videoContext = {
title: videoTitle,
channel: channelName,
videoId: videoId,
url: window.location.href,
};
} catch (error) {
console.error("Error extracting video context:", error);
chatState.videoContext = {
title: "Error extracting video information",
url: window.location.href,
};
}
}
// Inject the chat interface into the YouTube page
function injectChatInterface() {
// Create main container
const container = document.createElement("div");
container.id = "ai-chat-assistant-container";
container.className = "ai-chat-container";
// Set up basic HTML structure
container.innerHTML = `
<div class="ai-chat-header">
<div class="ai-chat-tabs">
<button class="ai-chat-tab active" data-tab="chat">Chat</button>
<button class="ai-chat-tab" data-tab="memories">Memories</button>
</div>
<div class="ai-chat-controls">
<button id="ai-chat-minimize" class="ai-chat-btn" title="Minimize">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
<button id="ai-chat-close" class="ai-chat-btn" title="Close">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
<div class="ai-chat-body">
<div id="ai-chat-content" class="ai-chat-content">
<div id="ai-chat-messages" class="ai-chat-messages"></div>
<div class="ai-chat-input-container">
<textarea id="ai-chat-input" placeholder="Ask about this video..."></textarea>
<button id="ai-chat-send" class="ai-chat-send-btn" title="Send message">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</svg>
</button>
</div>
</div>
<div id="ai-chat-memories" class="ai-chat-memories" style="display: none;">
<div class="memories-header">
<div class="memories-title">
Manage memories <a href="#" id="manage-memories-link" title="Open options page">here <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg></a>
</div>
<button id="refresh-memories" class="ai-chat-btn" title="Refresh memories">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M23 4v6h-6"></path>
<path d="M1 20v-6h6"></path>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
</svg>
</button>
</div>
<div id="memories-list" class="memories-list"></div>
</div>
</div>
`;
// Append to body
document.body.appendChild(container);
// Add welcome message
addMessage(
"assistant",
"Hello! I can help answer questions about this video. What would you like to know?"
);
}
// Set up event listeners for the chat interface
function setupEventListeners() {
// Tab switching
const tabs = document.querySelectorAll(".ai-chat-tab");
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
// Update active tab
tabs.forEach((t) => t.classList.remove("active"));
tab.classList.add("active");
// Show corresponding content
const tabName = tab.dataset.tab;
document.getElementById("ai-chat-content").style.display =
tabName === "chat" ? "flex" : "none";
document.getElementById("ai-chat-memories").style.display =
tabName === "memories" ? "flex" : "none";
// Load memories if switching to memories tab
if (tabName === "memories") {
loadMemories();
}
});
});
// Refresh memories button
document
.getElementById("refresh-memories")
?.addEventListener("click", loadMemories);
// Toggle chat visibility
document.getElementById("ai-chat-toggle")?.addEventListener("click", () => {
const container = document.getElementById("ai-chat-assistant-container");
chatState.isVisible = !chatState.isVisible;
if (chatState.isVisible) {
container.classList.add("visible");
} else {
container.classList.remove("visible");
}
});
// Close button
document.getElementById("ai-chat-close")?.addEventListener("click", () => {
const container = document.getElementById("ai-chat-assistant-container");
container.classList.remove("visible");
chatState.isVisible = false;
});
// Minimize button
document.getElementById("ai-chat-minimize")?.addEventListener("click", () => {
const container = document.getElementById("ai-chat-assistant-container");
container.classList.toggle("minimized");
});
// Send message on button click
document
.getElementById("ai-chat-send")
?.addEventListener("click", sendMessage);
// Send message on Enter key (but allow Shift+Enter for new lines)
document.getElementById("ai-chat-input")?.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Add click handler for manage memories link
document
.getElementById("manage-memories-link")
.addEventListener("click", (e) => {
e.preventDefault();
chrome.runtime.sendMessage({ action: "openOptions" }, (response) => {
if (chrome.runtime.lastError) {
console.error("Error opening options:", chrome.runtime.lastError);
// Fallback: Try to open directly in a new tab
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") });
}
});
});
}
// Add a message to the chat
function addMessage(role, text, isStreaming = false) {
const messagesContainer = document.getElementById("ai-chat-messages");
if (!messagesContainer) return;
const messageElement = document.createElement("div");
messageElement.className = `ai-chat-message ${role}`;
// Enhanced markdown-like formatting
let formattedText = text
// Code blocks
.replace(/```([\s\S]*?)```/g, "<pre><code>$1</code></pre>")
// Inline code
.replace(/`([^`]+)`/g, "<code>$1</code>")
// Links
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
// Bold text
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
// Italic text
.replace(/\*([^*]+)\*/g, "<em>$1</em>")
// Lists
.replace(/^\s*[-*]\s+(.+)$/gm, "<li>$1</li>")
.replace(/(<li>.*<\/li>)/s, "<ul>$1</ul>")
// Line breaks
.replace(/\n/g, "<br>");
messageElement.innerHTML = formattedText;
messagesContainer.appendChild(messageElement);
// Scroll to bottom
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Add to messages array if not streaming
if (!isStreaming) {
chatState.messages.push({ role, content: text });
}
return messageElement;
}
// Format streaming text with markdown
function formatStreamingText(text) {
return text
// Code blocks
.replace(/```([\s\S]*?)```/g, "<pre><code>$1</code></pre>")
// Inline code
.replace(/`([^`]+)`/g, "<code>$1</code>")
// Links
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
// Bold text
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
// Italic text
.replace(/\*([^*]+)\*/g, "<em>$1</em>")
// Lists
.replace(/^\s*[-*]\s+(.+)$/gm, "<li>$1</li>")
.replace(/(<li>.*<\/li>)/s, "<ul>$1</ul>")
// Line breaks
.replace(/\n/g, "<br>");
}
// Send a message to the AI
async function sendMessage() {
const inputElement = document.getElementById("ai-chat-input");
if (!inputElement) return;
const userMessage = inputElement.value.trim();
if (!userMessage) return;
// Clear input
inputElement.value = "";
// Add user message to chat
addMessage("user", userMessage);
// Show loading indicator
chatState.isLoading = true;
const loadingMessage = document.createElement("div");
loadingMessage.className = "ai-chat-message assistant loading";
loadingMessage.textContent = "Thinking...";
document.getElementById("ai-chat-messages").appendChild(loadingMessage);
try {
// If mem0client is available, store the message as a memory and search for relevant memories
if (mem0client) {
try {
// Store the message as a memory
await mem0client.add(
[
{
role: "user",
content: `${userMessage}\n\nVideo title: ${chatState.videoContext?.title}`,
},
],
{
user_id: "youtube-assistant-mem0", // Required parameter
metadata: {
videoId: chatState.videoContext?.videoId || "",
videoTitle: chatState.videoContext?.title || "",
},
}
);
// Search for relevant memories
const searchResults = await mem0client.search(userMessage, {
user_id: "youtube-assistant-mem0", // Required parameter
limit: 5,
});
// Store the retrieved memories
chatState.userMemories = searchResults || null;
} catch (memoryError) {
console.error("Error with Mem0AI operations:", memoryError);
// Continue with the chat process even if memory operations fail
}
}
// Prepare messages with context (now includes memories if available)
const contextualizedMessages = prepareMessagesWithContext();
// Remove loading message
document.getElementById("ai-chat-messages").removeChild(loadingMessage);
// Create a new message element for streaming
chatState.currentStreamingMessage = addMessage("assistant", "", true);
// Send to background script to handle API call
chrome.runtime.sendMessage(
{
action: "sendChatRequest",
messages: contextualizedMessages,
model: config.model,
},
(response) => {
chatState.isLoading = false;
if (response.error) {
addMessage("system", `Error: ${response.error}`);
}
}
);
} catch (error) {
// Remove loading indicator
document.getElementById("ai-chat-messages").removeChild(loadingMessage);
chatState.isLoading = false;
// Show error
addMessage("system", `Error: ${error.message}`);
}
}
// Prepare messages with added context
function prepareMessagesWithContext() {
const messages = [...chatState.messages];
// If we have video context, add it as system message at the beginning
if (chatState.videoContext) {
let transcriptSection = "";
// Add transcript if available
if (chatState.transcript) {
// Format transcript into a readable string
const formattedTranscript = chatState.transcript
.map((entry) => `${entry.text}`)
.join("\n");
transcriptSection = `\n\nTranscript:\n${formattedTranscript}`;
}
// Add user memories if available
let userMemoriesSection = "";
if (chatState.userMemories && chatState.userMemories.length > 0) {
const formattedMemories = chatState.userMemories
.map((memory) => `${memory.memory}`)
.join("\n");
userMemoriesSection = `\n\nUser Memories:\n${formattedMemories}\n\n`;
}
const systemContent = `You are an AI assistant helping with a YouTube video. Here's the context:
Title: ${chatState.videoContext.title}
Channel: ${chatState.videoContext.channel}
URL: ${chatState.videoContext.url}
${
userMemoriesSection
? `Use the user memories below to personalize your response based on their past interactions and interests. These memories represent relevant past conversations and information about the user.
${userMemoriesSection}
`
: ""
}
Please provide helpful, relevant information based on the video's content.
${
transcriptSection
? `"Use the transcript below to provide accurate answers about the video. Ignore if the transcript doesn't make sense."
${transcriptSection}
`
: "Since the transcript is not available, focus on general questions about the topic and use the video title for context. If asked about specific parts of the video content, politely explain that the video doesn't have a transcript."
}
Be concise and helpful in your responses.
`;
messages.unshift({
role: "system",
content: systemContent,
});
}
return messages;
}
// Listen for commands from the background script or popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "toggleChat") {
const container = document.getElementById("ai-chat-assistant-container");
chatState.isVisible = !chatState.isVisible;
if (chatState.isVisible) {
container.classList.add("visible");
} else {
container.classList.remove("visible");
}
sendResponse({ success: true });
} else if (message.action === "streamChunk") {
// Handle streaming chunks
if (chatState.currentStreamingMessage) {
const currentContent = chatState.currentStreamingMessage.innerHTML;
chatState.currentStreamingMessage.innerHTML = formatStreamingText(currentContent + message.chunk);
// Scroll to bottom
const messagesContainer = document.getElementById("ai-chat-messages");
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
}
});
// Load memories from mem0
async function loadMemories() {
try {
const memoriesContainer = document.getElementById("memories-list");
memoriesContainer.innerHTML =
'<div class="loading">Loading memories...</div>';
// If client isn't initialized, try to initialize it
if (!mem0client) {
const initialized = await initializeMem0AI();
if (!initialized) {
memoriesContainer.innerHTML =
'<div class="error">Please set your Mem0 API key in the extension options.</div>';
return;
}
}
const response = await mem0client.getAll({
user_id: "youtube-assistant-mem0",
page: 1,
page_size: 50,
});
if (response && response.results) {
memoriesContainer.innerHTML = "";
response.results.forEach((memory) => {
const memoryElement = document.createElement("div");
memoryElement.className = "memory-item";
memoryElement.textContent = memory.memory;
memoriesContainer.appendChild(memoryElement);
});
if (response.results.length === 0) {
memoriesContainer.innerHTML =
'<div class="no-memories">No memories found</div>';
}
} else {
memoriesContainer.innerHTML =
'<div class="no-memories">No memories found</div>';
}
} catch (error) {
console.error("Error loading memories:", error);
document.getElementById("memories-list").innerHTML =
'<div class="error">Error loading memories. Please try again.</div>';
}
}
+452
View File
@@ -0,0 +1,452 @@
// Options page functionality for AI Chat Assistant
import { MemoryClient } from "mem0ai";
// Default configuration
const defaultConfig = {
model: "gpt-4o",
maxTokens: 2000,
temperature: 0.7,
enabledSites: ["youtube.com"],
};
// Initialize Mem0AI client
let mem0client = null;
// Initialize when the DOM is fully loaded
document.addEventListener("DOMContentLoaded", init);
// Initialize options page
async function init() {
// Set up event listeners
document
.getElementById("save-options")
.addEventListener("click", saveOptions);
document
.getElementById("reset-defaults")
.addEventListener("click", resetToDefaults);
document.getElementById("add-memory").addEventListener("click", addMemory);
// Set up slider value display
const temperatureSlider = document.getElementById("temperature");
const temperatureValue = document.getElementById("temperature-value");
temperatureSlider.addEventListener("input", () => {
temperatureValue.textContent = temperatureSlider.value;
});
// Set up memories sidebar functionality
document
.getElementById("refresh-memories")
.addEventListener("click", fetchMemories);
document
.getElementById("delete-all-memories")
.addEventListener("click", deleteAllMemories);
document
.getElementById("close-edit-modal")
.addEventListener("click", closeEditModal);
document.getElementById("save-memory").addEventListener("click", saveMemory);
document
.getElementById("delete-memory")
.addEventListener("click", deleteMemory);
// Load current configuration
await loadConfig();
// Initialize Mem0AI and load memories
await initializeMem0AI();
await fetchMemories();
}
// Initialize Mem0AI with API key from storage
async function initializeMem0AI() {
try {
const response = await chrome.runtime.sendMessage({ action: "getConfig" });
const mem0ApiKey = response.config.mem0ApiKey;
if (!mem0ApiKey) {
showMemoriesError("Please configure your Mem0 API key in the popup");
return false;
}
mem0client = new MemoryClient({
apiKey: mem0ApiKey,
projectId: "youtube-assistant",
isExtension: true,
});
return true;
} catch (error) {
console.error("Error initializing Mem0AI:", error);
showMemoriesError("Failed to initialize Mem0AI");
return false;
}
}
// Load configuration from storage
async function loadConfig() {
try {
const response = await chrome.runtime.sendMessage({ action: "getConfig" });
const config = response.config;
// Update form fields with current values
if (config.model) {
document.getElementById("model").value = config.model;
}
if (config.maxTokens) {
document.getElementById("max-tokens").value = config.maxTokens;
}
if (config.temperature !== undefined) {
const temperatureSlider = document.getElementById("temperature");
temperatureSlider.value = config.temperature;
document.getElementById("temperature-value").textContent =
config.temperature;
}
} catch (error) {
showStatus(`Error loading configuration: ${error.message}`, "error");
}
}
// Save options to storage
async function saveOptions() {
// Get values from form
const model = document.getElementById("model").value;
const maxTokens = parseInt(document.getElementById("max-tokens").value);
const temperature = parseFloat(document.getElementById("temperature").value);
// Validate inputs
if (maxTokens < 50 || maxTokens > 4000) {
showStatus("Maximum tokens must be between 50 and 4000", "error");
return;
}
if (temperature < 0 || temperature > 1) {
showStatus("Temperature must be between 0 and 1", "error");
return;
}
// Prepare config object
const config = {
model,
maxTokens,
temperature,
};
// Show loading status
showStatus("Saving options...", "warning");
try {
// Send to background script for saving
const response = await chrome.runtime.sendMessage({
action: "saveConfig",
config,
});
if (response.error) {
showStatus(`Error: ${response.error}`, "error");
} else {
showStatus("Options saved successfully", "success");
loadConfig(); // Refresh the UI with the latest saved values
}
} catch (error) {
showStatus(`Error: ${error.message}`, "error");
}
}
// Reset options to defaults
function resetToDefaults() {
if (
confirm(
"Are you sure you want to reset all options to their default values?"
)
) {
// Set form fields to default values
document.getElementById("model").value = defaultConfig.model;
document.getElementById("max-tokens").value = defaultConfig.maxTokens;
const temperatureSlider = document.getElementById("temperature");
temperatureSlider.value = defaultConfig.temperature;
document.getElementById("temperature-value").textContent =
defaultConfig.temperature;
showStatus("Restored default values. Click Save to apply.", "warning");
}
}
// Memories functionality
let currentMemory = null;
async function fetchMemories() {
try {
if (!mem0client) {
const initialized = await initializeMem0AI();
if (!initialized) return;
}
const memories = await mem0client.getAll({
user_id: "youtube-assistant-mem0",
page: 1,
page_size: 50,
});
displayMemories(memories.results);
} catch (error) {
console.error("Error fetching memories:", error);
showMemoriesError("Failed to load memories");
}
}
function displayMemories(memories) {
const memoriesList = document.getElementById("memories-list");
memoriesList.innerHTML = "";
if (memories.length === 0) {
memoriesList.innerHTML = `
<div class="memory-item">
<div class="memory-content">No memories found. Your memories will appear here.</div>
</div>
`;
return;
}
memories.forEach((memory) => {
const memoryElement = document.createElement("div");
memoryElement.className = "memory-item";
memoryElement.innerHTML = `
<div class="memory-content">${memory.memory}</div>
<div class="memory-meta">Last updated: ${new Date(
memory.updated_at
).toLocaleString()}</div>
<div class="memory-actions">
<button class="memory-action-btn edit" data-id="${
memory.id
}">Edit</button>
<button class="memory-action-btn delete" data-id="${
memory.id
}">Delete</button>
</div>
`;
// Add event listeners
memoryElement
.querySelector(".edit")
.addEventListener("click", () => editMemory(memory));
memoryElement
.querySelector(".delete")
.addEventListener("click", () => deleteMemory(memory.id));
memoriesList.appendChild(memoryElement);
});
}
function showMemoriesError(message) {
const memoriesList = document.getElementById("memories-list");
memoriesList.innerHTML = `
<div class="memory-item">
<div class="memory-content">${message}</div>
</div>
`;
}
async function deleteAllMemories() {
if (
!confirm(
"Are you sure you want to delete all memories? This action cannot be undone."
)
) {
return;
}
try {
if (!mem0client) {
const initialized = await initializeMem0AI();
if (!initialized) return;
}
await mem0client.deleteAll({
user_id: "youtube-assistant-mem0",
});
showStatus("All memories deleted successfully", "success");
await fetchMemories();
} catch (error) {
console.error("Error deleting memories:", error);
showStatus("Failed to delete memories", "error");
}
}
function editMemory(memory) {
currentMemory = memory;
const modal = document.getElementById("edit-memory-modal");
const textarea = document.getElementById("edit-memory-text");
textarea.value = memory.memory;
modal.classList.add("open");
}
function closeEditModal() {
const modal = document.getElementById("edit-memory-modal");
modal.classList.remove("open");
currentMemory = null;
}
async function saveMemory() {
if (!currentMemory) return;
try {
if (!mem0client) {
const initialized = await initializeMem0AI();
if (!initialized) return;
}
const textarea = document.getElementById("edit-memory-text");
const updatedMemory = textarea.value.trim();
if (!updatedMemory) {
showStatus("Memory cannot be empty", "error");
return;
}
await mem0client.update(currentMemory.id, updatedMemory);
showStatus("Memory updated successfully", "success");
closeEditModal();
await fetchMemories();
} catch (error) {
console.error("Error updating memory:", error);
showStatus("Failed to update memory", "error");
}
}
async function deleteMemory(memoryId) {
if (
!confirm(
"Are you sure you want to delete this memory? This action cannot be undone."
)
) {
return;
}
try {
if (!mem0client) {
const initialized = await initializeMem0AI();
if (!initialized) return;
}
await mem0client.delete(memoryId);
showStatus("Memory deleted successfully", "success");
await fetchMemories();
} catch (error) {
console.error("Error deleting memory:", error);
showStatus("Failed to delete memory", "error");
}
}
// Show status message
function showStatus(message, type = "info") {
const statusContainer = document.getElementById("status-container");
// Clear previous status
statusContainer.innerHTML = "";
// Create status element
const statusElement = document.createElement("div");
statusElement.className = `status ${type}`;
statusElement.textContent = message;
// Add to container
statusContainer.appendChild(statusElement);
// Auto-clear success messages after 3 seconds
if (type === "success") {
setTimeout(() => {
statusElement.style.opacity = "0";
setTimeout(() => {
if (statusContainer.contains(statusElement)) {
statusContainer.removeChild(statusElement);
}
}, 300);
}, 3000);
}
}
// Add memory to Mem0
async function addMemory() {
const memoryInput = document.getElementById("memory-input");
const addButton = document.getElementById("add-memory");
const memoryResult = document.getElementById("memory-result");
const buttonText = addButton.querySelector(".button-text");
const content = memoryInput.value.trim();
if (!content) {
showMemoryResult(
"Please enter some information to add as a memory",
"error"
);
return;
}
// Show loading state
addButton.disabled = true;
buttonText.textContent = "Adding...";
addButton.innerHTML =
'<div class="loading-spinner"></div><span class="button-text">Adding...</span>';
memoryResult.style.display = "none";
try {
if (!mem0client) {
const initialized = await initializeMem0AI();
if (!initialized) return;
}
const result = await mem0client.add(
[
{
role: "user",
content: content,
},
],
{
user_id: "youtube-assistant-mem0",
}
);
// Show success message with number of memories added
showMemoryResult(
`Added ${result.length || 0} new ${
result.length === 1 ? "memory" : "memories"
}`,
"success"
);
// Clear the input
memoryInput.value = "";
// Refresh the memories list
await fetchMemories();
} catch (error) {
showMemoryResult(`Error adding memory: ${error.message}`, "error");
} finally {
// Reset button state
addButton.disabled = false;
buttonText.textContent = "Add Memory";
addButton.innerHTML = '<span class="button-text">Add Memory</span>';
}
}
// Show memory result message
function showMemoryResult(message, type) {
const memoryResult = document.getElementById("memory-result");
memoryResult.textContent = message;
memoryResult.className = `memory-result ${type}`;
memoryResult.style.display = "block";
// Auto-clear success messages after 3 seconds
if (type === "success") {
setTimeout(() => {
memoryResult.style.opacity = "0";
setTimeout(() => {
memoryResult.style.display = "none";
memoryResult.style.opacity = "1";
}, 300);
}, 3000);
}
}
+241
View File
@@ -0,0 +1,241 @@
// Popup functionality for AI Chat Assistant
document.addEventListener("DOMContentLoaded", init);
// Initialize popup
async function init() {
try {
// Set up event listeners
document
.getElementById("toggle-chat")
.addEventListener("click", toggleChat);
document
.getElementById("open-options")
.addEventListener("click", openOptions);
document
.getElementById("save-api-key")
.addEventListener("click", saveApiKey);
document
.getElementById("save-mem0-api-key")
.addEventListener("click", saveMem0ApiKey);
// Set up password toggle listeners
document
.getElementById("toggle-openai-key")
.addEventListener("click", () => togglePasswordVisibility("api-key"));
document
.getElementById("toggle-mem0-key")
.addEventListener("click", () =>
togglePasswordVisibility("mem0-api-key")
);
// Load current configuration and wait for it to complete
await loadConfig();
} catch (error) {
console.error("Initialization error:", error);
showStatus("Error initializing popup", "error");
}
}
// Toggle chat visibility in the active tab
function toggleChat() {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]) {
// First check if we can inject the content script
chrome.scripting
.executeScript({
target: { tabId: tabs[0].id },
files: ["dist/content.bundle.js"],
})
.then(() => {
// Now try to toggle the chat
chrome.tabs
.sendMessage(tabs[0].id, { action: "toggleChat" })
.then((response) => {
if (response && response.error) {
console.error("Error toggling chat:", response.error);
showStatus(
"Chat interface not available on this page",
"warning"
);
} else {
// Close the popup after successful toggle
window.close();
}
})
.catch((error) => {
console.error("Error toggling chat:", error);
showStatus(
"Chat interface not available on this page",
"warning"
);
});
})
.catch((error) => {
console.error("Error injecting content script:", error);
showStatus("Cannot inject chat interface on this page", "error");
});
}
});
}
// Open options page
function openOptions() {
// Send message to background script to handle opening options
chrome.runtime.sendMessage({ action: "openOptions" }, (response) => {
if (chrome.runtime.lastError) {
console.error("Error opening options:", chrome.runtime.lastError);
// Direct fallback if communication with background script fails
try {
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") });
} catch (err) {
console.error("Fallback failed:", err);
// Last resort
window.open(chrome.runtime.getURL("options.html"), "_blank");
}
}
});
}
// Toggle password visibility
function togglePasswordVisibility(inputId) {
const input = document.getElementById(inputId);
const type = input.type === "password" ? "text" : "password";
input.type = type;
// Update the eye icon
const button = input.nextElementSibling;
const icon = button.querySelector(".icon");
if (type === "text") {
icon.innerHTML =
'<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>';
} else {
icon.innerHTML =
'<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle>';
}
}
// Save API key to storage
async function saveApiKey() {
const apiKeyInput = document.getElementById("api-key");
const apiKey = apiKeyInput.value.trim();
// Show loading status
showStatus("Saving API key...", "warning");
try {
// Send to background script for validation and saving
const response = await chrome.runtime.sendMessage({
action: "saveConfig",
config: { apiKey },
});
if (response.error) {
showStatus(`Error: ${response.error}`, "error");
} else {
showStatus("API key saved successfully", "success");
loadConfig(); // Refresh the UI
}
} catch (error) {
showStatus(`Error: ${error.message}`, "error");
}
}
// Save mem0 API key to storage
async function saveMem0ApiKey() {
const apiKeyInput = document.getElementById("mem0-api-key");
const apiKey = apiKeyInput.value.trim();
// Show loading status
showStatus("Saving Mem0 API key...", "warning");
try {
// Send to background script for saving
const response = await chrome.runtime.sendMessage({
action: "saveConfig",
config: { mem0ApiKey: apiKey },
});
if (response.error) {
showStatus(`Error: ${response.error}`, "error");
} else {
showStatus("Mem0 API key saved successfully", "success");
loadConfig(); // Refresh the UI
}
} catch (error) {
showStatus(`Error: ${error.message}`, "error");
}
}
// Load configuration from storage
async function loadConfig() {
try {
// Add a small delay to ensure background script is ready
await new Promise((resolve) => setTimeout(resolve, 100));
const response = await chrome.runtime.sendMessage({ action: "getConfig" });
const config = response.config || {};
// Update OpenAI API key field
const apiKeyInput = document.getElementById("api-key");
if (config.apiKey) {
apiKeyInput.value = config.apiKey;
apiKeyInput.type = "password"; // Ensure it's hidden by default
document.getElementById("api-key-section").style.display = "block";
} else {
apiKeyInput.value = "";
document.getElementById("api-key-section").style.display = "block";
showStatus("Please set your OpenAI API key", "warning");
}
// Update mem0 API key field
const mem0ApiKeyInput = document.getElementById("mem0-api-key");
if (config.mem0ApiKey) {
mem0ApiKeyInput.value = config.mem0ApiKey;
mem0ApiKeyInput.type = "password"; // Ensure it's hidden by default
document.getElementById("mem0-api-key-section").style.display = "block";
document.getElementById("mem0-status-text").textContent = "Connected";
document.getElementById("mem0-status-text").style.color =
"var(--success-color)";
} else {
mem0ApiKeyInput.value = "";
document.getElementById("mem0-api-key-section").style.display = "block";
document.getElementById("mem0-status-text").textContent =
"Not configured";
document.getElementById("mem0-status-text").style.color =
"var(--warning-color)";
}
} catch (error) {
console.error("Error loading configuration:", error);
showStatus(`Error loading configuration: ${error.message}`, "error");
}
}
// Show status message
function showStatus(message, type = "info") {
const statusContainer = document.getElementById("status-container");
// Clear previous status
statusContainer.innerHTML = "";
// Create status element
const statusElement = document.createElement("div");
statusElement.className = `status ${type}`;
statusElement.textContent = message;
// Add to container
statusContainer.appendChild(statusElement);
// Auto-clear success messages after 3 seconds
if (type === "success") {
setTimeout(() => {
statusElement.style.opacity = "0";
setTimeout(() => {
if (statusContainer.contains(statusElement)) {
statusContainer.removeChild(statusElement);
}
}, 300);
}, 3000);
}
}
@@ -0,0 +1,492 @@
/* Styles for the AI Chat Assistant */
/* Modern Dark Theme with Blue Accents */
:root {
--chat-dark-bg: #1a1a1a;
--chat-darker-bg: #121212;
--chat-light-text: #f1f1f1;
--chat-blue-accent: #3d84f7;
--chat-blue-hover: #2d74e7;
--chat-blue-light: rgba(61, 132, 247, 0.15);
--chat-error: #ff4a4a;
--chat-border-radius: 12px;
--chat-message-radius: 12px;
--chat-transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Main container */
#ai-chat-assistant-container {
position: fixed;
right: 20px;
bottom: 20px;
width: 380px;
height: 550px;
background-color: var(--chat-dark-bg);
border-radius: var(--chat-border-radius);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
z-index: 9999;
overflow: hidden;
transition: var(--chat-transition);
opacity: 0;
transform: translateY(20px) scale(0.98);
pointer-events: none;
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif;
border: 1px solid rgba(255, 255, 255, 0.08);
}
/* When visible */
#ai-chat-assistant-container.visible {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: all;
}
/* When minimized */
#ai-chat-assistant-container.minimized {
height: 50px;
}
#ai-chat-assistant-container.minimized .ai-chat-body {
display: none;
}
/* Header */
.ai-chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background-color: var(--chat-darker-bg);
color: var(--chat-light-text);
border-top-left-radius: var(--chat-border-radius);
border-top-right-radius: var(--chat-border-radius);
cursor: move;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.ai-chat-title {
font-weight: 500;
font-size: 15px;
display: flex;
align-items: center;
gap: 6px;
}
.ai-chat-title::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
background-color: var(--chat-blue-accent);
border-radius: 50%;
box-shadow: 0 0 10px var(--chat-blue-accent);
}
.ai-chat-controls {
display: flex;
gap: 8px;
}
.ai-chat-btn {
background: none;
border: none;
color: var(--chat-light-text);
font-size: 18px;
cursor: pointer;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: var(--chat-transition);
}
.ai-chat-btn:hover {
background-color: rgba(255, 255, 255, 0.08);
}
/* Body */
.ai-chat-body {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: var(--chat-dark-bg);
}
/* Messages container */
.ai-chat-messages {
flex: 1;
overflow-y: auto;
padding: 15px;
display: flex;
flex-direction: column;
gap: 12px;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
}
.ai-chat-messages::-webkit-scrollbar {
width: 5px;
}
.ai-chat-messages::-webkit-scrollbar-track {
background: transparent;
}
.ai-chat-messages::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
}
/* Individual message */
.ai-chat-message {
max-width: 85%;
padding: 12px 16px;
border-radius: var(--chat-message-radius);
line-height: 1.5;
position: relative;
font-size: 14px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
animation: message-fade-in 0.3s ease;
word-break: break-word;
}
@keyframes message-fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* User message */
.ai-chat-message.user {
align-self: flex-end;
background-color: var(--chat-blue-accent);
color: white;
border-bottom-right-radius: 4px;
}
/* Assistant message */
.ai-chat-message.assistant {
align-self: flex-start;
background-color: rgba(255, 255, 255, 0.08);
color: var(--chat-light-text);
border-bottom-left-radius: 4px;
}
/* System message */
.ai-chat-message.system {
align-self: center;
background-color: rgba(255, 76, 76, 0.1);
color: var(--chat-error);
max-width: 90%;
font-size: 13px;
border-radius: 8px;
border: 1px solid rgba(255, 76, 76, 0.2);
}
/* Loading animation */
.ai-chat-message.loading {
background-color: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.7);
}
.ai-chat-message.loading:after {
content: "...";
animation: thinking 1.5s infinite;
}
@keyframes thinking {
0% { content: "."; }
33% { content: ".."; }
66% { content: "..."; }
}
/* Input area */
.ai-chat-input-container {
display: flex;
padding: 12px 16px;
border-top: 1px solid rgba(255, 255, 255, 0.05);
background-color: var(--chat-darker-bg);
}
#ai-chat-input {
flex: 1;
border: 1px solid rgba(255, 255, 255, 0.1);
background-color: rgba(255, 255, 255, 0.05);
color: var(--chat-light-text);
border-radius: 20px;
padding: 10px 16px;
font-size: 14px;
resize: none;
max-height: 100px;
outline: none;
font-family: inherit;
transition: var(--chat-transition);
}
#ai-chat-input::placeholder {
color: rgba(255, 255, 255, 0.4);
}
#ai-chat-input:focus {
border-color: var(--chat-blue-accent);
background-color: rgba(255, 255, 255, 0.07);
box-shadow: 0 0 0 1px rgba(61, 132, 247, 0.1);
}
.ai-chat-send-btn {
background: none;
border: none;
color: var(--chat-blue-accent);
cursor: pointer;
padding: 8px;
margin-left: 8px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: var(--chat-transition);
}
.ai-chat-send-btn:hover {
background-color: var(--chat-blue-light);
transform: scale(1.05);
}
/* Toggle button */
.ai-chat-toggle {
position: fixed;
right: 20px;
bottom: 20px;
width: 56px;
height: 56px;
border-radius: 50%;
background-color: var(--chat-blue-accent);
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 15px rgba(61, 132, 247, 0.35);
z-index: 9998;
transition: var(--chat-transition);
border: none;
}
.ai-chat-toggle:hover {
transform: scale(1.05);
box-shadow: 0 6px 20px rgba(61, 132, 247, 0.45);
}
#ai-chat-assistant-container.visible + .ai-chat-toggle {
transform: scale(0);
opacity: 0;
}
/* Code formatting */
.ai-chat-message pre {
background-color: rgba(0, 0, 0, 0.3);
padding: 10px;
border-radius: 6px;
overflow-x: auto;
margin: 10px 0;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.ai-chat-message code {
font-family: 'Cascadia Code', 'Fira Code', 'Source Code Pro', monospace;
font-size: 12px;
}
.ai-chat-message.user code {
background-color: rgba(255, 255, 255, 0.2);
padding: 2px 5px;
border-radius: 3px;
}
.ai-chat-message.assistant code {
background-color: rgba(0, 0, 0, 0.3);
padding: 2px 5px;
border-radius: 3px;
color: #e2e2e2;
}
/* Links */
.ai-chat-message a {
color: var(--chat-blue-accent);
text-decoration: none;
border-bottom: 1px dotted rgba(61, 132, 247, 0.5);
transition: var(--chat-transition);
}
.ai-chat-message a:hover {
border-bottom: 1px solid var(--chat-blue-accent);
}
.ai-chat-message.user a {
color: white;
border-bottom: 1px dotted rgba(255, 255, 255, 0.5);
}
.ai-chat-message.user a:hover {
border-bottom: 1px solid white;
}
/* Responsive adjustments */
@media (max-width: 768px) {
#ai-chat-assistant-container {
width: calc(100% - 20px);
height: 60vh;
right: 10px;
bottom: 10px;
}
.ai-chat-toggle {
right: 10px;
bottom: 10px;
}
}
/* Tab styles */
.ai-chat-tabs {
display: flex;
gap: 10px;
margin-right: 10px;
}
.ai-chat-tab {
background: none;
border: none;
color: var(--chat-light-text);
padding: 5px 10px;
cursor: pointer;
font-size: 14px;
border-radius: 4px;
transition: var(--chat-transition);
}
.ai-chat-tab:hover {
background-color: rgba(255, 255, 255, 0.08);
}
.ai-chat-tab.active {
background-color: var(--chat-blue-accent);
color: white;
}
/* Content area */
.ai-chat-content {
display: flex;
flex-direction: column;
height: 100%;
}
/* Memories tab styles */
.ai-chat-memories {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--chat-dark-bg);
}
.memories-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
padding-left: 16px;
padding-right: 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.memories-title {
display: inline;
align-items: center;
font-size: 14px;
color: var(--chat-light-text);
}
.memories-title a {
color: var(--chat-blue-accent);
text-decoration: none;
font-weight: 500;
transition: var(--chat-transition);
display: inline-flex;
align-items: center;
gap: 4px;
}
.memories-title a:hover {
color: var(--chat-blue-hover);
text-decoration: underline;
}
.memories-title a svg {
vertical-align: middle;
}
.memories-title svg {
vertical-align: middle;
margin-left: 4px;
}
.memories-list {
flex: 1;
overflow-y: auto;
padding: 10px;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
}
.memories-list::-webkit-scrollbar {
width: 5px;
}
.memories-list::-webkit-scrollbar-track {
background: transparent;
}
.memories-list::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
}
.memory-item {
background-color: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: var(--chat-message-radius);
padding: 12px 16px;
margin-bottom: 10px;
font-size: 14px;
line-height: 1.4;
color: var(--chat-light-text);
}
.memory-item:last-child {
margin-bottom: 0;
}
.loading, .no-memories, .error, .info {
text-align: center;
padding: 20px;
font-size: 14px;
color: var(--chat-light-text);
}
.error {
color: var(--chat-error);
font-size: 14px;
}
.info {
color: var(--chat-blue-accent);
}
@@ -0,0 +1,587 @@
:root {
--dark-bg: #1a1a1a;
--darker-bg: #121212;
--section-bg: #202020;
--light-text: #f1f1f1;
--dim-text: rgba(255, 255, 255, 0.7);
--dim-text-2: rgba(255, 255, 255, 0.5);
--blue-accent: #3d84f7;
--blue-hover: #2d74e7;
--blue-light: rgba(61, 132, 247, 0.15);
--error-color: #ff4a4a;
--warning-color: #ffaa33;
--success-color: #4caf50;
--border-radius: 8px;
--transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
body {
font-family: "Roboto", -apple-system, BlinkMacSystemFont, sans-serif;
margin: 0;
padding: 20px 20px 40px;
color: var(--light-text);
background-color: var(--dark-bg);
max-width: 1200px;
margin: 0 auto;
}
header {
max-width: 800px;
padding-left: 28px;
padding-top: 10px;
color: #f1f1f1;
}
h1 {
font-size: 32px;
margin: 0 0 12px 0;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
}
.title-container {
display: flex;
align-items: center;
gap: 10px;
}
.logo-img {
height: 20px;
width: auto;
margin-left: 8px;
position: relative;
top: 1px;
}
.powered-by {
font-size: 12px;
font-weight: normal;
color: rgba(255, 255, 255, 0.6);
line-height: 1;
}
.branding-container {
display: flex;
align-items: center;
justify-content: center;
}
.description {
color: var(--dim-text);
margin-bottom: 20px;
font-size: 15px;
line-height: 1.5;
}
.section {
margin-bottom: 30px;
background: var(--section-bg);
padding: 28px;
border-radius: var(--border-radius);
border: 1px solid rgba(255, 255, 255, 0.05);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
h2 {
font-size: 18px;
margin-top: 0;
margin-bottom: 15px;
color: var(--light-text);
display: flex;
align-items: center;
gap: 8px;
}
h2::before {
content: "";
display: inline-block;
width: 5px;
height: 20px;
background-color: var(--blue-accent);
border-radius: 3px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: var(--light-text);
}
input[type="text"],
input[type="password"],
input[type="number"],
select {
width: 100%;
padding: 12px;
background-color: rgba(255, 255, 255, 0.05);
color: var(--light-text);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: var(--border-radius);
font-size: 14px;
box-sizing: border-box;
transition: var(--transition);
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="number"]:focus,
select:focus {
border-color: var(--blue-accent);
outline: none;
box-shadow: 0 0 0 1px rgba(61, 132, 247, 0.2);
}
select {
appearance: none;
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%207l5%205%205-5%22%20stroke%3D%22%23fff%22%20stroke-width%3D%221.5%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
}
input[type="number"] {
width: 120px;
}
input[type="checkbox"] {
margin-right: 10px;
position: relative;
width: 18px;
height: 18px;
-webkit-appearance: none;
appearance: none;
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
cursor: pointer;
transition: var(--transition);
}
input[type="checkbox"]:checked {
background-color: var(--blue-accent);
border-color: var(--blue-accent);
}
input[type="checkbox"]:checked::after {
content: "";
position: absolute;
left: 5px;
top: 2px;
width: 6px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
input[type="checkbox"]:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.checkbox-label {
display: flex;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
color: var(--light-text);
}
.checkbox-label label {
margin-bottom: 0;
margin-left: 8px;
}
button {
background-color: var(--blue-accent);
color: white;
border: none;
padding: 12px 20px;
border-radius: var(--border-radius);
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
button:hover {
background-color: var(--blue-hover);
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
button:active {
transform: translateY(1px);
box-shadow: none;
}
button:disabled {
background-color: rgba(255, 255, 255, 0.1);
color: var(--dim-text-2);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.status {
padding: 15px;
border-radius: var(--border-radius);
margin-top: 20px;
font-size: 14px;
animation: fade-in 0.3s ease;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.status.error {
background-color: rgba(255, 74, 74, 0.1);
color: var(--error-color);
border: 1px solid rgba(255, 74, 74, 0.2);
}
.status.success {
background-color: rgba(76, 175, 80, 0.1);
color: var(--success-color);
border: 1px solid rgba(76, 175, 80, 0.2);
}
.status.warning {
background-color: rgba(255, 170, 51, 0.1);
color: var(--warning-color);
border: 1px solid rgba(255, 170, 51, 0.2);
}
.actions {
display: flex;
gap: 10px;
}
.secondary-button {
background-color: rgba(255, 255, 255, 0.08);
color: var(--light-text);
}
.secondary-button:hover {
background-color: rgba(255, 255, 255, 0.12);
}
.api-key-container {
display: flex;
gap: 10px;
}
.api-key-container input {
flex: 1;
}
/* Slider styles */
.slider-container {
margin-top: 12px;
display: flex;
align-items: center;
}
.slider {
-webkit-appearance: none;
flex: 1;
height: 4px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
outline: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--blue-accent);
cursor: pointer;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
transition: var(--transition);
}
.slider::-webkit-slider-thumb:hover {
transform: scale(1.1);
box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--blue-accent);
cursor: pointer;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
transition: var(--transition);
border: none;
}
.slider::-moz-range-thumb:hover {
transform: scale(1.1);
box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);
}
/* Add styles for memory creation section */
.memory-input {
width: 100%;
min-height: 150px;
padding: 12px;
background-color: rgba(255, 255, 255, 0.05);
color: var(--light-text);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: var(--border-radius);
font-size: 14px;
box-sizing: border-box;
transition: var(--transition);
resize: vertical;
font-family: inherit;
}
.memory-input:focus {
border-color: var(--blue-accent);
outline: none;
box-shadow: 0 0 0 1px rgba(61, 132, 247, 0.2);
}
.memory-result {
margin-top: 15px;
padding: 12px;
border-radius: var(--border-radius);
font-size: 14px;
display: none;
}
.memory-result.success {
background-color: rgba(76, 175, 80, 0.1);
color: var(--success-color);
border: 1px solid rgba(76, 175, 80, 0.2);
display: block;
}
.memory-result.error {
background-color: rgba(255, 74, 74, 0.1);
color: var(--error-color);
border: 1px solid rgba(255, 74, 74, 0.2);
display: block;
}
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: var(--light-text);
animation: spin 1s linear infinite;
margin-right: 8px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Add new styles for the memories sidebar */
.memories-sidebar {
position: fixed;
top: 0;
right: 0;
width: 384px;
height: 100vh;
background: var(--section-bg);
border-left: 1px solid rgba(255, 255, 255, 0.05);
transition: transform 0.3s ease;
z-index: 1000;
display: flex;
flex-direction: column;
}
.memories-sidebar.collapsed {
transform: translateX(384px);
}
.memories-header {
padding: 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
display: flex;
justify-content: space-between;
align-items: center;
}
.memories-title {
font-size: 16px;
font-weight: 500;
color: var(--light-text);
}
.memories-actions {
display: flex;
gap: 8px;
}
.memories-list {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.memory-item {
padding: 12px;
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: var(--border-radius);
margin-bottom: 12px;
cursor: pointer;
transition: var(--transition);
}
.memory-item:hover {
background: rgba(255, 255, 255, 0.05);
}
.memory-content {
font-size: 14px;
color: var(--light-text);
margin-bottom: 8px;
text-align: center;
text-wrap-style: pretty;
}
.memory-item .memory-content {
text-align: left;
}
.memory-meta {
font-size: 12px;
color: var(--dim-text);
}
.memory-actions {
display: flex;
gap: 8px;
margin-top: 8px;
}
.memory-action-btn {
padding: 8px;
font-size: 12px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.05);
color: var(--light-text);
border: none;
cursor: pointer;
transition: var(--transition);
}
.memory-action-btn:hover {
background: rgba(255, 255, 255, 0.1);
}
.memory-action-btn.delete:hover {
background-color: var(--error-color);
}
.edit-memory-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1100;
align-items: center;
justify-content: center;
}
.edit-memory-modal.open {
display: flex;
}
.edit-memory-content {
display: flex;
flex-direction: column;
background: var(--section-bg);
padding: 24px;
border-radius: var(--border-radius);
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
}
.edit-memory-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.edit-memory-title {
font-size: 18px;
font-weight: 500;
color: var(--light-text);
}
.edit-memory-close {
background: none;
border: none;
color: var(--dim-text);
cursor: pointer;
padding: 4px;
font-size: 20px;
width: 30px;
}
.edit-memory-textarea {
min-height: 20px;
max-height: 70px;
padding: 12px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: var(--border-radius);
color: var(--light-text);
font-family: inherit;
margin-bottom: 16px;
resize: vertical;
}
.edit-memory-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.main-content {
margin-right: 400px;
transition: margin-right 0.3s ease;
max-width: 800px;
}
.main-content.sidebar-collapsed {
margin-right: 0;
}
#status-container {
margin-bottom: 12px;
}
@@ -0,0 +1,259 @@
:root {
--dark-bg: #1a1a1a;
--darker-bg: #121212;
--light-text: #f1f1f1;
--blue-accent: #3d84f7;
--blue-hover: #2d74e7;
--blue-light: rgba(61, 132, 247, 0.15);
--error-color: #ff4a4a;
--warning-color: #ffaa33;
--success-color: #4caf50;
--border-radius: 8px;
--transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
body {
font-family: "Roboto", -apple-system, BlinkMacSystemFont, sans-serif;
width: 320px;
margin: 0;
padding: 0;
color: var(--light-text);
background-color: var(--dark-bg);
}
header {
background-color: var(--darker-bg);
color: var(--light-text);
padding: 16px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
h1 {
font-size: 18px;
margin: 0 0 8px 0;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
}
.logo-img {
height: 16px;
width: auto;
margin-left: 8px;
position: relative;
top: 1px;
}
.powered-by {
font-size: 12px;
font-weight: normal;
color: rgba(255, 255, 255, 0.6);
line-height: 1;
}
.branding-container {
display: flex;
align-items: center;
justify-content: center;
margin-top: 4px;
}
.content {
padding: 16px;
}
.status {
padding: 12px;
border-radius: var(--border-radius);
margin-bottom: 16px;
font-size: 14px;
animation: fade-in 0.3s ease;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.status.error {
background-color: rgba(255, 74, 74, 0.1);
color: var(--error-color);
border: 1px solid rgba(255, 74, 74, 0.2);
}
.status.success {
background-color: rgba(76, 175, 80, 0.1);
color: var(--success-color);
border: 1px solid rgba(76, 175, 80, 0.2);
}
.status.warning {
background-color: rgba(255, 170, 51, 0.1);
color: var(--warning-color);
border: 1px solid rgba(255, 170, 51, 0.2);
}
button {
background-color: var(--blue-accent);
color: white;
border: none;
padding: 12px 16px;
border-radius: 6px;
cursor: pointer;
width: 100%;
font-size: 14px;
font-weight: 500;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
button:hover {
background-color: var(--blue-hover);
transform: translateY(-1px);
}
button:active {
transform: translateY(1px);
}
button:disabled {
background-color: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.4);
cursor: not-allowed;
transform: none;
}
.actions {
display: flex;
flex-direction: row;
gap: 12px;
}
.api-key-section {
margin-bottom: 20px;
position: relative;
}
.api-key-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.toggle-password {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
padding: 4px;
cursor: pointer;
color: rgba(255, 255, 255, 0.5);
width: auto;
display: flex;
align-items: center;
justify-content: center;
}
.toggle-password:hover {
color: rgba(255, 255, 255, 0.8);
background: none;
transform: translateY(-50%);
}
.toggle-password .icon {
width: 16px;
height: 16px;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 12px;
padding-right: 40px;
background-color: rgba(255, 255, 255, 0.05);
color: var(--light-text);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: var(--border-radius);
margin-top: 6px;
box-sizing: border-box;
transition: var(--transition);
font-size: 14px;
}
input[type="text"]:focus,
input[type="password"]:focus {
border-color: var(--blue-accent);
outline: none;
box-shadow: 0 0 0 1px rgba(61, 132, 247, 0.2);
}
input::placeholder {
color: rgba(255, 255, 255, 0.3);
}
label {
font-size: 14px;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
display: block;
margin-bottom: 4px;
}
.save-button {
margin-top: 10px;
}
.mem0-status {
margin-top: 20px;
padding: 12px;
background-color: rgba(255, 255, 255, 0.03);
border-radius: var(--border-radius);
font-size: 13px;
color: rgba(255, 255, 255, 0.7);
}
.mem0-status p {
margin: 0;
}
#mem0-status-text {
color: var(--blue-accent);
font-weight: 500;
}
/* Icons */
.icon {
display: inline-block;
width: 18px;
height: 18px;
fill: currentColor;
}
.get-key-link {
color: var(--blue-accent);
text-decoration: none;
font-size: 13px;
transition: color 0.2s ease;
}
.get-key-link:hover {
color: var(--blue-accent-hover);
text-decoration: underline;
}
.get-key-link:visited {
color: var(--blue-accent);
}
@@ -0,0 +1,40 @@
const path = require('path');
module.exports = {
mode: 'production',
entry: {
content: './src/content.js',
options: './src/options.js',
popup: './src/popup.js',
background: './src/background.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
devtool: 'source-map',
optimization: {
minimize: false
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
resolve: {
extensions: ['.js']
}
};
+7 -4
View File
@@ -1,6 +1,6 @@
{
"name": "mem0ai",
"version": "2.1.13",
"version": "2.1.18",
"description": "The Memory Layer For Your AI Apps",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -92,22 +92,25 @@
},
"dependencies": {
"axios": "1.7.7",
"openai": "4.28.0",
"openai": "^4.93.0",
"uuid": "9.0.1",
"zod": "3.22.4"
"zod": "^3.24.1"
},
"peerDependencies": {
"@anthropic-ai/sdk": "0.18.0",
"@google/genai": "^0.7.0",
"@mistralai/mistralai": "^1.5.2",
"@qdrant/js-client-rest": "1.13.0",
"@supabase/supabase-js": "^2.49.1",
"@types/jest": "29.5.14",
"@types/pg": "8.11.0",
"@types/sqlite3": "3.1.11",
"groq-sdk": "0.3.0",
"@langchain/core": "^0.3.44",
"neo4j-driver": "^5.28.1",
"ollama": "^0.5.14",
"pg": "8.11.3",
"redis": "4.7.0",
"redis": "^4.6.13",
"sqlite3": "5.1.7"
},
"engines": {
+427 -13
View File
@@ -10,6 +10,15 @@ 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)
"@langchain/core":
specifier: ^0.3.44
version: 0.3.44(openai@4.93.0(encoding@0.1.13)(ws@8.18.1)(zod@3.24.2))
"@mistralai/mistralai":
specifier: ^1.5.2
version: 1.5.2(zod@3.24.2)
"@qdrant/js-client-rest":
specifier: 1.13.0
version: 1.13.0(typescript@5.5.4)
@@ -38,13 +47,13 @@ importers:
specifier: ^0.5.14
version: 0.5.14
openai:
specifier: 4.28.0
version: 4.28.0(encoding@0.1.13)
specifier: ^4.93.0
version: 4.93.0(encoding@0.1.13)(ws@8.18.1)(zod@3.24.2)
pg:
specifier: 8.11.3
version: 8.11.3
redis:
specifier: 4.7.0
specifier: ^4.6.13
version: 4.7.0
sqlite3:
specifier: 5.1.7
@@ -53,8 +62,8 @@ importers:
specifier: 9.0.1
version: 9.0.1
zod:
specifier: 3.22.4
version: 3.22.4
specifier: ^3.24.1
version: 3.24.2
devDependencies:
"@types/node":
specifier: ^22.7.6
@@ -370,6 +379,12 @@ packages:
integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==,
}
"@cfworker/json-schema@4.1.1":
resolution:
{
integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==,
}
"@cspotcode/source-map-support@0.8.1":
resolution:
{
@@ -615,6 +630,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:
{
@@ -783,6 +805,21 @@ packages:
integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==,
}
"@langchain/core@0.3.44":
resolution:
{
integrity: sha512-3BsSFf7STvPPZyl2kMANgtVnCUvDdyP4k+koP+nY2Tczd5V+RFkuazIn/JOj/xxy/neZjr4PxFU4BFyF1aKXOA==,
}
engines: { node: ">=18" }
"@mistralai/mistralai@1.5.2":
resolution:
{
integrity: sha512-mBTIDQmuAX9RowMYteZFHJIYlEwDcHzzaxgXzrFtlvH9CkKXK7R1VnZ1sZSe+uLMg0dIXUVdPRUh1SwyFeSqXw==,
}
peerDependencies:
zod: ">= 3"
"@nodelib/fs.scandir@2.1.5":
resolution:
{
@@ -1242,6 +1279,12 @@ packages:
integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==,
}
"@types/retry@0.12.0":
resolution:
{
integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==,
}
"@types/sqlite3@3.1.11":
resolution:
{
@@ -1254,6 +1297,12 @@ packages:
integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==,
}
"@types/uuid@10.0.0":
resolution:
{
integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==,
}
"@types/uuid@9.0.8":
resolution:
{
@@ -1313,6 +1362,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 +1540,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 +1605,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:
{
@@ -1772,6 +1840,12 @@ packages:
integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==,
}
console-table-printer@2.12.1:
resolution:
{
integrity: sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==,
}
convert-source-map@2.0.0:
resolution:
{
@@ -1817,6 +1891,13 @@ packages:
supports-color:
optional: true
decamelize@1.2.0:
resolution:
{
integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==,
}
engines: { node: ">=0.10.0" }
decompress-response@6.0.0:
resolution:
{
@@ -1916,6 +1997,12 @@ packages:
integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==,
}
ecdsa-sig-formatter@1.0.11:
resolution:
{
integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==,
}
ejs@3.1.10:
resolution:
{
@@ -2045,6 +2132,12 @@ packages:
}
engines: { node: ">=6" }
eventemitter3@4.0.7:
resolution:
{
integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==,
}
execa@5.1.1:
resolution:
{
@@ -2073,6 +2166,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 +2321,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 +2418,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 +2451,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 +2532,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:
{
@@ -2834,6 +2975,12 @@ packages:
}
engines: { node: ">=10" }
js-tiktoken@1.0.19:
resolution:
{
integrity: sha512-XC63YQeEcS47Y53gg950xiZ4IWmkfMe4p2V9OSaBt26q+p47WHn18izuXzSclCI73B7yGqtfRsT6jcZQI0y08g==,
}
js-tokens@4.0.0:
resolution:
{
@@ -2861,6 +3008,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 +3035,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:
{
@@ -2895,6 +3060,17 @@ packages:
integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==,
}
langsmith@0.3.15:
resolution:
{
integrity: sha512-cv3ebg0Hh0gRbl72cv/uzaZ+KOdfa2mGF1s74vmB2vlNVO/Ap/O9RYaHV+tpR8nwhGZ50R3ILnTOwSwGP+XQxw==,
}
peerDependencies:
openai: "*"
peerDependenciesMeta:
openai:
optional: true
leven@3.1.0:
resolution:
{
@@ -3163,6 +3339,13 @@ packages:
integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==,
}
mustache@4.2.0:
resolution:
{
integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==,
}
hasBin: true
mz@2.7.0:
resolution:
{
@@ -3343,12 +3526,27 @@ packages:
}
engines: { node: ">=6" }
openai@4.28.0:
openai@4.93.0:
resolution:
{
integrity: sha512-JM8fhcpmpGN0vrUwGquYIzdcEQHtFuom6sRCbbCM6CfzZXNuRk33G7KfeRAIfnaCxSpzrP5iHtwJzIm6biUZ2Q==,
integrity: sha512-2kONcISbThKLfm7T9paVzg+QCE1FOZtNMMUfXyXckUAoXRRS/mTP89JSDHPMp8uM5s0bz28RISbvQjArD6mgUQ==,
}
hasBin: true
peerDependencies:
ws: ^8.18.0
zod: ^3.23.8
peerDependenciesMeta:
ws:
optional: true
zod:
optional: true
p-finally@1.0.0:
resolution:
{
integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==,
}
engines: { node: ">=4" }
p-limit@2.3.0:
resolution:
@@ -3378,6 +3576,27 @@ packages:
}
engines: { node: ">=10" }
p-queue@6.6.2:
resolution:
{
integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==,
}
engines: { node: ">=8" }
p-retry@4.6.2:
resolution:
{
integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==,
}
engines: { node: ">=8" }
p-timeout@3.2.0:
resolution:
{
integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==,
}
engines: { node: ">=8" }
p-try@2.2.0:
resolution:
{
@@ -3816,6 +4035,13 @@ packages:
}
engines: { node: ">= 4" }
retry@0.13.1:
resolution:
{
integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==,
}
engines: { node: ">= 4" }
reusify@1.1.0:
resolution:
{
@@ -3937,6 +4163,12 @@ packages:
}
engines: { node: ">=10" }
simple-wcswidth@1.0.1:
resolution:
{
integrity: sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==,
}
sisteransi@1.0.5:
resolution:
{
@@ -4430,6 +4662,13 @@ packages:
integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==,
}
uuid@10.0.0:
resolution:
{
integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==,
}
hasBin: true
uuid@9.0.1:
resolution:
{
@@ -4616,10 +4855,18 @@ packages:
}
engines: { node: ">=10" }
zod@3.22.4:
zod-to-json-schema@3.24.5:
resolution:
{
integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==,
integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==,
}
peerDependencies:
zod: ^3.24.1
zod@3.24.2:
resolution:
{
integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==,
}
snapshots:
@@ -4829,6 +5076,8 @@ snapshots:
"@bcoe/v8-coverage@0.2.3": {}
"@cfworker/json-schema@4.1.1": {}
"@cspotcode/source-map-support@0.8.1":
dependencies:
"@jridgewell/trace-mapping": 0.3.9
@@ -4913,6 +5162,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
@@ -5116,6 +5375,28 @@ snapshots:
"@jridgewell/resolve-uri": 3.1.2
"@jridgewell/sourcemap-codec": 1.5.0
"@langchain/core@0.3.44(openai@4.93.0(encoding@0.1.13)(ws@8.18.1)(zod@3.24.2))":
dependencies:
"@cfworker/json-schema": 4.1.1
ansi-styles: 5.2.0
camelcase: 6.3.0
decamelize: 1.2.0
js-tiktoken: 1.0.19
langsmith: 0.3.15(openai@4.93.0(encoding@0.1.13)(ws@8.18.1)(zod@3.24.2))
mustache: 4.2.0
p-queue: 6.6.2
p-retry: 4.6.2
uuid: 10.0.0
zod: 3.24.2
zod-to-json-schema: 3.24.5(zod@3.24.2)
transitivePeerDependencies:
- openai
"@mistralai/mistralai@1.5.2(zod@3.24.2)":
dependencies:
zod: 3.24.2
zod-to-json-schema: 3.24.5(zod@3.24.2)
"@nodelib/fs.scandir@2.1.5":
dependencies:
"@nodelib/fs.stat": 2.0.5
@@ -5368,12 +5649,16 @@ snapshots:
"@types/phoenix@1.6.6": {}
"@types/retry@0.12.0": {}
"@types/sqlite3@3.1.11":
dependencies:
"@types/node": 22.13.5
"@types/stack-utils@2.0.3": {}
"@types/uuid@10.0.0": {}
"@types/uuid@9.0.8": {}
"@types/ws@8.18.0":
@@ -5406,6 +5691,8 @@ snapshots:
- supports-color
optional: true
agent-base@7.1.3: {}
agentkeepalive@4.6.0:
dependencies:
humanize-ms: 1.2.1
@@ -5527,6 +5814,8 @@ snapshots:
base64-js@1.5.1: {}
bignumber.js@9.2.0: {}
binary-extensions@2.3.0: {}
bindings@1.5.0:
@@ -5567,6 +5856,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: {}
@@ -5695,6 +5986,10 @@ snapshots:
console-control-strings@1.1.0:
optional: true
console-table-printer@2.12.1:
dependencies:
simple-wcswidth: 1.0.1
convert-source-map@2.0.0: {}
create-jest@29.7.0(@types/node@22.13.5)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.5.4)):
@@ -5728,6 +6023,8 @@ snapshots:
optionalDependencies:
supports-color: 5.5.0
decamelize@1.2.0: {}
decompress-response@6.0.0:
dependencies:
mimic-response: 3.1.0
@@ -5766,6 +6063,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
@@ -5848,6 +6149,8 @@ snapshots:
event-target-shim@5.0.1: {}
eventemitter3@4.0.7: {}
execa@5.1.1:
dependencies:
cross-spawn: 7.0.6
@@ -5872,6 +6175,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 +6267,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 +6341,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 +6373,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 +6424,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:
@@ -6516,6 +6870,10 @@ snapshots:
joycon@3.1.1: {}
js-tiktoken@1.0.19:
dependencies:
base64-js: 1.5.1
js-tokens@4.0.0: {}
js-yaml@3.14.1:
@@ -6528,16 +6886,43 @@ 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: {}
langsmith@0.3.15(openai@4.93.0(encoding@0.1.13)(ws@8.18.1)(zod@3.24.2)):
dependencies:
"@types/uuid": 10.0.0
chalk: 4.1.2
console-table-printer: 2.12.1
p-queue: 6.6.2
p-retry: 4.6.2
semver: 7.7.1
uuid: 10.0.0
optionalDependencies:
openai: 4.93.0(encoding@0.1.13)(ws@8.18.1)(zod@3.24.2)
leven@3.1.0: {}
lilconfig@3.1.3: {}
@@ -6689,6 +7074,8 @@ snapshots:
ms@2.1.3: {}
mustache@4.2.0: {}
mz@2.7.0:
dependencies:
any-promise: 1.3.0
@@ -6808,20 +7195,23 @@ snapshots:
dependencies:
mimic-fn: 2.1.0
openai@4.28.0(encoding@0.1.13):
openai@4.93.0(encoding@0.1.13)(ws@8.18.1)(zod@3.24.2):
dependencies:
"@types/node": 18.19.76
"@types/node-fetch": 2.6.12
abort-controller: 3.0.0
agentkeepalive: 4.6.0
digest-fetch: 1.3.0
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0(encoding@0.1.13)
web-streams-polyfill: 3.3.3
optionalDependencies:
ws: 8.18.1
zod: 3.24.2
transitivePeerDependencies:
- encoding
p-finally@1.0.0: {}
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
@@ -6839,6 +7229,20 @@ snapshots:
aggregate-error: 3.1.0
optional: true
p-queue@6.6.2:
dependencies:
eventemitter3: 4.0.7
p-timeout: 3.2.0
p-retry@4.6.2:
dependencies:
"@types/retry": 0.12.0
retry: 0.13.1
p-timeout@3.2.0:
dependencies:
p-finally: 1.0.0
p-try@2.2.0: {}
package-json-from-dist@1.0.1: {}
@@ -7077,6 +7481,8 @@ snapshots:
retry@0.12.0:
optional: true
retry@0.13.1: {}
reusify@1.1.0: {}
rimraf@3.0.2:
@@ -7156,6 +7562,8 @@ snapshots:
dependencies:
semver: 7.7.1
simple-wcswidth@1.0.1: {}
sisteransi@1.0.5: {}
slash@3.0.0: {}
@@ -7467,6 +7875,8 @@ snapshots:
util-deprecate@1.0.2: {}
uuid@10.0.0: {}
uuid@9.0.1: {}
v8-compile-cache-lib@3.0.1: {}
@@ -7561,4 +7971,8 @@ snapshots:
yocto-queue@0.1.0: {}
zod@3.22.4: {}
zod-to-json-schema@3.24.5(zod@3.24.2):
dependencies:
zod: 3.24.2
zod@3.24.2: {}
+1
View File
@@ -20,6 +20,7 @@ export interface MemoryOptions {
start_date?: string;
end_date?: string;
custom_categories?: custom_categories[];
custom_instructions?: string;
}
export interface ProjectOptions {
+5 -2
View File
@@ -1,10 +1,13 @@
// @ts-nocheck
import type { TelemetryClient, TelemetryOptions } from "./telemetry.types";
let version = "2.1.12";
let version = "2.1.16";
// 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/";
@@ -0,0 +1,78 @@
import dotenv from "dotenv";
import { MistralLLM } from "../../src/llms/mistral";
// Load environment variables
dotenv.config();
async function testMistral() {
// Check for API key
if (!process.env.MISTRAL_API_KEY) {
console.error("MISTRAL_API_KEY environment variable is required");
process.exit(1);
}
console.log("Testing Mistral LLM implementation...");
// Initialize MistralLLM
const mistral = new MistralLLM({
apiKey: process.env.MISTRAL_API_KEY,
model: "mistral-tiny-latest", // You can change to other models like mistral-small-latest
});
try {
// Test simple chat completion
console.log("Testing simple chat completion:");
const chatResponse = await mistral.generateChat([
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What is the capital of France?" },
]);
console.log("Chat response:");
console.log(`Role: ${chatResponse.role}`);
console.log(`Content: ${chatResponse.content}\n`);
// Test with functions/tools
console.log("Testing tool calling:");
const tools = [
{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather in a given location",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "The unit of temperature",
},
},
required: ["location"],
},
},
},
];
const toolResponse = await mistral.generateResponse(
[
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What's the weather like in Paris, France?" },
],
undefined,
tools,
);
console.log("Tool response:", toolResponse);
console.log("\n✅ All tests completed successfully");
} catch (error) {
console.error("Error testing Mistral LLM:", error);
}
}
testMistral().catch(console.error);
+1
View File
@@ -22,6 +22,7 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = {
config: {
apiKey: process.env.OPENAI_API_KEY || "",
model: "gpt-4-turbo-preview",
modelProperties: undefined,
},
},
enableGraph: false,
+73 -25
View File
@@ -9,40 +9,88 @@ export class ConfigManager {
provider:
userConfig.embedder?.provider ||
DEFAULT_MEMORY_CONFIG.embedder.provider,
config: {
apiKey:
userConfig.embedder?.config?.apiKey ||
DEFAULT_MEMORY_CONFIG.embedder.config.apiKey,
model:
userConfig.embedder?.config?.model ||
DEFAULT_MEMORY_CONFIG.embedder.config.model,
},
config: (() => {
const defaultConf = DEFAULT_MEMORY_CONFIG.embedder.config;
const userConf = userConfig.embedder?.config;
let finalModel: string | any = defaultConf.model;
if (userConf?.model && typeof userConf.model === "object") {
finalModel = userConf.model;
} else if (userConf?.model && typeof userConf.model === "string") {
finalModel = userConf.model;
}
return {
apiKey:
userConf?.apiKey !== undefined
? userConf.apiKey
: defaultConf.apiKey,
model: finalModel,
url: userConf?.url,
modelProperties:
userConf?.modelProperties !== undefined
? userConf.modelProperties
: defaultConf.modelProperties,
};
})(),
},
vectorStore: {
provider:
userConfig.vectorStore?.provider ||
DEFAULT_MEMORY_CONFIG.vectorStore.provider,
config: {
collectionName:
userConfig.vectorStore?.config?.collectionName ||
DEFAULT_MEMORY_CONFIG.vectorStore.config.collectionName,
dimension:
userConfig.vectorStore?.config?.dimension ||
DEFAULT_MEMORY_CONFIG.vectorStore.config.dimension,
...userConfig.vectorStore?.config,
},
config: (() => {
const defaultConf = DEFAULT_MEMORY_CONFIG.vectorStore.config;
const userConf = userConfig.vectorStore?.config;
// Prioritize user-provided client instance
if (userConf?.client && typeof userConf.client === "object") {
return {
client: userConf.client,
// Include other fields from userConf if necessary, or omit defaults
collectionName: userConf.collectionName, // Can be undefined
dimension: userConf.dimension || defaultConf.dimension, // Merge dimension
...userConf, // Include any other passthrough fields from user
};
} else {
// If no client provided, merge standard fields
return {
collectionName:
userConf?.collectionName || defaultConf.collectionName,
dimension: userConf?.dimension || defaultConf.dimension,
// Ensure client is not carried over from defaults if not provided by user
client: undefined,
// Include other passthrough fields from userConf even if no client
...userConf,
};
}
})(),
},
llm: {
provider:
userConfig.llm?.provider || DEFAULT_MEMORY_CONFIG.llm.provider,
config: {
apiKey:
userConfig.llm?.config?.apiKey ||
DEFAULT_MEMORY_CONFIG.llm.config.apiKey,
model:
userConfig.llm?.config?.model ||
DEFAULT_MEMORY_CONFIG.llm.config.model,
},
config: (() => {
const defaultConf = DEFAULT_MEMORY_CONFIG.llm.config;
const userConf = userConfig.llm?.config;
let finalModel: string | any = defaultConf.model;
if (userConf?.model && typeof userConf.model === "object") {
finalModel = userConf.model;
} else if (userConf?.model && typeof userConf.model === "string") {
finalModel = userConf.model;
}
return {
apiKey:
userConf?.apiKey !== undefined
? userConf.apiKey
: defaultConf.apiKey,
model: finalModel,
modelProperties:
userConf?.modelProperties !== undefined
? userConf.modelProperties
: defaultConf.modelProperties,
};
})(),
},
historyDbPath:
userConfig.historyDbPath || DEFAULT_MEMORY_CONFIG.historyDbPath,
+39
View File
@@ -0,0 +1,39 @@
import { AzureOpenAI } from "openai";
import { Embedder } from "./base";
import { EmbeddingConfig } from "../types";
export class AzureOpenAIEmbedder implements Embedder {
private client: AzureOpenAI;
private model: string;
constructor(config: EmbeddingConfig) {
if (!config.apiKey || !config.modelProperties?.endpoint) {
throw new Error("Azure OpenAI requires both API key and endpoint");
}
const { endpoint, ...rest } = config.modelProperties;
this.client = new AzureOpenAI({
apiKey: config.apiKey,
endpoint: endpoint as string,
...rest,
});
this.model = config.model || "text-embedding-3-small";
}
async embed(text: string): Promise<number[]> {
const response = await this.client.embeddings.create({
model: this.model,
input: text,
});
return response.data[0].embedding;
}
async embedBatch(texts: string[]): Promise<number[][]> {
const response = await this.client.embeddings.create({
model: this.model,
input: texts,
});
return response.data.map((item) => item.embedding);
}
}
@@ -0,0 +1,50 @@
import { Embeddings } from "@langchain/core/embeddings";
import { Embedder } from "./base";
import { EmbeddingConfig } from "../types";
export class LangchainEmbedder implements Embedder {
private embedderInstance: Embeddings;
private batchSize?: number; // Some LC embedders have batch size
constructor(config: EmbeddingConfig) {
// Check if config.model is provided and is an object (the instance)
if (!config.model || typeof config.model !== "object") {
throw new Error(
"Langchain embedder provider requires an initialized Langchain Embeddings instance passed via the 'model' field in the embedder config.",
);
}
// Basic check for embedding methods
if (
typeof (config.model as any).embedQuery !== "function" ||
typeof (config.model as any).embedDocuments !== "function"
) {
throw new Error(
"Provided Langchain 'instance' in the 'model' field does not appear to be a valid Langchain Embeddings instance (missing embedQuery or embedDocuments method).",
);
}
this.embedderInstance = config.model as Embeddings;
// Store batch size if the instance has it (optional)
this.batchSize = (this.embedderInstance as any).batchSize;
}
async embed(text: string): Promise<number[]> {
try {
// Use embedQuery for single text embedding
return await this.embedderInstance.embedQuery(text);
} catch (error) {
console.error("Error embedding text with Langchain Embedder:", error);
throw error;
}
}
async embedBatch(texts: string[]): Promise<number[][]> {
try {
// Use embedDocuments for batch embedding
// Langchain's embedDocuments handles batching internally if needed/supported
return await this.embedderInstance.embedDocuments(texts);
} catch (error) {
console.error("Error embedding batch with Langchain Embedder:", error);
throw error;
}
}
}
+54
View File
@@ -1,3 +1,5 @@
import { z } from "zod";
export interface GraphToolParameters {
source: string;
destination: string;
@@ -21,6 +23,58 @@ export interface GraphRelationsParameters {
}>;
}
// --- Zod Schemas for Tool Arguments ---
// Schema for simple relationship arguments (Update, Delete)
export const GraphSimpleRelationshipArgsSchema = z.object({
source: z
.string()
.describe("The identifier of the source node in the relationship."),
relationship: z
.string()
.describe("The relationship between the source and destination nodes."),
destination: z
.string()
.describe("The identifier of the destination node in the relationship."),
});
// Schema for adding a relationship (includes types)
export const GraphAddRelationshipArgsSchema =
GraphSimpleRelationshipArgsSchema.extend({
source_type: z
.string()
.describe("The type or category of the source node."),
destination_type: z
.string()
.describe("The type or category of the destination node."),
});
// Schema for extracting entities
export const GraphExtractEntitiesArgsSchema = z.object({
entities: z
.array(
z.object({
entity: z.string().describe("The name or identifier of the entity."),
entity_type: z.string().describe("The type or category of the entity."),
}),
)
.describe("An array of entities with their types."),
});
// Schema for establishing relationships
export const GraphRelationsArgsSchema = z.object({
entities: z
.array(GraphSimpleRelationshipArgsSchema)
.describe("An array of relationships (source, relationship, destination)."),
});
// --- Tool Definitions (using JSON schema, keep as is) ---
// Note: The tool definitions themselves still use JSON schema format
// as expected by the LLM APIs. The Zod schemas above are for internal
// validation and potentially for use with Langchain's .withStructuredOutput
// if we adapt it to handle tool calls via schema.
export const UPDATE_MEMORY_TOOL_GRAPH = {
type: "function",
function: {
+6
View File
@@ -5,6 +5,8 @@ export * from "./embeddings/base";
export * from "./embeddings/openai";
export * from "./embeddings/ollama";
export * from "./embeddings/google";
export * from "./embeddings/azure";
export * from "./embeddings/langchain";
export * from "./llms/base";
export * from "./llms/openai";
export * from "./llms/google";
@@ -12,8 +14,12 @@ export * from "./llms/openai_structured";
export * from "./llms/anthropic";
export * from "./llms/groq";
export * from "./llms/ollama";
export * from "./llms/mistral";
export * from "./llms/langchain";
export * from "./vector_stores/base";
export * from "./vector_stores/memory";
export * from "./vector_stores/qdrant";
export * from "./vector_stores/redis";
export * from "./vector_stores/supabase";
export * from "./vector_stores/langchain";
export * from "./utils/factory";
+82
View File
@@ -0,0 +1,82 @@
import { AzureOpenAI } from "openai";
import { LLM, LLMResponse } from "./base";
import { LLMConfig, Message } from "../types";
export class AzureOpenAILLM implements LLM {
private client: AzureOpenAI;
private model: string;
constructor(config: LLMConfig) {
if (!config.apiKey || !config.modelProperties?.endpoint) {
throw new Error("Azure OpenAI requires both API key and endpoint");
}
const { endpoint, ...rest } = config.modelProperties;
this.client = new AzureOpenAI({
apiKey: config.apiKey,
endpoint: endpoint as string,
...rest,
});
this.model = config.model || "gpt-4";
}
async generateResponse(
messages: Message[],
responseFormat?: { type: string },
tools?: any[],
): Promise<string | LLMResponse> {
const completion = await this.client.chat.completions.create({
messages: messages.map((msg) => {
const role = msg.role as "system" | "user" | "assistant";
return {
role,
content:
typeof msg.content === "string"
? msg.content
: JSON.stringify(msg.content),
};
}),
model: this.model,
response_format: responseFormat as { type: "text" | "json_object" },
...(tools && { tools, tool_choice: "auto" }),
});
const response = completion.choices[0].message;
if (response.tool_calls) {
return {
content: response.content || "",
role: response.role,
toolCalls: response.tool_calls.map((call) => ({
name: call.function.name,
arguments: call.function.arguments,
})),
};
}
return response.content || "";
}
async generateChat(messages: Message[]): Promise<LLMResponse> {
const completion = await this.client.chat.completions.create({
messages: messages.map((msg) => {
const role = msg.role as "system" | "user" | "assistant";
return {
role,
content:
typeof msg.content === "string"
? msg.content
: JSON.stringify(msg.content),
};
}),
model: this.model,
});
const response = completion.choices[0].message;
return {
content: response.content || "",
role: response.role,
};
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ export interface LLMResponse {
export interface LLM {
generateResponse(
messages: Array<{ role: string; content: string }>,
response_format: { type: string },
response_format?: { type: string },
tools?: any[],
): Promise<any>;
generateChat(messages: Message[]): Promise<LLMResponse>;
+3 -1
View File
@@ -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 || "";
}
+255
View File
@@ -0,0 +1,255 @@
import { BaseLanguageModel } from "@langchain/core/language_models/base";
import {
AIMessage,
HumanMessage,
SystemMessage,
BaseMessage,
} from "@langchain/core/messages";
import { z } from "zod";
import { LLM, LLMResponse } from "./base";
import { LLMConfig, Message } from "../types/index";
// Import the schemas directly into LangchainLLM
import { FactRetrievalSchema, MemoryUpdateSchema } from "../prompts";
// Import graph tool argument schemas
import {
GraphExtractEntitiesArgsSchema,
GraphRelationsArgsSchema,
GraphSimpleRelationshipArgsSchema, // Used for delete tool
} from "../graphs/tools";
const convertToLangchainMessages = (messages: Message[]): BaseMessage[] => {
return messages.map((msg) => {
const content =
typeof msg.content === "string"
? msg.content
: JSON.stringify(msg.content);
switch (msg.role?.toLowerCase()) {
case "system":
return new SystemMessage(content);
case "user":
case "human":
return new HumanMessage(content);
case "assistant":
case "ai":
return new AIMessage(content);
default:
console.warn(
`Unsupported message role '${msg.role}' for Langchain. Treating as 'human'.`,
);
return new HumanMessage(content);
}
});
};
export class LangchainLLM implements LLM {
private llmInstance: BaseLanguageModel;
private modelName: string;
constructor(config: LLMConfig) {
if (!config.model || typeof config.model !== "object") {
throw new Error(
"Langchain provider requires an initialized Langchain instance passed via the 'model' field in the LLM config.",
);
}
if (typeof (config.model as any).invoke !== "function") {
throw new Error(
"Provided Langchain 'instance' in the 'model' field does not appear to be a valid Langchain language model (missing invoke method).",
);
}
this.llmInstance = config.model as BaseLanguageModel;
this.modelName =
(this.llmInstance as any).modelId ||
(this.llmInstance as any).model ||
"langchain-model";
}
async generateResponse(
messages: Message[],
response_format?: { type: string },
tools?: any[],
): Promise<string | LLMResponse> {
const langchainMessages = convertToLangchainMessages(messages);
let runnable: any = this.llmInstance;
const invokeOptions: Record<string, any> = {};
let isStructuredOutput = false;
let selectedSchema: z.ZodSchema<any> | null = null;
let isToolCallResponse = false;
// --- Internal Schema Selection Logic (runs regardless of response_format) ---
const systemPromptContent =
(messages.find((m) => m.role === "system")?.content as string) || "";
const userPromptContent =
(messages.find((m) => m.role === "user")?.content as string) || "";
const toolNames = tools?.map((t) => t.function.name) || [];
// Prioritize tool call argument schemas
if (toolNames.includes("extract_entities")) {
selectedSchema = GraphExtractEntitiesArgsSchema;
isToolCallResponse = true;
} else if (toolNames.includes("establish_relationships")) {
selectedSchema = GraphRelationsArgsSchema;
isToolCallResponse = true;
} else if (toolNames.includes("delete_graph_memory")) {
selectedSchema = GraphSimpleRelationshipArgsSchema;
isToolCallResponse = true;
}
// Check for memory prompts if no tool schema matched
else if (
systemPromptContent.includes("Personal Information Organizer") &&
systemPromptContent.includes("extract relevant pieces of information")
) {
selectedSchema = FactRetrievalSchema;
} else if (
userPromptContent.includes("smart memory manager") &&
userPromptContent.includes("Compare newly retrieved facts")
) {
selectedSchema = MemoryUpdateSchema;
}
// --- Apply Structured Output if Schema Selected ---
if (
selectedSchema &&
typeof (this.llmInstance as any).withStructuredOutput === "function"
) {
// Apply if a schema was selected (for memory or single tool calls)
if (
!isToolCallResponse ||
(isToolCallResponse && tools && tools.length === 1)
) {
try {
runnable = (this.llmInstance as any).withStructuredOutput(
selectedSchema,
{ name: tools?.[0]?.function.name },
);
isStructuredOutput = true;
} catch (e) {
isStructuredOutput = false; // Ensure flag is false on error
// No fallback to response_format here unless explicitly passed
if (response_format?.type === "json_object") {
invokeOptions.response_format = { type: "json_object" };
}
}
} else if (isToolCallResponse) {
// If multiple tools, don't apply structured output, handle via tool binding below
}
} else if (selectedSchema && response_format?.type === "json_object") {
// Schema selected, but no .withStructuredOutput. Try basic response_format only if explicitly requested.
if (
(this.llmInstance as any)._identifyingParams?.response_format ||
(this.llmInstance as any).response_format
) {
invokeOptions.response_format = { type: "json_object" };
}
} else if (!selectedSchema && response_format?.type === "json_object") {
// Explicit JSON request, but no schema inferred. Try basic response_format.
if (
(this.llmInstance as any)._identifyingParams?.response_format ||
(this.llmInstance as any).response_format
) {
invokeOptions.response_format = { type: "json_object" };
}
}
// --- Handle tool binding ---
if (tools && tools.length > 0) {
if (typeof (runnable as any).bindTools === "function") {
try {
runnable = (runnable as any).bindTools(tools);
} catch (e) {}
} else {
}
}
// --- Invoke and Process Response ---
try {
const response = await runnable.invoke(langchainMessages, invokeOptions);
if (isStructuredOutput && !isToolCallResponse) {
// Memory prompt with structured output
return JSON.stringify(response);
} else if (isStructuredOutput && isToolCallResponse) {
// Tool call with structured arguments
if (response?.tool_calls && Array.isArray(response.tool_calls)) {
const mappedToolCalls = response.tool_calls.map((call: any) => ({
name: call.name || tools?.[0]?.function.name || "unknown_tool",
arguments:
typeof call.args === "string"
? call.args
: JSON.stringify(call.args),
}));
return {
content: response.content || "",
role: "assistant",
toolCalls: mappedToolCalls,
};
} else {
// Direct object response for tool args
return {
content: "",
role: "assistant",
toolCalls: [
{
name: tools?.[0]?.function.name || "unknown_tool",
arguments: JSON.stringify(response),
},
],
};
}
} else if (
response &&
response.tool_calls &&
Array.isArray(response.tool_calls)
) {
// Standard tool call response (no structured output used/failed)
const mappedToolCalls = response.tool_calls.map((call: any) => ({
name: call.name || "unknown_tool",
arguments:
typeof call.args === "string"
? call.args
: JSON.stringify(call.args),
}));
return {
content: response.content || "",
role: "assistant",
toolCalls: mappedToolCalls,
};
} else if (response && typeof response.content === "string") {
// Standard text response
return response.content;
} else {
// Fallback for unexpected formats
return JSON.stringify(response);
}
} catch (error) {
throw error;
}
}
async generateChat(messages: Message[]): Promise<LLMResponse> {
const langchainMessages = convertToLangchainMessages(messages);
try {
const response = await this.llmInstance.invoke(langchainMessages);
if (response && typeof response.content === "string") {
return {
content: response.content,
role: (response as BaseMessage).lc_id ? "assistant" : "assistant",
};
} else {
console.warn(
`Unexpected response format from Langchain instance (${this.modelName}) for generateChat:`,
response,
);
return {
content: JSON.stringify(response),
role: "assistant",
};
}
} catch (error) {
console.error(
`Error invoking Langchain instance (${this.modelName}) for generateChat:`,
error,
);
throw error;
}
}
}
+112
View File
@@ -0,0 +1,112 @@
import { Mistral } from "@mistralai/mistralai";
import { LLM, LLMResponse } from "./base";
import { LLMConfig, Message } from "../types";
export class MistralLLM implements LLM {
private client: Mistral;
private model: string;
constructor(config: LLMConfig) {
if (!config.apiKey) {
throw new Error("Mistral API key is required");
}
this.client = new Mistral({
apiKey: config.apiKey,
});
this.model = config.model || "mistral-tiny-latest";
}
// Helper function to convert content to string
private contentToString(content: any): string {
if (typeof content === "string") {
return content;
}
if (Array.isArray(content)) {
// Handle ContentChunk array - extract text content
return content
.map((chunk) => {
if (chunk.type === "text") {
return chunk.text;
} else {
return JSON.stringify(chunk);
}
})
.join("");
}
return String(content || "");
}
async generateResponse(
messages: Message[],
responseFormat?: { type: string },
tools?: any[],
): Promise<string | LLMResponse> {
const response = await this.client.chat.complete({
model: this.model,
messages: messages.map((msg) => ({
role: msg.role as "system" | "user" | "assistant",
content:
typeof msg.content === "string"
? msg.content
: JSON.stringify(msg.content),
})),
...(tools && { tools }),
...(responseFormat && { response_format: responseFormat }),
});
if (!response || !response.choices || response.choices.length === 0) {
return "";
}
const message = response.choices[0].message;
if (!message) {
return "";
}
if (message.toolCalls && message.toolCalls.length > 0) {
return {
content: this.contentToString(message.content),
role: message.role || "assistant",
toolCalls: message.toolCalls.map((call) => ({
name: call.function.name,
arguments:
typeof call.function.arguments === "string"
? call.function.arguments
: JSON.stringify(call.function.arguments),
})),
};
}
return this.contentToString(message.content);
}
async generateChat(messages: Message[]): Promise<LLMResponse> {
const formattedMessages = messages.map((msg) => ({
role: msg.role as "system" | "user" | "assistant",
content:
typeof msg.content === "string"
? msg.content
: JSON.stringify(msg.content),
}));
const response = await this.client.chat.complete({
model: this.model,
messages: formattedMessages,
});
if (!response || !response.choices || response.choices.length === 0) {
return {
content: "",
role: "assistant",
};
}
const message = response.choices[0].message;
return {
content: this.contentToString(message.content),
role: message.role || "assistant",
};
}
}
+138 -11
View File
@@ -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;
@@ -41,10 +43,11 @@ export class Memory {
private vectorStore: VectorStore;
private llm: LLM;
private db: HistoryManager;
private collectionName: string;
private collectionName: string | undefined;
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,
@@ -185,12 +241,10 @@ export class Memory {
}
const parsedMessages = messages.map((m) => m.content).join("\n");
// Get prompts
const [systemPrompt, userPrompt] = this.customPrompt
? [this.customPrompt, `Input:\n${parsedMessages}`]
: getFactRetrievalMessages(parsedMessages);
// Extract facts using LLM
const response = await this.llm.generateResponse(
[
{ role: "system", content: systemPrompt },
@@ -199,8 +253,18 @@ export class Memory {
{ type: "json_object" },
);
const cleanResponse = removeCodeBlocks(response);
const facts = JSON.parse(cleanResponse).facts || [];
const cleanResponse = removeCodeBlocks(response as string);
let facts: string[] = [];
try {
facts = JSON.parse(cleanResponse).facts || [];
} catch (e) {
console.error(
"Failed to parse facts from LLM response:",
cleanResponse,
e,
);
facts = [];
}
// Get embeddings for new facts
const newMessageEmbeddings: Record<string, number[]> = {};
@@ -236,13 +300,24 @@ export class Memory {
// Get memory update decisions
const updatePrompt = getUpdateMemoryMessages(uniqueOldMemories, facts);
const updateResponse = await this.llm.generateResponse(
[{ role: "user", content: updatePrompt }],
{ type: "json_object" },
);
const cleanUpdateResponse = removeCodeBlocks(updateResponse);
const memoryActions = JSON.parse(cleanUpdateResponse).memory || [];
const cleanUpdateResponse = removeCodeBlocks(updateResponse as string);
let memoryActions: any[] = [];
try {
memoryActions = JSON.parse(cleanUpdateResponse).memory || [];
} catch (e) {
console.error(
"Failed to parse memory actions from LLM response:",
cleanUpdateResponse,
e,
);
memoryActions = [];
}
// Process memory actions
const results: MemoryItem[] = [];
@@ -341,6 +416,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 +482,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 +497,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,18 +528,58 @@ export class Memory {
}
async reset(): Promise<void> {
await this._captureEvent("reset");
await this.db.reset();
await this.vectorStore.deleteCol();
if (this.graphMemory) {
await this.graphMemory.deleteAll({ userId: "default" });
// Check provider before attempting deleteCol
if (this.config.vectorStore.provider.toLowerCase() !== "langchain") {
try {
await this.vectorStore.deleteCol();
} catch (e) {
console.error(
`Failed to delete collection for provider '${this.config.vectorStore.provider}':`,
e,
);
// Decide if you want to re-throw or just log
}
} else {
console.warn(
"Memory.reset(): Skipping vector store collection deletion as 'langchain' provider is used. Underlying Langchain vector store data is not cleared by this operation.",
);
}
if (this.graphMemory) {
await this.graphMemory.deleteAll({ userId: "default" }); // Assuming this is okay, or needs similar check?
}
// Re-initialize factories/clients based on the original config
this.embedder = EmbedderFactory.create(
this.config.embedder.provider,
this.config.embedder.config,
);
// Re-create vector store instance - crucial for Langchain to reset wrapper state if needed
this.vectorStore = VectorStoreFactory.create(
this.config.vectorStore.provider,
this.config.vectorStore.config,
this.config.vectorStore.config, // This will pass the original client instance back
);
this.llm = LLMFactory.create(
this.config.llm.provider,
this.config.llm.config,
);
// Re-init DB if needed (though db.reset() likely handles its state)
// Re-init Graph if needed
// Re-initialize telemetry
this._initializeTelemetry();
}
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 = {};
+34
View File
@@ -1,3 +1,37 @@
import { z } from "zod";
// Define Zod schema for fact retrieval output
export const FactRetrievalSchema = z.object({
facts: z
.array(z.string())
.describe("An array of distinct facts extracted from the conversation."),
});
// Define Zod schema for memory update output
export const MemoryUpdateSchema = z.object({
memory: z
.array(
z.object({
id: z.string().describe("The unique identifier of the memory item."),
text: z.string().describe("The content of the memory item."),
event: z
.enum(["ADD", "UPDATE", "DELETE", "NONE"])
.describe(
"The action taken for this memory item (ADD, UPDATE, DELETE, or NONE).",
),
old_memory: z
.string()
.optional()
.describe(
"The previous content of the memory item if the event was UPDATE.",
),
}),
)
.describe(
"An array representing the state of memory items after processing new facts.",
),
});
export function getFactRetrievalMessages(
parsedMessages: string,
): [string, string] {
+15 -8
View File
@@ -14,13 +14,16 @@ export interface Message {
export interface EmbeddingConfig {
apiKey?: string;
model?: string;
model?: string | any;
url?: string;
modelProperties?: Record<string, any>;
}
export interface VectorStoreConfig {
collectionName: string;
collectionName?: string;
dimension?: number;
client?: any;
instance?: any;
[key: string]: any;
}
@@ -38,7 +41,8 @@ export interface LLMConfig {
provider?: string;
config?: Record<string, any>;
apiKey?: string;
model?: string;
model?: string | any;
modelProperties?: Record<string, any>;
}
export interface Neo4jConfig {
@@ -109,24 +113,27 @@ export const MemoryConfigSchema = z.object({
embedder: z.object({
provider: z.string(),
config: z.object({
apiKey: z.string(),
model: z.string().optional(),
modelProperties: z.record(z.string(), z.any()).optional(),
apiKey: z.string().optional(),
model: z.union([z.string(), z.any()]).optional(),
}),
}),
vectorStore: z.object({
provider: z.string(),
config: z
.object({
collectionName: z.string(),
collectionName: z.string().optional(),
dimension: z.number().optional(),
client: z.any().optional(),
})
.passthrough(),
}),
llm: z.object({
provider: z.string(),
config: z.object({
apiKey: z.string(),
model: z.string().optional(),
apiKey: z.string().optional(),
model: z.union([z.string(), z.any()]).optional(),
modelProperties: z.record(z.string(), z.any()).optional(),
}),
}),
historyDbPath: z.string().optional(),
+22 -4
View File
@@ -4,6 +4,7 @@ import { OpenAILLM } from "../llms/openai";
import { OpenAIStructuredLLM } from "../llms/openai_structured";
import { AnthropicLLM } from "../llms/anthropic";
import { GroqLLM } from "../llms/groq";
import { MistralLLM } from "../llms/mistral";
import { MemoryVectorStore } from "../vector_stores/memory";
import {
EmbeddingConfig,
@@ -24,6 +25,11 @@ import { SupabaseHistoryManager } from "../storage/SupabaseHistoryManager";
import { HistoryManager } from "../storage/base";
import { GoogleEmbedder } from "../embeddings/google";
import { GoogleLLM } from "../llms/google";
import { AzureOpenAILLM } from "../llms/azure";
import { AzureOpenAIEmbedder } from "../embeddings/azure";
import { LangchainLLM } from "../llms/langchain";
import { LangchainEmbedder } from "../embeddings/langchain";
import { LangchainVectorStore } from "../vector_stores/langchain";
export class EmbedderFactory {
static create(provider: string, config: EmbeddingConfig): Embedder {
@@ -34,6 +40,10 @@ export class EmbedderFactory {
return new OllamaEmbedder(config);
case "google":
return new GoogleEmbedder(config);
case "azure_openai":
return new AzureOpenAIEmbedder(config);
case "langchain":
return new LangchainEmbedder(config);
default:
throw new Error(`Unsupported embedder provider: ${provider}`);
}
@@ -42,7 +52,7 @@ export class EmbedderFactory {
export class LLMFactory {
static create(provider: string, config: LLMConfig): LLM {
switch (provider) {
switch (provider.toLowerCase()) {
case "openai":
return new OpenAILLM(config);
case "openai_structured":
@@ -55,6 +65,12 @@ export class LLMFactory {
return new OllamaLLM(config);
case "google":
return new GoogleLLM(config);
case "azure_openai":
return new AzureOpenAILLM(config);
case "mistral":
return new MistralLLM(config);
case "langchain":
return new LangchainLLM(config);
default:
throw new Error(`Unsupported LLM provider: ${provider}`);
}
@@ -67,11 +83,13 @@ export class VectorStoreFactory {
case "memory":
return new MemoryVectorStore(config);
case "qdrant":
return new Qdrant(config as any); // Type assertion needed as config is extended
return new Qdrant(config as any);
case "redis":
return new RedisDB(config as any); // Type assertion needed as config is extended
return new RedisDB(config as any);
case "supabase":
return new SupabaseDB(config as any); // Type assertion needed as config is extended
return new SupabaseDB(config as any);
case "langchain":
return new LangchainVectorStore(config as any);
default:
throw new Error(`Unsupported vector store provider: ${provider}`);
}
+98
View File
@@ -0,0 +1,98 @@
import type {
TelemetryClient,
TelemetryInstance,
TelemetryEventData,
} from "./telemetry.types";
let version = "2.1.16";
// 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>;
}
@@ -0,0 +1,231 @@
import { VectorStore as LangchainVectorStoreInterface } from "@langchain/core/vectorstores";
import { Document } from "@langchain/core/documents";
import { VectorStore } from "./base"; // mem0's VectorStore interface
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
// Config specifically for the Langchain wrapper
interface LangchainStoreConfig extends VectorStoreConfig {
client: LangchainVectorStoreInterface;
// dimension might still be useful for validation if not automatically inferred
}
export class LangchainVectorStore implements VectorStore {
private lcStore: LangchainVectorStoreInterface;
private dimension?: number;
private storeUserId: string = "anonymous-langchain-user"; // Simple in-memory user ID
constructor(config: LangchainStoreConfig) {
if (!config.client || typeof config.client !== "object") {
throw new Error(
"Langchain vector store provider requires an initialized Langchain VectorStore instance passed via the 'client' field.",
);
}
// Basic checks for core methods
if (
typeof config.client.addVectors !== "function" ||
typeof config.client.similaritySearchVectorWithScore !== "function"
) {
throw new Error(
"Provided Langchain 'client' does not appear to be a valid Langchain VectorStore (missing addVectors or similaritySearchVectorWithScore method).",
);
}
this.lcStore = config.client;
this.dimension = config.dimension;
// Attempt to get dimension from the underlying store if not provided
if (
!this.dimension &&
(this.lcStore as any).embeddings?.embeddingDimension
) {
this.dimension = (this.lcStore as any).embeddings.embeddingDimension;
}
if (
!this.dimension &&
(this.lcStore as any).embedding?.embeddingDimension
) {
this.dimension = (this.lcStore as any).embedding.embeddingDimension;
}
// If still no dimension, we might need to throw or warn, as it's needed for validation
if (!this.dimension) {
console.warn(
"LangchainVectorStore: Could not determine embedding dimension. Input validation might be skipped.",
);
}
}
// --- Method Mappings ---
async insert(
vectors: number[][],
ids: string[],
payloads: Record<string, any>[],
): Promise<void> {
if (!ids || ids.length !== vectors.length) {
throw new Error(
"IDs array must be provided and have the same length as vectors.",
);
}
if (this.dimension) {
vectors.forEach((v, i) => {
if (v.length !== this.dimension) {
throw new Error(
`Vector dimension mismatch at index ${i}. Expected ${this.dimension}, got ${v.length}`,
);
}
});
}
// Convert payloads to Langchain Document metadata format
const documents = payloads.map((payload, i) => {
// Provide empty pageContent, store mem0 id and other data in metadata
return new Document({
pageContent: "", // Add required empty pageContent
metadata: { ...payload, _mem0_id: ids[i] },
});
});
// Use addVectors. Note: Langchain stores often generate their own internal IDs.
// We store the mem0 ID in the metadata (`_mem0_id`).
try {
await this.lcStore.addVectors(vectors, documents, { ids }); // Pass mem0 ids if the store supports it
} catch (e) {
// Fallback if the store doesn't support passing ids directly during addVectors
console.warn(
"Langchain store might not support custom IDs on insert. Trying without IDs.",
e,
);
await this.lcStore.addVectors(vectors, documents);
}
}
async search(
query: number[],
limit: number = 5,
filters?: SearchFilters, // filters parameter is received but will be ignored
): Promise<VectorStoreResult[]> {
if (this.dimension && query.length !== this.dimension) {
throw new Error(
`Query vector dimension mismatch. Expected ${this.dimension}, got ${query.length}`,
);
}
// --- Remove filter processing logic ---
// Filters passed via mem0 interface are not reliably translatable to generic Langchain stores.
// let lcFilter: any = undefined;
// if (filters && ...) { ... }
// console.warn("LangchainVectorStore: Passing filters directly..."); // Remove warning
// Call similaritySearchVectorWithScore WITHOUT the filter argument
const results = await this.lcStore.similaritySearchVectorWithScore(
query,
limit,
// Do not pass lcFilter here
);
// Map Langchain results [Document, score] back to mem0 VectorStoreResult
return results.map(([doc, score]) => ({
id: doc.metadata._mem0_id || "unknown_id",
payload: doc.metadata,
score: score,
}));
}
// --- Methods with No Direct Langchain Equivalent (Throwing Errors) ---
async get(vectorId: string): Promise<VectorStoreResult | null> {
// Most Langchain stores lack a direct getById. Simulation is inefficient.
console.error(
`LangchainVectorStore: The 'get' method is not directly supported by most Langchain VectorStores.`,
);
throw new Error(
"Method 'get' not reliably supported by LangchainVectorStore wrapper.",
);
// Potential (inefficient) simulation:
// Perform a search with a filter like { _mem0_id: vectorId }, limit 1.
// This requires the underlying store to support filtering on _mem0_id.
}
async update(
vectorId: string,
vector: number[],
payload: Record<string, any>,
): Promise<void> {
// Updates often require delete + add in Langchain.
console.error(
`LangchainVectorStore: The 'update' method is not directly supported. Use delete followed by insert.`,
);
throw new Error(
"Method 'update' not supported by LangchainVectorStore wrapper.",
);
// Possible implementation: Check if store has delete, call delete({_mem0_id: vectorId}), then insert.
}
async delete(vectorId: string): Promise<void> {
// Check if the underlying store supports deletion by ID
if (typeof (this.lcStore as any).delete === "function") {
try {
// We need to delete based on our stored _mem0_id.
// Langchain's delete often takes its own internal IDs or filter.
// Attempting deletion via filter is the most likely approach.
console.warn(
"LangchainVectorStore: Attempting delete via filter on '_mem0_id'. Success depends on the specific Langchain VectorStore's delete implementation.",
);
await (this.lcStore as any).delete({ filter: { _mem0_id: vectorId } });
// OR if it takes IDs directly (less common for *our* IDs):
// await (this.lcStore as any).delete({ ids: [vectorId] });
} catch (e) {
console.error(
`LangchainVectorStore: Delete failed. Underlying store's delete method might expect different arguments or filters. Error: ${e}`,
);
throw new Error(`Delete failed in underlying Langchain store: ${e}`);
}
} else {
console.error(
`LangchainVectorStore: The underlying Langchain store instance does not seem to support a 'delete' method.`,
);
throw new Error(
"Method 'delete' not available on the provided Langchain VectorStore client.",
);
}
}
async list(
filters?: SearchFilters,
limit: number = 100,
): Promise<[VectorStoreResult[], number]> {
// No standard list method in Langchain core interface.
console.error(
`LangchainVectorStore: The 'list' method is not supported by the generic LangchainVectorStore wrapper.`,
);
throw new Error(
"Method 'list' not supported by LangchainVectorStore wrapper.",
);
// Could potentially be implemented if the underlying store has a specific list/scroll/query capability.
}
async deleteCol(): Promise<void> {
console.error(
`LangchainVectorStore: The 'deleteCol' method is not supported by the generic LangchainVectorStore wrapper.`,
);
throw new Error(
"Method 'deleteCol' not supported by LangchainVectorStore wrapper.",
);
}
// --- Wrapper-Specific Methods (In-Memory User ID) ---
async getUserId(): Promise<string> {
return this.storeUserId;
}
async setUserId(userId: string): Promise<void> {
this.storeUserId = userId;
}
async initialize(): Promise<void> {
// No specific initialization needed for the wrapper itself,
// assuming the passed Langchain client is already initialized.
return Promise.resolve();
}
}
@@ -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();
}
}
+44 -11
View File
@@ -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],
);
}
}
+158 -100
View File
@@ -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;
}
}
}
+86 -51
View File
@@ -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;
}
}
}
+99 -8
View File
@@ -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,16 +99,20 @@ 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);
// First try to delete any existing test vector
try {
await this.client.from(this.tableName).delete().eq("id", "test_vector");
} catch (error) {
console.warn("No test vector to delete, safe to ignore.");
} catch {
// Ignore delete errors - table might not exist yet
}
const { error: testError } = await this.client
// Try to insert the test vector
const { error: insertError } = await this.client
.from(this.tableName)
.insert({
id: "test_vector",
@@ -111,8 +121,9 @@ export class SupabaseDB implements VectorStore {
})
.select();
if (testError) {
console.error("Test insert error:", testError);
// If we get a duplicate key error, that's actually fine - it means the table exists
if (insertError && insertError.code !== "23505") {
console.error("Test insert error:", insertError);
throw new Error(
`Vector operations failed. Please ensure:
1. The vector extension is enabled
@@ -133,6 +144,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),
@@ -166,8 +183,12 @@ See the SQL migration instructions in the code comments.`,
);
}
// Clean up test vector
await this.client.from(this.tableName).delete().eq("id", "test_vector");
// Clean up test vector - ignore errors here too
try {
await this.client.from(this.tableName).delete().eq("id", "test_vector");
} catch {
// Ignore delete errors
}
console.log("Connected to Supabase successfully");
} catch (error) {
@@ -336,4 +357,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);
}
}
}
+1 -1
View File
@@ -3,4 +3,4 @@ import importlib.metadata
__version__ = importlib.metadata.version("mem0ai")
from mem0.client.main import AsyncMemoryClient, MemoryClient # noqa
from mem0.memory.main import Memory # noqa
from mem0.memory.main import Memory, AsyncMemory # noqa
+64 -56
View File
@@ -1,6 +1,7 @@
import logging
import os
import warnings
import hashlib
from functools import wraps
from typing import Any, Dict, List, Optional, Union
@@ -83,13 +84,16 @@ class MemoryClient:
if not self.api_key:
raise ValueError("Mem0 API Key not provided. Please provide an API Key.")
# Create MD5 hash of API key for user_id
self.user_id = hashlib.md5(self.api_key.encode()).hexdigest()
self.client = httpx.Client(
base_url=self.host,
headers={"Authorization": f"Token {self.api_key}", "Mem0-User-ID": self.user_id},
timeout=300,
)
self.user_email = self._validate_api_key()
capture_client_event("client.init", self)
capture_client_event("client.init", self, {"sync_type": "sync"})
def _validate_api_key(self):
"""Validate the API key by making a test request."""
@@ -143,7 +147,7 @@ class MemoryClient:
response.raise_for_status()
if "metadata" in kwargs:
del kwargs["metadata"]
capture_client_event("client.add", self, {"keys": list(kwargs.keys())})
capture_client_event("client.add", self, {"keys": list(kwargs.keys()), "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -162,7 +166,7 @@ class MemoryClient:
params = self._prepare_params()
response = self.client.get(f"/v1/memories/{memory_id}/", params=params)
response.raise_for_status()
capture_client_event("client.get", self, {"memory_id": memory_id})
capture_client_event("client.get", self, {"memory_id": memory_id, "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -194,7 +198,7 @@ class MemoryClient:
capture_client_event(
"client.get_all",
self,
{"api_version": version, "keys": list(kwargs.keys())},
{"api_version": version, "keys": list(kwargs.keys()), "sync_type": "sync"},
)
return response.json()
@@ -220,7 +224,7 @@ class MemoryClient:
response.raise_for_status()
if "metadata" in kwargs:
del kwargs["metadata"]
capture_client_event("client.search", self, {"api_version": version, "keys": list(kwargs.keys())})
capture_client_event("client.search", self, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -233,7 +237,7 @@ class MemoryClient:
Returns:
Dict[str, Any]: The response from the server.
"""
capture_client_event("client.update", self, {"memory_id": memory_id})
capture_client_event("client.update", self, {"memory_id": memory_id, "sync_type": "sync"})
params = self._prepare_params()
response = self.client.put(f"/v1/memories/{memory_id}/", json={"text": data}, params=params)
response.raise_for_status()
@@ -255,7 +259,7 @@ class MemoryClient:
params = self._prepare_params()
response = self.client.delete(f"/v1/memories/{memory_id}/", params=params)
response.raise_for_status()
capture_client_event("client.delete", self, {"memory_id": memory_id})
capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -274,7 +278,7 @@ class MemoryClient:
params = self._prepare_params(kwargs)
response = self.client.delete("/v1/memories/", params=params)
response.raise_for_status()
capture_client_event("client.delete_all", self, {"keys": list(kwargs.keys())})
capture_client_event("client.delete_all", self, {"keys": list(kwargs.keys()), "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -293,7 +297,7 @@ class MemoryClient:
params = self._prepare_params()
response = self.client.get(f"/v1/memories/{memory_id}/history/", params=params)
response.raise_for_status()
capture_client_event("client.history", self, {"memory_id": memory_id})
capture_client_event("client.history", self, {"memory_id": memory_id, "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -302,7 +306,7 @@ class MemoryClient:
params = self._prepare_params()
response = self.client.get("/v1/entities/", params=params)
response.raise_for_status()
capture_client_event("client.users", self)
capture_client_event("client.users", self, {"sync_type": "sync"})
return response.json()
@api_error_handler
@@ -354,7 +358,7 @@ class MemoryClient:
response.raise_for_status()
capture_client_event(
"client.delete_users", self, {"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id}
"client.delete_users", self, {"user_id": user_id, "agent_id": agent_id, "app_id": app_id, "run_id": run_id, "sync_type": "sync"}
)
return {
"message": "Entity deleted successfully."
@@ -378,7 +382,7 @@ class MemoryClient:
# This will also delete the memories
self.delete_users()
capture_client_event("client.reset", self)
capture_client_event("client.reset", self, {"sync_type": "sync"})
return {"message": "Client reset successful. All users and memories deleted."}
@api_error_handler
@@ -399,7 +403,7 @@ class MemoryClient:
response = self.client.put("/v1/batch/", json={"memories": memories})
response.raise_for_status()
capture_client_event("client.batch_update", self)
capture_client_event("client.batch_update", self, {"sync_type": "sync"})
return response.json()
@api_error_handler
@@ -419,7 +423,7 @@ class MemoryClient:
response = self.client.request("DELETE", "/v1/batch/", json={"memories": memories})
response.raise_for_status()
capture_client_event("client.batch_delete", self)
capture_client_event("client.batch_delete", self, {"sync_type": "sync"})
return response.json()
@api_error_handler
@@ -435,7 +439,7 @@ class MemoryClient:
"""
response = self.client.post("/v1/exports/", json={"schema": schema, **self._prepare_params(kwargs)})
response.raise_for_status()
capture_client_event("client.create_memory_export", self, {"schema": schema, "keys": list(kwargs.keys())})
capture_client_event("client.create_memory_export", self, {"schema": schema, "keys": list(kwargs.keys()), "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -448,9 +452,9 @@ 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())})
capture_client_event("client.get_memory_export", self, {"keys": list(kwargs.keys()), "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -476,12 +480,15 @@ class MemoryClient:
params=params,
)
response.raise_for_status()
capture_client_event("client.get_project_details", self, {"fields": fields})
capture_client_event("client.get_project_details", self, {"fields": fields, "sync_type": "sync"})
return response.json()
@api_error_handler
def update_project(
self, custom_instructions: Optional[str] = None, custom_categories: Optional[List[str]] = None
self,
custom_instructions: Optional[str] = None,
custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
) -> Dict[str, Any]:
"""Update the project settings.
@@ -499,13 +506,13 @@ class MemoryClient:
if not (self.org_id and self.project_id):
raise ValueError("org_id and project_id must be set to update instructions or categories")
if custom_instructions is None and custom_categories is None:
if custom_instructions is None and custom_categories is None and retrieval_criteria is None:
raise ValueError(
"Currently we only support updating custom_instructions or custom_categories, so you must provide at least one of them"
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
)
payload = self._prepare_params(
{"custom_instructions": custom_instructions, "custom_categories": custom_categories}
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria}
)
response = self.client.patch(
f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
@@ -515,7 +522,7 @@ class MemoryClient:
capture_client_event(
"client.update_project",
self,
{"custom_instructions": custom_instructions, "custom_categories": custom_categories},
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria, "sync_type": "sync"},
)
return response.json()
@@ -544,7 +551,7 @@ class MemoryClient:
response = self.client.get(f"api/v1/webhooks/projects/{project_id}/")
response.raise_for_status()
capture_client_event("client.get_webhook", self)
capture_client_event("client.get_webhook", self, {"sync_type": "sync"})
return response.json()
@api_error_handler
@@ -567,7 +574,7 @@ class MemoryClient:
payload = {"url": url, "name": name, "event_types": event_types}
response = self.client.post(f"api/v1/webhooks/projects/{project_id}/", json=payload)
response.raise_for_status()
capture_client_event("client.create_webhook", self)
capture_client_event("client.create_webhook", self, {"sync_type": "sync"})
return response.json()
@api_error_handler
@@ -596,7 +603,7 @@ class MemoryClient:
payload = {k: v for k, v in {"name": name, "url": url, "event_types": event_types}.items() if v is not None}
response = self.client.put(f"api/v1/webhooks/{webhook_id}/", json=payload)
response.raise_for_status()
capture_client_event("client.update_webhook", self, {"webhook_id": webhook_id})
capture_client_event("client.update_webhook", self, {"webhook_id": webhook_id, "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -615,7 +622,7 @@ class MemoryClient:
response = self.client.delete(f"api/v1/webhooks/{webhook_id}/")
response.raise_for_status()
capture_client_event("client.delete_webhook", self, {"webhook_id": webhook_id})
capture_client_event("client.delete_webhook", self, {"webhook_id": webhook_id, "sync_type": "sync"})
return response.json()
@api_error_handler
@@ -632,7 +639,7 @@ class MemoryClient:
response = self.client.post("/v1/feedback/", json=data)
response.raise_for_status()
capture_client_event("client.feedback", self, data)
capture_client_event("client.feedback", self, data, {"sync_type": "sync"})
return response.json()
def _prepare_payload(
@@ -721,7 +728,7 @@ class AsyncMemoryClient:
response.raise_for_status()
if "metadata" in kwargs:
del kwargs["metadata"]
capture_client_event("async_client.add", self.sync_client, {"keys": list(kwargs.keys())})
capture_client_event("client.add", self.sync_client, {"keys": list(kwargs.keys()), "sync_type": "async"})
return response.json()
@api_error_handler
@@ -729,7 +736,7 @@ class AsyncMemoryClient:
params = self.sync_client._prepare_params()
response = await self.async_client.get(f"/v1/memories/{memory_id}/", params=params)
response.raise_for_status()
capture_client_event("async_client.get", self.sync_client, {"memory_id": memory_id})
capture_client_event("client.get", self.sync_client, {"memory_id": memory_id, "sync_type": "async"})
return response.json()
@api_error_handler
@@ -743,7 +750,7 @@ class AsyncMemoryClient:
if "metadata" in kwargs:
del kwargs["metadata"]
capture_client_event(
"async_client.get_all", self.sync_client, {"api_version": version, "keys": list(kwargs.keys())}
"client.get_all", self.sync_client, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "async"}
)
return response.json()
@@ -756,7 +763,7 @@ class AsyncMemoryClient:
if "metadata" in kwargs:
del kwargs["metadata"]
capture_client_event(
"async_client.search", self.sync_client, {"api_version": version, "keys": list(kwargs.keys())}
"client.search", self.sync_client, {"api_version": version, "keys": list(kwargs.keys()), "sync_type": "async"}
)
return response.json()
@@ -765,7 +772,7 @@ class AsyncMemoryClient:
params = self.sync_client._prepare_params()
response = await self.async_client.put(f"/v1/memories/{memory_id}/", json={"text": data}, params=params)
response.raise_for_status()
capture_client_event("async_client.update", self.sync_client, {"memory_id": memory_id})
capture_client_event("client.update", self.sync_client, {"memory_id": memory_id, "sync_type": "async"})
return response.json()
@api_error_handler
@@ -773,7 +780,7 @@ class AsyncMemoryClient:
params = self.sync_client._prepare_params()
response = await self.async_client.delete(f"/v1/memories/{memory_id}/", params=params)
response.raise_for_status()
capture_client_event("async_client.delete", self.sync_client, {"memory_id": memory_id})
capture_client_event("client.delete", self.sync_client, {"memory_id": memory_id, "sync_type": "async"})
return response.json()
@api_error_handler
@@ -781,7 +788,7 @@ class AsyncMemoryClient:
params = self.sync_client._prepare_params(kwargs)
response = await self.async_client.delete("/v1/memories/", params=params)
response.raise_for_status()
capture_client_event("async_client.delete_all", self.sync_client, {"keys": list(kwargs.keys())})
capture_client_event("client.delete_all", self.sync_client, {"keys": list(kwargs.keys()), "sync_type": "async"})
return response.json()
@api_error_handler
@@ -789,7 +796,7 @@ class AsyncMemoryClient:
params = self.sync_client._prepare_params()
response = await self.async_client.get(f"/v1/memories/{memory_id}/history/", params=params)
response.raise_for_status()
capture_client_event("async_client.history", self.sync_client, {"memory_id": memory_id})
capture_client_event("client.history", self.sync_client, {"memory_id": memory_id, "sync_type": "async"})
return response.json()
@api_error_handler
@@ -797,7 +804,7 @@ class AsyncMemoryClient:
params = self.sync_client._prepare_params()
response = await self.async_client.get("/v1/entities/", params=params)
response.raise_for_status()
capture_client_event("async_client.users", self.sync_client)
capture_client_event("client.users", self.sync_client, {"sync_type": "async"})
return response.json()
@api_error_handler
@@ -848,7 +855,7 @@ class AsyncMemoryClient:
response = await self.async_client.delete(f"/v1/entities/{entity['type']}/{entity['id']}/", params=params)
response.raise_for_status()
capture_client_event("async_client.delete_users", self.sync_client)
capture_client_event("client.delete_users", self.sync_client, {"sync_type": "async"})
return {
"message": "Entity deleted successfully."
if (user_id or agent_id or app_id or run_id)
@@ -858,7 +865,7 @@ class AsyncMemoryClient:
@api_error_handler
async def reset(self) -> Dict[str, str]:
await self.delete_users()
capture_client_event("async_client.reset", self.sync_client)
capture_client_event("client.reset", self.sync_client, {"sync_type": "async"})
return {"message": "Client reset successful. All users and memories deleted."}
@api_error_handler
@@ -879,7 +886,7 @@ class AsyncMemoryClient:
response = await self.async_client.put("/v1/batch/", json={"memories": memories})
response.raise_for_status()
capture_client_event("async_client.batch_update", self.sync_client)
capture_client_event("client.batch_update", self.sync_client, {"sync_type": "async"})
return response.json()
@api_error_handler
@@ -899,7 +906,7 @@ class AsyncMemoryClient:
response = await self.async_client.request("DELETE", "/v1/batch/", json={"memories": memories})
response.raise_for_status()
capture_client_event("async_client.batch_delete", self.sync_client)
capture_client_event("client.batch_delete", self.sync_client, {"sync_type": "async"})
return response.json()
@api_error_handler
@@ -916,7 +923,7 @@ class AsyncMemoryClient:
response = await self.async_client.post("/v1/exports/", json={"schema": schema, **self._prepare_params(kwargs)})
response.raise_for_status()
capture_client_event(
"async_client.create_memory_export", self.sync_client, {"schema": schema, "keys": list(kwargs.keys())}
"client.create_memory_export", self.sync_client, {"schema": schema, "keys": list(kwargs.keys()), "sync_type": "async"}
)
return response.json()
@@ -930,9 +937,9 @@ 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())})
capture_client_event("client.get_memory_export", self.sync_client, {"keys": list(kwargs.keys()), "sync_type": "async"})
return response.json()
@api_error_handler
@@ -946,23 +953,24 @@ class AsyncMemoryClient:
params=params,
)
response.raise_for_status()
capture_client_event("async_client.get_project", self.sync_client, {"fields": fields})
capture_client_event("client.get_project", self.sync_client, {"fields": fields, "sync_type": "async"})
return response.json()
@api_error_handler
async def update_project(
self, custom_instructions: Optional[str] = None, custom_categories: Optional[List[str]] = None
self, custom_instructions: Optional[str] = None, custom_categories: Optional[List[str]] = None,
retrieval_criteria: Optional[List[Dict[str, Any]]] = None
) -> Dict[str, Any]:
if not (self.sync_client.org_id and self.sync_client.project_id):
raise ValueError("org_id and project_id must be set to update instructions or categories")
if custom_instructions is None and custom_categories is None:
if custom_instructions is None and custom_categories is None and retrieval_criteria is None:
raise ValueError(
"Currently we only support updating custom_instructions or custom_categories, so you must provide at least one of them"
"Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
)
payload = self.sync_client._prepare_params(
{"custom_instructions": custom_instructions, "custom_categories": custom_categories}
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria}
)
response = await self.async_client.patch(
f"/api/v1/orgs/organizations/{self.sync_client.org_id}/projects/{self.sync_client.project_id}/",
@@ -970,9 +978,9 @@ class AsyncMemoryClient:
)
response.raise_for_status()
capture_client_event(
"async_client.update_project",
"client.update_project",
self.sync_client,
{"custom_instructions": custom_instructions, "custom_categories": custom_categories},
{"custom_instructions": custom_instructions, "custom_categories": custom_categories, "retrieval_criteria": retrieval_criteria, "sync_type": "async"},
)
return response.json()
@@ -985,7 +993,7 @@ class AsyncMemoryClient:
f"api/v1/webhooks/projects/{project_id}/",
)
response.raise_for_status()
capture_client_event("async_client.get_webhook", self.sync_client)
capture_client_event("client.get_webhook", self.sync_client, {"sync_type": "async"})
return response.json()
@api_error_handler
@@ -993,7 +1001,7 @@ class AsyncMemoryClient:
payload = {"url": url, "name": name, "event_types": event_types}
response = await self.async_client.post(f"api/v1/webhooks/projects/{project_id}/", json=payload)
response.raise_for_status()
capture_client_event("async_client.create_webhook", self.sync_client)
capture_client_event("client.create_webhook", self.sync_client, {"sync_type": "async"})
return response.json()
@api_error_handler
@@ -1007,14 +1015,14 @@ class AsyncMemoryClient:
payload = {k: v for k, v in {"name": name, "url": url, "event_types": event_types}.items() if v is not None}
response = await self.async_client.put(f"api/v1/webhooks/{webhook_id}/", json=payload)
response.raise_for_status()
capture_client_event("async_client.update_webhook", self.sync_client, {"webhook_id": webhook_id})
capture_client_event("client.update_webhook", self.sync_client, {"webhook_id": webhook_id, "sync_type": "async"})
return response.json()
@api_error_handler
async def delete_webhook(self, webhook_id: int) -> Dict[str, str]:
response = await self.async_client.delete(f"api/v1/webhooks/{webhook_id}/")
response.raise_for_status()
capture_client_event("async_client.delete_webhook", self.sync_client, {"webhook_id": webhook_id})
capture_client_event("client.delete_webhook", self.sync_client, {"webhook_id": webhook_id, "sync_type": "async"})
return response.json()
@api_error_handler
@@ -1031,5 +1039,5 @@ class AsyncMemoryClient:
response = await self.async_client.post("/v1/feedback/", json=data)
response.raise_for_status()
capture_client_event("async_client.feedback", self.sync_client, data)
capture_client_event("client.feedback", self.sync_client, data, {"sync_type": "async"})
return response.json()
+4 -1
View File
@@ -6,9 +6,12 @@ from pydantic import BaseModel, Field
from mem0.embeddings.configs import EmbedderConfig
from mem0.graphs.configs import GraphStoreConfig
from mem0.llms.configs import LlmConfig
from mem0.memory.setup import mem0_dir
from mem0.vector_stores.configs import VectorStoreConfig
# Set up the directory path
home_dir = os.path.expanduser("~")
mem0_dir = os.environ.get("MEM0_DIR") or os.path.join(home_dir, ".mem0")
class MemoryItem(BaseModel):
id: str = Field(..., description="The unique identifier for the text data")
@@ -7,7 +7,7 @@ class AzureAISearchConfig(BaseModel):
collection_name: str = Field("mem0", description="Name of the collection")
service_name: str = Field(None, description="Azure AI Search service name")
api_key: str = Field(None, description="API key for the Azure AI Search service")
embedding_model_dims: int = Field(None, description="Dimension of the embedding vector")
embedding_model_dims: int = Field(1536, description="Dimension of the embedding vector")
compression_type: Optional[str] = Field(
None, description="Type of vector compression to use. Options: 'scalar', 'binary', or None"
)
+1
View File
@@ -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
+32
View File
@@ -0,0 +1,32 @@
from typing import Any, ClassVar, Dict
from pydantic import BaseModel, Field, model_validator
class LangchainConfig(BaseModel):
try:
from langchain_community.vectorstores import VectorStore
except ImportError:
raise ImportError(
"The 'langchain_community' library is required. Please install it using 'pip install langchain_community'."
)
VectorStore: ClassVar[type] = VectorStore
client: VectorStore = Field(description="Existing VectorStore instance")
collection_name: str = Field("mem0", description="Name of the collection to use")
@model_validator(mode="before")
@classmethod
def validate_extra_fields(cls, values: Dict[str, Any]) -> Dict[str, Any]:
allowed_fields = set(cls.model_fields.keys())
input_fields = set(values.keys())
extra_fields = input_fields - allowed_fields
if extra_fields:
raise ValueError(
f"Extra fields not allowed: {', '.join(extra_fields)}. Please input only the following fields: {', '.join(allowed_fields)}"
)
return values
model_config = {
"arbitrary_types_allowed": True,
}
@@ -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,
}
+5
View File
@@ -1,5 +1,10 @@
import logging
from typing import Literal, Optional
logging.getLogger("transformers").setLevel(logging.WARNING)
logging.getLogger("sentence_transformers").setLevel(logging.WARNING)
logging.getLogger("huggingface_hub").setLevel(logging.WARNING)
from sentence_transformers import SentenceTransformer
from mem0.configs.embeddings.base import BaseEmbedderConfig
+11
View File
@@ -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]
+9 -4
View File
@@ -34,7 +34,9 @@ class MemoryGraph:
self.config.graph_store.config.username,
self.config.graph_store.config.password,
)
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.llm_provider = "openai_structured"
if self.config.llm.provider:
@@ -164,15 +166,18 @@ class MemoryGraph:
entity_type_map = {}
try:
for item in search_results["tool_calls"][0]["arguments"]["entities"]:
entity_type_map[item["entity"]] = item["entity_type"]
for tool_call in search_results["tool_calls"]:
if tool_call["name"] != "extract_entities":
continue
for item in tool_call["arguments"]["entities"]:
entity_type_map[item["entity"]] = item["entity_type"]
except Exception as e:
logger.exception(
f"Error in search tool: {e}, llm_provider={self.llm_provider}, search_results={search_results}"
)
entity_type_map = {k.lower().replace(" ", "_"): v.lower().replace(" ", "_") for k, v in entity_type_map.items()}
logger.debug(f"Entity type map: {entity_type_map}")
logger.debug(f"Entity type map: {entity_type_map}\n search_results={search_results}")
return entity_type_map
def _establish_nodes_relations_from_data(self, data, filters, entity_type_map):
+865 -37
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -3,6 +3,7 @@ import os
import uuid
# Set up the directory path
VECTOR_ID = str(uuid.uuid4())
home_dir = os.path.expanduser("~")
mem0_dir = os.environ.get("MEM0_DIR") or os.path.join(home_dir, ".mem0")
os.makedirs(mem0_dir, exist_ok=True)
@@ -29,3 +30,27 @@ def get_user_id():
return user_id
except Exception:
return "anonymous_user"
def get_or_create_user_id(vector_store):
"""Store user_id in vector store and return it."""
user_id = get_user_id()
# Try to get existing user_id from vector store
try:
existing = vector_store.get(vector_id=VECTOR_ID)
if existing and hasattr(existing, "payload") and existing.payload and "user_id" in existing.payload:
return existing.payload["user_id"]
except:
pass
# If we get here, we need to insert the user_id
try:
dims = getattr(vector_store, "embedding_model_dims", 1536)
vector_store.insert(
vectors=[[0.0] * dims], payloads=[{"user_id": user_id, "type": "user_identity"}], ids=[VECTOR_ID]
)
except:
pass
return user_id
-6
View File
@@ -142,9 +142,3 @@ class SQLiteManager:
}
for row in rows
]
def reset(self):
with self._lock:
with self.connection:
self.connection.execute("DROP TABLE IF EXISTS history")
self._create_history_table()
+17 -13
View File
@@ -6,9 +6,11 @@ import sys
from posthog import Posthog
import mem0
from mem0.memory.setup import get_user_id, setup_config
from mem0.memory.setup import get_or_create_user_id
MEM0_TELEMETRY = os.environ.get("MEM0_TELEMETRY", "True")
PROJECT_API_KEY="phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
HOST="https://us.i.posthog.com"
if isinstance(MEM0_TELEMETRY, str):
MEM0_TELEMETRY = MEM0_TELEMETRY.lower() in ("true", "1", "yes")
@@ -21,11 +23,11 @@ logging.getLogger("urllib3").setLevel(logging.CRITICAL + 1)
class AnonymousTelemetry:
def __init__(self, project_api_key, host):
self.posthog = Posthog(project_api_key=project_api_key, host=host)
# Call setup config to ensure that the user_id is generated
setup_config()
self.user_id = get_user_id()
def __init__(self, vector_store=None):
self.posthog = Posthog(project_api_key=PROJECT_API_KEY, host=HOST)
self.user_id = get_or_create_user_id(vector_store)
if not MEM0_TELEMETRY:
self.posthog.disabled = True
@@ -50,14 +52,16 @@ class AnonymousTelemetry:
self.posthog.shutdown()
# Initialize AnonymousTelemetry
telemetry = AnonymousTelemetry(
project_api_key="phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX",
host="https://us.i.posthog.com",
)
client_telemetry = AnonymousTelemetry()
def capture_event(event_name, memory_instance, additional_data=None):
oss_telemetry = AnonymousTelemetry(
vector_store=memory_instance._telemetry_vector_store
if hasattr(memory_instance, "_telemetry_vector_store")
else None,
)
event_data = {
"collection": memory_instance.collection_name,
"vector_size": memory_instance.embedding_model.config.embedding_dims,
@@ -73,7 +77,7 @@ def capture_event(event_name, memory_instance, additional_data=None):
if additional_data:
event_data.update(additional_data)
telemetry.capture_event(event_name, event_data)
oss_telemetry.capture_event(event_name, event_data)
def capture_client_event(event_name, instance, additional_data=None):
@@ -83,4 +87,4 @@ def capture_client_event(event_name, instance, additional_data=None):
if additional_data:
event_data.update(additional_data)
telemetry.capture_event(event_name, event_data, instance.user_email)
client_telemetry.capture_event(event_name, event_data, instance.user_email)
+7 -1
View File
@@ -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",
@@ -79,6 +84,7 @@ class VectorStoreFactory:
"supabase": "mem0.vector_stores.supabase.Supabase",
"weaviate": "mem0.vector_stores.weaviate.Weaviate",
"faiss": "mem0.vector_stores.faiss.FAISS",
"langchain": "mem0.vector_stores.langchain.Langchain",
}
@classmethod
+3 -1
View File
@@ -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",
@@ -24,6 +25,7 @@ class VectorStoreConfig(BaseModel):
"supabase": "SupabaseConfig",
"weaviate": "WeaviateConfig",
"faiss": "FAISSConfig",
"langchain": "LangchainConfig",
}
@model_validator(mode="after")
+2 -2
View File
@@ -40,7 +40,7 @@ class ElasticsearchDB(VectorStoreBase):
)
self.collection_name = config.collection_name
self.vector_dim = config.embedding_model_dims
self.embedding_model_dims = config.embedding_model_dims
# Create index only if auto_create_index is True
if config.auto_create_index:
@@ -58,7 +58,7 @@ class ElasticsearchDB(VectorStoreBase):
"mappings": {
"properties": {
"text": {"type": "text"},
"vector": {"type": "dense_vector", "dims": self.vector_dim, "index": True, "similarity": "cosine"},
"vector": {"type": "dense_vector", "dims": self.embedding_model_dims, "index": True, "similarity": "cosine"},
"metadata": {"type": "object", "properties": {"user_id": {"type": "keyword"}}},
}
},
+8 -4
View File
@@ -9,6 +9,9 @@ import numpy as np
from pydantic import BaseModel
try:
logging.getLogger("faiss").setLevel(logging.WARNING)
logging.getLogger("faiss.loader").setLevel(logging.WARNING)
import faiss
except ImportError:
raise ImportError(
@@ -35,6 +38,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 +55,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 +150,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 +166,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
+151
View File
@@ -0,0 +1,151 @@
from typing import Dict, List, Optional
from pydantic import BaseModel
try:
from langchain_community.vectorstores import VectorStore
except ImportError:
raise ImportError(
"The 'langchain_community' library is required. Please install it using 'pip install langchain_community'."
)
from mem0.vector_stores.base import VectorStoreBase
class OutputData(BaseModel):
id: Optional[str] # memory id
score: Optional[float] # distance
payload: Optional[Dict] # metadata
class Langchain(VectorStoreBase):
def __init__(self, client: VectorStore, collection_name: str = "mem0"):
self.client = client
self.collection_name = collection_name
def _parse_output(self, data: Dict) -> List[OutputData]:
"""
Parse the output data.
Args:
data (Dict): Output data or list of Document objects.
Returns:
List[OutputData]: Parsed output data.
"""
# Check if input is a list of Document objects
if isinstance(data, list) and all(hasattr(doc, "metadata") for doc in data if hasattr(doc, "__dict__")):
result = []
for doc in data:
entry = OutputData(
id=getattr(doc, "id", None),
score=None, # Document objects typically don't include scores
payload=getattr(doc, "metadata", {}),
)
result.append(entry)
return result
# Original format handling
keys = ["ids", "distances", "metadatas"]
values = []
for key in keys:
value = data.get(key, [])
if isinstance(value, list) and value and isinstance(value[0], list):
value = value[0]
values.append(value)
ids, distances, metadatas = values
max_length = max(len(v) for v in values if isinstance(v, list) and v is not None)
result = []
for i in range(max_length):
entry = OutputData(
id=ids[i] if isinstance(ids, list) and ids and i < len(ids) else None,
score=(distances[i] if isinstance(distances, list) and distances and i < len(distances) else None),
payload=(metadatas[i] if isinstance(metadatas, list) and metadatas and i < len(metadatas) else None),
)
result.append(entry)
return result
def create_col(self, name, vector_size=None, distance=None):
self.collection_name = name
return self.client
def insert(
self, vectors: List[List[float]], payloads: Optional[List[Dict]] = None, ids: Optional[List[str]] = None
):
"""
Insert vectors into the LangChain vectorstore.
"""
# Check if client has add_embeddings method
if hasattr(self.client, "add_embeddings"):
# Some LangChain vectorstores have a direct add_embeddings method
self.client.add_embeddings(embeddings=vectors, metadatas=payloads, ids=ids)
else:
# Fallback to add_texts method
texts = [payload.get("data", "") for payload in payloads] if payloads else [""] * len(vectors)
self.client.add_texts(texts=texts, metadatas=payloads, ids=ids)
def search(self, query: str, vectors: List[List[float]], limit: int = 5, filters: Optional[Dict] = None):
"""
Search for similar vectors in LangChain.
"""
# For each vector, perform a similarity search
if filters:
results = self.client.similarity_search_by_vector(embedding=vectors, k=limit, filter=filters)
else:
results = self.client.similarity_search_by_vector(embedding=vectors, k=limit)
final_results = self._parse_output(results)
return final_results
def delete(self, vector_id):
"""
Delete a vector by ID.
"""
self.client.delete(ids=[vector_id])
def update(self, vector_id, vector=None, payload=None):
"""
Update a vector and its payload.
"""
self.delete(vector_id)
self.insert(vector, payload, [vector_id])
def get(self, vector_id):
"""
Retrieve a vector by ID.
"""
docs = self.client.get_by_ids([vector_id])
if docs and len(docs) > 0:
doc = docs[0]
return self._parse_output([doc])[0]
return None
def list_cols(self):
"""
List all collections.
"""
# LangChain doesn't have collections
return [self.collection_name]
def delete_col(self):
"""
Delete a collection.
"""
self.client.delete(ids=None)
def col_info(self):
"""
Get information about a collection.
"""
return {"name": self.collection_name}
def list(self, filters=None, limit=None):
"""
List all vectors in a collection.
"""
# This would require implementation-specific access to the underlying store
raise NotImplementedError("Listing all vectors not directly supported by LangChain vectorstores")
+2 -2
View File
@@ -37,7 +37,7 @@ class OpenSearchDB(VectorStoreBase):
)
self.collection_name = config.collection_name
self.vector_dim = config.embedding_model_dims
self.embedding_model_dims = config.embedding_model_dims
# Create index only if auto_create_index is True
if config.auto_create_index:
@@ -54,7 +54,7 @@ class OpenSearchDB(VectorStoreBase):
"text": {"type": "text"},
"vector": {
"type": "knn_vector",
"dimension": self.vector_dim,
"dimension": self.embedding_model_dims,
"method": {"engine": "lucene", "name": "hnsw", "space_type": "cosinesimil"},
},
"metadata": {"type": "object", "properties": {"user_id": {"type": "keyword"}}},
+1
View File
@@ -51,6 +51,7 @@ class PGVector(VectorStoreBase):
self.collection_name = collection_name
self.use_diskann = diskann
self.use_hnsw = hnsw
self.embedding_model_dims = embedding_model_dims
self.conn = psycopg2.connect(dbname=dbname, user=user, password=password, host=host, port=port)
self.cur = self.conn.cursor()

Some files were not shown because too many files have changed in this diff Show More