haystack-experimental 0.17.0__py3-none-any.whl → 0.18.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.
@@ -2,26 +2,12 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from dataclasses import replace
6
- from typing import TYPE_CHECKING, Any
5
+ from typing import Any
7
6
 
8
- from haystack.components.agents.state import State
9
- from haystack.components.tools.tool_invoker import ToolInvoker
10
- from haystack.core.serialization import default_from_dict, default_to_dict, import_class_by_name
11
- from haystack.dataclasses import ChatMessage, StreamingCallbackT
12
- from haystack.tools import Tool
13
-
14
- from haystack_experimental.components.agents.human_in_the_loop import (
15
- ConfirmationPolicy,
16
- ConfirmationStrategy,
17
- ConfirmationUI,
18
- HITLBreakpointException,
19
- ToolExecutionDecision,
20
- )
21
-
22
- if TYPE_CHECKING:
23
- from haystack_experimental.components.agents.agent import _ExecutionContext
7
+ from haystack.core.serialization import default_from_dict, default_to_dict
8
+ from haystack.human_in_the_loop.dataclasses import ToolExecutionDecision
24
9
 
10
+ from haystack_experimental.components.agents.human_in_the_loop import HITLBreakpointException
25
11
 
26
12
  _REJECTION_FEEDBACK_TEMPLATE = "Tool execution for '{tool_name}' was rejected by the user."
27
13
  _MODIFICATION_FEEDBACK_TEMPLATE = (
@@ -29,163 +15,6 @@ _MODIFICATION_FEEDBACK_TEMPLATE = (
29
15
  )
30
16
 
31
17
 
32
- class BlockingConfirmationStrategy:
33
- """
34
- Confirmation strategy that blocks execution to gather user feedback.
35
- """
36
-
37
- def __init__(self, confirmation_policy: ConfirmationPolicy, confirmation_ui: ConfirmationUI) -> None:
38
- """
39
- Initialize the BlockingConfirmationStrategy with a confirmation policy and UI.
40
-
41
- :param confirmation_policy:
42
- The confirmation policy to determine when to ask for user confirmation.
43
- :param confirmation_ui:
44
- The user interface to interact with the user for confirmation.
45
- """
46
- self.confirmation_policy = confirmation_policy
47
- self.confirmation_ui = confirmation_ui
48
-
49
- def run(
50
- self,
51
- *,
52
- tool_name: str,
53
- tool_description: str,
54
- tool_params: dict[str, Any],
55
- tool_call_id: str | None = None,
56
- confirmation_strategy_context: dict[str, Any] | None = None,
57
- ) -> ToolExecutionDecision:
58
- """
59
- Run the human-in-the-loop strategy for a given tool and its parameters.
60
-
61
- :param tool_name:
62
- The name of the tool to be executed.
63
- :param tool_description:
64
- The description of the tool.
65
- :param tool_params:
66
- The parameters to be passed to the tool.
67
- :param tool_call_id:
68
- Optional unique identifier for the tool call. This can be used to track and correlate the decision with a
69
- specific tool invocation.
70
- :param confirmation_strategy_context:
71
- Optional dictionary for passing request-scoped resources. Useful in web/server environments
72
- to provide per-request objects (e.g., WebSocket connections, async queues, Redis pub/sub clients)
73
- that strategies can use for non-blocking user interaction.
74
-
75
- :returns:
76
- A ToolExecutionDecision indicating whether to execute the tool with the given parameters, or a
77
- feedback message if rejected.
78
- """
79
- # Check if we should ask based on policy
80
- if not self.confirmation_policy.should_ask(
81
- tool_name=tool_name, tool_description=tool_description, tool_params=tool_params
82
- ):
83
- return ToolExecutionDecision(
84
- tool_name=tool_name, execute=True, tool_call_id=tool_call_id, final_tool_params=tool_params
85
- )
86
-
87
- # Get user confirmation through UI
88
- confirmation_ui_result = self.confirmation_ui.get_user_confirmation(tool_name, tool_description, tool_params)
89
-
90
- # Pass back the result to the policy for any learning/updating
91
- self.confirmation_policy.update_after_confirmation(
92
- tool_name, tool_description, tool_params, confirmation_ui_result
93
- )
94
-
95
- # Process the confirmation result
96
- final_args = {}
97
- if confirmation_ui_result.action == "reject":
98
- explanation_text = _REJECTION_FEEDBACK_TEMPLATE.format(tool_name=tool_name)
99
- if confirmation_ui_result.feedback:
100
- explanation_text += f" With feedback: {confirmation_ui_result.feedback}"
101
- return ToolExecutionDecision(
102
- tool_name=tool_name, execute=False, tool_call_id=tool_call_id, feedback=explanation_text
103
- )
104
- elif confirmation_ui_result.action == "modify" and confirmation_ui_result.new_tool_params:
105
- # Update the tool call params with the new params
106
- final_args.update(confirmation_ui_result.new_tool_params)
107
- explanation_text = _MODIFICATION_FEEDBACK_TEMPLATE.format(tool_name=tool_name, final_tool_params=final_args)
108
- if confirmation_ui_result.feedback:
109
- explanation_text += f" With feedback: {confirmation_ui_result.feedback}"
110
- return ToolExecutionDecision(
111
- tool_name=tool_name,
112
- tool_call_id=tool_call_id,
113
- execute=True,
114
- feedback=explanation_text,
115
- final_tool_params=final_args,
116
- )
117
- else: # action == "confirm"
118
- return ToolExecutionDecision(
119
- tool_name=tool_name, execute=True, tool_call_id=tool_call_id, final_tool_params=tool_params
120
- )
121
-
122
- async def run_async(
123
- self,
124
- *,
125
- tool_name: str,
126
- tool_description: str,
127
- tool_params: dict[str, Any],
128
- tool_call_id: str | None = None,
129
- confirmation_strategy_context: dict[str, Any] | None = None,
130
- ) -> ToolExecutionDecision:
131
- """
132
- Async version of run. Calls the sync run() method by default.
133
-
134
- :param tool_name:
135
- The name of the tool to be executed.
136
- :param tool_description:
137
- The description of the tool.
138
- :param tool_params:
139
- The parameters to be passed to the tool.
140
- :param tool_call_id:
141
- Optional unique identifier for the tool call.
142
- :param confirmation_strategy_context:
143
- Optional dictionary for passing request-scoped resources.
144
-
145
- :returns:
146
- A ToolExecutionDecision indicating whether to execute the tool with the given parameters.
147
- """
148
- return self.run(
149
- tool_name=tool_name,
150
- tool_description=tool_description,
151
- tool_params=tool_params,
152
- tool_call_id=tool_call_id,
153
- confirmation_strategy_context=confirmation_strategy_context,
154
- )
155
-
156
- def to_dict(self) -> dict[str, Any]:
157
- """
158
- Serializes the BlockingConfirmationStrategy to a dictionary.
159
-
160
- :returns:
161
- Dictionary with serialized data.
162
- """
163
- return default_to_dict(
164
- self, confirmation_policy=self.confirmation_policy.to_dict(), confirmation_ui=self.confirmation_ui.to_dict()
165
- )
166
-
167
- @classmethod
168
- def from_dict(cls, data: dict[str, Any]) -> "BlockingConfirmationStrategy":
169
- """
170
- Deserializes the BlockingConfirmationStrategy from a dictionary.
171
-
172
- :param data:
173
- Dictionary to deserialize from.
174
-
175
- :returns:
176
- Deserialized BlockingConfirmationStrategy.
177
- """
178
- policy_data = data["init_parameters"]["confirmation_policy"]
179
- policy_class = import_class_by_name(policy_data["type"])
180
- if not hasattr(policy_class, "from_dict"):
181
- raise ValueError(f"Class {policy_class} does not implement from_dict method.")
182
- ui_data = data["init_parameters"]["confirmation_ui"]
183
- ui_class = import_class_by_name(ui_data["type"])
184
- if not hasattr(ui_class, "from_dict"):
185
- raise ValueError(f"Class {ui_class} does not implement from_dict method.")
186
- return cls(confirmation_policy=policy_class.from_dict(policy_data), confirmation_ui=ui_class.from_dict(ui_data))
187
-
188
-
189
18
  class BreakpointConfirmationStrategy:
190
19
  """
191
20
  Confirmation strategy that raises a tool breakpoint exception to pause execution and gather user feedback.
@@ -297,376 +126,3 @@ class BreakpointConfirmationStrategy:
297
126
  Deserialized BreakpointConfirmationStrategy.
298
127
  """
299
128
  return default_from_dict(cls, data)
300
-
301
-
302
- def _prepare_tool_args(
303
- *,
304
- tool: Tool,
305
- tool_call_arguments: dict[str, Any],
306
- state: State,
307
- streaming_callback: StreamingCallbackT | None = None,
308
- enable_streaming_passthrough: bool = False,
309
- ) -> dict[str, Any]:
310
- """
311
- Prepare the final arguments for a tool by injecting state inputs and optionally a streaming callback.
312
-
313
- :param tool:
314
- The tool instance to prepare arguments for.
315
- :param tool_call_arguments:
316
- The initial arguments provided for the tool call.
317
- :param state:
318
- The current state containing inputs to be injected into the tool arguments.
319
- :param streaming_callback:
320
- Optional streaming callback to be injected if enabled and applicable.
321
- :param enable_streaming_passthrough:
322
- Flag indicating whether to inject the streaming callback into the tool arguments.
323
-
324
- :returns:
325
- A dictionary of final arguments ready for tool invocation.
326
- """
327
- # Combine user + state inputs
328
- final_args = ToolInvoker._inject_state_args(tool, tool_call_arguments.copy(), state)
329
- # Check whether to inject streaming_callback
330
- if (
331
- enable_streaming_passthrough
332
- and streaming_callback is not None
333
- and "streaming_callback" not in final_args
334
- and "streaming_callback" in ToolInvoker._get_func_params(tool)
335
- ):
336
- final_args["streaming_callback"] = streaming_callback
337
- return final_args
338
-
339
-
340
- def _process_confirmation_strategies(
341
- *,
342
- confirmation_strategies: dict[str, ConfirmationStrategy],
343
- messages_with_tool_calls: list[ChatMessage],
344
- execution_context: "_ExecutionContext",
345
- ) -> tuple[list[ChatMessage], list[ChatMessage]]:
346
- """
347
- Run the confirmation strategies and return modified tool call messages and updated chat history.
348
-
349
- :param confirmation_strategies: Mapping of tool names to their corresponding confirmation strategies
350
- :param messages_with_tool_calls: Chat messages containing tool calls
351
- :param execution_context: The current execution context of the agent
352
- :returns:
353
- Tuple of modified messages with confirmed tool calls and updated chat history
354
- """
355
- # Run confirmation strategies and get tool execution decisions
356
- teds = _run_confirmation_strategies(
357
- confirmation_strategies=confirmation_strategies,
358
- messages_with_tool_calls=messages_with_tool_calls,
359
- execution_context=execution_context,
360
- )
361
-
362
- # Apply tool execution decisions to messages_with_tool_calls
363
- rejection_messages, modified_tool_call_messages = _apply_tool_execution_decisions(
364
- tool_call_messages=messages_with_tool_calls,
365
- tool_execution_decisions=teds,
366
- )
367
-
368
- # Update the chat history with rejection messages and new tool call messages
369
- new_chat_history = _update_chat_history(
370
- chat_history=execution_context.state.get("messages"),
371
- rejection_messages=rejection_messages,
372
- tool_call_and_explanation_messages=modified_tool_call_messages,
373
- )
374
-
375
- return modified_tool_call_messages, new_chat_history
376
-
377
-
378
- async def _process_confirmation_strategies_async(
379
- *,
380
- confirmation_strategies: dict[str, ConfirmationStrategy],
381
- messages_with_tool_calls: list[ChatMessage],
382
- execution_context: "_ExecutionContext",
383
- ) -> tuple[list[ChatMessage], list[ChatMessage]]:
384
- """
385
- Async version of _process_confirmation_strategies.
386
-
387
- Run the confirmation strategies and return modified tool call messages and updated chat history.
388
-
389
- :param confirmation_strategies: Mapping of tool names to their corresponding confirmation strategies
390
- :param messages_with_tool_calls: Chat messages containing tool calls
391
- :param execution_context: The current execution context of the agent
392
- :returns:
393
- Tuple of modified messages with confirmed tool calls and updated chat history
394
- """
395
- # Run confirmation strategies and get tool execution decisions (async version)
396
- teds = await _run_confirmation_strategies_async(
397
- confirmation_strategies=confirmation_strategies,
398
- messages_with_tool_calls=messages_with_tool_calls,
399
- execution_context=execution_context,
400
- )
401
-
402
- # Apply tool execution decisions to messages_with_tool_calls
403
- rejection_messages, modified_tool_call_messages = _apply_tool_execution_decisions(
404
- tool_call_messages=messages_with_tool_calls,
405
- tool_execution_decisions=teds,
406
- )
407
-
408
- # Update the chat history with rejection messages and new tool call messages
409
- new_chat_history = _update_chat_history(
410
- chat_history=execution_context.state.get("messages"),
411
- rejection_messages=rejection_messages,
412
- tool_call_and_explanation_messages=modified_tool_call_messages,
413
- )
414
-
415
- return modified_tool_call_messages, new_chat_history
416
-
417
-
418
- def _run_confirmation_strategies(
419
- confirmation_strategies: dict[str, ConfirmationStrategy],
420
- messages_with_tool_calls: list[ChatMessage],
421
- execution_context: "_ExecutionContext",
422
- ) -> list[ToolExecutionDecision]:
423
- """
424
- Run confirmation strategies for tool calls in the provided chat messages.
425
-
426
- :param confirmation_strategies: Mapping of tool names to their corresponding confirmation strategies
427
- :param messages_with_tool_calls: Messages containing tool calls to process
428
- :param execution_context: The current execution context containing state and inputs
429
- :returns:
430
- A list of ToolExecutionDecision objects representing the decisions made for each tool call.
431
- """
432
- state = execution_context.state
433
- tools_with_names = {tool.name: tool for tool in execution_context.tool_invoker_inputs["tools"]}
434
- existing_teds = execution_context.tool_execution_decisions if execution_context.tool_execution_decisions else []
435
- existing_teds_by_name = {ted.tool_name: ted for ted in existing_teds if ted.tool_name}
436
- existing_teds_by_id = {ted.tool_call_id: ted for ted in existing_teds if ted.tool_call_id}
437
-
438
- teds = []
439
- for message in messages_with_tool_calls:
440
- if not message.tool_calls:
441
- continue
442
-
443
- for tool_call in message.tool_calls:
444
- tool_name = tool_call.tool_name
445
- tool_to_invoke = tools_with_names[tool_name]
446
-
447
- # Prepare final tool args
448
- final_args = _prepare_tool_args(
449
- tool=tool_to_invoke,
450
- tool_call_arguments=tool_call.arguments,
451
- state=state,
452
- streaming_callback=execution_context.tool_invoker_inputs.get("streaming_callback"),
453
- enable_streaming_passthrough=execution_context.tool_invoker_inputs.get(
454
- "enable_streaming_passthrough", False
455
- ),
456
- )
457
-
458
- # Get tool execution decisions from confirmation strategies
459
- # If no confirmation strategy is defined for this tool, proceed with execution
460
- if tool_name not in confirmation_strategies:
461
- teds.append(
462
- ToolExecutionDecision(
463
- tool_call_id=tool_call.id,
464
- tool_name=tool_name,
465
- execute=True,
466
- final_tool_params=final_args,
467
- )
468
- )
469
- continue
470
-
471
- # Check if there's already a decision for this tool call in the execution context
472
- ted = existing_teds_by_id.get(tool_call.id or "") or existing_teds_by_name.get(tool_name)
473
-
474
- # If not, run the confirmation strategy
475
- if not ted:
476
- ted = confirmation_strategies[tool_name].run(
477
- tool_name=tool_name,
478
- tool_description=tool_to_invoke.description,
479
- tool_params=final_args,
480
- tool_call_id=tool_call.id,
481
- confirmation_strategy_context=execution_context.confirmation_strategy_context,
482
- )
483
- teds.append(ted)
484
-
485
- return teds
486
-
487
-
488
- async def _run_confirmation_strategies_async(
489
- confirmation_strategies: dict[str, ConfirmationStrategy],
490
- messages_with_tool_calls: list[ChatMessage],
491
- execution_context: "_ExecutionContext",
492
- ) -> list[ToolExecutionDecision]:
493
- """
494
- Async version of _run_confirmation_strategies.
495
-
496
- Run confirmation strategies for tool calls in the provided chat messages.
497
-
498
- :param confirmation_strategies: Mapping of tool names to their corresponding confirmation strategies
499
- :param messages_with_tool_calls: Messages containing tool calls to process
500
- :param execution_context: The current execution context containing state and inputs
501
- :returns:
502
- A list of ToolExecutionDecision objects representing the decisions made for each tool call.
503
- """
504
- state = execution_context.state
505
- tools_with_names = {tool.name: tool for tool in execution_context.tool_invoker_inputs["tools"]}
506
- existing_teds = execution_context.tool_execution_decisions if execution_context.tool_execution_decisions else []
507
- existing_teds_by_name = {ted.tool_name: ted for ted in existing_teds if ted.tool_name}
508
- existing_teds_by_id = {ted.tool_call_id: ted for ted in existing_teds if ted.tool_call_id}
509
-
510
- teds = []
511
- for message in messages_with_tool_calls:
512
- if not message.tool_calls:
513
- continue
514
-
515
- for tool_call in message.tool_calls:
516
- tool_name = tool_call.tool_name
517
- tool_to_invoke = tools_with_names[tool_name]
518
-
519
- # Prepare final tool args
520
- final_args = _prepare_tool_args(
521
- tool=tool_to_invoke,
522
- tool_call_arguments=tool_call.arguments,
523
- state=state,
524
- streaming_callback=execution_context.tool_invoker_inputs.get("streaming_callback"),
525
- enable_streaming_passthrough=execution_context.tool_invoker_inputs.get(
526
- "enable_streaming_passthrough", False
527
- ),
528
- )
529
-
530
- # Get tool execution decisions from confirmation strategies
531
- # If no confirmation strategy is defined for this tool, proceed with execution
532
- if tool_name not in confirmation_strategies:
533
- teds.append(
534
- ToolExecutionDecision(
535
- tool_call_id=tool_call.id,
536
- tool_name=tool_name,
537
- execute=True,
538
- final_tool_params=final_args,
539
- )
540
- )
541
- continue
542
-
543
- # Check if there's already a decision for this tool call in the execution context
544
- ted = existing_teds_by_id.get(tool_call.id or "") or existing_teds_by_name.get(tool_name)
545
-
546
- # If not, run the confirmation strategy (async version)
547
- if not ted:
548
- strategy = confirmation_strategies[tool_name]
549
- # Use run_async if available, otherwise fall back to sync run
550
- if hasattr(strategy, "run_async"):
551
- ted = await strategy.run_async(
552
- tool_name=tool_name,
553
- tool_description=tool_to_invoke.description,
554
- tool_params=final_args,
555
- tool_call_id=tool_call.id,
556
- confirmation_strategy_context=execution_context.confirmation_strategy_context,
557
- )
558
- else:
559
- ted = strategy.run(
560
- tool_name=tool_name,
561
- tool_description=tool_to_invoke.description,
562
- tool_params=final_args,
563
- tool_call_id=tool_call.id,
564
- confirmation_strategy_context=execution_context.confirmation_strategy_context,
565
- )
566
- teds.append(ted)
567
-
568
- return teds
569
-
570
-
571
- def _apply_tool_execution_decisions(
572
- tool_call_messages: list[ChatMessage], tool_execution_decisions: list[ToolExecutionDecision]
573
- ) -> tuple[list[ChatMessage], list[ChatMessage]]:
574
- """
575
- Apply the tool execution decisions to the tool call messages.
576
-
577
- :param tool_call_messages: The tool call messages to apply the decisions to.
578
- :param tool_execution_decisions: The tool execution decisions to apply.
579
- :returns:
580
- A tuple containing:
581
- - A list of rejection messages for rejected tool calls. These are pairs of tool call and tool call result
582
- messages.
583
- - A list of tool call messages for confirmed or modified tool calls. If tool parameters were modified,
584
- a user message explaining the modification is included before the tool call message.
585
- """
586
- decision_by_id = {d.tool_call_id: d for d in tool_execution_decisions if d.tool_call_id}
587
- decision_by_name = {d.tool_name: d for d in tool_execution_decisions if d.tool_name}
588
-
589
- def make_assistant_message(chat_message, tool_calls):
590
- return ChatMessage.from_assistant(
591
- text=chat_message.text,
592
- meta=chat_message.meta,
593
- name=chat_message.name,
594
- tool_calls=tool_calls,
595
- reasoning=chat_message.reasoning,
596
- )
597
-
598
- new_tool_call_messages = []
599
- rejection_messages = []
600
-
601
- for chat_msg in tool_call_messages:
602
- new_tool_calls = []
603
- for tc in chat_msg.tool_calls or []:
604
- ted = decision_by_id.get(tc.id or "") or decision_by_name.get(tc.tool_name)
605
- if not ted:
606
- # This shouldn't happen, if so something went wrong in _run_confirmation_strategies
607
- continue
608
-
609
- if not ted.execute:
610
- # rejected tool call
611
- tool_result_text = ted.feedback or _REJECTION_FEEDBACK_TEMPLATE.format(tool_name=tc.tool_name)
612
- rejection_messages.extend(
613
- [
614
- make_assistant_message(chat_msg, [tc]),
615
- ChatMessage.from_tool(tool_result=tool_result_text, origin=tc, error=True),
616
- ]
617
- )
618
- continue
619
-
620
- # Covers confirm and modify cases
621
- final_args = ted.final_tool_params or {}
622
- if tc.arguments != final_args:
623
- # In the modify case we add a user message explaining the modification otherwise the LLM won't know
624
- # why the tool parameters changed and will likely just try and call the tool again with the
625
- # original parameters.
626
- user_text = ted.feedback or _MODIFICATION_FEEDBACK_TEMPLATE.format(
627
- tool_name=tc.tool_name, final_tool_params=final_args
628
- )
629
- new_tool_call_messages.append(ChatMessage.from_user(text=user_text))
630
- new_tool_calls.append(replace(tc, arguments=final_args))
631
-
632
- # Only add the tool call message if there are any tool calls left (i.e. not all were rejected)
633
- if new_tool_calls:
634
- new_tool_call_messages.append(make_assistant_message(chat_msg, new_tool_calls))
635
-
636
- return rejection_messages, new_tool_call_messages
637
-
638
-
639
- def _update_chat_history(
640
- chat_history: list[ChatMessage],
641
- rejection_messages: list[ChatMessage],
642
- tool_call_and_explanation_messages: list[ChatMessage],
643
- ) -> list[ChatMessage]:
644
- """
645
- Update the chat history to include rejection messages and tool call messages at the appropriate positions.
646
-
647
- Steps:
648
- 1. Identify the last user message and the last tool message in the current chat history.
649
- 2. Determine the insertion point as the maximum index of these two messages.
650
- 3. Create a new chat history that includes:
651
- - All messages up to the insertion point.
652
- - Any rejection messages (pairs of tool call and tool call result messages).
653
- - Any tool call messages for confirmed or modified tool calls, including user messages explaining modifications.
654
-
655
- :param chat_history: The current chat history.
656
- :param rejection_messages: Chat messages to add for rejected tool calls (pairs of tool call and tool call result
657
- messages).
658
- :param tool_call_and_explanation_messages: Tool call messages for confirmed or modified tool calls, which may
659
- include user messages explaining modifications.
660
- :returns:
661
- The updated chat history.
662
- """
663
- user_indices = [i for i, message in enumerate(chat_history) if message.is_from("user")]
664
- tool_indices = [i for i, message in enumerate(chat_history) if message.is_from("tool")]
665
-
666
- last_user_idx = max(user_indices) if user_indices else -1
667
- last_tool_idx = max(tool_indices) if tool_indices else -1
668
-
669
- insertion_point = max(last_user_idx, last_tool_idx)
670
-
671
- new_chat_history = chat_history[: insertion_point + 1] + rejection_messages + tool_call_and_explanation_messages
672
- return new_chat_history
@@ -6,10 +6,9 @@ from dataclasses import dataclass
6
6
  from datetime import datetime
7
7
  from typing import Any
8
8
 
9
- from haystack.dataclasses.breakpoints import AgentBreakpoint
10
- from haystack.dataclasses.breakpoints import AgentSnapshot as HaystackAgentSnapshot
11
-
12
- from haystack_experimental.components.agents.human_in_the_loop.dataclasses import ToolExecutionDecision
9
+ from haystack.dataclasses import AgentBreakpoint
10
+ from haystack.dataclasses import AgentSnapshot as HaystackAgentSnapshot
11
+ from haystack.human_in_the_loop.dataclasses import ToolExecutionDecision
13
12
 
14
13
 
15
14
  @dataclass