unique_toolkit 0.8.39__tar.gz → 0.8.41__tar.gz

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.
Files changed (138) hide show
  1. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/CHANGELOG.md +6 -0
  2. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/PKG-INFO +8 -1
  3. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/pyproject.toml +2 -1
  4. unique_toolkit-0.8.41/unique_toolkit/_common/base_model_type_attribute.py +303 -0
  5. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/a2a/service.py +4 -0
  6. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/LICENSE +0 -0
  7. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/README.md +0 -0
  8. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/__init__.py +0 -0
  9. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/_base_service.py +0 -0
  10. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/_time_utils.py +0 -0
  11. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/chunk_relevancy_sorter/config.py +0 -0
  12. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/chunk_relevancy_sorter/exception.py +0 -0
  13. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +0 -0
  14. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/chunk_relevancy_sorter/service.py +0 -0
  15. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +0 -0
  16. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/default_language_model.py +0 -0
  17. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/endpoint_builder.py +0 -0
  18. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/exception.py +0 -0
  19. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/feature_flags/schema.py +0 -0
  20. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/pydantic_helpers.py +0 -0
  21. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/token/image_token_counting.py +0 -0
  22. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/token/token_counting.py +0 -0
  23. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/utils/structured_output/schema.py +0 -0
  24. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/validate_required_values.py +0 -0
  25. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/_common/validators.py +0 -0
  26. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/app/__init__.py +0 -0
  27. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/app/dev_util.py +0 -0
  28. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/app/init_logging.py +0 -0
  29. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/app/init_sdk.py +0 -0
  30. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/app/performance/async_tasks.py +0 -0
  31. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/app/performance/async_wrapper.py +0 -0
  32. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/app/schemas.py +0 -0
  33. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/app/unique_settings.py +0 -0
  34. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/app/verification.py +0 -0
  35. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/chat/__init__.py +0 -0
  36. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/chat/constants.py +0 -0
  37. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/chat/functions.py +0 -0
  38. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/chat/schemas.py +0 -0
  39. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/chat/service.py +0 -0
  40. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/chat/state.py +0 -0
  41. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/chat/utils.py +0 -0
  42. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/content/__init__.py +0 -0
  43. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/content/constants.py +0 -0
  44. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/content/functions.py +0 -0
  45. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/content/schemas.py +0 -0
  46. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/content/service.py +0 -0
  47. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/content/utils.py +0 -0
  48. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/debug_info_manager/debug_info_manager.py +0 -0
  49. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/embedding/__init__.py +0 -0
  50. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/embedding/constants.py +0 -0
  51. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/embedding/functions.py +0 -0
  52. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/embedding/schemas.py +0 -0
  53. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/embedding/service.py +0 -0
  54. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/embedding/utils.py +0 -0
  55. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/config.py +0 -0
  56. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/context_relevancy/prompts.py +0 -0
  57. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/context_relevancy/schema.py +0 -0
  58. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/context_relevancy/service.py +0 -0
  59. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/evaluation_manager.py +0 -0
  60. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/exception.py +0 -0
  61. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/hallucination/constants.py +0 -0
  62. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/hallucination/hallucination_evaluation.py +0 -0
  63. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/hallucination/prompts.py +0 -0
  64. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/hallucination/service.py +0 -0
  65. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/hallucination/utils.py +0 -0
  66. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/output_parser.py +0 -0
  67. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/schemas.py +0 -0
  68. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/tests/test_context_relevancy_service.py +0 -0
  69. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evals/tests/test_output_parser.py +0 -0
  70. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/__init__.py +0 -0
  71. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/config.py +0 -0
  72. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/constants.py +0 -0
  73. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/context_relevancy/constants.py +0 -0
  74. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/context_relevancy/prompts.py +0 -0
  75. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/context_relevancy/service.py +0 -0
  76. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/context_relevancy/utils.py +0 -0
  77. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/exception.py +0 -0
  78. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/hallucination/constants.py +0 -0
  79. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/hallucination/prompts.py +0 -0
  80. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/hallucination/service.py +0 -0
  81. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/hallucination/utils.py +0 -0
  82. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/output_parser.py +0 -0
  83. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/evaluators/schemas.py +0 -0
  84. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/framework_utilities/__init__.py +0 -0
  85. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/framework_utilities/langchain/client.py +0 -0
  86. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/framework_utilities/langchain/history.py +0 -0
  87. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/framework_utilities/openai/__init__.py +0 -0
  88. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/framework_utilities/openai/client.py +0 -0
  89. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/framework_utilities/openai/message_builder.py +0 -0
  90. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/framework_utilities/utils.py +0 -0
  91. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/history_manager/history_construction_with_contents.py +0 -0
  92. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/history_manager/history_manager.py +0 -0
  93. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/history_manager/loop_token_reducer.py +0 -0
  94. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/history_manager/utils.py +0 -0
  95. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/__init__.py +0 -0
  96. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/builder.py +0 -0
  97. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/constants.py +0 -0
  98. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/functions.py +0 -0
  99. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/infos.py +0 -0
  100. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/prompt.py +0 -0
  101. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/reference.py +0 -0
  102. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/schemas.py +0 -0
  103. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/service.py +0 -0
  104. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/language_model/utils.py +0 -0
  105. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/postprocessor/postprocessor_manager.py +0 -0
  106. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/protocols/support.py +0 -0
  107. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/reference_manager/reference_manager.py +0 -0
  108. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/short_term_memory/__init__.py +0 -0
  109. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/short_term_memory/constants.py +0 -0
  110. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/short_term_memory/functions.py +0 -0
  111. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/short_term_memory/persistent_short_term_memory_manager.py +0 -0
  112. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/short_term_memory/schemas.py +0 -0
  113. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/short_term_memory/service.py +0 -0
  114. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/smart_rules/__init__.py +0 -0
  115. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/smart_rules/compile.py +0 -0
  116. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/thinking_manager/thinking_manager.py +0 -0
  117. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/a2a/__init__.py +0 -0
  118. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/a2a/config.py +0 -0
  119. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/a2a/manager.py +0 -0
  120. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/a2a/memory.py +0 -0
  121. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/a2a/schema.py +0 -0
  122. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/agent_chunks_hanlder.py +0 -0
  123. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/config.py +0 -0
  124. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/factory.py +0 -0
  125. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/mcp/__init__.py +0 -0
  126. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/mcp/manager.py +0 -0
  127. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/mcp/models.py +0 -0
  128. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/mcp/tool_wrapper.py +0 -0
  129. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/schemas.py +0 -0
  130. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/test/test_mcp_manager.py +0 -0
  131. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/test/test_tool_progress_reporter.py +0 -0
  132. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/tool.py +0 -0
  133. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/tool_manager.py +0 -0
  134. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/tool_progress_reporter.py +0 -0
  135. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/utils/execution/execution.py +0 -0
  136. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/utils/source_handling/schema.py +0 -0
  137. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/utils/source_handling/source_formatting.py +0 -0
  138. {unique_toolkit-0.8.39 → unique_toolkit-0.8.41}/unique_toolkit/tools/utils/source_handling/tests/test_source_formatting.py +0 -0
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.8.41] - 2025-09-02
9
+ - Make A2A tool auto register with tool factory
10
+
11
+ ## [0.8.40] - 2025-09-02
12
+ - Add frontend compatible type for pydantic BaseModel types in pydantic BaseModels
13
+
8
14
  ## [0.8.39] - 2025-09-02
9
15
  - include `get_async_openai_client`
10
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 0.8.39
3
+ Version: 0.8.41
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Martin Fadler
@@ -10,6 +10,7 @@ Classifier: License :: Other/Proprietary License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: jambo (>=0.1.2,<0.2.0)
13
14
  Requires-Dist: numpy (>=1.26.4,<2.0.0)
14
15
  Requires-Dist: openai (>=1.99.9,<2.0.0)
15
16
  Requires-Dist: pillow (>=10.4.0,<11.0.0)
@@ -116,6 +117,12 @@ All notable changes to this project will be documented in this file.
116
117
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
117
118
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
118
119
 
120
+ ## [0.8.41] - 2025-09-02
121
+ - Make A2A tool auto register with tool factory
122
+
123
+ ## [0.8.40] - 2025-09-02
124
+ - Add frontend compatible type for pydantic BaseModel types in pydantic BaseModels
125
+
119
126
  ## [0.8.39] - 2025-09-02
120
127
  - include `get_async_openai_client`
121
128
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "unique_toolkit"
3
- version = "0.8.39"
3
+ version = "0.8.41"
4
4
  description = ""
5
5
  authors = [
6
6
  "Martin Fadler <martin.fadler@unique.ch>",
@@ -26,6 +26,7 @@ openai = "^1.99.9"
26
26
  platformdirs = "^4.0.0"
27
27
  pillow = "^10.4.0"
28
28
  unique-sdk = "^0.10.0"
29
+ jambo = "^0.1.2"
29
30
 
30
31
  [tool.poetry.group.langchain]
31
32
  optional = true
@@ -0,0 +1,303 @@
1
+ """
2
+ The following can be used to define a pydantic BaseModel that has has
3
+ an attribute of type Pydantic BaseModel.
4
+
5
+ This is useful for:
6
+ - Tooldefinition for large language models (LLMs) with flexible parameters.
7
+ - General Endpoint defintions from configuration
8
+ """
9
+
10
+ import json
11
+ from enum import StrEnum
12
+ from typing import Annotated, Any, TypeVar, Union, get_args, get_origin
13
+
14
+ from jambo import SchemaConverter
15
+ from jambo.types.json_schema_type import JSONSchema
16
+ from pydantic import (
17
+ BaseModel,
18
+ BeforeValidator,
19
+ Field,
20
+ create_model,
21
+ )
22
+
23
+
24
+ def _get_actual_type(python_type: type) -> type | None | Any:
25
+ if get_origin(python_type) is not None:
26
+ origin = get_origin(python_type)
27
+ args = get_args(python_type)
28
+
29
+ if origin is Annotated:
30
+ # For Annotated types, the first argument is the actual type
31
+ if args:
32
+ actual_type = args[0]
33
+ # Recursively handle nested generic types (e.g., Annotated[Optional[str], ...])
34
+ if get_origin(actual_type) is not None:
35
+ return _get_actual_type(actual_type)
36
+ else:
37
+ raise ValueError(f"Invalid Annotated type: {python_type}")
38
+ elif origin is Union:
39
+ # For Union types (including Optional), use the first non-None type
40
+ if args:
41
+ for arg in args:
42
+ if arg is not type(None): # Skip NoneType
43
+ return _get_actual_type(arg)
44
+ raise ValueError(f"Union type contains only None: {python_type}")
45
+ else:
46
+ raise ValueError(f"Invalid Union type: {python_type}")
47
+ else:
48
+ # Other generic types, use the origin
49
+ actual_type = origin
50
+ else:
51
+ # Regular type
52
+ actual_type = python_type
53
+
54
+ return actual_type
55
+
56
+
57
+ class ParameterType(StrEnum):
58
+ STRING = "string"
59
+ INTEGER = "integer"
60
+ NUMBER = "number"
61
+ BOOLEAN = "boolean"
62
+
63
+ def to_python_type(self) -> type:
64
+ """Convert ParameterType to Python type"""
65
+
66
+ match self:
67
+ case ParameterType.STRING:
68
+ return str
69
+ case ParameterType.INTEGER:
70
+ return int
71
+ case ParameterType.NUMBER:
72
+ return float
73
+ case ParameterType.BOOLEAN:
74
+ return bool
75
+ case _:
76
+ raise ValueError(f"Invalid ParameterType: {self}")
77
+
78
+ @classmethod
79
+ def from_python_type(cls, python_type: type) -> "ParameterType":
80
+ type_to_check = _get_actual_type(python_type)
81
+
82
+ # Ensure we have a class before calling issubclass
83
+ if not isinstance(type_to_check, type):
84
+ raise ValueError(f"Invalid Python type: {python_type}")
85
+
86
+ # Check bool first since bool is a subclass of int in Python
87
+ if issubclass(type_to_check, bool):
88
+ return cls.BOOLEAN
89
+ if issubclass(type_to_check, int):
90
+ return cls.INTEGER
91
+ if issubclass(type_to_check, float):
92
+ return cls.NUMBER
93
+ if issubclass(type_to_check, str):
94
+ return cls.STRING
95
+ raise ValueError(f"Invalid Python type: {python_type}")
96
+
97
+
98
+ class Parameter(BaseModel):
99
+ type: ParameterType
100
+ name: str
101
+ description: str
102
+ required: bool
103
+
104
+
105
+ def create_pydantic_model_from_parameter_list(
106
+ title: str, parameter_list: list[Parameter]
107
+ ) -> type[BaseModel]:
108
+ """Create a Pydantic model from MCP tool's input schema"""
109
+
110
+ # Convert JSON schema properties to Pydantic fields
111
+ fields = {}
112
+ for parameter in parameter_list:
113
+ if parameter.required:
114
+ field = Field(description=parameter.description)
115
+ else:
116
+ field = Field(default=None, description=parameter.description)
117
+
118
+ fields[parameter.name] = (
119
+ parameter.type.to_python_type(),
120
+ field,
121
+ )
122
+
123
+ return create_model(title, **fields)
124
+
125
+
126
+ def convert_to_base_model_type(
127
+ value: type[BaseModel] | str | list[Parameter] | None,
128
+ ) -> type[BaseModel]:
129
+ """
130
+ BeforeValidator that ensures the final type is always of type[BaseModel].
131
+
132
+ If the input is already a BaseModel class, returns it as-is.
133
+ If the input is a list of Parameter as defined above, converts it to a BaseModel class
134
+ If the input is a str (JSON schema), converts it to a BaseModel class using SchemaConverter from Jambo.
135
+ """
136
+ if isinstance(value, type) and issubclass(value, BaseModel):
137
+ return value
138
+
139
+ if isinstance(value, list):
140
+ if all(isinstance(item, Parameter) for item in value):
141
+ return create_pydantic_model_from_parameter_list("Parameters", value)
142
+
143
+ converter = SchemaConverter()
144
+ if isinstance(value, str):
145
+ return converter.build(JSONSchema(**json.loads(value)))
146
+
147
+ raise ValueError(f"Invalid value: {value}")
148
+
149
+
150
+ def base_model_to_parameter_list(model: type[BaseModel]) -> list[Parameter]:
151
+ parameter = []
152
+ for field_name, field_info in model.model_fields.items():
153
+ parameter.append(
154
+ Parameter(
155
+ type=ParameterType.from_python_type(field_info.annotation or str),
156
+ name=field_name,
157
+ description=field_info.description or "",
158
+ required=field_info.is_required(),
159
+ )
160
+ )
161
+ return parameter
162
+
163
+
164
+ # Create the annotated type that ensures BaseModel and generates clean JSON schema
165
+
166
+ TModel = TypeVar("TModel", bound=BaseModel)
167
+
168
+
169
+ class BaseModelTypeTitle(StrEnum):
170
+ LIST_OF_PARAMETERS = "List of Parameters"
171
+ JSON_SCHEMA_AS_STRING = "JSON Schema as String"
172
+ USE_MODEL_FROM_CODE = "Use Model from Code"
173
+
174
+
175
+ ListOfParameters = Annotated[
176
+ list[Parameter], Field(title=BaseModelTypeTitle.LIST_OF_PARAMETERS.value)
177
+ ]
178
+ JSONSchemaString = Annotated[
179
+ str, Field(title=BaseModelTypeTitle.JSON_SCHEMA_AS_STRING.value)
180
+ ]
181
+ CodefinedModelType = Annotated[
182
+ None, Field(title=BaseModelTypeTitle.USE_MODEL_FROM_CODE.value)
183
+ ]
184
+
185
+
186
+ BaseModelType = Annotated[
187
+ type[TModel],
188
+ BeforeValidator(
189
+ convert_to_base_model_type,
190
+ json_schema_input_type=ListOfParameters | JSONSchemaString | CodefinedModelType,
191
+ ),
192
+ ]
193
+
194
+
195
+ def get_json_schema_extra_for_base_model_type(model: type[BaseModel]):
196
+ """
197
+ Returns a json_schema_extra mutator that injects defaults
198
+ into both the 'string' and 'list[Parameter]' branches.
199
+
200
+ This is used to define default for the "oneOf"/"anyOf" validation
201
+ of the parameters attribute.
202
+ """
203
+ sample_params = base_model_to_parameter_list(model)
204
+
205
+ def _mutate(schema: dict) -> None:
206
+ json_default = json.dumps(model.model_json_schema())
207
+ params_default = [p.model_dump() for p in sample_params]
208
+
209
+ for key in ("oneOf", "anyOf"):
210
+ if key in schema:
211
+ for entry in schema[key]:
212
+ if (
213
+ entry.get("type") == "string"
214
+ and entry.get("title")
215
+ == BaseModelTypeTitle.JSON_SCHEMA_AS_STRING.value
216
+ ):
217
+ entry["default"] = json_default
218
+ if (
219
+ entry.get("type") == "array"
220
+ and entry.get("title")
221
+ == BaseModelTypeTitle.LIST_OF_PARAMETERS.value
222
+ ):
223
+ entry["default"] = params_default
224
+
225
+ return _mutate
226
+
227
+
228
+ if __name__ == "__main__":
229
+ import json
230
+ from pathlib import Path
231
+ from typing import Generic
232
+
233
+ from openai.types.chat.chat_completion_tool_param import ChatCompletionToolParam
234
+ from openai.types.shared_params.function_definition import FunctionDefinition
235
+ from pydantic import BaseModel, Field, field_serializer
236
+
237
+ class ToolDescription(BaseModel, Generic[TModel]):
238
+ name: str = Field(
239
+ ...,
240
+ pattern=r"^[a-zA-Z1-9_-]+$",
241
+ description="Name must adhere to the pattern ^[a-zA-Z1-9_-]+$",
242
+ )
243
+ description: str = Field(
244
+ ...,
245
+ description="Description of what the tool is doing the tool",
246
+ )
247
+
248
+ strict: bool = Field(
249
+ default=False,
250
+ description="Setting strict to true will ensure function calls reliably adhere to the function schema, instead of being best effort.",
251
+ )
252
+
253
+ parameters: BaseModelType[TModel] = Field(
254
+ ...,
255
+ description="Json Schema for the tool parameters. Must be valid JSON Schema and able to convert to a Pydantic model",
256
+ )
257
+
258
+ @field_serializer("parameters")
259
+ def serialize_parameters(self, parameters: type[BaseModel]):
260
+ return parameters.model_json_schema()
261
+
262
+ def to_openai(self) -> ChatCompletionToolParam:
263
+ return ChatCompletionToolParam(
264
+ function=FunctionDefinition(
265
+ name=self.name,
266
+ description=self.description,
267
+ parameters=self.parameters.model_json_schema(),
268
+ strict=self.strict,
269
+ ),
270
+ type="function",
271
+ )
272
+
273
+ class WeatherToolParameterModel(BaseModel):
274
+ lon: float = Field(
275
+ ..., description="The longitude of the location to get the weather for"
276
+ )
277
+ lat: float = Field(
278
+ ..., description="The latitude of the location to get the weather for"
279
+ )
280
+ name: str = Field(
281
+ ..., description="The name of the location to get the weather for"
282
+ )
283
+
284
+ class GetWeatherTool(ToolDescription[WeatherToolParameterModel]):
285
+ parameters: BaseModelType[WeatherToolParameterModel] = Field(
286
+ default=WeatherToolParameterModel,
287
+ json_schema_extra=get_json_schema_extra_for_base_model_type(
288
+ WeatherToolParameterModel
289
+ ),
290
+ )
291
+
292
+ # The json schema can be used in the RSJF library to create a valid frontend component.
293
+ # You can test it on https://rjsf-team.github.io/react-jsonschema-form/
294
+ file = Path(__file__).parent / "weather_tool_schema.json"
295
+ with file.open("w") as f:
296
+ f.write(json.dumps(GetWeatherTool.model_json_schema(), indent=2))
297
+
298
+ # Notice that the t.parameters is a pydantic model with type annotations
299
+ t = GetWeatherTool(
300
+ name="GetWeather", description="Get the weather for a given location"
301
+ )
302
+ t.parameters(lon=100, lat=100, name="Test")
303
+ print(t.model_dump())
@@ -16,6 +16,7 @@ from unique_toolkit.tools.a2a.schema import (
16
16
  SubAgentShortTermMemorySchema,
17
17
  SubAgentToolInput,
18
18
  )
19
+ from unique_toolkit.tools.factory import ToolFactory
19
20
  from unique_toolkit.tools.schemas import ToolCallResponse
20
21
  from unique_toolkit.tools.tool import Tool
21
22
  from unique_toolkit.tools.tool_progress_reporter import (
@@ -150,3 +151,6 @@ class SubAgentTool(Tool[SubAgentToolConfig]):
150
151
  name=tool_response.name,
151
152
  content=tool_response.content,
152
153
  )
154
+
155
+
156
+ ToolFactory.register_tool(SubAgentTool, SubAgentToolConfig)
File without changes