zep-crewai 0.1.0__py3-none-any.whl → 1.0.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 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
- enabling persistent conversation memory and context retrieval.
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 ZepStorage
12
- from zep_cloud.client import AsyncZep
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 and storage
17
- zep_client = AsyncZep(api_key="your-api-key")
18
- zep_storage = ZepStorage(client=zep_client, user_id="user123", thread_id="thread123")
19
- external_memory = ExternalMemory(storage=zep_storage)
20
-
21
- # Create crew with Zep memory
22
- crew = Crew(
23
- agents=[...],
24
- tasks=[...],
25
- external_memory=external_memory
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__ = ["ZepStorage", "ZepDependencyError"]
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)