quantalogic 0.33.4__py3-none-any.whl → 0.40.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 +0 -4
- quantalogic/agent.py +603 -362
- quantalogic/agent_config.py +260 -28
- quantalogic/agent_factory.py +43 -17
- quantalogic/coding_agent.py +20 -12
- quantalogic/config.py +7 -4
- quantalogic/console_print_events.py +4 -8
- quantalogic/console_print_token.py +2 -2
- quantalogic/docs_cli.py +15 -10
- quantalogic/event_emitter.py +258 -83
- quantalogic/flow/__init__.py +23 -0
- quantalogic/flow/flow.py +595 -0
- quantalogic/flow/flow_extractor.py +672 -0
- quantalogic/flow/flow_generator.py +89 -0
- quantalogic/flow/flow_manager.py +407 -0
- quantalogic/flow/flow_manager_schema.py +169 -0
- quantalogic/flow/flow_yaml.md +419 -0
- quantalogic/generative_model.py +109 -77
- quantalogic/get_model_info.py +6 -6
- quantalogic/interactive_text_editor.py +100 -73
- quantalogic/main.py +36 -23
- quantalogic/model_info_list.py +12 -0
- quantalogic/model_info_litellm.py +14 -14
- quantalogic/prompts.py +2 -1
- quantalogic/{llm.py → quantlitellm.py} +29 -39
- quantalogic/search_agent.py +4 -4
- quantalogic/server/models.py +4 -1
- quantalogic/task_file_reader.py +5 -5
- quantalogic/task_runner.py +21 -20
- quantalogic/tool_manager.py +10 -21
- quantalogic/tools/__init__.py +98 -68
- quantalogic/tools/composio/composio.py +416 -0
- quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
- quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
- quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
- quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
- quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
- quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
- quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
- quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
- quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
- quantalogic/tools/duckduckgo_search_tool.py +2 -4
- quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
- quantalogic/tools/finance/ccxt_tool.py +373 -0
- quantalogic/tools/finance/finance_llm_tool.py +387 -0
- quantalogic/tools/finance/google_finance.py +192 -0
- quantalogic/tools/finance/market_intelligence_tool.py +520 -0
- quantalogic/tools/finance/technical_analysis_tool.py +491 -0
- quantalogic/tools/finance/tradingview_tool.py +336 -0
- quantalogic/tools/finance/yahoo_finance.py +236 -0
- quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
- quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
- quantalogic/tools/git/clone_repo_tool.py +189 -0
- quantalogic/tools/git/git_operations_tool.py +532 -0
- quantalogic/tools/google_packages/google_news_tool.py +480 -0
- quantalogic/tools/grep_app_tool.py +123 -186
- quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
- quantalogic/tools/jinja_tool.py +6 -10
- quantalogic/tools/language_handlers/__init__.py +22 -9
- quantalogic/tools/list_directory_tool.py +131 -42
- quantalogic/tools/llm_tool.py +45 -15
- quantalogic/tools/llm_vision_tool.py +59 -7
- quantalogic/tools/markitdown_tool.py +17 -5
- quantalogic/tools/nasa_packages/models.py +47 -0
- quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
- quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
- quantalogic/tools/nasa_packages/services.py +82 -0
- quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
- quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
- quantalogic/tools/product_hunt/services.py +63 -0
- quantalogic/tools/rag_tool/__init__.py +48 -0
- quantalogic/tools/rag_tool/document_metadata.py +15 -0
- quantalogic/tools/rag_tool/query_response.py +20 -0
- quantalogic/tools/rag_tool/rag_tool.py +566 -0
- quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
- quantalogic/tools/read_html_tool.py +24 -38
- quantalogic/tools/replace_in_file_tool.py +10 -10
- quantalogic/tools/safe_python_interpreter_tool.py +10 -24
- quantalogic/tools/search_definition_names.py +2 -2
- quantalogic/tools/sequence_tool.py +14 -23
- quantalogic/tools/sql_query_tool.py +17 -19
- quantalogic/tools/tool.py +39 -15
- quantalogic/tools/unified_diff_tool.py +1 -1
- quantalogic/tools/utilities/csv_processor_tool.py +234 -0
- quantalogic/tools/utilities/download_file_tool.py +179 -0
- quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
- quantalogic/tools/utils/__init__.py +1 -4
- quantalogic/tools/utils/create_sample_database.py +24 -38
- quantalogic/tools/utils/generate_database_report.py +74 -82
- quantalogic/tools/wikipedia_search_tool.py +17 -21
- quantalogic/utils/ask_user_validation.py +1 -1
- quantalogic/utils/async_utils.py +35 -0
- quantalogic/utils/check_version.py +3 -5
- quantalogic/utils/get_all_models.py +2 -1
- quantalogic/utils/git_ls.py +21 -7
- quantalogic/utils/lm_studio_model_info.py +9 -7
- quantalogic/utils/python_interpreter.py +113 -43
- quantalogic/utils/xml_utility.py +178 -0
- quantalogic/version_check.py +1 -1
- quantalogic/welcome_message.py +7 -7
- quantalogic/xml_parser.py +0 -1
- {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/METADATA +44 -1
- quantalogic-0.40.0.dist-info/RECORD +148 -0
- quantalogic-0.33.4.dist-info/RECORD +0 -102
- {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/WHEEL +0 -0
- {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/entry_points.txt +0 -0
@@ -17,9 +17,11 @@ from quantalogic.tools.tool import Tool, ToolArgument
|
|
17
17
|
|
18
18
|
class ImageProvider(str, Enum):
|
19
19
|
"""Supported image generation providers."""
|
20
|
+
|
20
21
|
DALLE = "dall-e"
|
21
22
|
STABLE_DIFFUSION = "stable-diffusion"
|
22
23
|
|
24
|
+
|
23
25
|
PROVIDER_CONFIGS = {
|
24
26
|
ImageProvider.DALLE: {
|
25
27
|
"model_name": "dall-e-3",
|
@@ -28,14 +30,15 @@ PROVIDER_CONFIGS = {
|
|
28
30
|
"styles": ["vivid", "natural"],
|
29
31
|
},
|
30
32
|
ImageProvider.STABLE_DIFFUSION: {
|
31
|
-
#"model_name": "anthropic.claude-3-sonnet-20240229",
|
33
|
+
# "model_name": "anthropic.claude-3-sonnet-20240229",
|
32
34
|
"model_name": "amazon.titan-image-generator-v1",
|
33
35
|
"sizes": ["1024x1024"], # Bedrock SD supported size
|
34
36
|
"qualities": ["standard"], # SD quality is controlled by cfg_scale
|
35
|
-
"styles": ["none"], # Style is controlled through prompting
|
36
|
-
}
|
37
|
+
"styles": ["none"], # Style is controlled through prompting
|
38
|
+
},
|
37
39
|
}
|
38
40
|
|
41
|
+
|
39
42
|
class LLMImageGenerationTool(Tool):
|
40
43
|
"""Tool for generating images using DALL-E or Stable Diffusion."""
|
41
44
|
|
@@ -118,23 +121,31 @@ class LLMImageGenerationTool(Tool):
|
|
118
121
|
if self.generative_model is None:
|
119
122
|
self.generative_model = GenerativeModel(model=self.model_name)
|
120
123
|
logger.debug(f"Initialized LLMImageGenerationTool with model: {self.model_name}")
|
121
|
-
|
124
|
+
|
122
125
|
# Create output directory if it doesn't exist
|
123
126
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
124
127
|
|
125
128
|
def _validate_dalle_params(self, size: str, quality: str, style: str) -> None:
|
126
129
|
"""Validate DALL-E specific parameters."""
|
127
130
|
if size not in PROVIDER_CONFIGS[ImageProvider.DALLE]["sizes"]:
|
128
|
-
raise ValueError(
|
131
|
+
raise ValueError(
|
132
|
+
f"Invalid size for DALL-E. Must be one of: {PROVIDER_CONFIGS[ImageProvider.DALLE]['sizes']}"
|
133
|
+
)
|
129
134
|
if quality not in PROVIDER_CONFIGS[ImageProvider.DALLE]["qualities"]:
|
130
|
-
raise ValueError(
|
135
|
+
raise ValueError(
|
136
|
+
f"Invalid quality for DALL-E. Must be one of: {PROVIDER_CONFIGS[ImageProvider.DALLE]['qualities']}"
|
137
|
+
)
|
131
138
|
if style not in PROVIDER_CONFIGS[ImageProvider.DALLE]["styles"]:
|
132
|
-
raise ValueError(
|
139
|
+
raise ValueError(
|
140
|
+
f"Invalid style for DALL-E. Must be one of: {PROVIDER_CONFIGS[ImageProvider.DALLE]['styles']}"
|
141
|
+
)
|
133
142
|
|
134
143
|
def _validate_sd_params(self, size: str, cfg_scale: float) -> None:
|
135
144
|
"""Validate Stable Diffusion specific parameters."""
|
136
145
|
if size not in PROVIDER_CONFIGS[ImageProvider.STABLE_DIFFUSION]["sizes"]:
|
137
|
-
raise ValueError(
|
146
|
+
raise ValueError(
|
147
|
+
f"Invalid size for Stable Diffusion. Must be one of: {PROVIDER_CONFIGS[ImageProvider.STABLE_DIFFUSION]['sizes']}"
|
148
|
+
)
|
138
149
|
if not 1.0 <= cfg_scale <= 20.0:
|
139
150
|
raise ValueError("cfg_scale must be between 1.0 and 20.0")
|
140
151
|
|
@@ -144,12 +155,12 @@ class LLMImageGenerationTool(Tool):
|
|
144
155
|
try:
|
145
156
|
response = requests.get(image_url, timeout=30)
|
146
157
|
response.raise_for_status()
|
147
|
-
|
158
|
+
|
148
159
|
file_path = self.output_dir / filename
|
149
160
|
file_path.write_bytes(response.content)
|
150
161
|
logger.info(f"Image saved successfully at: {file_path}")
|
151
162
|
return file_path
|
152
|
-
|
163
|
+
|
153
164
|
except Exception as e:
|
154
165
|
logger.error(f"Error saving image: {e}")
|
155
166
|
raise
|
@@ -158,7 +169,7 @@ class LLMImageGenerationTool(Tool):
|
|
158
169
|
"""Save image metadata to JSON file."""
|
159
170
|
try:
|
160
171
|
metadata_path = self.output_dir / f"{metadata['filename']}.json"
|
161
|
-
with open(metadata_path,
|
172
|
+
with open(metadata_path, "w") as f:
|
162
173
|
json.dump(metadata, f, indent=2)
|
163
174
|
logger.info(f"Metadata saved successfully at: {metadata_path}")
|
164
175
|
except Exception as e:
|
@@ -176,7 +187,7 @@ class LLMImageGenerationTool(Tool):
|
|
176
187
|
cfg_scale: str = "7.5",
|
177
188
|
) -> str:
|
178
189
|
"""Execute the tool to generate an image based on the prompt.
|
179
|
-
|
190
|
+
|
180
191
|
Args:
|
181
192
|
prompt: Text description of the image to generate
|
182
193
|
provider: Provider to use (dall-e or stable-diffusion)
|
@@ -185,16 +196,18 @@ class LLMImageGenerationTool(Tool):
|
|
185
196
|
style: Style preference for DALL-E
|
186
197
|
negative_prompt: What to avoid in the image (Stable Diffusion only)
|
187
198
|
cfg_scale: Classifier Free Guidance scale (Stable Diffusion only)
|
188
|
-
|
199
|
+
|
189
200
|
Returns:
|
190
201
|
Path to the locally saved image
|
191
202
|
"""
|
192
203
|
try:
|
193
204
|
provider_enum = ImageProvider(provider.lower())
|
194
|
-
|
205
|
+
|
195
206
|
# Convert cfg_scale to float only if it's not empty and we're using Stable Diffusion
|
196
|
-
cfg_scale_float =
|
197
|
-
|
207
|
+
cfg_scale_float = (
|
208
|
+
float(cfg_scale) if cfg_scale and provider_enum == ImageProvider.STABLE_DIFFUSION else None
|
209
|
+
)
|
210
|
+
|
198
211
|
# Validate parameters based on provider
|
199
212
|
if provider_enum == ImageProvider.DALLE:
|
200
213
|
self._validate_dalle_params(size, quality, style)
|
@@ -203,7 +216,7 @@ class LLMImageGenerationTool(Tool):
|
|
203
216
|
"size": size,
|
204
217
|
"quality": quality,
|
205
218
|
"style": style,
|
206
|
-
"response_format": "url"
|
219
|
+
"response_format": "url",
|
207
220
|
}
|
208
221
|
else: # Stable Diffusion
|
209
222
|
if cfg_scale_float is None:
|
@@ -214,29 +227,26 @@ class LLMImageGenerationTool(Tool):
|
|
214
227
|
"negative_prompt": negative_prompt,
|
215
228
|
"cfg_scale": cfg_scale_float,
|
216
229
|
"size": size,
|
217
|
-
"response_format": "url"
|
230
|
+
"response_format": "url",
|
218
231
|
}
|
219
232
|
|
220
233
|
# Generate image
|
221
234
|
logger.info(f"Generating image with {provider} using params: {params}")
|
222
|
-
response = self.generative_model.generate_image(
|
223
|
-
prompt=prompt,
|
224
|
-
params=params
|
225
|
-
)
|
235
|
+
response = self.generative_model.generate_image(prompt=prompt, params=params)
|
226
236
|
|
227
237
|
# Extract image data from response
|
228
238
|
if not response.data:
|
229
239
|
raise ValueError("No image data in response")
|
230
|
-
|
240
|
+
|
231
241
|
image_data = response.data[0] # First image from the response
|
232
242
|
image_url = str(image_data.get("url", ""))
|
233
243
|
revised_prompt = str(image_data.get("revised_prompt", prompt))
|
234
|
-
|
244
|
+
|
235
245
|
if not image_url:
|
236
246
|
raise ValueError("No image URL in response")
|
237
247
|
|
238
248
|
# Save image locally
|
239
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
249
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
240
250
|
filename = f"{provider}_{timestamp}.png"
|
241
251
|
local_path = self._save_image(image_url, filename)
|
242
252
|
|
@@ -250,12 +260,12 @@ class LLMImageGenerationTool(Tool):
|
|
250
260
|
"created": str(response.created or ""),
|
251
261
|
"parameters": {k: str(v) for k, v in {**params, "prompt": prompt}.items()},
|
252
262
|
"image_url": str(image_url),
|
253
|
-
"local_path": str(local_path)
|
263
|
+
"local_path": str(local_path),
|
254
264
|
}
|
255
265
|
self._save_metadata(metadata)
|
256
266
|
|
257
267
|
logger.info(f"Image generated and saved at: {local_path}")
|
258
|
-
return str(
|
268
|
+
return str(metadata)
|
259
269
|
|
260
270
|
except Exception as e:
|
261
271
|
logger.error(f"Error generating image with {provider}: {e}")
|
quantalogic/tools/jinja_tool.py
CHANGED
@@ -14,7 +14,7 @@ class JinjaTool(Tool):
|
|
14
14
|
|
15
15
|
name: str = "jinja_tool"
|
16
16
|
description: str = (
|
17
|
-
"Renders an inline Jinja2 template string with a predefined context.\n"
|
17
|
+
"Renders an inline Jinja2 template string with a predefined context.\n"
|
18
18
|
"You can use the variables in the template just as var1, var2, etc.\n"
|
19
19
|
"Useful for simple calculations or string operations.\n"
|
20
20
|
)
|
@@ -41,7 +41,7 @@ class JinjaTool(Tool):
|
|
41
41
|
"""
|
42
42
|
super().__init__()
|
43
43
|
|
44
|
-
def execute(self, inline_template: str,variables: Optional[Dict[str, Any]] = None) -> str:
|
44
|
+
def execute(self, inline_template: str, variables: Optional[Dict[str, Any]] = None) -> str:
|
45
45
|
"""
|
46
46
|
Render an inline Jinja2 template with the predefined context.
|
47
47
|
|
@@ -72,20 +72,16 @@ class JinjaTool(Tool):
|
|
72
72
|
|
73
73
|
if __name__ == "__main__":
|
74
74
|
# Example of using JinjaTool with variables
|
75
|
-
tool = JinjaTool(context={
|
76
|
-
|
77
|
-
"var2": 42,
|
78
|
-
"var3": ["apple", "banana", "cherry"]
|
79
|
-
})
|
80
|
-
|
75
|
+
tool = JinjaTool(context={"var1": "World", "var2": 42, "var3": ["apple", "banana", "cherry"]})
|
76
|
+
|
81
77
|
# Inline template demonstrating variable usage
|
82
78
|
template = "Hello {{ var1 }}! The answer is {{ var2 }}. Fruits: {% for fruit in var3 %}{{ fruit }}{% if not loop.last %}, {% endif %}{% endfor %}."
|
83
|
-
|
79
|
+
|
84
80
|
# Render the template
|
85
81
|
result = tool.execute(template)
|
86
82
|
print("Rendered Template:")
|
87
83
|
print(result)
|
88
|
-
|
84
|
+
|
89
85
|
# Print the tool's markdown representation
|
90
86
|
print("\nTool Markdown:")
|
91
87
|
print(tool.to_markdown())
|
@@ -1,12 +1,4 @@
|
|
1
|
-
|
2
|
-
from .cpp_handler import CppLanguageHandler
|
3
|
-
from .go_handler import GoLanguageHandler
|
4
|
-
from .java_handler import JavaLanguageHandler
|
5
|
-
from .javascript_handler import JavaScriptLanguageHandler
|
6
|
-
from .python_handler import PythonLanguageHandler
|
7
|
-
from .rust_handler import RustLanguageHandler
|
8
|
-
from .scala_handler import ScalaLanguageHandler
|
9
|
-
from .typescript_handler import TypeScriptLanguageHandler
|
1
|
+
import importlib
|
10
2
|
|
11
3
|
__all__ = [
|
12
4
|
"CLanguageHandler",
|
@@ -19,3 +11,24 @@ __all__ = [
|
|
19
11
|
"ScalaLanguageHandler",
|
20
12
|
"TypeScriptLanguageHandler",
|
21
13
|
]
|
14
|
+
|
15
|
+
def __getattr__(name):
|
16
|
+
"""Lazily import language handlers when they are first requested."""
|
17
|
+
if name in __all__:
|
18
|
+
module_map = {
|
19
|
+
"CLanguageHandler": "c_handler",
|
20
|
+
"CppLanguageHandler": "cpp_handler",
|
21
|
+
"GoLanguageHandler": "go_handler",
|
22
|
+
"JavaLanguageHandler": "java_handler",
|
23
|
+
"JavaScriptLanguageHandler": "javascript_handler",
|
24
|
+
"PythonLanguageHandler": "python_handler",
|
25
|
+
"RustLanguageHandler": "rust_handler",
|
26
|
+
"ScalaLanguageHandler": "scala_handler",
|
27
|
+
"TypeScriptLanguageHandler": "typescript_handler",
|
28
|
+
}
|
29
|
+
|
30
|
+
module_name = module_map[name]
|
31
|
+
module = importlib.import_module(f".{module_name}", package=__package__)
|
32
|
+
return getattr(module, name)
|
33
|
+
|
34
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
@@ -1,9 +1,11 @@
|
|
1
1
|
"""Tool for listing the contents of a directory."""
|
2
2
|
|
3
3
|
import os
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import List, Dict
|
6
|
+
from loguru import logger
|
4
7
|
|
5
8
|
from quantalogic.tools.tool import Tool, ToolArgument
|
6
|
-
from quantalogic.utils.git_ls import git_ls
|
7
9
|
|
8
10
|
|
9
11
|
class ListDirectoryTool(Tool):
|
@@ -32,8 +34,8 @@ class ListDirectoryTool(Tool):
|
|
32
34
|
arg_type="int",
|
33
35
|
description="Maximum directory traversal depth",
|
34
36
|
required=False,
|
35
|
-
default="
|
36
|
-
example="
|
37
|
+
default="10",
|
38
|
+
example="10",
|
37
39
|
),
|
38
40
|
ToolArgument(
|
39
41
|
name="start_line",
|
@@ -53,16 +55,95 @@ class ListDirectoryTool(Tool):
|
|
53
55
|
),
|
54
56
|
]
|
55
57
|
|
58
|
+
def _list_directory(self, path: Path, max_depth: int, current_depth: int = 0) -> List[Dict]:
|
59
|
+
"""List directory contents recursively.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
path: Directory path to list
|
63
|
+
max_depth: Maximum recursion depth
|
64
|
+
current_depth: Current recursion depth
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
List of dictionaries containing file/directory information
|
68
|
+
"""
|
69
|
+
if current_depth > max_depth:
|
70
|
+
return []
|
71
|
+
|
72
|
+
results = []
|
73
|
+
try:
|
74
|
+
for item in sorted(path.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower())):
|
75
|
+
if item.name == ".git":
|
76
|
+
continue
|
77
|
+
|
78
|
+
try:
|
79
|
+
if item.is_file():
|
80
|
+
size = item.stat().st_size
|
81
|
+
results.append({
|
82
|
+
"type": "file",
|
83
|
+
"name": item.name,
|
84
|
+
"size": f"{size} bytes",
|
85
|
+
"path": str(item.relative_to(path.parent))
|
86
|
+
})
|
87
|
+
elif item.is_dir():
|
88
|
+
children = self._list_directory(item, max_depth, current_depth + 1)
|
89
|
+
results.append({
|
90
|
+
"type": "directory",
|
91
|
+
"name": item.name,
|
92
|
+
"children": children,
|
93
|
+
"path": str(item.relative_to(path.parent))
|
94
|
+
})
|
95
|
+
except PermissionError:
|
96
|
+
results.append({
|
97
|
+
"type": "error",
|
98
|
+
"name": item.name,
|
99
|
+
"error": "Permission denied"
|
100
|
+
})
|
101
|
+
except Exception as e:
|
102
|
+
logger.error(f"Error processing {item}: {str(e)}")
|
103
|
+
|
104
|
+
except PermissionError:
|
105
|
+
return [{"type": "error", "name": path.name, "error": "Permission denied"}]
|
106
|
+
except Exception as e:
|
107
|
+
logger.error(f"Error listing directory {path}: {str(e)}")
|
108
|
+
return [{"type": "error", "name": path.name, "error": str(e)}]
|
109
|
+
|
110
|
+
return results
|
111
|
+
|
112
|
+
def _format_tree(self, items: List[Dict], depth: int = 0) -> List[str]:
|
113
|
+
"""Format directory tree into lines of text.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
items: List of file/directory items
|
117
|
+
depth: Current indentation depth
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
List of formatted lines
|
121
|
+
"""
|
122
|
+
lines = []
|
123
|
+
indent = " " * depth
|
124
|
+
|
125
|
+
for item in items:
|
126
|
+
if item["type"] == "file":
|
127
|
+
lines.append(f"{indent} {item['path']} ({item['size']})")
|
128
|
+
elif item["type"] == "directory":
|
129
|
+
lines.append(f"{indent} {item['path']}/")
|
130
|
+
if "children" in item:
|
131
|
+
lines.extend(self._format_tree(item["children"], depth + 1))
|
132
|
+
elif item["type"] == "error":
|
133
|
+
lines.append(f"{indent} {item['name']} ({item['error']})")
|
134
|
+
|
135
|
+
return lines
|
136
|
+
|
56
137
|
def execute(
|
57
138
|
self,
|
58
139
|
directory_path: str,
|
59
140
|
recursive: str = "false",
|
60
|
-
max_depth: str = "
|
141
|
+
max_depth: str = "10",
|
61
142
|
start_line: str = "1",
|
62
143
|
end_line: str = "200",
|
63
144
|
) -> str:
|
64
145
|
"""
|
65
|
-
List directory contents with pagination
|
146
|
+
List directory contents with pagination.
|
66
147
|
|
67
148
|
Args:
|
68
149
|
directory_path: Absolute or relative path to target directory
|
@@ -77,47 +158,55 @@ class ListDirectoryTool(Tool):
|
|
77
158
|
Raises:
|
78
159
|
ValueError: For invalid directory paths or pagination parameters
|
79
160
|
"""
|
80
|
-
# Expand user home directory to full path
|
81
|
-
# This ensures compatibility with '~' shorthand for home directory
|
82
|
-
if directory_path.startswith("~"):
|
83
|
-
directory_path = os.path.expanduser(directory_path)
|
84
|
-
|
85
|
-
# Validate directory existence and type
|
86
|
-
# Fail early with clear error messages if path is invalid
|
87
|
-
if not os.path.exists(directory_path):
|
88
|
-
raise ValueError(f"The directory '{directory_path}' does not exist.")
|
89
|
-
if not os.path.isdir(directory_path):
|
90
|
-
raise ValueError(f"The path '{directory_path}' is not a directory.")
|
91
|
-
|
92
|
-
# Safely convert inputs with default values
|
93
|
-
start = int(start_line or "1")
|
94
|
-
end = int(end_line or "200")
|
95
|
-
max_depth_int = int(max_depth or "1")
|
96
|
-
is_recursive = (recursive or "false").lower() == "true"
|
97
|
-
|
98
|
-
# Validate pagination parameters
|
99
|
-
if start > end:
|
100
|
-
raise ValueError("start_line must be less than or equal to end_line.")
|
101
|
-
|
102
161
|
try:
|
103
|
-
#
|
104
|
-
|
105
|
-
directory_path=directory_path
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
162
|
+
# Expand user home directory
|
163
|
+
if directory_path.startswith("~"):
|
164
|
+
directory_path = os.path.expanduser(directory_path)
|
165
|
+
|
166
|
+
path = Path(directory_path)
|
167
|
+
|
168
|
+
# Validate directory
|
169
|
+
if not path.exists():
|
170
|
+
raise ValueError(f"The directory '{directory_path}' does not exist.")
|
171
|
+
if not path.is_dir():
|
172
|
+
raise ValueError(f"The path '{directory_path}' is not a directory.")
|
173
|
+
|
174
|
+
# Parse parameters
|
175
|
+
start = int(start_line)
|
176
|
+
end = int(end_line)
|
177
|
+
max_depth_int = int(max_depth)
|
178
|
+
is_recursive = recursive.lower() == "true"
|
179
|
+
|
180
|
+
if start > end:
|
181
|
+
raise ValueError("start_line must be less than or equal to end_line.")
|
182
|
+
|
183
|
+
# List directory contents
|
184
|
+
items = self._list_directory(
|
185
|
+
path=path,
|
186
|
+
max_depth=max_depth_int if is_recursive else 0
|
110
187
|
)
|
111
|
-
|
188
|
+
|
189
|
+
# Format output
|
190
|
+
lines = self._format_tree(items)
|
191
|
+
|
192
|
+
if not lines:
|
193
|
+
return "==== No files to display ===="
|
194
|
+
|
195
|
+
# Paginate results
|
196
|
+
total_lines = len(lines)
|
197
|
+
paginated_lines = lines[start - 1:end]
|
198
|
+
|
199
|
+
header = f"==== Lines {start}-{min(end, total_lines)} of {total_lines} ===="
|
200
|
+
if end >= total_lines:
|
201
|
+
header += " [LAST BLOCK]"
|
202
|
+
|
203
|
+
return f"{header}\n" + "\n".join(paginated_lines) + "\n==== End of Block ===="
|
204
|
+
|
112
205
|
except Exception as e:
|
113
|
-
|
114
|
-
|
115
|
-
traceback.print_exc()
|
116
|
-
return f"Error: {str(e)} occurred during directory listing. See logs for details."
|
206
|
+
logger.error(f"Error listing directory: {str(e)}")
|
207
|
+
return f"Error: {str(e)}"
|
117
208
|
|
118
209
|
|
119
210
|
if __name__ == "__main__":
|
120
211
|
tool = ListDirectoryTool()
|
121
|
-
|
122
|
-
tool.execute(directory_path=current_directory, recursive="true")
|
123
|
-
print(tool.to_markdown())
|
212
|
+
print(tool.execute(directory_path=".", recursive="true"))
|
quantalogic/tools/llm_tool.py
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
"""LLM Tool for generating answers to questions using a language model."""
|
2
2
|
|
3
|
+
import asyncio
|
3
4
|
from typing import Callable
|
4
5
|
|
5
6
|
from loguru import logger
|
6
7
|
from pydantic import ConfigDict, Field
|
7
8
|
|
8
9
|
from quantalogic.console_print_token import console_print_token
|
10
|
+
from quantalogic.event_emitter import EventEmitter
|
9
11
|
from quantalogic.generative_model import GenerativeModel, Message
|
10
12
|
from quantalogic.tools.tool import Tool, ToolArgument
|
11
13
|
|
@@ -58,6 +60,7 @@ class LLMTool(Tool):
|
|
58
60
|
system_prompt: str | None = Field(default=None)
|
59
61
|
on_token: Callable | None = Field(default=None, exclude=True)
|
60
62
|
generative_model: GenerativeModel | None = Field(default=None, exclude=True)
|
63
|
+
event_emitter: EventEmitter | None = Field(default=None, exclude=True)
|
61
64
|
|
62
65
|
def __init__(
|
63
66
|
self,
|
@@ -66,7 +69,17 @@ class LLMTool(Tool):
|
|
66
69
|
on_token: Callable | None = None,
|
67
70
|
name: str = "llm_tool",
|
68
71
|
generative_model: GenerativeModel | None = None,
|
72
|
+
event_emitter: EventEmitter | None = None,
|
69
73
|
):
|
74
|
+
"""Initialize the LLMTool with model configuration and optional callback.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
model_name (str): The name of the language model to use.
|
78
|
+
system_prompt (str, optional): Default system prompt for the model.
|
79
|
+
on_token (Callable, optional): Callback function for streaming tokens.
|
80
|
+
name (str): Name of the tool instance. Defaults to "llm_tool".
|
81
|
+
generative_model (GenerativeModel, optional): Pre-initialized generative model.
|
82
|
+
"""
|
70
83
|
# Use dict to pass validated data to parent constructor
|
71
84
|
super().__init__(
|
72
85
|
**{
|
@@ -75,16 +88,20 @@ class LLMTool(Tool):
|
|
75
88
|
"on_token": on_token,
|
76
89
|
"name": name,
|
77
90
|
"generative_model": generative_model,
|
91
|
+
"event_emitter": event_emitter,
|
78
92
|
}
|
79
93
|
)
|
80
|
-
|
94
|
+
|
81
95
|
# Initialize the generative model
|
82
96
|
self.model_post_init(None)
|
83
97
|
|
84
98
|
def model_post_init(self, __context):
|
85
99
|
"""Initialize the generative model after model initialization."""
|
86
100
|
if self.generative_model is None:
|
87
|
-
self.generative_model = GenerativeModel(
|
101
|
+
self.generative_model = GenerativeModel(
|
102
|
+
model=self.model_name,
|
103
|
+
event_emitter=self.event_emitter
|
104
|
+
)
|
88
105
|
logger.debug(f"Initialized LLMTool with model: {self.model_name}")
|
89
106
|
|
90
107
|
# Only set up event listener if on_token is provided
|
@@ -92,14 +109,17 @@ class LLMTool(Tool):
|
|
92
109
|
logger.debug(f"Setting up event listener for LLMTool with model: {self.model_name}")
|
93
110
|
self.generative_model.event_emitter.on("stream_chunk", self.on_token)
|
94
111
|
|
95
|
-
def
|
112
|
+
async def async_execute(
|
96
113
|
self, system_prompt: str | None = None, prompt: str | None = None, temperature: str | None = None
|
97
114
|
) -> str:
|
98
|
-
"""Execute the tool to generate an answer
|
115
|
+
"""Execute the tool to generate an answer asynchronously.
|
116
|
+
|
117
|
+
This method provides a native asynchronous implementation, utilizing the generative model's
|
118
|
+
asynchronous capabilities for improved performance in async contexts.
|
99
119
|
|
100
120
|
Args:
|
101
|
-
system_prompt (str): The system prompt to guide the model.
|
102
|
-
prompt (str): The question to be answered.
|
121
|
+
system_prompt (str, optional): The system prompt to guide the model.
|
122
|
+
prompt (str, optional): The question to be answered.
|
103
123
|
temperature (str, optional): Sampling temperature. Defaults to "0.7".
|
104
124
|
|
105
125
|
Returns:
|
@@ -130,24 +150,25 @@ class LLMTool(Tool):
|
|
130
150
|
if self.generative_model:
|
131
151
|
self.generative_model.temperature = temp
|
132
152
|
|
133
|
-
# Generate the response using the generative model
|
153
|
+
# Generate the response asynchronously using the generative model
|
134
154
|
try:
|
135
|
-
result = self.generative_model.
|
155
|
+
result = await self.generative_model.async_generate_with_history(
|
136
156
|
messages_history=messages_history, prompt=prompt, streaming=is_streaming
|
137
157
|
)
|
138
158
|
|
139
159
|
if is_streaming:
|
140
160
|
response = ""
|
141
|
-
for chunk in result:
|
161
|
+
async for chunk in result:
|
142
162
|
response += chunk
|
163
|
+
# Note: on_token is handled via the event emitter set in model_post_init
|
143
164
|
else:
|
144
165
|
response = result.response
|
145
166
|
|
146
|
-
logger.debug(f"Generated response: {response}")
|
167
|
+
logger.debug(f"Generated async response: {response}")
|
147
168
|
return response
|
148
169
|
except Exception as e:
|
149
|
-
logger.error(f"Error generating response: {e}")
|
150
|
-
raise Exception(f"Error generating response: {e}") from e
|
170
|
+
logger.error(f"Error generating async response: {e}")
|
171
|
+
raise Exception(f"Error generating async response: {e}") from e
|
151
172
|
else:
|
152
173
|
raise ValueError("Generative model not initialized")
|
153
174
|
|
@@ -158,16 +179,25 @@ if __name__ == "__main__":
|
|
158
179
|
system_prompt = 'Answer the question as truthfully as possible using the provided context, and if the answer is not contained within the context, say "I don\'t know".'
|
159
180
|
question = "What is the meaning of life?"
|
160
181
|
temperature = "0.7"
|
182
|
+
|
183
|
+
# Synchronous execution
|
161
184
|
answer = tool.execute(system_prompt=system_prompt, prompt=question, temperature=temperature)
|
185
|
+
print("Synchronous Answer:")
|
162
186
|
print(answer)
|
187
|
+
|
188
|
+
# Asynchronous execution with streaming
|
163
189
|
pirate = LLMTool(
|
164
190
|
model_name="openrouter/openai/gpt-4o-mini", system_prompt="You are a pirate.", on_token=console_print_token
|
165
191
|
)
|
166
|
-
pirate_answer =
|
167
|
-
|
168
|
-
|
192
|
+
pirate_answer = asyncio.run(
|
193
|
+
pirate.async_execute(system_prompt=system_prompt, prompt=question, temperature=temperature)
|
194
|
+
)
|
195
|
+
print("\nAsynchronous Pirate Answer:")
|
196
|
+
print(f"Answer: {pirate_answer}")
|
169
197
|
|
198
|
+
# Display tool configuration in Markdown
|
170
199
|
custom_tool = LLMTool(
|
171
200
|
model_name="openrouter/openai/gpt-4o-mini", system_prompt="You are a pirate.", on_token=console_print_token
|
172
201
|
)
|
202
|
+
print("\nTool Configuration:")
|
173
203
|
print(custom_tool.to_markdown())
|