quantalogic 0.80__py3-none-any.whl → 0.93__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.
- quantalogic/flow/__init__.py +16 -34
- quantalogic/main.py +11 -6
- quantalogic/tools/tool.py +8 -922
- quantalogic-0.93.dist-info/METADATA +475 -0
- {quantalogic-0.80.dist-info → quantalogic-0.93.dist-info}/RECORD +8 -54
- quantalogic/codeact/TODO.md +0 -14
- quantalogic/codeact/__init__.py +0 -0
- quantalogic/codeact/agent.py +0 -478
- quantalogic/codeact/cli.py +0 -50
- quantalogic/codeact/cli_commands/__init__.py +0 -0
- quantalogic/codeact/cli_commands/create_toolbox.py +0 -45
- quantalogic/codeact/cli_commands/install_toolbox.py +0 -20
- quantalogic/codeact/cli_commands/list_executor.py +0 -15
- quantalogic/codeact/cli_commands/list_reasoners.py +0 -15
- quantalogic/codeact/cli_commands/list_toolboxes.py +0 -47
- quantalogic/codeact/cli_commands/task.py +0 -215
- quantalogic/codeact/cli_commands/tool_info.py +0 -24
- quantalogic/codeact/cli_commands/uninstall_toolbox.py +0 -43
- quantalogic/codeact/config.yaml +0 -21
- quantalogic/codeact/constants.py +0 -9
- quantalogic/codeact/events.py +0 -85
- quantalogic/codeact/examples/README.md +0 -342
- quantalogic/codeact/examples/agent_sample.yaml +0 -29
- quantalogic/codeact/executor.py +0 -186
- quantalogic/codeact/history_manager.py +0 -94
- quantalogic/codeact/llm_util.py +0 -57
- quantalogic/codeact/plugin_manager.py +0 -92
- quantalogic/codeact/prompts/error_format.j2 +0 -11
- quantalogic/codeact/prompts/generate_action.j2 +0 -77
- quantalogic/codeact/prompts/generate_program.j2 +0 -52
- quantalogic/codeact/prompts/response_format.j2 +0 -11
- quantalogic/codeact/react_agent.py +0 -318
- quantalogic/codeact/reasoner.py +0 -185
- quantalogic/codeact/templates/toolbox/README.md.j2 +0 -10
- quantalogic/codeact/templates/toolbox/pyproject.toml.j2 +0 -16
- quantalogic/codeact/templates/toolbox/tools.py.j2 +0 -6
- quantalogic/codeact/templates.py +0 -7
- quantalogic/codeact/tools_manager.py +0 -258
- quantalogic/codeact/utils.py +0 -62
- quantalogic/codeact/xml_utils.py +0 -126
- quantalogic/flow/flow.py +0 -1070
- quantalogic/flow/flow_extractor.py +0 -783
- quantalogic/flow/flow_generator.py +0 -322
- quantalogic/flow/flow_manager.py +0 -676
- quantalogic/flow/flow_manager_schema.py +0 -287
- quantalogic/flow/flow_mermaid.py +0 -365
- quantalogic/flow/flow_validator.py +0 -479
- quantalogic/flow/flow_yaml.linkedin.md +0 -31
- quantalogic/flow/flow_yaml.md +0 -767
- quantalogic/flow/templates/prompt_check_inventory.j2 +0 -1
- quantalogic/flow/templates/system_check_inventory.j2 +0 -1
- quantalogic-0.80.dist-info/METADATA +0 -900
- {quantalogic-0.80.dist-info → quantalogic-0.93.dist-info}/LICENSE +0 -0
- {quantalogic-0.80.dist-info → quantalogic-0.93.dist-info}/WHEEL +0 -0
- {quantalogic-0.80.dist-info → quantalogic-0.93.dist-info}/entry_points.txt +0 -0
quantalogic/tools/tool.py
CHANGED
@@ -1,922 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
""
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
import inspect
|
10
|
-
from typing import Any, Callable, TypeVar, Union, get_args, get_origin
|
11
|
-
|
12
|
-
from docstring_parser import parse as parse_docstring
|
13
|
-
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
14
|
-
|
15
|
-
# Type variable for create_tool to preserve function signature
|
16
|
-
F = TypeVar('F', bound=Callable[..., Any])
|
17
|
-
|
18
|
-
|
19
|
-
def type_hint_to_str(type_hint):
|
20
|
-
"""Convert a type hint to a string representation.
|
21
|
-
|
22
|
-
Args:
|
23
|
-
type_hint: The type hint to convert.
|
24
|
-
|
25
|
-
Returns:
|
26
|
-
A string representation of the type hint, e.g., 'list[int]', 'dict[str, float]'.
|
27
|
-
"""
|
28
|
-
origin = get_origin(type_hint)
|
29
|
-
if origin is not None:
|
30
|
-
origin_name = origin.__name__
|
31
|
-
args = get_args(type_hint)
|
32
|
-
args_str = ", ".join(type_hint_to_str(arg) for arg in args)
|
33
|
-
return f"{origin_name}[{args_str}]"
|
34
|
-
elif hasattr(type_hint, "__name__"):
|
35
|
-
return type_hint.__name__
|
36
|
-
else:
|
37
|
-
return str(type_hint)
|
38
|
-
|
39
|
-
|
40
|
-
def get_type_description(type_hint):
|
41
|
-
"""Generate a detailed, natural-language description of a type hint.
|
42
|
-
|
43
|
-
Args:
|
44
|
-
type_hint: The type hint to describe.
|
45
|
-
|
46
|
-
Returns:
|
47
|
-
A string with a detailed description of the type, e.g., 'a list of int', or a structured class description.
|
48
|
-
"""
|
49
|
-
basic_types = {
|
50
|
-
int: "integer",
|
51
|
-
str: "string",
|
52
|
-
float: "float",
|
53
|
-
bool: "boolean",
|
54
|
-
type(None): "None",
|
55
|
-
}
|
56
|
-
|
57
|
-
if type_hint in basic_types:
|
58
|
-
return basic_types[type_hint]
|
59
|
-
|
60
|
-
try:
|
61
|
-
origin = get_origin(type_hint)
|
62
|
-
if origin is None:
|
63
|
-
if inspect.isclass(type_hint):
|
64
|
-
doc = inspect.getdoc(type_hint)
|
65
|
-
desc_prefix = f"{doc} " if doc else ""
|
66
|
-
if hasattr(type_hint, "__annotations__"):
|
67
|
-
annotations = type_hint.__annotations__
|
68
|
-
attrs = ", ".join(f"{name}: {get_type_description(typ)}" for name, typ in annotations.items())
|
69
|
-
return f"{desc_prefix}an instance of {type_hint.__name__} with attributes: {attrs}"
|
70
|
-
return f"{desc_prefix}{type_hint.__name__}"
|
71
|
-
return str(type_hint)
|
72
|
-
|
73
|
-
args = get_args(type_hint)
|
74
|
-
|
75
|
-
if origin is list:
|
76
|
-
if args and len(args) >= 1:
|
77
|
-
return f"a list of {get_type_description(args[0])}"
|
78
|
-
return "a list"
|
79
|
-
|
80
|
-
elif origin is dict:
|
81
|
-
if args and len(args) == 2:
|
82
|
-
return f"a dictionary with {get_type_description(args[0])} keys and {get_type_description(args[1])} values"
|
83
|
-
return "a dictionary with any keys and values"
|
84
|
-
|
85
|
-
elif origin is tuple:
|
86
|
-
if args:
|
87
|
-
types_desc = ", ".join(get_type_description(t) for t in args)
|
88
|
-
return f"a tuple containing {types_desc}"
|
89
|
-
return "a tuple"
|
90
|
-
|
91
|
-
elif origin is Union:
|
92
|
-
if args:
|
93
|
-
if len(args) == 2 and type(None) in args:
|
94
|
-
non_none_type = next(t for t in args if t is not type(None))
|
95
|
-
return f"an optional {get_type_description(non_none_type)} (can be None)"
|
96
|
-
types_desc = ", ".join(get_type_description(t) for t in args)
|
97
|
-
return f"one of {types_desc}"
|
98
|
-
return "any type"
|
99
|
-
|
100
|
-
return type_hint_to_str(type_hint)
|
101
|
-
except Exception:
|
102
|
-
return str(type_hint)
|
103
|
-
|
104
|
-
|
105
|
-
def get_type_schema(type_hint):
|
106
|
-
"""Generate a schema-like string representation of a type hint.
|
107
|
-
|
108
|
-
Args:
|
109
|
-
type_hint: The type hint to convert.
|
110
|
-
|
111
|
-
Returns:
|
112
|
-
A string representing the type's structure, e.g., '[integer, ...]' or "{'x': 'integer', 'y': 'integer'}".
|
113
|
-
"""
|
114
|
-
basic_types = {
|
115
|
-
int: "integer",
|
116
|
-
str: "string",
|
117
|
-
float: "float",
|
118
|
-
bool: "boolean",
|
119
|
-
type(None): "null",
|
120
|
-
}
|
121
|
-
|
122
|
-
if type_hint in basic_types:
|
123
|
-
return basic_types[type_hint]
|
124
|
-
|
125
|
-
origin = get_origin(type_hint)
|
126
|
-
if origin is None:
|
127
|
-
if inspect.isclass(type_hint) and hasattr(type_hint, "__annotations__"):
|
128
|
-
annotations = type_hint.__annotations__
|
129
|
-
fields = {name: get_type_schema(typ) for name, typ in annotations.items()}
|
130
|
-
return "{" + ", ".join(f"'{name}': {schema}" for name, schema in fields.items()) + "}"
|
131
|
-
return type_hint.__name__
|
132
|
-
|
133
|
-
elif origin is list:
|
134
|
-
item_type = get_args(type_hint)[0]
|
135
|
-
return f"[{get_type_schema(item_type)}, ...]"
|
136
|
-
|
137
|
-
elif origin is dict:
|
138
|
-
args = get_args(type_hint)
|
139
|
-
if len(args) == 2:
|
140
|
-
key_type, value_type = args
|
141
|
-
if key_type is str:
|
142
|
-
return f"{{{get_type_schema(value_type)}}}"
|
143
|
-
return f"dictionary with keys of type {get_type_schema(key_type)} and values of type {get_type_schema(value_type)}"
|
144
|
-
return "dictionary"
|
145
|
-
|
146
|
-
elif origin is tuple:
|
147
|
-
tuple_types = get_args(type_hint)
|
148
|
-
return f"[{', '.join(get_type_schema(t) for t in tuple_types)}]"
|
149
|
-
|
150
|
-
elif origin is Union:
|
151
|
-
union_types = get_args(type_hint)
|
152
|
-
if len(union_types) == 2 and type(None) in union_types:
|
153
|
-
non_none_type = next(t for t in union_types if t is not type(None))
|
154
|
-
return f"{get_type_schema(non_none_type)} or null"
|
155
|
-
return " or ".join(get_type_schema(t) for t in union_types)
|
156
|
-
|
157
|
-
else:
|
158
|
-
return type_hint_to_str(type_hint)
|
159
|
-
|
160
|
-
|
161
|
-
class ToolArgument(BaseModel):
|
162
|
-
"""Represents an argument for a tool with validation and description.
|
163
|
-
|
164
|
-
Attributes:
|
165
|
-
name: The name of the argument.
|
166
|
-
arg_type: The type of the argument, e.g., 'string', 'int', 'list[int]', 'dict[str, float]'.
|
167
|
-
description: Optional description of the argument.
|
168
|
-
required: Indicates if the argument is mandatory.
|
169
|
-
default: Optional default value for the argument.
|
170
|
-
example: Optional example value to illustrate the argument's usage.
|
171
|
-
type_details: Detailed description of the argument's type.
|
172
|
-
"""
|
173
|
-
|
174
|
-
name: str = Field(..., description="The name of the argument.")
|
175
|
-
arg_type: str = Field(
|
176
|
-
..., description="The type of the argument, e.g., 'string', 'int', 'list[int]', 'dict[str, float]', etc."
|
177
|
-
)
|
178
|
-
description: str | None = Field(default=None, description="A brief description of the argument.")
|
179
|
-
required: bool = Field(default=False, description="Indicates if the argument is required.")
|
180
|
-
default: str | None = Field(
|
181
|
-
default=None, description="The default value for the argument. This parameter is required."
|
182
|
-
)
|
183
|
-
example: str | None = Field(default=None, description="An example value to illustrate the argument's usage.")
|
184
|
-
type_details: str | None = Field(default=None, description="Detailed description of the argument's type.")
|
185
|
-
|
186
|
-
|
187
|
-
class ToolDefinition(BaseModel):
|
188
|
-
"""Base class for defining tool configurations without execution logic.
|
189
|
-
|
190
|
-
Attributes:
|
191
|
-
name: Unique name of the tool.
|
192
|
-
description: Brief description of the tool's functionality.
|
193
|
-
arguments: List of arguments the tool accepts.
|
194
|
-
return_type: The return type of the tool's execution method. Defaults to "str".
|
195
|
-
return_description: Optional description of the return value.
|
196
|
-
return_type_details: Detailed description of the return type.
|
197
|
-
original_docstring: The full original docstring of the function, if applicable.
|
198
|
-
need_validation: Flag to indicate if tool requires validation.
|
199
|
-
is_async: Flag to indicate if the tool is asynchronous (for documentation purposes).
|
200
|
-
toolbox_name: Optional name of the toolbox this tool belongs to.
|
201
|
-
"""
|
202
|
-
|
203
|
-
model_config = ConfigDict(extra="allow", validate_assignment=True)
|
204
|
-
|
205
|
-
name: str = Field(..., description="The unique name of the tool.")
|
206
|
-
description: str = Field(..., description="A brief description of what the tool does.")
|
207
|
-
arguments: list[ToolArgument] = Field(default_factory=list, description="A list of arguments the tool accepts.")
|
208
|
-
return_type: str = Field(default="str", description="The return type of the tool's execution method.")
|
209
|
-
return_description: str | None = Field(default=None, description="Description of the return value.")
|
210
|
-
return_type_details: str | None = Field(default=None, description="Detailed description of the return type.")
|
211
|
-
return_example: str | None = Field(default=None, description="Example of the return value.")
|
212
|
-
return_structure: str | None = Field(default=None, description="Structure of the return value.")
|
213
|
-
original_docstring: str | None = Field(default=None, description="The full original docstring of the function, if applicable.")
|
214
|
-
need_validation: bool = Field(
|
215
|
-
default=False,
|
216
|
-
description="When True, requires user confirmation before execution. Useful for tools that perform potentially destructive operations.",
|
217
|
-
)
|
218
|
-
need_post_process: bool = Field(
|
219
|
-
default=True,
|
220
|
-
description="When True, requires user confirmation before execution. Useful for tools that perform potentially destructive operations.",
|
221
|
-
)
|
222
|
-
need_variables: bool = Field(
|
223
|
-
default=False,
|
224
|
-
description="When True, provides access to the agent's variable store. Required for tools that need to interpolate variables (e.g., Jinja templates).",
|
225
|
-
)
|
226
|
-
need_caller_context_memory: bool = Field(
|
227
|
-
default=False,
|
228
|
-
description="When True, provides access to the agent's conversation history. Useful for tools that need context from previous interactions.",
|
229
|
-
)
|
230
|
-
is_async: bool = Field(
|
231
|
-
default=False,
|
232
|
-
description="Indicates if the tool is asynchronous (used for documentation).",
|
233
|
-
)
|
234
|
-
toolbox_name: str | None = Field(
|
235
|
-
default=None,
|
236
|
-
description="The name of the toolbox this tool belongs to, set during registration if applicable."
|
237
|
-
)
|
238
|
-
|
239
|
-
def get_properties(self, exclude: list[str] | None = None) -> dict[str, Any]:
|
240
|
-
"""Return a dictionary of all non-None properties, excluding Tool class fields and specified fields.
|
241
|
-
|
242
|
-
Args:
|
243
|
-
exclude: Optional list of field names to exclude from the result
|
244
|
-
|
245
|
-
Returns:
|
246
|
-
Dictionary of property names and values, excluding Tool class fields and specified fields.
|
247
|
-
"""
|
248
|
-
exclude = exclude or []
|
249
|
-
tool_fields = {
|
250
|
-
"name",
|
251
|
-
"description",
|
252
|
-
"arguments",
|
253
|
-
"return_type",
|
254
|
-
"return_description",
|
255
|
-
"return_type_details",
|
256
|
-
"return_example",
|
257
|
-
"return_structure",
|
258
|
-
"original_docstring",
|
259
|
-
"need_validation",
|
260
|
-
"need_post_process",
|
261
|
-
"need_variables",
|
262
|
-
"need_caller_context_memory",
|
263
|
-
"is_async",
|
264
|
-
"toolbox_name",
|
265
|
-
}
|
266
|
-
properties = {}
|
267
|
-
|
268
|
-
for name, value in self.__dict__.items():
|
269
|
-
if name not in tool_fields and name not in exclude and value is not None and not name.startswith("_"):
|
270
|
-
properties[name] = value
|
271
|
-
|
272
|
-
return properties
|
273
|
-
|
274
|
-
def to_json(self) -> str:
|
275
|
-
"""Convert the tool to a JSON string representation.
|
276
|
-
|
277
|
-
Returns:
|
278
|
-
A JSON string of the tool's configuration.
|
279
|
-
"""
|
280
|
-
return self.model_dump_json()
|
281
|
-
|
282
|
-
def to_markdown(self) -> str:
|
283
|
-
"""Create a comprehensive Markdown representation of the tool.
|
284
|
-
|
285
|
-
Returns:
|
286
|
-
A detailed Markdown string representing the tool's configuration and usage.
|
287
|
-
"""
|
288
|
-
markdown = f"`{self.name}`:\n"
|
289
|
-
markdown += f"- **Description**: {self.description}\n\n"
|
290
|
-
|
291
|
-
properties_injectable = self.get_injectable_properties_in_execution()
|
292
|
-
if any(properties_injectable.get(arg.name) is not None for arg in self.arguments):
|
293
|
-
markdown += "- **Note**: Some arguments are injected from the tool's configuration and may not need to be provided explicitly.\n\n"
|
294
|
-
|
295
|
-
if self.arguments:
|
296
|
-
markdown += "- **Parameters**:\n"
|
297
|
-
parameters = ""
|
298
|
-
for arg in self.arguments:
|
299
|
-
if properties_injectable.get(arg.name) is not None:
|
300
|
-
continue
|
301
|
-
type_info = f"{arg.arg_type}"
|
302
|
-
if arg.type_details and arg.type_details != arg.arg_type:
|
303
|
-
type_info += f" ({arg.type_details})"
|
304
|
-
required_status = "required" if arg.required else "optional"
|
305
|
-
value_info = ""
|
306
|
-
if arg.default is not None:
|
307
|
-
value_info += f", default: `{arg.default}`"
|
308
|
-
if arg.example is not None:
|
309
|
-
value_info += f", example: `{arg.example}`"
|
310
|
-
parameters += (
|
311
|
-
f" - `{arg.name}`: ({type_info}, {required_status}{value_info})\n"
|
312
|
-
f" {arg.description or ''}\n"
|
313
|
-
)
|
314
|
-
if parameters:
|
315
|
-
markdown += parameters + "\n"
|
316
|
-
else:
|
317
|
-
markdown += " None\n\n"
|
318
|
-
|
319
|
-
standard_fields = {
|
320
|
-
"name", "description", "arguments", "return_type", "return_description", "return_type_details",
|
321
|
-
"return_example", "return_structure", "original_docstring", "need_validation", "need_post_process", "need_variables", "need_caller_context_memory", "is_async",
|
322
|
-
"toolbox_name"
|
323
|
-
}
|
324
|
-
additional_fields = [f for f in self.model_fields if f not in standard_fields]
|
325
|
-
if additional_fields:
|
326
|
-
markdown += "- **Configuration**:\n"
|
327
|
-
for field in additional_fields:
|
328
|
-
field_info = self.model_fields[field]
|
329
|
-
field_type = type_hint_to_str(field_info.annotation)
|
330
|
-
field_desc = field_info.description or "No description provided."
|
331
|
-
if field in properties_injectable:
|
332
|
-
field_desc += f" Injects into '{field}' argument."
|
333
|
-
markdown += f" - `{field}`: ({field_type}) - {field_desc}\n"
|
334
|
-
markdown += "\n"
|
335
|
-
|
336
|
-
markdown += "- **Usage**: This tool can be invoked using the following XML-like syntax:\n"
|
337
|
-
markdown += "```xml\n"
|
338
|
-
markdown += f"<{self.name}>\n"
|
339
|
-
|
340
|
-
for arg in self.arguments:
|
341
|
-
if properties_injectable.get(arg.name) is not None:
|
342
|
-
continue
|
343
|
-
example_value = arg.example or arg.default or f"Your {arg.name} here"
|
344
|
-
markdown += f" <{arg.name}>{example_value}</{arg.name}>\n"
|
345
|
-
|
346
|
-
markdown += f"</{self.name}>\n"
|
347
|
-
markdown += "```\n\n"
|
348
|
-
|
349
|
-
markdown += f"- **Returns**: `{self.return_type}` - {self.return_description or 'The result of the tool execution.'}\n"
|
350
|
-
if self.return_type_details and self.return_type_details != self.return_type:
|
351
|
-
markdown += f" {self.return_type_details}\n"
|
352
|
-
if self.return_example:
|
353
|
-
markdown += f"- **Example Return Value**: `{self.return_example}`\n"
|
354
|
-
if self.return_structure:
|
355
|
-
markdown += f"- **Return Structure**: `{self.return_structure}`\n"
|
356
|
-
|
357
|
-
return markdown
|
358
|
-
|
359
|
-
def get_non_injectable_arguments(self) -> list[ToolArgument]:
|
360
|
-
"""Get arguments that cannot be injected from properties.
|
361
|
-
|
362
|
-
Returns:
|
363
|
-
List of ToolArgument instances that cannot be injected by the agent.
|
364
|
-
"""
|
365
|
-
properties_injectable = self.get_injectable_properties_in_execution()
|
366
|
-
return [arg for arg in self.arguments if properties_injectable.get(arg.name) is None]
|
367
|
-
|
368
|
-
def get_injectable_properties_in_execution(self) -> dict[str, Any]:
|
369
|
-
"""Get injectable properties excluding tool arguments.
|
370
|
-
|
371
|
-
Returns:
|
372
|
-
A dictionary of property names and values, excluding tool arguments and None values.
|
373
|
-
"""
|
374
|
-
return {}
|
375
|
-
|
376
|
-
def to_docstring(self) -> str:
|
377
|
-
"""Convert the tool definition into a Google-style docstring with function signature.
|
378
|
-
|
379
|
-
If an original_docstring is provided (e.g., from a function via create_tool), it is used directly.
|
380
|
-
Otherwise, constructs a detailed docstring from the tool's metadata.
|
381
|
-
|
382
|
-
Returns:
|
383
|
-
A string formatted as a valid Python docstring representing the tool's configuration,
|
384
|
-
including the function signature, detailed argument types, and return type with descriptions.
|
385
|
-
"""
|
386
|
-
signature_parts = []
|
387
|
-
for arg in self.arguments:
|
388
|
-
arg_str = f"{arg.name}: {arg.arg_type}"
|
389
|
-
if arg.default is not None:
|
390
|
-
arg_str += f" = {arg.default}"
|
391
|
-
signature_parts.append(arg_str)
|
392
|
-
signature = f"{'async ' if self.is_async else ''}def {self.name}({', '.join(signature_parts)}) -> {self.return_type}:"
|
393
|
-
|
394
|
-
if self.original_docstring:
|
395
|
-
# Use the full original docstring if available, ensuring proper indentation
|
396
|
-
docstring = f'"""\n{signature}\n\n{self.original_docstring.rstrip()}\n"""'
|
397
|
-
else:
|
398
|
-
# Fall back to constructing the docstring from metadata
|
399
|
-
docstring = f'"""\n{signature}\n\n{self.description}\n'
|
400
|
-
properties_injectable = self.get_injectable_properties_in_execution()
|
401
|
-
if properties_injectable:
|
402
|
-
docstring += "\n Note: Some arguments may be injected from the tool's configuration.\n"
|
403
|
-
|
404
|
-
if self.arguments:
|
405
|
-
docstring += "\nArgs:\n"
|
406
|
-
for arg in self.arguments:
|
407
|
-
arg_line = f" {arg.name} ({arg.arg_type})"
|
408
|
-
details = []
|
409
|
-
if not arg.required:
|
410
|
-
details.append("optional")
|
411
|
-
if arg.default is not None:
|
412
|
-
details.append(f"defaults to {arg.default}")
|
413
|
-
if arg.example is not None:
|
414
|
-
details.append(f"e.g., {arg.example}")
|
415
|
-
if details:
|
416
|
-
arg_line += f" [{', '.join(details)}]"
|
417
|
-
if arg.description:
|
418
|
-
arg_line += f": {arg.description}"
|
419
|
-
if arg.type_details and arg.type_details != arg.arg_type and arg.type_details != arg.description:
|
420
|
-
arg_line += f"\n {arg.type_details}"
|
421
|
-
docstring += f"{arg_line}\n"
|
422
|
-
|
423
|
-
if self.return_description:
|
424
|
-
docstring += f"\nReturns:\n {self.return_type}: {self.return_description.split(':')[0]}:\n"
|
425
|
-
if ':' in self.return_description:
|
426
|
-
fields = self.return_description.split(':', 1)[1].strip()
|
427
|
-
for line in fields.split('\n'):
|
428
|
-
if line.strip():
|
429
|
-
docstring += f" {line.strip()}\n"
|
430
|
-
else:
|
431
|
-
return_desc = self.return_type_details or "The result of the tool execution."
|
432
|
-
docstring += f"\nReturns:\n {self.return_type}: {return_desc}"
|
433
|
-
if self.return_structure:
|
434
|
-
docstring += f"\n Structure: {self.return_structure}"
|
435
|
-
|
436
|
-
if self.return_example:
|
437
|
-
docstring += f"\n Example: {self.return_example}"
|
438
|
-
|
439
|
-
docstring += "\n\nExamples:\n"
|
440
|
-
args_str = ", ".join([f"{arg.name}=\"{arg.example or '...'}\"" for arg in self.arguments if arg.required])
|
441
|
-
prefix = " result = " if not self.is_async else " result = await "
|
442
|
-
docstring += f"{prefix}{self.name}({args_str})\n"
|
443
|
-
|
444
|
-
standard_fields = {
|
445
|
-
"name", "description", "arguments", "return_type", "return_description", "return_type_details",
|
446
|
-
"return_example", "return_structure", "original_docstring", "need_validation", "need_post_process", "need_variables", "need_caller_context_memory", "is_async",
|
447
|
-
"toolbox_name"
|
448
|
-
}
|
449
|
-
additional_fields = [f for f in self.model_fields if f not in standard_fields]
|
450
|
-
if additional_fields:
|
451
|
-
docstring += "\nConfiguration:\n"
|
452
|
-
for field in additional_fields:
|
453
|
-
field_info = self.model_fields[field]
|
454
|
-
field_type = type_hint_to_str(field_info.annotation)
|
455
|
-
field_desc = field_info.description or "No description provided."
|
456
|
-
docstring += f" {field} ({field_type}): {field_desc}\n"
|
457
|
-
|
458
|
-
docstring += '\n"""'
|
459
|
-
return docstring
|
460
|
-
|
461
|
-
|
462
|
-
class Tool(ToolDefinition):
|
463
|
-
"""Extended class for tools with execution capabilities.
|
464
|
-
|
465
|
-
Inherits from ToolDefinition and adds execution functionality.
|
466
|
-
"""
|
467
|
-
|
468
|
-
@field_validator("arguments", mode="before")
|
469
|
-
@classmethod
|
470
|
-
def validate_arguments(cls, v: Any) -> list[ToolArgument]:
|
471
|
-
"""Validate and convert arguments to ToolArgument instances.
|
472
|
-
|
473
|
-
Args:
|
474
|
-
v: Input arguments to validate.
|
475
|
-
|
476
|
-
Returns:
|
477
|
-
A list of validated ToolArgument instances.
|
478
|
-
"""
|
479
|
-
if isinstance(v, list):
|
480
|
-
return [
|
481
|
-
ToolArgument(**arg)
|
482
|
-
if isinstance(arg, dict)
|
483
|
-
else arg
|
484
|
-
if isinstance(arg, ToolArgument)
|
485
|
-
else ToolArgument(name=str(arg), arg_type=type(arg).__name__)
|
486
|
-
for arg in v
|
487
|
-
]
|
488
|
-
return []
|
489
|
-
|
490
|
-
def execute(self, **kwargs: Any) -> Any:
|
491
|
-
"""Execute the tool with provided arguments.
|
492
|
-
|
493
|
-
If not implemented by a subclass, falls back to the asynchronous execute_async method.
|
494
|
-
|
495
|
-
Args:
|
496
|
-
**kwargs: Keyword arguments for tool execution.
|
497
|
-
|
498
|
-
Returns:
|
499
|
-
The result of tool execution, preserving the original type returned by the tool's logic.
|
500
|
-
"""
|
501
|
-
if self.__class__.execute is Tool.execute:
|
502
|
-
return asyncio.run(self.async_execute(**kwargs))
|
503
|
-
raise NotImplementedError("This method should be implemented by subclasses.")
|
504
|
-
|
505
|
-
async def async_execute(self, **kwargs: Any) -> Any:
|
506
|
-
"""Asynchronous version of execute.
|
507
|
-
|
508
|
-
By default, runs the synchronous execute method in a separate thread using asyncio.to_thread.
|
509
|
-
Subclasses can override this method to provide a native asynchronous implementation for
|
510
|
-
operations that benefit from async I/O (e.g., network requests).
|
511
|
-
|
512
|
-
Args:
|
513
|
-
**kwargs: Keyword arguments for tool execution.
|
514
|
-
|
515
|
-
Returns:
|
516
|
-
The result of tool execution, preserving the original type returned by the tool's logic.
|
517
|
-
"""
|
518
|
-
if self.__class__.async_execute is Tool.async_execute:
|
519
|
-
return await asyncio.to_thread(self.execute, **kwargs)
|
520
|
-
raise NotImplementedError("This method should be implemented by subclasses.")
|
521
|
-
|
522
|
-
async def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
523
|
-
"""Make the tool callable, handling both synchronous and asynchronous executions.
|
524
|
-
|
525
|
-
The tool can be called with positional and/or keyword arguments, matching the arguments defined in the tool's metadata.
|
526
|
-
|
527
|
-
Args:
|
528
|
-
*args: Positional arguments to pass to the tool's execution method, mapped to argument names in the order defined by self.arguments.
|
529
|
-
**kwargs: Keyword arguments to pass to the tool's execution method.
|
530
|
-
|
531
|
-
Returns:
|
532
|
-
The result of the tool execution, preserving the original return type.
|
533
|
-
|
534
|
-
Raises:
|
535
|
-
TypeError: If too many positional arguments are provided or if an argument is specified both positionally and by keyword.
|
536
|
-
|
537
|
-
Usage:
|
538
|
-
For synchronous contexts: result = asyncio.run(tool(*args, **kwargs))
|
539
|
-
For asynchronous contexts: result = await tool(*args, **kwargs)
|
540
|
-
"""
|
541
|
-
arg_names = [arg.name for arg in self.arguments]
|
542
|
-
|
543
|
-
for i, arg_value in enumerate(args):
|
544
|
-
if i >= len(arg_names):
|
545
|
-
raise TypeError(f"{self.name}() takes {len(arg_names)} positional arguments but {len(args)} were given")
|
546
|
-
arg_name = arg_names[i]
|
547
|
-
if arg_name in kwargs:
|
548
|
-
raise TypeError(f"{self.name}() got multiple values for argument '{arg_name}'")
|
549
|
-
kwargs[arg_name] = arg_value
|
550
|
-
|
551
|
-
if self.is_async:
|
552
|
-
return await self.async_execute(**kwargs)
|
553
|
-
else:
|
554
|
-
return await asyncio.to_thread(self.execute, **kwargs)
|
555
|
-
|
556
|
-
def get_injectable_properties_in_execution(self) -> dict[str, Any]:
|
557
|
-
"""Get injectable properties excluding tool arguments.
|
558
|
-
|
559
|
-
Returns:
|
560
|
-
A dictionary of property names and values, excluding tool arguments and None values.
|
561
|
-
"""
|
562
|
-
argument_names = {arg.name for arg in self.arguments}
|
563
|
-
properties = self.get_properties(exclude=["arguments"])
|
564
|
-
return {name: value for name, value in properties.items() if value is not None and name in argument_names}
|
565
|
-
|
566
|
-
|
567
|
-
def create_tool(func: F) -> Tool:
|
568
|
-
"""Create a Tool instance from a Python function using AST analysis with enhanced return type metadata.
|
569
|
-
|
570
|
-
Analyzes the function's source code to extract its name, docstring, and arguments,
|
571
|
-
then constructs a Tool subclass with appropriate execution logic for both
|
572
|
-
synchronous and asynchronous functions.
|
573
|
-
|
574
|
-
Args:
|
575
|
-
func: The Python function (sync or async) to convert into a Tool.
|
576
|
-
|
577
|
-
Returns:
|
578
|
-
A Tool subclass instance configured based on the function.
|
579
|
-
|
580
|
-
Raises:
|
581
|
-
ValueError: If the input is not a valid function or lacks a function definition.
|
582
|
-
"""
|
583
|
-
if not callable(func):
|
584
|
-
raise ValueError("Input must be a callable function")
|
585
|
-
|
586
|
-
try:
|
587
|
-
source = inspect.getsource(func).strip()
|
588
|
-
tree = ast.parse(source)
|
589
|
-
except (OSError, TypeError, SyntaxError) as e:
|
590
|
-
raise ValueError(f"Failed to parse function source: {e}")
|
591
|
-
|
592
|
-
if not tree.body or not isinstance(tree.body[0], (ast.FunctionDef, ast.AsyncFunctionDef)):
|
593
|
-
raise ValueError("Source must define a single function")
|
594
|
-
func_def = tree.body[0]
|
595
|
-
|
596
|
-
name = func_def.name
|
597
|
-
docstring = ast.get_docstring(func_def) or ""
|
598
|
-
parsed_doc = parse_docstring(docstring)
|
599
|
-
description = parsed_doc.short_description or f"Tool generated from {name}"
|
600
|
-
param_docs = {p.arg_name: p.description for p in parsed_doc.params}
|
601
|
-
return_description = parsed_doc.returns.description if parsed_doc.returns else None
|
602
|
-
is_async = isinstance(func_def, ast.AsyncFunctionDef)
|
603
|
-
|
604
|
-
from typing import get_type_hints
|
605
|
-
type_hints = get_type_hints(func)
|
606
|
-
|
607
|
-
args = func_def.args
|
608
|
-
defaults = [None] * (len(args.args) - len(args.defaults)) + [
|
609
|
-
ast.unparse(d) if isinstance(d, ast.AST) else str(d) for d in args.defaults
|
610
|
-
]
|
611
|
-
arguments: list[ToolArgument] = []
|
612
|
-
|
613
|
-
for i, arg in enumerate(args.args):
|
614
|
-
arg_name = arg.arg
|
615
|
-
default = defaults[i]
|
616
|
-
required = default is None
|
617
|
-
hint = type_hints.get(arg_name, str)
|
618
|
-
arg_type = type_hint_to_str(hint)
|
619
|
-
description = param_docs.get(arg_name, f"Argument {arg_name}")
|
620
|
-
arguments.append(ToolArgument(
|
621
|
-
name=arg_name,
|
622
|
-
arg_type=arg_type,
|
623
|
-
description=description,
|
624
|
-
required=required,
|
625
|
-
default=default,
|
626
|
-
example=default if default else None,
|
627
|
-
type_details=get_type_description(hint)
|
628
|
-
))
|
629
|
-
|
630
|
-
return_type = type_hints.get("return", str)
|
631
|
-
return_type_str = type_hint_to_str(return_type)
|
632
|
-
return_type_details = get_type_description(return_type)
|
633
|
-
return_structure = get_type_schema(return_type)
|
634
|
-
|
635
|
-
class GeneratedTool(Tool):
|
636
|
-
def __init__(self, *args: Any, **kwargs: Any):
|
637
|
-
super().__init__(
|
638
|
-
*args,
|
639
|
-
name=name,
|
640
|
-
description=description,
|
641
|
-
arguments=arguments,
|
642
|
-
return_type=return_type_str,
|
643
|
-
return_description=return_description,
|
644
|
-
return_type_details=return_type_details,
|
645
|
-
return_structure=return_structure,
|
646
|
-
original_docstring=docstring, # Preserve full original docstring
|
647
|
-
is_async=is_async,
|
648
|
-
toolbox_name=None,
|
649
|
-
**kwargs
|
650
|
-
)
|
651
|
-
self._func = func
|
652
|
-
|
653
|
-
def execute(self, **kwargs: Any) -> Any:
|
654
|
-
"""Execute the tool synchronously, handling both sync and async functions.
|
655
|
-
|
656
|
-
Args:
|
657
|
-
**kwargs: Keyword arguments for tool execution.
|
658
|
-
|
659
|
-
Returns:
|
660
|
-
The result of the function execution, preserving its original type.
|
661
|
-
"""
|
662
|
-
injectable = self.get_injectable_properties_in_execution()
|
663
|
-
full_kwargs = {**injectable, **kwargs}
|
664
|
-
if self.is_async:
|
665
|
-
return asyncio.run(self.async_execute(**full_kwargs))
|
666
|
-
else:
|
667
|
-
return self._func(**full_kwargs)
|
668
|
-
|
669
|
-
async def async_execute(self, **kwargs: Any) -> Any:
|
670
|
-
"""Execute the tool asynchronously, handling both sync and async functions.
|
671
|
-
|
672
|
-
Args:
|
673
|
-
**kwargs: Keyword arguments for tool execution.
|
674
|
-
|
675
|
-
Returns:
|
676
|
-
The result of the function execution, preserving its original type.
|
677
|
-
"""
|
678
|
-
injectable = self.get_injectable_properties_in_execution()
|
679
|
-
full_kwargs = {**injectable, **kwargs}
|
680
|
-
if self.is_async:
|
681
|
-
return await self._func(**full_kwargs)
|
682
|
-
else:
|
683
|
-
return await asyncio.to_thread(self._func, **full_kwargs)
|
684
|
-
|
685
|
-
return GeneratedTool()
|
686
|
-
|
687
|
-
|
688
|
-
if __name__ == "__main__":
|
689
|
-
# Basic tool with argument
|
690
|
-
tool = Tool(
|
691
|
-
name="my_tool",
|
692
|
-
description="A simple tool",
|
693
|
-
arguments=[ToolArgument(name="arg1", arg_type="string")]
|
694
|
-
)
|
695
|
-
print("Basic Tool Markdown:")
|
696
|
-
print(tool.to_markdown())
|
697
|
-
print("Basic Tool Docstring:")
|
698
|
-
print(tool.to_docstring())
|
699
|
-
print()
|
700
|
-
|
701
|
-
# Tool with injectable field (undefined)
|
702
|
-
class MyTool(Tool):
|
703
|
-
field1: str | None = Field(default=None, description="Field 1 description")
|
704
|
-
|
705
|
-
tool_with_fields = MyTool(
|
706
|
-
name="my_tool1",
|
707
|
-
description="A simple tool with a field",
|
708
|
-
arguments=[ToolArgument(name="field1", arg_type="string")]
|
709
|
-
)
|
710
|
-
print("Tool with Undefined Field Markdown:")
|
711
|
-
print(tool_with_fields.to_markdown())
|
712
|
-
print("Injectable Properties (should be empty):", tool_with_fields.get_injectable_properties_in_execution())
|
713
|
-
print("Tool with Undefined Field Docstring:")
|
714
|
-
print(tool_with_fields.to_docstring())
|
715
|
-
print()
|
716
|
-
|
717
|
-
# Tool with defined injectable field
|
718
|
-
tool_with_fields_defined = MyTool(
|
719
|
-
name="my_tool2",
|
720
|
-
description="A simple tool with a defined field",
|
721
|
-
field1="field1_value",
|
722
|
-
arguments=[ToolArgument(name="field1", arg_type="string")]
|
723
|
-
)
|
724
|
-
print("Tool with Defined Field Markdown:")
|
725
|
-
print(tool_with_fields_defined.to_markdown())
|
726
|
-
print("Injectable Properties (should include field1):", tool_with_fields_defined.get_injectable_properties_in_execution())
|
727
|
-
print("Tool with Defined Field Docstring:")
|
728
|
-
print(tool_with_fields_defined.to_docstring())
|
729
|
-
print()
|
730
|
-
|
731
|
-
# Test create_tool with synchronous function
|
732
|
-
def add(a: int, b: int = 0) -> int:
|
733
|
-
"""Add two numbers.
|
734
|
-
|
735
|
-
Args:
|
736
|
-
a: First number.
|
737
|
-
b: Second number (optional).
|
738
|
-
|
739
|
-
Returns:
|
740
|
-
The sum of a and b.
|
741
|
-
"""
|
742
|
-
return a + b
|
743
|
-
|
744
|
-
sync_tool = create_tool(add)
|
745
|
-
print("Synchronous Tool Markdown:")
|
746
|
-
print(sync_tool.to_markdown())
|
747
|
-
print("Synchronous Tool Docstring:")
|
748
|
-
print(sync_tool.to_docstring())
|
749
|
-
print("Execution result (sync):", sync_tool.execute(a=5, b=3))
|
750
|
-
print("Execution result (async):", asyncio.run(sync_tool.async_execute(a=5, b=3)))
|
751
|
-
print("Execution result (callable):", asyncio.run(sync_tool(a=5, b=3)))
|
752
|
-
print()
|
753
|
-
|
754
|
-
# Test create_tool with asynchronous function
|
755
|
-
async def greet(name: str) -> str:
|
756
|
-
"""Greet a person.
|
757
|
-
|
758
|
-
Args:
|
759
|
-
name: Name of the person.
|
760
|
-
|
761
|
-
Returns:
|
762
|
-
A greeting message.
|
763
|
-
|
764
|
-
Examples:
|
765
|
-
>>> await greet("Alice")
|
766
|
-
'Hello, Alice'
|
767
|
-
"""
|
768
|
-
await asyncio.sleep(0.1)
|
769
|
-
return f"Hello, {name}"
|
770
|
-
|
771
|
-
async_tool = create_tool(greet)
|
772
|
-
print("Asynchronous Tool Markdown:")
|
773
|
-
print(async_tool.to_markdown())
|
774
|
-
print("Asynchronous Tool Docstring:")
|
775
|
-
print(async_tool.to_docstring())
|
776
|
-
print("Execution result (sync):", async_tool.execute(name="Alice"))
|
777
|
-
print("Execution result (async):", asyncio.run(async_tool.async_execute(name="Alice")))
|
778
|
-
print("Execution result (callable):", asyncio.run(async_tool(name="Alice")))
|
779
|
-
print()
|
780
|
-
|
781
|
-
# Comprehensive tool with complex types
|
782
|
-
from typing import Dict, List
|
783
|
-
|
784
|
-
def process_data(data: List[int], options: Dict[str, bool] = {}) -> Dict[str, int]:
|
785
|
-
"""Process a list of integers with options.
|
786
|
-
|
787
|
-
Args:
|
788
|
-
data: List of integers to process.
|
789
|
-
options: Dictionary of options.
|
790
|
-
|
791
|
-
Returns:
|
792
|
-
A dictionary with results.
|
793
|
-
"""
|
794
|
-
return {str(i): i for i in data}
|
795
|
-
|
796
|
-
complex_tool = create_tool(process_data)
|
797
|
-
print("Complex Tool Markdown:")
|
798
|
-
print(complex_tool.to_markdown())
|
799
|
-
print("Complex Tool Docstring:")
|
800
|
-
print(complex_tool.to_docstring())
|
801
|
-
print("Execution result (sync):", complex_tool.execute(data=[1, 2, 3]))
|
802
|
-
print("Execution result (callable):", asyncio.run(complex_tool(data=[1, 2, 3])))
|
803
|
-
print()
|
804
|
-
|
805
|
-
# Comprehensive tool for to_docstring demonstration with custom return type
|
806
|
-
docstring_tool = Tool(
|
807
|
-
name="sample_tool",
|
808
|
-
description="A sample tool for testing docstring generation.",
|
809
|
-
arguments=[
|
810
|
-
ToolArgument(name="x", arg_type="int", description="The first number", required=True),
|
811
|
-
ToolArgument(name="y", arg_type="float", description="The second number", default="0.0", example="1.5"),
|
812
|
-
ToolArgument(name="verbose", arg_type="boolean", description="Print extra info", default="False")
|
813
|
-
],
|
814
|
-
return_type="int",
|
815
|
-
return_description="The computed result of the operation.",
|
816
|
-
return_example="42",
|
817
|
-
return_structure="A single integer value."
|
818
|
-
)
|
819
|
-
print("Comprehensive Tool Markdown:")
|
820
|
-
print(docstring_tool.to_markdown())
|
821
|
-
print("Comprehensive Tool Docstring with Custom Return Type:")
|
822
|
-
print(docstring_tool.to_docstring())
|
823
|
-
|
824
|
-
from dataclasses import dataclass
|
825
|
-
|
826
|
-
@dataclass
|
827
|
-
class Point:
|
828
|
-
x: int
|
829
|
-
y: int
|
830
|
-
|
831
|
-
async def distance(point1: Point, point2: Point) -> float:
|
832
|
-
"""
|
833
|
-
Calculate the Euclidean distance between two points.
|
834
|
-
|
835
|
-
Args:
|
836
|
-
point1: First point with x and y coordinates.
|
837
|
-
point2: Second point with x and y coordinates.
|
838
|
-
|
839
|
-
Returns:
|
840
|
-
The Euclidean distance between two points.
|
841
|
-
"""
|
842
|
-
return ((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2) ** 0.5
|
843
|
-
|
844
|
-
distance_tool = create_tool(distance)
|
845
|
-
print("Distance Tool Markdown:")
|
846
|
-
print(distance_tool.to_markdown())
|
847
|
-
print("Distance Tool Docstring:")
|
848
|
-
print(distance_tool.to_docstring())
|
849
|
-
print("Execution result (sync):", distance_tool.execute(point1=Point(x=1, y=2), point2=Point(x=4, y=6)))
|
850
|
-
print("Execution result (callable):", asyncio.run(distance_tool(point1=Point(x=1, y=2), point2=Point(x=4, y=6))))
|
851
|
-
print()
|
852
|
-
|
853
|
-
async def hello(name: str) -> str:
|
854
|
-
"""
|
855
|
-
Greet a person.
|
856
|
-
|
857
|
-
Args:
|
858
|
-
name: Name of the person.
|
859
|
-
|
860
|
-
Returns:
|
861
|
-
A greeting message.
|
862
|
-
"""
|
863
|
-
return f"Hello, {name}"
|
864
|
-
|
865
|
-
hello_tool = create_tool(hello)
|
866
|
-
print("Hello Tool Markdown:")
|
867
|
-
print(hello_tool.to_markdown())
|
868
|
-
print("Hello Tool Docstring:")
|
869
|
-
print(hello_tool.to_docstring())
|
870
|
-
print("Execution result (sync):", hello_tool.execute(name="Alice"))
|
871
|
-
print("Execution result (async):", asyncio.run(hello_tool.async_execute(name="Alice")))
|
872
|
-
print("Execution result (callable named arguments):", asyncio.run(hello_tool(name="Alice")))
|
873
|
-
print("Execution result (callable positional arguments):", asyncio.run(hello_tool("Alice")))
|
874
|
-
print()
|
875
|
-
|
876
|
-
def add(a: int, b: int = 0) -> int:
|
877
|
-
"""
|
878
|
-
Add two numbers.
|
879
|
-
|
880
|
-
Args:
|
881
|
-
a: First number.
|
882
|
-
b: Second number (optional).
|
883
|
-
|
884
|
-
Returns:
|
885
|
-
The sum of a and b.
|
886
|
-
"""
|
887
|
-
return a + b
|
888
|
-
|
889
|
-
add_tool = create_tool(add)
|
890
|
-
print("Add Tool Markdown:")
|
891
|
-
print(add_tool.to_markdown())
|
892
|
-
print("Add Tool Docstring:")
|
893
|
-
print(add_tool.to_docstring())
|
894
|
-
print("Execution result (sync):", add_tool.execute(a=5, b=3))
|
895
|
-
print("Execution result (async):", asyncio.run(add_tool.async_execute(a=5, b=3)))
|
896
|
-
print("Execution result (callable named arguments):", asyncio.run(add_tool(a=5, b=3)))
|
897
|
-
print("Execution result (callable positional arguments):", asyncio.run(add_tool(5, 3)))
|
898
|
-
print()
|
899
|
-
|
900
|
-
def subtract(a: int, b: int = 0) -> int:
|
901
|
-
"""
|
902
|
-
Subtract two numbers.
|
903
|
-
|
904
|
-
Args:
|
905
|
-
a: First number.
|
906
|
-
b: Second number (optional).
|
907
|
-
|
908
|
-
Returns:
|
909
|
-
The difference of a and b.
|
910
|
-
"""
|
911
|
-
return a - b
|
912
|
-
|
913
|
-
subtract_tool = create_tool(subtract)
|
914
|
-
print("Subtract Tool Markdown:")
|
915
|
-
print(subtract_tool.to_markdown())
|
916
|
-
print("Subtract Tool Docstring:")
|
917
|
-
print(subtract_tool.to_docstring())
|
918
|
-
print("Execution result (sync):", subtract_tool.execute(a=5, b=3))
|
919
|
-
print("Execution result (async):", asyncio.run(subtract_tool.async_execute(a=5, b=3)))
|
920
|
-
print("Execution result (callable named arguments):", asyncio.run(subtract_tool(a=5, b=3)))
|
921
|
-
print("Execution result (callable positional arguments):", asyncio.run(subtract_tool(5, 3)))
|
922
|
-
print()
|
1
|
+
from quantalogic_toolbox.tool import Tool, ToolArgument, create_tool
|
2
|
+
|
3
|
+
__all__ = [
|
4
|
+
"ToolArgument",
|
5
|
+
"Tool",
|
6
|
+
"create_tool",
|
7
|
+
]
|
8
|
+
|