quantalogic 0.61.3__py3-none-any.whl → 0.92__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/agent.py +0 -1
- quantalogic/flow/__init__.py +16 -34
- quantalogic/main.py +11 -6
- quantalogic/tools/action_gen.py +1 -1
- quantalogic/tools/tool.py +8 -500
- quantalogic-0.92.dist-info/METADATA +448 -0
- {quantalogic-0.61.3.dist-info → quantalogic-0.92.dist-info}/RECORD +10 -33
- {quantalogic-0.61.3.dist-info → quantalogic-0.92.dist-info}/WHEEL +1 -1
- quantalogic-0.92.dist-info/entry_points.txt +3 -0
- quantalogic/codeact/__init__.py +0 -0
- quantalogic/codeact/agent.py +0 -499
- quantalogic/codeact/cli.py +0 -232
- quantalogic/codeact/constants.py +0 -9
- quantalogic/codeact/events.py +0 -78
- quantalogic/codeact/llm_util.py +0 -76
- quantalogic/codeact/prompts/error_format.j2 +0 -11
- quantalogic/codeact/prompts/generate_action.j2 +0 -26
- quantalogic/codeact/prompts/generate_program.j2 +0 -39
- quantalogic/codeact/prompts/response_format.j2 +0 -11
- quantalogic/codeact/tools_manager.py +0 -135
- quantalogic/codeact/utils.py +0 -135
- quantalogic/flow/flow.py +0 -960
- quantalogic/flow/flow_extractor.py +0 -723
- quantalogic/flow/flow_generator.py +0 -294
- quantalogic/flow/flow_manager.py +0 -637
- quantalogic/flow/flow_manager_schema.py +0 -255
- 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.61.3.dist-info/METADATA +0 -900
- quantalogic-0.61.3.dist-info/entry_points.txt +0 -6
- {quantalogic-0.61.3.dist-info → quantalogic-0.92.dist-info}/LICENSE +0 -0
quantalogic/agent.py
CHANGED
@@ -23,7 +23,6 @@ from quantalogic.utils import get_environment
|
|
23
23
|
from quantalogic.utils.ask_user_validation import console_ask_for_user_validation
|
24
24
|
from quantalogic.xml_parser import ToleranceXMLParser
|
25
25
|
from quantalogic.xml_tool_parser import ToolParser
|
26
|
-
import uuid
|
27
26
|
|
28
27
|
# Maximum ratio occupancy of the occupied memory
|
29
28
|
MAX_OCCUPANCY = 90.0
|
quantalogic/flow/__init__.py
CHANGED
@@ -1,40 +1,22 @@
|
|
1
|
-
"""
|
2
|
-
|
1
|
+
"""Stub alias for backwards compatibility: re-export Flow API from the quantalogic_flow package."""
|
2
|
+
from quantalogic_flow import (
|
3
|
+
Nodes,
|
4
|
+
Workflow,
|
5
|
+
WorkflowEngine,
|
6
|
+
WorkflowManager,
|
7
|
+
extract_workflow_from_file,
|
8
|
+
generate_executable_script,
|
9
|
+
generate_mermaid_diagram,
|
10
|
+
validate_workflow_definition,
|
11
|
+
)
|
3
12
|
|
4
|
-
This module initializes the flow package and provides package-level imports.
|
5
|
-
Now supports nested workflows for hierarchical flow definitions.
|
6
|
-
|
7
|
-
Key Visualization Utilities:
|
8
|
-
- generate_mermaid_diagram(): Convert workflow definitions to visual Mermaid flowcharts
|
9
|
-
- Supports pastel-colored node styling
|
10
|
-
- Generates interactive, readable workflow diagrams
|
11
|
-
- Handles complex workflows with multiple node types
|
12
|
-
|
13
|
-
- Generates descriptive labels
|
14
|
-
- Supports conditional node detection
|
15
|
-
"""
|
16
|
-
|
17
|
-
from loguru import logger
|
18
|
-
|
19
|
-
# Expose key components for easy importing
|
20
|
-
from .flow import Nodes, Workflow, WorkflowEngine
|
21
|
-
from .flow_extractor import extract_workflow_from_file
|
22
|
-
from .flow_generator import generate_executable_script
|
23
|
-
from .flow_manager import WorkflowManager
|
24
|
-
from .flow_mermaid import generate_mermaid_diagram
|
25
|
-
from .flow_validator import validate_workflow_definition
|
26
|
-
|
27
|
-
# Define which symbols are exported when using `from flow import *`
|
28
13
|
__all__ = [
|
29
|
-
"
|
14
|
+
"extract_workflow_from_file",
|
15
|
+
"generate_executable_script",
|
16
|
+
"generate_mermaid_diagram",
|
30
17
|
"Nodes",
|
18
|
+
"validate_workflow_definition",
|
31
19
|
"Workflow",
|
32
20
|
"WorkflowEngine",
|
33
|
-
"
|
34
|
-
"extract_workflow_from_file",
|
35
|
-
"generate_executable_script",
|
36
|
-
"validate_workflow_definition"
|
21
|
+
"WorkflowManager",
|
37
22
|
]
|
38
|
-
|
39
|
-
# Package-level logger configuration
|
40
|
-
logger.info("Initializing Quantalogic Flow Package")
|
quantalogic/main.py
CHANGED
@@ -18,6 +18,11 @@ load_dotenv()
|
|
18
18
|
|
19
19
|
# Configure logger
|
20
20
|
logger.remove()
|
21
|
+
logger.add(
|
22
|
+
sys.stderr,
|
23
|
+
level="ERROR", # Changed from WARNING to ERROR
|
24
|
+
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>"
|
25
|
+
)
|
21
26
|
|
22
27
|
from rich.console import Console # noqa: E402
|
23
28
|
from rich.panel import Panel # noqa: E402
|
@@ -98,9 +103,9 @@ def restore_terminal(old_settings):
|
|
98
103
|
)
|
99
104
|
@click.option(
|
100
105
|
"--log",
|
101
|
-
type=click.Choice(["info", "debug", "warning"]),
|
106
|
+
type=click.Choice(["info", "debug", "warning", "error"]),
|
102
107
|
default="info",
|
103
|
-
help="Set logging level (info/debug/warning).",
|
108
|
+
help="Set logging level (info/debug/warning/error).",
|
104
109
|
)
|
105
110
|
@click.option("--verbose", is_flag=True, help="Enable verbose output.")
|
106
111
|
@click.option("--mode", type=click.Choice(AGENT_MODES), default="basic", help="Agent mode (code/search/full/chat).")
|
@@ -196,9 +201,9 @@ def cli(
|
|
196
201
|
@click.option("--mode", type=click.Choice(AGENT_MODES), default="basic", help="Agent mode (code/search/full/chat).")
|
197
202
|
@click.option(
|
198
203
|
"--log",
|
199
|
-
type=click.Choice(["info", "debug", "warning"]),
|
204
|
+
type=click.Choice(["info", "debug", "warning", "error"]),
|
200
205
|
default="info",
|
201
|
-
help="Set logging level (info/debug/warning).",
|
206
|
+
help="Set logging level (info/debug/warning/error).",
|
202
207
|
)
|
203
208
|
@click.option(
|
204
209
|
"--vision-model-name",
|
@@ -318,9 +323,9 @@ def list_models(search: Optional[str] = None):
|
|
318
323
|
)
|
319
324
|
@click.option(
|
320
325
|
"--log",
|
321
|
-
type=click.Choice(["info", "debug", "warning"]),
|
326
|
+
type=click.Choice(["info", "debug", "warning", "error"]),
|
322
327
|
default="info",
|
323
|
-
help="Set logging level (info/debug/warning).",
|
328
|
+
help="Set logging level (info/debug/warning/error).",
|
324
329
|
)
|
325
330
|
@click.option("--verbose", is_flag=True, help="Enable verbose output.")
|
326
331
|
@click.option(
|
quantalogic/tools/action_gen.py
CHANGED
@@ -8,8 +8,8 @@ from typing import Callable, Dict, List
|
|
8
8
|
import litellm
|
9
9
|
import typer
|
10
10
|
from loguru import logger
|
11
|
+
from quantalogic_pythonbox.python_interpreter import execute_async
|
11
12
|
|
12
|
-
from quantalogic.python_interpreter import execute_async
|
13
13
|
from quantalogic.tools.tool import Tool, ToolArgument
|
14
14
|
|
15
15
|
# Configure loguru to log to a file with rotation, matching original
|
quantalogic/tools/tool.py
CHANGED
@@ -1,500 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
""
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
import inspect
|
10
|
-
from typing import Any, Callable, Literal, TypeVar
|
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
|
-
class ToolArgument(BaseModel):
|
19
|
-
"""Represents an argument for a tool with validation and description.
|
20
|
-
|
21
|
-
Attributes:
|
22
|
-
name: The name of the argument.
|
23
|
-
arg_type: The type of the argument (integer, float, boolean).
|
24
|
-
description: Optional description of the argument.
|
25
|
-
required: Indicates if the argument is mandatory.
|
26
|
-
default: Optional default value for the argument.
|
27
|
-
example: Optional example value to illustrate the argument's usage.
|
28
|
-
"""
|
29
|
-
|
30
|
-
name: str = Field(..., description="The name of the argument.")
|
31
|
-
arg_type: Literal["string", "int", "float", "boolean"] = Field(
|
32
|
-
..., description="The type of the argument. Must be one of: string, integer, float, boolean."
|
33
|
-
)
|
34
|
-
description: str | None = Field(default=None, description="A brief description of the argument.")
|
35
|
-
required: bool = Field(default=False, description="Indicates if the argument is required.")
|
36
|
-
default: str | None = Field(
|
37
|
-
default=None, description="The default value for the argument. This parameter is required."
|
38
|
-
)
|
39
|
-
example: str | None = Field(default=None, description="An example value to illustrate the argument's usage.")
|
40
|
-
|
41
|
-
class ToolDefinition(BaseModel):
|
42
|
-
"""Base class for defining tool configurations without execution logic.
|
43
|
-
|
44
|
-
Attributes:
|
45
|
-
name: Unique name of the tool.
|
46
|
-
description: Brief description of the tool's functionality.
|
47
|
-
arguments: List of arguments the tool accepts.
|
48
|
-
return_type: The return type of the tool's execution method. Defaults to "str".
|
49
|
-
need_validation: Flag to indicate if tool requires validation.
|
50
|
-
"""
|
51
|
-
|
52
|
-
model_config = ConfigDict(extra="allow", validate_assignment=True)
|
53
|
-
|
54
|
-
name: str = Field(..., description="The unique name of the tool.")
|
55
|
-
description: str = Field(..., description="A brief description of what the tool does.")
|
56
|
-
arguments: list[ToolArgument] = Field(default_factory=list, description="A list of arguments the tool accepts.")
|
57
|
-
return_type: str = Field(default="str", description="The return type of the tool's execution method.")
|
58
|
-
need_validation: bool = Field(
|
59
|
-
default=False,
|
60
|
-
description="When True, requires user confirmation before execution. Useful for tools that perform potentially destructive operations.",
|
61
|
-
)
|
62
|
-
need_post_process: bool = Field(
|
63
|
-
default=True,
|
64
|
-
description="When True, requires user confirmation before execution. Useful for tools that perform potentially destructive operations.",
|
65
|
-
)
|
66
|
-
need_variables: bool = Field(
|
67
|
-
default=False,
|
68
|
-
description="When True, provides access to the agent's variable store. Required for tools that need to interpolate variables (e.g., Jinja templates).",
|
69
|
-
)
|
70
|
-
need_caller_context_memory: bool = Field(
|
71
|
-
default=False,
|
72
|
-
description="When True, provides access to the agent's conversation history. Useful for tools that need context from previous interactions.",
|
73
|
-
)
|
74
|
-
|
75
|
-
def get_properties(self, exclude: list[str] | None = None) -> dict[str, Any]:
|
76
|
-
"""Return a dictionary of all non-None properties, excluding Tool class fields and specified fields.
|
77
|
-
|
78
|
-
Args:
|
79
|
-
exclude: Optional list of field names to exclude from the result
|
80
|
-
|
81
|
-
Returns:
|
82
|
-
Dictionary of property names and values, excluding Tool class fields and specified fields.
|
83
|
-
"""
|
84
|
-
exclude = exclude or []
|
85
|
-
tool_fields = {
|
86
|
-
"name",
|
87
|
-
"description",
|
88
|
-
"arguments",
|
89
|
-
"return_type",
|
90
|
-
"need_validation",
|
91
|
-
"need_variables",
|
92
|
-
"need_caller_context_memory",
|
93
|
-
}
|
94
|
-
properties = {}
|
95
|
-
|
96
|
-
for name, value in self.__dict__.items():
|
97
|
-
if name not in tool_fields and name not in exclude and value is not None and not name.startswith("_"):
|
98
|
-
properties[name] = value
|
99
|
-
|
100
|
-
return properties
|
101
|
-
|
102
|
-
def to_json(self) -> str:
|
103
|
-
"""Convert the tool to a JSON string representation.
|
104
|
-
|
105
|
-
Returns:
|
106
|
-
A JSON string of the tool's configuration.
|
107
|
-
"""
|
108
|
-
return self.model_dump_json()
|
109
|
-
|
110
|
-
def to_markdown(self) -> str:
|
111
|
-
"""Create a comprehensive Markdown representation of the tool.
|
112
|
-
|
113
|
-
Returns:
|
114
|
-
A detailed Markdown string representing the tool's configuration and usage.
|
115
|
-
"""
|
116
|
-
# Tool name and description
|
117
|
-
markdown = f"`{self.name}`:\n"
|
118
|
-
markdown += f"- **Description**: {self.description}\n\n"
|
119
|
-
|
120
|
-
properties_injectable = self.get_injectable_properties_in_execution()
|
121
|
-
|
122
|
-
# Parameters section
|
123
|
-
if self.arguments:
|
124
|
-
markdown += "- **Parameters**:\n"
|
125
|
-
parameters = ""
|
126
|
-
for arg in self.arguments:
|
127
|
-
# Skip if parameter name matches an object property with non-None value
|
128
|
-
if properties_injectable.get(arg.name) is not None:
|
129
|
-
continue
|
130
|
-
|
131
|
-
required_status = "required" if arg.required else "optional"
|
132
|
-
# Prioritize example, then default, then create a generic description
|
133
|
-
value_info = ""
|
134
|
-
if arg.example is not None:
|
135
|
-
value_info = f" (example: `{arg.example}`)"
|
136
|
-
elif arg.default is not None:
|
137
|
-
value_info = f" (default: `{arg.default}`)"
|
138
|
-
|
139
|
-
parameters += (
|
140
|
-
f" - `{arg.name}`: " f"({required_status}{value_info})\n" f" {arg.description or ''}\n"
|
141
|
-
)
|
142
|
-
if len(parameters) > 0:
|
143
|
-
markdown += parameters + "\n\n"
|
144
|
-
else:
|
145
|
-
markdown += "None\n\n"
|
146
|
-
|
147
|
-
# Usage section with XML-style example
|
148
|
-
markdown += "**Usage**:\n"
|
149
|
-
markdown += "```xml\n"
|
150
|
-
markdown += f"<{self.name}>\n"
|
151
|
-
|
152
|
-
# Generate example parameters
|
153
|
-
for arg in self.arguments:
|
154
|
-
if properties_injectable.get(arg.name) is not None:
|
155
|
-
continue
|
156
|
-
# Prioritize example, then default, then create a generic example
|
157
|
-
example_value = arg.example or arg.default or f"Your {arg.name} here"
|
158
|
-
markdown += f" <{arg.name}>{example_value}</{arg.name}>\n"
|
159
|
-
|
160
|
-
markdown += f"</{self.name}>\n"
|
161
|
-
markdown += "```\n"
|
162
|
-
|
163
|
-
return markdown
|
164
|
-
|
165
|
-
def get_non_injectable_arguments(self) -> list[ToolArgument]:
|
166
|
-
"""Get arguments that cannot be injected from properties.
|
167
|
-
|
168
|
-
Returns:
|
169
|
-
List of ToolArgument instances that cannot be injected by the agent.
|
170
|
-
"""
|
171
|
-
properties_injectable = self.get_injectable_properties_in_execution()
|
172
|
-
|
173
|
-
return [arg for arg in self.arguments if properties_injectable.get(arg.name) is None]
|
174
|
-
|
175
|
-
def get_injectable_properties_in_execution(self) -> dict[str, Any]:
|
176
|
-
"""Get injectable properties excluding tool arguments.
|
177
|
-
|
178
|
-
Returns:
|
179
|
-
A dictionary of property names and values, excluding tool arguments and None values.
|
180
|
-
"""
|
181
|
-
# This method is defined here in ToolDefinition and overridden in Tool
|
182
|
-
# For ToolDefinition, it returns an empty dict since it has no execution context yet
|
183
|
-
return {}
|
184
|
-
|
185
|
-
def to_docstring(self) -> str:
|
186
|
-
"""Convert the tool definition into a Google-style docstring with function signature.
|
187
|
-
|
188
|
-
Returns:
|
189
|
-
A string formatted as a valid Python docstring representing the tool's configuration,
|
190
|
-
including the function signature and return type.
|
191
|
-
"""
|
192
|
-
# Construct the function signature
|
193
|
-
signature_parts = []
|
194
|
-
for arg in self.arguments:
|
195
|
-
# Base argument: name and type
|
196
|
-
arg_str = f"{arg.name}: {arg.arg_type}"
|
197
|
-
# Add default value if present
|
198
|
-
if arg.default is not None:
|
199
|
-
arg_str += f" = {arg.default}"
|
200
|
-
signature_parts.append(arg_str)
|
201
|
-
signature = f"def {self.name}({', '.join(signature_parts)}) -> {self.return_type}:"
|
202
|
-
|
203
|
-
# Start with the signature and description
|
204
|
-
docstring = f'"""\n{signature}\n\n{self.description}\n\n'
|
205
|
-
|
206
|
-
# Add Arguments section if there are any
|
207
|
-
if self.arguments:
|
208
|
-
docstring += "Args:\n"
|
209
|
-
for arg in self.arguments:
|
210
|
-
# Base argument line: name and type
|
211
|
-
arg_line = f" {arg.name} ({arg.arg_type})"
|
212
|
-
|
213
|
-
# Add optional/required status and default/example if present
|
214
|
-
details = []
|
215
|
-
if not arg.required:
|
216
|
-
details.append("optional")
|
217
|
-
if arg.default is not None:
|
218
|
-
details.append(f"defaults to {arg.default}")
|
219
|
-
if arg.example is not None:
|
220
|
-
details.append(f"e.g., {arg.example}")
|
221
|
-
if details:
|
222
|
-
arg_line += f" [{', '.join(details)}]"
|
223
|
-
|
224
|
-
# Add description if present
|
225
|
-
if arg.description:
|
226
|
-
arg_line += f": {arg.description}"
|
227
|
-
|
228
|
-
docstring += f"{arg_line}\n"
|
229
|
-
|
230
|
-
# Add Returns section
|
231
|
-
docstring += f"Returns:\n {self.return_type}: The result of the tool execution.\n"
|
232
|
-
|
233
|
-
# Close the docstring
|
234
|
-
docstring += '"""'
|
235
|
-
|
236
|
-
return docstring
|
237
|
-
|
238
|
-
class Tool(ToolDefinition):
|
239
|
-
"""Extended class for tools with execution capabilities.
|
240
|
-
|
241
|
-
Inherits from ToolDefinition and adds execution functionality.
|
242
|
-
"""
|
243
|
-
|
244
|
-
@field_validator("arguments", mode="before")
|
245
|
-
@classmethod
|
246
|
-
def validate_arguments(cls, v: Any) -> list[ToolArgument]:
|
247
|
-
"""Validate and convert arguments to ToolArgument instances.
|
248
|
-
|
249
|
-
Args:
|
250
|
-
v: Input arguments to validate.
|
251
|
-
|
252
|
-
Returns:
|
253
|
-
A list of validated ToolArgument instances.
|
254
|
-
"""
|
255
|
-
if isinstance(v, list):
|
256
|
-
return [
|
257
|
-
ToolArgument(**arg)
|
258
|
-
if isinstance(arg, dict)
|
259
|
-
else arg
|
260
|
-
if isinstance(arg, ToolArgument)
|
261
|
-
else ToolArgument(name=str(arg), type=type(arg).__name__)
|
262
|
-
for arg in v
|
263
|
-
]
|
264
|
-
return []
|
265
|
-
|
266
|
-
def execute(self, **kwargs: Any) -> str:
|
267
|
-
"""Execute the tool with provided arguments.
|
268
|
-
|
269
|
-
If not implemented by a subclass, falls back to the asynchronous execute_async method.
|
270
|
-
|
271
|
-
Args:
|
272
|
-
**kwargs: Keyword arguments for tool execution.
|
273
|
-
|
274
|
-
Returns:
|
275
|
-
A string representing the result of tool execution.
|
276
|
-
"""
|
277
|
-
# Check if execute is implemented in the subclass
|
278
|
-
if self.__class__.execute is Tool.execute:
|
279
|
-
# If not implemented, run the async version synchronously
|
280
|
-
return asyncio.run(self.async_execute(**kwargs))
|
281
|
-
raise NotImplementedError("This method should be implemented by subclasses.")
|
282
|
-
|
283
|
-
async def async_execute(self, **kwargs: Any) -> str:
|
284
|
-
"""Asynchronous version of execute.
|
285
|
-
|
286
|
-
By default, runs the synchronous execute method in a separate thread using asyncio.to_thread.
|
287
|
-
Subclasses can override this method to provide a native asynchronous implementation for
|
288
|
-
operations that benefit from async I/O (e.g., network requests).
|
289
|
-
|
290
|
-
Args:
|
291
|
-
**kwargs: Keyword arguments for tool execution.
|
292
|
-
|
293
|
-
Returns:
|
294
|
-
A string representing the result of tool execution.
|
295
|
-
"""
|
296
|
-
# Check if execute_async is implemented in the subclass
|
297
|
-
if self.__class__.async_execute is Tool.async_execute:
|
298
|
-
return await asyncio.to_thread(self.execute, **kwargs)
|
299
|
-
raise NotImplementedError("This method should be implemented by subclasses.")
|
300
|
-
|
301
|
-
def get_injectable_properties_in_execution(self) -> dict[str, Any]:
|
302
|
-
"""Get injectable properties excluding tool arguments.
|
303
|
-
|
304
|
-
Returns:
|
305
|
-
A dictionary of property names and values, excluding tool arguments and None values.
|
306
|
-
"""
|
307
|
-
# Get argument names from tool definition
|
308
|
-
argument_names = {arg.name for arg in self.arguments}
|
309
|
-
|
310
|
-
# Get properties excluding arguments and filter out None values
|
311
|
-
properties = self.get_properties(exclude=["arguments"])
|
312
|
-
return {name: value for name, value in properties.items() if value is not None and name in argument_names}
|
313
|
-
|
314
|
-
def create_tool(func: F) -> Tool:
|
315
|
-
"""Create a Tool instance from a Python function using AST analysis.
|
316
|
-
|
317
|
-
Analyzes the function's source code to extract its name, docstring, and arguments,
|
318
|
-
then constructs a Tool subclass with appropriate execution logic.
|
319
|
-
|
320
|
-
Args:
|
321
|
-
func: The Python function (sync or async) to convert into a Tool.
|
322
|
-
|
323
|
-
Returns:
|
324
|
-
A Tool subclass instance configured based on the function.
|
325
|
-
|
326
|
-
Raises:
|
327
|
-
ValueError: If the input is not a valid function or lacks a function definition.
|
328
|
-
"""
|
329
|
-
if not callable(func):
|
330
|
-
raise ValueError("Input must be a callable function")
|
331
|
-
|
332
|
-
# Get source code and parse with AST
|
333
|
-
try:
|
334
|
-
source = inspect.getsource(func).strip()
|
335
|
-
tree = ast.parse(source)
|
336
|
-
except (OSError, TypeError, SyntaxError) as e:
|
337
|
-
raise ValueError(f"Failed to parse function source: {e}")
|
338
|
-
|
339
|
-
# Ensure root node is a function definition
|
340
|
-
if not tree.body or not isinstance(tree.body[0], (ast.FunctionDef, ast.AsyncFunctionDef)):
|
341
|
-
raise ValueError("Source must define a single function")
|
342
|
-
func_def = tree.body[0]
|
343
|
-
|
344
|
-
# Extract metadata
|
345
|
-
name = func_def.name
|
346
|
-
docstring = ast.get_docstring(func_def) or ""
|
347
|
-
parsed_doc = parse_docstring(docstring)
|
348
|
-
description = parsed_doc.short_description or f"Tool generated from {name}"
|
349
|
-
param_docs = {p.arg_name: p.description for p in parsed_doc.params}
|
350
|
-
is_async = isinstance(func_def, ast.AsyncFunctionDef)
|
351
|
-
|
352
|
-
# Get type hints using typing module
|
353
|
-
from typing import get_type_hints
|
354
|
-
type_hints = get_type_hints(func)
|
355
|
-
type_map = {int: "int", str: "string", float: "float", bool: "boolean"}
|
356
|
-
|
357
|
-
# Process arguments
|
358
|
-
args = func_def.args
|
359
|
-
defaults = [None] * (len(args.args) - len(args.defaults)) + [
|
360
|
-
ast.unparse(d) if isinstance(d, ast.AST) else str(d) for d in args.defaults
|
361
|
-
]
|
362
|
-
arguments: list[ToolArgument] = []
|
363
|
-
|
364
|
-
for i, arg in enumerate(args.args):
|
365
|
-
arg_name = arg.arg
|
366
|
-
default = defaults[i]
|
367
|
-
required = default is None
|
368
|
-
|
369
|
-
# Determine argument type
|
370
|
-
hint = type_hints.get(arg_name, str) # Default to str if no hint
|
371
|
-
arg_type = type_map.get(hint, "string") # Fallback to string for unmapped types
|
372
|
-
|
373
|
-
# Use docstring or default description
|
374
|
-
description = param_docs.get(arg_name, f"Argument {arg_name}")
|
375
|
-
|
376
|
-
# Create ToolArgument
|
377
|
-
arguments.append(ToolArgument(
|
378
|
-
name=arg_name,
|
379
|
-
arg_type=arg_type,
|
380
|
-
description=description,
|
381
|
-
required=required,
|
382
|
-
default=default,
|
383
|
-
example=default if default else None
|
384
|
-
))
|
385
|
-
|
386
|
-
# Determine return type from type hints
|
387
|
-
return_type = type_hints.get("return", str)
|
388
|
-
return_type_str = type_map.get(return_type, "string")
|
389
|
-
|
390
|
-
# Define Tool subclass
|
391
|
-
class GeneratedTool(Tool):
|
392
|
-
def __init__(self, *args: Any, **kwargs: Any):
|
393
|
-
super().__init__(*args, name=name, description=description, arguments=arguments, return_type=return_type_str, **kwargs)
|
394
|
-
self._func = func
|
395
|
-
|
396
|
-
if is_async:
|
397
|
-
async def async_execute(self, **kwargs: Any) -> str:
|
398
|
-
result = await self._func(**kwargs)
|
399
|
-
return str(result)
|
400
|
-
else:
|
401
|
-
def execute(self, **kwargs: Any) -> str:
|
402
|
-
result = self._func(**kwargs)
|
403
|
-
return str(result)
|
404
|
-
|
405
|
-
return GeneratedTool()
|
406
|
-
|
407
|
-
if __name__ == "__main__":
|
408
|
-
# Basic tool with argument
|
409
|
-
tool = Tool(
|
410
|
-
name="my_tool",
|
411
|
-
description="A simple tool",
|
412
|
-
arguments=[ToolArgument(name="arg1", arg_type="string")]
|
413
|
-
)
|
414
|
-
print("Basic Tool Markdown:")
|
415
|
-
print(tool.to_markdown())
|
416
|
-
print("Basic Tool Docstring:")
|
417
|
-
print(tool.to_docstring())
|
418
|
-
print()
|
419
|
-
|
420
|
-
# Tool with injectable field (undefined)
|
421
|
-
class MyTool(Tool):
|
422
|
-
field1: str | None = Field(default=None, description="Field 1 description")
|
423
|
-
|
424
|
-
tool_with_fields = MyTool(
|
425
|
-
name="my_tool1",
|
426
|
-
description="A simple tool with a field",
|
427
|
-
arguments=[ToolArgument(name="field1", arg_type="string")]
|
428
|
-
)
|
429
|
-
print("Tool with Undefined Field Markdown:")
|
430
|
-
print(tool_with_fields.to_markdown())
|
431
|
-
print("Injectable Properties (should be empty):", tool_with_fields.get_injectable_properties_in_execution())
|
432
|
-
print("Tool with Undefined Field Docstring:")
|
433
|
-
print(tool_with_fields.to_docstring())
|
434
|
-
print()
|
435
|
-
|
436
|
-
# Tool with defined injectable field
|
437
|
-
tool_with_fields_defined = MyTool(
|
438
|
-
name="my_tool2",
|
439
|
-
description="A simple tool with a defined field",
|
440
|
-
field1="field1_value",
|
441
|
-
arguments=[ToolArgument(name="field1", arg_type="string")]
|
442
|
-
)
|
443
|
-
print("Tool with Defined Field Markdown:")
|
444
|
-
print(tool_with_fields_defined.to_markdown())
|
445
|
-
print("Injectable Properties (should include field1):", tool_with_fields_defined.get_injectable_properties_in_execution())
|
446
|
-
print("Tool with Defined Field Docstring:")
|
447
|
-
print(tool_with_fields_defined.to_docstring())
|
448
|
-
print()
|
449
|
-
|
450
|
-
# Test create_tool with synchronous function
|
451
|
-
def add(a: int, b: int = 0) -> int:
|
452
|
-
"""Add two numbers.
|
453
|
-
|
454
|
-
Args:
|
455
|
-
a: First number.
|
456
|
-
b: Second number (optional).
|
457
|
-
"""
|
458
|
-
return a + b
|
459
|
-
|
460
|
-
sync_tool = create_tool(add)
|
461
|
-
print("Synchronous Tool Markdown:")
|
462
|
-
print(sync_tool.to_markdown())
|
463
|
-
print("Synchronous Tool Docstring:")
|
464
|
-
print(sync_tool.to_docstring())
|
465
|
-
print("Execution result:", sync_tool.execute(a=5, b=3))
|
466
|
-
print()
|
467
|
-
|
468
|
-
# Test create_tool with asynchronous function
|
469
|
-
async def greet(name: str) -> str:
|
470
|
-
"""Greet a person.
|
471
|
-
|
472
|
-
Args:
|
473
|
-
name: Name of the person.
|
474
|
-
"""
|
475
|
-
await asyncio.sleep(0.1) # Simulate async work
|
476
|
-
return f"Hello, {name}"
|
477
|
-
|
478
|
-
async_tool = create_tool(greet)
|
479
|
-
print("Asynchronous Tool Markdown:")
|
480
|
-
print(async_tool.to_markdown())
|
481
|
-
print("Asynchronous Tool Docstring:")
|
482
|
-
print(async_tool.to_docstring())
|
483
|
-
print("Execution result:", asyncio.run(async_tool.async_execute(name="Alice")))
|
484
|
-
print()
|
485
|
-
|
486
|
-
# Comprehensive tool for to_docstring demonstration with custom return type
|
487
|
-
docstring_tool = Tool(
|
488
|
-
name="sample_tool",
|
489
|
-
description="A sample tool for testing docstring generation.",
|
490
|
-
arguments=[
|
491
|
-
ToolArgument(name="x", arg_type="int", description="The first number", required=True),
|
492
|
-
ToolArgument(name="y", arg_type="float", description="The second number", default="0.0", example="1.5"),
|
493
|
-
ToolArgument(name="verbose", arg_type="boolean", description="Print extra info", default="False")
|
494
|
-
],
|
495
|
-
return_type="int" # Custom return type
|
496
|
-
)
|
497
|
-
print("Comprehensive Tool Markdown:")
|
498
|
-
print(docstring_tool.to_markdown())
|
499
|
-
print("Comprehensive Tool Docstring with Custom Return Type:")
|
500
|
-
print(docstring_tool.to_docstring())
|
1
|
+
from quantalogic_toolbox.tool import Tool, ToolArgument, create_tool
|
2
|
+
|
3
|
+
__all__ = [
|
4
|
+
"ToolArgument",
|
5
|
+
"Tool",
|
6
|
+
"create_tool",
|
7
|
+
]
|
8
|
+
|