unique_toolkit 1.8.1__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 +20 -0
- unique_toolkit/_common/api_calling/human_verification_manager.py +121 -28
- unique_toolkit/_common/chunk_relevancy_sorter/config.py +3 -3
- unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +2 -5
- unique_toolkit/_common/default_language_model.py +9 -3
- 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 +138 -117
- unique_toolkit/_common/endpoint_requestor.py +240 -14
- unique_toolkit/_common/exception.py +20 -0
- unique_toolkit/_common/feature_flags/schema.py +1 -5
- unique_toolkit/_common/referencing.py +53 -0
- unique_toolkit/_common/string_utilities.py +52 -1
- unique_toolkit/_common/tests/test_referencing.py +521 -0
- unique_toolkit/_common/tests/test_string_utilities.py +506 -0
- unique_toolkit/_common/utils/files.py +43 -0
- unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +16 -6
- unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
- unique_toolkit/agentic/evaluation/config.py +3 -2
- unique_toolkit/agentic/evaluation/context_relevancy/service.py +2 -2
- unique_toolkit/agentic/evaluation/evaluation_manager.py +9 -5
- unique_toolkit/agentic/evaluation/hallucination/constants.py +1 -1
- unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +26 -3
- unique_toolkit/agentic/history_manager/history_manager.py +14 -11
- unique_toolkit/agentic/history_manager/loop_token_reducer.py +3 -4
- unique_toolkit/agentic/history_manager/utils.py +10 -87
- unique_toolkit/agentic/postprocessor/postprocessor_manager.py +107 -16
- unique_toolkit/agentic/reference_manager/reference_manager.py +1 -1
- 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/tools/a2a/__init__.py +18 -2
- unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +2 -0
- unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +3 -3
- unique_toolkit/agentic/tools/a2a/evaluation/config.py +1 -1
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +143 -91
- unique_toolkit/agentic/tools/a2a/manager.py +7 -1
- unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +11 -3
- 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 +21 -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/config.py +15 -5
- unique_toolkit/agentic/tools/a2a/tool/service.py +69 -36
- unique_toolkit/agentic/tools/config.py +16 -2
- unique_toolkit/agentic/tools/factory.py +4 -0
- unique_toolkit/agentic/tools/mcp/tool_wrapper.py +7 -35
- 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/test/test_mcp_manager.py +95 -7
- unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +240 -0
- unique_toolkit/agentic/tools/tool.py +0 -11
- unique_toolkit/agentic/tools/tool_manager.py +337 -122
- unique_toolkit/agentic/tools/tool_progress_reporter.py +81 -15
- unique_toolkit/agentic/tools/utils/__init__.py +18 -0
- unique_toolkit/agentic/tools/utils/execution/execution.py +8 -4
- unique_toolkit/agentic/tools/utils/source_handling/schema.py +1 -1
- unique_toolkit/chat/__init__.py +8 -1
- unique_toolkit/chat/deprecated/service.py +232 -0
- unique_toolkit/chat/functions.py +54 -40
- unique_toolkit/chat/rendering.py +34 -0
- unique_toolkit/chat/responses_api.py +461 -0
- unique_toolkit/chat/schemas.py +1 -1
- unique_toolkit/chat/service.py +96 -1569
- unique_toolkit/content/functions.py +116 -1
- unique_toolkit/content/schemas.py +59 -0
- unique_toolkit/content/service.py +5 -37
- unique_toolkit/content/smart_rules.py +301 -0
- unique_toolkit/framework_utilities/langchain/client.py +27 -3
- unique_toolkit/framework_utilities/openai/client.py +12 -1
- unique_toolkit/framework_utilities/openai/message_builder.py +85 -1
- unique_toolkit/language_model/default_language_model.py +3 -0
- unique_toolkit/language_model/functions.py +25 -9
- unique_toolkit/language_model/infos.py +72 -4
- unique_toolkit/language_model/schemas.py +246 -40
- unique_toolkit/protocols/support.py +91 -9
- 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/smart_rules/compile.py +56 -301
- unique_toolkit/test_utilities/events.py +197 -0
- {unique_toolkit-1.8.1.dist-info → unique_toolkit-1.23.0.dist-info}/METADATA +173 -3
- {unique_toolkit-1.8.1.dist-info → unique_toolkit-1.23.0.dist-info}/RECORD +99 -67
- unique_toolkit/agentic/tools/a2a/postprocessing/_display.py +0 -122
- unique_toolkit/agentic/tools/a2a/postprocessing/_utils.py +0 -19
- unique_toolkit/agentic/tools/a2a/postprocessing/postprocessor.py +0 -230
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_consolidate_references.py +0 -665
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +0 -391
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_postprocessor_reference_functions.py +0 -256
- {unique_toolkit-1.8.1.dist-info → unique_toolkit-1.23.0.dist-info}/LICENSE +0 -0
- {unique_toolkit-1.8.1.dist-info → unique_toolkit-1.23.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import override
|
|
3
|
+
|
|
4
|
+
from openai import AsyncOpenAI, BaseModel, NotFoundError
|
|
5
|
+
from openai.types.responses.tool_param import CodeInterpreter
|
|
6
|
+
|
|
7
|
+
from unique_toolkit import ContentService, ShortTermMemoryService
|
|
8
|
+
from unique_toolkit.agentic.short_term_memory_manager.persistent_short_term_memory_manager import (
|
|
9
|
+
PersistentShortMemoryManager,
|
|
10
|
+
)
|
|
11
|
+
from unique_toolkit.agentic.tools.openai_builtin.base import (
|
|
12
|
+
OpenAIBuiltInTool,
|
|
13
|
+
OpenAIBuiltInToolName,
|
|
14
|
+
)
|
|
15
|
+
from unique_toolkit.agentic.tools.openai_builtin.code_interpreter.config import (
|
|
16
|
+
OpenAICodeInterpreterConfig,
|
|
17
|
+
)
|
|
18
|
+
from unique_toolkit.agentic.tools.schemas import ToolPrompts
|
|
19
|
+
from unique_toolkit.content.schemas import (
|
|
20
|
+
Content,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_SHORT_TERM_MEMORY_NAME = "container_code_execution"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CodeExecutionShortTermMemorySchema(BaseModel):
|
|
30
|
+
container_id: str | None = None
|
|
31
|
+
file_ids: dict[str, str] = {} # Mapping of unique file id to openai file id
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
CodeExecutionMemoryManager = PersistentShortMemoryManager[
|
|
35
|
+
CodeExecutionShortTermMemorySchema
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_container_code_execution_short_term_memory_manager(
|
|
40
|
+
company_id: str, user_id: str, chat_id: str
|
|
41
|
+
) -> CodeExecutionMemoryManager:
|
|
42
|
+
short_term_memory_service = ShortTermMemoryService(
|
|
43
|
+
company_id=company_id,
|
|
44
|
+
user_id=user_id,
|
|
45
|
+
chat_id=chat_id,
|
|
46
|
+
message_id=None,
|
|
47
|
+
)
|
|
48
|
+
short_term_memory_manager = PersistentShortMemoryManager(
|
|
49
|
+
short_term_memory_service=short_term_memory_service,
|
|
50
|
+
short_term_memory_schema=CodeExecutionShortTermMemorySchema,
|
|
51
|
+
short_term_memory_name=_SHORT_TERM_MEMORY_NAME,
|
|
52
|
+
)
|
|
53
|
+
return short_term_memory_manager
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def _create_container_if_not_exists(
|
|
57
|
+
client: AsyncOpenAI,
|
|
58
|
+
chat_id: str,
|
|
59
|
+
user_id: str,
|
|
60
|
+
company_id: str,
|
|
61
|
+
expires_after_minutes: int,
|
|
62
|
+
memory: CodeExecutionShortTermMemorySchema | None = None,
|
|
63
|
+
) -> CodeExecutionShortTermMemorySchema:
|
|
64
|
+
if memory is not None:
|
|
65
|
+
logger.info("Container found in short term memory")
|
|
66
|
+
else:
|
|
67
|
+
logger.info("No Container in short term memory, creating a new container")
|
|
68
|
+
memory = CodeExecutionShortTermMemorySchema()
|
|
69
|
+
|
|
70
|
+
container_id = memory.container_id
|
|
71
|
+
|
|
72
|
+
if container_id is not None:
|
|
73
|
+
try:
|
|
74
|
+
container = await client.containers.retrieve(container_id)
|
|
75
|
+
if container.status not in ["active", "running"]:
|
|
76
|
+
logger.info(
|
|
77
|
+
"Container has status `%s`, recreating a new one", container.status
|
|
78
|
+
)
|
|
79
|
+
container_id = None
|
|
80
|
+
except NotFoundError:
|
|
81
|
+
container_id = None
|
|
82
|
+
|
|
83
|
+
if container_id is None:
|
|
84
|
+
memory = CodeExecutionShortTermMemorySchema()
|
|
85
|
+
|
|
86
|
+
container = await client.containers.create(
|
|
87
|
+
name=f"code_execution_{company_id}_{user_id}_{chat_id}",
|
|
88
|
+
expires_after={
|
|
89
|
+
"anchor": "last_active_at",
|
|
90
|
+
"minutes": expires_after_minutes,
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
memory.container_id = container.id
|
|
95
|
+
|
|
96
|
+
return memory
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def _upload_files_to_container(
|
|
100
|
+
client: AsyncOpenAI,
|
|
101
|
+
uploaded_files: list[Content],
|
|
102
|
+
memory: CodeExecutionShortTermMemorySchema,
|
|
103
|
+
content_service: ContentService,
|
|
104
|
+
chat_id: str,
|
|
105
|
+
) -> CodeExecutionShortTermMemorySchema:
|
|
106
|
+
container_id = memory.container_id
|
|
107
|
+
|
|
108
|
+
assert container_id is not None
|
|
109
|
+
|
|
110
|
+
memory = memory.model_copy(deep=True)
|
|
111
|
+
|
|
112
|
+
for file in uploaded_files:
|
|
113
|
+
upload = True
|
|
114
|
+
if file.id in memory.file_ids:
|
|
115
|
+
try:
|
|
116
|
+
_ = await client.containers.files.retrieve(
|
|
117
|
+
container_id=container_id, file_id=memory.file_ids[file.id]
|
|
118
|
+
)
|
|
119
|
+
logger.info("File with id %s already uploaded to container", file.id)
|
|
120
|
+
upload = False
|
|
121
|
+
except NotFoundError:
|
|
122
|
+
upload = True
|
|
123
|
+
|
|
124
|
+
if upload:
|
|
125
|
+
logger.info(
|
|
126
|
+
"Uploding file %s to container %s", file.id, memory.container_id
|
|
127
|
+
)
|
|
128
|
+
file_content = content_service.download_content_to_bytes(
|
|
129
|
+
content_id=file.id, chat_id=chat_id
|
|
130
|
+
) # TODO: Use async version when available
|
|
131
|
+
|
|
132
|
+
openai_file = await client.containers.files.create(
|
|
133
|
+
container_id=container_id,
|
|
134
|
+
file=(file.key, file_content),
|
|
135
|
+
)
|
|
136
|
+
memory.file_ids[file.id] = openai_file.id
|
|
137
|
+
|
|
138
|
+
return memory
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class OpenAICodeInterpreterTool(OpenAIBuiltInTool[CodeInterpreter]):
|
|
142
|
+
DISPLAY_NAME = "Code Interpreter"
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
config: OpenAICodeInterpreterConfig,
|
|
147
|
+
container_id: str | None,
|
|
148
|
+
):
|
|
149
|
+
self._config = config
|
|
150
|
+
|
|
151
|
+
if not config.use_auto_container and container_id is None:
|
|
152
|
+
raise ValueError("`container_id` required when not using `auto` containers")
|
|
153
|
+
|
|
154
|
+
self._container_id = container_id
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
@override
|
|
158
|
+
def name(self) -> OpenAIBuiltInToolName:
|
|
159
|
+
return OpenAIBuiltInToolName.CODE_INTERPRETER
|
|
160
|
+
|
|
161
|
+
@override
|
|
162
|
+
def tool_description(self) -> CodeInterpreter:
|
|
163
|
+
if self._config.use_auto_container:
|
|
164
|
+
return {"container": {"type": "auto"}, "type": "code_interpreter"}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"container": self._container_id, # type: ignore
|
|
168
|
+
"type": "code_interpreter",
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
async def build_tool(
|
|
173
|
+
cls,
|
|
174
|
+
config: OpenAICodeInterpreterConfig,
|
|
175
|
+
uploaded_files: list[Content],
|
|
176
|
+
client: AsyncOpenAI,
|
|
177
|
+
content_service: ContentService,
|
|
178
|
+
company_id: str,
|
|
179
|
+
user_id: str,
|
|
180
|
+
chat_id: str,
|
|
181
|
+
) -> "OpenAICodeInterpreterTool":
|
|
182
|
+
if config.use_auto_container:
|
|
183
|
+
logger.info("Using `auto` container setting")
|
|
184
|
+
return cls(config=config, container_id=None)
|
|
185
|
+
|
|
186
|
+
memory_manager = _get_container_code_execution_short_term_memory_manager(
|
|
187
|
+
company_id=company_id,
|
|
188
|
+
user_id=user_id,
|
|
189
|
+
chat_id=chat_id,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
memory = await memory_manager.load_async()
|
|
193
|
+
|
|
194
|
+
memory = await _create_container_if_not_exists(
|
|
195
|
+
client=client,
|
|
196
|
+
memory=memory,
|
|
197
|
+
chat_id=chat_id,
|
|
198
|
+
user_id=user_id,
|
|
199
|
+
company_id=company_id,
|
|
200
|
+
expires_after_minutes=config.expires_after_minutes,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
memory = await _upload_files_to_container(
|
|
204
|
+
client=client,
|
|
205
|
+
uploaded_files=uploaded_files,
|
|
206
|
+
content_service=content_service,
|
|
207
|
+
chat_id=chat_id,
|
|
208
|
+
memory=memory,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
await memory_manager.save_async(memory)
|
|
212
|
+
|
|
213
|
+
assert memory.container_id is not None
|
|
214
|
+
|
|
215
|
+
return OpenAICodeInterpreterTool(
|
|
216
|
+
config=config, container_id=memory.container_id
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
@override
|
|
220
|
+
def get_tool_prompts(self) -> ToolPrompts:
|
|
221
|
+
return ToolPrompts(
|
|
222
|
+
name="the python tool", # https://platform.openai.com/docs/guides/tools-code-interpreter
|
|
223
|
+
display_name=self.DISPLAY_NAME,
|
|
224
|
+
tool_description=self._config.tool_description,
|
|
225
|
+
tool_system_prompt=self._config.tool_description_for_system_prompt,
|
|
226
|
+
tool_format_information_for_system_prompt=self._config.tool_format_information_for_system_prompt,
|
|
227
|
+
tool_user_prompt=self._config.tool_description_for_user_prompt,
|
|
228
|
+
tool_format_information_for_user_prompt=self._config.tool_format_information_for_user_prompt,
|
|
229
|
+
input_model={},
|
|
230
|
+
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from openai import AsyncOpenAI
|
|
2
|
+
|
|
3
|
+
from unique_toolkit.agentic.tools.config import ToolBuildConfig
|
|
4
|
+
from unique_toolkit.agentic.tools.openai_builtin.base import (
|
|
5
|
+
OpenAIBuiltInTool,
|
|
6
|
+
OpenAIBuiltInToolName,
|
|
7
|
+
)
|
|
8
|
+
from unique_toolkit.agentic.tools.openai_builtin.code_interpreter import (
|
|
9
|
+
OpenAICodeInterpreterConfig,
|
|
10
|
+
OpenAICodeInterpreterTool,
|
|
11
|
+
)
|
|
12
|
+
from unique_toolkit.content.schemas import Content
|
|
13
|
+
from unique_toolkit.content.service import ContentService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OpenAIBuiltInToolManager:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
uploaded_files: list[Content],
|
|
20
|
+
content_service: ContentService,
|
|
21
|
+
user_id: str,
|
|
22
|
+
company_id: str,
|
|
23
|
+
chat_id: str,
|
|
24
|
+
client: AsyncOpenAI,
|
|
25
|
+
):
|
|
26
|
+
self._uploaded_files = uploaded_files
|
|
27
|
+
self._content_service = content_service
|
|
28
|
+
self._user_id = user_id
|
|
29
|
+
self._company_id = company_id
|
|
30
|
+
self._client = client
|
|
31
|
+
self._chat_id = chat_id
|
|
32
|
+
|
|
33
|
+
async def _build_tool(self, tool_config: ToolBuildConfig) -> OpenAIBuiltInTool:
|
|
34
|
+
if tool_config.name == OpenAIBuiltInToolName.CODE_INTERPRETER:
|
|
35
|
+
assert isinstance(tool_config.configuration, OpenAICodeInterpreterConfig)
|
|
36
|
+
tool = await OpenAICodeInterpreterTool.build_tool(
|
|
37
|
+
config=tool_config.configuration,
|
|
38
|
+
uploaded_files=self._uploaded_files,
|
|
39
|
+
user_id=self._user_id,
|
|
40
|
+
company_id=self._company_id,
|
|
41
|
+
chat_id=self._chat_id,
|
|
42
|
+
content_service=self._content_service,
|
|
43
|
+
client=self._client,
|
|
44
|
+
)
|
|
45
|
+
return tool
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError(f"Unknown built-in tool name: {tool_config.name}")
|
|
48
|
+
|
|
49
|
+
async def get_all_openai_builtin_tools(
|
|
50
|
+
self, tool_configs: list[ToolBuildConfig]
|
|
51
|
+
) -> tuple[list[ToolBuildConfig], list[OpenAIBuiltInTool]]:
|
|
52
|
+
openai_builtin_tools = []
|
|
53
|
+
filtered_tool_configs = []
|
|
54
|
+
|
|
55
|
+
for tool_config in tool_configs:
|
|
56
|
+
if tool_config.name not in OpenAIBuiltInToolName:
|
|
57
|
+
filtered_tool_configs.append(tool_config)
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
openai_builtin_tools.append(await self._build_tool(tool_config))
|
|
61
|
+
|
|
62
|
+
return filtered_tool_configs, openai_builtin_tools
|
|
@@ -5,6 +5,7 @@ import pytest
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
|
|
7
7
|
from tests.test_obj_factory import get_event_obj
|
|
8
|
+
from unique_toolkit.agentic.tools.a2a import SubAgentResponseWatcher
|
|
8
9
|
from unique_toolkit.agentic.tools.a2a.manager import A2AManager
|
|
9
10
|
from unique_toolkit.agentic.tools.config import (
|
|
10
11
|
ToolBuildConfig,
|
|
@@ -13,6 +14,8 @@ from unique_toolkit.agentic.tools.config import (
|
|
|
13
14
|
)
|
|
14
15
|
from unique_toolkit.agentic.tools.factory import ToolFactory
|
|
15
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
|
|
16
19
|
from unique_toolkit.agentic.tools.schemas import BaseToolConfig
|
|
17
20
|
from unique_toolkit.agentic.tools.tool import Tool
|
|
18
21
|
from unique_toolkit.agentic.tools.tool_manager import ToolManager, ToolManagerConfig
|
|
@@ -48,11 +51,6 @@ class MockInternalSearchTool(Tool[BaseToolConfig]):
|
|
|
48
51
|
def tool_format_information_for_system_prompt(self) -> str:
|
|
49
52
|
return "Use this tool to search for content"
|
|
50
53
|
|
|
51
|
-
def get_tool_call_result_for_loop_history(self, tool_response):
|
|
52
|
-
from unique_toolkit.language_model.schemas import LanguageModelMessage
|
|
53
|
-
|
|
54
|
-
return LanguageModelMessage(role="tool", content="Mock search result")
|
|
55
|
-
|
|
56
54
|
def evaluation_check_list(self):
|
|
57
55
|
return []
|
|
58
56
|
|
|
@@ -114,11 +112,22 @@ class TestMCPManager:
|
|
|
114
112
|
name="mcp_test_tool",
|
|
115
113
|
description="Test MCP tool",
|
|
116
114
|
input_schema={
|
|
115
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
117
116
|
"type": "object",
|
|
118
117
|
"properties": {
|
|
119
|
-
"
|
|
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
|
+
},
|
|
120
130
|
},
|
|
121
|
-
"required": ["query"],
|
|
122
131
|
},
|
|
123
132
|
output_schema=None,
|
|
124
133
|
annotations=None,
|
|
@@ -173,6 +182,7 @@ class TestMCPManager:
|
|
|
173
182
|
return A2AManager(
|
|
174
183
|
logger=self.logger,
|
|
175
184
|
tool_progress_reporter=tool_progress_reporter,
|
|
185
|
+
response_watcher=SubAgentResponseWatcher(),
|
|
176
186
|
)
|
|
177
187
|
|
|
178
188
|
@pytest.fixture
|
|
@@ -277,6 +287,7 @@ class TestMCPManager:
|
|
|
277
287
|
a2a_manager = A2AManager(
|
|
278
288
|
logger=self.logger,
|
|
279
289
|
tool_progress_reporter=tool_progress_reporter,
|
|
290
|
+
response_watcher=SubAgentResponseWatcher(),
|
|
280
291
|
)
|
|
281
292
|
|
|
282
293
|
tool_manager = ToolManager(
|
|
@@ -313,6 +324,7 @@ class TestMCPManager:
|
|
|
313
324
|
a2a_manager = A2AManager(
|
|
314
325
|
logger=self.logger,
|
|
315
326
|
tool_progress_reporter=tool_progress_reporter,
|
|
327
|
+
response_watcher=SubAgentResponseWatcher(),
|
|
316
328
|
)
|
|
317
329
|
|
|
318
330
|
tool_manager = ToolManager(
|
|
@@ -349,6 +361,7 @@ class TestMCPManager:
|
|
|
349
361
|
a2a_manager = A2AManager(
|
|
350
362
|
logger=self.logger,
|
|
351
363
|
tool_progress_reporter=tool_progress_reporter,
|
|
364
|
+
response_watcher=SubAgentResponseWatcher(),
|
|
352
365
|
)
|
|
353
366
|
|
|
354
367
|
tool_manager = ToolManager(
|
|
@@ -388,6 +401,7 @@ class TestMCPManager:
|
|
|
388
401
|
a2a_manager = A2AManager(
|
|
389
402
|
logger=self.logger,
|
|
390
403
|
tool_progress_reporter=tool_progress_reporter,
|
|
404
|
+
response_watcher=SubAgentResponseWatcher(),
|
|
391
405
|
)
|
|
392
406
|
|
|
393
407
|
tool_manager = ToolManager(
|
|
@@ -429,6 +443,7 @@ class TestMCPManager:
|
|
|
429
443
|
a2a_manager = A2AManager(
|
|
430
444
|
logger=self.logger,
|
|
431
445
|
tool_progress_reporter=tool_progress_reporter,
|
|
446
|
+
response_watcher=SubAgentResponseWatcher(),
|
|
432
447
|
)
|
|
433
448
|
|
|
434
449
|
tool_manager = ToolManager(
|
|
@@ -446,3 +461,76 @@ class TestMCPManager:
|
|
|
446
461
|
assert "internal_search" not in tool_names
|
|
447
462
|
assert "mcp_test_tool" in tool_names
|
|
448
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
|