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.
Files changed (190) hide show
  1. unique_toolkit/__init__.py +36 -3
  2. unique_toolkit/_common/api_calling/human_verification_manager.py +357 -0
  3. unique_toolkit/_common/base_model_type_attribute.py +303 -0
  4. unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
  5. unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
  6. unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
  7. unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
  8. unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
  9. unique_toolkit/_common/default_language_model.py +12 -0
  10. unique_toolkit/_common/docx_generator/__init__.py +7 -0
  11. unique_toolkit/_common/docx_generator/config.py +12 -0
  12. unique_toolkit/_common/docx_generator/schemas.py +80 -0
  13. unique_toolkit/_common/docx_generator/service.py +225 -0
  14. unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
  15. unique_toolkit/_common/endpoint_builder.py +368 -0
  16. unique_toolkit/_common/endpoint_requestor.py +480 -0
  17. unique_toolkit/_common/exception.py +24 -0
  18. unique_toolkit/_common/experimental/endpoint_builder.py +368 -0
  19. unique_toolkit/_common/experimental/endpoint_requestor.py +488 -0
  20. unique_toolkit/_common/feature_flags/schema.py +9 -0
  21. unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
  22. unique_toolkit/_common/pydantic_helpers.py +174 -0
  23. unique_toolkit/_common/referencing.py +53 -0
  24. unique_toolkit/_common/string_utilities.py +140 -0
  25. unique_toolkit/_common/tests/test_referencing.py +521 -0
  26. unique_toolkit/_common/tests/test_string_utilities.py +506 -0
  27. unique_toolkit/_common/token/image_token_counting.py +67 -0
  28. unique_toolkit/_common/token/token_counting.py +204 -0
  29. unique_toolkit/_common/utils/__init__.py +1 -0
  30. unique_toolkit/_common/utils/files.py +43 -0
  31. unique_toolkit/_common/utils/image/encode.py +25 -0
  32. unique_toolkit/_common/utils/jinja/helpers.py +10 -0
  33. unique_toolkit/_common/utils/jinja/render.py +18 -0
  34. unique_toolkit/_common/utils/jinja/schema.py +65 -0
  35. unique_toolkit/_common/utils/jinja/utils.py +80 -0
  36. unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
  37. unique_toolkit/_common/utils/structured_output/schema.py +5 -0
  38. unique_toolkit/_common/utils/write_configuration.py +51 -0
  39. unique_toolkit/_common/validators.py +101 -4
  40. unique_toolkit/agentic/__init__.py +1 -0
  41. unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
  42. unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
  43. unique_toolkit/agentic/evaluation/config.py +36 -0
  44. unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
  45. unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
  46. unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
  47. unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
  48. unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
  49. unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +112 -0
  50. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
  51. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +20 -16
  52. unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +32 -21
  53. unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
  54. unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
  55. unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
  56. unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
  57. unique_toolkit/agentic/history_manager/history_construction_with_contents.py +298 -0
  58. unique_toolkit/agentic/history_manager/history_manager.py +241 -0
  59. unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
  60. unique_toolkit/agentic/history_manager/utils.py +96 -0
  61. unique_toolkit/agentic/message_log_manager/__init__.py +5 -0
  62. unique_toolkit/agentic/message_log_manager/service.py +93 -0
  63. unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
  64. unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
  65. unique_toolkit/agentic/responses_api/__init__.py +19 -0
  66. unique_toolkit/agentic/responses_api/postprocessors/code_display.py +71 -0
  67. unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +297 -0
  68. unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
  69. unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
  70. unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
  71. unique_toolkit/agentic/tools/__init__.py +1 -0
  72. unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
  73. unique_toolkit/agentic/tools/a2a/config.py +17 -0
  74. unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
  75. unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
  76. unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
  77. unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
  78. unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
  79. unique_toolkit/agentic/tools/a2a/manager.py +55 -0
  80. unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
  81. unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +240 -0
  82. unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +84 -0
  83. unique_toolkit/agentic/tools/a2a/postprocessing/config.py +78 -0
  84. unique_toolkit/agentic/tools/a2a/postprocessing/display.py +264 -0
  85. unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
  86. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +421 -0
  87. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +2103 -0
  88. unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
  89. unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
  90. unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
  91. unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
  92. unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
  93. unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
  94. unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
  95. unique_toolkit/agentic/tools/a2a/tool/config.py +158 -0
  96. unique_toolkit/agentic/tools/a2a/tool/service.py +393 -0
  97. unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
  98. unique_toolkit/agentic/tools/config.py +128 -0
  99. unique_toolkit/agentic/tools/factory.py +44 -0
  100. unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
  101. unique_toolkit/agentic/tools/mcp/manager.py +71 -0
  102. unique_toolkit/agentic/tools/mcp/models.py +28 -0
  103. unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
  104. unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
  105. unique_toolkit/agentic/tools/openai_builtin/base.py +46 -0
  106. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
  107. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +88 -0
  108. unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +250 -0
  109. unique_toolkit/agentic/tools/openai_builtin/manager.py +79 -0
  110. unique_toolkit/agentic/tools/schemas.py +145 -0
  111. unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
  112. unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
  113. unique_toolkit/agentic/tools/tool.py +187 -0
  114. unique_toolkit/agentic/tools/tool_manager.py +492 -0
  115. unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
  116. unique_toolkit/agentic/tools/utils/__init__.py +19 -0
  117. unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
  118. unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
  119. unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
  120. unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
  121. unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
  122. unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
  123. unique_toolkit/app/__init__.py +9 -0
  124. unique_toolkit/app/dev_util.py +180 -0
  125. unique_toolkit/app/fast_api_factory.py +131 -0
  126. unique_toolkit/app/init_sdk.py +32 -1
  127. unique_toolkit/app/schemas.py +206 -31
  128. unique_toolkit/app/unique_settings.py +367 -0
  129. unique_toolkit/app/webhook.py +77 -0
  130. unique_toolkit/chat/__init__.py +8 -1
  131. unique_toolkit/chat/deprecated/service.py +232 -0
  132. unique_toolkit/chat/functions.py +648 -78
  133. unique_toolkit/chat/rendering.py +34 -0
  134. unique_toolkit/chat/responses_api.py +461 -0
  135. unique_toolkit/chat/schemas.py +134 -2
  136. unique_toolkit/chat/service.py +115 -767
  137. unique_toolkit/content/functions.py +353 -8
  138. unique_toolkit/content/schemas.py +128 -15
  139. unique_toolkit/content/service.py +321 -45
  140. unique_toolkit/content/smart_rules.py +301 -0
  141. unique_toolkit/content/utils.py +10 -3
  142. unique_toolkit/data_extraction/README.md +96 -0
  143. unique_toolkit/data_extraction/__init__.py +11 -0
  144. unique_toolkit/data_extraction/augmented/__init__.py +5 -0
  145. unique_toolkit/data_extraction/augmented/service.py +93 -0
  146. unique_toolkit/data_extraction/base.py +25 -0
  147. unique_toolkit/data_extraction/basic/__init__.py +11 -0
  148. unique_toolkit/data_extraction/basic/config.py +18 -0
  149. unique_toolkit/data_extraction/basic/prompt.py +13 -0
  150. unique_toolkit/data_extraction/basic/service.py +55 -0
  151. unique_toolkit/embedding/service.py +103 -12
  152. unique_toolkit/framework_utilities/__init__.py +1 -0
  153. unique_toolkit/framework_utilities/langchain/__init__.py +10 -0
  154. unique_toolkit/framework_utilities/langchain/client.py +71 -0
  155. unique_toolkit/framework_utilities/langchain/history.py +19 -0
  156. unique_toolkit/framework_utilities/openai/__init__.py +6 -0
  157. unique_toolkit/framework_utilities/openai/client.py +84 -0
  158. unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
  159. unique_toolkit/framework_utilities/utils.py +23 -0
  160. unique_toolkit/language_model/__init__.py +3 -0
  161. unique_toolkit/language_model/_responses_api_utils.py +93 -0
  162. unique_toolkit/language_model/builder.py +27 -11
  163. unique_toolkit/language_model/default_language_model.py +3 -0
  164. unique_toolkit/language_model/functions.py +345 -43
  165. unique_toolkit/language_model/infos.py +1288 -46
  166. unique_toolkit/language_model/reference.py +242 -0
  167. unique_toolkit/language_model/schemas.py +481 -49
  168. unique_toolkit/language_model/service.py +229 -28
  169. unique_toolkit/protocols/support.py +145 -0
  170. unique_toolkit/services/__init__.py +7 -0
  171. unique_toolkit/services/chat_service.py +1631 -0
  172. unique_toolkit/services/knowledge_base.py +1094 -0
  173. unique_toolkit/short_term_memory/service.py +178 -41
  174. unique_toolkit/smart_rules/__init__.py +0 -0
  175. unique_toolkit/smart_rules/compile.py +56 -0
  176. unique_toolkit/test_utilities/events.py +197 -0
  177. unique_toolkit-1.33.3.dist-info/METADATA +1145 -0
  178. unique_toolkit-1.33.3.dist-info/RECORD +205 -0
  179. unique_toolkit/evaluators/__init__.py +0 -1
  180. unique_toolkit/evaluators/config.py +0 -35
  181. unique_toolkit/evaluators/constants.py +0 -1
  182. unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
  183. unique_toolkit/evaluators/context_relevancy/service.py +0 -53
  184. unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
  185. unique_toolkit/evaluators/hallucination/constants.py +0 -41
  186. unique_toolkit-0.7.9.dist-info/METADATA +0 -413
  187. unique_toolkit-0.7.9.dist-info/RECORD +0 -64
  188. /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
  189. {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/LICENSE +0 -0
  190. {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,492 @@
1
+ import asyncio
2
+ from logging import Logger, getLogger
3
+ from typing import Generic, Literal, TypeVar, overload
4
+
5
+ from openai.types.chat import (
6
+ ChatCompletionNamedToolChoiceParam,
7
+ )
8
+ from openai.types.responses import (
9
+ ToolParam,
10
+ response_create_params,
11
+ )
12
+ from pydantic import BaseModel, Field
13
+
14
+ from unique_toolkit.agentic.evaluation.schemas import EvaluationMetricName
15
+ from unique_toolkit.agentic.tools.a2a import A2AManager, SubAgentTool
16
+ from unique_toolkit.agentic.tools.config import ToolBuildConfig
17
+ from unique_toolkit.agentic.tools.factory import ToolFactory
18
+ from unique_toolkit.agentic.tools.mcp.manager import MCPManager
19
+ from unique_toolkit.agentic.tools.openai_builtin.base import (
20
+ OpenAIBuiltInTool,
21
+ OpenAIBuiltInToolName,
22
+ )
23
+ from unique_toolkit.agentic.tools.openai_builtin.manager import OpenAIBuiltInToolManager
24
+ from unique_toolkit.agentic.tools.schemas import ToolCallResponse, ToolPrompts
25
+ from unique_toolkit.agentic.tools.tool import Tool
26
+ from unique_toolkit.agentic.tools.tool_progress_reporter import ToolProgressReporter
27
+ from unique_toolkit.agentic.tools.utils.execution.execution import (
28
+ Result,
29
+ SafeTaskExecutor,
30
+ )
31
+ from unique_toolkit.app.schemas import ChatEvent
32
+ from unique_toolkit.language_model.schemas import (
33
+ LanguageModelFunction,
34
+ LanguageModelToolDescription,
35
+ )
36
+
37
+
38
+ class ToolManagerConfig(BaseModel):
39
+ tools: list[ToolBuildConfig] = Field(
40
+ default=[],
41
+ description="List of tools that the agent can use.",
42
+ )
43
+
44
+ max_tool_calls: int = Field(
45
+ default=10,
46
+ ge=1,
47
+ description="Maximum number of tool calls that can be executed in one iteration.",
48
+ )
49
+
50
+
51
+ _ApiMode = TypeVar("_ApiMode", Literal["completions"], Literal["responses"])
52
+
53
+
54
+ class _ToolManager(Generic[_ApiMode]):
55
+ """
56
+ Manages the tools available to the agent and executes tool calls.
57
+
58
+ This class is responsible for:
59
+ - Initializing tools based on the provided configuration and runtime events.
60
+ - Filtering tools based on availability, exclusivity, and user-defined constraints.
61
+ - Managing the lifecycle of tools, including retrieval, execution, and logging.
62
+ - Executing tool calls in parallel when possible to optimize performance.
63
+ - Enforcing limits on the number of tool calls and handling duplicate requests.
64
+
65
+ Key Features:
66
+ - Dynamic Tool Initialization: Tools are dynamically selected and initialized
67
+ based on runtime events and user preferences.
68
+ - Parallel Execution: Supports asynchronous execution of tools for efficiency.
69
+ - Error Handling: Provides detailed error messages and logs for failed tool calls.
70
+ - Scalability: Designed to handle a large number of tools and tool calls efficiently.
71
+
72
+ Only the ToolManager is allowed to interact with the tools directly.
73
+ """
74
+
75
+ def __init__(
76
+ self,
77
+ logger: Logger,
78
+ config: ToolManagerConfig,
79
+ event: ChatEvent,
80
+ tool_progress_reporter: ToolProgressReporter,
81
+ mcp_manager: MCPManager,
82
+ a2a_manager: A2AManager,
83
+ api_mode: _ApiMode,
84
+ builtin_tool_manager: OpenAIBuiltInToolManager | None = None,
85
+ ) -> None:
86
+ self._logger = logger
87
+ self._config = config
88
+ self._tool_progress_reporter = tool_progress_reporter
89
+ self._tools: list[Tool | OpenAIBuiltInTool] = []
90
+ self._tool_choices = event.payload.tool_choices
91
+ self._disabled_tools = event.payload.disabled_tools
92
+ self._exclusive_tools = [
93
+ tool.name for tool in self._config.tools if tool.is_exclusive
94
+ ]
95
+ # this needs to be a set of strings to avoid duplicates
96
+ self._tool_evaluation_check_list: set[EvaluationMetricName] = set()
97
+ self._mcp_manager = mcp_manager
98
+ self._a2a_manager = a2a_manager
99
+ self._builtin_tool_manager = builtin_tool_manager
100
+ self._api_mode = api_mode
101
+ self._init__tools(event)
102
+
103
+ def _init__tools(self, event: ChatEvent) -> None:
104
+ tool_choices = self._tool_choices
105
+ tool_configs = self._config.tools
106
+ self._logger.info("Initializing tool definitions...")
107
+ self._logger.info(f"Tool choices: {tool_choices}")
108
+
109
+ tool_configs, sub_agents = self._a2a_manager.get_all_sub_agents(
110
+ tool_configs, event
111
+ )
112
+ self._sub_agents = sub_agents
113
+
114
+ registered_tool_names = set(t.name for t in self._sub_agents)
115
+
116
+ self._builtin_tools = []
117
+ if self._builtin_tool_manager is not None and self._api_mode == "responses":
118
+ self._builtin_tools = (
119
+ self._builtin_tool_manager.get_all_openai_builtin_tools()
120
+ )
121
+
122
+ registered_tool_names.update(t.name for t in self._builtin_tools)
123
+
124
+ # Get MCP tools (these are already properly instantiated)
125
+ self._mcp_tools = self._mcp_manager.get_all_mcp_tools()
126
+
127
+ registered_tool_names.update(t.name for t in self._mcp_tools)
128
+
129
+ # Build internal tools from configurations
130
+ self._internal_tools = [
131
+ ToolFactory.build_tool_with_settings(
132
+ t.name,
133
+ t,
134
+ t.configuration,
135
+ event,
136
+ tool_progress_reporter=self._tool_progress_reporter,
137
+ )
138
+ for t in tool_configs
139
+ if t.name not in registered_tool_names # Skip already handled tools
140
+ and t.name not in OpenAIBuiltInToolName # Safeguard
141
+ ]
142
+
143
+ # Combine all types of tools
144
+ self.available_tools = (
145
+ self._internal_tools
146
+ + self._mcp_tools
147
+ + self._sub_agents
148
+ + self._builtin_tools
149
+ )
150
+
151
+ for t in self.available_tools:
152
+ if not t.is_enabled():
153
+ continue
154
+ if t.name in self._disabled_tools:
155
+ continue
156
+ # if tool choices are given, only include those tools
157
+ if len(self._tool_choices) > 0 and t.name not in self._tool_choices:
158
+ continue
159
+ # is the tool exclusive and has been choosen by the user?
160
+ if t.is_exclusive() and len(tool_choices) > 0 and t.name in tool_choices:
161
+ self._tools = [t] # override all other tools
162
+ break
163
+ # if the tool is exclusive but no tool choices are given, skip it
164
+ if t.is_exclusive():
165
+ continue
166
+
167
+ self._tools.append(t)
168
+
169
+ def filter_tool_calls(
170
+ self,
171
+ tool_calls: list[LanguageModelFunction],
172
+ tool_types: list[Literal["mcp", "internal", "subagent", "openai_builtin"]],
173
+ ) -> list[LanguageModelFunction]:
174
+ filtered_calls = []
175
+
176
+ # Build sets for efficient lookup
177
+ internal_tool_names = {tool.name for tool in self._internal_tools}
178
+ mcp_tool_names = {tool.name for tool in self._mcp_tools}
179
+ sub_agent_names = {tool.name for tool in self._sub_agents}
180
+ builtin_tool_names = {tool.name for tool in self._builtin_tools}
181
+
182
+ for tool_call in tool_calls:
183
+ if "internal" in tool_types and tool_call.name in internal_tool_names:
184
+ filtered_calls.append(tool_call)
185
+ elif "mcp" in tool_types and tool_call.name in mcp_tool_names:
186
+ filtered_calls.append(tool_call)
187
+ elif "subagent" in tool_types and tool_call.name in sub_agent_names:
188
+ filtered_calls.append(tool_call)
189
+ elif (
190
+ "openai_builtin" in tool_types and tool_call.name in builtin_tool_names
191
+ ):
192
+ filtered_calls.append(tool_call)
193
+
194
+ return filtered_calls
195
+
196
+ @property
197
+ def sub_agents(self) -> list[SubAgentTool]:
198
+ return self._sub_agents
199
+
200
+ def log_loaded_tools(self):
201
+ self._logger.info(f"Loaded tools: {[tool.name for tool in self._tools]}")
202
+
203
+ def get_tool_choices(self) -> list[str]:
204
+ return self._tool_choices.copy()
205
+
206
+ def get_exclusive_tools(self) -> list[str]:
207
+ return self._exclusive_tools.copy()
208
+
209
+ def get_tool_prompts(self) -> list[ToolPrompts]:
210
+ return [tool.get_tool_prompts() for tool in self._tools]
211
+
212
+ def add_forced_tool(self, name):
213
+ tool = self.get_tool_by_name(name)
214
+ if not tool:
215
+ raise ValueError(f"Tool {name} not found")
216
+
217
+ if tool.name not in self._tool_choices:
218
+ self._tool_choices.append(tool.name)
219
+
220
+ def does_a_tool_take_control(self, tool_calls: list[LanguageModelFunction]) -> bool:
221
+ for tool_call in tool_calls:
222
+ tool_instance = self.get_tool_by_name(tool_call.name)
223
+ if tool_instance and tool_instance.takes_control():
224
+ return True
225
+ return False
226
+
227
+ def get_evaluation_check_list(self) -> list[EvaluationMetricName]:
228
+ return list(self._tool_evaluation_check_list)
229
+
230
+ async def execute_selected_tools(
231
+ self,
232
+ tool_calls: list[LanguageModelFunction],
233
+ ) -> list[ToolCallResponse]:
234
+ tool_call_responses = await self._execute_parallelized(tool_calls)
235
+ return tool_call_responses
236
+
237
+ async def _execute_parallelized(
238
+ self,
239
+ tool_calls: list[LanguageModelFunction],
240
+ ) -> list[ToolCallResponse]:
241
+ self._logger.info("Execute tool calls")
242
+
243
+ task_executor = SafeTaskExecutor(
244
+ logger=self._logger,
245
+ )
246
+
247
+ # Create tasks for each tool call
248
+ tasks = [
249
+ task_executor.execute_async(
250
+ self.execute_tool_call,
251
+ tool_call=tool_call,
252
+ )
253
+ for tool_call in tool_calls
254
+ ]
255
+
256
+ # Wait until all tasks are finished
257
+ tool_call_results = await asyncio.gather(*tasks)
258
+ tool_call_results_unpacked: list[ToolCallResponse] = []
259
+ for i, result in enumerate(tool_call_results):
260
+ unpacked_tool_call_result = self._create_tool_call_response(
261
+ result, tool_calls[i]
262
+ )
263
+ if unpacked_tool_call_result.debug_info is None:
264
+ unpacked_tool_call_result.debug_info = {}
265
+ unpacked_tool_call_result.debug_info["is_exclusive"] = (
266
+ tool_calls[i].name in self.get_exclusive_tools()
267
+ )
268
+ unpacked_tool_call_result.debug_info["is_forced"] = (
269
+ tool_calls[i].name in self.get_tool_choices()
270
+ )
271
+ tool_call_results_unpacked.append(unpacked_tool_call_result)
272
+
273
+ return tool_call_results_unpacked
274
+
275
+ async def execute_tool_call(
276
+ self, tool_call: LanguageModelFunction
277
+ ) -> ToolCallResponse:
278
+ self._logger.info(f"Processing tool call: {tool_call.name}")
279
+
280
+ tool_instance = self.get_tool_by_name(
281
+ tool_call.name
282
+ ) # we need to copy this as it will have problematic interference on multi calls.
283
+
284
+ if not isinstance(tool_instance, Tool):
285
+ raise ValueError(f"Tool {tool_call.name} cannot be run")
286
+
287
+ if tool_instance:
288
+ # Execute the tool
289
+ tool_response: ToolCallResponse = await tool_instance.run(
290
+ tool_call=tool_call
291
+ )
292
+ evaluation_checks = tool_instance.evaluation_check_list()
293
+ self._tool_evaluation_check_list.update(evaluation_checks)
294
+
295
+ return tool_response
296
+
297
+ return ToolCallResponse(
298
+ id=tool_call.id, # type: ignore
299
+ name=tool_call.name,
300
+ error_message=f"Tool of name {tool_call.name} not found",
301
+ )
302
+
303
+ def _create_tool_call_response(
304
+ self, result: Result[ToolCallResponse], tool_call: LanguageModelFunction
305
+ ) -> ToolCallResponse:
306
+ if not result.success:
307
+ return ToolCallResponse(
308
+ id=tool_call.id or "unknown_id",
309
+ name=tool_call.name,
310
+ error_message=str(result.exception),
311
+ )
312
+ unpacked = result.unpack()
313
+ if not isinstance(unpacked, ToolCallResponse):
314
+ return ToolCallResponse(
315
+ id=tool_call.id or "unknown_id",
316
+ name=tool_call.name,
317
+ error_message="Tool call response is not of type ToolCallResponse",
318
+ )
319
+ return unpacked
320
+
321
+ def filter_duplicate_tool_calls(
322
+ self,
323
+ tool_calls: list[LanguageModelFunction],
324
+ ) -> list[LanguageModelFunction]:
325
+ """
326
+ Filter out duplicate tool calls based on name and arguments.
327
+ """
328
+
329
+ unique_tool_calls = []
330
+
331
+ for call in tool_calls:
332
+ if all(not call == other_call for other_call in unique_tool_calls):
333
+ unique_tool_calls.append(call)
334
+
335
+ if len(tool_calls) != len(unique_tool_calls):
336
+ self._logger = getLogger(__name__)
337
+ self._logger.warning(
338
+ f"Filtered out {len(tool_calls) - len(unique_tool_calls)} duplicate tool calls."
339
+ )
340
+ return unique_tool_calls
341
+
342
+ def filter_tool_calls_by_max_tool_calls_allowed(
343
+ self, tool_calls: list[LanguageModelFunction]
344
+ ) -> list[LanguageModelFunction]:
345
+ if len(tool_calls) > self._config.max_tool_calls:
346
+ self._logger.warning(
347
+ (
348
+ "Number of tool calls %s exceeds the allowed maximum of %s."
349
+ "The tool calls will be reduced to the first %s."
350
+ ),
351
+ len(tool_calls),
352
+ self._config.max_tool_calls,
353
+ self._config.max_tool_calls,
354
+ )
355
+ return tool_calls[: self._config.max_tool_calls]
356
+ return tool_calls
357
+
358
+ @overload
359
+ def get_tool_by_name(
360
+ self: "_ToolManager[Literal['completions']]", name: str
361
+ ) -> Tool | None: ...
362
+
363
+ @overload
364
+ def get_tool_by_name(
365
+ self: "_ToolManager[Literal['responses']]", name: str
366
+ ) -> Tool | OpenAIBuiltInTool | None: ...
367
+
368
+ @overload # Unknown API mode typing
369
+ def get_tool_by_name(
370
+ self: "_ToolManager", name: str
371
+ ) -> Tool | OpenAIBuiltInTool | None: ...
372
+
373
+ def get_tool_by_name(self, name: str) -> Tool | OpenAIBuiltInTool | None:
374
+ for tool in self._tools:
375
+ if tool.name == name:
376
+ return tool
377
+ return None
378
+
379
+ @overload
380
+ def get_tools(self: "_ToolManager[Literal['completions']]") -> list[Tool]: ...
381
+
382
+ @overload
383
+ def get_tools(
384
+ self: "_ToolManager[Literal['responses']]",
385
+ ) -> list[Tool | OpenAIBuiltInTool]: ...
386
+
387
+ def get_tools(self) -> list[Tool] | list[Tool | OpenAIBuiltInTool]:
388
+ return self._tools.copy()
389
+
390
+ @overload
391
+ def get_forced_tools(
392
+ self: "_ToolManager[Literal['completions']]",
393
+ ) -> list[ChatCompletionNamedToolChoiceParam]: ...
394
+
395
+ @overload
396
+ def get_forced_tools(
397
+ self: "_ToolManager[Literal['responses']]",
398
+ ) -> list[response_create_params.ToolChoice]: ...
399
+
400
+ def get_forced_tools(
401
+ self,
402
+ ) -> (
403
+ list[ChatCompletionNamedToolChoiceParam]
404
+ | list[response_create_params.ToolChoice]
405
+ ):
406
+ return [
407
+ _convert_to_forced_tool(t.name, mode=self._api_mode)
408
+ for t in self._tools
409
+ if t.name in self._tool_choices
410
+ ] # type: ignore
411
+
412
+ @overload
413
+ def get_tool_definitions(
414
+ self: "_ToolManager[Literal['completions']]",
415
+ ) -> list[LanguageModelToolDescription]: ...
416
+
417
+ @overload
418
+ def get_tool_definitions(
419
+ self: "_ToolManager[Literal['responses']]",
420
+ ) -> list[LanguageModelToolDescription | ToolParam]: ...
421
+
422
+ def get_tool_definitions(
423
+ self,
424
+ ) -> (
425
+ list[LanguageModelToolDescription]
426
+ | list[LanguageModelToolDescription | ToolParam]
427
+ ):
428
+ return [tool.tool_description() for tool in self._tools]
429
+
430
+
431
+ def _convert_to_forced_tool(
432
+ tool_name: str, mode: Literal["completions", "responses"]
433
+ ) -> ChatCompletionNamedToolChoiceParam | response_create_params.ToolChoice:
434
+ if mode == "completions":
435
+ return {
436
+ "type": "function",
437
+ "function": {"name": tool_name},
438
+ }
439
+ else:
440
+ if tool_name in OpenAIBuiltInToolName:
441
+ # Built-in have a special syntax for forcing
442
+ return {"type": tool_name} # type: ignore
443
+ else:
444
+ return {
445
+ "name": tool_name,
446
+ "type": "function",
447
+ }
448
+
449
+
450
+ class ToolManager(_ToolManager[Literal["completions"]]):
451
+ def __init__(
452
+ self,
453
+ logger: Logger,
454
+ config: ToolManagerConfig,
455
+ event: ChatEvent,
456
+ tool_progress_reporter: ToolProgressReporter,
457
+ mcp_manager: MCPManager,
458
+ a2a_manager: A2AManager,
459
+ ) -> None:
460
+ super().__init__(
461
+ logger=logger,
462
+ config=config,
463
+ event=event,
464
+ tool_progress_reporter=tool_progress_reporter,
465
+ mcp_manager=mcp_manager,
466
+ a2a_manager=a2a_manager,
467
+ api_mode="completions",
468
+ builtin_tool_manager=None,
469
+ )
470
+
471
+
472
+ class ResponsesApiToolManager(_ToolManager[Literal["responses"]]):
473
+ def __init__(
474
+ self,
475
+ logger: Logger,
476
+ config: ToolManagerConfig,
477
+ event: ChatEvent,
478
+ tool_progress_reporter: ToolProgressReporter,
479
+ mcp_manager: MCPManager,
480
+ a2a_manager: A2AManager,
481
+ builtin_tool_manager: OpenAIBuiltInToolManager,
482
+ ) -> None:
483
+ super().__init__(
484
+ logger=logger,
485
+ config=config,
486
+ event=event,
487
+ tool_progress_reporter=tool_progress_reporter,
488
+ mcp_manager=mcp_manager,
489
+ a2a_manager=a2a_manager,
490
+ api_mode="responses",
491
+ builtin_tool_manager=builtin_tool_manager,
492
+ )