Integrate with Mem0 OSS
This commit is contained in:
@@ -15,16 +15,20 @@ The example:
|
|||||||
2. Uses Mem0 to store and retrieve memories from conversations
|
2. Uses Mem0 to store and retrieve memories from conversations
|
||||||
3. Creates personalized greetings based on previous interactions
|
3. Creates personalized greetings based on previous interactions
|
||||||
4. Handles multi-modal interaction through audio
|
4. Handles multi-modal interaction through audio
|
||||||
|
5. Demonstrates two approaches for memory management:
|
||||||
|
- Using Mem0 API (cloud-based memory storage)
|
||||||
|
- Using local configuration with custom LLM (self-hosted memory)
|
||||||
|
|
||||||
Example usage (run from pipecat root directory):
|
Example usage (run from pipecat root directory):
|
||||||
$ pip install "pipecat-ai[daily,openai,elevenlabs,silero,mem0]"
|
$ pip install "pipecat-ai[daily,openai,elevenlabs,silero,mem0]"
|
||||||
$ python examples/foundational/35-mem0.py
|
$ python examples/foundational/37-mem0.py
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- OpenAI API key (for GPT-4o-mini)
|
- OpenAI API key (for GPT-4o-mini)
|
||||||
- ElevenLabs API key (for text-to-speech)
|
- ElevenLabs API key (for text-to-speech)
|
||||||
- Daily API key (for video/audio transport)
|
- Daily API key (for video/audio transport)
|
||||||
- Mem0 API key (for memory storage and retrieval)
|
- Mem0 API key (for cloud-based memory storage)
|
||||||
|
- [Optional] Anthropic API key (if using Claude with local config)
|
||||||
|
|
||||||
Environment variables (set in .env or in your terminal using `export`):
|
Environment variables (set in .env or in your terminal using `export`):
|
||||||
DAILY_SAMPLE_ROOM_URL=daily_sample_room_url
|
DAILY_SAMPLE_ROOM_URL=daily_sample_room_url
|
||||||
@@ -32,16 +36,16 @@ Requirements:
|
|||||||
OPENAI_API_KEY=openai_api_key
|
OPENAI_API_KEY=openai_api_key
|
||||||
ELEVENLABS_API_KEY=elevenlabs_api_key
|
ELEVENLABS_API_KEY=elevenlabs_api_key
|
||||||
MEM0_API_KEY=mem0_api_key
|
MEM0_API_KEY=mem0_api_key
|
||||||
|
ANTHROPIC_API_KEY=anthropic_api_key (if using Claude with local config)
|
||||||
|
|
||||||
The bot runs as part of a pipeline that processes audio frames and manages the conversation flow.
|
The bot runs as part of a pipeline that processes audio frames and manages the conversation flow.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
from typing import Union
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from openai import audio
|
|
||||||
|
|
||||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
@@ -60,7 +64,7 @@ from pipecat.transports.network.webrtc_connection import SmallWebRTCConnection
|
|||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from mem0 import MemoryClient
|
from mem0 import MemoryClient, Memory
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
logger.error(f"Exception: {e}")
|
logger.error(f"Exception: {e}")
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -70,7 +74,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
|
|
||||||
async def get_initial_greeting(
|
async def get_initial_greeting(
|
||||||
memory_client: MemoryClient, user_id: str, agent_id: str, run_id: str
|
memory_client: Union[MemoryClient, Memory], user_id: str, agent_id: str, run_id: str
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Fetch all memories for the user and create a personalized greeting.
|
"""Fetch all memories for the user and create a personalized greeting.
|
||||||
|
|
||||||
@@ -78,13 +82,18 @@ async def get_initial_greeting(
|
|||||||
A personalized greeting based on user memories
|
A personalized greeting based on user memories
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Create filters based on available IDs
|
if isinstance(memory_client, Memory):
|
||||||
id_pairs = [("user_id", user_id), ("agent_id", agent_id), ("run_id", run_id)]
|
filters = {"user_id": user_id, "agent_id": agent_id, "run_id": run_id}
|
||||||
clauses = [{name: value} for name, value in id_pairs if value is not None]
|
filters = {k: v for k, v in filters.items() if v is not None}
|
||||||
filters = {"AND": clauses} if clauses else {}
|
memories = memory_client.get_all(**filters)
|
||||||
|
else:
|
||||||
|
# Create filters based on available IDs
|
||||||
|
id_pairs = [("user_id", user_id), ("agent_id", agent_id), ("run_id", run_id)]
|
||||||
|
clauses = [{name: value} for name, value in id_pairs if value is not None]
|
||||||
|
filters = {"AND": clauses} if clauses else {}
|
||||||
|
|
||||||
# Get all memories for this user
|
# Get all memories for this user
|
||||||
memories = memory_client.get_all(filters=filters, version="v2")
|
memories = memory_client.get_all(filters=filters, version="v2", output_format="v1.1")
|
||||||
|
|
||||||
if not memories or len(memories) == 0:
|
if not memories or len(memories) == 0:
|
||||||
logger.debug(f"!!! No memories found for this user. {memories}")
|
logger.debug(f"!!! No memories found for this user. {memories}")
|
||||||
@@ -96,7 +105,7 @@ async def get_initial_greeting(
|
|||||||
# Add some personalization based on memories (limit to 3 memories for brevity)
|
# Add some personalization based on memories (limit to 3 memories for brevity)
|
||||||
if len(memories) > 0:
|
if len(memories) > 0:
|
||||||
greeting += "Based on our previous conversations, I remember: "
|
greeting += "Based on our previous conversations, I remember: "
|
||||||
for i, memory in enumerate(memories[:3], 1):
|
for i, memory in enumerate(memories["results"][:3], 1):
|
||||||
memory_content = memory.get("memory", "")
|
memory_content = memory.get("memory", "")
|
||||||
# Keep memory references brief
|
# Keep memory references brief
|
||||||
if len(memory_content) > 100:
|
if len(memory_content) > 100:
|
||||||
@@ -120,7 +129,7 @@ async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespac
|
|||||||
- Daily video transport
|
- Daily video transport
|
||||||
- Speech-to-text and text-to-speech services
|
- Speech-to-text and text-to-speech services
|
||||||
- Language model integration
|
- Language model integration
|
||||||
- Mem0 memory service
|
- Mem0 memory service (using either API or local configuration)
|
||||||
- RTVI event handling
|
- RTVI event handling
|
||||||
"""
|
"""
|
||||||
# Note: You can pass the user_id as a parameter in API call
|
# Note: You can pass the user_id as a parameter in API call
|
||||||
@@ -145,12 +154,16 @@ async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespac
|
|||||||
voice_id="pNInz6obpgDQGcFmaJgB",
|
voice_id="pNInz6obpgDQGcFmaJgB",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize Mem0 memory service
|
# =====================================================================
|
||||||
|
# OPTION 1: Using Mem0 API (cloud-based approach)
|
||||||
|
# This approach uses Mem0's cloud service for memory management
|
||||||
|
# Requires: MEM0_API_KEY set in your environment
|
||||||
|
# =====================================================================
|
||||||
memory = Mem0MemoryService(
|
memory = Mem0MemoryService(
|
||||||
api_key=os.getenv("MEM0_API_KEY"),
|
api_key=os.getenv("MEM0_API_KEY"), # Your Mem0 API key
|
||||||
user_id=USER_ID, # Unique identifier for the user
|
user_id=USER_ID, # Unique identifier for the user
|
||||||
# agent_id="agent1", # Optional identifier for the agent
|
agent_id="agent1", # Optional identifier for the agent
|
||||||
# run_id="session1", # Optional identifier for the run
|
run_id="session1", # Optional identifier for the run
|
||||||
params=Mem0MemoryService.InputParams(
|
params=Mem0MemoryService.InputParams(
|
||||||
search_limit=10,
|
search_limit=10,
|
||||||
search_threshold=0.3,
|
search_threshold=0.3,
|
||||||
@@ -161,6 +174,37 @@ async def run_bot(webrtc_connection: SmallWebRTCConnection, _: argparse.Namespac
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# =====================================================================
|
||||||
|
# OPTION 2: Using Mem0 with local configuration (self-hosted approach)
|
||||||
|
# This approach uses a local LLM configuration for memory management
|
||||||
|
# Requires: Anthropic API key if using Claude model
|
||||||
|
# =====================================================================
|
||||||
|
# Uncomment the following code and comment out the previous memory initialization to use local config
|
||||||
|
|
||||||
|
# local_config = {
|
||||||
|
# "llm": {
|
||||||
|
# "provider": "anthropic",
|
||||||
|
# "config": {
|
||||||
|
# "model": "claude-3-5-sonnet-20240620",
|
||||||
|
# "api_key": os.getenv("ANTHROPIC_API_KEY"), # Make sure to set this in your .env
|
||||||
|
# }
|
||||||
|
# },
|
||||||
|
# "embedder": {
|
||||||
|
# "provider": "openai",
|
||||||
|
# "config": {
|
||||||
|
# "model": "text-embedding-3-large"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
# # Initialize Mem0 memory service with local configuration
|
||||||
|
# memory = Mem0MemoryService(
|
||||||
|
# local_config=local_config, # Use local LLM for memory processing
|
||||||
|
# user_id=USER_ID, # Unique identifier for the user
|
||||||
|
# # agent_id="agent1", # Optional identifier for the agent
|
||||||
|
# # run_id="session1", # Optional identifier for the run
|
||||||
|
# )
|
||||||
|
|
||||||
# Initialize LLM service
|
# Initialize LLM service
|
||||||
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o-mini")
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o-mini")
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ livekit = [ "livekit~=0.22.0", "livekit-api~=0.8.2", "tenacity~=9.0.0" ]
|
|||||||
lmnt = [ "websockets~=13.1" ]
|
lmnt = [ "websockets~=13.1" ]
|
||||||
local = [ "pyaudio~=0.2.14" ]
|
local = [ "pyaudio~=0.2.14" ]
|
||||||
mcp = [ "mcp[cli]~=1.6.0" ]
|
mcp = [ "mcp[cli]~=1.6.0" ]
|
||||||
mem0 = [ "mem0ai~=0.1.76" ]
|
mem0 = [ "mem0ai~=0.1.94" ]
|
||||||
mlx-whisper = [ "mlx-whisper~=0.4.2" ]
|
mlx-whisper = [ "mlx-whisper~=0.4.2" ]
|
||||||
moondream = [ "einops~=0.8.0", "timm~=1.0.13", "transformers~=4.48.0" ]
|
moondream = [ "einops~=0.8.0", "timm~=1.0.13", "transformers~=4.48.0" ]
|
||||||
nim = []
|
nim = []
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from pipecat.processors.aggregators.openai_llm_context import (
|
|||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from mem0 import MemoryClient # noqa: F401
|
from mem0 import MemoryClient, Memory # noqa: F401
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
logger.error(f"Exception: {e}")
|
logger.error(f"Exception: {e}")
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -49,7 +49,8 @@ class Mem0MemoryService(FrameProcessor):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
api_key: str,
|
api_key: str = None,
|
||||||
|
local_config: Dict[str, Any] = {},
|
||||||
user_id: str = None,
|
user_id: str = None,
|
||||||
agent_id: str = None,
|
agent_id: str = None,
|
||||||
run_id: str = None,
|
run_id: str = None,
|
||||||
@@ -58,7 +59,10 @@ class Mem0MemoryService(FrameProcessor):
|
|||||||
# Important: Call the parent class __init__ first
|
# Important: Call the parent class __init__ first
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.memory_client = MemoryClient(api_key=api_key)
|
if local_config:
|
||||||
|
self.memory_client = Memory.from_config(local_config)
|
||||||
|
else:
|
||||||
|
self.memory_client = MemoryClient(api_key=api_key)
|
||||||
# At least one of user_id, agent_id, or run_id must be provided
|
# At least one of user_id, agent_id, or run_id must be provided
|
||||||
if not any([user_id, agent_id, run_id]):
|
if not any([user_id, agent_id, run_id]):
|
||||||
raise ValueError("At least one of user_id, agent_id, or run_id must be provided")
|
raise ValueError("At least one of user_id, agent_id, or run_id must be provided")
|
||||||
@@ -91,6 +95,9 @@ class Mem0MemoryService(FrameProcessor):
|
|||||||
for id in ["user_id", "agent_id", "run_id"]:
|
for id in ["user_id", "agent_id", "run_id"]:
|
||||||
if getattr(self, id):
|
if getattr(self, id):
|
||||||
params[id] = getattr(self, id)
|
params[id] = getattr(self, id)
|
||||||
|
|
||||||
|
if isinstance(self.memory_client, Memory):
|
||||||
|
del params["output_format"]
|
||||||
# Note: You can run this in background to avoid blocking the conversation
|
# Note: You can run this in background to avoid blocking the conversation
|
||||||
self.memory_client.add(**params)
|
self.memory_client.add(**params)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -107,20 +114,32 @@ class Mem0MemoryService(FrameProcessor):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Retrieving memories for query: {query}")
|
logger.debug(f"Retrieving memories for query: {query}")
|
||||||
id_pairs = [
|
if isinstance(self.memory_client, Memory):
|
||||||
("user_id", self.user_id),
|
params = {
|
||||||
("agent_id", self.agent_id),
|
"query": query,
|
||||||
("run_id", self.run_id),
|
"user_id": self.user_id,
|
||||||
]
|
"agent_id": self.agent_id,
|
||||||
clauses = [{name: value} for name, value in id_pairs if value is not None]
|
"run_id": self.run_id,
|
||||||
filters = {"AND": clauses} if clauses else {}
|
"limit": self.search_limit
|
||||||
results = self.memory_client.search(
|
}
|
||||||
query=query,
|
params = {k: v for k, v in params.items() if v is not None}
|
||||||
filters=filters,
|
results = self.memory_client.search(**params)
|
||||||
version=self.api_version,
|
else:
|
||||||
top_k=self.search_limit,
|
id_pairs = [
|
||||||
threshold=self.search_threshold,
|
("user_id", self.user_id),
|
||||||
)
|
("agent_id", self.agent_id),
|
||||||
|
("run_id", self.run_id),
|
||||||
|
]
|
||||||
|
clauses = [{name: value} for name, value in id_pairs if value is not None]
|
||||||
|
filters = {"AND": clauses} if clauses else {}
|
||||||
|
results = self.memory_client.search(
|
||||||
|
query=query,
|
||||||
|
filters=filters,
|
||||||
|
version=self.api_version,
|
||||||
|
top_k=self.search_limit,
|
||||||
|
threshold=self.search_threshold,
|
||||||
|
output_format="v1.1",
|
||||||
|
)
|
||||||
|
|
||||||
logger.debug(f"Retrieved {len(results)} memories from Mem0")
|
logger.debug(f"Retrieved {len(results)} memories from Mem0")
|
||||||
return results
|
return results
|
||||||
@@ -147,7 +166,7 @@ class Mem0MemoryService(FrameProcessor):
|
|||||||
|
|
||||||
# Format memories as a message
|
# Format memories as a message
|
||||||
memory_text = self.system_prompt
|
memory_text = self.system_prompt
|
||||||
for i, memory in enumerate(memories, 1):
|
for i, memory in enumerate(memories["results"], 1):
|
||||||
memory_text += f"{i}. {memory.get('memory', '')}\n\n"
|
memory_text += f"{i}. {memory.get('memory', '')}\n\n"
|
||||||
|
|
||||||
# Add memories as a system message or user message based on configuration
|
# Add memories as a system message or user message based on configuration
|
||||||
|
|||||||
Reference in New Issue
Block a user