Memora is an agent that aims to replicate the human memory for AI assistants / agents.
Project description
When we interact with people 🗣️👂, we naturally remember details from past interactions 💭, feelings 😜😢, and shared experiences 🤝. That's what makes us human. We're bringing this same ability to AI, helping it recall just like us.
Give the github repo a starhug ⭐️—it’s feeling a lil’ lonely 🥺
Key Features
- Temporal Memory Recall: Enables AI to remember timestamped memories from past interactions.
- Multi-Tenancy: Accommodates multiple organizations, agents, and users.
- Flexible Name Handling: Uses placeholders for easy updates to user and agent names.
- Scalability: Designed to handle millions of users, interactions, and memories.
- Developer-Friendly: Modular architecture for easy customization and feature integration.
Quick Start
Before using Memora, you'll need to set up the following:
-
Neo4j Database
- Option A: Install Neo4j locally (Free)
- Option B: Use Neo4j AuraDB Cloud (Free Option available)
-
Qdrant Vector Database
- Option A: Install Qdrant locally (Free)
- Option B: Use Qdrant Cloud (Free Option available)
-
LLM Providers
Choose one of the following providers and obtain an API key:- OpenAI
- Azure OpenAI
- Together AI
- Groq
- Or Integrate Your Own LLM Provider. (See Docs: Custom LLM Backend)
-
Optional: Rust/Cargo Setup
Theneo4j-rust-extpackage may require Rust/Cargo for building from source if pre-built wheels are unavailable or fail to install on your system.- For Unix-like systems:
Run the following command in your terminal:curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - For other platforms or installation methods:
Refer to the Rust installation guide.
Note: This setup is necessary if you encounter build errors related to the
neo4j-rust-extorpy-rust-stemmerspackage. - For Unix-like systems:
Installation
Install Memora using pip:
pip install memora-core
Basic Setup
Here's how to initialize Memora with the databases and an LLM provider:
from memora import Memora
from qdrant_client import AsyncQdrantClient
from memora.vector_db import QdrantDB
from memora.graph_db import Neo4jGraphInterface
from memora.llm_backends import GroqBackendLLM
# Initialize databases
vector_db = QdrantDB(async_client=AsyncQdrantClient(url="QDRANT_URL", api_key="QDRANT_API_KEY"))
graph_db = Neo4jGraphInterface(uri="Neo4jURI", username="Username", password="Password", database="DBName")
# Only call setup on the very first run - (creates necessary indexes, contraints, etc.)
await vector_db.setup()
await graph_db.setup()
# Initialize Memora with Groq as the LLM provider
memora = Memora(
vector_db=vector_db,
graph_db=graph_db,
# Fast model for memory search queries / filtering.
memory_search_model=GroqBackendLLM(api_key="GROQ_API_KEY", model="mixtral-8x7b-32768"),
# Powerful model for memory extraction
extraction_model=GroqBackendLLM(api_key="GROQ_API_KEY", model="llama-3.3-70b-versatile", max_tokens=8000)
)
Creating An Organization, User, and Agent
# Create an organization
org = await memora.graph.create_organization("My Organization")
org_id = org.org_id # Short UUID e.g 'gmDr4sUiWMCqbGAiV8jjbU'
# Create a user in the organization
user = await memora.graph.create_user(org_id, "Alice")
user_id = user.user_id # Short UUID e.g '89sSRXoz53gdWPJoTEEass'
# Create an agent belonging to the organization and user (optionally).
agent = await memora.graph.create_agent(org_id, "Jenny, Personal AI", user_id=user_id)
agent_id = agent.agent_id # Short UUID e.g 'CcyKXxhi2skEcDpRzNZim7'
Core Operations
1. Saving an Interaction and its Memories
# Example interaction
interaction = [
{"role": "user", "content": "I had another peanut incident today; it confirms I am allergic."},
{"role": "assistant", "content": "Oh Jake, I'm sorry to hear that. Are you okay? I'll make sure to keep peanuts 🥜 away from any future food-related situations."},
{"role": "user", "content": "Yes, I'm okay, though it stressed out Sarah, my wife, which isn't cool because she is due in December."},
{"role": "assistant", "content": "I'm glad you're fine now, and that's wonderful news about Sarah's pregnancy! Congratulations to you both. When in December is she due?"},
{"role": "user", "content": "December 15th."},
{"role": "assistant", "content": "Congrats again, and feel free to bombard me with every baby question you have 👶🍼, we're raising this baby together LOL 😉"}
]
# Save the interaction and its memories
interaction_id, created_at_datetime = await memora.save_or_update_interaction_and_memories(
org_id=org_id,
user_id=user_id,
agent_id=agent_id,
interaction=interaction,
current_datetime=datetime.now()
)
2. Updating An Existing Interaction and its Memories
The updated interaction will be compared with the existing one:
- If differences are found, truncates existing record from that point and replaces with updated version. Old memories from truncated message(s) remain but are not linked to the truncated message(s).
- If no differences, appends new messages from the update.
updated_interaction = interaction + [
{"role": "user", "content": "Thanks! We're pretty sure it's a girl, but we'll know for certain at the next ultrasound."},
{"role": "assistant", "content": "The anticipation must be building. Do you have any name ideas?."}
]
# Update the existing interaction (In this case it simply appends the new messages)
interaction_id, updated_at_datetime = await memora.save_or_update_interaction_and_memories(
org_id=org_id,
user_id=user_id,
agent_id=agent_id,
interaction=updated_interaction,
interaction_id=interaction_id, # Pass the existing interaction_id to update
current_datetime=datetime.now()
)
3. Searching Memories
from memora.vector_db.base import MemorySearchScope
# Returns a consolidated list of results for all queries (sorted by relevance), instead of individual lists per query.
memories = await memora.search_memories_as_one(
org_id=org_id,
user_id=user_id,
search_queries=["Who is my wife?", "When is my wife due?"],
search_across_agents=True
)
# memories: [
# Memory(..., memory_id='uuid string', memory="Jake married Sarah on August 12th, 2023", obtained_at=datetime(...), message_sources=[...]),
# Memory(..., memory_id='uuid string', memory="Jake's wife Sarah is due on December 15th", obtained_at=datetime(...), message_sources=[...])
# ...]
# Perform a batch search for memories, obtaining a list of results for each query, and can specify the search scope
batch_memories = await memora.search_memories_as_batch(
org_id=org_id,
search_queries=["user's allergies", "user's family details"],
user_id=user_id,
memory_search_scope=MemorySearchScope.USER, # Can be "user" or "organization"
search_across_agents=True
)
# batch_memories: [
# [Memory(..., memory_id='uuid string', memory="Jake has confirmed he is allergic to peanuts", obtained_at=datetime(...), message_sources=[...]), ...],
# [Memory(..., memory_id='uuid string', memory="Jake's wife Sarah is due on December 15th", obtained_at=datetime(...), message_sources=[...]), ...]
#]
4. Recall Memories for a User's Message in Interaction
recalled_memories, just_memory_ids = await memora.recall_memories_for_message(
org_id,
user_id,
latest_msg="Sarah is really in pain more nowdays, so both of us can't sleep.",
# Optional: Add previous messages in the interaction for context.
preceding_msg_for_context=[],
# Optional: Exclude previously recalled memories (e.g They are already in the conversation). See sample personal assistant below.
filter_out_memory_ids_set={'4b9df118-fa11-4e29-abfd-3b02587aa251'}
)
# recalled_memories: [
# Memory(..., memory_id='uuid string', memory="Jake's wife Sarah is due on December 15th", obtained_at=datetime(...), message_sources=[...]),
# Memory(..., memory_id='uuid string', memory="Jake and Sarah are pretty confident the baby’s a girl but will confirm at the next ultrasound.", obtained_at=datetime(...), message_sources=[...]),
# ...]
# just_memory_ids: ["uuid string", "uuid string", ...]
5. Managing Memories
# Get all memories for a user
all_memories = await memora.graph.get_all_user_memories(org_id, user_id)
# Get memories from a specific interaction
interaction_memories = await memora.graph.get_interaction(org_id, user_id, interaction_id, with_memories=True, with_messages=False)
# Get the history of a specific memory, this contains all updates of a memory in descending order (starting with the latest version to the oldest)
history = await memora.graph.get_user_memory_history(org_id, user_id, "memory_uuid")
# Delete a specific memory
await memora.graph.delete_user_memory(org_id, user_id, "memory_uuid")
... # See documentation for more methods.
A Simple Example
from openai import AsyncOpenAI
... # Preceding code where you have initialized memora, and stored org_id and user_id in variables.
# Async client initialization
client = AsyncOpenAI(api_key="YourOpenAIAPIKey")
messages=[{ "role": "system", "content": "You are jake's assistant, given memories in 'memory recall: ...'"}]
user_message = "Hello, what is my wife's name ?"
recalled_memories, _ = await memora.recall_memories_for_message(org_id, user_id, latest_msg=user_message)
include_memory_in_message = """
memory recall: {memories}\n---\nmessage: {message}
""".format(memories=str([memory.memory_and_timestamp_dict() for memory in recalled_memories]), message=user_message)
messages.append({'role': 'user', 'content': include_memory_in_message})
response = await client.chat.completions.create(model="gpt-4o", messages=messages)
print(f">>> Assistant Reply: {response.choices[0].message.content}")
Contributing
We welcome contributions! Please see our Contributing Guidelines for more information on how to get involved.
License
Memora is released under the Apache License 2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file memora_core-0.3.1.tar.gz.
File metadata
- Download URL: memora_core-0.3.1.tar.gz
- Upload date:
- Size: 48.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.5 CPython/3.10.10 Linux/5.15.0-1077-aws
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e9d8e142b16aba14d8fc943927495d93d5c82313e58b1626aee9267df369f42
|
|
| MD5 |
fcbb4ecdd7587b803e4b11cc426d7c5b
|
|
| BLAKE2b-256 |
869e393238f79fd74753ce58ada455c0ba86d87ebfc8d4cb95d83b0b854cacce
|
File details
Details for the file memora_core-0.3.1-py3-none-any.whl.
File metadata
- Download URL: memora_core-0.3.1-py3-none-any.whl
- Upload date:
- Size: 58.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.5 CPython/3.10.10 Linux/5.15.0-1077-aws
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f18a39c821a9131bda9be0a5c20cb2f7007287b016bc81487403088b3c856ee
|
|
| MD5 |
cc4c097099e1862b91a823f3a811e5d4
|
|
| BLAKE2b-256 |
7deeff1c65e015e542ec7c0fae82f02dabe702b3f0bd43218b5396f5f13c9fa2
|