quantalogic 0.61.2__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.
- quantalogic/codeact/agent.py +1 -1
- quantalogic/codeact/cli.py +5 -5
- quantalogic/codeact/tools_manager.py +1 -1
- quantalogic/tools/action_gen_safe.py +340 -0
- quantalogic/tools/write_file_tool.py +7 -8
- {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/METADATA +2 -1
- {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/RECORD +10 -28
- quantalogic/python_interpreter/__init__.py +0 -23
- quantalogic/python_interpreter/assignment_visitors.py +0 -63
- quantalogic/python_interpreter/base_visitors.py +0 -20
- quantalogic/python_interpreter/class_visitors.py +0 -22
- quantalogic/python_interpreter/comprehension_visitors.py +0 -172
- quantalogic/python_interpreter/context_visitors.py +0 -59
- quantalogic/python_interpreter/control_flow_visitors.py +0 -88
- quantalogic/python_interpreter/exception_visitors.py +0 -109
- quantalogic/python_interpreter/exceptions.py +0 -39
- quantalogic/python_interpreter/execution.py +0 -202
- quantalogic/python_interpreter/function_utils.py +0 -386
- quantalogic/python_interpreter/function_visitors.py +0 -209
- quantalogic/python_interpreter/import_visitors.py +0 -28
- quantalogic/python_interpreter/interpreter_core.py +0 -358
- quantalogic/python_interpreter/literal_visitors.py +0 -74
- quantalogic/python_interpreter/misc_visitors.py +0 -148
- quantalogic/python_interpreter/operator_visitors.py +0 -108
- quantalogic/python_interpreter/scope.py +0 -10
- quantalogic/python_interpreter/visit_handlers.py +0 -110
- {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/LICENSE +0 -0
- {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/WHEEL +0 -0
- {quantalogic-0.61.2.dist-info → quantalogic-0.61.3.dist-info}/entry_points.txt +0 -0
quantalogic/codeact/agent.py
CHANGED
@@ -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
|
quantalogic/codeact/cli.py
CHANGED
@@ -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.
|
8
|
-
|
9
|
-
from .
|
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
|
|
@@ -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()
|
@@ -10,10 +10,10 @@ from quantalogic.tools.tool import Tool, ToolArgument
|
|
10
10
|
|
11
11
|
|
12
12
|
class WriteFileTool(Tool):
|
13
|
-
"""Tool for writing a text file
|
13
|
+
"""Tool for writing a text file to a specified path."""
|
14
14
|
|
15
15
|
name: str = "write_file_tool"
|
16
|
-
description: str = "Writes a file
|
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."
|
17
17
|
need_validation: bool = True
|
18
18
|
|
19
19
|
disable_ensure_tmp_path: bool = Field(default=False)
|
@@ -25,7 +25,7 @@ class WriteFileTool(Tool):
|
|
25
25
|
arg_type="string",
|
26
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
27
|
required=True,
|
28
|
-
example="/tmp/myfile.txt or myfile.txt",
|
28
|
+
example="/tmp/myfile.txt or ./myfile.txt",
|
29
29
|
),
|
30
30
|
ToolArgument(
|
31
31
|
name="content",
|
@@ -107,11 +107,8 @@ class WriteFileTool(Tool):
|
|
107
107
|
# Ensure path is in /tmp and normalize it
|
108
108
|
if not self.disable_ensure_tmp_path:
|
109
109
|
file_path = self._ensure_tmp_path(file_path)
|
110
|
-
|
111
|
-
|
112
|
-
parent_dir = os.path.dirname(file_path)
|
113
|
-
if parent_dir.startswith("/tmp/"):
|
114
|
-
os.makedirs(parent_dir, exist_ok=True)
|
110
|
+
if not file_path.startswith('/tmp/'):
|
111
|
+
raise ValueError('File path must be under /tmp when disable_ensure_tmp_path is False')
|
115
112
|
|
116
113
|
# Determine file write mode based on append_mode
|
117
114
|
mode = "a" if append_mode_bool else "w"
|
@@ -122,6 +119,8 @@ class WriteFileTool(Tool):
|
|
122
119
|
f"File {file_path} already exists. Set append_mode=True to append or overwrite=True to overwrite."
|
123
120
|
)
|
124
121
|
|
122
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
123
|
+
|
125
124
|
with open(file_path, mode, encoding="utf-8") as f:
|
126
125
|
f.write(content)
|
127
126
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: quantalogic
|
3
|
-
Version: 0.61.
|
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
|
@@ -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)
|
@@ -3,8 +3,8 @@ quantalogic/agent.py,sha256=VChZXFLEsIIrBtXVQZ-FGZ72GCUXDL3g9br8Vo1t5V8,75072
|
|
3
3
|
quantalogic/agent_config.py,sha256=HNXHmu3BiT4q9i-L2wJKUh6PuiZ_ryTNaB7ga24kaJo,8334
|
4
4
|
quantalogic/agent_factory.py,sha256=soActorasOqs5g6NmlyeEjRYbJceIGGg7wuUS-vA898,6878
|
5
5
|
quantalogic/codeact/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
quantalogic/codeact/agent.py,sha256=
|
7
|
-
quantalogic/codeact/cli.py,sha256=
|
6
|
+
quantalogic/codeact/agent.py,sha256=lF5Cq9e_XrBrGwhEZoNNbV-FxAMM2S12_u2og5W4994,22445
|
7
|
+
quantalogic/codeact/cli.py,sha256=Nxo5Biv08yvA72c860ikKI6Ebp0vlygoVrIMCpYsSxA,10867
|
8
8
|
quantalogic/codeact/constants.py,sha256=_xJ2QwP5wUE9vwSDm0JTSMC4GVjXAaxHTwcZBjDCoKk,244
|
9
9
|
quantalogic/codeact/events.py,sha256=4itxGsz54Te9vim13JC8QhacnLQ8cqtOOIyOWUrKFi0,1530
|
10
10
|
quantalogic/codeact/llm_util.py,sha256=9HqN0prWyAgdt6FYtdzjWoIoPpDavHQ7EIIfJr2Fi8w,2749
|
@@ -12,7 +12,7 @@ quantalogic/codeact/prompts/error_format.j2,sha256=_8fNWxDr3U1Fq8SdzwRIBNsNG1WvP
|
|
12
12
|
quantalogic/codeact/prompts/generate_action.j2,sha256=v0LXFxz6L2EwIPesu_mYSVqyNctjHOCIfdawHCb2rtc,1289
|
13
13
|
quantalogic/codeact/prompts/generate_program.j2,sha256=05_1bAwIJLLBKt4o9c7sRWLHIoiHPyGl9OquieMZT5g,2048
|
14
14
|
quantalogic/codeact/prompts/response_format.j2,sha256=TwO43dEG-3justNigpX4yyzGR1TGS3YDlpJQq8Z5Vf4,355
|
15
|
-
quantalogic/codeact/tools_manager.py,sha256=
|
15
|
+
quantalogic/codeact/tools_manager.py,sha256=Q_BGtqN3E8QGbobtaYMpQXqqTv_eb5HmIoZelleUMeI,4491
|
16
16
|
quantalogic/codeact/utils.py,sha256=B7Xowk4id6Sgoyl0eBJsi6JiKa-BAwnj0a5z70vrV1M,5308
|
17
17
|
quantalogic/coding_agent.py,sha256=WFfabRwwPZFV3Pw3seLKpSrFE9Li4pz8Z8mCdsUIDi8,5532
|
18
18
|
quantalogic/config.py,sha256=bmPI2rvQ9M8Zdl1H7K588JgJdnkxSE0L5_i5aBqsagA,564
|
@@ -57,25 +57,6 @@ quantalogic/prompts/task_summary_prompt.j2,sha256=fcjV96nqi6jsfR8l6uyep20rCaOi-z
|
|
57
57
|
quantalogic/prompts/tools_prompt.j2,sha256=ZjvTAZtAk-FmzDb1QuyDJg1hzo-FqayQ7wN_ytHAi2s,385
|
58
58
|
quantalogic/prompts/variables_prompt.j2,sha256=N3OsMbzy3PfEUqYZVF_34wVidhh2ghVToSgdRCv2qgM,231
|
59
59
|
quantalogic/prompts.py,sha256=zqmyosq4hdYpzHI-FG0m5CKV2wQnjgyMJf6Vo1ZaLW4,1982
|
60
|
-
quantalogic/python_interpreter/__init__.py,sha256=cNYxH9jy5QEAPZo44zmJzy3Kci14e0lL2h20Cbrs7RQ,675
|
61
|
-
quantalogic/python_interpreter/assignment_visitors.py,sha256=t6ttdYuwjcz8VtqJS5vMaaUUG6GbEhoNmnNDUj8dZiI,2771
|
62
|
-
quantalogic/python_interpreter/base_visitors.py,sha256=aD1pc3DbdlPdjQ3yJTYhBqNpN6tant7NOHXShts8coo,765
|
63
|
-
quantalogic/python_interpreter/class_visitors.py,sha256=FUgBVg_TmpAdLdLlYlPnruSm8iViijW3gTBQveJjkpI,862
|
64
|
-
quantalogic/python_interpreter/comprehension_visitors.py,sha256=AdPn047QUeoG5D1VD4wU2JuXS2KBTwLOIGbApmx9s7A,8468
|
65
|
-
quantalogic/python_interpreter/context_visitors.py,sha256=_4-Xfk0wzsla2rSIJZYEyYK4yIK7yRZWX6-HNBFSWTs,2137
|
66
|
-
quantalogic/python_interpreter/control_flow_visitors.py,sha256=trJ0Led1pPWKyseQF7BbIF5LFopkOiRDSVHz9Hi7-hI,3662
|
67
|
-
quantalogic/python_interpreter/exception_visitors.py,sha256=dbRuk2CaEZN-tYrzcRxZQh_UdLfpcW7wwrG8iQoGeE8,4571
|
68
|
-
quantalogic/python_interpreter/exceptions.py,sha256=mL4G9a0PZrvW_hAjkweMohvN8ryZsZOiIOolZUDSq9Y,1296
|
69
|
-
quantalogic/python_interpreter/execution.py,sha256=bggzv-GMnFe0Pkt00T4IlPUnDMsuqcHO1Qo0Wq6rEbs,8420
|
70
|
-
quantalogic/python_interpreter/function_utils.py,sha256=1LCEqCDt0p0uBTeC6rcre84r_j06jc6HuWLZ5Fc8m8M,18133
|
71
|
-
quantalogic/python_interpreter/function_visitors.py,sha256=WcGBWXZ_A7vekkrejL2XTuVPyqsKymK4svJr6-kR-OI,11317
|
72
|
-
quantalogic/python_interpreter/import_visitors.py,sha256=AgqFM8SO1wuJluDRsDGBwm-FQ5K374pQrXgbXXEBgPI,1331
|
73
|
-
quantalogic/python_interpreter/interpreter_core.py,sha256=PV_XFx0xhUG9ACvhz3FGat2FSaXK3C031HjRizHbox0,16646
|
74
|
-
quantalogic/python_interpreter/literal_visitors.py,sha256=TX9-02rn093OFTDvKkhp0PbTsXqIU1voBfbxs-TXfu0,3715
|
75
|
-
quantalogic/python_interpreter/misc_visitors.py,sha256=_olOaGD04icqKDqeEFUPIY5Nq0cH5pdGkq-8T6xbilM,6758
|
76
|
-
quantalogic/python_interpreter/operator_visitors.py,sha256=WTf4EfwE88lMoTKjuL8UBNpbwJ8Z6DTdrwvVffVw-Ic,4048
|
77
|
-
quantalogic/python_interpreter/scope.py,sha256=HSSbtDAMeDbE5LjSckTHvFlhcOUGv8UzxpI4NgUbp5U,257
|
78
|
-
quantalogic/python_interpreter/visit_handlers.py,sha256=dGh7BxwGArnBnmwxVzpV6l_uXQ5J6esUm2xKtXne30M,2610
|
79
60
|
quantalogic/quantlitellm.py,sha256=nf-awyOxP0ANoAPGHNvHfdLu8gNn65L39gl7x4saIQc,5550
|
80
61
|
quantalogic/search_agent.py,sha256=tr0cwscJ4wu_G1aumjFyvGHQ0eQv5OL5sxj17s6Ocls,2470
|
81
62
|
quantalogic/server/__init__.py,sha256=8sz_PYAUCrkM6JM5EAUeIzNM4NPW6j6UT72JVkc21WQ,91
|
@@ -91,6 +72,7 @@ quantalogic/task_runner.py,sha256=NB7TqNuwCstCAsTkjGcJSQRapNk8-iXe7d_2qf-fs1s,15
|
|
91
72
|
quantalogic/tool_manager.py,sha256=vNA7aBKgdU3wpw_goom6i9rg_64pNZapNxvg4cUhhCI,6983
|
92
73
|
quantalogic/tools/__init__.py,sha256=NU_M6VYYaAbSUtb2Qdu1lsYaDh0G3f_8jnrZTsBD0eY,2390
|
93
74
|
quantalogic/tools/action_gen.py,sha256=M16voPq7tMR1qiCKTaZAusmVchFbBIluuBbLQH9kEG4,15580
|
75
|
+
quantalogic/tools/action_gen_safe.py,sha256=7doUtPnV9oxBc02N_N-xcMxmfT9Nd6gHfkNU7MtwVUk,13996
|
94
76
|
quantalogic/tools/agent_tool.py,sha256=MXCXxWHRch7VK4UWhtRP1jeI8Np9Ne2CUGo8vm1oZiM,3064
|
95
77
|
quantalogic/tools/composio/__init__.py,sha256=Yo9ygNx0qQILVhIfRgqpO8fgnCgp5WoZMd3Hm5D20GY,429
|
96
78
|
quantalogic/tools/composio/composio.py,sha256=icVHA_Scr1pViBhahGGBGBRBl9JSB3hGSqpgQzAIUH8,17627
|
@@ -188,7 +170,7 @@ quantalogic/tools/utils/generate_database_report.py,sha256=IU_XGTDNXfJXxzpHR1F4c
|
|
188
170
|
quantalogic/tools/web_navigation/__init__.py,sha256=O7SkVqbGwN4zt8Sm3H8AHF9451FSgI5h0J3fDj1rFS4,142
|
189
171
|
quantalogic/tools/web_navigation/web_tool.py,sha256=AxAxQLUNwCElqxP2ceOOjHVY80ck-Md-uNsjHdR9ErA,4721
|
190
172
|
quantalogic/tools/wikipedia_search_tool.py,sha256=LXQSPH8961Efw2QNxKe-cD5ZiIYD3ufEgrxH4y5uB74,5180
|
191
|
-
quantalogic/tools/write_file_tool.py,sha256=
|
173
|
+
quantalogic/tools/write_file_tool.py,sha256=cOheTr2ZTWNbPHlB1PRhc1btJ2mW8TvE5PLQFIdbUb8,5578
|
192
174
|
quantalogic/utils/__init__.py,sha256=E442CJQuTohKzgI0Wrd4NZEpKascFjz6F4Vy8Y1c_0Y,634
|
193
175
|
quantalogic/utils/ask_user_validation.py,sha256=kSr7TXPTpsLR9zgwpGWgvffx8-cKAC_rdFRdLqwC22A,1176
|
194
176
|
quantalogic/utils/async_utils.py,sha256=FOizWRbHdsZwoD36dNErzunfwPlE7zDprS6RXcWuWSo,963
|
@@ -209,8 +191,8 @@ quantalogic/version_check.py,sha256=JyQFTNMDWtpHCLnN-BiakzB2cyXf6kUFsTjvmSruZi4,
|
|
209
191
|
quantalogic/welcome_message.py,sha256=o4tHdgabNuIV9kbIDPgS3_2yzJhayK30oKad2UouYDc,3020
|
210
192
|
quantalogic/xml_parser.py,sha256=bLLwIwO-VEHWF3heNS7nuPC8wgdYw9F_fVZZNW1figY,11728
|
211
193
|
quantalogic/xml_tool_parser.py,sha256=hGHA1q20JUoTNTbZYmi4FTdA5I25-AGEIP8DwZgQCNA,3897
|
212
|
-
quantalogic-0.61.
|
213
|
-
quantalogic-0.61.
|
214
|
-
quantalogic-0.61.
|
215
|
-
quantalogic-0.61.
|
216
|
-
quantalogic-0.61.
|
194
|
+
quantalogic-0.61.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
195
|
+
quantalogic-0.61.3.dist-info/METADATA,sha256=6VPi8J-lWUhZsEKVuCKFuJaWChJ2bHRO6qCQN47I_Cw,33029
|
196
|
+
quantalogic-0.61.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
197
|
+
quantalogic-0.61.3.dist-info/entry_points.txt,sha256=h74O_Q3qBRCrDR99qvwB4BpBGzASPUIjCfxHq6Qnups,183
|
198
|
+
quantalogic-0.61.3.dist-info/RECORD,,
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# quantalogic/utils/__init__.py
|
2
|
-
from .exceptions import BreakException, ContinueException, ReturnException, WrappedException, has_await
|
3
|
-
from .execution import AsyncExecutionResult, execute_async, interpret_ast, interpret_code
|
4
|
-
from .function_utils import AsyncFunction, Function, LambdaFunction
|
5
|
-
from .interpreter_core import ASTInterpreter
|
6
|
-
from .scope import Scope
|
7
|
-
|
8
|
-
__all__ = [
|
9
|
-
'ASTInterpreter',
|
10
|
-
'execute_async',
|
11
|
-
'interpret_ast',
|
12
|
-
'interpret_code',
|
13
|
-
'AsyncExecutionResult',
|
14
|
-
'ReturnException',
|
15
|
-
'BreakException',
|
16
|
-
'ContinueException',
|
17
|
-
'WrappedException',
|
18
|
-
'has_await',
|
19
|
-
'Function',
|
20
|
-
'AsyncFunction',
|
21
|
-
'LambdaFunction',
|
22
|
-
'Scope',
|
23
|
-
]
|
@@ -1,63 +0,0 @@
|
|
1
|
-
import ast
|
2
|
-
from typing import Any
|
3
|
-
|
4
|
-
from .interpreter_core import ASTInterpreter
|
5
|
-
|
6
|
-
async def visit_Assign(self: ASTInterpreter, node: ast.Assign, wrap_exceptions: bool = True) -> None:
|
7
|
-
value: Any = await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
8
|
-
for target in node.targets:
|
9
|
-
if isinstance(target, ast.Subscript):
|
10
|
-
obj = await self.visit(target.value, wrap_exceptions=wrap_exceptions)
|
11
|
-
key = await self.visit(target.slice, wrap_exceptions=wrap_exceptions)
|
12
|
-
obj[key] = value
|
13
|
-
else:
|
14
|
-
await self.assign(target, value)
|
15
|
-
|
16
|
-
async def visit_AugAssign(self: ASTInterpreter, node: ast.AugAssign, wrap_exceptions: bool = True) -> Any:
|
17
|
-
if isinstance(node.target, ast.Name):
|
18
|
-
current_val: Any = self.get_variable(node.target.id)
|
19
|
-
else:
|
20
|
-
current_val: Any = await self.visit(node.target, wrap_exceptions=wrap_exceptions)
|
21
|
-
right_val: Any = await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
22
|
-
op = node.op
|
23
|
-
if isinstance(op, ast.Add):
|
24
|
-
result: Any = current_val + right_val
|
25
|
-
elif isinstance(op, ast.Sub):
|
26
|
-
result = current_val - right_val
|
27
|
-
elif isinstance(op, ast.Mult):
|
28
|
-
result = current_val * right_val
|
29
|
-
elif isinstance(op, ast.Div):
|
30
|
-
result = current_val / right_val
|
31
|
-
elif isinstance(op, ast.FloorDiv):
|
32
|
-
result = current_val // right_val
|
33
|
-
elif isinstance(op, ast.Mod):
|
34
|
-
result = current_val % right_val
|
35
|
-
elif isinstance(op, ast.Pow):
|
36
|
-
result = current_val**right_val
|
37
|
-
elif isinstance(op, ast.BitAnd):
|
38
|
-
result = current_val & right_val
|
39
|
-
elif isinstance(op, ast.BitOr):
|
40
|
-
result = current_val | right_val
|
41
|
-
elif isinstance(op, ast.BitXor):
|
42
|
-
result = current_val ^ right_val
|
43
|
-
elif isinstance(op, ast.LShift):
|
44
|
-
result = current_val << right_val
|
45
|
-
elif isinstance(op, ast.RShift):
|
46
|
-
result = current_val >> right_val
|
47
|
-
else:
|
48
|
-
raise Exception("Unsupported augmented operator: " + str(op))
|
49
|
-
await self.assign(node.target, result)
|
50
|
-
return result
|
51
|
-
|
52
|
-
async def visit_AnnAssign(self: ASTInterpreter, node: ast.AnnAssign, wrap_exceptions: bool = True) -> None:
|
53
|
-
value = await self.visit(node.value, wrap_exceptions=wrap_exceptions) if node.value else None
|
54
|
-
annotation = await self.visit(node.annotation, wrap_exceptions=True)
|
55
|
-
if isinstance(node.target, ast.Name):
|
56
|
-
self.type_hints[node.target.id] = annotation
|
57
|
-
if value is not None or node.simple:
|
58
|
-
await self.assign(node.target, value)
|
59
|
-
|
60
|
-
async def visit_NamedExpr(self: ASTInterpreter, node: ast.NamedExpr, wrap_exceptions: bool = True) -> Any:
|
61
|
-
value = await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
62
|
-
await self.assign(node.target, value)
|
63
|
-
return value
|
@@ -1,20 +0,0 @@
|
|
1
|
-
import ast
|
2
|
-
from typing import Any
|
3
|
-
|
4
|
-
from .exceptions import WrappedException
|
5
|
-
from .interpreter_core import ASTInterpreter
|
6
|
-
|
7
|
-
async def visit_Module(self: ASTInterpreter, node: ast.Module, wrap_exceptions: bool = True) -> Any:
|
8
|
-
last_value = None
|
9
|
-
for stmt in node.body:
|
10
|
-
last_value = await self.visit(stmt, wrap_exceptions=True)
|
11
|
-
return last_value
|
12
|
-
|
13
|
-
async def visit_Expr(self: ASTInterpreter, node: ast.Expr, wrap_exceptions: bool = True) -> Any:
|
14
|
-
return await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
15
|
-
|
16
|
-
async def visit_Pass(self: ASTInterpreter, node: ast.Pass, wrap_exceptions: bool = True) -> None:
|
17
|
-
return None
|
18
|
-
|
19
|
-
async def visit_TypeIgnore(self: ASTInterpreter, node: ast.TypeIgnore, wrap_exceptions: bool = True) -> None:
|
20
|
-
pass
|
@@ -1,22 +0,0 @@
|
|
1
|
-
import ast
|
2
|
-
from typing import Any, Dict, List
|
3
|
-
|
4
|
-
from .function_utils import Function
|
5
|
-
from .interpreter_core import ASTInterpreter
|
6
|
-
|
7
|
-
async def visit_ClassDef(self: ASTInterpreter, node: ast.ClassDef, wrap_exceptions: bool = True) -> Any:
|
8
|
-
base_frame = {}
|
9
|
-
self.env_stack.append(base_frame)
|
10
|
-
bases = [await self.visit(base, wrap_exceptions=True) for base in node.bases]
|
11
|
-
try:
|
12
|
-
for stmt in node.body:
|
13
|
-
await self.visit(stmt, wrap_exceptions=True)
|
14
|
-
class_dict = {k: v for k, v in self.env_stack[-1].items() if k not in ["__builtins__"]}
|
15
|
-
cls = type(node.name, tuple(bases), class_dict)
|
16
|
-
for name, value in class_dict.items():
|
17
|
-
if isinstance(value, Function):
|
18
|
-
value.defining_class = cls
|
19
|
-
self.env_stack[-2][node.name] = cls
|
20
|
-
return cls
|
21
|
-
finally:
|
22
|
-
self.env_stack.pop()
|