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.
Files changed (107) hide show
  1. quantalogic/__init__.py +0 -4
  2. quantalogic/agent.py +603 -362
  3. quantalogic/agent_config.py +260 -28
  4. quantalogic/agent_factory.py +43 -17
  5. quantalogic/coding_agent.py +20 -12
  6. quantalogic/config.py +7 -4
  7. quantalogic/console_print_events.py +4 -8
  8. quantalogic/console_print_token.py +2 -2
  9. quantalogic/docs_cli.py +15 -10
  10. quantalogic/event_emitter.py +258 -83
  11. quantalogic/flow/__init__.py +23 -0
  12. quantalogic/flow/flow.py +595 -0
  13. quantalogic/flow/flow_extractor.py +672 -0
  14. quantalogic/flow/flow_generator.py +89 -0
  15. quantalogic/flow/flow_manager.py +407 -0
  16. quantalogic/flow/flow_manager_schema.py +169 -0
  17. quantalogic/flow/flow_yaml.md +419 -0
  18. quantalogic/generative_model.py +109 -77
  19. quantalogic/get_model_info.py +6 -6
  20. quantalogic/interactive_text_editor.py +100 -73
  21. quantalogic/main.py +36 -23
  22. quantalogic/model_info_list.py +12 -0
  23. quantalogic/model_info_litellm.py +14 -14
  24. quantalogic/prompts.py +2 -1
  25. quantalogic/{llm.py → quantlitellm.py} +29 -39
  26. quantalogic/search_agent.py +4 -4
  27. quantalogic/server/models.py +4 -1
  28. quantalogic/task_file_reader.py +5 -5
  29. quantalogic/task_runner.py +21 -20
  30. quantalogic/tool_manager.py +10 -21
  31. quantalogic/tools/__init__.py +98 -68
  32. quantalogic/tools/composio/composio.py +416 -0
  33. quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
  34. quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
  35. quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
  36. quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
  37. quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
  38. quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
  39. quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
  40. quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
  41. quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
  42. quantalogic/tools/duckduckgo_search_tool.py +2 -4
  43. quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
  44. quantalogic/tools/finance/ccxt_tool.py +373 -0
  45. quantalogic/tools/finance/finance_llm_tool.py +387 -0
  46. quantalogic/tools/finance/google_finance.py +192 -0
  47. quantalogic/tools/finance/market_intelligence_tool.py +520 -0
  48. quantalogic/tools/finance/technical_analysis_tool.py +491 -0
  49. quantalogic/tools/finance/tradingview_tool.py +336 -0
  50. quantalogic/tools/finance/yahoo_finance.py +236 -0
  51. quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
  52. quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
  53. quantalogic/tools/git/clone_repo_tool.py +189 -0
  54. quantalogic/tools/git/git_operations_tool.py +532 -0
  55. quantalogic/tools/google_packages/google_news_tool.py +480 -0
  56. quantalogic/tools/grep_app_tool.py +123 -186
  57. quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
  58. quantalogic/tools/jinja_tool.py +6 -10
  59. quantalogic/tools/language_handlers/__init__.py +22 -9
  60. quantalogic/tools/list_directory_tool.py +131 -42
  61. quantalogic/tools/llm_tool.py +45 -15
  62. quantalogic/tools/llm_vision_tool.py +59 -7
  63. quantalogic/tools/markitdown_tool.py +17 -5
  64. quantalogic/tools/nasa_packages/models.py +47 -0
  65. quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
  66. quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
  67. quantalogic/tools/nasa_packages/services.py +82 -0
  68. quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
  69. quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
  70. quantalogic/tools/product_hunt/services.py +63 -0
  71. quantalogic/tools/rag_tool/__init__.py +48 -0
  72. quantalogic/tools/rag_tool/document_metadata.py +15 -0
  73. quantalogic/tools/rag_tool/query_response.py +20 -0
  74. quantalogic/tools/rag_tool/rag_tool.py +566 -0
  75. quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
  76. quantalogic/tools/read_html_tool.py +24 -38
  77. quantalogic/tools/replace_in_file_tool.py +10 -10
  78. quantalogic/tools/safe_python_interpreter_tool.py +10 -24
  79. quantalogic/tools/search_definition_names.py +2 -2
  80. quantalogic/tools/sequence_tool.py +14 -23
  81. quantalogic/tools/sql_query_tool.py +17 -19
  82. quantalogic/tools/tool.py +39 -15
  83. quantalogic/tools/unified_diff_tool.py +1 -1
  84. quantalogic/tools/utilities/csv_processor_tool.py +234 -0
  85. quantalogic/tools/utilities/download_file_tool.py +179 -0
  86. quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
  87. quantalogic/tools/utils/__init__.py +1 -4
  88. quantalogic/tools/utils/create_sample_database.py +24 -38
  89. quantalogic/tools/utils/generate_database_report.py +74 -82
  90. quantalogic/tools/wikipedia_search_tool.py +17 -21
  91. quantalogic/utils/ask_user_validation.py +1 -1
  92. quantalogic/utils/async_utils.py +35 -0
  93. quantalogic/utils/check_version.py +3 -5
  94. quantalogic/utils/get_all_models.py +2 -1
  95. quantalogic/utils/git_ls.py +21 -7
  96. quantalogic/utils/lm_studio_model_info.py +9 -7
  97. quantalogic/utils/python_interpreter.py +113 -43
  98. quantalogic/utils/xml_utility.py +178 -0
  99. quantalogic/version_check.py +1 -1
  100. quantalogic/welcome_message.py +7 -7
  101. quantalogic/xml_parser.py +0 -1
  102. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/METADATA +44 -1
  103. quantalogic-0.40.0.dist-info/RECORD +148 -0
  104. quantalogic-0.33.4.dist-info/RECORD +0 -102
  105. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/LICENSE +0 -0
  106. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/WHEEL +0 -0
  107. {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(f"Invalid size for DALL-E. Must be one of: {PROVIDER_CONFIGS[ImageProvider.DALLE]['sizes']}")
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(f"Invalid quality for DALL-E. Must be one of: {PROVIDER_CONFIGS[ImageProvider.DALLE]['qualities']}")
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(f"Invalid style for DALL-E. Must be one of: {PROVIDER_CONFIGS[ImageProvider.DALLE]['styles']}")
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(f"Invalid size for Stable Diffusion. Must be one of: {PROVIDER_CONFIGS[ImageProvider.STABLE_DIFFUSION]['sizes']}")
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, 'w') as f:
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 = float(cfg_scale) if cfg_scale and provider_enum == ImageProvider.STABLE_DIFFUSION else None
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(local_path)
268
+ return str(metadata)
259
269
 
260
270
  except Exception as e:
261
271
  logger.error(f"Error generating image with {provider}: {e}")
@@ -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
- "var1": "World",
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
- from .c_handler import CLanguageHandler
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="1",
36
- example="1",
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 = "1",
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 and .gitignore support.
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
- # Use git_ls for directory listing with .gitignore support
104
- all_lines = git_ls(
105
- directory_path=directory_path,
106
- recursive=is_recursive,
107
- max_depth=max_depth_int,
108
- start_line=start,
109
- end_line=end,
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
- return all_lines
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
- import traceback
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
- current_directory = os.getcwd()
122
- tool.execute(directory_path=current_directory, recursive="true")
123
- print(tool.to_markdown())
212
+ print(tool.execute(directory_path=".", recursive="true"))
@@ -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(model=self.model_name)
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 execute(
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 based on the provided question.
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.generate_with_history(
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 = pirate.execute(system_prompt=system_prompt, prompt=question, temperature=temperature)
167
- print("\n")
168
- print(f"Anwser: {pirate_answer}")
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())