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,91 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
from typing import NamedTuple
|
|
4
|
+
|
|
5
|
+
import unique_sdk
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _clone_message(
|
|
9
|
+
message: unique_sdk.Space.Message,
|
|
10
|
+
) -> unique_sdk.Space.Message:
|
|
11
|
+
# copy.deepcopy does not work for instances of UniqueObject
|
|
12
|
+
return json.loads(json.dumps(message))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SubAgentResponse(NamedTuple):
|
|
16
|
+
assistant_id: str
|
|
17
|
+
name: str
|
|
18
|
+
sequence_number: int
|
|
19
|
+
message: unique_sdk.Space.Message
|
|
20
|
+
timestamp: datetime.datetime
|
|
21
|
+
|
|
22
|
+
def clone(self) -> "SubAgentResponse":
|
|
23
|
+
return SubAgentResponse(
|
|
24
|
+
assistant_id=self.assistant_id,
|
|
25
|
+
name=self.name,
|
|
26
|
+
sequence_number=self.sequence_number,
|
|
27
|
+
message=_clone_message(self.message),
|
|
28
|
+
timestamp=self.timestamp,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SubAgentResponseWatcher:
|
|
33
|
+
"""
|
|
34
|
+
Save and retrieve sub agent responses immutably.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
self._response_registry: dict[str, list[SubAgentResponse]] = {}
|
|
39
|
+
|
|
40
|
+
def notify_response(
|
|
41
|
+
self,
|
|
42
|
+
assistant_id: str,
|
|
43
|
+
name: str,
|
|
44
|
+
sequence_number: int,
|
|
45
|
+
response: unique_sdk.Space.Message,
|
|
46
|
+
timestamp: datetime.datetime,
|
|
47
|
+
) -> None:
|
|
48
|
+
if assistant_id not in self._response_registry:
|
|
49
|
+
self._response_registry[assistant_id] = []
|
|
50
|
+
|
|
51
|
+
response = _clone_message(response)
|
|
52
|
+
|
|
53
|
+
self._response_registry[assistant_id].append(
|
|
54
|
+
SubAgentResponse(
|
|
55
|
+
assistant_id=assistant_id,
|
|
56
|
+
name=name,
|
|
57
|
+
sequence_number=sequence_number,
|
|
58
|
+
message=response,
|
|
59
|
+
timestamp=timestamp,
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def get_responses(self, assistant_id: str) -> list[SubAgentResponse]:
|
|
64
|
+
return _sort_responses( # Always return a consistent order
|
|
65
|
+
[
|
|
66
|
+
response.clone()
|
|
67
|
+
for response in self._response_registry.get(assistant_id, [])
|
|
68
|
+
],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def get_all_responses(self) -> list[SubAgentResponse]:
|
|
72
|
+
return _sort_responses(
|
|
73
|
+
[
|
|
74
|
+
response.clone()
|
|
75
|
+
for sub_agent_responses in self._response_registry.values()
|
|
76
|
+
for response in sub_agent_responses
|
|
77
|
+
],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _sort_responses(
|
|
82
|
+
responses: list[SubAgentResponse],
|
|
83
|
+
) -> list[SubAgentResponse]:
|
|
84
|
+
return sorted(
|
|
85
|
+
responses,
|
|
86
|
+
key=lambda response: (
|
|
87
|
+
response.timestamp,
|
|
88
|
+
response.assistant_id,
|
|
89
|
+
response.sequence_number,
|
|
90
|
+
),
|
|
91
|
+
)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
1
3
|
from pydantic import Field
|
|
2
4
|
|
|
3
5
|
from unique_toolkit._common.pydantic_helpers import get_configuration_dict
|
|
@@ -7,10 +9,6 @@ DEFAULT_PARAM_DESCRIPTION_SUB_AGENT_USER_MESSAGE = """
|
|
|
7
9
|
This is the message that will be sent to the sub-agent.
|
|
8
10
|
""".strip()
|
|
9
11
|
|
|
10
|
-
DEFAULT_FORMAT_INFORMATION_SUB_AGENT_SYSTEM_MESSAGE = """
|
|
11
|
-
NEVER mention any references from sub-agent answers in your response.
|
|
12
|
-
""".strip()
|
|
13
|
-
|
|
14
12
|
|
|
15
13
|
class SubAgentToolConfig(BaseToolConfig):
|
|
16
14
|
model_config = get_configuration_dict()
|
|
@@ -27,6 +25,14 @@ class SubAgentToolConfig(BaseToolConfig):
|
|
|
27
25
|
default=True,
|
|
28
26
|
description="Whether to reuse the existing chat or create a new one for each sub-agent call.",
|
|
29
27
|
)
|
|
28
|
+
use_sub_agent_references: bool = Field(
|
|
29
|
+
default=True,
|
|
30
|
+
description="Whether this sub agent's references should be used in the main agent's response.",
|
|
31
|
+
)
|
|
32
|
+
forced_tools: list[str] | None = Field(
|
|
33
|
+
default=None,
|
|
34
|
+
description="The list of tool names that will be forced to be called for this sub-agent.",
|
|
35
|
+
)
|
|
30
36
|
|
|
31
37
|
tool_description_for_system_prompt: str = Field(
|
|
32
38
|
default="",
|
|
@@ -41,7 +47,7 @@ class SubAgentToolConfig(BaseToolConfig):
|
|
|
41
47
|
description="Description of the user message parameter that will be sent to the model.",
|
|
42
48
|
)
|
|
43
49
|
tool_format_information_for_system_prompt: str = Field(
|
|
44
|
-
default=
|
|
50
|
+
default="",
|
|
45
51
|
description="Format information that will be included in the system prompt to guide response formatting.",
|
|
46
52
|
)
|
|
47
53
|
tool_description_for_user_prompt: str = Field(
|
|
@@ -61,3 +67,7 @@ class SubAgentToolConfig(BaseToolConfig):
|
|
|
61
67
|
default=120.0,
|
|
62
68
|
description="Maximum time in seconds to wait for the sub-agent response before timing out.",
|
|
63
69
|
)
|
|
70
|
+
stop_condition: Literal["stoppedStreamingAt", "completedAt"] = Field(
|
|
71
|
+
default="completedAt",
|
|
72
|
+
description="The condition that will be used to stop the polling for the sub-agent response.",
|
|
73
|
+
)
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import contextlib
|
|
3
|
-
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import override
|
|
4
7
|
|
|
5
8
|
import unique_sdk
|
|
6
9
|
from pydantic import Field, create_model
|
|
7
10
|
from unique_sdk.utils.chat_in_space import send_message_and_wait_for_completion
|
|
8
11
|
|
|
12
|
+
from unique_toolkit._common.referencing import (
|
|
13
|
+
get_all_ref_numbers,
|
|
14
|
+
remove_all_refs,
|
|
15
|
+
replace_ref_number,
|
|
16
|
+
)
|
|
9
17
|
from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
|
|
18
|
+
from unique_toolkit.agentic.tools.a2a.response_watcher import SubAgentResponseWatcher
|
|
10
19
|
from unique_toolkit.agentic.tools.a2a.tool._memory import (
|
|
11
20
|
get_sub_agent_short_term_memory_manager,
|
|
12
21
|
)
|
|
@@ -17,7 +26,6 @@ from unique_toolkit.agentic.tools.a2a.tool._schema import (
|
|
|
17
26
|
from unique_toolkit.agentic.tools.a2a.tool.config import (
|
|
18
27
|
SubAgentToolConfig,
|
|
19
28
|
)
|
|
20
|
-
from unique_toolkit.agentic.tools.agent_chunks_hanlder import AgentChunksHandler
|
|
21
29
|
from unique_toolkit.agentic.tools.factory import ToolFactory
|
|
22
30
|
from unique_toolkit.agentic.tools.schemas import ToolCallResponse
|
|
23
31
|
from unique_toolkit.agentic.tools.tool import Tool
|
|
@@ -30,22 +38,8 @@ from unique_toolkit.language_model import (
|
|
|
30
38
|
LanguageModelFunction,
|
|
31
39
|
LanguageModelToolDescription,
|
|
32
40
|
)
|
|
33
|
-
from unique_toolkit.language_model.schemas import LanguageModelMessage
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
class SubAgentResponseSubscriber(Protocol):
|
|
37
|
-
def notify_sub_agent_response(
|
|
38
|
-
self,
|
|
39
|
-
response: unique_sdk.Space.Message,
|
|
40
|
-
sub_agent_assistant_id: str,
|
|
41
|
-
sequence_number: int,
|
|
42
|
-
) -> None: ...
|
|
43
|
-
|
|
44
|
-
"""
|
|
45
|
-
Notify the subscriber that a sub agent response has been received.
|
|
46
|
-
Important: The subscriber should NOT modify the response in place.
|
|
47
|
-
The sequence number is a 1-indexed counter that is incremented for each concurrent run of the same sub agent.
|
|
48
|
-
"""
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
49
43
|
|
|
50
44
|
|
|
51
45
|
class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
@@ -58,6 +52,7 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
58
52
|
tool_progress_reporter: ToolProgressReporter | None = None,
|
|
59
53
|
name: str = "SubAgentTool",
|
|
60
54
|
display_name: str = "SubAgentTool",
|
|
55
|
+
response_watcher: SubAgentResponseWatcher | None = None,
|
|
61
56
|
):
|
|
62
57
|
super().__init__(configuration, event, tool_progress_reporter)
|
|
63
58
|
self._user_id = event.user_id
|
|
@@ -72,15 +67,25 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
72
67
|
chat_id=event.payload.chat_id,
|
|
73
68
|
assistant_id=self.config.assistant_id,
|
|
74
69
|
)
|
|
75
|
-
self._subscribers: list[SubAgentResponseSubscriber] = []
|
|
76
70
|
self._should_run_evaluation = False
|
|
77
71
|
|
|
72
|
+
self._response_watcher = response_watcher
|
|
73
|
+
|
|
78
74
|
# Synchronization state
|
|
79
75
|
self._sequence_number = 1
|
|
80
76
|
self._lock = asyncio.Lock()
|
|
81
77
|
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
@staticmethod
|
|
79
|
+
def get_sub_agent_reference_format(
|
|
80
|
+
name: str, sequence_number: int, reference_number: int
|
|
81
|
+
) -> str:
|
|
82
|
+
return f"<sup><name>{name} {sequence_number}</name>{reference_number}</sup>"
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def get_sub_agent_reference_re(
|
|
86
|
+
name: str, sequence_number: int, reference_number: int
|
|
87
|
+
) -> str:
|
|
88
|
+
return rf"<sup>\s*<name>\s*{re.escape(name)}\s*{sequence_number}\s*</name>\s*{reference_number}\s*</sup>"
|
|
84
89
|
|
|
85
90
|
@override
|
|
86
91
|
def display_name(self) -> str:
|
|
@@ -132,6 +137,7 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
132
137
|
@override
|
|
133
138
|
async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
|
|
134
139
|
tool_input = SubAgentToolInput.model_validate(tool_call.arguments)
|
|
140
|
+
timestamp = datetime.now()
|
|
135
141
|
|
|
136
142
|
if self._lock.locked():
|
|
137
143
|
await self._notify_progress(
|
|
@@ -167,7 +173,7 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
167
173
|
response["assessment"] is not None and len(response["assessment"]) > 0
|
|
168
174
|
) # Run evaluation if any sub agent returned an assessment
|
|
169
175
|
|
|
170
|
-
self.
|
|
176
|
+
self._notify_watcher(response, sequence_number, timestamp)
|
|
171
177
|
|
|
172
178
|
if chat_id is None:
|
|
173
179
|
await self._save_chat_id(response["chatId"])
|
|
@@ -175,6 +181,11 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
175
181
|
if response["text"] is None:
|
|
176
182
|
raise ValueError("No response returned from sub agent")
|
|
177
183
|
|
|
184
|
+
response_text_with_references = self._prepare_response_references(
|
|
185
|
+
response=response["text"],
|
|
186
|
+
sequence_number=sequence_number,
|
|
187
|
+
)
|
|
188
|
+
|
|
178
189
|
await self._notify_progress(
|
|
179
190
|
tool_call=tool_call,
|
|
180
191
|
message=tool_input.user_message,
|
|
@@ -184,16 +195,9 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
184
195
|
return ToolCallResponse(
|
|
185
196
|
id=tool_call.id, # type: ignore
|
|
186
197
|
name=tool_call.name,
|
|
187
|
-
content=
|
|
198
|
+
content=response_text_with_references,
|
|
188
199
|
)
|
|
189
200
|
|
|
190
|
-
@override
|
|
191
|
-
def get_tool_call_result_for_loop_history(
|
|
192
|
-
self,
|
|
193
|
-
tool_response: ToolCallResponse,
|
|
194
|
-
agent_chunks_handler: AgentChunksHandler,
|
|
195
|
-
) -> LanguageModelMessage: ... # Empty as method is deprecated
|
|
196
|
-
|
|
197
201
|
async def _get_chat_id(self) -> str | None:
|
|
198
202
|
if not self.config.reuse_chat:
|
|
199
203
|
return None
|
|
@@ -209,6 +213,23 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
209
213
|
|
|
210
214
|
return None
|
|
211
215
|
|
|
216
|
+
def _prepare_response_references(self, response: str, sequence_number: int) -> str:
|
|
217
|
+
if not self.config.use_sub_agent_references:
|
|
218
|
+
# Remove all references from the response
|
|
219
|
+
response = remove_all_refs(response)
|
|
220
|
+
return response
|
|
221
|
+
|
|
222
|
+
for ref_number in get_all_ref_numbers(response):
|
|
223
|
+
reference = self.get_sub_agent_reference_format(
|
|
224
|
+
name=self.name,
|
|
225
|
+
sequence_number=sequence_number,
|
|
226
|
+
reference_number=ref_number,
|
|
227
|
+
)
|
|
228
|
+
response = replace_ref_number(
|
|
229
|
+
text=response, ref_number=ref_number, replacement=reference
|
|
230
|
+
)
|
|
231
|
+
return response
|
|
232
|
+
|
|
212
233
|
async def _save_chat_id(self, chat_id: str) -> None:
|
|
213
234
|
if not self.config.reuse_chat:
|
|
214
235
|
return
|
|
@@ -231,14 +252,25 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
231
252
|
state=state,
|
|
232
253
|
)
|
|
233
254
|
|
|
234
|
-
def
|
|
235
|
-
self,
|
|
255
|
+
def _notify_watcher(
|
|
256
|
+
self,
|
|
257
|
+
response: unique_sdk.Space.Message,
|
|
258
|
+
sequence_number: int,
|
|
259
|
+
timestamp: datetime,
|
|
236
260
|
) -> None:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
261
|
+
if self._response_watcher is not None:
|
|
262
|
+
self._response_watcher.notify_response(
|
|
263
|
+
assistant_id=self.config.assistant_id,
|
|
264
|
+
name=self.name,
|
|
241
265
|
sequence_number=sequence_number,
|
|
266
|
+
response=response,
|
|
267
|
+
timestamp=timestamp,
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
logger.warning(
|
|
271
|
+
"No response watcher found for sub agent %s (assistant_id: %s)",
|
|
272
|
+
self.name,
|
|
273
|
+
self.config.assistant_id,
|
|
242
274
|
)
|
|
243
275
|
|
|
244
276
|
async def _execute_and_handle_timeout(
|
|
@@ -255,8 +287,9 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
|
|
|
255
287
|
text=tool_user_message,
|
|
256
288
|
chat_id=chat_id,
|
|
257
289
|
poll_interval=self.config.poll_interval,
|
|
290
|
+
tool_choices=self.config.forced_tools,
|
|
258
291
|
max_wait=self.config.max_wait,
|
|
259
|
-
stop_condition=
|
|
292
|
+
stop_condition=self.config.stop_condition,
|
|
260
293
|
)
|
|
261
294
|
except TimeoutError as e:
|
|
262
295
|
await self._notify_progress(
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from enum import StrEnum
|
|
3
|
-
from typing import Any, Dict
|
|
3
|
+
from typing import Annotated, Any, Dict
|
|
4
4
|
|
|
5
5
|
from pydantic import (
|
|
6
6
|
BaseModel,
|
|
7
|
+
BeforeValidator,
|
|
7
8
|
Field,
|
|
8
9
|
ValidationInfo,
|
|
9
10
|
model_validator,
|
|
@@ -33,6 +34,16 @@ class ToolSelectionPolicy(StrEnum):
|
|
|
33
34
|
BY_USER = "ByUser"
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
def handle_undefined_icon(value: Any) -> ToolIcon:
|
|
38
|
+
try:
|
|
39
|
+
if isinstance(value, str):
|
|
40
|
+
return ToolIcon(value)
|
|
41
|
+
else:
|
|
42
|
+
return ToolIcon.BOOK
|
|
43
|
+
except ValueError:
|
|
44
|
+
return ToolIcon.BOOK
|
|
45
|
+
|
|
46
|
+
|
|
36
47
|
class ToolBuildConfig(BaseModel):
|
|
37
48
|
model_config = get_configuration_dict()
|
|
38
49
|
"""Main tool configuration"""
|
|
@@ -40,7 +51,10 @@ class ToolBuildConfig(BaseModel):
|
|
|
40
51
|
name: str
|
|
41
52
|
configuration: BaseToolConfig
|
|
42
53
|
display_name: str = ""
|
|
43
|
-
icon: ToolIcon =
|
|
54
|
+
icon: Annotated[ToolIcon, BeforeValidator(handle_undefined_icon)] = Field(
|
|
55
|
+
default=ToolIcon.BOOK,
|
|
56
|
+
description="The icon name that will be used to display the tool in the user interface.",
|
|
57
|
+
)
|
|
44
58
|
selection_policy: ToolSelectionPolicy = Field(
|
|
45
59
|
default=ToolSelectionPolicy.BY_USER,
|
|
46
60
|
)
|
|
@@ -11,6 +11,10 @@ class ToolFactory:
|
|
|
11
11
|
tool_map: dict[str, type[Tool]] = {}
|
|
12
12
|
tool_config_map: dict[str, Callable] = {}
|
|
13
13
|
|
|
14
|
+
@classmethod
|
|
15
|
+
def register_tool_config(cls, tool_name: str, tool_config: type[BaseToolConfig]):
|
|
16
|
+
cls.tool_config_map[tool_name] = tool_config
|
|
17
|
+
|
|
14
18
|
@classmethod
|
|
15
19
|
def register_tool(
|
|
16
20
|
cls,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import logging
|
|
2
3
|
from typing import Any, Dict
|
|
3
4
|
|
|
4
5
|
import unique_sdk
|
|
5
|
-
from pydantic import BaseModel, Field, create_model
|
|
6
6
|
|
|
7
7
|
from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
|
|
8
8
|
from unique_toolkit.agentic.tools.mcp.models import MCPToolConfig
|
|
@@ -13,12 +13,13 @@ from unique_toolkit.agentic.tools.tool_progress_reporter import (
|
|
|
13
13
|
ToolProgressReporter,
|
|
14
14
|
)
|
|
15
15
|
from unique_toolkit.app.schemas import ChatEvent, McpServer, McpTool
|
|
16
|
-
from unique_toolkit.language_model import LanguageModelMessage
|
|
17
16
|
from unique_toolkit.language_model.schemas import (
|
|
18
17
|
LanguageModelFunction,
|
|
19
18
|
LanguageModelToolDescription,
|
|
20
19
|
)
|
|
21
20
|
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
22
23
|
|
|
23
24
|
class MCPToolWrapper(Tool[MCPToolConfig]):
|
|
24
25
|
"""Wrapper class for MCP tools that implements the Tool interface"""
|
|
@@ -39,39 +40,16 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
|
|
|
39
40
|
def tool_description(self) -> LanguageModelToolDescription:
|
|
40
41
|
"""Convert MCP tool schema to LanguageModelToolDescription"""
|
|
41
42
|
# Create a Pydantic model from the MCP tool's input schema
|
|
42
|
-
|
|
43
|
+
logger.info(
|
|
44
|
+
"MCP tool %s schema %s", self._mcp_tool.name, self._mcp_tool.input_schema
|
|
45
|
+
)
|
|
43
46
|
|
|
44
47
|
return LanguageModelToolDescription(
|
|
45
48
|
name=self.name,
|
|
46
49
|
description=self._mcp_tool.description or "",
|
|
47
|
-
parameters=
|
|
50
|
+
parameters=self._mcp_tool.input_schema,
|
|
48
51
|
)
|
|
49
52
|
|
|
50
|
-
def _create_parameters_model(self) -> type[BaseModel]:
|
|
51
|
-
"""Create a Pydantic model from MCP tool's input schema"""
|
|
52
|
-
properties = self._mcp_tool.input_schema.get("properties", {})
|
|
53
|
-
required_fields = self._mcp_tool.input_schema.get("required", [])
|
|
54
|
-
|
|
55
|
-
# Convert JSON schema properties to Pydantic fields
|
|
56
|
-
fields = {}
|
|
57
|
-
for prop_name, prop_schema in properties.items():
|
|
58
|
-
field_type = self._json_schema_to_python_type(prop_schema)
|
|
59
|
-
field_description = prop_schema.get("description", "")
|
|
60
|
-
|
|
61
|
-
if prop_name in required_fields:
|
|
62
|
-
fields[prop_name] = (
|
|
63
|
-
field_type,
|
|
64
|
-
Field(description=field_description),
|
|
65
|
-
)
|
|
66
|
-
else:
|
|
67
|
-
fields[prop_name] = (
|
|
68
|
-
field_type,
|
|
69
|
-
Field(default=None, description=field_description),
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
# Create dynamic model
|
|
73
|
-
return create_model(f"{self.name}Parameters", **fields)
|
|
74
|
-
|
|
75
53
|
def _json_schema_to_python_type(self, schema: Dict[str, Any]) -> type:
|
|
76
54
|
"""Convert JSON schema type to Python type"""
|
|
77
55
|
json_type = schema.get("type", "string")
|
|
@@ -120,12 +98,6 @@ class MCPToolWrapper(Tool[MCPToolConfig]):
|
|
|
120
98
|
"""Return evaluation checks based on tool response"""
|
|
121
99
|
return []
|
|
122
100
|
|
|
123
|
-
def get_tool_call_result_for_loop_history(
|
|
124
|
-
self,
|
|
125
|
-
tool_response: ToolCallResponse,
|
|
126
|
-
) -> LanguageModelMessage:
|
|
127
|
-
raise NotImplementedError("function is not supported")
|
|
128
|
-
|
|
129
101
|
async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
|
|
130
102
|
"""Execute the MCP tool using SDK to call public API"""
|
|
131
103
|
self.logger.info(f"Running MCP tool: {self.name}")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from unique_toolkit.agentic.tools.openai_builtin.code_interpreter import (
|
|
2
|
+
OpenAICodeInterpreterConfig,
|
|
3
|
+
OpenAICodeInterpreterTool,
|
|
4
|
+
)
|
|
5
|
+
from unique_toolkit.agentic.tools.openai_builtin.manager import OpenAIBuiltInToolManager
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"OpenAIBuiltInToolManager",
|
|
9
|
+
"OpenAICodeInterpreterTool",
|
|
10
|
+
"OpenAICodeInterpreterConfig",
|
|
11
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from openai.types.responses.tool_param import CodeInterpreter
|
|
6
|
+
|
|
7
|
+
from unique_toolkit.agentic.tools.schemas import ToolPrompts
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OpenAIBuiltInToolName(StrEnum):
|
|
11
|
+
CODE_INTERPRETER = "code_interpreter"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
BuiltInToolType = CodeInterpreter # Add other tool types when needed
|
|
15
|
+
ToolType = TypeVar("ToolType", bound=BuiltInToolType)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OpenAIBuiltInTool(ABC, Generic[ToolType]):
|
|
19
|
+
@property
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def name(self) -> OpenAIBuiltInToolName:
|
|
22
|
+
raise NotImplementedError()
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def tool_description(self) -> BuiltInToolType:
|
|
26
|
+
raise NotImplementedError()
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def get_tool_prompts(self) -> ToolPrompts:
|
|
30
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from unique_toolkit.agentic.tools.openai_builtin.code_interpreter.config import (
|
|
2
|
+
OpenAICodeInterpreterConfig,
|
|
3
|
+
)
|
|
4
|
+
from unique_toolkit.agentic.tools.openai_builtin.code_interpreter.service import (
|
|
5
|
+
OpenAICodeInterpreterTool,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = ["OpenAICodeInterpreterConfig", "OpenAICodeInterpreterTool"]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from unique_toolkit.agentic.tools.factory import ToolFactory
|
|
4
|
+
from unique_toolkit.agentic.tools.openai_builtin.base import (
|
|
5
|
+
OpenAIBuiltInToolName,
|
|
6
|
+
)
|
|
7
|
+
from unique_toolkit.agentic.tools.schemas import BaseToolConfig
|
|
8
|
+
|
|
9
|
+
DEFAULT_TOOL_DESCRIPTION = "Use this tool to run python code, e.g to generate plots, process excel files, perform calculations, etc."
|
|
10
|
+
|
|
11
|
+
DEFAULT_TOOL_DESCRIPTION_FOR_SYSTEM_PROMPT = """
|
|
12
|
+
Use this tool to run python code, e.g to generate plots, process excel files, perform calculations, etc.
|
|
13
|
+
Instructions:
|
|
14
|
+
- All files uploaded to the chat are available in the code interpreter under the path `/mnt/data/<filename>
|
|
15
|
+
- All files generated through code should be saved in the `/mnt/data` folder
|
|
16
|
+
|
|
17
|
+
Instructions for displaying images and files in the chat:
|
|
18
|
+
Once files are generated in the `/mnt/data` folder you MUST reference them in the chat using markdown syntax in order to display them in the chat.
|
|
19
|
+
|
|
20
|
+
- If you want to display an image, use the following syntax: ``
|
|
21
|
+
- Images will be converted and shown in the chat.
|
|
22
|
+
- Do NOT display an extra download link for images a part from the markdown above.
|
|
23
|
+
- Not using markdown syntax will FAIL to show images to the user.
|
|
24
|
+
- YOU MUST use the syntax above to display images, otherwise the image will not be displayed in the chat.
|
|
25
|
+
- For displaying a link to a file, use the following syntax: `[filename](sandbox:/mnt/data/<filename>)`
|
|
26
|
+
- Files are converted to references the user can click on to download the file
|
|
27
|
+
|
|
28
|
+
You MUST always use this syntax, otherwise the files will not be displayed in the chat.
|
|
29
|
+
""".strip()
|
|
30
|
+
|
|
31
|
+
DEFAULT_TOOL_FORMAT_INFORMATION_FOR_SYSTEM_PROMPT = ""
|
|
32
|
+
|
|
33
|
+
DEFAULT_TOOL_FORMAT_INFORMATION_FOR_USER_PROMPT = ""
|
|
34
|
+
|
|
35
|
+
DEFAULT_TOOL_DESCRIPTION_FOR_USER_PROMPT = ""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class OpenAICodeInterpreterConfig(BaseToolConfig):
|
|
39
|
+
upload_files_in_chat: bool = Field(default=True)
|
|
40
|
+
|
|
41
|
+
tool_description: str = DEFAULT_TOOL_DESCRIPTION
|
|
42
|
+
tool_description_for_system_prompt: str = DEFAULT_TOOL_DESCRIPTION_FOR_SYSTEM_PROMPT
|
|
43
|
+
tool_format_information_for_system_prompt: str = (
|
|
44
|
+
DEFAULT_TOOL_FORMAT_INFORMATION_FOR_SYSTEM_PROMPT
|
|
45
|
+
)
|
|
46
|
+
tool_description_for_user_prompt: str = DEFAULT_TOOL_DESCRIPTION_FOR_USER_PROMPT
|
|
47
|
+
tool_format_information_for_user_prompt: str = (
|
|
48
|
+
DEFAULT_TOOL_FORMAT_INFORMATION_FOR_USER_PROMPT
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
expires_after_minutes: int = 20
|
|
52
|
+
use_auto_container: bool = False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
ToolFactory.register_tool_config(
|
|
56
|
+
OpenAIBuiltInToolName.CODE_INTERPRETER, OpenAICodeInterpreterConfig
|
|
57
|
+
)
|