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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: haystack-experimental
3
- Version: 0.17.0
3
+ Version: 0.18.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
@@ -24,7 +24,6 @@ Classifier: Programming Language :: Python :: 3.13
24
24
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
25
  Requires-Python: >=3.10
26
26
  Requires-Dist: haystack-ai
27
- Requires-Dist: rich
28
27
  Description-Content-Type: text/markdown
29
28
 
30
29
  [![PyPI - Version](https://img.shields.io/pypi/v/haystack-experimental.svg)](https://pypi.org/project/haystack-experimental)
@@ -70,14 +69,14 @@ that includes it. Once it reaches the end of its lifespan, the experiment will b
70
69
 
71
70
  ### Active experiments
72
71
 
73
- | Name | Type | Expected End Date | Dependencies | Cookbook | Discussion |
74
- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------|-------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
75
- | [`OpenAIChatGenerator`][9] | Chat Generator Component | November 2025 | None | <a href="https://colab.research.google.com/github/deepset-ai/haystack-cookbook/blob/main/notebooks/hallucination_score_calculator.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/> | [Discuss][10] |
76
- | [`MarkdownHeaderLevelInferrer`][15] | Preprocessor | January 2025 | None | None | [Discuss][16] |
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
- | [`LLMSummarizer`][24] | Document Summarizer | January 2025 | None | None | [Discuss][25] |
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 | -- |
72
+ | Name | Type | Expected End Date | Dependencies | Cookbook | Discussion |
73
+ |----------------------------------------------------------------------------------------|---------------------------------------|-------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
74
+ | [`OpenAIChatGenerator`][9] | Chat Generator Component | November 2025 | None | <a href="https://colab.research.google.com/github/deepset-ai/haystack-cookbook/blob/main/notebooks/hallucination_score_calculator.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/> | [Discuss][10] |
75
+ | [`MarkdownHeaderLevelInferrer`][15] | Preprocessor | January 2025 | None | None | [Discuss][16] |
76
+ | [`Agent`][17]; [`BreakpointConfirmationStrategy`][20]; [`HITLBreakpointException`][22] | Human in the Loop via Breakpoints | December 2025 | None | None | [Discuss][23] |
77
+ | [`LLMSummarizer`][24] | Document Summarizer | January 2025 | None | None | [Discuss][25] |
78
+ | [`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] |
79
+ | [`Mem0MemoryStore`][26] | MemoryStore | April 2025 | mem0ai | None | -- |
81
80
 
82
81
  [1]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/chat_message_stores/in_memory.py
83
82
  [2]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/retrievers/chat_message_retriever.py
@@ -88,10 +87,7 @@ that includes it. Once it reaches the end of its lifespan, the experiment will b
88
87
  [15]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/preprocessors/md_header_level_inferrer.py
89
88
  [16]: https://github.com/deepset-ai/haystack-experimental/discussions/376
90
89
  [17]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/agents/agent.py
91
- [18]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/agents/human_in_the_loop/policies.py
92
- [19]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/agents/human_in_the_loop/user_interfaces.py
93
90
  [20]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/agents/human_in_the_loop/strategies.py
94
- [21]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/agents/human_in_the_loop/dataclasses.py
95
91
  [22]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/agents/human_in_the_loop/errors.py
96
92
  [23]: https://github.com/deepset-ai/haystack-experimental/discussions/381
97
93
  [24]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/components/sumarizers/llm_summarizer.py
@@ -99,20 +95,21 @@ that includes it. Once it reaches the end of its lifespan, the experiment will b
99
95
  [26]: https://github.com/deepset-ai/haystack-experimental/blob/main/haystack_experimental/memory_stores/mem0/memory_store.py
100
96
 
101
97
  ### Adopted experiments
102
- | Name | Type | Final release |
103
- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|---------------|
104
- | `ChatMessage` refactoring; `Tool` class; tool support in ChatGenerators; `ToolInvoker` | Tool Calling support | 0.4.0 |
105
- | `AsyncPipeline`; `Pipeline` bug fixes and refactoring | AsyncPipeline execution | 0.7.0 |
106
- | `LLMMetadataExtractor` | Metadata extraction with LLM | 0.7.0 |
107
- | `Auto-Merging Retriever` & `HierarchicalDocumentSplitter` | Document Splitting & Retrieval Technique | 0.8.0 |
108
- | `Agent` | Simplify Agent development | 0.8.0 |
109
- | `SuperComponent` | Simplify Pipeline development | 0.8.0 |
110
- | `Pipeline` | Pipeline breakpoints for debugging | 0.12.0 |
111
- | `ImageContent`; Image Converters; multimodal support in `OpenAIChatGenerator` and `AmazonBedrockChatGenerator`; `ChatPromptBuilder` refactoring; `SentenceTransformersDocumentImageEmbedder`; `LLMDocumentContentExtractor`; new `Routers` | Multimodality | 0.12.0 |
112
- | `QueryExpander` | Query Expansion Component | 0.14.3 |
113
- | `MultiQueryEmbeddingRetriever` | MultiQueryEmbeddingRetriever | 0.14.3 |
114
- | `MultiQueryTextRetriever` | MultiQueryTextRetriever | 0.14.3 |
115
- | `EmbeddingBasedDocumentSplitter` | Document Splitting | 0.15.2 |
98
+ | Name | Type | Final release |
99
+ |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|---------------|
100
+ | `ChatMessage` refactoring; `Tool` class; tool support in ChatGenerators; `ToolInvoker` | Tool Calling support | 0.4.0 |
101
+ | `AsyncPipeline`; `Pipeline` bug fixes and refactoring | AsyncPipeline execution | 0.7.0 |
102
+ | `LLMMetadataExtractor` | Metadata extraction with LLM | 0.7.0 |
103
+ | `Auto-Merging Retriever` & `HierarchicalDocumentSplitter` | Document Splitting & Retrieval Technique | 0.8.0 |
104
+ | `Agent` | Simplify Agent development | 0.8.0 |
105
+ | `SuperComponent` | Simplify Pipeline development | 0.8.0 |
106
+ | `Pipeline` | Pipeline breakpoints for debugging | 0.12.0 |
107
+ | `ImageContent`; Image Converters; multimodal support in `OpenAIChatGenerator` and `AmazonBedrockChatGenerator`; `ChatPromptBuilder` refactoring; `SentenceTransformersDocumentImageEmbedder`; `LLMDocumentContentExtractor`; new `Routers` | Multimodality | 0.12.0 |
108
+ | `QueryExpander` | Query Expansion Component | 0.14.3 |
109
+ | `MultiQueryEmbeddingRetriever` | MultiQueryEmbeddingRetriever | 0.14.3 |
110
+ | `MultiQueryTextRetriever` | MultiQueryTextRetriever | 0.14.3 |
111
+ | `EmbeddingBasedDocumentSplitter` | Document Splitting | 0.15.2 |
112
+ | `Confirmation Policies`; `ConfirmationUIs`; `BlockingConfirmationStrategy`; `ConfirmationUIResult`; `ToolExecutionDecision` | Human in the Loop | 0.16.0 |
116
113
 
117
114
  ### Discontinued experiments
118
115
 
@@ -4,15 +4,11 @@ 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=R0QUvQsLSuA-t90dASNdOE_cAwX_JJJamjU5RpQLcbs,50686
8
- haystack_experimental/components/agents/human_in_the_loop/__init__.py,sha256=xLr1G9pNWMmCpKN9mbv6yqeFfwMcbZyaVfCkzlwMxhY,1674
9
- haystack_experimental/components/agents/human_in_the_loop/breakpoint.py,sha256=K5rU61821uXjXkcXuXkN3p1_9Boq7FhJK1GOcAz7C0g,2939
10
- haystack_experimental/components/agents/human_in_the_loop/dataclasses.py,sha256=qQG4yRlzzcNeBlWagnZpjeJKZ2v_n2_9V9W0ZvsgBn4,2538
7
+ haystack_experimental/components/agents/agent.py,sha256=KKVQe6JAsy6TE8vS22xyMcLXP2Ds8JVLXdkL9wM2jYM,46801
8
+ haystack_experimental/components/agents/human_in_the_loop/__init__.py,sha256=NAVRXBjgLXn734XPr208WSjqAinSkkgM4LTpr1cZZ8k,746
9
+ haystack_experimental/components/agents/human_in_the_loop/breakpoint.py,sha256=P94JmVgu2B5spUPu4dMltleOdG7qsoSiJxnxkGGvKrY,2907
11
10
  haystack_experimental/components/agents/human_in_the_loop/errors.py,sha256=Mgn8HWx-3AqTFHu5ZY9PWWb_brMsO1xtdrOFzHzbXRI,1020
12
- haystack_experimental/components/agents/human_in_the_loop/policies.py,sha256=nzblePptT4Fg2GFHa4_SDIK_d7hZ_70qPhkteZBRXWk,3172
13
- haystack_experimental/components/agents/human_in_the_loop/strategies.py,sha256=b9oJgXJ805VMljm55i1S2j2wQ-2-bPxkn5oWG5Vx9RU,28608
14
- haystack_experimental/components/agents/human_in_the_loop/types.py,sha256=Fu6LR67GOimGhzEgJOnpU4pb14zEfk1JtBrDFwTuLk8,4582
15
- haystack_experimental/components/agents/human_in_the_loop/user_interfaces.py,sha256=LHHZc0JljvnzoQaNgFGiAoJyWDYFWeYFPE2khZ4z-x4,8694
11
+ haystack_experimental/components/agents/human_in_the_loop/strategies.py,sha256=027sKjy8oe1tSTBRi9XNpHqJlr5k8IHJRYrfUqFNeRg,4863
16
12
  haystack_experimental/components/embedders/__init__.py,sha256=eHD7xrty2PCky_gG3ty19rpM4WfV32TyytM7gJODwl4,110
17
13
  haystack_experimental/components/embedders/types/__init__.py,sha256=HGR8aavwIEx7v-8nm5JxFIw47EWn7vAUmywhakTNDCo,182
18
14
  haystack_experimental/components/embedders/types/protocol.py,sha256=nVMo2x_sFP9T_DN-q-_HKGrLRd3rj27m7ZLxtigY4UQ,1026
@@ -33,7 +29,7 @@ haystack_experimental/core/__init__.py,sha256=eHD7xrty2PCky_gG3ty19rpM4WfV32Tyyt
33
29
  haystack_experimental/core/pipeline/__init__.py,sha256=eHD7xrty2PCky_gG3ty19rpM4WfV32TyytM7gJODwl4,110
34
30
  haystack_experimental/core/pipeline/breakpoint.py,sha256=NcagwEJupIZ_Mp20YLmJyeqTZe8qwo6LfuB7OTXnlXk,5214
35
31
  haystack_experimental/dataclasses/__init__.py,sha256=eHD7xrty2PCky_gG3ty19rpM4WfV32TyytM7gJODwl4,110
36
- haystack_experimental/dataclasses/breakpoints.py,sha256=JevVOYiUMAsdg0QmSGiF_6aPveO83q47U-v-8Vd4nd0,2082
32
+ haystack_experimental/dataclasses/breakpoints.py,sha256=ZeSjfb72UwGDW9P5sTC59gyEXSobwDtdTus0vMJMuIM,2026
37
33
  haystack_experimental/memory_stores/__init__.py,sha256=xkSdV7qQ_e1kNO4PrXW37CIJMGavYiwbPtsfeSHQaQ4,169
38
34
  haystack_experimental/memory_stores/mem0/__init__.py,sha256=pntAgiFC6jXvzyHUQUNPtyChlys69jLa6rTVbClTb9I,458
39
35
  haystack_experimental/memory_stores/mem0/memory_store.py,sha256=PkDMVRWXooorTpHtnMRHZLgsxokJR8DlnV7vgedhPyk,14148
@@ -45,8 +41,8 @@ haystack_experimental/utils/hallucination_risk_calculator/core_math.py,sha256=8X
45
41
  haystack_experimental/utils/hallucination_risk_calculator/dataclasses.py,sha256=8HQL-ktwoog0WZlFz3-NQhhmVMjzMIrRjRoYuYXmorE,2284
46
42
  haystack_experimental/utils/hallucination_risk_calculator/openai_planner.py,sha256=Vt0icGcrPtTRywh9L2YfwB62a7pDynrm5-qlKaLHLsA,11381
47
43
  haystack_experimental/utils/hallucination_risk_calculator/skeletonization.py,sha256=KYdBDw5LcRtw8cmKW4aNGOKh3YrA17CPmcRE-FG6kSA,5466
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,,
44
+ haystack_experimental-0.18.0.dist-info/METADATA,sha256=z0FIckGo2ezlyJ5uUjkjonjpJWWT7HQjEL1gKjCOml8,16826
45
+ haystack_experimental-0.18.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
46
+ haystack_experimental-0.18.0.dist-info/licenses/LICENSE,sha256=93_5nS97uHxptHvK9E8BZgKxLGeIS-rBWT2swIv-X5Y,11368
47
+ haystack_experimental-0.18.0.dist-info/licenses/LICENSE-MIT.txt,sha256=knmLkIKj_6tTrTSVRg9Tq88Kww4UCPLt2I1RGXJv9sQ,1037
48
+ haystack_experimental-0.18.0.dist-info/RECORD,,
@@ -1,72 +0,0 @@
1
- # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- from dataclasses import asdict, dataclass
6
- from typing import Any
7
-
8
-
9
- @dataclass
10
- class ConfirmationUIResult:
11
- """
12
- Result of the confirmation UI interaction.
13
-
14
- :param action:
15
- The action taken by the user such as "confirm", "reject", or "modify".
16
- This action type is not enforced to allow for custom actions to be implemented.
17
- :param feedback:
18
- Optional feedback message from the user. For example, if the user rejects the tool execution,
19
- they might provide a reason for the rejection.
20
- :param new_tool_params:
21
- Optional set of new parameters for the tool. For example, if the user chooses to modify the tool parameters,
22
- they can provide a new set of parameters here.
23
- """
24
-
25
- action: str # "confirm", "reject", "modify"
26
- feedback: str | None = None
27
- new_tool_params: dict[str, Any] | None = None
28
-
29
-
30
- @dataclass
31
- class ToolExecutionDecision:
32
- """
33
- Decision made regarding tool execution.
34
-
35
- :param tool_name:
36
- The name of the tool to be executed.
37
- :param execute:
38
- A boolean indicating whether to execute the tool with the provided parameters.
39
- :param tool_call_id:
40
- Optional unique identifier for the tool call. This can be used to track and correlate the decision with a
41
- specific tool invocation.
42
- :param feedback:
43
- Optional feedback message.
44
- For example, if the tool execution is rejected, this can contain the reason. Or if the tool parameters were
45
- modified, this can contain the modification details.
46
- :param final_tool_params:
47
- Optional final parameters for the tool if execution is confirmed or modified.
48
- """
49
-
50
- tool_name: str
51
- execute: bool
52
- tool_call_id: str | None = None
53
- feedback: str | None = None
54
- final_tool_params: dict[str, Any] | None = None
55
-
56
- def to_dict(self) -> dict[str, Any]:
57
- """
58
- Convert the ToolExecutionDecision to a dictionary representation.
59
-
60
- :return: A dictionary containing the tool execution decision details.
61
- """
62
- return asdict(self)
63
-
64
- @classmethod
65
- def from_dict(cls, data: dict[str, Any]) -> "ToolExecutionDecision":
66
- """
67
- Populate the ToolExecutionDecision from a dictionary representation.
68
-
69
- :param data: A dictionary containing the tool execution decision details.
70
- :return: An instance of ToolExecutionDecision.
71
- """
72
- return cls(**data)
@@ -1,78 +0,0 @@
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_experimental.components.agents.human_in_the_loop.dataclasses import ConfirmationUIResult
8
- from haystack_experimental.components.agents.human_in_the_loop.types import ConfirmationPolicy
9
-
10
-
11
- class AlwaysAskPolicy(ConfirmationPolicy):
12
- """Always ask for confirmation."""
13
-
14
- def should_ask(self, tool_name: str, tool_description: str, tool_params: dict[str, Any]) -> bool:
15
- """
16
- Always ask for confirmation before executing the tool.
17
-
18
- :param tool_name: The name of the tool to be executed.
19
- :param tool_description: The description of the tool.
20
- :param tool_params: The parameters to be passed to the tool.
21
- :returns: Always returns True, indicating confirmation is needed.
22
- """
23
- return True
24
-
25
-
26
- class NeverAskPolicy(ConfirmationPolicy):
27
- """Never ask for confirmation."""
28
-
29
- def should_ask(self, tool_name: str, tool_description: str, tool_params: dict[str, Any]) -> bool:
30
- """
31
- Never ask for confirmation, always proceed with tool execution.
32
-
33
- :param tool_name: The name of the tool to be executed.
34
- :param tool_description: The description of the tool.
35
- :param tool_params: The parameters to be passed to the tool.
36
- :returns: Always returns False, indicating no confirmation is needed.
37
- """
38
- return False
39
-
40
-
41
- class AskOncePolicy(ConfirmationPolicy):
42
- """Ask only once per tool with specific parameters."""
43
-
44
- def __init__(self):
45
- self._asked_tools = {}
46
-
47
- def should_ask(self, tool_name: str, tool_description: str, tool_params: dict[str, Any]) -> bool:
48
- """
49
- Ask for confirmation only once per tool with specific parameters.
50
-
51
- :param tool_name: The name of the tool to be executed.
52
- :param tool_description: The description of the tool.
53
- :param tool_params: The parameters to be passed to the tool.
54
- :returns: True if confirmation is needed, False if already asked with the same parameters.
55
- """
56
- # Don't ask again if we've already asked for this tool with the same parameters
57
- return not (tool_name in self._asked_tools and self._asked_tools[tool_name] == tool_params)
58
-
59
- def update_after_confirmation(
60
- self,
61
- tool_name: str,
62
- tool_description: str,
63
- tool_params: dict[str, Any],
64
- confirmation_result: ConfirmationUIResult,
65
- ) -> None:
66
- """
67
- Store the tool and parameters if the action was "confirm" to avoid asking again.
68
-
69
- This method updates the internal state to remember that the user has already confirmed the execution of the
70
- tool with the given parameters.
71
-
72
- :param tool_name: The name of the tool that was executed.
73
- :param tool_description: The description of the tool.
74
- :param tool_params: The parameters that were passed to the tool.
75
- :param confirmation_result: The result from the confirmation UI.
76
- """
77
- if confirmation_result.action == "confirm":
78
- self._asked_tools[tool_name] = tool_params
@@ -1,124 +0,0 @@
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.core.serialization import default_from_dict, default_to_dict
8
-
9
- from haystack_experimental.components.agents.human_in_the_loop.dataclasses import (
10
- ConfirmationUIResult,
11
- ToolExecutionDecision,
12
- )
13
-
14
- # Ellipsis are needed to define the Protocol but pylint complains. See https://github.com/pylint-dev/pylint/issues/9319.
15
- # pylint: disable=unnecessary-ellipsis
16
-
17
-
18
- class ConfirmationUI(Protocol):
19
- """Base class for confirmation UIs."""
20
-
21
- def get_user_confirmation(
22
- self, tool_name: str, tool_description: str, tool_params: dict[str, Any]
23
- ) -> ConfirmationUIResult:
24
- """Get user confirmation for tool execution."""
25
- ...
26
-
27
- def to_dict(self) -> dict[str, Any]:
28
- """Serialize the UI to a dictionary."""
29
- return default_to_dict(self)
30
-
31
- @classmethod
32
- def from_dict(cls, data: dict[str, Any]) -> "ConfirmationUI":
33
- """Deserialize the ConfirmationUI from a dictionary."""
34
- return default_from_dict(cls, data)
35
-
36
-
37
- class ConfirmationPolicy(Protocol):
38
- """Base class for confirmation policies."""
39
-
40
- def should_ask(self, tool_name: str, tool_description: str, tool_params: dict[str, Any]) -> bool:
41
- """Determine whether to ask for confirmation."""
42
- ...
43
-
44
- def update_after_confirmation(
45
- self,
46
- tool_name: str,
47
- tool_description: str,
48
- tool_params: dict[str, Any],
49
- confirmation_result: ConfirmationUIResult,
50
- ) -> None:
51
- """Update the policy based on the confirmation UI result."""
52
- pass
53
-
54
- def to_dict(self) -> dict[str, Any]:
55
- """Serialize the policy to a dictionary."""
56
- return default_to_dict(self)
57
-
58
- @classmethod
59
- def from_dict(cls, data: dict[str, Any]) -> "ConfirmationPolicy":
60
- """Deserialize the policy from a dictionary."""
61
- return default_from_dict(cls, data)
62
-
63
-
64
- class ConfirmationStrategy(Protocol):
65
- def run(
66
- self,
67
- tool_name: str,
68
- tool_description: str,
69
- tool_params: dict[str, Any],
70
- tool_call_id: str | None = None,
71
- **kwargs: dict[str, Any] | None,
72
- ) -> ToolExecutionDecision:
73
- """
74
- Run the confirmation strategy for a given tool and its parameters.
75
-
76
- :param tool_name: The name of the tool to be executed.
77
- :param tool_description: The description of the tool.
78
- :param tool_params: The parameters to be passed to the tool.
79
- :param tool_call_id: Optional unique identifier for the tool call. This can be used to track and correlate
80
- the decision with a specific tool invocation.
81
- :param kwargs: Additional keyword arguments. Implementations may accept `confirmation_strategy_context`
82
- for passing request-scoped resources (e.g., WebSocket connections, async queues) in web/server
83
- environments.
84
-
85
- :returns:
86
- The result of the confirmation strategy (e.g., tool output, rejection message, etc.).
87
- """
88
- ...
89
-
90
- async def run_async(
91
- self,
92
- tool_name: str,
93
- tool_description: str,
94
- tool_params: dict[str, Any],
95
- tool_call_id: str | None = None,
96
- **kwargs: dict[str, Any] | None,
97
- ) -> ToolExecutionDecision:
98
- """
99
- Async version of run. Run the confirmation strategy for a given tool and its parameters.
100
-
101
- Default implementation calls the sync run() method. Override for true async behavior.
102
-
103
- :param tool_name: The name of the tool to be executed.
104
- :param tool_description: The description of the tool.
105
- :param tool_params: The parameters to be passed to the tool.
106
- :param tool_call_id: Optional unique identifier for the tool call. This can be used to track and correlate
107
- the decision with a specific tool invocation.
108
- :param kwargs: Additional keyword arguments. Implementations may accept `confirmation_strategy_context`
109
- for passing request-scoped resources (e.g., WebSocket connections, async queues) in web/server
110
- environments.
111
-
112
- :returns:
113
- The result of the confirmation strategy (e.g., tool output, rejection message, etc.).
114
- """
115
- ...
116
-
117
- def to_dict(self) -> dict[str, Any]:
118
- """Serialize the strategy to a dictionary."""
119
- ...
120
-
121
- @classmethod
122
- def from_dict(cls, data: dict[str, Any]) -> "ConfirmationStrategy":
123
- """Deserialize the strategy from a dictionary."""
124
- ...
@@ -1,209 +0,0 @@
1
- # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- import json
6
- from threading import Lock
7
- from typing import Any
8
-
9
- from haystack.core.serialization import default_to_dict
10
- from rich.console import Console
11
- from rich.panel import Panel
12
- from rich.prompt import Prompt
13
-
14
- from haystack_experimental.components.agents.human_in_the_loop.dataclasses import ConfirmationUIResult
15
- from haystack_experimental.components.agents.human_in_the_loop.types import ConfirmationUI
16
-
17
- _ui_lock = Lock()
18
-
19
-
20
- class RichConsoleUI(ConfirmationUI):
21
- """Rich console interface for user interaction."""
22
-
23
- def __init__(self, console: Console | None = None):
24
- self.console = console or Console()
25
-
26
- def get_user_confirmation(
27
- self, tool_name: str, tool_description: str, tool_params: dict[str, Any]
28
- ) -> ConfirmationUIResult:
29
- """
30
- Get user confirmation for tool execution via rich console prompts.
31
-
32
- :param tool_name: The name of the tool to be executed.
33
- :param tool_description: The description of the tool.
34
- :param tool_params: The parameters to be passed to the tool.
35
- :returns: ConfirmationUIResult based on user input.
36
- """
37
- with _ui_lock:
38
- self._display_tool_info(tool_name, tool_description, tool_params)
39
- # If wrong input is provided, Prompt.ask will re-prompt
40
- choice = Prompt.ask("\nYour choice", choices=["y", "n", "m"], default="y", console=self.console)
41
- return self._process_choice(choice, tool_params)
42
-
43
- def _display_tool_info(self, tool_name: str, tool_description: str, tool_params: dict[str, Any]) -> None:
44
- """
45
- Display tool information and parameters in a rich panel.
46
-
47
- :param tool_name: The name of the tool to be executed.
48
- :param tool_description: The description of the tool.
49
- :param tool_params: The parameters to be passed to the tool.
50
- """
51
- lines = [
52
- f"[bold yellow]Tool:[/bold yellow] {tool_name}",
53
- f"[bold yellow]Description:[/bold yellow] {tool_description}",
54
- "\n[bold yellow]Arguments:[/bold yellow]",
55
- ]
56
-
57
- if tool_params:
58
- for k, v in tool_params.items():
59
- lines.append(f"[cyan]{k}:[/cyan] {v}")
60
- else:
61
- lines.append(" (No arguments)")
62
-
63
- self.console.print(Panel("\n".join(lines), title="🔧 Tool Execution Request", title_align="left"))
64
-
65
- def _process_choice(self, choice: str, tool_params: dict[str, Any]) -> ConfirmationUIResult:
66
- """
67
- Process the user's choice and return the corresponding ConfirmationUIResult.
68
-
69
- :param choice: The user's choice ('y', 'n', or 'm').
70
- :param tool_params: The original tool parameters.
71
- :returns:
72
- ConfirmationUIResult based on user input.
73
- """
74
- if choice == "y":
75
- return ConfirmationUIResult(action="confirm")
76
- elif choice == "m":
77
- return self._modify_params(tool_params)
78
- else: # reject
79
- feedback = Prompt.ask("Feedback message (optional)", default="", console=self.console)
80
- return ConfirmationUIResult(action="reject", feedback=feedback or None)
81
-
82
- def _modify_params(self, tool_params: dict[str, Any]) -> ConfirmationUIResult:
83
- """
84
- Prompt the user to modify tool parameters.
85
-
86
- :param tool_params: The original tool parameters.
87
- :returns:
88
- ConfirmationUIResult with modified parameters.
89
- """
90
- new_params: dict[str, Any] = {}
91
- for k, v in tool_params.items():
92
- # We don't JSON dump strings to avoid users needing to input extra quotes
93
- default_val = json.dumps(v) if not isinstance(v, str) else v
94
- while True:
95
- new_val = Prompt.ask(f"Modify '{k}'", default=default_val, console=self.console)
96
- try:
97
- if isinstance(v, str):
98
- # Always treat input as string
99
- new_params[k] = new_val
100
- else:
101
- # Parse JSON for all non-string types
102
- new_params[k] = json.loads(new_val)
103
- break
104
- except json.JSONDecodeError:
105
- self.console.print("[red]❌ Invalid JSON, please try again.[/red]")
106
-
107
- return ConfirmationUIResult(action="modify", new_tool_params=new_params)
108
-
109
- def to_dict(self) -> dict[str, Any]:
110
- """
111
- Serializes the RichConsoleConfirmationUI to a dictionary.
112
-
113
- :returns:
114
- Dictionary with serialized data.
115
- """
116
- # Note: Console object is not serializable; we store None
117
- return default_to_dict(self, console=None)
118
-
119
-
120
- class SimpleConsoleUI(ConfirmationUI):
121
- """Simple console interface using standard input/output."""
122
-
123
- def get_user_confirmation(
124
- self, tool_name: str, tool_description: str, tool_params: dict[str, Any]
125
- ) -> ConfirmationUIResult:
126
- """
127
- Get user confirmation for tool execution via simple console prompts.
128
-
129
- :param tool_name: The name of the tool to be executed.
130
- :param tool_description: The description of the tool.
131
- :param tool_params: The parameters to be passed to the tool.
132
- """
133
- with _ui_lock:
134
- self._display_tool_info(tool_name, tool_description, tool_params)
135
- valid_choices = {"y", "yes", "n", "no", "m", "modify"}
136
- while True:
137
- choice = input("Confirm execution? (y=confirm / n=reject / m=modify): ").strip().lower()
138
- if choice in valid_choices:
139
- break
140
- print("Invalid input. Please enter 'y', 'n', or 'm'.")
141
- return self._process_choice(choice, tool_params)
142
-
143
- def _display_tool_info(self, tool_name: str, tool_description: str, tool_params: dict[str, Any]) -> None:
144
- """
145
- Display tool information and parameters in the console.
146
-
147
- :param tool_name: The name of the tool to be executed.
148
- :param tool_description: The description of the tool.
149
- :param tool_params: The parameters to be passed to the tool.
150
- """
151
- print("\n--- Tool Execution Request ---")
152
- print(f"Tool: {tool_name}")
153
- print(f"Description: {tool_description}")
154
- print("Arguments:")
155
- if tool_params:
156
- for k, v in tool_params.items():
157
- print(f" {k}: {v}")
158
- else:
159
- print(" (No arguments)")
160
- print("-" * 30)
161
-
162
- def _process_choice(self, choice: str, tool_params: dict[str, Any]) -> ConfirmationUIResult:
163
- """
164
- Process the user's choice and return the corresponding ConfirmationUIResult.
165
-
166
- :param choice: The user's choice ('y', 'n', or 'm').
167
- :param tool_params: The original tool parameters.
168
- :returns:
169
- ConfirmationUIResult based on user input.
170
- """
171
- if choice in ("y", "yes"):
172
- return ConfirmationUIResult(action="confirm")
173
- elif choice in ("m", "modify"):
174
- return self._modify_params(tool_params)
175
- else: # reject
176
- feedback = input("Feedback message (optional): ").strip()
177
- return ConfirmationUIResult(action="reject", feedback=feedback or None)
178
-
179
- def _modify_params(self, tool_params: dict[str, Any]) -> ConfirmationUIResult:
180
- """
181
- Prompt the user to modify tool parameters.
182
-
183
- :param tool_params: The original tool parameters.
184
- :returns:
185
- ConfirmationUIResult with modified parameters.
186
- """
187
- new_params: dict[str, Any] = {}
188
-
189
- if not tool_params:
190
- print("No parameters to modify, skipping modification.")
191
- return ConfirmationUIResult(action="modify", new_tool_params=new_params)
192
-
193
- for k, v in tool_params.items():
194
- # We don't JSON dump strings to avoid users needing to input extra quotes
195
- default_val = json.dumps(v) if not isinstance(v, str) else v
196
- while True:
197
- new_val = input(f"Modify '{k}' (current: {default_val}): ").strip() or default_val
198
- try:
199
- if isinstance(v, str):
200
- # Always treat input as string
201
- new_params[k] = new_val
202
- else:
203
- # Parse JSON for all non-string types
204
- new_params[k] = json.loads(new_val)
205
- break
206
- except json.JSONDecodeError:
207
- print("❌ Invalid JSON, please try again.")
208
-
209
- return ConfirmationUIResult(action="modify", new_tool_params=new_params)