unique_toolkit 0.7.9__py3-none-any.whl → 1.33.3__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.
- unique_toolkit/__init__.py +36 -3
- unique_toolkit/_common/api_calling/human_verification_manager.py +357 -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 +225 -0
- unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
- unique_toolkit/_common/endpoint_builder.py +368 -0
- unique_toolkit/_common/endpoint_requestor.py +480 -0
- unique_toolkit/_common/exception.py +24 -0
- unique_toolkit/_common/experimental/endpoint_builder.py +368 -0
- unique_toolkit/_common/experimental/endpoint_requestor.py +488 -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 +174 -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/image/encode.py +25 -0
- unique_toolkit/_common/utils/jinja/helpers.py +10 -0
- unique_toolkit/_common/utils/jinja/render.py +18 -0
- unique_toolkit/_common/utils/jinja/schema.py +65 -0
- unique_toolkit/_common/utils/jinja/utils.py +80 -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 +112 -0
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +20 -16
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +32 -21
- 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 +298 -0
- unique_toolkit/agentic/history_manager/history_manager.py +241 -0
- unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
- unique_toolkit/agentic/history_manager/utils.py +96 -0
- unique_toolkit/agentic/message_log_manager/__init__.py +5 -0
- unique_toolkit/agentic/message_log_manager/service.py +93 -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 +71 -0
- unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +297 -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 +240 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +84 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py +78 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/display.py +264 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +421 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +2103 -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 +158 -0
- unique_toolkit/agentic/tools/a2a/tool/service.py +393 -0
- unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
- unique_toolkit/agentic/tools/config.py +128 -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 +46 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +88 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +250 -0
- unique_toolkit/agentic/tools/openai_builtin/manager.py +79 -0
- unique_toolkit/agentic/tools/schemas.py +145 -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 +187 -0
- unique_toolkit/agentic/tools/tool_manager.py +492 -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 +9 -0
- unique_toolkit/app/dev_util.py +180 -0
- unique_toolkit/app/fast_api_factory.py +131 -0
- unique_toolkit/app/init_sdk.py +32 -1
- unique_toolkit/app/schemas.py +206 -31
- unique_toolkit/app/unique_settings.py +367 -0
- unique_toolkit/app/webhook.py +77 -0
- unique_toolkit/chat/__init__.py +8 -1
- unique_toolkit/chat/deprecated/service.py +232 -0
- unique_toolkit/chat/functions.py +648 -78
- unique_toolkit/chat/rendering.py +34 -0
- unique_toolkit/chat/responses_api.py +461 -0
- unique_toolkit/chat/schemas.py +134 -2
- unique_toolkit/chat/service.py +115 -767
- unique_toolkit/content/functions.py +353 -8
- unique_toolkit/content/schemas.py +128 -15
- unique_toolkit/content/service.py +321 -45
- unique_toolkit/content/smart_rules.py +301 -0
- unique_toolkit/content/utils.py +10 -3
- unique_toolkit/data_extraction/README.md +96 -0
- unique_toolkit/data_extraction/__init__.py +11 -0
- unique_toolkit/data_extraction/augmented/__init__.py +5 -0
- unique_toolkit/data_extraction/augmented/service.py +93 -0
- unique_toolkit/data_extraction/base.py +25 -0
- unique_toolkit/data_extraction/basic/__init__.py +11 -0
- unique_toolkit/data_extraction/basic/config.py +18 -0
- unique_toolkit/data_extraction/basic/prompt.py +13 -0
- unique_toolkit/data_extraction/basic/service.py +55 -0
- unique_toolkit/embedding/service.py +103 -12
- unique_toolkit/framework_utilities/__init__.py +1 -0
- unique_toolkit/framework_utilities/langchain/__init__.py +10 -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 +84 -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/_responses_api_utils.py +93 -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 +345 -43
- unique_toolkit/language_model/infos.py +1288 -46
- unique_toolkit/language_model/reference.py +242 -0
- unique_toolkit/language_model/schemas.py +481 -49
- unique_toolkit/language_model/service.py +229 -28
- unique_toolkit/protocols/support.py +145 -0
- unique_toolkit/services/__init__.py +7 -0
- unique_toolkit/services/chat_service.py +1631 -0
- unique_toolkit/services/knowledge_base.py +1094 -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-1.33.3.dist-info/METADATA +1145 -0
- unique_toolkit-1.33.3.dist-info/RECORD +205 -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.9.dist-info/METADATA +0 -413
- unique_toolkit-0.7.9.dist-info/RECORD +0 -64
- /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
- {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/LICENSE +0 -0
- {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing_extensions import deprecated
|
|
2
|
+
|
|
3
|
+
from unique_toolkit.content.schemas import ContentChunk, ContentReference
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@deprecated("do not use this its only used in old tools")
|
|
7
|
+
class AgentChunksHandler:
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self._tool_chunks = {}
|
|
10
|
+
self._chunks: list[ContentChunk] = []
|
|
11
|
+
self._references: list[list[ContentReference]] = []
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def chunks(self) -> list[ContentChunk]:
|
|
15
|
+
return self._chunks
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def tool_chunks(self) -> dict:
|
|
19
|
+
return self._tool_chunks
|
|
20
|
+
|
|
21
|
+
def extend(self, chunks: list[ContentChunk]):
|
|
22
|
+
self._chunks.extend(chunks)
|
|
23
|
+
|
|
24
|
+
def replace(self, chunks: list[ContentChunk]):
|
|
25
|
+
self._chunks = chunks
|
|
26
|
+
|
|
27
|
+
def add_references(
|
|
28
|
+
self,
|
|
29
|
+
references: list[ContentReference],
|
|
30
|
+
):
|
|
31
|
+
self._references.append(references)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def all_references(
|
|
35
|
+
self,
|
|
36
|
+
) -> list[list[ContentReference]]:
|
|
37
|
+
return self._references
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def latest_references(
|
|
41
|
+
self,
|
|
42
|
+
) -> list[ContentReference]:
|
|
43
|
+
if not self._references:
|
|
44
|
+
return []
|
|
45
|
+
return self._references[-1]
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def latest_referenced_chunks(self) -> list[ContentChunk]:
|
|
49
|
+
if not self._references:
|
|
50
|
+
return []
|
|
51
|
+
return self._get_referenced_chunks_from_references(self._references[-1])
|
|
52
|
+
|
|
53
|
+
def _get_referenced_chunks_from_references(
|
|
54
|
+
self,
|
|
55
|
+
references: list[ContentReference],
|
|
56
|
+
) -> list[ContentChunk]:
|
|
57
|
+
"""
|
|
58
|
+
Get _referenced_chunks by matching sourceId from _references with merged id and chunk_id from _chunks.
|
|
59
|
+
"""
|
|
60
|
+
referenced_chunks: list[ContentChunk] = []
|
|
61
|
+
for ref in references:
|
|
62
|
+
for chunk in self._chunks:
|
|
63
|
+
if ref.source_id == str(chunk.id) + "_" + str(chunk.chunk_id):
|
|
64
|
+
referenced_chunks.append(chunk)
|
|
65
|
+
return referenced_chunks
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from typing import Annotated, Any
|
|
3
|
+
|
|
4
|
+
from pydantic import (
|
|
5
|
+
BaseModel,
|
|
6
|
+
BeforeValidator,
|
|
7
|
+
Field,
|
|
8
|
+
ValidationInfo,
|
|
9
|
+
field_serializer,
|
|
10
|
+
model_validator,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from unique_toolkit._common.pydantic_helpers import get_configuration_dict
|
|
14
|
+
from unique_toolkit.agentic.tools.schemas import BaseToolConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ToolIcon(StrEnum):
|
|
18
|
+
ANALYTICS = "IconAnalytics"
|
|
19
|
+
BOOK = "IconBook"
|
|
20
|
+
FOLDERDATA = "IconFolderData"
|
|
21
|
+
INTEGRATION = "IconIntegration"
|
|
22
|
+
TEXT_COMPARE = "IconTextCompare"
|
|
23
|
+
WORLD = "IconWorld"
|
|
24
|
+
QUICK_REPLY = "IconQuickReply"
|
|
25
|
+
CHAT_PLUS = "IconChatPlus"
|
|
26
|
+
TELESCOPE = "IconTelescope"
|
|
27
|
+
CHART_BAR = "IconChartBar"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ToolSelectionPolicy(StrEnum):
|
|
31
|
+
"""Determine the usage policy of tools."""
|
|
32
|
+
|
|
33
|
+
FORCED_BY_DEFAULT = "ForcedByDefault"
|
|
34
|
+
ON_BY_DEFAULT = "OnByDefault"
|
|
35
|
+
BY_USER = "ByUser"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def handle_undefined_icon(value: Any) -> ToolIcon:
|
|
39
|
+
try:
|
|
40
|
+
if isinstance(value, str):
|
|
41
|
+
return ToolIcon(value)
|
|
42
|
+
else:
|
|
43
|
+
return ToolIcon.BOOK
|
|
44
|
+
except ValueError:
|
|
45
|
+
return ToolIcon.BOOK
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ToolBuildConfig(BaseModel):
|
|
49
|
+
model_config = get_configuration_dict()
|
|
50
|
+
"""Main tool configuration"""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
configuration: BaseToolConfig
|
|
54
|
+
display_name: str = ""
|
|
55
|
+
icon: Annotated[ToolIcon, BeforeValidator(handle_undefined_icon)] = Field(
|
|
56
|
+
default=ToolIcon.BOOK,
|
|
57
|
+
description="The icon name that will be used to display the tool in the user interface.",
|
|
58
|
+
)
|
|
59
|
+
selection_policy: ToolSelectionPolicy = Field(
|
|
60
|
+
default=ToolSelectionPolicy.BY_USER,
|
|
61
|
+
)
|
|
62
|
+
is_exclusive: bool = Field(
|
|
63
|
+
default=False,
|
|
64
|
+
description="This tool must be chosen by the user and no other tools are used for this iteration.",
|
|
65
|
+
)
|
|
66
|
+
is_sub_agent: bool = False
|
|
67
|
+
|
|
68
|
+
is_enabled: bool = Field(default=True)
|
|
69
|
+
|
|
70
|
+
@model_validator(mode="before")
|
|
71
|
+
def initialize_config_based_on_tool_name(
|
|
72
|
+
cls,
|
|
73
|
+
value: Any,
|
|
74
|
+
info: ValidationInfo,
|
|
75
|
+
) -> Any:
|
|
76
|
+
"""Check the given values for."""
|
|
77
|
+
if not isinstance(value, dict):
|
|
78
|
+
return value
|
|
79
|
+
|
|
80
|
+
is_mcp_tool = value.get("mcp_source_id", "") != ""
|
|
81
|
+
mcp_configuration = value.get("configuration", {})
|
|
82
|
+
|
|
83
|
+
# Import at runtime to avoid circular imports
|
|
84
|
+
from unique_toolkit.agentic.tools.mcp.models import MCPToolConfig
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
isinstance(mcp_configuration, MCPToolConfig)
|
|
88
|
+
and mcp_configuration.mcp_source_id
|
|
89
|
+
):
|
|
90
|
+
return value
|
|
91
|
+
if is_mcp_tool:
|
|
92
|
+
# For MCP tools, skip ToolFactory validation
|
|
93
|
+
# Configuration can remain as a dict
|
|
94
|
+
return value
|
|
95
|
+
|
|
96
|
+
is_sub_agent_tool = (
|
|
97
|
+
value.get("is_sub_agent") or value.get("isSubAgent") or False
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
configuration = value.get("configuration", {})
|
|
101
|
+
|
|
102
|
+
if is_sub_agent_tool:
|
|
103
|
+
from unique_toolkit.agentic.tools.a2a import ExtendedSubAgentToolConfig
|
|
104
|
+
|
|
105
|
+
config = ExtendedSubAgentToolConfig.model_validate(configuration)
|
|
106
|
+
elif isinstance(configuration, dict):
|
|
107
|
+
# Local import to avoid circular import at module import time
|
|
108
|
+
from unique_toolkit.agentic.tools.factory import ToolFactory
|
|
109
|
+
|
|
110
|
+
config = ToolFactory.build_tool_config(
|
|
111
|
+
value["name"],
|
|
112
|
+
**configuration,
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
# Check that the type of config matches the tool name
|
|
116
|
+
from unique_toolkit.agentic.tools.factory import ToolFactory
|
|
117
|
+
|
|
118
|
+
assert isinstance(
|
|
119
|
+
configuration,
|
|
120
|
+
ToolFactory.tool_config_map[value["name"]], # type: ignore
|
|
121
|
+
)
|
|
122
|
+
config = configuration
|
|
123
|
+
value["configuration"] = config
|
|
124
|
+
return value
|
|
125
|
+
|
|
126
|
+
@field_serializer("configuration")
|
|
127
|
+
def serialize_config(self, value: BaseToolConfig) -> dict[str, Any]:
|
|
128
|
+
return value.__class__.model_dump(value)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Callable
|
|
2
|
+
|
|
3
|
+
from unique_toolkit.agentic.tools.schemas import BaseToolConfig
|
|
4
|
+
from unique_toolkit.agentic.tools.tool import Tool
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from unique_toolkit.agentic.tools.config import ToolBuildConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolFactory:
|
|
11
|
+
tool_map: dict[str, type[Tool]] = {}
|
|
12
|
+
tool_config_map: dict[str, Callable] = {}
|
|
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
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def register_tool(
|
|
20
|
+
cls,
|
|
21
|
+
tool: type[Tool],
|
|
22
|
+
tool_config: type[BaseToolConfig],
|
|
23
|
+
):
|
|
24
|
+
cls.tool_map[tool.name] = tool
|
|
25
|
+
cls.tool_config_map[tool.name] = tool_config
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def build_tool(cls, tool_name: str, *args, **kwargs) -> Tool[BaseToolConfig]:
|
|
29
|
+
tool = cls.tool_map[tool_name](*args, **kwargs)
|
|
30
|
+
return tool
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def build_tool_with_settings(
|
|
34
|
+
cls, tool_name: str, settings: "ToolBuildConfig", *args, **kwargs
|
|
35
|
+
) -> Tool[BaseToolConfig]:
|
|
36
|
+
tool = cls.tool_map[tool_name](*args, **kwargs)
|
|
37
|
+
tool.settings = settings
|
|
38
|
+
return tool
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def build_tool_config(cls, tool_name: str, **kwargs) -> BaseToolConfig:
|
|
42
|
+
if tool_name not in cls.tool_config_map:
|
|
43
|
+
raise ValueError(f"Tool {tool_name} not found")
|
|
44
|
+
return cls.tool_config_map[tool_name](**kwargs)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from unique_toolkit.agentic.tools.config import (
|
|
4
|
+
ToolBuildConfig,
|
|
5
|
+
ToolIcon,
|
|
6
|
+
ToolSelectionPolicy,
|
|
7
|
+
)
|
|
8
|
+
from unique_toolkit.agentic.tools.mcp.models import MCPToolConfig
|
|
9
|
+
from unique_toolkit.agentic.tools.mcp.tool_wrapper import MCPToolWrapper
|
|
10
|
+
from unique_toolkit.agentic.tools.schemas import BaseToolConfig
|
|
11
|
+
from unique_toolkit.agentic.tools.tool import Tool
|
|
12
|
+
from unique_toolkit.agentic.tools.tool_progress_reporter import ToolProgressReporter
|
|
13
|
+
from unique_toolkit.app.schemas import ChatEvent, McpServer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MCPManager:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
mcp_servers: list[McpServer],
|
|
20
|
+
event: ChatEvent,
|
|
21
|
+
tool_progress_reporter: ToolProgressReporter,
|
|
22
|
+
):
|
|
23
|
+
self._mcp_servers = mcp_servers
|
|
24
|
+
self._event = event
|
|
25
|
+
self._tool_progress_reporter = tool_progress_reporter
|
|
26
|
+
|
|
27
|
+
def get_mcp_servers(self):
|
|
28
|
+
return self._mcp_servers
|
|
29
|
+
|
|
30
|
+
def get_mcp_server_by_id(self, id: str):
|
|
31
|
+
return next((server for server in self._mcp_servers if server.id == id), None)
|
|
32
|
+
|
|
33
|
+
def get_all_mcp_tools(self) -> list[Tool[BaseToolConfig]]:
|
|
34
|
+
selected_tools = []
|
|
35
|
+
for server in self._mcp_servers:
|
|
36
|
+
if not hasattr(server, "tools"):
|
|
37
|
+
continue
|
|
38
|
+
if not server.tools:
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
for tool in server.tools:
|
|
42
|
+
try:
|
|
43
|
+
config = MCPToolConfig(
|
|
44
|
+
server_id=server.id,
|
|
45
|
+
server_name=server.name,
|
|
46
|
+
server_system_prompt=server.system_prompt,
|
|
47
|
+
server_user_prompt=server.user_prompt,
|
|
48
|
+
mcp_source_id=server.id,
|
|
49
|
+
)
|
|
50
|
+
wrapper = MCPToolWrapper(
|
|
51
|
+
mcp_server=server,
|
|
52
|
+
mcp_tool=tool,
|
|
53
|
+
config=config,
|
|
54
|
+
event=self._event,
|
|
55
|
+
tool_progress_reporter=self._tool_progress_reporter,
|
|
56
|
+
)
|
|
57
|
+
wrapper.settings = ToolBuildConfig( # TODO: this must be refactored to behave like the other tools.
|
|
58
|
+
name=tool.name,
|
|
59
|
+
configuration=config,
|
|
60
|
+
display_name=tool.title or tool.name,
|
|
61
|
+
is_exclusive=False,
|
|
62
|
+
is_enabled=True,
|
|
63
|
+
icon=ToolIcon.BOOK,
|
|
64
|
+
selection_policy=ToolSelectionPolicy.BY_USER,
|
|
65
|
+
)
|
|
66
|
+
selected_tools.append(wrapper)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logging.error(
|
|
69
|
+
f"Error creating MCP tool wrapper for {tool.name}: {e}"
|
|
70
|
+
)
|
|
71
|
+
return selected_tools
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from unique_toolkit.agentic.tools.schemas import BaseToolConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MCPTool:
|
|
7
|
+
"""Protocol defining the expected structure of an MCP tool."""
|
|
8
|
+
|
|
9
|
+
name: str
|
|
10
|
+
description: Optional[str]
|
|
11
|
+
input_schema: Dict[str, Any]
|
|
12
|
+
output_schema: Optional[Dict[str, Any]]
|
|
13
|
+
annotations: Optional[Dict[str, Any]]
|
|
14
|
+
title: Optional[str]
|
|
15
|
+
icon: Optional[str]
|
|
16
|
+
system_prompt: Optional[str]
|
|
17
|
+
user_prompt: Optional[str]
|
|
18
|
+
is_connected: bool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MCPToolConfig(BaseToolConfig):
|
|
22
|
+
"""Configuration for MCP tools"""
|
|
23
|
+
|
|
24
|
+
server_id: str
|
|
25
|
+
server_name: str
|
|
26
|
+
server_system_prompt: Optional[str] = None
|
|
27
|
+
server_user_prompt: Optional[str] = None
|
|
28
|
+
mcp_source_id: str
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
import unique_sdk
|
|
6
|
+
|
|
7
|
+
from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
|
|
8
|
+
from unique_toolkit.agentic.tools.mcp.models import MCPToolConfig
|
|
9
|
+
from unique_toolkit.agentic.tools.schemas import ToolCallResponse
|
|
10
|
+
from unique_toolkit.agentic.tools.tool import Tool
|
|
11
|
+
from unique_toolkit.agentic.tools.tool_progress_reporter import (
|
|
12
|
+
ProgressState,
|
|
13
|
+
ToolProgressReporter,
|
|
14
|
+
)
|
|
15
|
+
from unique_toolkit.app.schemas import ChatEvent, McpServer, McpTool
|
|
16
|
+
from unique_toolkit.language_model.schemas import (
|
|
17
|
+
LanguageModelFunction,
|
|
18
|
+
LanguageModelToolDescription,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MCPToolWrapper(Tool[MCPToolConfig]):
|
|
25
|
+
"""Wrapper class for MCP tools that implements the Tool interface"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
mcp_server: McpServer,
|
|
30
|
+
mcp_tool: McpTool,
|
|
31
|
+
config: MCPToolConfig,
|
|
32
|
+
event: ChatEvent,
|
|
33
|
+
tool_progress_reporter: ToolProgressReporter | None = None,
|
|
34
|
+
):
|
|
35
|
+
self.name = mcp_tool.name
|
|
36
|
+
super().__init__(config, event, tool_progress_reporter)
|
|
37
|
+
self._mcp_tool = mcp_tool
|
|
38
|
+
self._mcp_server = mcp_server
|
|
39
|
+
|
|
40
|
+
def tool_description(self) -> LanguageModelToolDescription:
|
|
41
|
+
"""Convert MCP tool schema to LanguageModelToolDescription"""
|
|
42
|
+
# Create a Pydantic model from the MCP tool's input schema
|
|
43
|
+
logger.info(
|
|
44
|
+
"MCP tool %s schema %s", self._mcp_tool.name, self._mcp_tool.input_schema
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return LanguageModelToolDescription(
|
|
48
|
+
name=self.name,
|
|
49
|
+
description=self._mcp_tool.description or "",
|
|
50
|
+
parameters=self._mcp_tool.input_schema,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def _json_schema_to_python_type(self, schema: Dict[str, Any]) -> type:
|
|
54
|
+
"""Convert JSON schema type to Python type"""
|
|
55
|
+
json_type = schema.get("type", "string")
|
|
56
|
+
|
|
57
|
+
type_mapping = {
|
|
58
|
+
"string": str,
|
|
59
|
+
"integer": int,
|
|
60
|
+
"number": float,
|
|
61
|
+
"boolean": bool,
|
|
62
|
+
"array": list,
|
|
63
|
+
"object": dict,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return type_mapping.get(json_type, str)
|
|
67
|
+
|
|
68
|
+
def tool_description_for_system_prompt(self) -> str:
|
|
69
|
+
"""Return tool description for system prompt"""
|
|
70
|
+
# Not using jinja here to keep it simple and not import new packages.
|
|
71
|
+
description = (
|
|
72
|
+
f"**MCP Server**: {self._mcp_server.name}\n"
|
|
73
|
+
f"**Tool Name**: {self.name}\n"
|
|
74
|
+
f"{self._mcp_tool.system_prompt}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return description
|
|
78
|
+
|
|
79
|
+
def tool_description_for_user_prompt(self) -> str:
|
|
80
|
+
return self._mcp_tool.user_prompt or ""
|
|
81
|
+
|
|
82
|
+
def tool_format_information_for_user_prompt(self) -> str:
|
|
83
|
+
return ""
|
|
84
|
+
|
|
85
|
+
def tool_format_information_for_system_prompt(self) -> str:
|
|
86
|
+
"""Return formatting information for system prompt"""
|
|
87
|
+
return self._mcp_tool.tool_format_information or ""
|
|
88
|
+
|
|
89
|
+
def evaluation_check_list(self) -> list[EvaluationMetricName]:
|
|
90
|
+
"""Return evaluation check list - empty for MCP tools for now"""
|
|
91
|
+
# TODO: this is empty for now as it requires a setting in the backend for choosing a suitable validator.
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
def get_evaluation_checks_based_on_tool_response(
|
|
95
|
+
self,
|
|
96
|
+
tool_response: ToolCallResponse,
|
|
97
|
+
) -> list[EvaluationMetricName]:
|
|
98
|
+
"""Return evaluation checks based on tool response"""
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
async def run(self, tool_call: LanguageModelFunction) -> ToolCallResponse:
|
|
102
|
+
"""Execute the MCP tool using SDK to call public API"""
|
|
103
|
+
self.logger.info(f"Running MCP tool: {self.name}")
|
|
104
|
+
|
|
105
|
+
# Notify progress if reporter is available
|
|
106
|
+
if self._tool_progress_reporter:
|
|
107
|
+
await self._tool_progress_reporter.notify_from_tool_call(
|
|
108
|
+
tool_call=tool_call,
|
|
109
|
+
name=f"**{self.display_name()}**",
|
|
110
|
+
message=f"Executing MCP tool: {self.display_name()}",
|
|
111
|
+
state=ProgressState.RUNNING,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
# Robust argument extraction and validation
|
|
116
|
+
arguments = self._extract_and_validate_arguments(tool_call)
|
|
117
|
+
|
|
118
|
+
# Use SDK to call the public API
|
|
119
|
+
result = await self._call_mcp_tool_via_sdk(arguments)
|
|
120
|
+
|
|
121
|
+
# Create successful response
|
|
122
|
+
tool_response = ToolCallResponse( # TODO: Why result here not applied directly to the body of the tool_response? like so how does it know the results in the history?
|
|
123
|
+
id=tool_call.id or "",
|
|
124
|
+
name=self.name,
|
|
125
|
+
debug_info={
|
|
126
|
+
"mcp_tool": self.name,
|
|
127
|
+
"arguments": arguments,
|
|
128
|
+
},
|
|
129
|
+
error_message="",
|
|
130
|
+
content=json.dumps(result),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Notify completion
|
|
134
|
+
if self._tool_progress_reporter:
|
|
135
|
+
await self._tool_progress_reporter.notify_from_tool_call(
|
|
136
|
+
tool_call=tool_call,
|
|
137
|
+
name=f"**{self.display_name()}**",
|
|
138
|
+
message=f"MCP tool completed: {self.display_name()}",
|
|
139
|
+
state=ProgressState.FINISHED,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return tool_response
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
self.logger.error(f"Error executing MCP tool {self.name}: {e}")
|
|
146
|
+
|
|
147
|
+
# Notify failure
|
|
148
|
+
if self._tool_progress_reporter:
|
|
149
|
+
await self._tool_progress_reporter.notify_from_tool_call(
|
|
150
|
+
tool_call=tool_call,
|
|
151
|
+
name=f"**{self.display_name()}**",
|
|
152
|
+
message=f"MCP tool failed: {str(e)}",
|
|
153
|
+
state=ProgressState.FAILED,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return ToolCallResponse(
|
|
157
|
+
id=tool_call.id or "",
|
|
158
|
+
name=self.name,
|
|
159
|
+
debug_info={
|
|
160
|
+
"mcp_tool": self.name,
|
|
161
|
+
"error": str(e),
|
|
162
|
+
"original_arguments": getattr(tool_call, "arguments", None),
|
|
163
|
+
},
|
|
164
|
+
error_message=str(e),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def _extract_and_validate_arguments(
|
|
168
|
+
self, tool_call: LanguageModelFunction
|
|
169
|
+
) -> Dict[str, Any]:
|
|
170
|
+
"""
|
|
171
|
+
Extract and validate arguments from tool call, handling various formats robustly.
|
|
172
|
+
|
|
173
|
+
The arguments field can come in different formats:
|
|
174
|
+
1. As a JSON string (expected format from OpenAI API)
|
|
175
|
+
2. As a dictionary (from internal processing)
|
|
176
|
+
3. As None or empty (edge cases)
|
|
177
|
+
"""
|
|
178
|
+
raw_arguments = tool_call.arguments
|
|
179
|
+
|
|
180
|
+
# Handle None or empty arguments
|
|
181
|
+
if not raw_arguments:
|
|
182
|
+
self.logger.warning(f"MCP tool {self.name} called with empty arguments")
|
|
183
|
+
return {}
|
|
184
|
+
|
|
185
|
+
# Handle string arguments (JSON format)
|
|
186
|
+
if isinstance(raw_arguments, str):
|
|
187
|
+
try:
|
|
188
|
+
parsed_arguments = json.loads(raw_arguments)
|
|
189
|
+
if not isinstance(parsed_arguments, dict):
|
|
190
|
+
self.logger.warning(
|
|
191
|
+
f"MCP tool {self.name}: arguments JSON parsed to non-dict: {type(parsed_arguments)}"
|
|
192
|
+
)
|
|
193
|
+
return {}
|
|
194
|
+
return parsed_arguments
|
|
195
|
+
except json.JSONDecodeError as e:
|
|
196
|
+
self.logger.error(
|
|
197
|
+
f"MCP tool {self.name}: failed to parse arguments JSON '{raw_arguments}': {e}"
|
|
198
|
+
)
|
|
199
|
+
raise ValueError(
|
|
200
|
+
f"Invalid JSON arguments for MCP tool {self.name}: {e}"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Handle dictionary arguments (already parsed)
|
|
204
|
+
if isinstance(raw_arguments, dict):
|
|
205
|
+
self.logger.debug(f"MCP tool {self.name}: arguments already in dict format")
|
|
206
|
+
return raw_arguments
|
|
207
|
+
|
|
208
|
+
# Handle unexpected argument types
|
|
209
|
+
self.logger.error(
|
|
210
|
+
f"MCP tool {self.name}: unexpected arguments type {type(raw_arguments)}: {raw_arguments}"
|
|
211
|
+
)
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f"Unexpected arguments type for MCP tool {self.name}: {type(raw_arguments)}"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
async def _call_mcp_tool_via_sdk(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
217
|
+
"""Call MCP tool via SDK to public API"""
|
|
218
|
+
try:
|
|
219
|
+
result = unique_sdk.MCP.call_tool(
|
|
220
|
+
user_id=self.event.user_id,
|
|
221
|
+
company_id=self.event.company_id,
|
|
222
|
+
name=self.name,
|
|
223
|
+
arguments=arguments,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
self.logger.info(
|
|
227
|
+
f"Calling MCP tool {self.name} with arguments: {arguments}"
|
|
228
|
+
)
|
|
229
|
+
self.logger.debug(f"Result: {result}")
|
|
230
|
+
|
|
231
|
+
return result
|
|
232
|
+
except Exception as e:
|
|
233
|
+
self.logger.error(f"SDK call failed for MCP tool {self.name}: {e}")
|
|
234
|
+
raise
|
|
@@ -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,46 @@
|
|
|
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()
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def is_enabled(self) -> bool:
|
|
34
|
+
raise NotImplementedError()
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def is_exclusive(self) -> bool:
|
|
38
|
+
raise NotImplementedError()
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def takes_control(self) -> bool:
|
|
42
|
+
raise NotImplementedError()
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def display_name(self) -> str:
|
|
46
|
+
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"]
|