quantalogic 0.61.1__py3-none-any.whl → 0.61.3__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 (31) hide show
  1. quantalogic/agent_config.py +4 -4
  2. quantalogic/codeact/agent.py +1 -1
  3. quantalogic/codeact/cli.py +5 -5
  4. quantalogic/codeact/tools_manager.py +1 -1
  5. quantalogic/coding_agent.py +1 -1
  6. quantalogic/tools/action_gen_safe.py +340 -0
  7. quantalogic/tools/write_file_tool.py +49 -43
  8. {quantalogic-0.61.1.dist-info → quantalogic-0.61.3.dist-info}/METADATA +4 -3
  9. {quantalogic-0.61.1.dist-info → quantalogic-0.61.3.dist-info}/RECORD +12 -30
  10. quantalogic/python_interpreter/__init__.py +0 -23
  11. quantalogic/python_interpreter/assignment_visitors.py +0 -63
  12. quantalogic/python_interpreter/base_visitors.py +0 -20
  13. quantalogic/python_interpreter/class_visitors.py +0 -22
  14. quantalogic/python_interpreter/comprehension_visitors.py +0 -172
  15. quantalogic/python_interpreter/context_visitors.py +0 -59
  16. quantalogic/python_interpreter/control_flow_visitors.py +0 -88
  17. quantalogic/python_interpreter/exception_visitors.py +0 -109
  18. quantalogic/python_interpreter/exceptions.py +0 -39
  19. quantalogic/python_interpreter/execution.py +0 -202
  20. quantalogic/python_interpreter/function_utils.py +0 -386
  21. quantalogic/python_interpreter/function_visitors.py +0 -209
  22. quantalogic/python_interpreter/import_visitors.py +0 -28
  23. quantalogic/python_interpreter/interpreter_core.py +0 -358
  24. quantalogic/python_interpreter/literal_visitors.py +0 -74
  25. quantalogic/python_interpreter/misc_visitors.py +0 -148
  26. quantalogic/python_interpreter/operator_visitors.py +0 -108
  27. quantalogic/python_interpreter/scope.py +0 -10
  28. quantalogic/python_interpreter/visit_handlers.py +0 -110
  29. {quantalogic-0.61.1.dist-info → quantalogic-0.61.3.dist-info}/LICENSE +0 -0
  30. {quantalogic-0.61.1.dist-info → quantalogic-0.61.3.dist-info}/WHEEL +0 -0
  31. {quantalogic-0.61.1.dist-info → quantalogic-0.61.3.dist-info}/entry_points.txt +0 -0
@@ -75,7 +75,7 @@ def create_agent(
75
75
  TaskCompleteTool(),
76
76
  ReadFileTool(),
77
77
  ReadFileBlockTool(),
78
- WriteFileTool(),
78
+ WriteFileTool(disable_ensure_tmp_path=True),
79
79
  EditWholeContentTool(),
80
80
  InputQuestionTool(),
81
81
  ListDirectoryTool(),
@@ -129,7 +129,7 @@ def create_interpreter_agent(
129
129
  TaskCompleteTool(),
130
130
  ReadFileTool(),
131
131
  ReadFileBlockTool(),
132
- WriteFileTool(),
132
+ WriteFileTool(disable_ensure_tmp_path=True),
133
133
  EditWholeContentTool(),
134
134
  InputQuestionTool(),
135
135
  ListDirectoryTool(),
@@ -176,7 +176,7 @@ def create_full_agent(
176
176
  TaskCompleteTool(),
177
177
  ReadFileTool(),
178
178
  ReadFileBlockTool(),
179
- WriteFileTool(),
179
+ WriteFileTool(disable_ensure_tmp_path=True),
180
180
  EditWholeContentTool(),
181
181
  InputQuestionTool(),
182
182
  ListDirectoryTool(),
@@ -237,7 +237,7 @@ def create_basic_agent(
237
237
  SearchDefinitionNamesTool(),
238
238
  ReadFileTool(),
239
239
  ReplaceInFileTool(),
240
- WriteFileTool(),
240
+ WriteFileTool(disable_ensure_tmp_path=True),
241
241
  EditWholeContentTool(),
242
242
  ReplaceInFileTool(),
243
243
  InputQuestionTool(),
@@ -5,8 +5,8 @@ from typing import Callable, Dict, List, Optional, Tuple
5
5
  from jinja2 import Environment, FileSystemLoader
6
6
  from loguru import logger
7
7
  from lxml import etree
8
+ from quantalogic_pythonbox import execute_async
8
9
 
9
- from quantalogic.python_interpreter import execute_async
10
10
  from quantalogic.tools import Tool
11
11
 
12
12
  from .constants import MAX_GENERATE_PROGRAM_TOKENS, MAX_HISTORY_TOKENS, MAX_TOKENS, TEMPLATE_DIR
@@ -4,11 +4,9 @@ from typing import Callable, List, Optional, Union
4
4
  import typer
5
5
  from loguru import logger
6
6
 
7
- from quantalogic.tools import create_tool
8
-
9
- from .agent import Agent # Import only Agent from agent.py
10
- from .constants import DEFAULT_MODEL, LOG_FILE
11
- from .events import ( # Import events from events.py
7
+ from quantalogic.codeact.agent import Agent # Import only Agent from agent.py
8
+ from quantalogic.codeact.constants import DEFAULT_MODEL, LOG_FILE
9
+ from quantalogic.codeact.events import ( # Import events from events.py
12
10
  ActionExecutedEvent,
13
11
  ActionGeneratedEvent,
14
12
  ErrorOccurredEvent,
@@ -22,6 +20,8 @@ from .events import ( # Import events from events.py
22
20
  ToolExecutionErrorEvent,
23
21
  ToolExecutionStartedEvent,
24
22
  )
23
+ from quantalogic.tools import create_tool
24
+
25
25
  from .tools_manager import Tool, get_default_tools
26
26
  from .utils import XMLResultHandler
27
27
 
@@ -130,6 +130,6 @@ def get_default_tools(model: str) -> List[Tool]:
130
130
  # RipgrepTool(),
131
131
  # SearchDefinitionNamesTool(),
132
132
  #TaskCompleteTool(),
133
- WriteFileTool(),
133
+ WriteFileTool(disable_ensure_tmp_path=True),
134
134
  AgentTool(model=model),
135
135
  ]
@@ -65,7 +65,7 @@ def create_coding_agent(
65
65
  # Core file manipulation tools
66
66
  TaskCompleteTool(), # Marks task completion
67
67
  ReadFileBlockTool(), # Reads specific file sections
68
- WriteFileTool(), # Creates new files
68
+ WriteFileTool(disable_ensure_tmp_path=True), # Creates new files
69
69
  ReplaceInFileTool(), # Updates file sections
70
70
  EditWholeContentTool(), # Modifies entire files
71
71
  # Code navigation and search tools
@@ -0,0 +1,340 @@
1
+ import ast
2
+ import asyncio
3
+ from functools import partial
4
+ from typing import Callable, Dict, List
5
+
6
+ import litellm
7
+ import typer
8
+ from loguru import logger
9
+
10
+ from quantalogic.tools.tool import Tool, ToolArgument
11
+ from quantalogic.utils.python_interpreter import ASTInterpreter
12
+
13
+ # Configure loguru to log to a file with rotation
14
+ logger.add("action_gen_safe.log", rotation="10 MB", level="DEBUG")
15
+
16
+ # Initialize Typer app
17
+ app = typer.Typer()
18
+
19
+ # Define tool classes with logging in async_execute
20
+ class AddTool(Tool):
21
+ def __init__(self):
22
+ super().__init__(
23
+ name="add_tool",
24
+ description="Adds two numbers and returns the sum.",
25
+ arguments=[
26
+ ToolArgument(name="a", arg_type="int", description="First number", required=True),
27
+ ToolArgument(name="b", arg_type="int", description="Second number", required=True)
28
+ ],
29
+ return_type="int"
30
+ )
31
+
32
+ async def async_execute(self, **kwargs) -> str:
33
+ logger.info(f"Adding {kwargs['a']} and {kwargs['b']}")
34
+ return str(int(kwargs["a"]) + int(kwargs["b"]))
35
+
36
+ class MultiplyTool(Tool):
37
+ def __init__(self):
38
+ super().__init__(
39
+ name="multiply_tool",
40
+ description="Multiplies two numbers and returns the product.",
41
+ arguments=[
42
+ ToolArgument(name="x", arg_type="int", description="First number", required=True),
43
+ ToolArgument(name="y", arg_type="int", description="Second number", required=True)
44
+ ],
45
+ return_type="int"
46
+ )
47
+
48
+ async def async_execute(self, **kwargs) -> str:
49
+ logger.info(f"Multiplying {kwargs['x']} and {kwargs['y']}")
50
+ return str(int(kwargs["x"]) * int(kwargs["y"]))
51
+
52
+ class ConcatTool(Tool):
53
+ def __init__(self):
54
+ super().__init__(
55
+ name="concat_tool",
56
+ description="Concatenates two strings.",
57
+ arguments=[
58
+ ToolArgument(name="s1", arg_type="string", description="First string", required=True),
59
+ ToolArgument(name="s2", arg_type="string", description="Second string", required=True)
60
+ ],
61
+ return_type="string"
62
+ )
63
+
64
+ async def async_execute(self, **kwargs) -> str:
65
+ logger.info(f"Concatenating '{kwargs['s1']}' and '{kwargs['s2']}'")
66
+ return kwargs["s1"] + kwargs["s2"]
67
+
68
+ class AgentTool(Tool):
69
+ def __init__(self, model: str = "gemini/gemini-2.0-flash"):
70
+ super().__init__(
71
+ name="agent_tool",
72
+ description="Generates text using a language model based on a system prompt and user prompt.",
73
+ arguments=[
74
+ ToolArgument(name="system_prompt", arg_type="string", description="System prompt to guide the model's behavior", required=True),
75
+ ToolArgument(name="prompt", arg_type="string", description="User prompt to generate a response for", required=True),
76
+ ToolArgument(name="temperature", arg_type="float", description="Temperature for generation (0 to 1)", required=True)
77
+ ],
78
+ return_type="string"
79
+ )
80
+ self.model = model
81
+
82
+ async def async_execute(self, **kwargs) -> str:
83
+ system_prompt = kwargs["system_prompt"]
84
+ prompt = kwargs["prompt"]
85
+ temperature = float(kwargs["temperature"])
86
+
87
+ # Validate temperature
88
+ if not 0 <= temperature <= 1:
89
+ logger.error(f"Temperature {temperature} is out of range (0-1)")
90
+ raise ValueError("Temperature must be between 0 and 1")
91
+
92
+ logger.info(f"Generating text with model {self.model}, temperature {temperature}")
93
+ try:
94
+ response = await litellm.acompletion(
95
+ model=self.model,
96
+ messages=[
97
+ {"role": "system", "content": system_prompt},
98
+ {"role": "user", "content": prompt}
99
+ ],
100
+ temperature=temperature,
101
+ max_tokens=1000 # Reasonable default for text generation
102
+ )
103
+ generated_text = response.choices[0].message.content.strip()
104
+ logger.debug(f"Generated text: {generated_text}")
105
+ return generated_text
106
+ except Exception as e:
107
+ logger.error(f"Failed to generate text with {self.model}: {str(e)}")
108
+ raise RuntimeError(f"Text generation failed: {str(e)}")
109
+
110
+ # Asynchronous function to generate the program
111
+ async def generate_program(task_description: str, tools: List[Tool], model: str, max_tokens: int) -> str:
112
+ """
113
+ Asynchronously generate a Python program that solves a given task using a list of tools.
114
+
115
+ Args:
116
+ task_description (str): A description of the task to be solved.
117
+ tools (List[Tool]): A list of Tool objects available for use.
118
+ model (str): The litellm model to use for code generation.
119
+ max_tokens (int): Maximum number of tokens for the generated response.
120
+
121
+ Returns:
122
+ str: A string containing a complete Python program.
123
+ """
124
+ logger.debug(f"Generating program for task: {task_description}")
125
+ # Collect tool docstrings
126
+ tool_docstrings = "\n\n".join([tool.to_docstring() for tool in tools])
127
+
128
+ # Construct the prompt for litellm
129
+ prompt = f"""
130
+ You are a Python code generator. Your task is to create a Python program that solves the following task:
131
+ "{task_description}"
132
+
133
+ You have access to the following pre-defined async tool functions, as defined with their signatures and descriptions:
134
+
135
+ {tool_docstrings}
136
+
137
+ Instructions:
138
+ 1. Generate a Python program as a single string.
139
+ 2. Include only the import for asyncio (import asyncio).
140
+ 3. Define an async function named main() that solves the task.
141
+ 4. Use the pre-defined tool functions (e.g., add_tool, multiply_tool, concat_tool) directly by calling them with await and the appropriate arguments as specified in their descriptions.
142
+ 5. Do not redefine the tool functions within the program; assume they are already available in the namespace.
143
+ 6. Return the program as a string enclosed in triple quotes (\"\"\"program\"\"\")).
144
+ 7. Do not include asyncio.run(main()) or any code outside the main() function definition.
145
+ 8. Do not include explanatory text outside the program string.
146
+ 9. Express all string variables as multiline strings (\"\"\"\nstring\n\"\"\"), always start a string at the beginning of a line.
147
+ 10. Always print the result at the end of the program.
148
+ 11. Never call the main() function.
149
+ 12. Never use not defined variables, modules, or functions.
150
+
151
+ Example task: "Add 5 and 7 and print the result"
152
+ Example output:
153
+ \"\"\"import asyncio
154
+ async def main():
155
+ result = await add_tool(a=5, b=7)
156
+ print(result)
157
+ \"\"\"
158
+ """
159
+
160
+ logger.debug(f"Prompt sent to litellm:\n{prompt}")
161
+
162
+ try:
163
+ # Call litellm asynchronously to generate the program
164
+ logger.debug(f"Calling litellm with model {model}")
165
+ response = await litellm.acompletion(
166
+ model=model,
167
+ messages=[
168
+ {"role": "system", "content": "You are a Python code generator."},
169
+ {"role": "user", "content": prompt}
170
+ ],
171
+ max_tokens=max_tokens,
172
+ temperature=0.3
173
+ )
174
+ generated_code = response.choices[0].message.content.strip()
175
+ logger.debug("Code generation successful")
176
+ except Exception as e:
177
+ logger.error(f"Failed to generate code: {str(e)}")
178
+ raise typer.BadParameter(f"Failed to generate code with model '{model}': {str(e)}")
179
+
180
+ # Clean up the output
181
+ if generated_code.startswith('"""') and generated_code.endswith('"""'):
182
+ generated_code = generated_code[3:-3]
183
+ elif generated_code.startswith("```python") and generated_code.endswith("```"):
184
+ generated_code = generated_code[9:-3].strip()
185
+
186
+ return generated_code
187
+
188
+ # Async core logic for generate
189
+ async def generate_core(task: str, model: str, max_tokens: int):
190
+ logger.info(f"Starting generate command for task: {task}")
191
+ # Input validation
192
+ if not task.strip():
193
+ logger.error("Task description is empty")
194
+ raise typer.BadParameter("Task description cannot be empty")
195
+ if max_tokens <= 0:
196
+ logger.error("max-tokens must be positive")
197
+ raise typer.BadParameter("max-tokens must be a positive integer")
198
+
199
+ # Initialize tools
200
+ tools = [
201
+ AddTool(),
202
+ MultiplyTool(),
203
+ ConcatTool(),
204
+ AgentTool(model=model) # Include AgentTool with the specified model
205
+ ]
206
+
207
+ # Generate the program
208
+ try:
209
+ program = await generate_program(task, tools, model, max_tokens)
210
+ except Exception as e:
211
+ logger.error(f"Failed to generate program: {str(e)}")
212
+ typer.echo(typer.style(f"Error: {str(e)}", fg=typer.colors.RED))
213
+ raise typer.Exit(code=1)
214
+
215
+ logger.debug(f"Generated program:\n{program}")
216
+ # Output the generated program
217
+ typer.echo(typer.style("Generated Python Program:", fg=typer.colors.GREEN, bold=True))
218
+ typer.echo(program)
219
+
220
+ # Attempt to execute the program using the safe interpreter
221
+ typer.echo("\n" + typer.style("Executing the program safely:", fg=typer.colors.GREEN, bold=True))
222
+ try:
223
+ # Create instances of tools
224
+ add_tool_instance = AddTool()
225
+ multiply_tool_instance = MultiplyTool()
226
+ concat_tool_instance = ConcatTool()
227
+ agent_tool_instance = AgentTool(model=model)
228
+
229
+ # Define the allowed modules
230
+ allowed_modules = ["asyncio"]
231
+
232
+ # Create a namespace with our tools
233
+ namespace = {
234
+ "add_tool": add_tool_instance.async_execute,
235
+ "multiply_tool": multiply_tool_instance.async_execute,
236
+ "concat_tool": concat_tool_instance.async_execute,
237
+ "agent_tool": agent_tool_instance.async_execute,
238
+ }
239
+
240
+ # Parse the program to AST
241
+ program_ast = ast.parse(program)
242
+
243
+ # Extract imports first to ensure they're allowed
244
+ for node in program_ast.body:
245
+ if isinstance(node, ast.Import):
246
+ for name in node.names:
247
+ if name.name not in allowed_modules:
248
+ raise ValueError(f"Import not allowed: {name.name}")
249
+ elif isinstance(node, ast.ImportFrom):
250
+ if node.module not in allowed_modules:
251
+ raise ValueError(f"Import from not allowed: {node.module}")
252
+
253
+ # Find the main function in the AST
254
+ main_func_node = None
255
+ for node in program_ast.body:
256
+ if isinstance(node, ast.AsyncFunctionDef) and node.name == "main":
257
+ main_func_node = node
258
+ break
259
+
260
+ if not main_func_node:
261
+ logger.warning("No async main() function found in generated code")
262
+ typer.echo(typer.style("Warning: No async main() function found", fg=typer.colors.YELLOW))
263
+ return
264
+
265
+ # Execute the code safely to define functions in the namespace
266
+ # Use a custom ASTInterpreter directly instead of calling interpret_code
267
+ interpreter = ASTInterpreter(allowed_modules=allowed_modules, source=program)
268
+
269
+ # Add tools to the interpreter's environment
270
+ interpreter.env_stack[0].update({
271
+ "add_tool": add_tool_instance.async_execute,
272
+ "multiply_tool": multiply_tool_instance.async_execute,
273
+ "concat_tool": concat_tool_instance.async_execute,
274
+ "agent_tool": agent_tool_instance.async_execute,
275
+ })
276
+
277
+ # First, execute all non-main code (imports and other top-level code)
278
+ for node in program_ast.body:
279
+ if not isinstance(node, ast.AsyncFunctionDef) or node.name != "main":
280
+ await interpreter.visit(node, wrap_exceptions=True)
281
+
282
+ # Then execute the main function definition
283
+ for node in program_ast.body:
284
+ if isinstance(node, ast.AsyncFunctionDef) and node.name == "main":
285
+ await interpreter.visit(node, wrap_exceptions=True)
286
+
287
+ # Get the main function from the environment
288
+ main_func = interpreter.get_variable("main")
289
+
290
+ # Execute the main function in the current event loop
291
+ logger.debug("Executing main() function")
292
+ await main_func()
293
+
294
+ logger.info("Program executed successfully")
295
+ typer.echo(typer.style("Execution completed successfully", fg=typer.colors.GREEN))
296
+
297
+ except SyntaxError as e:
298
+ logger.error(f"Syntax error in generated code: {e}")
299
+ typer.echo(typer.style(f"Syntax error: {e}", fg=typer.colors.RED))
300
+ except Exception as e:
301
+ logger.error(f"Execution error: {e}")
302
+ typer.echo(typer.style(f"Execution failed: {e}", fg=typer.colors.RED))
303
+
304
+ # Synchronous callback to invoke async generate_core
305
+ @app.callback(invoke_without_command=True)
306
+ def generate(
307
+ task: str = typer.Argument(
308
+ ...,
309
+ help="The task description to generate a program for (e.g., 'Add 5 and 7 and print the result')"
310
+ ),
311
+ model: str = typer.Option(
312
+ "gemini/gemini-2.0-flash",
313
+ "--model",
314
+ "-m",
315
+ help="The litellm model to use for generation (e.g., 'gpt-3.5-turbo', 'gpt-4')"
316
+ ),
317
+ max_tokens: int = typer.Option(
318
+ 4000,
319
+ "--max-tokens",
320
+ "-t",
321
+ help="Maximum number of tokens for the generated response (default: 4000)"
322
+ )
323
+ ):
324
+ """
325
+ Asynchronously generate a Python program based on a task description using specified tools and model.
326
+ Executes the program in a controlled, safe environment.
327
+
328
+ Examples:
329
+ $ python action_gen_safe.py "Add 5 and 7 and print the result"
330
+ $ python action_gen_safe.py "Concatenate 'Hello' and 'World' and print it" --model gpt-4 --max-tokens 5000
331
+ """
332
+ asyncio.run(generate_core(task, model, max_tokens))
333
+
334
+ # Entry point to start the app
335
+ def main():
336
+ logger.debug("Starting script execution")
337
+ app()
338
+
339
+ if __name__ == "__main__":
340
+ main()
@@ -4,50 +4,56 @@ import os
4
4
  from pathlib import Path
5
5
 
6
6
  from loguru import logger
7
+ from pydantic import Field
8
+
7
9
  from quantalogic.tools.tool import Tool, ToolArgument
8
10
 
9
11
 
10
12
  class WriteFileTool(Tool):
11
- """Tool for writing a text file in /tmp directory."""
13
+ """Tool for writing a text file to a specified path."""
12
14
 
13
15
  name: str = "write_file_tool"
14
- description: str = "Writes a file with the given content in /tmp directory. The tool will fail if the file already exists when not used in append mode."
16
+ description: str = "Writes a file to the specified path. The tool will fail if the file already exists when not used in append mode."
15
17
  need_validation: bool = True
16
- arguments: list = [
17
- ToolArgument(
18
- name="file_path",
19
- arg_type="string",
20
- description="The name of the file to write in /tmp directory. Can include subdirectories within /tmp.",
21
- required=True,
22
- example="/tmp/myfile.txt or myfile.txt",
23
- ),
24
- ToolArgument(
25
- name="content",
26
- arg_type="string",
27
- description="""
28
- The content to write to the file. Use CDATA to escape special characters.
29
- Don't add newlines at the beginning or end of the content.
30
- """,
31
- required=True,
32
- example="Hello, world!",
33
- ),
34
- ToolArgument(
35
- name="append_mode",
36
- arg_type="string",
37
- description="""Append mode. If true, the content will be appended to the end of the file.
38
- """,
39
- required=False,
40
- example="False",
41
- ),
42
- ToolArgument(
43
- name="overwrite",
44
- arg_type="string",
45
- description="Overwrite mode. If true, existing files can be overwritten. Defaults to False.",
46
- required=False,
47
- example="False",
48
- default="False",
49
- ),
50
- ]
18
+
19
+ disable_ensure_tmp_path: bool = Field(default=False)
20
+
21
+ arguments: list = Field(
22
+ default=[
23
+ ToolArgument(
24
+ name="file_path",
25
+ arg_type="string",
26
+ description="The path to the file to write. By default, paths will be forced to /tmp directory unless disable_ensure_tmp_path is enabled. Can include subdirectories.",
27
+ required=True,
28
+ example="/tmp/myfile.txt or ./myfile.txt",
29
+ ),
30
+ ToolArgument(
31
+ name="content",
32
+ arg_type="string",
33
+ description="""The content to write to the file. Use CDATA to escape special characters.
34
+ Don't add newlines at the beginning or end of the content.
35
+ """,
36
+ required=True,
37
+ example="Hello, world!",
38
+ ),
39
+ ToolArgument(
40
+ name="append_mode",
41
+ arg_type="string",
42
+ description="""Append mode. If true, the content will be appended to the end of the file.
43
+ """,
44
+ required=False,
45
+ example="False",
46
+ ),
47
+ ToolArgument(
48
+ name="overwrite",
49
+ arg_type="string",
50
+ description="Overwrite mode. If true, existing files can be overwritten. Defaults to False.",
51
+ required=False,
52
+ example="False",
53
+ default="False",
54
+ ),
55
+ ],
56
+ )
51
57
 
52
58
  def _ensure_tmp_path(self, file_path: str) -> str:
53
59
  """Ensures the file path is within /tmp directory.
@@ -99,12 +105,10 @@ class WriteFileTool(Tool):
99
105
  overwrite_bool = overwrite.lower() in ["true", "1", "yes"]
100
106
 
101
107
  # Ensure path is in /tmp and normalize it
102
- file_path = self._ensure_tmp_path(file_path)
103
-
104
- # Ensure parent directory exists (only within /tmp)
105
- parent_dir = os.path.dirname(file_path)
106
- if parent_dir.startswith("/tmp/"):
107
- os.makedirs(parent_dir, exist_ok=True)
108
+ if not self.disable_ensure_tmp_path:
109
+ file_path = self._ensure_tmp_path(file_path)
110
+ if not file_path.startswith('/tmp/'):
111
+ raise ValueError('File path must be under /tmp when disable_ensure_tmp_path is False')
108
112
 
109
113
  # Determine file write mode based on append_mode
110
114
  mode = "a" if append_mode_bool else "w"
@@ -115,6 +119,8 @@ class WriteFileTool(Tool):
115
119
  f"File {file_path} already exists. Set append_mode=True to append or overwrite=True to overwrite."
116
120
  )
117
121
 
122
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
123
+
118
124
  with open(file_path, mode, encoding="utf-8") as f:
119
125
  f.write(content)
120
126
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantalogic
3
- Version: 0.61.1
3
+ Version: 0.61.3
4
4
  Summary: QuantaLogic ReAct Agents
5
5
  Author: Raphaël MANSUY
6
6
  Author-email: raphael.mansuy@gmail.com
@@ -22,7 +22,7 @@ Requires-Dist: google-search-results (>=2.4.2,<3.0.0)
22
22
  Requires-Dist: html2text (>=2024.2.26,<2025.0.0)
23
23
  Requires-Dist: instructor (>=1.7.2,<2.0.0)
24
24
  Requires-Dist: jinja2 (>=3.1.5,<4.0.0)
25
- Requires-Dist: litellm (>=1.59.0,<2.0.0)
25
+ Requires-Dist: litellm (>=1.63.14,<2.0.0)
26
26
  Requires-Dist: loguru (>=0.7.3,<0.8.0)
27
27
  Requires-Dist: markdownify (>=0.14.1,<0.15.0)
28
28
  Requires-Dist: markitdown (>=0.0.1a3,<0.0.2)
@@ -34,6 +34,7 @@ Requires-Dist: psutil (>=7.0.0,<8.0.0)
34
34
  Requires-Dist: pydantic (>=2.10.4,<3.0.0)
35
35
  Requires-Dist: pytest-asyncio (>=0.25.3,<0.26.0)
36
36
  Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
37
+ Requires-Dist: quantalogic-pythonbox (>=0.8.3,<0.9.0)
37
38
  Requires-Dist: requests (>=2.32.3,<3.0.0)
38
39
  Requires-Dist: rich (>=13.9.4,<14.0.0)
39
40
  Requires-Dist: serpapi (>=0.1.5,<0.2.0)
@@ -388,7 +389,7 @@ The **Flow module** is your architect—building workflows that hum with precisi
388
389
 
389
390
  🔍 **Want to dive deeper?** Check out our comprehensive [Workflow YAML DSL Specification](./quantalogic/flow/flow_yaml.md), a detailed guide that walks you through defining powerful, structured workflows. From basic node configurations to complex transition logic, this documentation is your roadmap to mastering workflow design with QuantaLogic.
390
391
 
391
- 📚 **For a deeper understanding of Flow YAML and its applications, please refer to the official [Flow YAML Documentation](https://quantalogic.github.io/quantalogic/flow/flow_yaml).**
392
+ 📚 **For a deeper understanding of Flow YAML and its applications, please refer to the official [Flow YAMAL Documentation](./quantalogic/flow/flow_yaml.md).**
392
393
 
393
394
  The Flow YAML documentation provides a comprehensive overview of the Flow YAML language, including its syntax, features, and best practices. It's a valuable resource for anyone looking to create complex workflows with QuantaLogic.
394
395