Skip to content

Commit 9ee2758

Browse files
authored
Merge pull request #193 from helloissariel/main
Add demonstrations for the delete and update tools
2 parents ce36dbf + c2f2956 commit 9ee2758

File tree

2 files changed

+154
-21
lines changed

2 files changed

+154
-21
lines changed

examples/mem0_tool_agent.py

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
"""
22
Mem0 toolkit demo using SpoonReactAI.
3-
This demo requires spoon-toolkit to be installed
3+
This demo requires spoon-toolkit to be installed (pip install spoon-toolkit or local install).
44
"""
55

66
import asyncio
7-
from typing import Any, Dict, List
7+
from typing import Dict, List, Optional
88

99
from pydantic import Field
1010

1111
from spoon_ai.agents.spoon_react import SpoonReactAI
1212
from spoon_ai.chat import ChatBot
13+
from spoon_ai.llm.manager import get_llm_manager
14+
from spoon_ai.memory.utils import extract_memories, extract_first_memory_id
1315
from spoon_ai.tools.tool_manager import ToolManager
1416
from spoon_ai.tools.base import ToolResult
15-
from spoon_toolkits.memory import AddMemoryTool, SearchMemoryTool, GetAllMemoryTool
17+
from spoon_toolkits.memory import (
18+
AddMemoryTool,
19+
SearchMemoryTool,
20+
GetAllMemoryTool,
21+
UpdateMemoryTool,
22+
DeleteMemoryTool,
23+
)
1624

1725

18-
USER_ID = "defi_user_002"
26+
USER_ID = "defi_user_005"
1927

2028

2129
class DeFiMemoryAgent(SpoonReactAI):
@@ -26,14 +34,14 @@ class DeFiMemoryAgent(SpoonReactAI):
2634

2735
def model_post_init(self, __context: Any = None) -> None:
2836
super().model_post_init(__context)
29-
# Rebuild tools with the injected mem0_config for this agent
3037
memory_tools = [
3138
AddMemoryTool(mem0_config=self.mem0_config),
3239
SearchMemoryTool(mem0_config=self.mem0_config),
3340
GetAllMemoryTool(mem0_config=self.mem0_config),
41+
UpdateMemoryTool(mem0_config=self.mem0_config),
42+
DeleteMemoryTool(mem0_config=self.mem0_config),
3443
]
3544
self.available_tools = ToolManager(memory_tools)
36-
# Refresh prompts so SpoonReactAI lists the newly provided tools
3745
if hasattr(self, "_refresh_prompts"):
3846
self._refresh_prompts()
3947

@@ -55,11 +63,10 @@ def build_agent(mem0_cfg: Dict[str, Any]) -> DeFiMemoryAgent:
5563

5664

5765
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 []
66+
memories = extract_memories(result)
6267
print(f"[Mem0] {label}:")
68+
if not memories:
69+
print(" (none)")
6370
for m in memories:
6471
print(f" - {m}")
6572

@@ -77,12 +84,26 @@ async def phase_capture(agent: DeFiMemoryAgent) -> None:
7784
"and dislike Ethereum gas fees."
7885
),
7986
}
80-
]
87+
],
88+
"user_id": USER_ID,
89+
"async_mode": False,
8190
},
8291
)
92+
# Verify storage immediately after add to avoid read-after-write surprises
93+
verified: ToolResult = ToolResult()
94+
for attempt in range(3):
95+
verified = await agent.available_tools.execute(
96+
name="get_all_memory",
97+
tool_input={"user_id": USER_ID, "limit": 5},
98+
)
99+
if extract_memories(verified):
100+
break
101+
await asyncio.sleep(0.5)
102+
print_memories(verified, "Verification after Phase 1 store")
103+
83104
memories = await agent.available_tools.execute(
84105
name="search_memory",
85-
tool_input={"query": "Solana meme coins high risk"},
106+
tool_input={"query": "Solana meme coins high risk", "user_id": USER_ID},
86107
)
87108
print_memories(memories, "After Phase 1 store")
88109

@@ -92,12 +113,12 @@ async def phase_recall(mem0_cfg: Dict[str, Any]) -> None:
92113
agent = build_agent(mem0_cfg)
93114
memories = await agent.available_tools.execute(
94115
name="search_memory",
95-
tool_input={"query": "trading strategy solana meme"},
116+
tool_input={"query": "trading strategy solana meme", "user_id": USER_ID},
96117
)
97118
print_memories(memories, "Retrieved for Phase 2")
98119

99120

100-
async def phase_update(agent: DeFiMemoryAgent) -> None:
121+
async def phase_update(agent: DeFiMemoryAgent, memory_id: Optional[str]) -> None:
101122
print("\n=== Phase 3: Update preferences to safer Arbitrum yield ===")
102123
await agent.available_tools.execute(
103124
name="add_memory",
@@ -109,27 +130,63 @@ async def phase_update(agent: DeFiMemoryAgent) -> None:
109130
"I lost too much money. I want to pivot to safe stablecoin yield farming on Arbitrum now."
110131
),
111132
}
112-
]
133+
],
134+
"user_id": USER_ID,
135+
"async_mode": False,
113136
},
114137
)
138+
update_result = await agent.available_tools.execute(
139+
name="update_memory",
140+
tool_input={
141+
"memory_id": memory_id,
142+
"text": "User pivoted to safer Arbitrum stablecoin yield farming with low risk.",
143+
"user_id": USER_ID,
144+
},
145+
)
146+
print(f"[Mem0] Update result: {update_result}")
115147
memories = await agent.available_tools.execute(
116148
name="search_memory",
117-
tool_input={"query": "stablecoin yield chain choice"},
149+
tool_input={"query": "stablecoin yield chain choice", "user_id": USER_ID},
118150
)
119151
print_memories(memories, "Retrieved after update (Phase 3)")
120152

121153

154+
async def phase_cleanup(agent: DeFiMemoryAgent, memory_id: Optional[str]) -> None:
155+
print("\n=== Phase 4: Clean up a memory entry ===")
156+
delete_result = await agent.available_tools.execute(
157+
name="delete_memory",
158+
tool_input={"memory_id": memory_id, "user_id": USER_ID},
159+
)
160+
print(f"[Mem0] Delete result: {delete_result}")
161+
remaining = await agent.available_tools.execute(
162+
name="get_all_memory",
163+
tool_input={"limit": 5, "user_id": USER_ID},
164+
)
165+
print_memories(remaining, "Remaining memories after delete")
166+
167+
122168
async def main() -> None:
123169
mem0_cfg = {
124170
"user_id": USER_ID,
125171
"metadata": {"project": "defi-investment-advisor"},
126172
"async_mode": False, # synchronous writes so the next search sees new data
127173
}
128-
agent = build_agent(mem0_cfg)
129-
await phase_capture(agent)
130-
await phase_recall(mem0_cfg)
131-
await phase_update(agent)
132-
174+
175+
try:
176+
agent = build_agent(mem0_cfg)
177+
await phase_capture(agent)
178+
await phase_recall(mem0_cfg)
179+
180+
all_memories = await agent.available_tools.execute(
181+
name="get_all_memory", tool_input={"limit": 5, "user_id": USER_ID}
182+
)
183+
print_memories(all_memories, "All memories before update/delete")
184+
first_id = extract_first_memory_id(all_memories)
185+
186+
await phase_update(agent, first_id)
187+
await phase_cleanup(agent, first_id)
188+
finally:
189+
await get_llm_manager().cleanup()
133190

134191

135192
if __name__ == "__main__":

spoon_ai/memory/utils.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
Memory helpers shared across Mem0 demos and utilities.
3+
"""
4+
5+
from typing import Any, List, Optional
6+
7+
__all__ = ["extract_memories", "extract_first_memory_id"]
8+
9+
10+
def _unwrap_output(result: Any) -> Any:
11+
"""Extract the underlying payload from a ToolResult-like object or raw response."""
12+
if hasattr(result, "output"):
13+
return getattr(result, "output")
14+
return result
15+
16+
17+
def extract_memories(result: Any) -> List[str]:
18+
"""
19+
Normalize Mem0 search/get responses into a list of memory strings.
20+
Supports common shapes: {"memories": [...]}, {"results": [...]}, {"data": [...]}, list, or scalar.
21+
"""
22+
data = _unwrap_output(result)
23+
if not data:
24+
return []
25+
26+
if isinstance(data, dict):
27+
if isinstance(data.get("memories"), list):
28+
items = data.get("memories", [])
29+
elif isinstance(data.get("results"), list):
30+
items = data.get("results", [])
31+
elif isinstance(data.get("data"), list):
32+
items = data.get("data", [])
33+
else:
34+
items = [data]
35+
elif isinstance(data, list):
36+
items = data
37+
else:
38+
items = [data]
39+
40+
extracted: List[str] = []
41+
for item in items:
42+
if isinstance(item, str):
43+
extracted.append(item)
44+
elif isinstance(item, dict):
45+
text = item.get("memory") or item.get("text") or item.get("content") or item.get("value")
46+
if text:
47+
extracted.append(str(text))
48+
return extracted
49+
50+
51+
def extract_first_memory_id(result: Any) -> Optional[str]:
52+
"""
53+
Pull the first memory id from Mem0 responses.
54+
Supports common id fields: id, _id, memory_id, uuid.
55+
"""
56+
data = _unwrap_output(result)
57+
if not data:
58+
return None
59+
60+
candidates = []
61+
if isinstance(data, dict):
62+
if isinstance(data.get("results"), list):
63+
candidates = data["results"]
64+
elif isinstance(data.get("memories"), list):
65+
candidates = data["memories"]
66+
elif isinstance(data.get("data"), list):
67+
candidates = data["data"]
68+
elif isinstance(data, list):
69+
candidates = data
70+
71+
for item in candidates:
72+
if isinstance(item, dict):
73+
mem_id = item.get("id") or item.get("_id") or item.get("memory_id") or item.get("uuid")
74+
if mem_id:
75+
return str(mem_id)
76+
return None

0 commit comments

Comments
 (0)