quantalogic 0.61.2__py3-none-any.whl → 0.80__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 (67) hide show
  1. quantalogic/agent.py +0 -1
  2. quantalogic/codeact/TODO.md +14 -0
  3. quantalogic/codeact/agent.py +400 -421
  4. quantalogic/codeact/cli.py +42 -224
  5. quantalogic/codeact/cli_commands/__init__.py +0 -0
  6. quantalogic/codeact/cli_commands/create_toolbox.py +45 -0
  7. quantalogic/codeact/cli_commands/install_toolbox.py +20 -0
  8. quantalogic/codeact/cli_commands/list_executor.py +15 -0
  9. quantalogic/codeact/cli_commands/list_reasoners.py +15 -0
  10. quantalogic/codeact/cli_commands/list_toolboxes.py +47 -0
  11. quantalogic/codeact/cli_commands/task.py +215 -0
  12. quantalogic/codeact/cli_commands/tool_info.py +24 -0
  13. quantalogic/codeact/cli_commands/uninstall_toolbox.py +43 -0
  14. quantalogic/codeact/config.yaml +21 -0
  15. quantalogic/codeact/constants.py +1 -1
  16. quantalogic/codeact/events.py +12 -5
  17. quantalogic/codeact/examples/README.md +342 -0
  18. quantalogic/codeact/examples/agent_sample.yaml +29 -0
  19. quantalogic/codeact/executor.py +186 -0
  20. quantalogic/codeact/history_manager.py +94 -0
  21. quantalogic/codeact/llm_util.py +3 -22
  22. quantalogic/codeact/plugin_manager.py +92 -0
  23. quantalogic/codeact/prompts/generate_action.j2 +65 -14
  24. quantalogic/codeact/prompts/generate_program.j2 +32 -19
  25. quantalogic/codeact/react_agent.py +318 -0
  26. quantalogic/codeact/reasoner.py +185 -0
  27. quantalogic/codeact/templates/toolbox/README.md.j2 +10 -0
  28. quantalogic/codeact/templates/toolbox/pyproject.toml.j2 +16 -0
  29. quantalogic/codeact/templates/toolbox/tools.py.j2 +6 -0
  30. quantalogic/codeact/templates.py +7 -0
  31. quantalogic/codeact/tools_manager.py +242 -119
  32. quantalogic/codeact/utils.py +16 -89
  33. quantalogic/codeact/xml_utils.py +126 -0
  34. quantalogic/flow/flow.py +151 -41
  35. quantalogic/flow/flow_extractor.py +61 -1
  36. quantalogic/flow/flow_generator.py +34 -6
  37. quantalogic/flow/flow_manager.py +64 -25
  38. quantalogic/flow/flow_manager_schema.py +32 -0
  39. quantalogic/tools/action_gen.py +1 -1
  40. quantalogic/tools/action_gen_safe.py +340 -0
  41. quantalogic/tools/tool.py +531 -109
  42. quantalogic/tools/write_file_tool.py +7 -8
  43. {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/METADATA +3 -2
  44. {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/RECORD +47 -42
  45. {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/WHEEL +1 -1
  46. quantalogic-0.80.dist-info/entry_points.txt +3 -0
  47. quantalogic/python_interpreter/__init__.py +0 -23
  48. quantalogic/python_interpreter/assignment_visitors.py +0 -63
  49. quantalogic/python_interpreter/base_visitors.py +0 -20
  50. quantalogic/python_interpreter/class_visitors.py +0 -22
  51. quantalogic/python_interpreter/comprehension_visitors.py +0 -172
  52. quantalogic/python_interpreter/context_visitors.py +0 -59
  53. quantalogic/python_interpreter/control_flow_visitors.py +0 -88
  54. quantalogic/python_interpreter/exception_visitors.py +0 -109
  55. quantalogic/python_interpreter/exceptions.py +0 -39
  56. quantalogic/python_interpreter/execution.py +0 -202
  57. quantalogic/python_interpreter/function_utils.py +0 -386
  58. quantalogic/python_interpreter/function_visitors.py +0 -209
  59. quantalogic/python_interpreter/import_visitors.py +0 -28
  60. quantalogic/python_interpreter/interpreter_core.py +0 -358
  61. quantalogic/python_interpreter/literal_visitors.py +0 -74
  62. quantalogic/python_interpreter/misc_visitors.py +0 -148
  63. quantalogic/python_interpreter/operator_visitors.py +0 -108
  64. quantalogic/python_interpreter/scope.py +0 -10
  65. quantalogic/python_interpreter/visit_handlers.py +0 -110
  66. quantalogic-0.61.2.dist-info/entry_points.txt +0 -6
  67. {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/LICENSE +0 -0
@@ -0,0 +1,43 @@
1
+ import importlib.metadata
2
+ import subprocess
3
+
4
+ import typer
5
+ from rich.console import Console
6
+
7
+ app = typer.Typer()
8
+
9
+ console = Console()
10
+
11
+ @app.command()
12
+ def uninstall_toolbox(
13
+ toolbox_name: str = typer.Argument(..., help="Name of the toolbox or package to uninstall")
14
+ ) -> None:
15
+ """Uninstall a toolbox by its name or the package name that provides it."""
16
+ try:
17
+ # Get all entry points in the "quantalogic.tools" group
18
+ eps = importlib.metadata.entry_points(group="quantalogic.tools")
19
+
20
+ # Step 1: Check if the input is a toolbox name (entry point name)
21
+ for ep in eps:
22
+ if ep.name == toolbox_name:
23
+ package_name = ep.dist.name
24
+ subprocess.run(["uv", "pip", "uninstall", package_name], check=True)
25
+ console.print(f"[green]Toolbox '{toolbox_name}' (package '{package_name}') uninstalled successfully[/green]")
26
+ return
27
+
28
+ # Step 2: Check if the input is a package name providing any toolbox
29
+ package_eps = [ep for ep in eps if ep.dist.name == toolbox_name]
30
+ if package_eps:
31
+ subprocess.run(["uv", "pip", "uninstall", toolbox_name], check=True)
32
+ toolboxes = ", ".join(ep.name for ep in package_eps)
33
+ console.print(f"[green]Package '{toolbox_name}' providing toolbox(es) '{toolboxes}' uninstalled successfully[/green]")
34
+ return
35
+
36
+ # If neither a toolbox nor a package is found
37
+ console.print(f"[yellow]No toolbox or package '{toolbox_name}' found to uninstall[/yellow]")
38
+ except subprocess.CalledProcessError as e:
39
+ console.print(f"[red]Failed to uninstall: {e}[/red]")
40
+ raise typer.Exit(code=1)
41
+ except Exception as e:
42
+ console.print(f"[red]An error occurred: {e}[/red]")
43
+ raise typer.Exit(code=1)
@@ -0,0 +1,21 @@
1
+ model: "gemini/gemini-2.0-flash"
2
+ max_iterations: 5
3
+ max_history_tokens: 2000
4
+ enabled_toolboxes:
5
+ - math_tools
6
+ reasoner:
7
+ name: "default"
8
+ config:
9
+ temperature: 0.7
10
+ executor:
11
+ name: "default"
12
+ profile: "math_expert"
13
+ personality:
14
+ traits:
15
+ - witty
16
+ - helpful
17
+ tools_config:
18
+ - name: math_tools
19
+ enabled: true
20
+ config:
21
+ precision: "high"
@@ -6,4 +6,4 @@ LOG_FILE = "react_agent.log"
6
6
  DEFAULT_MODEL = "gemini/gemini-2.0-flash"
7
7
  MAX_TOKENS = 4000
8
8
  MAX_GENERATE_PROGRAM_TOKENS = 1000
9
- MAX_HISTORY_TOKENS = 2000
9
+ MAX_HISTORY_TOKENS = 8000
@@ -11,6 +11,7 @@ class Event(BaseModel):
11
11
 
12
12
  class TaskStartedEvent(Event):
13
13
  task_description: str
14
+ system_prompt: str = "" # Added for persistent context
14
15
 
15
16
 
16
17
  class ThoughtGeneratedEvent(Event):
@@ -52,27 +53,33 @@ class TaskCompletedEvent(Event):
52
53
 
53
54
  class StepStartedEvent(Event):
54
55
  step_number: int
56
+ system_prompt: str = "" # Added for persistent context
57
+ task_description: str = "" # Added for persistent context
55
58
 
56
59
 
57
60
  class ToolExecutionStartedEvent(Event):
58
61
  step_number: int
59
62
  tool_name: str
60
- parameters_summary: dict # Summary of tool parameters (e.g., {"param1": "value1"})
63
+ parameters_summary: dict
61
64
 
62
65
 
63
66
  class ToolExecutionCompletedEvent(Event):
64
67
  step_number: int
65
68
  tool_name: str
66
- result_summary: str # Summary of the execution result
69
+ result_summary: str
67
70
 
68
71
 
69
72
  class ToolExecutionErrorEvent(Event):
70
73
  step_number: int
71
74
  tool_name: str
72
- error: str # Error message if execution fails
75
+ error: str
73
76
 
74
77
 
75
- # In quantalogic/codeact/events.py
76
78
  class StreamTokenEvent(Event):
77
79
  token: str
78
- step_number: Optional[int] = None
80
+ step_number: Optional[int] = None
81
+
82
+
83
+ class PromptGeneratedEvent(Event):
84
+ step_number: int
85
+ prompt: str
@@ -0,0 +1,342 @@
1
+ # Quantalogic Agent Configuration YAML Format
2
+
3
+ This document describes the YAML configuration format for the `AgentConfig` class in the Quantalogic framework, used to configure an AI agent’s behavior, tools, and components. The configuration is flexible, supporting both simple setups and advanced customizations, and is fully backward-compatible with earlier versions.
4
+
5
+ ## Overview
6
+
7
+ The YAML configuration file defines the settings for an `Agent` instance, controlling aspects such as the language model, task-solving parameters, tool usage, and agent personality. You can provide this configuration via a file (passed as a string to `Agent`) or directly as arguments to `AgentConfig`. All fields are optional unless specified, with sensible defaults ensuring compatibility with minimal setups.
8
+
9
+ ### Example Minimal Configuration
10
+ ```yaml
11
+ model: "gemini/gemini-2.0-flash"
12
+ max_iterations: 5
13
+ name: "MathBot"
14
+ personality: "witty"
15
+ ```
16
+
17
+ ### Example Advanced Configuration
18
+ ```yaml
19
+ name: "EquationSolver"
20
+ model: "deepseek/deepseek-chat"
21
+ max_iterations: 5
22
+ max_history_tokens: 2000
23
+ profile: "math_expert"
24
+ customizations:
25
+ personality:
26
+ traits:
27
+ - "witty"
28
+ tools_config:
29
+ - name: "math_tools"
30
+ enabled: true
31
+ config:
32
+ precision: "high"
33
+ api_key: "{{ env.MATH_API_KEY }}"
34
+ reasoner:
35
+ name: "default"
36
+ config:
37
+ temperature: 0.7
38
+ executor:
39
+ name: "default"
40
+ config:
41
+ timeout: 300
42
+ sop: |
43
+ Always provide clear, concise answers.
44
+ Prioritize mathematical accuracy.
45
+ ```
46
+
47
+ ## Configuration Fields
48
+
49
+ Below is a detailed breakdown of each field in the YAML configuration, including the new `name` field.
50
+
51
+ ---
52
+
53
+ ### `name`
54
+ - **Description**: A unique identifier or nickname for the agent, included in the system prompt to personalize its identity.
55
+ - **Type**: String (optional)
56
+ - **Default**: `null`
57
+ - **Example**:
58
+ ```yaml
59
+ name: "MathBot"
60
+ ```
61
+ - **Notes**: If provided, the agent introduces itself with this name in the system prompt (e.g., "I am MathBot, an AI assistant.").
62
+
63
+ ---
64
+
65
+ ### `model`
66
+ - **Description**: Specifies the language model used by the agent for reasoning and text generation. Must be compatible with the `litellm` library.
67
+ - **Type**: String
68
+ - **Default**: `"gemini/gemini-2.0-flash"`
69
+ - **Example**:
70
+ ```yaml
71
+ model: "deepseek/deepseek-chat"
72
+ ```
73
+
74
+ ---
75
+
76
+ ### `max_iterations`
77
+ - **Description**: Sets the maximum number of reasoning steps the agent will take to solve a task using the ReAct framework.
78
+ - **Type**: Integer
79
+ - **Default**: `5`
80
+ - **Example**:
81
+ ```yaml
82
+ max_iterations: 10
83
+ ```
84
+
85
+ ---
86
+
87
+ ### `tools`
88
+ - **Description**: A list of pre-instantiated tools (as Python objects) to include in the agent. Typically used programmatically rather than in YAML.
89
+ - **Type**: List of `Tool` or callable objects (optional)
90
+ - **Default**: `null`
91
+ - **Example** (Programmatic):
92
+ ```python
93
+ config = AgentConfig(tools=[my_custom_tool])
94
+ ```
95
+
96
+ ---
97
+
98
+ ### `max_history_tokens`
99
+ - **Description**: Limits the number of tokens stored in the agent’s history, affecting memory usage and context retention.
100
+ - **Type**: Integer
101
+ - **Default**: `8000` (from `MAX_HISTORY_TOKENS`)
102
+ - **Example**:
103
+ ```yaml
104
+ max_history_tokens: 4000
105
+ ```
106
+
107
+ ---
108
+
109
+ ### `toolbox_directory`
110
+ - **Description**: Directory where custom toolbox modules are stored (used for local tool development).
111
+ - **Type**: String
112
+ - **Default**: `"toolboxes"`
113
+ - **Example**:
114
+ ```yaml
115
+ toolbox_directory: "custom_tools"
116
+ ```
117
+
118
+ ---
119
+
120
+ ### `enabled_toolboxes`
121
+ - **Description**: A list of toolbox names to load from registered entry points (e.g., installed Python packages).
122
+ - **Type**: List of strings (optional)
123
+ - **Default**: `null`
124
+ - **Example**:
125
+ ```yaml
126
+ enabled_toolboxes:
127
+ - "math_tools"
128
+ - "text_tools"
129
+ ```
130
+
131
+ ---
132
+
133
+ ### `reasoner_name` (Legacy)
134
+ - **Description**: Specifies the name of the reasoner plugin to use (older format, kept for compatibility).
135
+ - **Type**: String
136
+ - **Default**: `"default"`
137
+ - **Example**:
138
+ ```yaml
139
+ reasoner_name: "advanced_reasoner"
140
+ ```
141
+
142
+ ---
143
+
144
+ ### `executor_name` (Legacy)
145
+ - **Description**: Specifies the name of the executor plugin to use (older format, kept for compatibility).
146
+ - **Type**: String
147
+ - **Default**: `"default"`
148
+ - **Example**:
149
+ ```yaml
150
+ executor_name: "secure_executor"
151
+ ```
152
+
153
+ ---
154
+
155
+ ### `personality`
156
+ - **Description**: Defines the agent’s personality, influencing its system prompt. Can be a simple string (legacy) or a structured dictionary.
157
+ - **Type**: String or Dictionary (optional)
158
+ - **Default**: `null`
159
+ - **Subfields (when dictionary)**:
160
+ - `traits`: List of personality traits (e.g., "witty", "helpful").
161
+ - `tone`: Tone of responses (e.g., "formal", "casual").
162
+ - `humor_level`: Level of humor (e.g., "low", "medium", "high").
163
+ - **Examples**:
164
+ - Simple:
165
+ ```yaml
166
+ personality: "witty"
167
+ ```
168
+ - Structured:
169
+ ```yaml
170
+ personality:
171
+ traits:
172
+ - "witty"
173
+ - "helpful"
174
+ tone: "informal"
175
+ humor_level: "medium"
176
+ ```
177
+
178
+ ---
179
+
180
+ ### `backstory`
181
+ - **Description**: Provides a backstory for the agent, included in the system prompt. Can be a string (legacy) or a dictionary.
182
+ - **Type**: String or Dictionary (optional)
183
+ - **Default**: `null`
184
+ - **Subfields (when dictionary)**:
185
+ - `origin`: Where the agent was created.
186
+ - `purpose`: The agent’s intended purpose.
187
+ - `experience`: Relevant past experience.
188
+ - **Examples**:
189
+ - Simple:
190
+ ```yaml
191
+ backstory: "A seasoned AI assistant."
192
+ ```
193
+ - Structured:
194
+ ```yaml
195
+ backstory:
196
+ origin: "Created by Quantalogic."
197
+ purpose: "Solve complex problems."
198
+ experience: "Trained on 10,000+ math tasks."
199
+ ```
200
+
201
+ ---
202
+
203
+ ### `sop`
204
+ - **Description**: Standard Operating Procedure (SOP) as a multi-line string, guiding the agent’s behavior.
205
+ - **Type**: String (optional)
206
+ - **Default**: `null`
207
+ - **Example**:
208
+ ```yaml
209
+ sop: |
210
+ Always provide clear, concise answers.
211
+ Prioritize accuracy over speed.
212
+ ```
213
+
214
+ ---
215
+
216
+ ### `tools_config`
217
+ - **Description**: Configures specific tools or toolboxes, allowing enabling/disabling and setting properties.
218
+ - **Type**: List of dictionaries (optional)
219
+ - **Default**: `null`
220
+ - **Subfields**:
221
+ - `name`: Name of the tool or toolbox (matches `tool.name` or `tool.toolbox_name`).
222
+ - `enabled`: Boolean to include/exclude the tool/toolbox (default: `true`).
223
+ - Additional key-value pairs: Properties to set on the tool (e.g., `config`, `precision`).
224
+ - **Example**:
225
+ ```yaml
226
+ tools_config:
227
+ - name: "math_tools"
228
+ enabled: true
229
+ config:
230
+ precision: "high"
231
+ - name: "agent_tool"
232
+ enabled: false
233
+ model: "custom_model"
234
+ ```
235
+
236
+ ---
237
+
238
+ ### `reasoner`
239
+ - **Description**: Configures the reasoning component, replacing `reasoner_name` with added flexibility.
240
+ - **Type**: Dictionary (optional)
241
+ - **Default**: `{"name": "default"}`
242
+ - **Subfields**:
243
+ - `name`: Name of the reasoner plugin (e.g., `"default"`).
244
+ - `config`: Dictionary of reasoner-specific settings (e.g., `temperature`, `max_tokens`).
245
+ - **Example**:
246
+ ```yaml
247
+ reasoner:
248
+ name: "default"
249
+ config:
250
+ temperature: 0.7
251
+ max_tokens: 1500
252
+ ```
253
+
254
+ ---
255
+
256
+ ### `executor`
257
+ - **Description**: Configures the execution component, replacing `executor_name`.
258
+ - **Type**: Dictionary (optional)
259
+ - **Default**: `{"name": "default"}`
260
+ - **Subfields**:
261
+ - `name`: Name of the executor plugin (e.g., `"default"`).
262
+ - `config`: Dictionary of executor-specific settings (e.g., `timeout`).
263
+ - **Example**:
264
+ ```yaml
265
+ executor:
266
+ name: "default"
267
+ config:
268
+ timeout: 600
269
+ ```
270
+
271
+ ---
272
+
273
+ ### `profile`
274
+ - **Description**: Selects a predefined agent profile, setting defaults for other fields.
275
+ - **Type**: String (optional)
276
+ - **Default**: `null`
277
+ - **Supported Profiles**:
278
+ - `"math_expert"`: Configures for mathematical tasks.
279
+ - `"creative_writer"`: Configures for creative tasks.
280
+ - **Example**:
281
+ ```yaml
282
+ profile: "math_expert"
283
+ ```
284
+
285
+ ---
286
+
287
+ ### `customizations`
288
+ - **Description**: Overrides or extends profile defaults with custom settings.
289
+ - **Type**: Dictionary (optional)
290
+ - **Default**: `null`
291
+ - **Example**:
292
+ ```yaml
293
+ profile: "math_expert"
294
+ customizations:
295
+ personality:
296
+ traits:
297
+ - "witty"
298
+ tools_config:
299
+ - name: "advanced_calculus"
300
+ enabled: true
301
+ ```
302
+
303
+ ---
304
+
305
+ ## Tool Configuration Details
306
+ (unchanged from previous documentation, included for completeness)
307
+
308
+ ---
309
+
310
+ ## Backward Compatibility
311
+
312
+ The updated configuration format remains fully compatible with older versions:
313
+ - **New Field (`name`)**: Optional, defaults to `null`, so existing configs without `name` work unchanged.
314
+ - **Legacy Fields**: `reasoner_name`, `executor_name`, `personality` (string), and `backstory` (string) are still supported.
315
+ - **Minimal Configs**: A simple config like:
316
+ ```yaml
317
+ model: "gemini/gemini-2.0-flash"
318
+ ```
319
+ functions as before.
320
+
321
+ ---
322
+
323
+ ## Usage
324
+
325
+ ### Example with Name
326
+ ```yaml
327
+ name: "MathWizard"
328
+ model: "gemini/gemini-2.0-flash"
329
+ max_iterations: 5
330
+ profile: "math_expert"
331
+ ```
332
+
333
+ The agent will introduce itself as: "I am MathWizard, an AI assistant with the following personality traits: precise, logical."
334
+
335
+ ---
336
+
337
+ ## Best Practices
338
+
339
+ - **Use `name`**: Assign a unique name to distinguish agents in multi-agent setups or logs.
340
+ - **Profiles**: Leverage `profile` for quick setup, refining with `customizations`.
341
+ - **Secrets**: Use `{{ env.VAR_NAME }}` for sensitive data in `tools_config`.
342
+
@@ -0,0 +1,29 @@
1
+ # Model configuration
2
+ model: "deepseek/deepseek-chat"
3
+ # Task-solving parameters
4
+ max_iterations: 5
5
+ max_history_tokens: 2000
6
+ # Toolbox and tool configuration
7
+ toolbox_directory: "toolboxes"
8
+ enabled_toolboxes:
9
+ - math_tools
10
+ # Component selection
11
+ reasoner:
12
+ name: "default"
13
+ executor:
14
+ name: "default"
15
+ # Agent personality and behavior
16
+ profile: "math_expert"
17
+ customizations:
18
+ personality:
19
+ traits:
20
+ - witty
21
+ tools_config:
22
+ - name: "math_tools"
23
+ enabled: true
24
+ config:
25
+ precision: "high"
26
+ sop: |
27
+ Always provide clear, concise answers.
28
+ Prioritize mathematical accuracy and user satisfaction.
29
+ Inject humor where appropriate to keep interactions engaging.
@@ -0,0 +1,186 @@
1
+ import asyncio
2
+ import types
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Callable, Dict, List, Optional # Added Optional, Any
5
+
6
+ from lxml import etree
7
+ from quantalogic_pythonbox import AsyncExecutionResult, execute_async
8
+
9
+ from quantalogic.tools import Tool
10
+
11
+ from .events import ToolExecutionCompletedEvent, ToolExecutionErrorEvent, ToolExecutionStartedEvent
12
+ from .tools_manager import ToolRegistry
13
+ from .utils import validate_code
14
+ from .xml_utils import XMLResultHandler
15
+
16
+
17
+ class BaseExecutor(ABC):
18
+ """Abstract base class for execution components."""
19
+
20
+ @abstractmethod
21
+ async def execute_action(self, code: str, context_vars: Dict, step: int, timeout: int) -> str:
22
+ pass
23
+
24
+ @abstractmethod
25
+ def register_tool(self, tool: Tool) -> None:
26
+ pass
27
+
28
+
29
+ class Executor(BaseExecutor):
30
+ """Manages action execution and context updates with dynamic tool registration."""
31
+
32
+ def __init__(self, tools: List[Tool], notify_event: Callable, config: Optional[Dict[str, Any]] = None, verbose: bool = True):
33
+ self.registry = ToolRegistry()
34
+ for tool in tools:
35
+ self.registry.register(tool)
36
+ self.tools: Dict[tuple[str, str], Tool] = self.registry.tools
37
+ self.notify_event = notify_event
38
+ self.config = config or {}
39
+ self.verbose = verbose
40
+ self.tool_namespace = self._build_tool_namespace()
41
+
42
+ def _build_tool_namespace(self) -> Dict:
43
+ """Build the namespace with tools grouped by toolbox using SimpleNamespace."""
44
+ if not self.verbose:
45
+ toolboxes = {}
46
+ for (toolbox_name, tool_name), tool in self.tools.items():
47
+ if toolbox_name not in toolboxes:
48
+ toolboxes[toolbox_name] = types.SimpleNamespace()
49
+ setattr(toolboxes[toolbox_name], tool_name, tool.async_execute)
50
+ return {
51
+ "asyncio": asyncio,
52
+ "context_vars": {},
53
+ **toolboxes,
54
+ }
55
+
56
+ def wrap_tool(tool):
57
+ async def wrapped_tool(**kwargs):
58
+ current_step = self.tool_namespace.get("current_step", None)
59
+ parameters_summary = {
60
+ k: str(v)[:100] + "..." if len(str(v)) > 100 else str(v) for k, v in kwargs.items()
61
+ }
62
+ await self.notify_event(
63
+ ToolExecutionStartedEvent(
64
+ event_type="ToolExecutionStarted",
65
+ step_number=current_step,
66
+ tool_name=f"{tool.toolbox_name or 'default'}.{tool.name}",
67
+ parameters_summary=parameters_summary,
68
+ )
69
+ )
70
+ try:
71
+ result = await tool.async_execute(**kwargs)
72
+ result_summary = str(result)[:100] + "..." if len(str(result)) > 100 else str(result)
73
+ await self.notify_event(
74
+ ToolExecutionCompletedEvent(
75
+ event_type="ToolExecutionCompleted",
76
+ step_number=current_step,
77
+ tool_name=f"{tool.toolbox_name or 'default'}.{tool.name}",
78
+ result_summary=result_summary,
79
+ )
80
+ )
81
+ return result
82
+ except Exception as e:
83
+ await self.notify_event(
84
+ ToolExecutionErrorEvent(
85
+ event_type="ToolExecutionError",
86
+ step_number=current_step,
87
+ tool_name=f"{tool.toolbox_name or 'default'}.{tool.name}",
88
+ error=str(e)
89
+ )
90
+ )
91
+ raise
92
+
93
+ return wrapped_tool
94
+
95
+ toolboxes = {}
96
+ for (toolbox_name, tool_name), tool in self.tools.items():
97
+ if toolbox_name not in toolboxes:
98
+ toolboxes[toolbox_name] = types.SimpleNamespace()
99
+ setattr(toolboxes[toolbox_name], tool_name, wrap_tool(tool))
100
+
101
+ return {
102
+ "asyncio": asyncio,
103
+ "context_vars": {},
104
+ **toolboxes,
105
+ }
106
+
107
+ def register_tool(self, tool: Tool) -> None:
108
+ """Register a new tool dynamically at runtime."""
109
+ self.registry.register(tool)
110
+ key = (tool.toolbox_name or "default", tool.name)
111
+ self.tools[key] = tool
112
+ toolbox_name = tool.toolbox_name or "default"
113
+ if toolbox_name not in self.tool_namespace:
114
+ self.tool_namespace[toolbox_name] = types.SimpleNamespace()
115
+ setattr(self.tool_namespace[toolbox_name], tool.name,
116
+ self._wrap_tool(tool) if self.verbose else tool.async_execute)
117
+
118
+ def _wrap_tool(self, tool: Tool) -> Callable:
119
+ """Wrap a tool function to handle execution events (internal use)."""
120
+ async def wrapped_tool(**kwargs):
121
+ current_step = self.tool_namespace.get("current_step", None)
122
+ parameters_summary = {k: str(v)[:100] + "..." if len(str(v)) > 100 else str(v) for k, v in kwargs.items()}
123
+ await self.notify_event(
124
+ ToolExecutionStartedEvent(
125
+ event_type="ToolExecutionStarted",
126
+ step_number=current_step,
127
+ tool_name=f"{tool.toolbox_name or 'default'}.{tool.name}",
128
+ parameters_summary=parameters_summary,
129
+ )
130
+ )
131
+ try:
132
+ result = await tool.async_execute(**kwargs)
133
+ result_summary = str(result)[:100] + "..." if len(str(result)) > 100 else str(result)
134
+ await self.notify_event(
135
+ ToolExecutionCompletedEvent(
136
+ event_type="ToolExecutionCompleted",
137
+ step_number=current_step,
138
+ tool_name=f"{tool.toolbox_name or 'default'}.{tool.name}",
139
+ result_summary=result_summary,
140
+ )
141
+ )
142
+ return result
143
+ except Exception as e:
144
+ await self.notify_event(
145
+ ToolExecutionErrorEvent(
146
+ event_type="ToolExecutionError",
147
+ step_number=current_step,
148
+ tool_name=f"{tool.toolbox_name or 'default'}.{tool.name}",
149
+ error=str(e)
150
+ )
151
+ )
152
+ raise
153
+
154
+ return wrapped_tool
155
+
156
+ async def execute_action(self, code: str, context_vars: Dict, step: int, timeout: int = 300) -> str:
157
+ """Execute the generated code and return the result with local variables, setting the step number."""
158
+ self.tool_namespace["context_vars"] = context_vars
159
+ self.tool_namespace["current_step"] = step
160
+ timeout = self.config.get("timeout", timeout) # Use config timeout if provided
161
+ if not validate_code(code):
162
+ return etree.tostring(
163
+ etree.Element("ExecutionResult", status="Error", message="Code lacks async main()"), encoding="unicode"
164
+ )
165
+
166
+ try:
167
+ result: AsyncExecutionResult = await execute_async(
168
+ code=code,
169
+ timeout=timeout,
170
+ entry_point="main",
171
+ allowed_modules=["asyncio", "math", "random", "time"],
172
+ namespace=self.tool_namespace,
173
+ ignore_typing=True
174
+ )
175
+ if result.local_variables:
176
+ context_vars.update(
177
+ {k: v for k, v in result.local_variables.items() if not k.startswith("__") and not callable(v)}
178
+ )
179
+ return XMLResultHandler.format_execution_result(result)
180
+ except Exception as e:
181
+ root = etree.Element("ExecutionResult")
182
+ root.append(etree.Element("Status", text="Error"))
183
+ root.append(etree.Element("Value", text=f"Execution error: {str(e)}"))
184
+ root.append(etree.Element("ExecutionTime", text="0.00 seconds"))
185
+ root.append(etree.Element("Completed", text="false"))
186
+ return etree.tostring(root, pretty_print=True, encoding="unicode")