haystack-experimental 0.14.2__py3-none-any.whl → 0.15.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.
Files changed (27) hide show
  1. haystack_experimental/chat_message_stores/__init__.py +1 -1
  2. haystack_experimental/chat_message_stores/in_memory.py +176 -31
  3. haystack_experimental/chat_message_stores/types.py +33 -21
  4. haystack_experimental/components/agents/agent.py +184 -35
  5. haystack_experimental/components/agents/human_in_the_loop/strategies.py +220 -3
  6. haystack_experimental/components/agents/human_in_the_loop/types.py +36 -1
  7. haystack_experimental/components/embedders/types/protocol.py +2 -2
  8. haystack_experimental/components/preprocessors/__init__.py +2 -0
  9. haystack_experimental/components/preprocessors/embedding_based_document_splitter.py +16 -16
  10. haystack_experimental/components/preprocessors/md_header_level_inferrer.py +2 -2
  11. haystack_experimental/components/retrievers/__init__.py +1 -3
  12. haystack_experimental/components/retrievers/chat_message_retriever.py +57 -26
  13. haystack_experimental/components/writers/__init__.py +1 -1
  14. haystack_experimental/components/writers/chat_message_writer.py +25 -22
  15. haystack_experimental/core/pipeline/breakpoint.py +5 -3
  16. {haystack_experimental-0.14.2.dist-info → haystack_experimental-0.15.0.dist-info}/METADATA +24 -31
  17. {haystack_experimental-0.14.2.dist-info → haystack_experimental-0.15.0.dist-info}/RECORD +20 -27
  18. {haystack_experimental-0.14.2.dist-info → haystack_experimental-0.15.0.dist-info}/WHEEL +1 -1
  19. haystack_experimental/components/query/__init__.py +0 -18
  20. haystack_experimental/components/query/query_expander.py +0 -299
  21. haystack_experimental/components/retrievers/multi_query_embedding_retriever.py +0 -180
  22. haystack_experimental/components/retrievers/multi_query_text_retriever.py +0 -158
  23. haystack_experimental/super_components/__init__.py +0 -3
  24. haystack_experimental/super_components/indexers/__init__.py +0 -11
  25. haystack_experimental/super_components/indexers/sentence_transformers_document_indexer.py +0 -199
  26. {haystack_experimental-0.14.2.dist-info → haystack_experimental-0.15.0.dist-info}/licenses/LICENSE +0 -0
  27. {haystack_experimental-0.14.2.dist-info → haystack_experimental-0.15.0.dist-info}/licenses/LICENSE-MIT.txt +0 -0
@@ -22,11 +22,11 @@ import haystack_experimental.core.pipeline.breakpoint as exp_breakpoint
22
22
  hs_breakpoint._create_agent_snapshot = exp_breakpoint._create_agent_snapshot
23
23
  hs_breakpoint._create_pipeline_snapshot_from_tool_invoker = exp_breakpoint._create_pipeline_snapshot_from_tool_invoker # type: ignore[assignment]
24
24
 
25
- from haystack import logging
25
+ from haystack import DeserializationError, logging
26
26
  from haystack.components.agents.agent import Agent as HaystackAgent
27
27
  from haystack.components.agents.agent import _ExecutionContext as Haystack_ExecutionContext
28
28
  from haystack.components.agents.agent import _schema_from_dict
29
- from haystack.components.agents.state import replace_values
29
+ from haystack.components.agents.state import replace_values, State
30
30
  from haystack.components.generators.chat.types import ChatGenerator
31
31
  from haystack.core.errors import PipelineRuntimeError
32
32
  from haystack.core.pipeline import AsyncPipeline, Pipeline
@@ -36,19 +36,25 @@ from haystack.core.pipeline.breakpoint import (
36
36
  )
37
37
  from haystack.core.pipeline.utils import _deepcopy_with_exceptions
38
38
  from haystack.core.serialization import default_from_dict, import_class_by_name
39
- from haystack.dataclasses import ChatMessage
39
+ from haystack.dataclasses import ChatMessage, ChatRole
40
40
  from haystack.dataclasses.breakpoints import AgentBreakpoint, ToolBreakpoint
41
- from haystack.dataclasses.streaming_chunk import StreamingCallbackT
41
+ from haystack.dataclasses.streaming_chunk import StreamingCallbackT, select_streaming_callback
42
42
  from haystack.tools import ToolsType, deserialize_tools_or_toolset_inplace
43
43
  from haystack.utils.callable_serialization import deserialize_callable
44
44
  from haystack.utils.deserialization import deserialize_chatgenerator_inplace
45
45
 
46
+ from haystack_experimental.chat_message_stores.types import ChatMessageStore
46
47
  from haystack_experimental.components.agents.human_in_the_loop import (
47
48
  ConfirmationStrategy,
48
49
  ToolExecutionDecision,
49
50
  HITLBreakpointException,
50
51
  )
51
- from haystack_experimental.components.agents.human_in_the_loop.strategies import _process_confirmation_strategies
52
+ from haystack_experimental.components.agents.human_in_the_loop.strategies import (
53
+ _process_confirmation_strategies,
54
+ _process_confirmation_strategies_async,
55
+ )
56
+ from haystack_experimental.components.retrievers import ChatMessageRetriever
57
+ from haystack_experimental.components.writers import ChatMessageWriter
52
58
 
53
59
  logger = logging.getLogger(__name__)
54
60
 
@@ -62,9 +68,15 @@ class _ExecutionContext(Haystack_ExecutionContext):
62
68
 
63
69
  :param tool_execution_decisions: Optional list of ToolExecutionDecision objects to use instead of prompting
64
70
  the user. This is useful when restarting from a snapshot where tool execution decisions were already made.
71
+ :param confirmation_strategy_context: Optional dictionary for passing request-scoped resources
72
+ to confirmation strategies. In web/server environments, this enables passing per-request
73
+ objects (e.g., WebSocket connections, async queues, or pub/sub clients) that strategies can use for
74
+ non-blocking user interaction. This is passed directly to strategies via the `confirmation_strategy_context`
75
+ parameter in their `run()` and `run_async()` methods.
65
76
  """
66
77
 
67
78
  tool_execution_decisions: Optional[list[ToolExecutionDecision]] = None
79
+ confirmation_strategy_context: Optional[dict[str, Any]] = None
68
80
 
69
81
 
70
82
  class Agent(HaystackAgent):
@@ -131,6 +143,7 @@ class Agent(HaystackAgent):
131
143
  raise_on_tool_invocation_failure: bool = False,
132
144
  confirmation_strategies: Optional[dict[str, ConfirmationStrategy]] = None,
133
145
  tool_invoker_kwargs: Optional[dict[str, Any]] = None,
146
+ chat_message_store: Optional[ChatMessageStore] = None,
134
147
  ) -> None:
135
148
  """
136
149
  Initialize the agent component.
@@ -164,6 +177,13 @@ class Agent(HaystackAgent):
164
177
  tool_invoker_kwargs=tool_invoker_kwargs,
165
178
  )
166
179
  self._confirmation_strategies = confirmation_strategies or {}
180
+ self._chat_message_store = chat_message_store
181
+ self._chat_message_retriever = (
182
+ ChatMessageRetriever(chat_message_store=chat_message_store) if chat_message_store else None
183
+ )
184
+ self._chat_message_writer = (
185
+ ChatMessageWriter(chat_message_store=chat_message_store) if chat_message_store else None
186
+ )
167
187
 
168
188
  def _initialize_fresh_execution(
169
189
  self,
@@ -172,7 +192,10 @@ class Agent(HaystackAgent):
172
192
  requires_async: bool,
173
193
  *,
174
194
  system_prompt: Optional[str] = None,
195
+ generation_kwargs: Optional[dict[str, Any]] = None,
175
196
  tools: Optional[Union[ToolsType, list[str]]] = None,
197
+ confirmation_strategy_context: Optional[dict[str, Any]] = None,
198
+ chat_message_store_kwargs: Optional[dict[str, Any]] = None,
176
199
  **kwargs: dict[str, Any],
177
200
  ) -> _ExecutionContext:
178
201
  """
@@ -184,27 +207,56 @@ class Agent(HaystackAgent):
184
207
  :param system_prompt: System prompt for the agent. If provided, it overrides the default system prompt.
185
208
  :param tools: Optional list of Tool objects, a Toolset, or list of tool names to use for this run.
186
209
  When passing tool names, tools are selected from the Agent's originally configured tools.
210
+ :param confirmation_strategy_context: Optional dictionary for passing request-scoped resources
211
+ to confirmation strategies.
212
+ :param chat_message_store_kwargs: Optional dictionary of keyword arguments to pass to the ChatMessageStore.
187
213
  :param kwargs: Additional data to pass to the State used by the Agent.
188
214
  """
189
- exe_context = super(Agent, self)._initialize_fresh_execution(
190
- messages=messages,
191
- streaming_callback=streaming_callback,
192
- requires_async=requires_async,
193
- system_prompt=system_prompt,
194
- tools=tools,
195
- **kwargs,
215
+ system_prompt = system_prompt or self.system_prompt
216
+ if system_prompt is not None:
217
+ messages = [ChatMessage.from_system(system_prompt)] + messages
218
+
219
+ # NOTE: difference with parent method to add chat message retrieval
220
+ if self._chat_message_retriever:
221
+ retriever_kwargs = _select_kwargs(self._chat_message_retriever, chat_message_store_kwargs or {})
222
+ if "chat_history_id" in retriever_kwargs:
223
+ messages = self._chat_message_retriever.run(
224
+ current_messages=messages,
225
+ **retriever_kwargs,
226
+ )["messages"]
227
+
228
+ if all(m.is_from(ChatRole.SYSTEM) for m in messages):
229
+ logger.warning("All messages provided to the Agent component are system messages. This is not recommended.")
230
+
231
+ state = State(schema=self.state_schema, data=kwargs)
232
+ state.set("messages", messages)
233
+
234
+ streaming_callback = select_streaming_callback( # type: ignore[call-overload]
235
+ init_callback=self.streaming_callback, runtime_callback=streaming_callback, requires_async=requires_async
196
236
  )
197
- # NOTE: 1st difference with parent method to add this to tool_invoker_inputs
237
+
238
+ selected_tools = self._select_tools(tools)
239
+ tool_invoker_inputs: dict[str, Any] = {"tools": selected_tools}
240
+ generator_inputs: dict[str, Any] = {"tools": selected_tools}
241
+ if streaming_callback is not None:
242
+ tool_invoker_inputs["streaming_callback"] = streaming_callback
243
+ generator_inputs["streaming_callback"] = streaming_callback
244
+ if generation_kwargs is not None:
245
+ generator_inputs["generation_kwargs"] = generation_kwargs
246
+
247
+ # NOTE: difference with parent method to add this to tool_invoker_inputs
198
248
  if self._tool_invoker:
199
- exe_context.tool_invoker_inputs["enable_streaming_callback_passthrough"] = (
249
+ tool_invoker_inputs["enable_streaming_callback_passthrough"] = (
200
250
  self._tool_invoker.enable_streaming_callback_passthrough
201
251
  )
202
- # NOTE: 2nd difference is to use the extended _ExecutionContext
252
+
253
+ # NOTE: difference is to use the extended _ExecutionContext with confirmation_strategy_context
203
254
  return _ExecutionContext(
204
- state=exe_context.state,
205
- component_visits=exe_context.component_visits,
206
- chat_generator_inputs=exe_context.chat_generator_inputs,
207
- tool_invoker_inputs=exe_context.tool_invoker_inputs,
255
+ state=state,
256
+ component_visits=dict.fromkeys(["chat_generator", "tool_invoker"], 0),
257
+ chat_generator_inputs=generator_inputs,
258
+ tool_invoker_inputs=tool_invoker_inputs,
259
+ confirmation_strategy_context=confirmation_strategy_context,
208
260
  )
209
261
 
210
262
  def _initialize_from_snapshot( # type: ignore[override]
@@ -213,7 +265,9 @@ class Agent(HaystackAgent):
213
265
  streaming_callback: Optional[StreamingCallbackT],
214
266
  requires_async: bool,
215
267
  *,
268
+ generation_kwargs: Optional[dict[str, Any]] = None,
216
269
  tools: Optional[Union[ToolsType, list[str]]] = None,
270
+ confirmation_strategy_context: Optional[dict[str, Any]] = None,
217
271
  ) -> _ExecutionContext:
218
272
  """
219
273
  Initialize execution context from an AgentSnapshot.
@@ -221,18 +275,35 @@ class Agent(HaystackAgent):
221
275
  :param snapshot: An AgentSnapshot containing the state of a previously saved agent execution.
222
276
  :param streaming_callback: Optional callback for streaming responses.
223
277
  :param requires_async: Whether the agent run requires asynchronous execution.
278
+ :param generation_kwargs: Additional keyword arguments for chat generator. These parameters will
279
+ override the parameters passed during component initialization.
224
280
  :param tools: Optional list of Tool objects, a Toolset, or list of tool names to use for this run.
225
281
  When passing tool names, tools are selected from the Agent's originally configured tools.
282
+ :param confirmation_strategy_context: Optional dictionary for passing request-scoped resources
283
+ to confirmation strategies.
226
284
  """
227
- exe_context = super(Agent, self)._initialize_from_snapshot(
228
- snapshot=snapshot, streaming_callback=streaming_callback, requires_async=requires_async, tools=tools
229
- )
285
+ # The PR https://github.com/deepset-ai/haystack/pull/9616 added the generation_kwargs parameter to
286
+ # _initialize_from_snapshot. This change has been released in Haystack 2.20.0.
287
+ # To maintain compatibility with Haystack 2.19 we check the number of parameters and call accordingly.
288
+ if inspect.signature(super(Agent, self)._initialize_from_snapshot).parameters.get("generation_kwargs"):
289
+ exe_context = super(Agent, self)._initialize_from_snapshot( # type: ignore[call-arg]
290
+ snapshot=snapshot,
291
+ streaming_callback=streaming_callback,
292
+ requires_async=requires_async,
293
+ generation_kwargs=generation_kwargs,
294
+ tools=tools,
295
+ )
296
+ else:
297
+ exe_context = super(Agent, self)._initialize_from_snapshot(
298
+ snapshot=snapshot, streaming_callback=streaming_callback, requires_async=requires_async, tools=tools
299
+ )
230
300
  # NOTE: 1st difference with parent method to add this to tool_invoker_inputs
231
301
  if self._tool_invoker:
232
302
  exe_context.tool_invoker_inputs["enable_streaming_callback_passthrough"] = (
233
303
  self._tool_invoker.enable_streaming_callback_passthrough
234
304
  )
235
- # NOTE: 2nd difference is to use the extended _ExecutionContext and add tool_execution_decisions
305
+ # NOTE: 2nd difference is to use the extended _ExecutionContext
306
+ # and add tool_execution_decisions + confirmation_strategy_context
236
307
  return _ExecutionContext(
237
308
  state=exe_context.state,
238
309
  component_visits=exe_context.component_visits,
@@ -241,17 +312,21 @@ class Agent(HaystackAgent):
241
312
  counter=exe_context.counter,
242
313
  skip_chat_generator=exe_context.skip_chat_generator,
243
314
  tool_execution_decisions=snapshot.tool_execution_decisions,
315
+ confirmation_strategy_context=confirmation_strategy_context,
244
316
  )
245
317
 
246
- def run( # noqa: PLR0915
318
+ def run( # type: ignore[override] # noqa: PLR0915
247
319
  self,
248
320
  messages: list[ChatMessage],
249
321
  streaming_callback: Optional[StreamingCallbackT] = None,
250
322
  *,
323
+ generation_kwargs: Optional[dict[str, Any]] = None,
251
324
  break_point: Optional[AgentBreakpoint] = None,
252
- snapshot: Optional[AgentSnapshot] = None, # type: ignore[override]
325
+ snapshot: Optional[AgentSnapshot] = None,
253
326
  system_prompt: Optional[str] = None,
254
327
  tools: Optional[Union[ToolsType, list[str]]] = None,
328
+ confirmation_strategy_context: Optional[dict[str, Any]] = None,
329
+ chat_message_store_kwargs: Optional[dict[str, Any]] = None,
255
330
  **kwargs: Any,
256
331
  ) -> dict[str, Any]:
257
332
  """
@@ -260,6 +335,8 @@ class Agent(HaystackAgent):
260
335
  :param messages: List of Haystack ChatMessage objects to process.
261
336
  :param streaming_callback: A callback that will be invoked when a response is streamed from the LLM.
262
337
  The same callback can be configured to emit tool results when a tool is called.
338
+ :param generation_kwargs: Additional keyword arguments for LLM. These parameters will
339
+ override the parameters passed during component initialization.
263
340
  :param break_point: An AgentBreakpoint, can be a Breakpoint for the "chat_generator" or a ToolBreakpoint
264
341
  for "tool_invoker".
265
342
  :param snapshot: A dictionary containing a snapshot of a previously saved agent execution. The snapshot contains
@@ -267,6 +344,12 @@ class Agent(HaystackAgent):
267
344
  :param system_prompt: System prompt for the agent. If provided, it overrides the default system prompt.
268
345
  :param tools: Optional list of Tool objects, a Toolset, or list of tool names to use for this run.
269
346
  When passing tool names, tools are selected from the Agent's originally configured tools.
347
+ :param confirmation_strategy_context: Optional dictionary for passing request-scoped resources
348
+ to confirmation strategies. Useful in web/server environments to provide per-request
349
+ objects (e.g., WebSocket connections, async queues, Redis pub/sub clients) that strategies
350
+ can use for non-blocking user interaction.
351
+ :param chat_message_store_kwargs: Optional dictionary of keyword arguments to pass to the ChatMessageStore.
352
+ For example, it can include the `chat_history_id` and `last_k` parameters for retrieving chat history.
270
353
  :param kwargs: Additional data to pass to the State schema used by the Agent.
271
354
  The keys must match the schema defined in the Agent's `state_schema`.
272
355
  :returns:
@@ -290,13 +373,18 @@ class Agent(HaystackAgent):
290
373
  # _runtime_checks. This change will be released in Haystack 2.20.0.
291
374
  # To maintain compatibility with Haystack 2.19 we check the number of parameters and call accordingly.
292
375
  if len(inspect.signature(self._runtime_checks).parameters) == 2:
293
- self._runtime_checks(break_point, snapshot)
376
+ self._runtime_checks(break_point, snapshot) # type: ignore[call-arg] # pylint: disable=too-many-function-args
294
377
  else:
295
378
  self._runtime_checks(break_point) # type: ignore[call-arg] # pylint: disable=no-value-for-parameter
296
379
 
297
380
  if snapshot:
298
381
  exe_context = self._initialize_from_snapshot(
299
- snapshot=snapshot, streaming_callback=streaming_callback, requires_async=False, tools=tools
382
+ snapshot=snapshot,
383
+ streaming_callback=streaming_callback,
384
+ requires_async=False,
385
+ generation_kwargs=generation_kwargs,
386
+ tools=tools,
387
+ confirmation_strategy_context=confirmation_strategy_context,
300
388
  )
301
389
  else:
302
390
  exe_context = self._initialize_fresh_execution(
@@ -304,7 +392,10 @@ class Agent(HaystackAgent):
304
392
  streaming_callback=streaming_callback,
305
393
  requires_async=False,
306
394
  system_prompt=system_prompt,
395
+ generation_kwargs=generation_kwargs,
307
396
  tools=tools,
397
+ confirmation_strategy_context=confirmation_strategy_context,
398
+ chat_message_store_kwargs=chat_message_store_kwargs,
308
399
  **kwargs,
309
400
  )
310
401
 
@@ -431,17 +522,27 @@ class Agent(HaystackAgent):
431
522
  result = {**exe_context.state.data}
432
523
  if msgs := result.get("messages"):
433
524
  result["last_message"] = msgs[-1]
525
+
526
+ # Write messages to ChatMessageStore if configured
527
+ if self._chat_message_writer:
528
+ writer_kwargs = _select_kwargs(self._chat_message_writer, chat_message_store_kwargs or {})
529
+ if "chat_history_id" in writer_kwargs:
530
+ self._chat_message_writer.run(messages=result["messages"], **writer_kwargs)
531
+
434
532
  return result
435
533
 
436
- async def run_async(
534
+ async def run_async( # type: ignore[override]
437
535
  self,
438
536
  messages: list[ChatMessage],
439
537
  streaming_callback: Optional[StreamingCallbackT] = None,
440
538
  *,
539
+ generation_kwargs: Optional[dict[str, Any]] = None,
441
540
  break_point: Optional[AgentBreakpoint] = None,
442
- snapshot: Optional[AgentSnapshot] = None, # type: ignore[override]
541
+ snapshot: Optional[AgentSnapshot] = None,
443
542
  system_prompt: Optional[str] = None,
444
543
  tools: Optional[Union[ToolsType, list[str]]] = None,
544
+ confirmation_strategy_context: Optional[dict[str, Any]] = None,
545
+ chat_message_store_kwargs: Optional[dict[str, Any]] = None,
445
546
  **kwargs: Any,
446
547
  ) -> dict[str, Any]:
447
548
  """
@@ -454,12 +555,20 @@ class Agent(HaystackAgent):
454
555
  :param messages: List of Haystack ChatMessage objects to process.
455
556
  :param streaming_callback: An asynchronous callback that will be invoked when a response is streamed from the
456
557
  LLM. The same callback can be configured to emit tool results when a tool is called.
558
+ :param generation_kwargs: Additional keyword arguments for LLM. These parameters will
559
+ override the parameters passed during component initialization.
457
560
  :param break_point: An AgentBreakpoint, can be a Breakpoint for the "chat_generator" or a ToolBreakpoint
458
561
  for "tool_invoker".
459
562
  :param snapshot: A dictionary containing a snapshot of a previously saved agent execution. The snapshot contains
460
563
  the relevant information to restart the Agent execution from where it left off.
461
564
  :param system_prompt: System prompt for the agent. If provided, it overrides the default system prompt.
462
565
  :param tools: Optional list of Tool objects, a Toolset, or list of tool names to use for this run.
566
+ :param confirmation_strategy_context: Optional dictionary for passing request-scoped resources
567
+ to confirmation strategies. Useful in web/server environments to provide per-request
568
+ objects (e.g., WebSocket connections, async queues, Redis pub/sub clients) that strategies
569
+ can use for non-blocking user interaction.
570
+ :param chat_message_store_kwargs: Optional dictionary of keyword arguments to pass to the ChatMessageStore.
571
+ For example, it can include the `chat_history_id` and `last_k` parameters for retrieving chat history.
463
572
  :param kwargs: Additional data to pass to the State schema used by the Agent.
464
573
  The keys must match the schema defined in the Agent's `state_schema`.
465
574
  :returns:
@@ -483,13 +592,18 @@ class Agent(HaystackAgent):
483
592
  # _runtime_checks. This change will be released in Haystack 2.20.0.
484
593
  # To maintain compatibility with Haystack 2.19 we check the number of parameters and call accordingly.
485
594
  if len(inspect.signature(self._runtime_checks).parameters) == 2:
486
- self._runtime_checks(break_point, snapshot)
595
+ self._runtime_checks(break_point, snapshot) # type: ignore[call-arg] # pylint: disable=too-many-function-args
487
596
  else:
488
597
  self._runtime_checks(break_point) # type: ignore[call-arg] # pylint: disable=no-value-for-parameter
489
598
 
490
599
  if snapshot:
491
600
  exe_context = self._initialize_from_snapshot(
492
- snapshot=snapshot, streaming_callback=streaming_callback, requires_async=True, tools=tools
601
+ snapshot=snapshot,
602
+ streaming_callback=streaming_callback,
603
+ requires_async=True,
604
+ generation_kwargs=generation_kwargs,
605
+ tools=tools,
606
+ confirmation_strategy_context=confirmation_strategy_context,
493
607
  )
494
608
  else:
495
609
  exe_context = self._initialize_fresh_execution(
@@ -497,7 +611,10 @@ class Agent(HaystackAgent):
497
611
  streaming_callback=streaming_callback,
498
612
  requires_async=True,
499
613
  system_prompt=system_prompt,
614
+ generation_kwargs=generation_kwargs,
500
615
  tools=tools,
616
+ confirmation_strategy_context=confirmation_strategy_context,
617
+ chat_message_store_kwargs=chat_message_store_kwargs,
501
618
  **kwargs,
502
619
  )
503
620
 
@@ -535,8 +652,8 @@ class Agent(HaystackAgent):
535
652
 
536
653
  # Apply confirmation strategies and update State and messages sent to ToolInvoker
537
654
  try:
538
- # Run confirmation strategies to get updated tool call messages and modified chat history
539
- modified_tool_call_messages, new_chat_history = _process_confirmation_strategies(
655
+ # Run confirmation strategies to get updated tool call messages and modified chat history (async)
656
+ modified_tool_call_messages, new_chat_history = await _process_confirmation_strategies_async(
540
657
  confirmation_strategies=self._confirmation_strategies,
541
658
  messages_with_tool_calls=llm_messages,
542
659
  execution_context=exe_context,
@@ -600,6 +717,13 @@ class Agent(HaystackAgent):
600
717
  result = {**exe_context.state.data}
601
718
  if msgs := result.get("messages"):
602
719
  result["last_message"] = msgs[-1]
720
+
721
+ # Write messages to ChatMessageStore if configured
722
+ if self._chat_message_writer:
723
+ writer_kwargs = _select_kwargs(self._chat_message_writer, chat_message_store_kwargs or {})
724
+ if "chat_history_id" in writer_kwargs:
725
+ self._chat_message_writer.run(messages=result["messages"], **writer_kwargs)
726
+
603
727
  return result
604
728
 
605
729
  def to_dict(self) -> dict[str, Any]:
@@ -614,6 +738,9 @@ class Agent(HaystackAgent):
614
738
  if self._confirmation_strategies
615
739
  else None
616
740
  )
741
+ data["init_parameters"]["chat_message_store"] = (
742
+ self._chat_message_store.to_dict() if self._chat_message_store is not None else None
743
+ )
617
744
  return data
618
745
 
619
746
  @classmethod
@@ -638,9 +765,31 @@ class Agent(HaystackAgent):
638
765
 
639
766
  if "confirmation_strategies" in init_params and init_params["confirmation_strategies"] is not None:
640
767
  for name, strategy_dict in init_params["confirmation_strategies"].items():
641
- strategy_class = import_class_by_name(strategy_dict["type"])
768
+ try:
769
+ strategy_class = import_class_by_name(strategy_dict["type"])
770
+ except ImportError as e:
771
+ raise DeserializationError(f"Class '{strategy_dict['type']}' not correctly imported") from e
642
772
  if not hasattr(strategy_class, "from_dict"):
643
- raise TypeError(f"{strategy_class} does not have from_dict method implemented.")
773
+ raise DeserializationError(f"{strategy_class} does not have from_dict method implemented.")
644
774
  init_params["confirmation_strategies"][name] = strategy_class.from_dict(strategy_dict)
645
775
 
776
+ if "chat_message_store" in init_params and init_params["chat_message_store"] is not None:
777
+ cms_data = init_params["chat_message_store"]
778
+ try:
779
+ cms_class = import_class_by_name(cms_data["type"])
780
+ except ImportError as e:
781
+ raise DeserializationError(f"Class '{cms_data['type']}' not correctly imported") from e
782
+ if not hasattr(cms_class, "from_dict"):
783
+ raise DeserializationError(f"{cms_class} does not have from_dict method implemented.")
784
+ init_params["chat_message_store"] = cms_class.from_dict(cms_data)
785
+
646
786
  return default_from_dict(cls, data)
787
+
788
+
789
+ def _select_kwargs(obj: Any, source: dict) -> dict[str, Any]:
790
+ """
791
+ Select only those key-value pairs from source dict that are valid parameters for obj.run() method.
792
+ """
793
+ sig = inspect.signature(obj.run)
794
+ allowed = set(sig.parameters.keys())
795
+ return {k: v for k, v in source.items() if k in allowed}