zep-crewai 0.1.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zep_crewai/__init__.py +42 -18
- zep_crewai/graph_storage.py +147 -0
- zep_crewai/tools.py +318 -0
- zep_crewai/user_storage.py +208 -0
- zep_crewai/utils.py +139 -0
- zep_crewai-1.1.0.dist-info/METADATA +404 -0
- zep_crewai-1.1.0.dist-info/RECORD +10 -0
- zep_crewai-0.1.0.dist-info/METADATA +0 -186
- zep_crewai-0.1.0.dist-info/RECORD +0 -6
- {zep_crewai-0.1.0.dist-info → zep_crewai-1.1.0.dist-info}/WHEEL +0 -0
zep_crewai/__init__.py
CHANGED
@@ -1,28 +1,34 @@
|
|
1
1
|
"""
|
2
2
|
Zep CrewAI Integration.
|
3
3
|
|
4
|
-
This package provides memory integration between Zep and CrewAI agents,
|
5
|
-
|
4
|
+
This package provides comprehensive memory integration between Zep and CrewAI agents,
|
5
|
+
including graph storage, user storage, and tools for searching and adding data.
|
6
6
|
|
7
7
|
Installation:
|
8
8
|
pip install zep-crewai
|
9
9
|
|
10
10
|
Usage:
|
11
|
-
from zep_crewai import
|
12
|
-
from zep_cloud.client import
|
11
|
+
from zep_crewai import ZepGraphStorage, ZepUserStorage, create_search_tool
|
12
|
+
from zep_cloud.client import Zep
|
13
13
|
from crewai.memory.external.external_memory import ExternalMemory
|
14
|
-
from crewai import Crew
|
15
|
-
|
16
|
-
# Initialize Zep client
|
17
|
-
zep_client =
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
14
|
+
from crewai import Agent, Crew
|
15
|
+
|
16
|
+
# Initialize Zep client
|
17
|
+
zep_client = Zep(api_key="your-api-key")
|
18
|
+
|
19
|
+
# For user-specific storage
|
20
|
+
user_storage = ZepUserStorage(client=zep_client, user_id="user123", thread_id="thread123")
|
21
|
+
|
22
|
+
# For generic knowledge graphs
|
23
|
+
graph_storage = ZepGraphStorage(client=zep_client, graph_id="knowledge_base")
|
24
|
+
|
25
|
+
# Create tools
|
26
|
+
search_tool = create_search_tool(zep_client, user_id="user123")
|
27
|
+
|
28
|
+
# Create agent with tools
|
29
|
+
agent = Agent(
|
30
|
+
role="Assistant",
|
31
|
+
tools=[search_tool]
|
26
32
|
)
|
27
33
|
"""
|
28
34
|
|
@@ -35,11 +41,29 @@ from .exceptions import ZepDependencyError
|
|
35
41
|
try:
|
36
42
|
# Check for required CrewAI dependencies - just test import
|
37
43
|
import crewai.memory.storage.interface # noqa: F401
|
44
|
+
import crewai.tools # noqa: F401
|
38
45
|
|
39
|
-
# Import our integration
|
46
|
+
# Import our integration components
|
47
|
+
from .graph_storage import ZepGraphStorage
|
40
48
|
from .memory import ZepStorage
|
49
|
+
from .tools import (
|
50
|
+
ZepAddDataTool,
|
51
|
+
ZepSearchTool,
|
52
|
+
create_add_data_tool,
|
53
|
+
create_search_tool,
|
54
|
+
)
|
55
|
+
from .user_storage import ZepUserStorage
|
41
56
|
|
42
|
-
__all__ = [
|
57
|
+
__all__ = [
|
58
|
+
"ZepStorage",
|
59
|
+
"ZepGraphStorage",
|
60
|
+
"ZepUserStorage",
|
61
|
+
"ZepSearchTool",
|
62
|
+
"ZepAddDataTool",
|
63
|
+
"create_search_tool",
|
64
|
+
"create_add_data_tool",
|
65
|
+
"ZepDependencyError",
|
66
|
+
]
|
43
67
|
|
44
68
|
except ImportError as e:
|
45
69
|
raise ZepDependencyError(framework="CrewAI", install_command="pip install zep-crewai") from e
|
@@ -0,0 +1,147 @@
|
|
1
|
+
"""
|
2
|
+
Zep Graph Storage for CrewAI.
|
3
|
+
|
4
|
+
This module provides graph-based storage that integrates Zep's knowledge graph
|
5
|
+
capabilities with CrewAI's memory system.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
from crewai.memory.storage.interface import Storage
|
12
|
+
from zep_cloud.client import Zep
|
13
|
+
from zep_cloud.types import SearchFilters
|
14
|
+
|
15
|
+
from .utils import search_graph_and_compose_context
|
16
|
+
|
17
|
+
|
18
|
+
class ZepGraphStorage(Storage):
|
19
|
+
"""
|
20
|
+
Storage implementation for Zep's generic knowledge graphs.
|
21
|
+
|
22
|
+
This class provides persistent storage and retrieval of structured knowledge
|
23
|
+
using Zep's graph capabilities for non-user-specific data.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(
|
27
|
+
self,
|
28
|
+
client: Zep,
|
29
|
+
graph_id: str,
|
30
|
+
search_filters: SearchFilters | None = None,
|
31
|
+
facts_limit: int = 20,
|
32
|
+
entity_limit: int = 5,
|
33
|
+
**kwargs: Any,
|
34
|
+
) -> None:
|
35
|
+
"""
|
36
|
+
Initialize ZepGraphStorage with a Zep client instance.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
client: An initialized Zep instance (sync client)
|
40
|
+
graph_id: Identifier for the knowledge graph
|
41
|
+
search_filters: Optional filters for search operations
|
42
|
+
facts_limit: Maximum number of facts (edges) to retrieve for context
|
43
|
+
entity_limit: Maximum number of entities (nodes) to retrieve for context
|
44
|
+
**kwargs: Additional configuration options
|
45
|
+
"""
|
46
|
+
if not isinstance(client, Zep):
|
47
|
+
raise TypeError("client must be an instance of Zep")
|
48
|
+
|
49
|
+
if not graph_id:
|
50
|
+
raise ValueError("graph_id is required")
|
51
|
+
|
52
|
+
self._client = client
|
53
|
+
self._graph_id = graph_id
|
54
|
+
self._search_filters = search_filters
|
55
|
+
self._facts_limit = facts_limit
|
56
|
+
self._entity_limit = entity_limit
|
57
|
+
self._config = kwargs
|
58
|
+
|
59
|
+
self._logger = logging.getLogger(__name__)
|
60
|
+
|
61
|
+
def save(self, value: Any, metadata: dict[str, Any] | None = None) -> None:
|
62
|
+
"""
|
63
|
+
Save data to the Zep knowledge graph.
|
64
|
+
|
65
|
+
Routes storage based on metadata.type:
|
66
|
+
- "json": Store as JSON data
|
67
|
+
- "text": Store as text data (default)
|
68
|
+
- "message": Store as message data
|
69
|
+
|
70
|
+
Args:
|
71
|
+
value: The content to store
|
72
|
+
metadata: Metadata including type information
|
73
|
+
"""
|
74
|
+
metadata = metadata or {}
|
75
|
+
content_str = str(value)
|
76
|
+
content_type = metadata.get("type", "text")
|
77
|
+
|
78
|
+
# Validate content type
|
79
|
+
if content_type not in ["json", "text", "message"]:
|
80
|
+
content_type = "text"
|
81
|
+
|
82
|
+
try:
|
83
|
+
# Add data to the graph
|
84
|
+
self._client.graph.add(
|
85
|
+
graph_id=self._graph_id,
|
86
|
+
data=content_str,
|
87
|
+
type=content_type,
|
88
|
+
)
|
89
|
+
|
90
|
+
self._logger.debug(
|
91
|
+
f"Saved {content_type} data to graph {self._graph_id}: {content_str[:100]}..."
|
92
|
+
)
|
93
|
+
|
94
|
+
except Exception as e:
|
95
|
+
self._logger.error(f"Error saving to Zep graph: {e}")
|
96
|
+
raise
|
97
|
+
|
98
|
+
def search(
|
99
|
+
self, query: str, limit: int = 10, score_threshold: float = 0.0
|
100
|
+
) -> dict[str, Any] | list[Any]:
|
101
|
+
"""
|
102
|
+
Search the Zep knowledge graph and return composed context.
|
103
|
+
|
104
|
+
Performs parallel searches across edges, nodes, and episodes,
|
105
|
+
then returns a composed context string.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
query: Search query string from the agent
|
109
|
+
limit: Maximum number of results per scope
|
110
|
+
score_threshold: Minimum relevance score (not used in Zep, kept for interface compatibility)
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
List with a single dict containing the composed context string
|
114
|
+
"""
|
115
|
+
try:
|
116
|
+
# Use the shared utility function for graph search and context composition
|
117
|
+
context = search_graph_and_compose_context(
|
118
|
+
client=self._client,
|
119
|
+
query=query,
|
120
|
+
graph_id=self._graph_id,
|
121
|
+
facts_limit=self._facts_limit,
|
122
|
+
entity_limit=self._entity_limit,
|
123
|
+
episodes_limit=limit,
|
124
|
+
search_filters=self._search_filters,
|
125
|
+
)
|
126
|
+
|
127
|
+
if context:
|
128
|
+
self._logger.info(f"Composed context for query: {query}")
|
129
|
+
return [
|
130
|
+
{"memory": context, "type": "graph_context", "source": "graph", "query": query}
|
131
|
+
]
|
132
|
+
|
133
|
+
self._logger.info(f"No results found for query: {query}")
|
134
|
+
return []
|
135
|
+
|
136
|
+
except Exception as e:
|
137
|
+
self._logger.error(f"Error searching graph: {e}")
|
138
|
+
return []
|
139
|
+
|
140
|
+
def reset(self) -> None:
|
141
|
+
"""Reset is not implemented for graph storage as graphs should persist."""
|
142
|
+
pass
|
143
|
+
|
144
|
+
@property
|
145
|
+
def graph_id(self) -> str:
|
146
|
+
"""Get the graph ID."""
|
147
|
+
return self._graph_id
|
zep_crewai/tools.py
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
"""
|
2
|
+
Zep CrewAI Tools.
|
3
|
+
|
4
|
+
This module provides CrewAI tools for interacting with Zep memory storage,
|
5
|
+
including graph and user memory operations.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
from crewai.tools import BaseTool
|
12
|
+
from pydantic import BaseModel, Field
|
13
|
+
from zep_cloud.client import Zep
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
class SearchMemoryInput(BaseModel):
|
19
|
+
"""Input schema for memory search tool."""
|
20
|
+
|
21
|
+
query: str = Field(..., description="The search query to find relevant memories")
|
22
|
+
limit: int = Field(default=10, description="Maximum number of results to return")
|
23
|
+
scope: str = Field(
|
24
|
+
default="edges",
|
25
|
+
description="Scope of search: 'edges' (facts), 'nodes' (entities), 'episodes', or 'all'",
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
class AddGraphDataInput(BaseModel):
|
30
|
+
"""Input schema for adding data to graph."""
|
31
|
+
|
32
|
+
data: str = Field(..., description="The data/information to store in the graph")
|
33
|
+
data_type: str = Field(default="text", description="Type of data: 'text', 'json', or 'message'")
|
34
|
+
|
35
|
+
|
36
|
+
class ZepSearchTool(BaseTool):
|
37
|
+
"""
|
38
|
+
Tool for searching Zep memory storage.
|
39
|
+
|
40
|
+
Can search either graph memory or user memory depending on initialization.
|
41
|
+
"""
|
42
|
+
|
43
|
+
name: str = "Zep Memory Search"
|
44
|
+
description: str = "Search Zep memory storage for relevant information"
|
45
|
+
args_schema: type[BaseModel] = SearchMemoryInput
|
46
|
+
|
47
|
+
def __init__(
|
48
|
+
self, client: Zep, graph_id: str | None = None, user_id: str | None = None, **kwargs: Any
|
49
|
+
):
|
50
|
+
"""
|
51
|
+
Initialize search tool bound to either a graph or user.
|
52
|
+
|
53
|
+
Args:
|
54
|
+
client: Zep client instance
|
55
|
+
graph_id: Graph ID for generic knowledge graph search
|
56
|
+
user_id: User ID for user-specific graph search
|
57
|
+
**kwargs: Additional configuration
|
58
|
+
"""
|
59
|
+
if not graph_id and not user_id:
|
60
|
+
raise ValueError("Either graph_id or user_id must be provided")
|
61
|
+
|
62
|
+
if graph_id and user_id:
|
63
|
+
raise ValueError("Only one of graph_id or user_id should be provided")
|
64
|
+
|
65
|
+
# Update description based on target
|
66
|
+
if graph_id:
|
67
|
+
kwargs["description"] = f"Search Zep graph '{graph_id}' for relevant information"
|
68
|
+
else:
|
69
|
+
kwargs["description"] = f"Search user '{user_id}' memories for relevant information"
|
70
|
+
|
71
|
+
super().__init__(**kwargs)
|
72
|
+
|
73
|
+
# Store as private attributes to avoid Pydantic validation
|
74
|
+
self._client = client
|
75
|
+
self._graph_id = graph_id
|
76
|
+
self._user_id = user_id
|
77
|
+
|
78
|
+
@property
|
79
|
+
def client(self) -> Zep:
|
80
|
+
"""Get the Zep client."""
|
81
|
+
return self._client
|
82
|
+
|
83
|
+
@property
|
84
|
+
def graph_id(self) -> str | None:
|
85
|
+
"""Get the graph ID."""
|
86
|
+
return self._graph_id
|
87
|
+
|
88
|
+
@property
|
89
|
+
def user_id(self) -> str | None:
|
90
|
+
"""Get the user ID."""
|
91
|
+
return self._user_id
|
92
|
+
|
93
|
+
def _run(self, query: str, limit: int = 10, scope: str = "edges") -> str:
|
94
|
+
"""
|
95
|
+
Execute the search operation.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
query: Search query
|
99
|
+
limit: Maximum results
|
100
|
+
scope: Search scope
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
Formatted search results
|
104
|
+
"""
|
105
|
+
try:
|
106
|
+
results = []
|
107
|
+
|
108
|
+
if scope == "all":
|
109
|
+
# Search all scopes
|
110
|
+
scopes = ["edges", "nodes", "episodes"]
|
111
|
+
else:
|
112
|
+
scopes = [scope]
|
113
|
+
|
114
|
+
for search_scope in scopes:
|
115
|
+
if self._graph_id:
|
116
|
+
# Search graph memory
|
117
|
+
search_results = self._client.graph.search(
|
118
|
+
graph_id=self._graph_id, query=query, limit=limit, scope=search_scope
|
119
|
+
)
|
120
|
+
else:
|
121
|
+
# Search user memory
|
122
|
+
search_results = self._client.graph.search(
|
123
|
+
user_id=self._user_id, query=query, limit=limit, scope=search_scope
|
124
|
+
)
|
125
|
+
|
126
|
+
# Process results based on scope
|
127
|
+
if search_scope == "edges" and search_results.edges:
|
128
|
+
for edge in search_results.edges:
|
129
|
+
results.append(
|
130
|
+
{
|
131
|
+
"type": "fact",
|
132
|
+
"content": edge.fact,
|
133
|
+
"name": edge.name,
|
134
|
+
"created_at": str(edge.created_at) if edge.created_at else None,
|
135
|
+
}
|
136
|
+
)
|
137
|
+
|
138
|
+
elif search_scope == "nodes" and search_results.nodes:
|
139
|
+
for node in search_results.nodes:
|
140
|
+
results.append(
|
141
|
+
{
|
142
|
+
"type": "entity",
|
143
|
+
"content": f"{node.name}: {node.summary}",
|
144
|
+
"name": node.name,
|
145
|
+
"created_at": str(node.created_at) if node.created_at else None,
|
146
|
+
}
|
147
|
+
)
|
148
|
+
|
149
|
+
elif search_scope == "episodes" and search_results.episodes:
|
150
|
+
for episode in search_results.episodes:
|
151
|
+
results.append(
|
152
|
+
{
|
153
|
+
"type": "episode",
|
154
|
+
"content": episode.content,
|
155
|
+
"source": episode.source,
|
156
|
+
"role": episode.role,
|
157
|
+
"created_at": str(episode.created_at)
|
158
|
+
if episode.created_at
|
159
|
+
else None,
|
160
|
+
}
|
161
|
+
)
|
162
|
+
|
163
|
+
if not results:
|
164
|
+
return f"No results found for query: '{query}'"
|
165
|
+
|
166
|
+
# Format results for agent consumption
|
167
|
+
formatted = f"Found {len(results)} relevant memories:\n\n"
|
168
|
+
for i, result in enumerate(results, 1):
|
169
|
+
result_type = result.get("type", "unknown")
|
170
|
+
formatted += f"{i}. [{result_type.upper() if result_type else 'UNKNOWN'}] {result['content']}\n"
|
171
|
+
if result.get("created_at"):
|
172
|
+
formatted += f" (Created: {result['created_at']})\n"
|
173
|
+
formatted += "\n"
|
174
|
+
|
175
|
+
logger.info(f"Found {len(results)} memories for query: {query}")
|
176
|
+
return formatted
|
177
|
+
|
178
|
+
except Exception as e:
|
179
|
+
error_msg = f"Error searching Zep memory: {str(e)}"
|
180
|
+
logger.error(error_msg)
|
181
|
+
return error_msg
|
182
|
+
|
183
|
+
|
184
|
+
class ZepAddDataTool(BaseTool):
|
185
|
+
"""
|
186
|
+
Tool for adding data to Zep memory storage.
|
187
|
+
|
188
|
+
Can add data to either graph memory or user memory depending on initialization.
|
189
|
+
"""
|
190
|
+
|
191
|
+
name: str = "Zep Add Data"
|
192
|
+
description: str = "Add data to Zep memory storage"
|
193
|
+
args_schema: type[BaseModel] = AddGraphDataInput
|
194
|
+
|
195
|
+
def __init__(
|
196
|
+
self, client: Zep, graph_id: str | None = None, user_id: str | None = None, **kwargs: Any
|
197
|
+
):
|
198
|
+
"""
|
199
|
+
Initialize add data tool bound to either a graph or user.
|
200
|
+
|
201
|
+
Args:
|
202
|
+
client: Zep client instance
|
203
|
+
graph_id: Graph ID for generic knowledge graph
|
204
|
+
user_id: User ID for user-specific graph
|
205
|
+
**kwargs: Additional configuration
|
206
|
+
"""
|
207
|
+
if not graph_id and not user_id:
|
208
|
+
raise ValueError("Either graph_id or user_id must be provided")
|
209
|
+
|
210
|
+
if graph_id and user_id:
|
211
|
+
raise ValueError("Only one of graph_id or user_id should be provided")
|
212
|
+
|
213
|
+
# Update description based on target
|
214
|
+
if graph_id:
|
215
|
+
kwargs["description"] = f"Add data to Zep graph '{graph_id}'"
|
216
|
+
else:
|
217
|
+
kwargs["description"] = f"Add data to user '{user_id}' memory"
|
218
|
+
|
219
|
+
super().__init__(**kwargs)
|
220
|
+
|
221
|
+
# Store as private attributes to avoid Pydantic validation
|
222
|
+
self._client = client
|
223
|
+
self._graph_id = graph_id
|
224
|
+
self._user_id = user_id
|
225
|
+
|
226
|
+
@property
|
227
|
+
def client(self) -> Zep:
|
228
|
+
"""Get the Zep client."""
|
229
|
+
return self._client
|
230
|
+
|
231
|
+
@property
|
232
|
+
def graph_id(self) -> str | None:
|
233
|
+
"""Get the graph ID."""
|
234
|
+
return self._graph_id
|
235
|
+
|
236
|
+
@property
|
237
|
+
def user_id(self) -> str | None:
|
238
|
+
"""Get the user ID."""
|
239
|
+
return self._user_id
|
240
|
+
|
241
|
+
def _run(self, data: str, data_type: str = "text") -> str:
|
242
|
+
"""
|
243
|
+
Execute the add data operation.
|
244
|
+
|
245
|
+
Args:
|
246
|
+
data: Data to store
|
247
|
+
data_type: Type of data
|
248
|
+
|
249
|
+
Returns:
|
250
|
+
Success or error message
|
251
|
+
"""
|
252
|
+
try:
|
253
|
+
# Validate data type
|
254
|
+
if data_type not in ["text", "json", "message"]:
|
255
|
+
data_type = "text"
|
256
|
+
|
257
|
+
if self._graph_id:
|
258
|
+
# Add to graph memory
|
259
|
+
self._client.graph.add(graph_id=self._graph_id, type=data_type, data=data)
|
260
|
+
|
261
|
+
success_msg = f"Successfully added {data_type} data to graph '{self._graph_id}'"
|
262
|
+
logger.debug(f"Added data to graph {self._graph_id}: {data[:100]}...")
|
263
|
+
|
264
|
+
else:
|
265
|
+
# Add to user graph memory
|
266
|
+
self._client.graph.add(user_id=self._user_id, type=data_type, data=data)
|
267
|
+
|
268
|
+
success_msg = (
|
269
|
+
f"Successfully added {data_type} data to user '{self._user_id}' memory"
|
270
|
+
)
|
271
|
+
logger.debug(f"Added data to user {self._user_id}: {data[:100]}...")
|
272
|
+
|
273
|
+
return success_msg
|
274
|
+
|
275
|
+
except Exception as e:
|
276
|
+
error_msg = f"Error adding data to Zep: {str(e)}"
|
277
|
+
logger.error(error_msg)
|
278
|
+
return error_msg
|
279
|
+
|
280
|
+
|
281
|
+
def create_search_tool(
|
282
|
+
client: Zep, graph_id: str | None = None, user_id: str | None = None
|
283
|
+
) -> ZepSearchTool:
|
284
|
+
"""
|
285
|
+
Create a search tool bound to a Zep client.
|
286
|
+
|
287
|
+
Args:
|
288
|
+
client: Zep client instance
|
289
|
+
graph_id: Optional graph ID for generic knowledge graph
|
290
|
+
user_id: Optional user ID for user-specific graph
|
291
|
+
|
292
|
+
Returns:
|
293
|
+
ZepSearchTool instance
|
294
|
+
|
295
|
+
Raises:
|
296
|
+
ValueError: If neither or both IDs are provided
|
297
|
+
"""
|
298
|
+
return ZepSearchTool(client=client, graph_id=graph_id, user_id=user_id)
|
299
|
+
|
300
|
+
|
301
|
+
def create_add_data_tool(
|
302
|
+
client: Zep, graph_id: str | None = None, user_id: str | None = None
|
303
|
+
) -> ZepAddDataTool:
|
304
|
+
"""
|
305
|
+
Create an add data tool bound to a Zep client.
|
306
|
+
|
307
|
+
Args:
|
308
|
+
client: Zep client instance
|
309
|
+
graph_id: Optional graph ID for generic knowledge graph
|
310
|
+
user_id: Optional user ID for user-specific graph
|
311
|
+
|
312
|
+
Returns:
|
313
|
+
ZepAddDataTool instance
|
314
|
+
|
315
|
+
Raises:
|
316
|
+
ValueError: If neither or both IDs are provided
|
317
|
+
"""
|
318
|
+
return ZepAddDataTool(client=client, graph_id=graph_id, user_id=user_id)
|