Skip to content

Commit 4f45016

Browse files
authored
Merge pull request #192 from helloissariel/main
Mem0
2 parents 1db2556 + 393ded5 commit 4f45016

File tree

10 files changed

+632
-34
lines changed

10 files changed

+632
-34
lines changed

examples/mem0_agent_demo.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""Mem0 long-term memory demo: The Intelligent Web3 Portfolio Assistant"""
2+
3+
import asyncio
4+
import os
5+
from typing import List
6+
7+
from spoon_ai.chat import ChatBot
8+
9+
USER_ID = "crypto_whale_001"
10+
SYSTEM_PROMPT = (
11+
"You are the Intelligent Web3 Portfolio Assistant. "
12+
"Remember user risk appetite, preferred chains, and asset types. "
13+
"Recommend actionable strategies without re-asking for already stored preferences."
14+
)
15+
16+
17+
def new_llm(mem0_config: dict) -> ChatBot:
18+
"""Create a new ChatBot configured for long-term memory with Mem0."""
19+
return ChatBot(
20+
llm_provider="openrouter",
21+
model_name="openai/gpt-5.1",
22+
enable_long_term_memory=True,
23+
mem0_config=mem0_config,
24+
)
25+
26+
27+
def print_memories(memories: List[str], label: str) -> None:
28+
print(f"[Mem0] {label}:")
29+
for m in memories:
30+
print(f" - {m}")
31+
32+
33+
async def main() -> None:
34+
mem0_config = {
35+
"user_id": USER_ID,
36+
"metadata": {"project": "web3-portfolio-assistant"},
37+
"async_mode": False, # synchronous writes so retrieval in the next turn works immediately
38+
}
39+
40+
# Step 1: Introduction / preference capture
41+
print(" Session 1: Capturing preferences")
42+
llm = new_llm(mem0_config)
43+
first_reply = await llm.ask(
44+
[{"role": "user", "content": (
45+
"I am a high-risk degen trader. I exclusively trade meme coins on the Solana blockchain. "
46+
"I hate Ethereum gas fees.")}],
47+
system_msg=SYSTEM_PROMPT,
48+
)
49+
print("First reply:", first_reply)
50+
51+
memories = llm.mem0_client.search_memory("Solana meme coins high risk")
52+
print_memories(memories, "After Session 1")
53+
54+
# Step 2: Recall after re-initialization
55+
print(" Session 2: Recall with a brand new agent instance")
56+
llm_reloaded = new_llm(mem0_config)
57+
second_reply = await llm_reloaded.ask(
58+
[{"role": "user", "content": "Recommend a trading strategy for me today."}],
59+
system_msg=SYSTEM_PROMPT,
60+
)
61+
print("Second reply:", second_reply)
62+
63+
memories = llm_reloaded.mem0_client.search_memory("trading strategy solana meme")
64+
print_memories(memories, "Retrieved for Session 2")
65+
66+
67+
# Step 3: Update preferences and verify recency/relevance
68+
print(" Session 3: Updating preferences to safer Arbitrum yield")
69+
third_reply = await llm_reloaded.ask(
70+
[{"role": "user", "content": "I lost too much money. I want to pivot to safe stablecoin yield farming on Arbitrum now."}],
71+
system_msg=SYSTEM_PROMPT,
72+
)
73+
print("Third reply:", third_reply)
74+
fourth_reply = await llm_reloaded.ask(
75+
[{"role": "user", "content": "What chain should I use?"}],
76+
system_msg=SYSTEM_PROMPT,
77+
)
78+
print("Fourth reply:", fourth_reply)
79+
memories = llm_reloaded.mem0_client.search_memory("stablecoin yield chain choice")
80+
print_memories(memories, "Retrieved after update (Session 3)")
81+
82+
if __name__ == "__main__":
83+
asyncio.run(main())

examples/mem0_tool_agent.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""
2+
Mem0 toolkit demo using SpoonReactAI.
3+
This demo requires spoon-toolkit to be installed
4+
"""
5+
6+
import asyncio
7+
from typing import Any, Dict, List
8+
9+
from pydantic import Field
10+
11+
from spoon_ai.agents.spoon_react import SpoonReactAI
12+
from spoon_ai.chat import ChatBot
13+
from spoon_ai.tools.tool_manager import ToolManager
14+
from spoon_ai.tools.base import ToolResult
15+
from spoon_toolkits.memory import AddMemoryTool, SearchMemoryTool, GetAllMemoryTool
16+
17+
18+
USER_ID = "defi_user_002"
19+
20+
21+
class DeFiMemoryAgent(SpoonReactAI):
22+
"""Brain from spoon-core + memory tools from spoon-toolkit."""
23+
24+
mem0_config: Dict[str, Any] = Field(default_factory=dict)
25+
available_tools: ToolManager = Field(default_factory=lambda: ToolManager([]))
26+
27+
def model_post_init(self, __context: Any = None) -> None:
28+
super().model_post_init(__context)
29+
# Rebuild tools with the injected mem0_config for this agent
30+
memory_tools = [
31+
AddMemoryTool(mem0_config=self.mem0_config),
32+
SearchMemoryTool(mem0_config=self.mem0_config),
33+
GetAllMemoryTool(mem0_config=self.mem0_config),
34+
]
35+
self.available_tools = ToolManager(memory_tools)
36+
# Refresh prompts so SpoonReactAI lists the newly provided tools
37+
if hasattr(self, "_refresh_prompts"):
38+
self._refresh_prompts()
39+
40+
41+
def build_agent(mem0_cfg: Dict[str, Any]) -> DeFiMemoryAgent:
42+
return DeFiMemoryAgent(
43+
llm=ChatBot(
44+
llm_provider="openrouter",
45+
base_url="https://openrouter.ai/api/v1",
46+
model_name="anthropic/claude-3.5-sonnet",
47+
enable_long_term_memory=False, # memory comes from toolkit tools instead
48+
),
49+
mem0_config=mem0_cfg,
50+
system_prompt=(
51+
"You are a DeFi investment advisor. Use the provided Mem0 tools to recall "
52+
"and update user preferences before answering."
53+
),
54+
)
55+
56+
57+
def print_memories(result: ToolResult, label: str) -> None:
58+
if not isinstance(result, ToolResult):
59+
print(f"[Mem0] {label}: error -> {result}")
60+
return
61+
memories: List[str] = result.output.get("memories", []) if result and result.output else []
62+
print(f"[Mem0] {label}:")
63+
for m in memories:
64+
print(f" - {m}")
65+
66+
67+
async def phase_capture(agent: DeFiMemoryAgent) -> None:
68+
print("\n=== Phase 1: Capture high-risk Solana preferences ===")
69+
await agent.available_tools.execute(
70+
name="add_memory",
71+
tool_input={
72+
"messages": [
73+
{
74+
"role": "user",
75+
"content": (
76+
"I am a high-risk degen trader. I exclusively trade meme coins on Solana "
77+
"and dislike Ethereum gas fees."
78+
),
79+
}
80+
]
81+
},
82+
)
83+
memories = await agent.available_tools.execute(
84+
name="search_memory",
85+
tool_input={"query": "Solana meme coins high risk"},
86+
)
87+
print_memories(memories, "After Phase 1 store")
88+
89+
90+
async def phase_recall(mem0_cfg: Dict[str, Any]) -> None:
91+
print("\n=== Phase 2: Recall with a fresh agent instance ===")
92+
agent = build_agent(mem0_cfg)
93+
memories = await agent.available_tools.execute(
94+
name="search_memory",
95+
tool_input={"query": "trading strategy solana meme"},
96+
)
97+
print_memories(memories, "Retrieved for Phase 2")
98+
99+
100+
async def phase_update(agent: DeFiMemoryAgent) -> None:
101+
print("\n=== Phase 3: Update preferences to safer Arbitrum yield ===")
102+
await agent.available_tools.execute(
103+
name="add_memory",
104+
tool_input={
105+
"messages": [
106+
{
107+
"role": "user",
108+
"content": (
109+
"I lost too much money. I want to pivot to safe stablecoin yield farming on Arbitrum now."
110+
),
111+
}
112+
]
113+
},
114+
)
115+
memories = await agent.available_tools.execute(
116+
name="search_memory",
117+
tool_input={"query": "stablecoin yield chain choice"},
118+
)
119+
print_memories(memories, "Retrieved after update (Phase 3)")
120+
121+
122+
async def main() -> None:
123+
mem0_cfg = {
124+
"user_id": USER_ID,
125+
"metadata": {"project": "defi-investment-advisor"},
126+
"async_mode": False, # synchronous writes so the next search sees new data
127+
}
128+
agent = build_agent(mem0_cfg)
129+
await phase_capture(agent)
130+
await phase_recall(mem0_cfg)
131+
await phase_update(agent)
132+
133+
134+
135+
if __name__ == "__main__":
136+
asyncio.run(main())

pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ classifiers = [
1919
]
2020
dependencies = [ # Complete dependencies from requirements.txt for out-of-the-box usage
2121
"aiohappyeyeballs>=2.4.4",
22-
"aiohttp>=3.10.5",
22+
"aiohttp>=3.13.2",
2323
"aiosignal>=1.3.2",
2424
"annotated-types>=0.7.0",
2525
"anyio>=4.8.0",
@@ -78,6 +78,11 @@ dependencies = [ # Complete dependencies from requirements.txt for out-of-the-bo
7878
"x402>=0.2.1",
7979
]
8080

81+
[project.optional-dependencies]
82+
memory = [
83+
"mem0ai>=0.0.1",
84+
]
85+
8186
[project.urls]
8287
"Homepage" = "https://github.com/XSpoonAi/spoon-core" # Project URL
8388
"Bug Tracker" = "https://github.com/XSpoonAi/spoon-core/issues" # Project issue tracker URL

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#
77
aiohappyeyeballs>=2.4.4
88
# via aiohttp
9-
aiohttp==3.10.5
9+
aiohttp>=3.13.2
1010
aiosignal>=1.3.2
1111
# via aiohttp
1212
annotated-types>=0.7.0

spoon_ai/agents/base.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class BaseAgent(BaseModel, ABC):
7474

7575
llm: ChatBot = Field(..., description="The LLM to use for the agent")
7676
memory: Memory = Field(default_factory=Memory, description="The memory to use for the agent")
77+
enable_long_term_memory: bool = Field(default=False, description="Enable Mem0-based long-term memory")
78+
mem0_config: Dict[str, Any] = Field(default_factory=dict, description="Mem0 configuration passed to the LLM client")
7779
state: AgentState = Field(default=AgentState.IDLE, description="The state of the agent")
7880

7981
max_steps: int = Field(default=10, description="The maximum number of steps the agent can take")
@@ -113,6 +115,16 @@ def __init__(self, **kwargs):
113115
# Concurrency control
114116
self._active_operations = set()
115117
self._shutdown_event = asyncio.Event()
118+
119+
# Long-term memory configuration (Mem0)
120+
self.mem0_config = self.mem0_config or {}
121+
if self.name:
122+
self.mem0_config.setdefault("agent_id", self.name)
123+
if isinstance(self.llm, ChatBot):
124+
try:
125+
self.llm.update_mem0_config(self.mem0_config, enable=self.enable_long_term_memory)
126+
except Exception as exc:
127+
logger.warning("Unable to configure Mem0 for agent %s: %s", self.name, exc)
116128

117129
# Initialize callback manager
118130
self._callback_manager = CallbackManager.from_callbacks(self.callbacks)

0 commit comments

Comments
 (0)