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.
- unique_toolkit/__init__.py +28 -1
- unique_toolkit/_common/api_calling/human_verification_manager.py +343 -0
- unique_toolkit/_common/base_model_type_attribute.py +303 -0
- unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
- unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
- unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
- unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
- unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
- unique_toolkit/_common/default_language_model.py +12 -0
- unique_toolkit/_common/docx_generator/__init__.py +7 -0
- unique_toolkit/_common/docx_generator/config.py +12 -0
- unique_toolkit/_common/docx_generator/schemas.py +80 -0
- unique_toolkit/_common/docx_generator/service.py +252 -0
- unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
- unique_toolkit/_common/endpoint_builder.py +305 -0
- unique_toolkit/_common/endpoint_requestor.py +430 -0
- unique_toolkit/_common/exception.py +24 -0
- unique_toolkit/_common/feature_flags/schema.py +9 -0
- unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
- unique_toolkit/_common/pydantic_helpers.py +154 -0
- unique_toolkit/_common/referencing.py +53 -0
- unique_toolkit/_common/string_utilities.py +140 -0
- unique_toolkit/_common/tests/test_referencing.py +521 -0
- unique_toolkit/_common/tests/test_string_utilities.py +506 -0
- unique_toolkit/_common/token/image_token_counting.py +67 -0
- unique_toolkit/_common/token/token_counting.py +204 -0
- unique_toolkit/_common/utils/__init__.py +1 -0
- unique_toolkit/_common/utils/files.py +43 -0
- unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
- unique_toolkit/_common/utils/structured_output/schema.py +5 -0
- unique_toolkit/_common/utils/write_configuration.py +51 -0
- unique_toolkit/_common/validators.py +101 -4
- unique_toolkit/agentic/__init__.py +1 -0
- unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
- unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
- unique_toolkit/agentic/evaluation/config.py +36 -0
- unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
- unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
- unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
- unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
- unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
- unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +111 -0
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +16 -15
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +30 -20
- unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
- unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
- unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
- unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
- unique_toolkit/agentic/history_manager/history_construction_with_contents.py +297 -0
- unique_toolkit/agentic/history_manager/history_manager.py +242 -0
- unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
- unique_toolkit/agentic/history_manager/utils.py +96 -0
- unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
- unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
- unique_toolkit/agentic/responses_api/__init__.py +19 -0
- unique_toolkit/agentic/responses_api/postprocessors/code_display.py +63 -0
- unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +145 -0
- unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
- unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
- unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
- unique_toolkit/agentic/tools/__init__.py +1 -0
- unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
- unique_toolkit/agentic/tools/a2a/config.py +17 -0
- unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
- unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
- unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
- unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
- unique_toolkit/agentic/tools/a2a/manager.py +55 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +185 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +73 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py +45 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/display.py +180 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +1335 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
- unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
- unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
- unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
- unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
- unique_toolkit/agentic/tools/a2a/tool/config.py +73 -0
- unique_toolkit/agentic/tools/a2a/tool/service.py +306 -0
- unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
- unique_toolkit/agentic/tools/config.py +167 -0
- unique_toolkit/agentic/tools/factory.py +44 -0
- unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
- unique_toolkit/agentic/tools/mcp/manager.py +71 -0
- unique_toolkit/agentic/tools/mcp/models.py +28 -0
- unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
- unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
- unique_toolkit/agentic/tools/openai_builtin/base.py +30 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +57 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +230 -0
- unique_toolkit/agentic/tools/openai_builtin/manager.py +62 -0
- unique_toolkit/agentic/tools/schemas.py +141 -0
- unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
- unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
- unique_toolkit/agentic/tools/tool.py +183 -0
- unique_toolkit/agentic/tools/tool_manager.py +523 -0
- unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
- unique_toolkit/agentic/tools/utils/__init__.py +19 -0
- unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
- unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
- unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
- unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
- unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
- unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
- unique_toolkit/app/__init__.py +6 -0
- unique_toolkit/app/dev_util.py +180 -0
- unique_toolkit/app/init_sdk.py +32 -1
- unique_toolkit/app/schemas.py +198 -31
- unique_toolkit/app/unique_settings.py +367 -0
- unique_toolkit/chat/__init__.py +8 -1
- unique_toolkit/chat/deprecated/service.py +232 -0
- unique_toolkit/chat/functions.py +642 -77
- unique_toolkit/chat/rendering.py +34 -0
- unique_toolkit/chat/responses_api.py +461 -0
- unique_toolkit/chat/schemas.py +133 -2
- unique_toolkit/chat/service.py +115 -767
- unique_toolkit/content/functions.py +153 -4
- unique_toolkit/content/schemas.py +122 -15
- unique_toolkit/content/service.py +278 -44
- unique_toolkit/content/smart_rules.py +301 -0
- unique_toolkit/content/utils.py +8 -3
- unique_toolkit/embedding/service.py +102 -11
- unique_toolkit/framework_utilities/__init__.py +1 -0
- unique_toolkit/framework_utilities/langchain/client.py +71 -0
- unique_toolkit/framework_utilities/langchain/history.py +19 -0
- unique_toolkit/framework_utilities/openai/__init__.py +6 -0
- unique_toolkit/framework_utilities/openai/client.py +83 -0
- unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
- unique_toolkit/framework_utilities/utils.py +23 -0
- unique_toolkit/language_model/__init__.py +3 -0
- unique_toolkit/language_model/builder.py +27 -11
- unique_toolkit/language_model/default_language_model.py +3 -0
- unique_toolkit/language_model/functions.py +327 -43
- unique_toolkit/language_model/infos.py +992 -50
- unique_toolkit/language_model/reference.py +242 -0
- unique_toolkit/language_model/schemas.py +475 -48
- unique_toolkit/language_model/service.py +228 -27
- unique_toolkit/protocols/support.py +145 -0
- unique_toolkit/services/__init__.py +7 -0
- unique_toolkit/services/chat_service.py +1630 -0
- unique_toolkit/services/knowledge_base.py +861 -0
- unique_toolkit/short_term_memory/service.py +178 -41
- unique_toolkit/smart_rules/__init__.py +0 -0
- unique_toolkit/smart_rules/compile.py +56 -0
- unique_toolkit/test_utilities/events.py +197 -0
- {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/METADATA +606 -7
- unique_toolkit-1.23.0.dist-info/RECORD +182 -0
- unique_toolkit/evaluators/__init__.py +0 -1
- unique_toolkit/evaluators/config.py +0 -35
- unique_toolkit/evaluators/constants.py +0 -1
- unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
- unique_toolkit/evaluators/context_relevancy/service.py +0 -53
- unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
- unique_toolkit/evaluators/hallucination/constants.py +0 -41
- unique_toolkit-0.7.7.dist-info/RECORD +0 -64
- /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
- {unique_toolkit-0.7.7.dist-info → unique_toolkit-1.23.0.dist-info}/LICENSE +0 -0
- {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
|