unique_toolkit 0.7.7__py3-none-any.whl → 1.23.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.

Potentially problematic release.


This version of unique_toolkit might be problematic. Click here for more details.

Files changed (166) hide show
  1. unique_toolkit/__init__.py +28 -1
  2. unique_toolkit/_common/api_calling/human_verification_manager.py +343 -0
  3. unique_toolkit/_common/base_model_type_attribute.py +303 -0
  4. unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
  5. unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
  6. unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
  7. unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
  8. unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
  9. unique_toolkit/_common/default_language_model.py +12 -0
  10. unique_toolkit/_common/docx_generator/__init__.py +7 -0
  11. unique_toolkit/_common/docx_generator/config.py +12 -0
  12. unique_toolkit/_common/docx_generator/schemas.py +80 -0
  13. unique_toolkit/_common/docx_generator/service.py +252 -0
  14. unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
  15. unique_toolkit/_common/endpoint_builder.py +305 -0
  16. unique_toolkit/_common/endpoint_requestor.py +430 -0
  17. unique_toolkit/_common/exception.py +24 -0
  18. unique_toolkit/_common/feature_flags/schema.py +9 -0
  19. unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
  20. unique_toolkit/_common/pydantic_helpers.py +154 -0
  21. unique_toolkit/_common/referencing.py +53 -0
  22. unique_toolkit/_common/string_utilities.py +140 -0
  23. unique_toolkit/_common/tests/test_referencing.py +521 -0
  24. unique_toolkit/_common/tests/test_string_utilities.py +506 -0
  25. unique_toolkit/_common/token/image_token_counting.py +67 -0
  26. unique_toolkit/_common/token/token_counting.py +204 -0
  27. unique_toolkit/_common/utils/__init__.py +1 -0
  28. unique_toolkit/_common/utils/files.py +43 -0
  29. unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
  30. unique_toolkit/_common/utils/structured_output/schema.py +5 -0
  31. unique_toolkit/_common/utils/write_configuration.py +51 -0
  32. unique_toolkit/_common/validators.py +101 -4
  33. unique_toolkit/agentic/__init__.py +1 -0
  34. unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
  35. unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
  36. unique_toolkit/agentic/evaluation/config.py +36 -0
  37. unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
  38. unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
  39. unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
  40. unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
  41. unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
  42. unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +111 -0
  43. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
  44. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +16 -15
  45. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +30 -20
  46. unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
  47. unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
  48. unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
  49. unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
  50. unique_toolkit/agentic/history_manager/history_construction_with_contents.py +297 -0
  51. unique_toolkit/agentic/history_manager/history_manager.py +242 -0
  52. unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
  53. unique_toolkit/agentic/history_manager/utils.py +96 -0
  54. unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
  55. unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
  56. unique_toolkit/agentic/responses_api/__init__.py +19 -0
  57. unique_toolkit/agentic/responses_api/postprocessors/code_display.py +63 -0
  58. unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +145 -0
  59. unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
  60. unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
  61. unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
  62. unique_toolkit/agentic/tools/__init__.py +1 -0
  63. unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
  64. unique_toolkit/agentic/tools/a2a/config.py +17 -0
  65. unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
  66. unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
  67. unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
  68. unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
  69. unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
  70. unique_toolkit/agentic/tools/a2a/manager.py +55 -0
  71. unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
  72. unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +185 -0
  73. unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +73 -0
  74. unique_toolkit/agentic/tools/a2a/postprocessing/config.py +45 -0
  75. unique_toolkit/agentic/tools/a2a/postprocessing/display.py +180 -0
  76. unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
  77. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +1335 -0
  78. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
  79. unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
  80. unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
  81. unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
  82. unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
  83. unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
  84. unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
  85. unique_toolkit/agentic/tools/a2a/tool/config.py +73 -0
  86. unique_toolkit/agentic/tools/a2a/tool/service.py +306 -0
  87. unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
  88. unique_toolkit/agentic/tools/config.py +167 -0
  89. unique_toolkit/agentic/tools/factory.py +44 -0
  90. unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
  91. unique_toolkit/agentic/tools/mcp/manager.py +71 -0
  92. unique_toolkit/agentic/tools/mcp/models.py +28 -0
  93. unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
  94. unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
  95. unique_toolkit/agentic/tools/openai_builtin/base.py +30 -0
  96. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
  97. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +57 -0
  98. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +230 -0
  99. unique_toolkit/agentic/tools/openai_builtin/manager.py +62 -0
  100. unique_toolkit/agentic/tools/schemas.py +141 -0
  101. unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
  102. unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
  103. unique_toolkit/agentic/tools/tool.py +183 -0
  104. unique_toolkit/agentic/tools/tool_manager.py +523 -0
  105. unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
  106. unique_toolkit/agentic/tools/utils/__init__.py +19 -0
  107. unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
  108. unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
  109. unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
  110. unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
  111. unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
  112. unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
  113. unique_toolkit/app/__init__.py +6 -0
  114. unique_toolkit/app/dev_util.py +180 -0
  115. unique_toolkit/app/init_sdk.py +32 -1
  116. unique_toolkit/app/schemas.py +198 -31
  117. unique_toolkit/app/unique_settings.py +367 -0
  118. unique_toolkit/chat/__init__.py +8 -1
  119. unique_toolkit/chat/deprecated/service.py +232 -0
  120. unique_toolkit/chat/functions.py +642 -77
  121. unique_toolkit/chat/rendering.py +34 -0
  122. unique_toolkit/chat/responses_api.py +461 -0
  123. unique_toolkit/chat/schemas.py +133 -2
  124. unique_toolkit/chat/service.py +115 -767
  125. unique_toolkit/content/functions.py +153 -4
  126. unique_toolkit/content/schemas.py +122 -15
  127. unique_toolkit/content/service.py +278 -44
  128. unique_toolkit/content/smart_rules.py +301 -0
  129. unique_toolkit/content/utils.py +8 -3
  130. unique_toolkit/embedding/service.py +102 -11
  131. unique_toolkit/framework_utilities/__init__.py +1 -0
  132. unique_toolkit/framework_utilities/langchain/client.py +71 -0
  133. unique_toolkit/framework_utilities/langchain/history.py +19 -0
  134. unique_toolkit/framework_utilities/openai/__init__.py +6 -0
  135. unique_toolkit/framework_utilities/openai/client.py +83 -0
  136. unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
  137. unique_toolkit/framework_utilities/utils.py +23 -0
  138. unique_toolkit/language_model/__init__.py +3 -0
  139. unique_toolkit/language_model/builder.py +27 -11
  140. unique_toolkit/language_model/default_language_model.py +3 -0
  141. unique_toolkit/language_model/functions.py +327 -43
  142. unique_toolkit/language_model/infos.py +992 -50
  143. unique_toolkit/language_model/reference.py +242 -0
  144. unique_toolkit/language_model/schemas.py +475 -48
  145. unique_toolkit/language_model/service.py +228 -27
  146. unique_toolkit/protocols/support.py +145 -0
  147. unique_toolkit/services/__init__.py +7 -0
  148. unique_toolkit/services/chat_service.py +1630 -0
  149. unique_toolkit/services/knowledge_base.py +861 -0
  150. unique_toolkit/short_term_memory/service.py +178 -41
  151. unique_toolkit/smart_rules/__init__.py +0 -0
  152. unique_toolkit/smart_rules/compile.py +56 -0
  153. unique_toolkit/test_utilities/events.py +197 -0
  154. {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/METADATA +606 -7
  155. unique_toolkit-1.23.0.dist-info/RECORD +182 -0
  156. unique_toolkit/evaluators/__init__.py +0 -1
  157. unique_toolkit/evaluators/config.py +0 -35
  158. unique_toolkit/evaluators/constants.py +0 -1
  159. unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
  160. unique_toolkit/evaluators/context_relevancy/service.py +0 -53
  161. unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
  162. unique_toolkit/evaluators/hallucination/constants.py +0 -41
  163. unique_toolkit-0.7.7.dist-info/RECORD +0 -64
  164. /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
  165. {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/LICENSE +0 -0
  166. {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,536 @@
1
+ import logging
2
+ from unittest.mock import Mock
3
+
4
+ import pytest
5
+ from pydantic import BaseModel
6
+
7
+ from tests.test_obj_factory import get_event_obj
8
+ from unique_toolkit.agentic.tools.a2a import SubAgentResponseWatcher
9
+ from unique_toolkit.agentic.tools.a2a.manager import A2AManager
10
+ from unique_toolkit.agentic.tools.config import (
11
+ ToolBuildConfig,
12
+ ToolIcon,
13
+ ToolSelectionPolicy,
14
+ )
15
+ from unique_toolkit.agentic.tools.factory import ToolFactory
16
+ from unique_toolkit.agentic.tools.mcp.manager import MCPManager
17
+ from unique_toolkit.agentic.tools.mcp.models import MCPToolConfig
18
+ from unique_toolkit.agentic.tools.mcp.tool_wrapper import MCPToolWrapper
19
+ from unique_toolkit.agentic.tools.schemas import BaseToolConfig
20
+ from unique_toolkit.agentic.tools.tool import Tool
21
+ from unique_toolkit.agentic.tools.tool_manager import ToolManager, ToolManagerConfig
22
+ from unique_toolkit.agentic.tools.tool_progress_reporter import ToolProgressReporter
23
+ from unique_toolkit.app.schemas import McpServer, McpTool
24
+ from unique_toolkit.chat.service import ChatService
25
+
26
+
27
+ class MockParameters(BaseModel):
28
+ pass
29
+
30
+
31
+ class MockInternalSearchTool(Tool[BaseToolConfig]):
32
+ """Mock internal search tool for testing"""
33
+
34
+ name = "internal_search"
35
+
36
+ def __init__(self, config, event, tool_progress_reporter=None):
37
+ super().__init__(config, event, tool_progress_reporter)
38
+
39
+ def tool_description(self):
40
+ from unique_toolkit.language_model.schemas import LanguageModelToolDescription
41
+
42
+ return LanguageModelToolDescription(
43
+ name="internal_search",
44
+ description="Internal search tool for testing",
45
+ parameters=MockParameters,
46
+ )
47
+
48
+ def tool_description_for_system_prompt(self) -> str:
49
+ return "Internal search tool for searching content"
50
+
51
+ def tool_format_information_for_system_prompt(self) -> str:
52
+ return "Use this tool to search for content"
53
+
54
+ def evaluation_check_list(self):
55
+ return []
56
+
57
+ def get_evaluation_checks_based_on_tool_response(self, tool_response):
58
+ return []
59
+
60
+ def get_tool_prompts(self):
61
+ from unique_toolkit.agentic.tools.schemas import ToolPrompts
62
+
63
+ return ToolPrompts()
64
+
65
+ async def run(self, tool_call):
66
+ from unique_toolkit.agentic.tools.schemas import ToolCallResponse
67
+
68
+ return ToolCallResponse(id=tool_call.id, name=tool_call.name, content_chunks=[])
69
+
70
+
71
+ class MockInternalSearchConfig(BaseToolConfig):
72
+ """Mock configuration for internal search tool"""
73
+
74
+ pass
75
+
76
+
77
+ class TestMCPManager:
78
+ @pytest.fixture(autouse=True)
79
+ def setup(self):
80
+ """Setup test environment"""
81
+ self.logger = logging.getLogger(__name__)
82
+
83
+ # Register mock internal tool
84
+ ToolFactory.register_tool(MockInternalSearchTool, MockInternalSearchConfig)
85
+
86
+ self.event = get_event_obj(
87
+ user_id="test_user",
88
+ company_id="test_company",
89
+ assistant_id="test_assistant",
90
+ chat_id="test_chat",
91
+ )
92
+
93
+ # Set tool choices to include both internal and MCP tools
94
+ self.event.payload.tool_choices = ["internal_search", "mcp_test_tool"]
95
+ self.event.payload.disabled_tools = []
96
+
97
+ @pytest.fixture
98
+ def mock_chat_service(self):
99
+ """Create mock chat service for tool progress reporter"""
100
+ return Mock(spec=ChatService)
101
+
102
+ @pytest.fixture
103
+ def tool_progress_reporter(self, mock_chat_service):
104
+ """Create tool progress reporter fixture"""
105
+ return ToolProgressReporter(mock_chat_service)
106
+
107
+ @pytest.fixture
108
+ def mcp_tools(self):
109
+ """Create mock MCP tools fixture"""
110
+ mcp_tool = McpTool(
111
+ id="mcp_test_tool_id",
112
+ name="mcp_test_tool",
113
+ description="Test MCP tool",
114
+ input_schema={
115
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
116
+ "type": "object",
117
+ "properties": {
118
+ "folder": {
119
+ "description": "Mail folder to read mails from",
120
+ "default": "inbox",
121
+ "type": "string",
122
+ },
123
+ "limit": {
124
+ "description": "Number of emails to retrieve",
125
+ "default": 10,
126
+ "type": "number",
127
+ "minimum": 1,
128
+ "maximum": 50,
129
+ },
130
+ },
131
+ },
132
+ output_schema=None,
133
+ annotations=None,
134
+ title="Test MCP Tool",
135
+ icon=None,
136
+ system_prompt=None,
137
+ user_prompt=None,
138
+ is_connected=True,
139
+ )
140
+ return [mcp_tool]
141
+
142
+ @pytest.fixture
143
+ def mcp_servers(self, mcp_tools):
144
+ """Create mock MCP servers fixture"""
145
+ server = McpServer(
146
+ id="test_server_id",
147
+ name="test_server",
148
+ description="Test MCP server",
149
+ tools=mcp_tools,
150
+ system_prompt="Test system prompt",
151
+ user_prompt="Test user prompt",
152
+ is_connected=True,
153
+ )
154
+ return [server]
155
+
156
+ @pytest.fixture
157
+ def internal_tools(self):
158
+ """Create internal tools fixture"""
159
+ internal_tool_config = ToolBuildConfig(
160
+ name="internal_search",
161
+ configuration=MockInternalSearchConfig(),
162
+ display_name="Internal Search",
163
+ icon=ToolIcon.BOOK,
164
+ selection_policy=ToolSelectionPolicy.BY_USER,
165
+ is_exclusive=False,
166
+ is_enabled=True,
167
+ )
168
+ return [internal_tool_config]
169
+
170
+ @pytest.fixture
171
+ def mcp_manager(self, mcp_servers, tool_progress_reporter):
172
+ """Create MCP manager fixture"""
173
+ return MCPManager(
174
+ mcp_servers=mcp_servers,
175
+ event=self.event,
176
+ tool_progress_reporter=tool_progress_reporter,
177
+ )
178
+
179
+ @pytest.fixture
180
+ def a2a_manager(self, tool_progress_reporter):
181
+ """Create MCP manager fixture"""
182
+ return A2AManager(
183
+ logger=self.logger,
184
+ tool_progress_reporter=tool_progress_reporter,
185
+ response_watcher=SubAgentResponseWatcher(),
186
+ )
187
+
188
+ @pytest.fixture
189
+ def tool_manager_config(self, internal_tools):
190
+ """Create tool manager configuration fixture"""
191
+ return ToolManagerConfig(tools=internal_tools, max_tool_calls=10)
192
+
193
+ @pytest.fixture
194
+ def tool_manager(
195
+ self, tool_manager_config, mcp_manager, a2a_manager, tool_progress_reporter
196
+ ):
197
+ """Create tool manager fixture"""
198
+
199
+ return ToolManager(
200
+ logger=self.logger,
201
+ config=tool_manager_config,
202
+ event=self.event,
203
+ tool_progress_reporter=tool_progress_reporter,
204
+ mcp_manager=mcp_manager,
205
+ a2a_manager=a2a_manager,
206
+ )
207
+
208
+ def test_tool_manager_initialization(self, tool_manager):
209
+ """Test tool manager is initialized correctly"""
210
+ assert tool_manager is not None
211
+
212
+ assert (
213
+ len(tool_manager.get_tools()) >= 2
214
+ ) # Should have both internal and MCP tools
215
+
216
+ def test_tool_manager_has_both_tool_types(self, tool_manager):
217
+ """Test that tool manager contains both MCP and internal tools"""
218
+ tools = tool_manager.get_tools()
219
+ tool_names = [tool.name for tool in tools]
220
+
221
+ # Should contain internal search tool
222
+ assert "internal_search" in tool_names
223
+
224
+ # Should contain MCP tool (wrapped)
225
+ assert "mcp_test_tool" in tool_names
226
+
227
+ # Should have at least 2 tools total
228
+ assert len(tools) >= 2
229
+
230
+ def test_tool_manager_can_get_tools_by_name(self, tool_manager):
231
+ """Test that tool manager can retrieve tools by name"""
232
+ # Test getting internal tool
233
+ internal_tool = tool_manager.get_tool_by_name("internal_search")
234
+ assert internal_tool is not None
235
+ assert internal_tool.name == "internal_search"
236
+
237
+ # Test getting MCP tool
238
+ mcp_tool = tool_manager.get_tool_by_name("mcp_test_tool")
239
+ assert mcp_tool is not None
240
+ assert mcp_tool.name == "mcp_test_tool"
241
+
242
+ def test_tool_manager_tools_property_contains_both_types(self, tool_manager):
243
+ """Test that the _tools property contains both internal and MCP tools"""
244
+ # Access the private _tools attribute directly to verify integration
245
+ tools = tool_manager._tools
246
+ tool_names = [tool.name for tool in tools]
247
+
248
+ # Verify both tool types are present
249
+ assert "internal_search" in tool_names, (
250
+ f"Internal tool missing. Available tools: {tool_names}"
251
+ )
252
+ assert "mcp_test_tool" in tool_names, (
253
+ f"MCP tool missing. Available tools: {tool_names}"
254
+ )
255
+
256
+ # Verify we have the expected number of tools
257
+ assert len(tools) == 2, f"Expected 2 tools, got {len(tools)}: {tool_names}"
258
+
259
+ def test_tool_manager_logs_loaded_tools(self, tool_manager, caplog):
260
+ """Test that tool manager logs the loaded tools correctly"""
261
+ with caplog.at_level(logging.INFO):
262
+ tool_manager.log_loaded_tools()
263
+
264
+ # Check that both tools are mentioned in the logs
265
+ log_output = caplog.text
266
+ assert "internal_search" in log_output
267
+ assert "mcp_test_tool" in log_output
268
+
269
+ def test_tool_manager_gets_tool_definitions(self, tool_manager):
270
+ """Test that tool manager returns tool definitions for both tool types"""
271
+ definitions = tool_manager.get_tool_definitions()
272
+
273
+ # Should have definitions for both tools
274
+ assert len(definitions) == 2
275
+
276
+ definition_names = [defn.name for defn in definitions]
277
+ assert "internal_search" in definition_names
278
+ assert "mcp_test_tool" in definition_names
279
+
280
+ def test_init_tools_method(
281
+ self, tool_manager_config, mcp_manager, tool_progress_reporter
282
+ ):
283
+ """Test the _init__tools method behavior with different scenarios"""
284
+
285
+ # Test 1: Normal initialization with both tool types
286
+
287
+ a2a_manager = A2AManager(
288
+ logger=self.logger,
289
+ tool_progress_reporter=tool_progress_reporter,
290
+ response_watcher=SubAgentResponseWatcher(),
291
+ )
292
+
293
+ tool_manager = ToolManager(
294
+ logger=self.logger,
295
+ config=tool_manager_config,
296
+ event=self.event,
297
+ tool_progress_reporter=tool_progress_reporter,
298
+ mcp_manager=mcp_manager,
299
+ a2a_manager=a2a_manager,
300
+ )
301
+
302
+ # Verify both tools are loaded
303
+ tools = tool_manager.get_tools()
304
+ tool_names = [tool.name for tool in tools]
305
+ assert "internal_search" in tool_names
306
+ assert "mcp_test_tool" in tool_names
307
+ assert len(tools) == 2
308
+
309
+ def test_init_tools_with_disabled_tools(
310
+ self, tool_manager_config, mcp_manager, tool_progress_reporter
311
+ ):
312
+ """Test _init__tools method when some tools are disabled"""
313
+
314
+ # Modify event to disable the internal search tool
315
+ event_with_disabled = get_event_obj(
316
+ user_id="test_user",
317
+ company_id="test_company",
318
+ assistant_id="test_assistant",
319
+ chat_id="test_chat",
320
+ )
321
+ event_with_disabled.payload.tool_choices = ["internal_search", "mcp_test_tool"]
322
+ event_with_disabled.payload.disabled_tools = ["internal_search"]
323
+
324
+ a2a_manager = A2AManager(
325
+ logger=self.logger,
326
+ tool_progress_reporter=tool_progress_reporter,
327
+ response_watcher=SubAgentResponseWatcher(),
328
+ )
329
+
330
+ tool_manager = ToolManager(
331
+ logger=self.logger,
332
+ config=tool_manager_config,
333
+ event=event_with_disabled,
334
+ tool_progress_reporter=tool_progress_reporter,
335
+ mcp_manager=mcp_manager,
336
+ a2a_manager=a2a_manager,
337
+ )
338
+
339
+ # Should only have MCP tool, internal tool should be filtered out
340
+ tools = tool_manager.get_tools()
341
+ tool_names = [tool.name for tool in tools]
342
+ assert "internal_search" not in tool_names
343
+ assert "mcp_test_tool" in tool_names
344
+ assert len(tools) == 1
345
+
346
+ def test_init_tools_with_limited_tool_choices(
347
+ self, tool_manager_config, mcp_manager, tool_progress_reporter
348
+ ):
349
+ """Test _init__tools method when only specific tools are chosen"""
350
+
351
+ # Modify event to only choose internal search tool
352
+ event_with_limited_choices = get_event_obj(
353
+ user_id="test_user",
354
+ company_id="test_company",
355
+ assistant_id="test_assistant",
356
+ chat_id="test_chat",
357
+ )
358
+ event_with_limited_choices.payload.tool_choices = ["internal_search"]
359
+ event_with_limited_choices.payload.disabled_tools = []
360
+
361
+ a2a_manager = A2AManager(
362
+ logger=self.logger,
363
+ tool_progress_reporter=tool_progress_reporter,
364
+ response_watcher=SubAgentResponseWatcher(),
365
+ )
366
+
367
+ tool_manager = ToolManager(
368
+ logger=self.logger,
369
+ config=tool_manager_config,
370
+ event=event_with_limited_choices,
371
+ tool_progress_reporter=tool_progress_reporter,
372
+ mcp_manager=mcp_manager,
373
+ a2a_manager=a2a_manager,
374
+ )
375
+
376
+ # Should only have internal search tool
377
+ tools = tool_manager.get_tools()
378
+ tool_names = [tool.name for tool in tools]
379
+ assert "internal_search" in tool_names
380
+ assert "mcp_test_tool" not in tool_names
381
+ assert len(tools) == 1
382
+
383
+ def test_init_tools_with_exclusive_tool(self, mcp_manager, tool_progress_reporter):
384
+ """Test _init__tools method when an exclusive tool is present"""
385
+
386
+ # Create an exclusive tool configuration
387
+ exclusive_tool_config = ToolBuildConfig(
388
+ name="internal_search",
389
+ configuration=MockInternalSearchConfig(),
390
+ display_name="Internal Search",
391
+ icon=ToolIcon.BOOK,
392
+ selection_policy=ToolSelectionPolicy.BY_USER,
393
+ is_exclusive=True, # Make it exclusive
394
+ is_enabled=True,
395
+ )
396
+
397
+ config_with_exclusive = ToolManagerConfig(
398
+ tools=[exclusive_tool_config], max_tool_calls=10
399
+ )
400
+
401
+ a2a_manager = A2AManager(
402
+ logger=self.logger,
403
+ tool_progress_reporter=tool_progress_reporter,
404
+ response_watcher=SubAgentResponseWatcher(),
405
+ )
406
+
407
+ tool_manager = ToolManager(
408
+ logger=self.logger,
409
+ config=config_with_exclusive,
410
+ event=self.event,
411
+ tool_progress_reporter=tool_progress_reporter,
412
+ mcp_manager=mcp_manager,
413
+ a2a_manager=a2a_manager,
414
+ )
415
+
416
+ # Should only have the exclusive tool, MCP tools should be ignored
417
+ tools = tool_manager.get_tools()
418
+ tool_names = [tool.name for tool in tools]
419
+ assert "internal_search" in tool_names
420
+ assert "mcp_test_tool" not in tool_names
421
+ assert len(tools) == 1
422
+
423
+ def test_init_tools_with_disabled_tool_config(
424
+ self, mcp_manager, tool_progress_reporter
425
+ ):
426
+ """Test _init__tools method when a tool is disabled in its configuration"""
427
+
428
+ # Create a disabled tool configuration
429
+ disabled_tool_config = ToolBuildConfig(
430
+ name="internal_search",
431
+ configuration=MockInternalSearchConfig(),
432
+ display_name="Internal Search",
433
+ icon=ToolIcon.BOOK,
434
+ selection_policy=ToolSelectionPolicy.BY_USER,
435
+ is_exclusive=False,
436
+ is_enabled=False, # Disable the tool
437
+ )
438
+
439
+ config_with_disabled = ToolManagerConfig(
440
+ tools=[disabled_tool_config], max_tool_calls=10
441
+ )
442
+
443
+ a2a_manager = A2AManager(
444
+ logger=self.logger,
445
+ tool_progress_reporter=tool_progress_reporter,
446
+ response_watcher=SubAgentResponseWatcher(),
447
+ )
448
+
449
+ tool_manager = ToolManager(
450
+ logger=self.logger,
451
+ config=config_with_disabled,
452
+ event=self.event,
453
+ tool_progress_reporter=tool_progress_reporter,
454
+ mcp_manager=mcp_manager,
455
+ a2a_manager=a2a_manager,
456
+ )
457
+
458
+ # Should only have MCP tool, disabled internal tool should be filtered out
459
+ tools = tool_manager.get_tools()
460
+ tool_names = [tool.name for tool in tools]
461
+ assert "internal_search" not in tool_names
462
+ assert "mcp_test_tool" in tool_names
463
+ assert len(tools) == 1
464
+
465
+ def test_mcp_tool_wrapper_generates_correct_parameters(
466
+ self, mcp_tools, mcp_servers, tool_progress_reporter
467
+ ):
468
+ """Test that MCPToolWrapper correctly generates parameters field from MCP tool input schema"""
469
+ # Get the first MCP tool and server from fixtures
470
+ mcp_tool = mcp_tools[0]
471
+ mcp_server = mcp_servers[0]
472
+
473
+ # Create MCPToolConfig
474
+ config = MCPToolConfig(
475
+ server_id="test_server_id",
476
+ server_name="test_server",
477
+ mcp_source_id="test_server_id",
478
+ )
479
+
480
+ # Initialize the MCPToolWrapper
481
+ tool_wrapper = MCPToolWrapper(
482
+ mcp_server=mcp_server,
483
+ mcp_tool=mcp_tool,
484
+ config=config,
485
+ event=self.event,
486
+ tool_progress_reporter=tool_progress_reporter,
487
+ )
488
+
489
+ # Call tool_description to get the LanguageModelToolDescription
490
+ tool_description = tool_wrapper.tool_description()
491
+
492
+ # Verify the basic properties
493
+ assert tool_description.name == "mcp_test_tool"
494
+ assert tool_description.description == "Test MCP tool"
495
+
496
+ # Verify that parameters field is correctly set to the input_schema
497
+ expected_parameters = {
498
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
499
+ "type": "object",
500
+ "properties": {
501
+ "folder": {
502
+ "description": "Mail folder to read mails from",
503
+ "default": "inbox",
504
+ "type": "string",
505
+ },
506
+ "limit": {
507
+ "description": "Number of emails to retrieve",
508
+ "default": 10,
509
+ "type": "number",
510
+ "minimum": 1,
511
+ "maximum": 50,
512
+ },
513
+ },
514
+ }
515
+
516
+ assert tool_description.parameters == expected_parameters
517
+
518
+ # Verify specific parameter properties
519
+ parameters = tool_description.parameters
520
+ assert isinstance(parameters, dict)
521
+ assert parameters["type"] == "object"
522
+ assert "properties" in parameters
523
+
524
+ # Check folder parameter
525
+ folder_prop = parameters["properties"]["folder"]
526
+ assert folder_prop["type"] == "string"
527
+ assert folder_prop["description"] == "Mail folder to read mails from"
528
+ assert folder_prop["default"] == "inbox"
529
+
530
+ # Check limit parameter
531
+ limit_prop = parameters["properties"]["limit"]
532
+ assert limit_prop["type"] == "number"
533
+ assert limit_prop["description"] == "Number of emails to retrieve"
534
+ assert limit_prop["default"] == 10
535
+ assert limit_prop["minimum"] == 1
536
+ assert limit_prop["maximum"] == 50