quantalogic 0.2.0__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/__init__.py +20 -0
- quantalogic/agent.py +638 -0
- quantalogic/agent_config.py +138 -0
- quantalogic/coding_agent.py +83 -0
- quantalogic/event_emitter.py +223 -0
- quantalogic/generative_model.py +226 -0
- quantalogic/interactive_text_editor.py +190 -0
- quantalogic/main.py +185 -0
- quantalogic/memory.py +217 -0
- quantalogic/model_names.py +19 -0
- quantalogic/print_event.py +66 -0
- quantalogic/prompts.py +99 -0
- quantalogic/server/__init__.py +3 -0
- quantalogic/server/agent_server.py +633 -0
- quantalogic/server/models.py +60 -0
- quantalogic/server/routes.py +117 -0
- quantalogic/server/state.py +199 -0
- quantalogic/server/static/js/event_visualizer.js +430 -0
- quantalogic/server/static/js/quantalogic.js +571 -0
- quantalogic/server/templates/index.html +134 -0
- quantalogic/tool_manager.py +68 -0
- quantalogic/tools/__init__.py +46 -0
- quantalogic/tools/agent_tool.py +88 -0
- quantalogic/tools/download_http_file_tool.py +64 -0
- quantalogic/tools/edit_whole_content_tool.py +70 -0
- quantalogic/tools/elixir_tool.py +240 -0
- quantalogic/tools/execute_bash_command_tool.py +116 -0
- quantalogic/tools/input_question_tool.py +57 -0
- quantalogic/tools/language_handlers/__init__.py +21 -0
- quantalogic/tools/language_handlers/c_handler.py +33 -0
- quantalogic/tools/language_handlers/cpp_handler.py +33 -0
- quantalogic/tools/language_handlers/go_handler.py +33 -0
- quantalogic/tools/language_handlers/java_handler.py +37 -0
- quantalogic/tools/language_handlers/javascript_handler.py +42 -0
- quantalogic/tools/language_handlers/python_handler.py +29 -0
- quantalogic/tools/language_handlers/rust_handler.py +33 -0
- quantalogic/tools/language_handlers/scala_handler.py +33 -0
- quantalogic/tools/language_handlers/typescript_handler.py +42 -0
- quantalogic/tools/list_directory_tool.py +123 -0
- quantalogic/tools/llm_tool.py +119 -0
- quantalogic/tools/markitdown_tool.py +105 -0
- quantalogic/tools/nodejs_tool.py +515 -0
- quantalogic/tools/python_tool.py +469 -0
- quantalogic/tools/read_file_block_tool.py +140 -0
- quantalogic/tools/read_file_tool.py +79 -0
- quantalogic/tools/replace_in_file_tool.py +300 -0
- quantalogic/tools/ripgrep_tool.py +353 -0
- quantalogic/tools/search_definition_names.py +419 -0
- quantalogic/tools/task_complete_tool.py +35 -0
- quantalogic/tools/tool.py +146 -0
- quantalogic/tools/unified_diff_tool.py +387 -0
- quantalogic/tools/write_file_tool.py +97 -0
- quantalogic/utils/__init__.py +17 -0
- quantalogic/utils/ask_user_validation.py +12 -0
- quantalogic/utils/download_http_file.py +77 -0
- quantalogic/utils/get_coding_environment.py +15 -0
- quantalogic/utils/get_environment.py +26 -0
- quantalogic/utils/get_quantalogic_rules_content.py +19 -0
- quantalogic/utils/git_ls.py +121 -0
- quantalogic/utils/read_file.py +54 -0
- quantalogic/utils/read_http_text_content.py +101 -0
- quantalogic/xml_parser.py +242 -0
- quantalogic/xml_tool_parser.py +99 -0
- quantalogic-0.2.0.dist-info/LICENSE +201 -0
- quantalogic-0.2.0.dist-info/METADATA +1034 -0
- quantalogic-0.2.0.dist-info/RECORD +68 -0
- quantalogic-0.2.0.dist-info/WHEEL +4 -0
- quantalogic-0.2.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,419 @@
|
|
1
|
+
"""Tool for searching definition names in a directory using Tree-sitter."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import os
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Dict, List, Union
|
7
|
+
|
8
|
+
from pathspec import PathSpec
|
9
|
+
from tree_sitter import Parser
|
10
|
+
|
11
|
+
from quantalogic.tools.language_handlers.c_handler import CLanguageHandler
|
12
|
+
from quantalogic.tools.language_handlers.cpp_handler import CppLanguageHandler
|
13
|
+
from quantalogic.tools.language_handlers.go_handler import GoLanguageHandler
|
14
|
+
from quantalogic.tools.language_handlers.java_handler import JavaLanguageHandler
|
15
|
+
from quantalogic.tools.language_handlers.javascript_handler import JavaScriptLanguageHandler
|
16
|
+
from quantalogic.tools.language_handlers.python_handler import PythonLanguageHandler
|
17
|
+
from quantalogic.tools.language_handlers.rust_handler import RustLanguageHandler
|
18
|
+
from quantalogic.tools.language_handlers.scala_handler import ScalaLanguageHandler
|
19
|
+
from quantalogic.tools.language_handlers.typescript_handler import TypeScriptLanguageHandler
|
20
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
21
|
+
|
22
|
+
# Configure logging
|
23
|
+
logging.basicConfig(level=logging.INFO)
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
|
26
|
+
|
27
|
+
class SearchDefinitionNames(Tool):
|
28
|
+
"""Tool for searching definition names in a directory using Tree-sitter.
|
29
|
+
|
30
|
+
Supports searching for:
|
31
|
+
- Functions (including async functions)
|
32
|
+
- Classes
|
33
|
+
- Methods
|
34
|
+
- Class variables
|
35
|
+
- JavaScript functions and classes
|
36
|
+
"""
|
37
|
+
|
38
|
+
name: str = "search_definition_names_tool"
|
39
|
+
description: str = (
|
40
|
+
"Searches for definition names (classes, functions, methods) in a directory using Tree-sitter. "
|
41
|
+
"Very useful to locate quickly code locations of classes, functions, methods in large projects."
|
42
|
+
"Returns the list of definition names grouped by file name, with line numbers. "
|
43
|
+
)
|
44
|
+
arguments: list = [
|
45
|
+
ToolArgument(
|
46
|
+
name="directory_path",
|
47
|
+
arg_type="string",
|
48
|
+
description="The path to the directory to search in.",
|
49
|
+
required=True,
|
50
|
+
example="./path/to",
|
51
|
+
),
|
52
|
+
ToolArgument(
|
53
|
+
name="language_name",
|
54
|
+
arg_type="string",
|
55
|
+
description="The Tree-sitter language name (python, javascript, typescript, java, scala, go, rust, c, cpp).",
|
56
|
+
required=True,
|
57
|
+
example="python",
|
58
|
+
),
|
59
|
+
ToolArgument(
|
60
|
+
name="file_pattern",
|
61
|
+
arg_type="string",
|
62
|
+
description="Optional glob pattern to filter files (default: '*').",
|
63
|
+
required=False,
|
64
|
+
example="**/*.py",
|
65
|
+
),
|
66
|
+
]
|
67
|
+
|
68
|
+
def _get_language_handler(self, language_name: str):
|
69
|
+
"""Returns a language-specific handler based on the language name."""
|
70
|
+
if language_name == "python":
|
71
|
+
return PythonLanguageHandler()
|
72
|
+
elif language_name == "javascript":
|
73
|
+
return JavaScriptLanguageHandler()
|
74
|
+
elif language_name == "typescript":
|
75
|
+
return TypeScriptLanguageHandler()
|
76
|
+
elif language_name == "java":
|
77
|
+
return JavaLanguageHandler()
|
78
|
+
elif language_name == "scala":
|
79
|
+
return ScalaLanguageHandler()
|
80
|
+
elif language_name == "go":
|
81
|
+
return GoLanguageHandler()
|
82
|
+
elif language_name == "rust":
|
83
|
+
return RustLanguageHandler()
|
84
|
+
elif language_name == "c":
|
85
|
+
return CLanguageHandler()
|
86
|
+
elif language_name == "cpp":
|
87
|
+
return CppLanguageHandler()
|
88
|
+
else:
|
89
|
+
raise ValueError(f"Unsupported language: {language_name}")
|
90
|
+
|
91
|
+
def execute(
|
92
|
+
self, directory_path: str, language_name: str, file_pattern: str = "*", output_format: str = "text"
|
93
|
+
) -> Union[str, Dict]:
|
94
|
+
"""Searches for definition names in a directory using Tree-sitter.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
directory_path (str): The path to the directory to search in.
|
98
|
+
language_name (str): The Tree-sitter language name.
|
99
|
+
file_pattern (str): Optional glob pattern to filter files (default: '*').
|
100
|
+
output_format (str): Output format ('text', 'json', 'markdown').
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
Union[str, Dict]: The search results in the specified format.
|
104
|
+
"""
|
105
|
+
try:
|
106
|
+
# Set up Tree-sitter based on language
|
107
|
+
language_handler = self._get_language_handler(language_name)
|
108
|
+
parser = Parser(language_handler.get_language())
|
109
|
+
|
110
|
+
# Find files matching the pattern
|
111
|
+
directory_path = os.path.expanduser(directory_path)
|
112
|
+
gitignore_spec = self._load_gitignore_spec(Path(directory_path))
|
113
|
+
files = list(Path(directory_path).rglob(file_pattern))
|
114
|
+
|
115
|
+
results = []
|
116
|
+
|
117
|
+
for file_path in files:
|
118
|
+
# Skip files matching .gitignore patterns
|
119
|
+
if gitignore_spec.match_file(file_path):
|
120
|
+
continue
|
121
|
+
|
122
|
+
if file_path.is_file():
|
123
|
+
try:
|
124
|
+
with open(file_path, "rb") as f:
|
125
|
+
source_code = f.read()
|
126
|
+
|
127
|
+
tree = parser.parse(source_code)
|
128
|
+
root_node = tree.root_node
|
129
|
+
|
130
|
+
# Validate root node using language handler
|
131
|
+
if not language_handler.validate_root_node(root_node):
|
132
|
+
continue
|
133
|
+
|
134
|
+
definitions = self._extract_definitions(root_node, language_name)
|
135
|
+
|
136
|
+
if definitions:
|
137
|
+
results.append({"file_path": str(file_path), "definitions": definitions})
|
138
|
+
|
139
|
+
except Exception as e:
|
140
|
+
logger.warning(f"Error processing file {file_path}: {str(e)}")
|
141
|
+
continue
|
142
|
+
|
143
|
+
return self._format_results(results, directory_path, language_name, file_pattern, output_format)
|
144
|
+
|
145
|
+
except Exception as e:
|
146
|
+
logger.error(f"Error during search: {str(e)}")
|
147
|
+
return {"error": str(e)}
|
148
|
+
|
149
|
+
def _load_gitignore_spec(self, path: Path) -> PathSpec:
|
150
|
+
"""Load .gitignore patterns from directory and all parent directories."""
|
151
|
+
from pathspec import PathSpec
|
152
|
+
from pathspec.patterns import GitWildMatchPattern
|
153
|
+
|
154
|
+
ignore_patterns = []
|
155
|
+
current = path.absolute()
|
156
|
+
|
157
|
+
# Traverse up the directory tree
|
158
|
+
while current != current.parent: # Stop at root
|
159
|
+
gitignore_path = current / ".gitignore"
|
160
|
+
if gitignore_path.exists():
|
161
|
+
with open(gitignore_path) as f:
|
162
|
+
# Filter out empty lines and comments
|
163
|
+
patterns = [line.strip() for line in f.readlines() if line.strip() and not line.startswith("#")]
|
164
|
+
ignore_patterns.extend(patterns)
|
165
|
+
current = current.parent
|
166
|
+
|
167
|
+
return PathSpec.from_lines(GitWildMatchPattern, ignore_patterns)
|
168
|
+
|
169
|
+
def _extract_definitions(self, root_node, language_name: str) -> Dict:
|
170
|
+
"""Extracts definitions from a Tree-sitter syntax tree node.
|
171
|
+
|
172
|
+
Args:
|
173
|
+
root_node: The root node of the syntax tree.
|
174
|
+
language_name: The language being parsed.
|
175
|
+
|
176
|
+
Returns:
|
177
|
+
Dict: Dictionary containing classes with their methods and variables,
|
178
|
+
and standalone functions.
|
179
|
+
"""
|
180
|
+
definitions = {"classes": {}, "functions": []}
|
181
|
+
|
182
|
+
current_class = None
|
183
|
+
language_handler = self._get_language_handler(language_name)
|
184
|
+
|
185
|
+
def process_node(node):
|
186
|
+
nonlocal current_class
|
187
|
+
|
188
|
+
# Delegate node processing to language handler
|
189
|
+
node_type = language_handler.process_node(
|
190
|
+
node,
|
191
|
+
current_class,
|
192
|
+
definitions,
|
193
|
+
self._process_method,
|
194
|
+
self._process_function,
|
195
|
+
self._process_class,
|
196
|
+
self._process_class_variable,
|
197
|
+
)
|
198
|
+
|
199
|
+
# Update current_class if processing a class node
|
200
|
+
if node_type == "class":
|
201
|
+
current_class = self._process_class(node)
|
202
|
+
elif node_type == "end_class":
|
203
|
+
current_class = None
|
204
|
+
|
205
|
+
# Recursively process child nodes
|
206
|
+
for child in node.children:
|
207
|
+
process_node(child)
|
208
|
+
|
209
|
+
process_node(root_node)
|
210
|
+
return definitions
|
211
|
+
|
212
|
+
def _process_function(self, node, definitions):
|
213
|
+
"""Process a function definition node."""
|
214
|
+
name_node = node.child_by_field_name("name")
|
215
|
+
if name_node and name_node.type == "identifier":
|
216
|
+
definition_name = name_node.text.decode("utf-8")
|
217
|
+
start_line = node.start_point[0] + 1
|
218
|
+
end_line = node.end_point[0] + 1
|
219
|
+
|
220
|
+
# Extract function signature
|
221
|
+
parameters_node = node.child_by_field_name("parameters")
|
222
|
+
if parameters_node:
|
223
|
+
signature = f"{definition_name}{parameters_node.text.decode('utf-8')}"
|
224
|
+
definitions.append((signature, start_line, end_line))
|
225
|
+
else:
|
226
|
+
definitions.append((definition_name, start_line, end_line))
|
227
|
+
|
228
|
+
def _process_class(self, node) -> tuple:
|
229
|
+
"""Process a class definition node.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
node: The class definition node.
|
233
|
+
|
234
|
+
Returns:
|
235
|
+
tuple: (class_name, start_line, end_line)
|
236
|
+
"""
|
237
|
+
name_node = node.child_by_field_name("name")
|
238
|
+
if name_node and name_node.type == "identifier":
|
239
|
+
return (name_node.text.decode("utf-8"), node.start_point[0] + 1, node.end_point[0] + 1)
|
240
|
+
return ("", 0, 0)
|
241
|
+
|
242
|
+
def _process_method(self, node, definitions):
|
243
|
+
"""Process a method definition node."""
|
244
|
+
name_node = node.child_by_field_name("name")
|
245
|
+
if name_node and name_node.type == "identifier":
|
246
|
+
definition_name = name_node.text.decode("utf-8")
|
247
|
+
start_line = node.start_point[0] + 1
|
248
|
+
end_line = node.end_point[0] + 1
|
249
|
+
definitions.append((definition_name, start_line, end_line))
|
250
|
+
|
251
|
+
def _process_class_variable(self, node, definitions):
|
252
|
+
"""Process a class variable definition node."""
|
253
|
+
name_node = node.child_by_field_name("name")
|
254
|
+
if name_node and name_node.type == "identifier":
|
255
|
+
definition_name = name_node.text.decode("utf-8")
|
256
|
+
line_number = name_node.start_point[0] + 1
|
257
|
+
definitions.append((definition_name, line_number))
|
258
|
+
|
259
|
+
def _format_results(
|
260
|
+
self,
|
261
|
+
results: List[Dict],
|
262
|
+
directory_path: str,
|
263
|
+
language_name: str,
|
264
|
+
file_pattern: str,
|
265
|
+
output_format: str = "text",
|
266
|
+
) -> Union[str, Dict]:
|
267
|
+
"""Formats the search results in the specified format.
|
268
|
+
|
269
|
+
Args:
|
270
|
+
results: The search results to format.
|
271
|
+
directory_path: The directory that was searched.
|
272
|
+
language_name: The language that was searched.
|
273
|
+
file_pattern: The file pattern that was used.
|
274
|
+
output_format: The desired output format.
|
275
|
+
|
276
|
+
Returns:
|
277
|
+
Union[str, Dict]: The formatted results.
|
278
|
+
"""
|
279
|
+
if output_format == "json":
|
280
|
+
formatted_results = {
|
281
|
+
"directory": directory_path,
|
282
|
+
"language": language_name,
|
283
|
+
"file_pattern": file_pattern,
|
284
|
+
"results": [],
|
285
|
+
}
|
286
|
+
|
287
|
+
for result in results:
|
288
|
+
file_result = {
|
289
|
+
"file_path": result["file_path"],
|
290
|
+
"classes": [],
|
291
|
+
"functions": [
|
292
|
+
{"name": name, "start_line": start, "end_line": end}
|
293
|
+
for name, start, end in result["definitions"]["functions"]
|
294
|
+
],
|
295
|
+
}
|
296
|
+
|
297
|
+
for class_name, class_info in result["definitions"]["classes"].items():
|
298
|
+
class_data = {
|
299
|
+
"name": class_name,
|
300
|
+
"start_line": class_info["line"][0],
|
301
|
+
"end_line": class_info["line"][1],
|
302
|
+
"methods": [
|
303
|
+
{"name": name, "start_line": start, "end_line": end}
|
304
|
+
for name, start, end in class_info["methods"]
|
305
|
+
],
|
306
|
+
"variables": [
|
307
|
+
{"name": name, "start_line": start, "end_line": end}
|
308
|
+
for name, start, end in class_info["variables"]
|
309
|
+
],
|
310
|
+
}
|
311
|
+
file_result["classes"].append(class_data)
|
312
|
+
|
313
|
+
formatted_results["results"].append(file_result)
|
314
|
+
|
315
|
+
return formatted_results
|
316
|
+
|
317
|
+
elif output_format == "markdown":
|
318
|
+
markdown = "# Search Results\n\n"
|
319
|
+
markdown += f"- **Directory**: `{directory_path}`\n"
|
320
|
+
markdown += f"- **Language**: `{language_name}`\n"
|
321
|
+
markdown += f"- **File Pattern**: `{file_pattern}`\n\n"
|
322
|
+
|
323
|
+
for result in results:
|
324
|
+
markdown += f"## File: {result['file_path']}\n"
|
325
|
+
|
326
|
+
# Add standalone functions
|
327
|
+
if result["definitions"]["functions"]:
|
328
|
+
markdown += "### Functions\n"
|
329
|
+
for func, start, end in result["definitions"]["functions"]:
|
330
|
+
markdown += f"- `{func}` (lines {start}-{end})\n"
|
331
|
+
markdown += "\n"
|
332
|
+
|
333
|
+
# Add classes with their methods and variables
|
334
|
+
for class_name, class_info in result["definitions"]["classes"].items():
|
335
|
+
markdown += f"### Class: {class_name} (lines {class_info['line'][0]}-{class_info['line'][1]})\n"
|
336
|
+
|
337
|
+
if class_info["methods"]:
|
338
|
+
markdown += "#### Methods\n"
|
339
|
+
for method, start, end in class_info["methods"]:
|
340
|
+
markdown += f"- `{method}` (lines {start}-{end})\n"
|
341
|
+
|
342
|
+
if class_info["variables"]:
|
343
|
+
markdown += "#### Variables\n"
|
344
|
+
for var, start, end in class_info["variables"]:
|
345
|
+
markdown += f"- `{var}` (lines {start}-{end})\n"
|
346
|
+
|
347
|
+
markdown += "\n"
|
348
|
+
|
349
|
+
return markdown
|
350
|
+
|
351
|
+
else:
|
352
|
+
# Default to text format
|
353
|
+
text = "Search Results\n"
|
354
|
+
text += "==============\n"
|
355
|
+
text += f"Directory: {directory_path}\n"
|
356
|
+
text += f"Language: {language_name}\n"
|
357
|
+
text += f"File Pattern: {file_pattern}\n\n"
|
358
|
+
|
359
|
+
for result in results:
|
360
|
+
text += f"File: {result['file_path']}\n"
|
361
|
+
|
362
|
+
# Add standalone functions
|
363
|
+
if result["definitions"]["functions"]:
|
364
|
+
text += "Functions:\n"
|
365
|
+
for func, start, end in result["definitions"]["functions"]:
|
366
|
+
text += f" - {func} (lines {start}-{end})\n"
|
367
|
+
text += "\n"
|
368
|
+
|
369
|
+
# Add classes with their methods and variables
|
370
|
+
for class_name, class_info in result["definitions"]["classes"].items():
|
371
|
+
text += f"Class: {class_name} (lines {class_info['line'][0]}-{class_info['line'][1]})\n"
|
372
|
+
|
373
|
+
if class_info["methods"]:
|
374
|
+
text += " Methods:\n"
|
375
|
+
for method, start, end in class_info["methods"]:
|
376
|
+
text += f" - {method} (lines {start}-{end})\n"
|
377
|
+
|
378
|
+
if class_info["variables"]:
|
379
|
+
text += " Variables:\n"
|
380
|
+
for var, start, end in class_info["variables"]:
|
381
|
+
text += f" - {var} (lines {start}-{end})\n"
|
382
|
+
|
383
|
+
text += "\n"
|
384
|
+
|
385
|
+
return text
|
386
|
+
|
387
|
+
|
388
|
+
if __name__ == "__main__":
|
389
|
+
tool = SearchDefinitionNames()
|
390
|
+
print(tool.to_markdown())
|
391
|
+
|
392
|
+
# Example usage with different output formats
|
393
|
+
result_text = tool.execute(
|
394
|
+
directory_path="./quantalogic", language_name="python", file_pattern="**/*.py", output_format="text"
|
395
|
+
)
|
396
|
+
# print(result_text)
|
397
|
+
|
398
|
+
result_json = tool.execute(
|
399
|
+
directory_path="./quantalogic", language_name="python", file_pattern="**/*.py", output_format="json"
|
400
|
+
)
|
401
|
+
# print(result_json)
|
402
|
+
|
403
|
+
result_markdown = tool.execute(
|
404
|
+
directory_path="./quantalogic", language_name="python", file_pattern="**/*.py", output_format="markdown"
|
405
|
+
)
|
406
|
+
print(result_markdown)
|
407
|
+
|
408
|
+
result_markdown = tool.execute(
|
409
|
+
directory_path=".", language_name="javascript", file_pattern="**/*.js", output_format="markdown"
|
410
|
+
)
|
411
|
+
print(result_markdown)
|
412
|
+
|
413
|
+
result_markdown = tool.execute(
|
414
|
+
directory_path=".",
|
415
|
+
language_name="javascript",
|
416
|
+
file_pattern="./quantalogic/server/static/js/quantalogic.js",
|
417
|
+
output_format="markdown",
|
418
|
+
)
|
419
|
+
print(result_markdown)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"""Tool for reading a file and returning its content."""
|
2
|
+
|
3
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
4
|
+
|
5
|
+
|
6
|
+
class TaskCompleteTool(Tool):
|
7
|
+
"""Tool to reply answer to the user."""
|
8
|
+
|
9
|
+
name: str = "task_complete"
|
10
|
+
description: str = "Replies to the user when the task is completed."
|
11
|
+
arguments: list = [
|
12
|
+
ToolArgument(
|
13
|
+
name="answer",
|
14
|
+
arg_type="string",
|
15
|
+
description="The answer to the user. Use interpolation if possible example $var1$.",
|
16
|
+
required=True,
|
17
|
+
example="The answer to the meaning of life",
|
18
|
+
),
|
19
|
+
]
|
20
|
+
|
21
|
+
def execute(self, answer: str) -> str:
|
22
|
+
"""Attempts to reply to the user.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
answer (str): The answer to the user.
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
str: The answer to the user.
|
29
|
+
"""
|
30
|
+
return answer
|
31
|
+
|
32
|
+
|
33
|
+
if __name__ == "__main__":
|
34
|
+
tool = TaskCompleteTool()
|
35
|
+
print(tool.to_markdown())
|
@@ -0,0 +1,146 @@
|
|
1
|
+
"""Module for defining tool arguments and base tool classes.
|
2
|
+
|
3
|
+
This module provides base classes and data models for creating configurable tools
|
4
|
+
with type-validated arguments and execution methods.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Any, Literal
|
8
|
+
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
10
|
+
|
11
|
+
|
12
|
+
class ToolArgument(BaseModel):
|
13
|
+
"""Represents an argument for a tool with validation and description.
|
14
|
+
|
15
|
+
Attributes:
|
16
|
+
name: The name of the argument.
|
17
|
+
arg_type: The type of the argument (integer, float, boolean).
|
18
|
+
description: Optional description of the argument.
|
19
|
+
required: Indicates if the argument is mandatory.
|
20
|
+
default: Optional default value for the argument.
|
21
|
+
example: Optional example value to illustrate the argument's usage.
|
22
|
+
"""
|
23
|
+
|
24
|
+
name: str = Field(..., description="The name of the argument.")
|
25
|
+
arg_type: Literal["string", "int", "float", "boolean"] = Field(
|
26
|
+
..., description="The type of the argument. Must be one of: string, integer, float, boolean."
|
27
|
+
)
|
28
|
+
description: str | None = Field(None, description="A brief description of the argument.")
|
29
|
+
required: bool = Field(default=False, description="Indicates if the argument is required.")
|
30
|
+
default: str | None = Field(None, description="The default value for the argument.")
|
31
|
+
example: str | None = Field(None, description="An example value to illustrate the argument's usage.")
|
32
|
+
need_validation: bool = Field(default=False, description="Indicates if the argument needs validation.")
|
33
|
+
|
34
|
+
|
35
|
+
class ToolDefinition(BaseModel):
|
36
|
+
"""Base class for defining tool configurations without execution logic.
|
37
|
+
|
38
|
+
Attributes:
|
39
|
+
name: Unique name of the tool.
|
40
|
+
description: Brief description of the tool's functionality.
|
41
|
+
arguments: List of arguments the tool accepts.
|
42
|
+
need_validation: Flag to indicate if tool requires validation.
|
43
|
+
"""
|
44
|
+
|
45
|
+
model_config = ConfigDict(extra="forbid", validate_assignment=True)
|
46
|
+
|
47
|
+
name: str = Field(..., description="The unique name of the tool.")
|
48
|
+
description: str = Field(..., description="A brief description of what the tool does.")
|
49
|
+
arguments: list[ToolArgument] = Field(default_factory=list, description="A list of arguments the tool accepts.")
|
50
|
+
need_validation: bool = Field(default=False, description="Indicates if the tool needs validation.")
|
51
|
+
|
52
|
+
def to_json(self) -> str:
|
53
|
+
"""Convert the tool to a JSON string representation.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
A JSON string of the tool's configuration.
|
57
|
+
"""
|
58
|
+
return self.model_dump_json()
|
59
|
+
|
60
|
+
def to_markdown(self) -> str:
|
61
|
+
"""Create a comprehensive Markdown representation of the tool.
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
A detailed Markdown string representing the tool's configuration and usage.
|
65
|
+
"""
|
66
|
+
# Tool name and description
|
67
|
+
markdown = f"`{self.name}`:\n"
|
68
|
+
markdown += f"- **Description**: {self.description}\n\n"
|
69
|
+
|
70
|
+
# Parameters section
|
71
|
+
if self.arguments:
|
72
|
+
markdown += "- **Parameters**:\n"
|
73
|
+
for arg in self.arguments:
|
74
|
+
required_status = "required" if arg.required else "optional"
|
75
|
+
# Prioritize example, then default, then create a generic description
|
76
|
+
value_info = ""
|
77
|
+
if arg.example is not None:
|
78
|
+
value_info = f" (example: `{arg.example}`)"
|
79
|
+
elif arg.default is not None:
|
80
|
+
value_info = f" (default: `{arg.default}`)"
|
81
|
+
|
82
|
+
markdown += (
|
83
|
+
f" - `{arg.name}`: "
|
84
|
+
f"({required_status}{value_info})\n"
|
85
|
+
f" {arg.description or 'No description provided.'}\n"
|
86
|
+
)
|
87
|
+
markdown += "\n"
|
88
|
+
|
89
|
+
# Usage section with XML-style example
|
90
|
+
markdown += "**Usage**:\n"
|
91
|
+
markdown += "```xml\n"
|
92
|
+
markdown += f"<{self.name}>\n"
|
93
|
+
|
94
|
+
# Generate example parameters
|
95
|
+
for arg in self.arguments:
|
96
|
+
# Prioritize example, then default, then create a generic example
|
97
|
+
example_value = arg.example or arg.default or f"Your {arg.name} here"
|
98
|
+
markdown += f" <{arg.name}>{example_value}</{arg.name}>\n"
|
99
|
+
|
100
|
+
markdown += f"</{self.name}>\n"
|
101
|
+
markdown += "```\n"
|
102
|
+
|
103
|
+
return markdown
|
104
|
+
|
105
|
+
|
106
|
+
class Tool(ToolDefinition):
|
107
|
+
"""Extended class for tools with execution capabilities.
|
108
|
+
|
109
|
+
Inherits from ToolDefinition and adds execution functionality.
|
110
|
+
"""
|
111
|
+
|
112
|
+
@field_validator("arguments", mode="before")
|
113
|
+
@classmethod
|
114
|
+
def validate_arguments(cls, v: Any) -> list[ToolArgument]:
|
115
|
+
"""Validate and convert arguments to ToolArgument instances.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
v: Input arguments to validate.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
A list of validated ToolArgument instances.
|
122
|
+
"""
|
123
|
+
if isinstance(v, list):
|
124
|
+
return [
|
125
|
+
ToolArgument(**arg)
|
126
|
+
if isinstance(arg, dict)
|
127
|
+
else arg
|
128
|
+
if isinstance(arg, ToolArgument)
|
129
|
+
else ToolArgument(name=str(arg), type=type(arg).__name__)
|
130
|
+
for arg in v
|
131
|
+
]
|
132
|
+
return []
|
133
|
+
|
134
|
+
def execute(self, **kwargs) -> str:
|
135
|
+
"""Execute the tool with provided arguments.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
**kwargs: Keyword arguments for tool execution.
|
139
|
+
|
140
|
+
Raises:
|
141
|
+
NotImplementedError: If the method is not implemented by a subclass.
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
A string representing the result of tool execution.
|
145
|
+
"""
|
146
|
+
raise NotImplementedError("This method should be implemented by subclasses.")
|