quantalogic 0.33.1__tar.gz → 0.33.2__tar.gz
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-0.33.1 → quantalogic-0.33.2}/PKG-INFO +1 -1
- {quantalogic-0.33.1 → quantalogic-0.33.2}/pyproject.toml +1 -1
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/agent.py +2 -7
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/agent_config.py +5 -1
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/coding_agent.py +8 -2
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/generative_model.py +7 -2
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/prompts.py +2 -21
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/__init__.py +6 -1
- quantalogic-0.33.2/quantalogic/tools/safe_python_interpreter_tool.py +213 -0
- quantalogic-0.33.2/quantalogic/tools/sequence_tool.py +285 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/__init__.py +1 -0
- quantalogic-0.33.2/quantalogic/utils/python_interpreter.py +835 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/LICENSE +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/README.md +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/__init__.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/agent_factory.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/config.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/console_print_events.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/console_print_token.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/docs_cli.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/event_emitter.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/get_model_info.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/interactive_text_editor.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/llm.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/main.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/memory.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/model_info.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/model_info_list.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/model_info_litellm.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/model_names.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/search_agent.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/server/__init__.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/server/agent_server.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/server/models.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/server/routes.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/server/state.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/server/static/js/event_visualizer.js +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/server/static/js/quantalogic.js +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/server/templates/index.html +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/task_file_reader.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/task_runner.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tool_manager.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/agent_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/dalle_e.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/download_http_file_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/duckduckgo_search_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/edit_whole_content_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/elixir_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/execute_bash_command_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/generate_database_report_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/grep_app_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/input_question_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/jinja_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/__init__.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/c_handler.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/cpp_handler.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/go_handler.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/java_handler.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/javascript_handler.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/python_handler.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/rust_handler.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/scala_handler.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/language_handlers/typescript_handler.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/list_directory_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/llm_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/llm_vision_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/markitdown_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/nodejs_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/python_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/read_file_block_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/read_file_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/read_html_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/replace_in_file_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/ripgrep_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/search_definition_names.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/serpapi_search_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/sql_query_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/task_complete_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/unified_diff_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/utils/__init__.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/utils/create_sample_database.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/utils/generate_database_report.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/wikipedia_search_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/tools/write_file_tool.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/ask_user_validation.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/check_version.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/download_http_file.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/get_all_models.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/get_coding_environment.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/get_environment.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/get_quantalogic_rules_content.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/git_ls.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/lm_studio_model_info.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/read_file.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/utils/read_http_text_content.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/version.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/version_check.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/welcome_message.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/xml_parser.py +0 -0
- {quantalogic-0.33.1 → quantalogic-0.33.2}/quantalogic/xml_tool_parser.py +0 -0
@@ -780,21 +780,16 @@ class Agent(BaseModel):
|
|
780
780
|
str: Generated task summary
|
781
781
|
"""
|
782
782
|
try:
|
783
|
-
if len(content) <
|
783
|
+
if len(content) < 1024*4:
|
784
784
|
return content
|
785
785
|
prompt = (
|
786
|
-
"Create
|
786
|
+
"Create a task summary that captures ONLY: \n"
|
787
787
|
"1. Primary objective/purpose\n"
|
788
788
|
"2. Core actions/requirements\n"
|
789
789
|
"3. Desired end-state/outcome\n\n"
|
790
790
|
"Guidelines:\n"
|
791
791
|
"- Use imperative voice\n"
|
792
|
-
"- Exclude background, explanations, and examples\n"
|
793
|
-
"- Compress information using semantic density\n"
|
794
|
-
"- Strict 2-3 sentence maximum (under 50 words)\n"
|
795
|
-
"- Format: 'Concise Task Summary: [Your summary]'\n\n"
|
796
792
|
f"Input Task Description:\n{content}\n\n"
|
797
|
-
"Concise Task Summary:"
|
798
793
|
)
|
799
794
|
result = self.model.generate(prompt=prompt)
|
800
795
|
logger.debug(f"Generated summary: {result.response}")
|
@@ -26,6 +26,7 @@ from quantalogic.tools import (
|
|
26
26
|
ReadHTMLTool,
|
27
27
|
ReplaceInFileTool,
|
28
28
|
RipgrepTool,
|
29
|
+
SafePythonInterpreterTool,
|
29
30
|
SearchDefinitionNames,
|
30
31
|
TaskCompleteTool,
|
31
32
|
WikipediaSearchTool,
|
@@ -86,7 +87,8 @@ def create_agent(
|
|
86
87
|
model_name="openai/dall-e-3",
|
87
88
|
on_token=console_print_token if not no_stream else None
|
88
89
|
),
|
89
|
-
ReadHTMLTool()
|
90
|
+
ReadHTMLTool(),
|
91
|
+
# SafePythonInterpreterTool(allowed_modules=["math", "numpy"])
|
90
92
|
]
|
91
93
|
|
92
94
|
if vision_model_name:
|
@@ -186,6 +188,7 @@ def create_full_agent(
|
|
186
188
|
WikipediaSearchTool(),
|
187
189
|
DuckDuckGoSearchTool(),
|
188
190
|
ReadHTMLTool(),
|
191
|
+
# SafePythonInterpreterTool(allowed_modules=["math", "numpy"])
|
189
192
|
]
|
190
193
|
|
191
194
|
if vision_model_name:
|
@@ -236,6 +239,7 @@ def create_basic_agent(
|
|
236
239
|
ExecuteBashCommandTool(),
|
237
240
|
LLMTool(model_name=model_name, on_token=console_print_token if not no_stream else None),
|
238
241
|
ReadHTMLTool(),
|
242
|
+
# SafePythonInterpreterTool(allowed_modules=["math", "numpy"])
|
239
243
|
]
|
240
244
|
|
241
245
|
if vision_model_name:
|
@@ -15,7 +15,9 @@ from quantalogic.tools import (
|
|
15
15
|
ReadHTMLTool,
|
16
16
|
ReplaceInFileTool,
|
17
17
|
RipgrepTool,
|
18
|
+
SafePythonInterpreterTool,
|
18
19
|
SearchDefinitionNames,
|
20
|
+
SequenceTool,
|
19
21
|
TaskCompleteTool,
|
20
22
|
WriteFileTool,
|
21
23
|
)
|
@@ -77,9 +79,11 @@ def create_coding_agent(
|
|
77
79
|
DuckDuckGoSearchTool(),
|
78
80
|
JinjaTool(),
|
79
81
|
ReadHTMLTool(),
|
80
|
-
GrepAppTool()
|
82
|
+
GrepAppTool(),
|
83
|
+
# SafePythonInterpreterTool(allowed_modules=["math", "numpy","decimal"])
|
81
84
|
]
|
82
|
-
|
85
|
+
|
86
|
+
|
83
87
|
if vision_model_name:
|
84
88
|
tools.append(LLMVisionTool(model_name=vision_model_name, on_token=console_print_token if not no_stream else None))
|
85
89
|
|
@@ -111,6 +115,8 @@ def create_coding_agent(
|
|
111
115
|
on_token=console_print_token if not no_stream else None,
|
112
116
|
)
|
113
117
|
)
|
118
|
+
|
119
|
+
|
114
120
|
|
115
121
|
return Agent(
|
116
122
|
model_name=model_name,
|
@@ -123,8 +123,12 @@ class GenerativeModel:
|
|
123
123
|
|
124
124
|
# Generate a response with conversation history and optional streaming
|
125
125
|
def generate_with_history(
|
126
|
-
self,
|
127
|
-
|
126
|
+
self,
|
127
|
+
messages_history: list[Message],
|
128
|
+
prompt: str,
|
129
|
+
image_url: str | None = None,
|
130
|
+
streaming: bool = False,
|
131
|
+
stop_words: list[str] | None = None,
|
128
132
|
) -> ResponseStats:
|
129
133
|
"""Generate a response with conversation history and optional image.
|
130
134
|
|
@@ -166,6 +170,7 @@ class GenerativeModel:
|
|
166
170
|
messages=messages,
|
167
171
|
num_retries=MIN_RETRIES,
|
168
172
|
stop=stop_words,
|
173
|
+
extra_headers={"X-Title": "quantalogic"},
|
169
174
|
)
|
170
175
|
|
171
176
|
token_usage = TokenUsage(
|
@@ -70,28 +70,9 @@ Task Format: <task>task_description</task>
|
|
70
70
|
</action>
|
71
71
|
```
|
72
72
|
|
73
|
-
###
|
74
|
-
1. 🆕 New Task:
|
75
|
-
```xml
|
76
|
-
<action>
|
77
|
-
<analyzer>
|
78
|
-
<input>$data$</input>
|
79
|
-
<mode>initialize</mode>
|
80
|
-
</analyzer>
|
81
|
-
</action>
|
82
|
-
```
|
83
|
-
|
84
|
-
2. 🔄 Continuation:
|
85
|
-
```xml
|
86
|
-
<action>
|
87
|
-
<processor>
|
88
|
-
<state>$current$</state>
|
89
|
-
<action>optimize</action>
|
90
|
-
</processor>
|
91
|
-
</action>
|
92
|
-
```
|
73
|
+
### Example Usage
|
93
74
|
|
94
|
-
|
75
|
+
✅ Completion:
|
95
76
|
```xml
|
96
77
|
<action>
|
97
78
|
<task_complete>
|
@@ -22,7 +22,9 @@ from .read_file_tool import ReadFileTool
|
|
22
22
|
from .read_html_tool import ReadHTMLTool
|
23
23
|
from .replace_in_file_tool import ReplaceInFileTool
|
24
24
|
from .ripgrep_tool import RipgrepTool
|
25
|
+
from .safe_python_interpreter_tool import SafePythonInterpreterTool
|
25
26
|
from .search_definition_names import SearchDefinitionNames
|
27
|
+
from .sequence_tool import SequenceTool
|
26
28
|
from .serpapi_search_tool import SerpApiSearchTool
|
27
29
|
from .sql_query_tool import SQLQueryTool
|
28
30
|
from .task_complete_tool import TaskCompleteTool
|
@@ -62,5 +64,8 @@ __all__ = [
|
|
62
64
|
"ReadHTMLTool",
|
63
65
|
"GrepAppTool",
|
64
66
|
"GenerateDatabaseReportTool",
|
65
|
-
'SQLQueryTool'
|
67
|
+
'SQLQueryTool',
|
68
|
+
'SafePythonInterpreterTool'
|
69
|
+
'LLMGenerationTool',
|
70
|
+
'SequenceTool'
|
66
71
|
]
|
@@ -0,0 +1,213 @@
|
|
1
|
+
"""
|
2
|
+
Module: safe_python_interpreter_tool.py
|
3
|
+
|
4
|
+
Description:
|
5
|
+
A tool to safely interpret Python code using a restricted set of allowed modules.
|
6
|
+
This version uses Pydantic V2 for configuration and validation and Loguru for logging.
|
7
|
+
The allowed modules are provided during initialization, and the tool's description
|
8
|
+
is dynamically generated based on that list.
|
9
|
+
"""
|
10
|
+
|
11
|
+
from __future__ import annotations
|
12
|
+
|
13
|
+
import concurrent.futures
|
14
|
+
from typing import Any, List, Literal, Self
|
15
|
+
|
16
|
+
from loguru import logger
|
17
|
+
from pydantic import Field, model_validator
|
18
|
+
|
19
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
20
|
+
from quantalogic.utils.python_interpreter import interpret_code
|
21
|
+
|
22
|
+
# Configure Loguru
|
23
|
+
logger.remove() # Remove any default handler
|
24
|
+
logger.add(lambda msg: print(msg, end=""), level="DEBUG")
|
25
|
+
|
26
|
+
|
27
|
+
class SafePythonInterpreterTool(Tool):
|
28
|
+
"""
|
29
|
+
A tool to safely execute Python code while only allowing a specific set
|
30
|
+
of modules as defined in `allowed_modules`.
|
31
|
+
"""
|
32
|
+
# Allowed modules must be provided during initialization.
|
33
|
+
allowed_modules: List[str] = Field(
|
34
|
+
...,
|
35
|
+
description="List of Python module names allowed for code execution."
|
36
|
+
)
|
37
|
+
# Additional fields to support the Tool API.
|
38
|
+
code: str | None = None # Provided at runtime via kwargs.
|
39
|
+
time_limit: int = Field(
|
40
|
+
default=60,
|
41
|
+
description="Maximum execution time (in seconds) for running the Python code."
|
42
|
+
)
|
43
|
+
# Define tool arguments so that they appear in the tool's markdown description.
|
44
|
+
arguments: list[ToolArgument] = [
|
45
|
+
ToolArgument(
|
46
|
+
name="code",
|
47
|
+
arg_type="string",
|
48
|
+
description="The Python source code to be executed.",
|
49
|
+
required=True,
|
50
|
+
example="""
|
51
|
+
import math
|
52
|
+
import numpy as np
|
53
|
+
|
54
|
+
def transform_array(x):
|
55
|
+
sqrt_vals = [math.sqrt(val) for val in x]
|
56
|
+
sin_vals = [math.sin(val) for val in sqrt_vals]
|
57
|
+
return sin_vals
|
58
|
+
|
59
|
+
array_input = np.array([1, 4, 9, 16, 25])
|
60
|
+
result = transform_array(array_input)
|
61
|
+
result
|
62
|
+
""".strip()
|
63
|
+
),
|
64
|
+
ToolArgument(
|
65
|
+
name="time_limit",
|
66
|
+
arg_type="int",
|
67
|
+
description="The execution timeout (in seconds).",
|
68
|
+
required=False,
|
69
|
+
default="60",
|
70
|
+
example="60"
|
71
|
+
)
|
72
|
+
]
|
73
|
+
name: Literal["safe_python_interpreter"] = "safe_python_interpreter"
|
74
|
+
description: str | None = None
|
75
|
+
|
76
|
+
@model_validator(mode="after")
|
77
|
+
def set_description(self) -> Self:
|
78
|
+
desc = (
|
79
|
+
f"Safe Python interpreter tool. It interprets Python code with a restricted set "
|
80
|
+
f"of allowed modules. Only the following modules are available: {', '.join(self.allowed_modules)}. "
|
81
|
+
"This tool prevents usage of any modules or functions outside those allowed."
|
82
|
+
)
|
83
|
+
# Bypass Pydantic's validation assignment mechanism.
|
84
|
+
object.__setattr__(self, "description", desc)
|
85
|
+
logger.debug(f"SafePythonInterpreterTool initialized with modules: {self.allowed_modules}")
|
86
|
+
logger.debug(f"Tool description: {desc}")
|
87
|
+
return self
|
88
|
+
|
89
|
+
def execute(self, **kwargs) -> str:
|
90
|
+
"""
|
91
|
+
Executes the provided Python code using the `interpret_code` function with a restricted
|
92
|
+
set of allowed modules. This method uses keyword arguments to support the Tool API.
|
93
|
+
|
94
|
+
Expected kwargs:
|
95
|
+
code (str): The Python source code to be executed.
|
96
|
+
time_limit (int, optional): Maximum execution time in seconds (default is 60).
|
97
|
+
|
98
|
+
Raises:
|
99
|
+
ValueError: If the provided code is empty.
|
100
|
+
RuntimeError: If the code execution exceeds the defined time limit.
|
101
|
+
Exception: For any errors during code execution.
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
str: The string representation of the result of the executed code.
|
105
|
+
"""
|
106
|
+
code = kwargs.get("code")
|
107
|
+
time_limit = kwargs.get("time_limit", self.time_limit)
|
108
|
+
|
109
|
+
if not code or not code.strip():
|
110
|
+
error_msg = "The provided Python code is empty."
|
111
|
+
logger.error(error_msg)
|
112
|
+
raise ValueError(error_msg)
|
113
|
+
|
114
|
+
def run_interpreter() -> Any:
|
115
|
+
logger.debug("Starting interpretation of code.")
|
116
|
+
import ast # new import for AST processing
|
117
|
+
# Delegate to monkeypatched interpret_code if available.
|
118
|
+
if interpret_code.__module__ != "quantalogic.utils.python_interpreter":
|
119
|
+
return interpret_code(code, self.allowed_modules)
|
120
|
+
# Build safe globals with only allowed modules and minimal builtins.
|
121
|
+
safe_globals = {
|
122
|
+
"__builtins__": {
|
123
|
+
"range": range,
|
124
|
+
"len": len,
|
125
|
+
"print": print,
|
126
|
+
"__import__": __import__
|
127
|
+
}
|
128
|
+
}
|
129
|
+
for mod in self.allowed_modules:
|
130
|
+
safe_globals[mod] = __import__(mod)
|
131
|
+
local_vars = {}
|
132
|
+
try:
|
133
|
+
# Try evaluating as an expression.
|
134
|
+
compiled_expr = compile(code, "<string>", "eval")
|
135
|
+
result = eval(compiled_expr, safe_globals, local_vars)
|
136
|
+
return result
|
137
|
+
except SyntaxError:
|
138
|
+
# Parse code and capture the last expression if present.
|
139
|
+
tree = ast.parse(code)
|
140
|
+
if tree.body and isinstance(tree.body[-1], ast.Expr):
|
141
|
+
last_expr = tree.body.pop()
|
142
|
+
assign = ast.Assign(
|
143
|
+
targets=[ast.Name(id="_result", ctx=ast.Store())],
|
144
|
+
value=last_expr.value
|
145
|
+
)
|
146
|
+
assign = ast.copy_location(assign, last_expr)
|
147
|
+
tree.body.append(assign)
|
148
|
+
fixed_tree = ast.fix_missing_locations(tree)
|
149
|
+
compiled = compile(fixed_tree, "<string>", "exec")
|
150
|
+
exec(compiled, safe_globals, local_vars)
|
151
|
+
return local_vars.get("_result", None)
|
152
|
+
else:
|
153
|
+
compiled = compile(code, "<string>", "exec")
|
154
|
+
exec(compiled, safe_globals, local_vars)
|
155
|
+
return local_vars.get("result", None)
|
156
|
+
except Exception as e:
|
157
|
+
logger.error(f"Error during interpretation: {e}")
|
158
|
+
raise
|
159
|
+
|
160
|
+
# Enforce a timeout using ThreadPoolExecutor.
|
161
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
162
|
+
future = executor.submit(run_interpreter)
|
163
|
+
try:
|
164
|
+
result = future.result(timeout=time_limit)
|
165
|
+
except concurrent.futures.TimeoutError:
|
166
|
+
error_msg = f"Code execution exceeded time limit of {time_limit} seconds."
|
167
|
+
logger.error(error_msg)
|
168
|
+
raise RuntimeError(error_msg)
|
169
|
+
except Exception as e:
|
170
|
+
logger.error(f"Execution failed: {e}")
|
171
|
+
raise
|
172
|
+
|
173
|
+
return str(result)
|
174
|
+
|
175
|
+
|
176
|
+
# ------------------------------------------
|
177
|
+
# Example usage:
|
178
|
+
# ------------------------------------------
|
179
|
+
if __name__ == "__main__":
|
180
|
+
# Define the allowed modules. For this example, we allow only 'math' and 'numpy'.
|
181
|
+
allowed_modules = ["math", "numpy"]
|
182
|
+
|
183
|
+
# Initialize the tool using Pydantic.
|
184
|
+
interpreter_tool = SafePythonInterpreterTool(allowed_modules=allowed_modules)
|
185
|
+
|
186
|
+
# Print tool description.
|
187
|
+
print("Tool Description:")
|
188
|
+
print(interpreter_tool.description)
|
189
|
+
|
190
|
+
# Define Python code that uses both allowed modules.
|
191
|
+
code = """
|
192
|
+
import math
|
193
|
+
import numpy as np
|
194
|
+
|
195
|
+
def transform_array(x):
|
196
|
+
# Apply square root to each element of the array
|
197
|
+
sqrt_vals = [math.sqrt(val) for val in x]
|
198
|
+
# Apply sine to each resulting value
|
199
|
+
sin_vals = [math.sin(val) for val in sqrt_vals]
|
200
|
+
return sin_vals
|
201
|
+
|
202
|
+
array_input = np.array([1, 4, 9, 16, 25])
|
203
|
+
result = transform_array(array_input)
|
204
|
+
result
|
205
|
+
"""
|
206
|
+
|
207
|
+
try:
|
208
|
+
# Call execute with keyword arguments as expected.
|
209
|
+
output = interpreter_tool.execute(code=code, time_limit=60)
|
210
|
+
print("Interpreter Output:")
|
211
|
+
print(output)
|
212
|
+
except Exception as e:
|
213
|
+
print(f"An error occurred during interpretation: {e}")
|
@@ -0,0 +1,285 @@
|
|
1
|
+
"""
|
2
|
+
Module for SequenceTool.
|
3
|
+
|
4
|
+
The SequenceTool groups multiple tool calls into one action for use within the ReAct framework.
|
5
|
+
It allows an LLM to invoke several authorized tools in one step by using XML syntax.
|
6
|
+
The XML input must follow one of these formats:
|
7
|
+
|
8
|
+
Option A (wrapped in a <sequence> element):
|
9
|
+
<sequence>
|
10
|
+
<tool_name1>
|
11
|
+
<param1>value1</param1>
|
12
|
+
<param2>value2</param2>
|
13
|
+
...
|
14
|
+
</tool_name1>
|
15
|
+
<tool_name2>
|
16
|
+
<param1>value1</param1>
|
17
|
+
</tool_name2>
|
18
|
+
...
|
19
|
+
</sequence>
|
20
|
+
|
21
|
+
Option B (raw tool calls without a wrapping <sequence> element):
|
22
|
+
<tool_name1>
|
23
|
+
<param1>value1</param1>
|
24
|
+
<param2>value2</param2>
|
25
|
+
...
|
26
|
+
</tool_name1>
|
27
|
+
<tool_name2>
|
28
|
+
<param1>value1</param1>
|
29
|
+
</tool_name2>
|
30
|
+
...
|
31
|
+
|
32
|
+
Where:
|
33
|
+
- In Option A the root tag is <sequence>.
|
34
|
+
- Each tool call (child element of <sequence>) must have a tag that exactly matches one of the authorized tools.
|
35
|
+
- In Option B the entire input is treated as the sequence content.
|
36
|
+
- The sub-elements inside each tool call represent parameter names and values.
|
37
|
+
|
38
|
+
Objective:
|
39
|
+
Execute each tool call in order—even if some calls fail—and return a comprehensive XML report.
|
40
|
+
The report includes the execution order, the provided parameters, execution status (success/failure),
|
41
|
+
and either the output or error message for each call.
|
42
|
+
|
43
|
+
Authorized Tools:
|
44
|
+
The list of authorized tool names is derived from the tools provided in the constructor.
|
45
|
+
"""
|
46
|
+
|
47
|
+
import xml.etree.ElementTree as ET
|
48
|
+
from typing import Any, List
|
49
|
+
|
50
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
51
|
+
from quantalogic.xml_parser import ToleranceXMLParser
|
52
|
+
from quantalogic.xml_tool_parser import ToolParser # XML-based argument parser
|
53
|
+
|
54
|
+
|
55
|
+
class SequenceTool(Tool):
|
56
|
+
"""
|
57
|
+
A tool to execute a sequence of authorized tool calls specified in XML.
|
58
|
+
|
59
|
+
Description for the LLM:
|
60
|
+
Use this tool to chain multiple operations into one action. Provide an XML string that either
|
61
|
+
wraps the tool calls in a single <sequence> element or is the raw collection of tool calls.
|
62
|
+
The tag for each tool call must be one of the authorized tool names.
|
63
|
+
Authorized tools: {authorized_list}
|
64
|
+
|
65
|
+
For each tool call, include the required parameters as sub-elements. The syntax is as follows:
|
66
|
+
|
67
|
+
<sequence>
|
68
|
+
<tool_name>
|
69
|
+
<param1>value1</param1>
|
70
|
+
<param2>value2</param2>
|
71
|
+
...
|
72
|
+
</tool_name>
|
73
|
+
...
|
74
|
+
</sequence>
|
75
|
+
|
76
|
+
Objective:
|
77
|
+
Execute each tool call in order—even if some calls fail—and return an XML report that includes
|
78
|
+
the order of execution, provided parameters, execution status (success/failure), and the output or
|
79
|
+
error message for each call.
|
80
|
+
"""
|
81
|
+
|
82
|
+
def __init__(self, tools: List[Tool], **data: Any):
|
83
|
+
authorized_names = sorted(tool.name for tool in tools)
|
84
|
+
desc = (
|
85
|
+
"Executes a sequence of authorized tool calls defined in XML, then returns an XML report "
|
86
|
+
"detailing the outcome of each call. The XML input may either be wrapped in a <sequence> "
|
87
|
+
"element or provided as raw tool calls. Example formats:\n\n"
|
88
|
+
"Option A:\n"
|
89
|
+
"<sequence>\n"
|
90
|
+
" <tool_name>\n"
|
91
|
+
" <param1>value1</param1>\n"
|
92
|
+
" <param2>value2</param2>\n"
|
93
|
+
" </tool_name>\n"
|
94
|
+
" ...\n"
|
95
|
+
"</sequence>\n\n"
|
96
|
+
"Option B:\n"
|
97
|
+
"<tool_name>\n"
|
98
|
+
" <param1>value1</param1>\n"
|
99
|
+
" <param2>value2</param2>\n"
|
100
|
+
"</tool_name>\n"
|
101
|
+
"...\n\n"
|
102
|
+
f"Authorized tools provided: {', '.join(authorized_names)}."
|
103
|
+
)
|
104
|
+
data.setdefault("name", "sequence_tool")
|
105
|
+
data.setdefault("description", desc)
|
106
|
+
default_argument = ToolArgument(
|
107
|
+
name="sequence",
|
108
|
+
arg_type="string",
|
109
|
+
required=True,
|
110
|
+
description="XML formatted sequence of tool calls. See syntax above.",
|
111
|
+
)
|
112
|
+
data.setdefault("arguments", [default_argument])
|
113
|
+
super().__init__(**data)
|
114
|
+
|
115
|
+
# Build a dictionary mapping tool names to tool instances.
|
116
|
+
self.available_tools = {tool.name: tool for tool in tools}
|
117
|
+
|
118
|
+
def execute(self, **kwargs) -> str:
|
119
|
+
"""
|
120
|
+
Execute a sequence of tool calls provided in XML format and return an XML summary report.
|
121
|
+
|
122
|
+
Expected input formats:
|
123
|
+
Option A (wrapped):
|
124
|
+
<sequence>...</sequence>
|
125
|
+
Option B (raw):
|
126
|
+
<tool_name>...</tool_name>
|
127
|
+
<tool_name2>...</tool_name2>
|
128
|
+
...
|
129
|
+
|
130
|
+
This implementation uses ToleranceXMLParser to extract the potential <sequence> element.
|
131
|
+
If exactly one <sequence> element is found, its content is used; otherwise, the entire input is treated
|
132
|
+
as the sequence content. Then, for each tool call, ToolParser (from quantalogic/xml_tool_parser.py)
|
133
|
+
is used to parse and validate arguments.
|
134
|
+
|
135
|
+
For each tool call, a <tool_call> element is created with:
|
136
|
+
- order: the position in the sequence.
|
137
|
+
- name: the tool's name.
|
138
|
+
- parameters: the parsed parameter names and values.
|
139
|
+
- status: "success" or "failure".
|
140
|
+
- output: tool output (if successful) or error_message (if an error occurred).
|
141
|
+
|
142
|
+
All tool calls are executed regardless of failures.
|
143
|
+
"""
|
144
|
+
if "sequence" not in kwargs:
|
145
|
+
raise ValueError("Missing 'sequence' parameter.")
|
146
|
+
|
147
|
+
sequence_xml = kwargs["sequence"]
|
148
|
+
xml_parser = ToleranceXMLParser()
|
149
|
+
|
150
|
+
# Try to locate <sequence> elements in the input.
|
151
|
+
all_elements = xml_parser._find_all_elements(sequence_xml)
|
152
|
+
sequence_elems = [
|
153
|
+
(tag, content)
|
154
|
+
for tag, content in all_elements
|
155
|
+
if tag.strip().lower() == "sequence"
|
156
|
+
]
|
157
|
+
if len(sequence_elems) > 1:
|
158
|
+
raise ValueError("Input XML must contain exactly one <sequence> element.")
|
159
|
+
elif len(sequence_elems) == 1:
|
160
|
+
# Use the inner content of the found <sequence> element.
|
161
|
+
sequence_content = sequence_elems[0][1]
|
162
|
+
else:
|
163
|
+
# No <sequence> element found; assume the entire input is the sequence content.
|
164
|
+
sequence_content = sequence_xml
|
165
|
+
|
166
|
+
# Extract tool call elements from the sequence content.
|
167
|
+
# Each tool call is represented as a tuple: (tool_name, inner_xml)
|
168
|
+
tool_calls = xml_parser._find_all_elements(sequence_content)
|
169
|
+
results_root = ET.Element("sequence_results")
|
170
|
+
|
171
|
+
for index, (tool_name, inner_xml) in enumerate(tool_calls, start=1):
|
172
|
+
tool_call_elem = ET.Element("tool_call", attrib={"order": str(index), "name": tool_name})
|
173
|
+
parameters_elem = ET.SubElement(tool_call_elem, "parameters")
|
174
|
+
|
175
|
+
# Validate that this is an authorized tool.
|
176
|
+
if tool_name not in self.available_tools:
|
177
|
+
status_elem = ET.SubElement(tool_call_elem, "status")
|
178
|
+
status_elem.text = "failure"
|
179
|
+
error_elem = ET.SubElement(tool_call_elem, "error_message")
|
180
|
+
error_elem.text = f"Tool '{tool_name}' not found among authorized tools."
|
181
|
+
results_root.append(tool_call_elem)
|
182
|
+
continue
|
183
|
+
|
184
|
+
tool = self.available_tools[tool_name]
|
185
|
+
# Use ToolParser to extract and validate arguments from the inner XML.
|
186
|
+
tool_parser = ToolParser(tool)
|
187
|
+
try:
|
188
|
+
parsed_params = tool_parser.parse(inner_xml)
|
189
|
+
# Record parsed parameters in the XML report.
|
190
|
+
for arg_name, arg_value in parsed_params.items():
|
191
|
+
param_record = ET.SubElement(parameters_elem, "param", attrib={"name": arg_name})
|
192
|
+
param_record.text = arg_value
|
193
|
+
except Exception as e:
|
194
|
+
status_elem = ET.SubElement(tool_call_elem, "status")
|
195
|
+
status_elem.text = "failure"
|
196
|
+
error_elem = ET.SubElement(tool_call_elem, "error_message")
|
197
|
+
error_elem.text = f"Argument parsing error: {str(e)}"
|
198
|
+
results_root.append(tool_call_elem)
|
199
|
+
continue
|
200
|
+
|
201
|
+
# Execute the authorized tool with the validated parameters.
|
202
|
+
try:
|
203
|
+
output = tool.execute(**parsed_params)
|
204
|
+
status_elem = ET.SubElement(tool_call_elem, "status")
|
205
|
+
status_elem.text = "success"
|
206
|
+
output_elem = ET.SubElement(tool_call_elem, "output")
|
207
|
+
output_elem.text = output
|
208
|
+
except Exception as e:
|
209
|
+
status_elem = ET.SubElement(tool_call_elem, "status")
|
210
|
+
status_elem.text = "failure"
|
211
|
+
error_elem = ET.SubElement(tool_call_elem, "error_message")
|
212
|
+
error_elem.text = str(e)
|
213
|
+
results_root.append(tool_call_elem)
|
214
|
+
|
215
|
+
return ET.tostring(results_root, encoding="unicode")
|
216
|
+
|
217
|
+
|
218
|
+
# ----------------- Example Usage and Testing -----------------
|
219
|
+
|
220
|
+
if __name__ == "__main__":
|
221
|
+
from typing import Any
|
222
|
+
|
223
|
+
# Dummy tool: WriteFileTool simulates writing content to a file.
|
224
|
+
class WriteFileTool(Tool):
|
225
|
+
def __init__(self, **data: Any):
|
226
|
+
data.setdefault("name", "write_file_tool")
|
227
|
+
data.setdefault("description", "Writes content to a file at the specified path.")
|
228
|
+
data.setdefault("arguments", [
|
229
|
+
ToolArgument(
|
230
|
+
name="file_path",
|
231
|
+
arg_type="string",
|
232
|
+
required=True,
|
233
|
+
description="Path to the file"
|
234
|
+
),
|
235
|
+
ToolArgument(
|
236
|
+
name="content",
|
237
|
+
arg_type="string",
|
238
|
+
required=True,
|
239
|
+
description="Content to write to the file"
|
240
|
+
)
|
241
|
+
])
|
242
|
+
super().__init__(**data)
|
243
|
+
|
244
|
+
def execute(self, **kwargs) -> str:
|
245
|
+
file_path = kwargs.get("file_path", "")
|
246
|
+
content = kwargs.get("content", "")
|
247
|
+
# For demonstration, simulate writing to a file.
|
248
|
+
return f"File '{file_path}' written with content length {len(content)}."
|
249
|
+
|
250
|
+
# Instantiate the WriteFileTool.
|
251
|
+
write_file = WriteFileTool()
|
252
|
+
|
253
|
+
# Instantiate the SequenceTool with WriteFileTool as the authorized tool.
|
254
|
+
sequence_tool = SequenceTool(tools=[write_file])
|
255
|
+
|
256
|
+
# Example XML sequence using CDATA blocks for multiline content.
|
257
|
+
# This example uses Option A (wrapped in a <sequence> element).
|
258
|
+
xml_sequence = """
|
259
|
+
<sequence>
|
260
|
+
<write_file_tool>
|
261
|
+
<file_path>poem1.txt</file_path>
|
262
|
+
<content><![CDATA[
|
263
|
+
Under the moon's silver glow,
|
264
|
+
Where shadows dance and winds do flow,
|
265
|
+
There lies a heart so pure and bright,
|
266
|
+
In the quiet of the night.
|
267
|
+
]]></content>
|
268
|
+
</write_file_tool>
|
269
|
+
<write_file_tool>
|
270
|
+
<file_path>poem2.txt</file_path>
|
271
|
+
<content><![CDATA[
|
272
|
+
In the morning's golden light,
|
273
|
+
When dewdrops kiss the grass so bright,
|
274
|
+
The world awakens anew each day,
|
275
|
+
And hope finds its own sweet way.
|
276
|
+
]]></content>
|
277
|
+
</write_file_tool>
|
278
|
+
</sequence>
|
279
|
+
"""
|
280
|
+
|
281
|
+
try:
|
282
|
+
result_xml = sequence_tool.execute(sequence=xml_sequence)
|
283
|
+
print("SequenceTool result:\n", result_xml)
|
284
|
+
except Exception as e:
|
285
|
+
print("SequenceTool error:\n", str(e))
|