haystack-experimental 0.16.0__py3-none-any.whl → 0.17.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.
@@ -57,6 +57,7 @@ from haystack_experimental.components.agents.human_in_the_loop.strategies import
57
57
  )
58
58
  from haystack_experimental.components.retrievers import ChatMessageRetriever
59
59
  from haystack_experimental.components.writers import ChatMessageWriter
60
+ from haystack_experimental.memory_stores.types import MemoryStore
60
61
 
61
62
  logger = logging.getLogger(__name__)
62
63
 
@@ -146,6 +147,7 @@ class Agent(HaystackAgent):
146
147
  confirmation_strategies: dict[str, ConfirmationStrategy] | None = None,
147
148
  tool_invoker_kwargs: dict[str, Any] | None = None,
148
149
  chat_message_store: ChatMessageStore | None = None,
150
+ memory_store: MemoryStore | None = None,
149
151
  ) -> None:
150
152
  """
151
153
  Initialize the agent component.
@@ -164,6 +166,9 @@ class Agent(HaystackAgent):
164
166
  :param raise_on_tool_invocation_failure: Should the agent raise an exception when a tool invocation fails?
165
167
  If set to False, the exception will be turned into a chat message and passed to the LLM.
166
168
  :param tool_invoker_kwargs: Additional keyword arguments to pass to the ToolInvoker.
169
+ :param chat_message_store: The ChatMessageStore that the agent can use to store
170
+ and retrieve chat messages history.
171
+ :param memory_store: The memory store that the agent can use to store and retrieve memories.
167
172
  :raises TypeError: If the chat_generator does not support tools parameter in its run method.
168
173
  :raises ValueError: If the exit_conditions are not valid.
169
174
  """
@@ -186,6 +191,7 @@ class Agent(HaystackAgent):
186
191
  self._chat_message_writer = (
187
192
  ChatMessageWriter(chat_message_store=chat_message_store) if chat_message_store else None
188
193
  )
194
+ self._memory_store = memory_store
189
195
 
190
196
  def _initialize_fresh_execution(
191
197
  self,
@@ -198,6 +204,7 @@ class Agent(HaystackAgent):
198
204
  tools: ToolsType | list[str] | None = None,
199
205
  confirmation_strategy_context: dict[str, Any] | None = None,
200
206
  chat_message_store_kwargs: dict[str, Any] | None = None,
207
+ memory_store_kwargs: dict[str, Any] | None = None,
201
208
  **kwargs: dict[str, Any],
202
209
  ) -> _ExecutionContext:
203
210
  """
@@ -209,29 +216,62 @@ class Agent(HaystackAgent):
209
216
  :param system_prompt: System prompt for the agent. If provided, it overrides the default system prompt.
210
217
  :param tools: Optional list of Tool objects, a Toolset, or list of tool names to use for this run.
211
218
  When passing tool names, tools are selected from the Agent's originally configured tools.
219
+
220
+ :param memory_store_kwargs: Optional dictionary of keyword arguments to pass to the MemoryStore.
221
+ For example, it can include the `user_id`, `run_id`, and `agent_id` parameters
222
+ for storing and retrieving memories.
212
223
  :param confirmation_strategy_context: Optional dictionary for passing request-scoped resources
213
224
  to confirmation strategies.
214
225
  :param chat_message_store_kwargs: Optional dictionary of keyword arguments to pass to the ChatMessageStore.
226
+ For example, it can include the `chat_history_id` and `last_k` parameters for retrieving chat history.
215
227
  :param kwargs: Additional data to pass to the State used by the Agent.
216
228
  """
217
229
  system_prompt = system_prompt or self.system_prompt
218
- if system_prompt is not None:
219
- messages = [ChatMessage.from_system(system_prompt)] + messages
230
+ retrieved_memory = None
231
+ updated_system_prompt = system_prompt
232
+
233
+ # Retrieve memories from the memory store
234
+ if self._memory_store:
235
+ retrieved_memories = self._memory_store.search_memories(query=messages[-1].text, **memory_store_kwargs) # type: ignore[arg-type]
236
+
237
+ # we combine the memories into a single string
238
+ combined_memory = "\n".join(
239
+ f"- MEMORY #{idx + 1}: {memory.text}" for idx, memory in enumerate(retrieved_memories)
240
+ )
241
+ retrieved_memory = ChatMessage.from_system(text=combined_memory)
242
+
243
+ if retrieved_memory:
244
+ memory_instruction = (
245
+ "\n\nWhen messages start with `[MEMORY]`, treat them as long-term "
246
+ "context and use them to guide the response if relevant."
247
+ )
248
+ updated_system_prompt = f"{system_prompt}{memory_instruction}"
249
+
250
+ memory_text = f"Here are the relevant memories for the user's query: {retrieved_memory.text}"
251
+ print(memory_text)
252
+ updated_memory = ChatMessage.from_system(text=memory_text)
253
+ else:
254
+ updated_memory = None
255
+
256
+ combined_messages = messages + [updated_memory] if updated_memory else messages
257
+ if updated_system_prompt is not None:
258
+ combined_messages = [ChatMessage.from_system(updated_system_prompt)] + combined_messages
220
259
 
221
260
  # NOTE: difference with parent method to add chat message retrieval
222
261
  if self._chat_message_retriever:
223
262
  retriever_kwargs = _select_kwargs(self._chat_message_retriever, chat_message_store_kwargs or {})
224
263
  if "chat_history_id" in retriever_kwargs:
225
264
  messages = self._chat_message_retriever.run(
226
- current_messages=messages,
265
+ current_messages=combined_messages,
227
266
  **retriever_kwargs,
228
267
  )["messages"]
268
+ combined_messages = messages
229
269
 
230
- if all(m.is_from(ChatRole.SYSTEM) for m in messages):
270
+ if all(m.is_from(ChatRole.SYSTEM) for m in combined_messages):
231
271
  logger.warning("All messages provided to the Agent component are system messages. This is not recommended.")
232
272
 
233
273
  state = State(schema=self.state_schema, data=kwargs)
234
- state.set("messages", messages)
274
+ state.set("messages", combined_messages)
235
275
 
236
276
  streaming_callback = select_streaming_callback( # type: ignore[call-overload]
237
277
  init_callback=self.streaming_callback, runtime_callback=streaming_callback, requires_async=requires_async
@@ -329,6 +369,7 @@ class Agent(HaystackAgent):
329
369
  tools: ToolsType | list[str] | None = None,
330
370
  confirmation_strategy_context: dict[str, Any] | None = None,
331
371
  chat_message_store_kwargs: dict[str, Any] | None = None,
372
+ memory_store_kwargs: dict[str, Any] | None = None,
332
373
  **kwargs: Any,
333
374
  ) -> dict[str, Any]:
334
375
  """
@@ -352,6 +393,19 @@ class Agent(HaystackAgent):
352
393
  can use for non-blocking user interaction.
353
394
  :param chat_message_store_kwargs: Optional dictionary of keyword arguments to pass to the ChatMessageStore.
354
395
  For example, it can include the `chat_history_id` and `last_k` parameters for retrieving chat history.
396
+ :param memory_store_kwargs: Optional dictionary of keyword arguments to pass to the MemoryStore.
397
+ It can include:
398
+ - `user_id`: The user ID to search and add memories from.
399
+ - `run_id`: The run ID to search and add memories from.
400
+ - `agent_id`: The agent ID to search and add memories from.
401
+ - `search_criteria`: A dictionary of containing kwargs for the `search_memories` method.
402
+ This can include:
403
+ - `filters`: A dictionary of filters to search for memories.
404
+ - `query`: The query to search for memories.
405
+ Note: If you pass this, the user query passed to the agent will be
406
+ ignored for memory retrieval.
407
+ - `top_k`: The number of memories to return.
408
+ - `include_memory_metadata`: Whether to include the memory metadata in the ChatMessage.
355
409
  :param kwargs: Additional data to pass to the State schema used by the Agent.
356
410
  The keys must match the schema defined in the Agent's `state_schema`.
357
411
  :returns:
@@ -362,6 +416,8 @@ class Agent(HaystackAgent):
362
416
  :raises RuntimeError: If the Agent component wasn't warmed up before calling `run()`.
363
417
  :raises BreakpointException: If an agent breakpoint is triggered.
364
418
  """
419
+ memory_store_kwargs = memory_store_kwargs or {}
420
+
365
421
  agent_inputs = {
366
422
  "messages": messages,
367
423
  "streaming_callback": streaming_callback,
@@ -392,6 +448,7 @@ class Agent(HaystackAgent):
392
448
  tools=tools,
393
449
  confirmation_strategy_context=confirmation_strategy_context,
394
450
  chat_message_store_kwargs=chat_message_store_kwargs,
451
+ memory_store_kwargs=memory_store_kwargs,
395
452
  **kwargs,
396
453
  )
397
454
 
@@ -547,6 +604,11 @@ class Agent(HaystackAgent):
547
604
  if msgs := result.get("messages"):
548
605
  result["last_message"] = msgs[-1]
549
606
 
607
+ # Add the new conversation as memories to the memory store
608
+ if self._memory_store:
609
+ new_memories = [message for message in msgs if message.role.value != "system"]
610
+ self._memory_store.add_memories(messages=new_memories, **memory_store_kwargs)
611
+
550
612
  # Write messages to ChatMessageStore if configured
551
613
  if self._chat_message_writer:
552
614
  writer_kwargs = _select_kwargs(self._chat_message_writer, chat_message_store_kwargs or {})
@@ -567,6 +629,7 @@ class Agent(HaystackAgent):
567
629
  tools: ToolsType | list[str] | None = None,
568
630
  confirmation_strategy_context: dict[str, Any] | None = None,
569
631
  chat_message_store_kwargs: dict[str, Any] | None = None,
632
+ memory_store_kwargs: dict[str, Any] | None = None,
570
633
  **kwargs: Any,
571
634
  ) -> dict[str, Any]:
572
635
  """
@@ -593,6 +656,20 @@ class Agent(HaystackAgent):
593
656
  can use for non-blocking user interaction.
594
657
  :param chat_message_store_kwargs: Optional dictionary of keyword arguments to pass to the ChatMessageStore.
595
658
  For example, it can include the `chat_history_id` and `last_k` parameters for retrieving chat history.
659
+ :param kwargs: Additional data to pass to the State schema used by the Agent.
660
+ :param memory_store_kwargs: Optional dictionary of keyword arguments to pass to the MemoryStore.
661
+ It can include:
662
+ - `user_id`: The user ID to search and add memories from.
663
+ - `run_id`: The run ID to search and add memories from.
664
+ - `agent_id`: The agent ID to search and add memories from.
665
+ - `search_criteria`: A dictionary of containing kwargs for the `search_memories` method.
666
+ This can include:
667
+ - `filters`: A dictionary of filters to search for memories.
668
+ - `query`: The query to search for memories.
669
+ Note: If you pass this, the user query passed to the agent will be
670
+ ignored for memory retrieval.
671
+ - `top_k`: The number of memories to return.
672
+ - `include_memory_metadata`: Whether to include the memory metadata in the ChatMessage.
596
673
  :param kwargs: Additional data to pass to the State schema used by the Agent.
597
674
  The keys must match the schema defined in the Agent's `state_schema`.
598
675
  :returns:
@@ -603,6 +680,8 @@ class Agent(HaystackAgent):
603
680
  :raises RuntimeError: If the Agent component wasn't warmed up before calling `run_async()`.
604
681
  :raises BreakpointException: If an agent breakpoint is triggered.
605
682
  """
683
+ memory_store_kwargs = memory_store_kwargs or {}
684
+
606
685
  agent_inputs = {
607
686
  "messages": messages,
608
687
  "streaming_callback": streaming_callback,
@@ -631,6 +710,7 @@ class Agent(HaystackAgent):
631
710
  tools=tools,
632
711
  confirmation_strategy_context=confirmation_strategy_context,
633
712
  chat_message_store_kwargs=chat_message_store_kwargs,
713
+ memory_store_kwargs=memory_store_kwargs,
634
714
  **kwargs,
635
715
  )
636
716
 
@@ -773,6 +853,11 @@ class Agent(HaystackAgent):
773
853
  if msgs := result.get("messages"):
774
854
  result["last_message"] = msgs[-1]
775
855
 
856
+ # Add the new conversation as memories to the memory store
857
+ if self._memory_store:
858
+ new_memories = [message for message in msgs if message.role.value != "system"]
859
+ self._memory_store.add_memories(messages=new_memories, **memory_store_kwargs)
860
+
776
861
  # Write messages to ChatMessageStore if configured
777
862
  if self._chat_message_writer:
778
863
  writer_kwargs = _select_kwargs(self._chat_message_writer, chat_message_store_kwargs or {})
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from .types import MemoryStore
6
+
7
+ __all__ = ["MemoryStore"]
@@ -0,0 +1,16 @@
1
+ # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import sys
6
+ from typing import TYPE_CHECKING
7
+
8
+ from lazy_imports import LazyImporter
9
+
10
+ _import_structure = {"memory_store": ["Mem0MemoryStore"]}
11
+
12
+ if TYPE_CHECKING:
13
+ from .memory_store import Mem0MemoryStore as Mem0MemoryStore
14
+
15
+ else:
16
+ sys.modules[__name__] = LazyImporter(name=__name__, module_file=__file__, import_structure=_import_structure)
@@ -0,0 +1,323 @@
1
+ # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from typing import Any
6
+
7
+ from haystack import default_from_dict, default_to_dict, logging
8
+ from haystack.dataclasses.chat_message import ChatMessage
9
+ from haystack.lazy_imports import LazyImport
10
+ from haystack.utils import Secret, deserialize_secrets_inplace
11
+
12
+ with LazyImport(message="Run 'pip install mem0ai'") as mem0_import:
13
+ from mem0 import MemoryClient # pylint: disable=import-error
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class Mem0MemoryStore:
19
+ """
20
+ A memory store implementation using Mem0 as the backend.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ *,
26
+ api_key: Secret = Secret.from_env_var("MEM0_API_KEY"),
27
+ ):
28
+ """
29
+ Initialize the Mem0 memory store.
30
+
31
+ :param api_key: The Mem0 API key. You can also set it using `MEM0_API_KEY` environment variable.
32
+ """
33
+
34
+ mem0_import.check()
35
+ self.api_key = api_key
36
+ self.client = MemoryClient(
37
+ api_key=self.api_key.resolve_value(),
38
+ )
39
+
40
+ def to_dict(self) -> dict[str, Any]:
41
+ """Serialize the store configuration to a dictionary."""
42
+ return default_to_dict(
43
+ self,
44
+ api_key=self.api_key.to_dict(),
45
+ )
46
+
47
+ @classmethod
48
+ def from_dict(cls, data: dict[str, Any]) -> "Mem0MemoryStore":
49
+ """Deserialize the store from a dictionary."""
50
+ deserialize_secrets_inplace(data["init_parameters"], keys=["api_key"])
51
+
52
+ return default_from_dict(cls, data)
53
+
54
+ def add_memories(
55
+ self,
56
+ *,
57
+ messages: list[ChatMessage],
58
+ infer: bool = True,
59
+ user_id: str | None = None,
60
+ run_id: str | None = None,
61
+ agent_id: str | None = None,
62
+ async_mode: bool = False,
63
+ **kwargs: Any,
64
+ ) -> list[dict[str, Any]]:
65
+ """
66
+ Add ChatMessage memories to Mem0.
67
+
68
+ :param messages: List of ChatMessage objects with memory metadata
69
+ :param infer: Whether to infer facts from the messages. If False, the whole message will
70
+ be added as a memory.
71
+ :param user_id: The user ID to to store and retrieve memories from the memory store.
72
+ :param run_id: The run ID to to store and retrieve memories from the memory store.
73
+ :param agent_id: The agent ID to to store and retrieve memories from the memory store.
74
+ If you want Mem0 to store chat messages from the assistant, you need to set the agent_id.
75
+ :param async_mode: Whether to add memories asynchronously.
76
+ If True, the method will return immediately and the memories will be added in the background.
77
+ :param kwargs: Additional keyword arguments to pass to the Mem0 client.add method.
78
+ Note: ChatMessage.meta in the list of messages will be ignored because Mem0 doesn't allow
79
+ passing metadata for each message in the list. You can pass metadata for the whole memory
80
+ by passing the `metadata` keyword argument to the method.
81
+ :returns: List of objects with the memory_id and the memory
82
+ """
83
+ added_ids = []
84
+ ids = self._get_ids(user_id, run_id, agent_id)
85
+ instructions = """
86
+ Store all memories from the user and suggestions from the assistant.
87
+ """
88
+
89
+ self.client.project.update(custom_instructions=instructions)
90
+ mem0_messages = []
91
+ for message in messages:
92
+ if not message.text:
93
+ continue
94
+ # we save the role of the message in the metadata
95
+ mem0_messages.append({"content": message.text, "role": message.role.value})
96
+ try:
97
+ status = self.client.add(messages=mem0_messages, infer=infer, **ids, async_mode=async_mode, **kwargs)
98
+ if status:
99
+ for result in status["results"]:
100
+ memory_id = {"memory_id": result.get("id"), "memory": result["memory"]}
101
+ added_ids.append(memory_id)
102
+ except Exception as e:
103
+ raise RuntimeError(f"Failed to add memory message: {e}") from e
104
+ return added_ids
105
+
106
+ def search_memories(
107
+ self,
108
+ *,
109
+ query: str | None = None,
110
+ filters: dict[str, Any] | None = None,
111
+ top_k: int = 5,
112
+ user_id: str | None = None,
113
+ run_id: str | None = None,
114
+ agent_id: str | None = None,
115
+ include_memory_metadata: bool = False,
116
+ **kwargs: Any,
117
+ ) -> list[ChatMessage]:
118
+ """
119
+ Search for memories in Mem0.
120
+
121
+ If filters are not provided, at least one of user_id, run_id, or agent_id must be set.
122
+ If filters are provided, the search will be scoped to the provided filters and the other ids will be ignored.
123
+ :param query: Text query to search for. If not provided, all memories will be returned.
124
+ :param filters: Haystack filters to apply on search. For more details on Haystack filters, see https://docs.haystack.deepset.ai/docs/metadata-filtering
125
+ :param top_k: Maximum number of results to return
126
+ :param user_id: The user ID to to store and retrieve memories from the memory store.
127
+ :param run_id: The run ID to to store and retrieve memories from the memory store.
128
+ :param agent_id: The agent ID to to store and retrieve memories from the memory store.
129
+ If you want Mem0 to store chat messages from the assistant, you need to set the agent_id.
130
+ :param include_memory_metadata: Whether to include the mem0 related metadata for the
131
+ retrieved memory in the ChatMessage.
132
+ If True, the metadata will include the mem0 related metadata i.e. memory_id, score, etc.
133
+ in the `mem0_memory_metadata` key.
134
+ If False, the `ChatMessage.meta` will only contain the user defined metadata.
135
+ :param kwargs: Additional keyword arguments to pass to the Mem0 client.
136
+ If query is passed, the kwargs will be passed to the Mem0 client.search method.
137
+ If query is not passed, the kwargs will be passed to the Mem0 client.get_all method.
138
+ :returns: List of ChatMessage memories matching the criteria
139
+ """
140
+ # Prepare filters for Mem0
141
+
142
+ if filters:
143
+ mem0_filters = self.normalize_filters(filters)
144
+ else:
145
+ ids = self._get_ids(user_id, run_id, agent_id)
146
+ if len(ids) == 1:
147
+ mem0_filters = dict(ids)
148
+ else:
149
+ mem0_filters = {"AND": [{key: value} for key, value in ids.items()]}
150
+ try:
151
+ if not query:
152
+ memories = self.client.get_all(filters=mem0_filters, **kwargs)
153
+ else:
154
+ memories = self.client.search(
155
+ query=query,
156
+ top_k=top_k,
157
+ filters=mem0_filters,
158
+ **kwargs,
159
+ )
160
+ messages = []
161
+ for memory in memories["results"]:
162
+ meta = memory["metadata"].copy() if memory["metadata"] else {}
163
+ # we also include the mem0 related metadata i.e. memory_id, score, etc.
164
+ if include_memory_metadata:
165
+ meta["retrieved_memory_metadata"] = memory.copy()
166
+ meta["retrieved_memory_metadata"].pop("memory")
167
+ messages.append(ChatMessage.from_system(text=memory["memory"], meta=meta))
168
+ return messages
169
+
170
+ except Exception as e:
171
+ raise RuntimeError(f"Failed to search memories: {e}") from e
172
+
173
+ def search_memories_as_single_message(
174
+ self,
175
+ *,
176
+ query: str | None = None,
177
+ filters: dict[str, Any] | None = None,
178
+ top_k: int = 5,
179
+ user_id: str | None = None,
180
+ run_id: str | None = None,
181
+ agent_id: str | None = None,
182
+ **kwargs: Any,
183
+ ) -> ChatMessage:
184
+ """
185
+ Search for memories in Mem0 and return a single ChatMessage object.
186
+
187
+ If filters are not provided, at least one of user_id, run_id, or agent_id must be set.
188
+ If filters are provided, the search will be scoped to the provided filters and the other ids will be ignored.
189
+ :param query: Text query to search for. If not provided, all memories will be returned.
190
+ :param filters: Additional filters to apply on search. For more details on mem0 filters, see https://mem0.ai/docs/search/
191
+ :param top_k: Maximum number of results to return
192
+ :param user_id: The user ID to to store and retrieve memories from the memory store.
193
+ :param run_id: The run ID to to store and retrieve memories from the memory store.
194
+ :param agent_id: The agent ID to to store and retrieve memories from the memory store.
195
+ If you want Mem0 to store chat messages from the assistant, you need to set the agent_id.
196
+ :param kwargs: Additional keyword arguments to pass to the Mem0 client.
197
+ If query is passed, the kwargs will be passed to the Mem0 client.search method.
198
+ If query is not passed, the kwargs will be passed to the Mem0 client.get_all method.
199
+ :returns: A single ChatMessage object with the memories matching the criteria
200
+ """
201
+ # Prepare filters for Mem0
202
+ if filters:
203
+ mem0_filters = self.normalize_filters(filters)
204
+ else:
205
+ ids = self._get_ids(user_id, run_id, agent_id)
206
+ if len(ids) == 1:
207
+ mem0_filters = dict(ids)
208
+ else:
209
+ mem0_filters = {"AND": [{key: value} for key, value in ids.items()]}
210
+
211
+ try:
212
+ if not query:
213
+ memories = self.client.get_all(filters=mem0_filters, **kwargs)
214
+ else:
215
+ memories = self.client.search(
216
+ query=query,
217
+ top_k=top_k,
218
+ filters=mem0_filters,
219
+ **kwargs,
220
+ )
221
+
222
+ # we combine the memories into a single string
223
+ combined_memory = "\n".join(
224
+ f"- MEMORY #{idx + 1}: {memory['memory']}" for idx, memory in enumerate(memories["results"])
225
+ )
226
+
227
+ return ChatMessage.from_system(text=combined_memory)
228
+
229
+ except Exception as e:
230
+ raise RuntimeError(f"Failed to search memories: {e}") from e
231
+
232
+ def delete_all_memories(
233
+ self,
234
+ *,
235
+ user_id: str | None = None,
236
+ run_id: str | None = None,
237
+ agent_id: str | None = None,
238
+ **kwargs: Any,
239
+ ) -> None:
240
+ """
241
+ Delete memory records from Mem0.
242
+
243
+ At least one of user_id, run_id, or agent_id must be set.
244
+ :param user_id: The user ID to delete memories from.
245
+ :param run_id: The run ID to delete memories from.
246
+ :param agent_id: The agent ID to delete memories from.
247
+ :param kwargs: Additional keyword arguments to pass to the Mem0 client.delete_all method.
248
+ """
249
+ ids = self._get_ids(user_id, run_id, agent_id)
250
+
251
+ try:
252
+ self.client.delete_all(**ids, **kwargs)
253
+ logger.info("All memories deleted successfully for scope {ids}", ids=ids)
254
+ except Exception as e:
255
+ raise RuntimeError(f"Failed to delete memories with scope {ids}: {e}") from e
256
+
257
+ def delete_memory(self, memory_id: str, **kwargs: Any) -> None:
258
+ """
259
+ Delete memory from Mem0.
260
+
261
+ :param memory_id: The ID of the memory to delete.
262
+ :param kwargs: Additional keyword arguments to pass to the Mem0 client.delete method.
263
+ """
264
+ try:
265
+ self.client.delete(memory_id=memory_id, **kwargs)
266
+ logger.info("Memory deleted successfully for memory_id {memory_id}", memory_id=memory_id)
267
+ except Exception as e:
268
+ raise RuntimeError(f"Failed to delete memory {memory_id}: {e}") from e
269
+
270
+ def _get_ids(
271
+ self, user_id: str | None = None, run_id: str | None = None, agent_id: str | None = None
272
+ ) -> dict[str, Any]:
273
+ """
274
+ Check that at least one of the ids is set.
275
+
276
+ Return the set ids as a dictionary.
277
+ """
278
+ if not user_id and not run_id and not agent_id:
279
+ raise ValueError("At least one of user_id, run_id, or agent_id must be set")
280
+ ids = {
281
+ "user_id": user_id,
282
+ "run_id": run_id,
283
+ "agent_id": agent_id,
284
+ }
285
+ return {key: value for key, value in ids.items() if value is not None}
286
+
287
+ @staticmethod
288
+ def normalize_filters(filters: dict[str, Any]) -> dict[str, Any]:
289
+ """
290
+ Convert Haystack filters to Mem0 filters.
291
+ """
292
+
293
+ def convert_comparison(condition: dict[str, Any]) -> dict[str, Any]:
294
+ operator_map = {
295
+ "==": lambda field, value: {field: value},
296
+ "!=": lambda field, value: {field: {"ne": value}},
297
+ ">": lambda field, value: {field: {"gt": value}},
298
+ ">=": lambda field, value: {field: {"gte": value}},
299
+ "<": lambda field, value: {field: {"lt": value}},
300
+ "<=": lambda field, value: {field: {"lte": value}},
301
+ "in": lambda field, value: {field: {"in": value if isinstance(value, list) else [value]}},
302
+ "not in": lambda field, value: {field: {"ne": value}},
303
+ }
304
+ field = condition["field"]
305
+ value = condition["value"]
306
+ operator = condition["operator"]
307
+ if operator not in operator_map:
308
+ raise ValueError(f"Unsupported operator {operator}")
309
+ return operator_map[operator](field, value)
310
+
311
+ def convert_logic(node: dict[str, Any]) -> dict[str, Any]:
312
+ operator = node["operator"].upper()
313
+ if operator not in ("AND", "OR", "NOT"):
314
+ raise ValueError(f"Unsupported logic operator {operator}")
315
+ mem0_conditions = [convert_node(cond) for cond in node["conditions"]]
316
+ return {operator: mem0_conditions}
317
+
318
+ def convert_node(node: dict[str, Any]) -> dict[str, Any]:
319
+ if "field" in node:
320
+ return convert_comparison(node)
321
+ return convert_logic(node)
322
+
323
+ return convert_node(filters)
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from .protocol import MemoryStore
6
+
7
+ __all__ = ["MemoryStore"]
@@ -0,0 +1,94 @@
1
+ # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from typing import Any, Protocol
6
+
7
+ from haystack.dataclasses import ChatMessage
8
+
9
+ # Ellipsis are needed for the type checker, it's safe to disable module-wide
10
+ # pylint: disable=unnecessary-ellipsis
11
+
12
+
13
+ class MemoryStore(Protocol):
14
+ """
15
+ Stores ChatMessage-based memories to be used by agents and components.
16
+
17
+ Implementations typically persist user- and agent-specific memories and
18
+ support adding, searching, and deleting memories.
19
+ """
20
+
21
+ def to_dict(self) -> dict[str, Any]:
22
+ """
23
+ Serializes this memory store to a dictionary.
24
+ """
25
+ ...
26
+
27
+ @classmethod
28
+ def from_dict(cls, data: dict[str, Any]) -> "MemoryStore":
29
+ """
30
+ Deserializes the memory store from a dictionary.
31
+ """
32
+ ...
33
+
34
+ def add_memories(
35
+ self,
36
+ *,
37
+ messages: list[ChatMessage],
38
+ user_id: str | None = None,
39
+ **kwargs: Any,
40
+ ) -> None:
41
+ """
42
+ Add ChatMessage memories to the store.
43
+
44
+ :param messages: List of ChatMessage objects with memory metadata.
45
+ :param user_id: User ID to scope memories.
46
+ :param kwargs: Additional keyword arguments to pass to the add method.
47
+ """
48
+ ...
49
+
50
+ def search_memories(
51
+ self,
52
+ *,
53
+ query: str | None = None,
54
+ filters: dict[str, Any] | None = None,
55
+ top_k: int = 5,
56
+ user_id: str | None = None,
57
+ **kwargs: Any,
58
+ ) -> list[ChatMessage]:
59
+ """
60
+ Search for memories in the store.
61
+
62
+ :param query: Text query to search for. If not provided, all memories may be returned.
63
+ :param filters: Haystack filters to apply on search.
64
+ :param top_k: Maximum number of results to return.
65
+ :param user_id: User ID to scope memories.
66
+ :param kwargs: Additional keyword arguments to pass to the search method.
67
+
68
+ :returns: List of ChatMessage memories matching the criteria.
69
+ """
70
+ ...
71
+
72
+ def delete_all_memories(
73
+ self,
74
+ *,
75
+ user_id: str | None = None,
76
+ **kwargs: Any,
77
+ ) -> None:
78
+ """
79
+ Delete all memories in the given scope.
80
+
81
+ In case of multiple optional ids, one of the ids must be set.
82
+ :param user_id: User ID to delete memories.
83
+ :param kwargs: Additional keyword arguments to pass to the delete method.
84
+ """
85
+ ...
86
+
87
+ def delete_memory(self, memory_id: str, **kwargs: Any) -> None:
88
+ """
89
+ Delete a single memory by its ID.
90
+
91
+ :param memory_id: The ID of the memory to delete.
92
+ :param kwargs: Additional keyword arguments to pass to the delete method.
93
+ """
94
+ ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haystack-experimental
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Summary: Experimental components and features for the Haystack LLM framework.
5
5
  Project-URL: CI: GitHub, https://github.com/deepset-ai/haystack-experimental/actions
6
6
  Project-URL: GitHub: issues, https://github.com/deepset-ai/haystack-experimental/issues
@@ -77,6 +77,7 @@ that includes it. Once it reaches the end of its lifespan, the experiment will b
77
77
  | [`Agent`][17]; [Confirmation Policies][18]; [ConfirmationUIs][19]; [ConfirmationStrategies][20]; [`ConfirmationUIResult` and `ToolExecutionDecision`][21] [HITLBreakpointException][22] | Human in the Loop | December 2025 | rich | None | [Discuss][23] |
78
78
  | [`LLMSummarizer`][24] | Document Summarizer | January 2025 | None | None | [Discuss][25] |
79
79
  | [`InMemoryChatMessageStore`][1]; [`ChatMessageRetriever`][2]; [`ChatMessageWriter`][3] | Chat Message Store, Retriever, Writer | February 2025 | None | <a href="https://colab.research.google.com/github/deepset-ai/haystack-cookbook/blob/main/notebooks/conversational_rag_using_memory.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/> | [Discuss][4] |
80
+ | [`Mem0MemoryStore`][26] | MemoryStore | February 2025 | mem0ai | None | -- |
80
81
 
81
82
  [1]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/chat_message_stores/in_memory.py
82
83
  [2]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/retrievers/chat_message_retriever.py
@@ -95,6 +96,7 @@ that includes it. Once it reaches the end of its lifespan, the experiment will b
95
96
  [23]: https://github.com/deepset-ai/haystack-experimental/discussions/381
96
97
  [24]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/sumarizers/llm_summarizer.py
97
98
  [25]: https://github.com/deepset-ai/haystack-experimental/discussions/382
99
+ [26]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/memory_stores/mem0/memory_store.py
98
100
 
99
101
  ### Adopted experiments
100
102
  | Name | Type | Final release |
@@ -4,7 +4,7 @@ haystack_experimental/chat_message_stores/in_memory.py,sha256=Dw5N9l8qk-RONEVVJb
4
4
  haystack_experimental/chat_message_stores/types.py,sha256=BHpk36OesvAxMgu6iOOYt4gmqw_cIE4nFDVgiutCguA,2726
5
5
  haystack_experimental/components/__init__.py,sha256=eHD7xrty2PCky_gG3ty19rpM4WfV32TyytM7gJODwl4,110
6
6
  haystack_experimental/components/agents/__init__.py,sha256=Sxu9LxPpQ5cljgoTgUeNC0GY8CwUdiSy1JWkd_-RRJ4,414
7
- haystack_experimental/components/agents/agent.py,sha256=n76MmXq_P0TAuFxHwNe5FC1iBAIjKbs8xOoZioyaypY,45651
7
+ haystack_experimental/components/agents/agent.py,sha256=R0QUvQsLSuA-t90dASNdOE_cAwX_JJJamjU5RpQLcbs,50686
8
8
  haystack_experimental/components/agents/human_in_the_loop/__init__.py,sha256=xLr1G9pNWMmCpKN9mbv6yqeFfwMcbZyaVfCkzlwMxhY,1674
9
9
  haystack_experimental/components/agents/human_in_the_loop/breakpoint.py,sha256=K5rU61821uXjXkcXuXkN3p1_9Boq7FhJK1GOcAz7C0g,2939
10
10
  haystack_experimental/components/agents/human_in_the_loop/dataclasses.py,sha256=qQG4yRlzzcNeBlWagnZpjeJKZ2v_n2_9V9W0ZvsgBn4,2538
@@ -34,14 +34,19 @@ haystack_experimental/core/pipeline/__init__.py,sha256=eHD7xrty2PCky_gG3ty19rpM4
34
34
  haystack_experimental/core/pipeline/breakpoint.py,sha256=NcagwEJupIZ_Mp20YLmJyeqTZe8qwo6LfuB7OTXnlXk,5214
35
35
  haystack_experimental/dataclasses/__init__.py,sha256=eHD7xrty2PCky_gG3ty19rpM4WfV32TyytM7gJODwl4,110
36
36
  haystack_experimental/dataclasses/breakpoints.py,sha256=JevVOYiUMAsdg0QmSGiF_6aPveO83q47U-v-8Vd4nd0,2082
37
+ haystack_experimental/memory_stores/__init__.py,sha256=xkSdV7qQ_e1kNO4PrXW37CIJMGavYiwbPtsfeSHQaQ4,169
38
+ haystack_experimental/memory_stores/mem0/__init__.py,sha256=pntAgiFC6jXvzyHUQUNPtyChlys69jLa6rTVbClTb9I,458
39
+ haystack_experimental/memory_stores/mem0/memory_store.py,sha256=PkDMVRWXooorTpHtnMRHZLgsxokJR8DlnV7vgedhPyk,14148
40
+ haystack_experimental/memory_stores/types/__init__.py,sha256=7Sgg1w9bSGXDQkFXjmB4G4PGCBW7hDIYXItad7UxW4M,172
41
+ haystack_experimental/memory_stores/types/protocol.py,sha256=LG0oK5jAJiVivtzQV7GacyikCDuZZQRQtPtkoJLCDj8,2761
37
42
  haystack_experimental/utils/__init__.py,sha256=eHD7xrty2PCky_gG3ty19rpM4WfV32TyytM7gJODwl4,110
38
43
  haystack_experimental/utils/hallucination_risk_calculator/__init__.py,sha256=kCd-qceud_T8P1XJHgRMaOnljyDjfFQ5UIdxEb5t6V0,219
39
44
  haystack_experimental/utils/hallucination_risk_calculator/core_math.py,sha256=8XIa2gX1B7U400KutPgxfIUHrOggkBPAm9gIkwhF7UM,4079
40
45
  haystack_experimental/utils/hallucination_risk_calculator/dataclasses.py,sha256=8HQL-ktwoog0WZlFz3-NQhhmVMjzMIrRjRoYuYXmorE,2284
41
46
  haystack_experimental/utils/hallucination_risk_calculator/openai_planner.py,sha256=Vt0icGcrPtTRywh9L2YfwB62a7pDynrm5-qlKaLHLsA,11381
42
47
  haystack_experimental/utils/hallucination_risk_calculator/skeletonization.py,sha256=KYdBDw5LcRtw8cmKW4aNGOKh3YrA17CPmcRE-FG6kSA,5466
43
- haystack_experimental-0.16.0.dist-info/METADATA,sha256=C6Dzv_tSvCuvTNMBEgfvdX9nMiZ026jbgNU1AvzXx1w,17078
44
- haystack_experimental-0.16.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
45
- haystack_experimental-0.16.0.dist-info/licenses/LICENSE,sha256=93_5nS97uHxptHvK9E8BZgKxLGeIS-rBWT2swIv-X5Y,11368
46
- haystack_experimental-0.16.0.dist-info/licenses/LICENSE-MIT.txt,sha256=knmLkIKj_6tTrTSVRg9Tq88Kww4UCPLt2I1RGXJv9sQ,1037
47
- haystack_experimental-0.16.0.dist-info/RECORD,,
48
+ haystack_experimental-0.17.0.dist-info/METADATA,sha256=VSHcnCORUhN3N_yPhKH8bCM7WGrXLeDx5BJpVyw_7wI,17721
49
+ haystack_experimental-0.17.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
50
+ haystack_experimental-0.17.0.dist-info/licenses/LICENSE,sha256=93_5nS97uHxptHvK9E8BZgKxLGeIS-rBWT2swIv-X5Y,11368
51
+ haystack_experimental-0.17.0.dist-info/licenses/LICENSE-MIT.txt,sha256=knmLkIKj_6tTrTSVRg9Tq88Kww4UCPLt2I1RGXJv9sQ,1037
52
+ haystack_experimental-0.17.0.dist-info/RECORD,,