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.
- quantalogic/agent.py +0 -1
- quantalogic/codeact/TODO.md +14 -0
- quantalogic/codeact/agent.py +400 -421
- quantalogic/codeact/cli.py +42 -224
- quantalogic/codeact/cli_commands/__init__.py +0 -0
- quantalogic/codeact/cli_commands/create_toolbox.py +45 -0
- quantalogic/codeact/cli_commands/install_toolbox.py +20 -0
- quantalogic/codeact/cli_commands/list_executor.py +15 -0
- quantalogic/codeact/cli_commands/list_reasoners.py +15 -0
- quantalogic/codeact/cli_commands/list_toolboxes.py +47 -0
- quantalogic/codeact/cli_commands/task.py +215 -0
- quantalogic/codeact/cli_commands/tool_info.py +24 -0
- quantalogic/codeact/cli_commands/uninstall_toolbox.py +43 -0
- quantalogic/codeact/config.yaml +21 -0
- quantalogic/codeact/constants.py +1 -1
- quantalogic/codeact/events.py +12 -5
- quantalogic/codeact/examples/README.md +342 -0
- quantalogic/codeact/examples/agent_sample.yaml +29 -0
- quantalogic/codeact/executor.py +186 -0
- quantalogic/codeact/history_manager.py +94 -0
- quantalogic/codeact/llm_util.py +3 -22
- quantalogic/codeact/plugin_manager.py +92 -0
- quantalogic/codeact/prompts/generate_action.j2 +65 -14
- quantalogic/codeact/prompts/generate_program.j2 +32 -19
- quantalogic/codeact/react_agent.py +318 -0
- quantalogic/codeact/reasoner.py +185 -0
- quantalogic/codeact/templates/toolbox/README.md.j2 +10 -0
- quantalogic/codeact/templates/toolbox/pyproject.toml.j2 +16 -0
- quantalogic/codeact/templates/toolbox/tools.py.j2 +6 -0
- quantalogic/codeact/templates.py +7 -0
- quantalogic/codeact/tools_manager.py +242 -119
- quantalogic/codeact/utils.py +16 -89
- quantalogic/codeact/xml_utils.py +126 -0
- quantalogic/flow/flow.py +151 -41
- quantalogic/flow/flow_extractor.py +61 -1
- quantalogic/flow/flow_generator.py +34 -6
- quantalogic/flow/flow_manager.py +64 -25
- quantalogic/flow/flow_manager_schema.py +32 -0
- quantalogic/tools/action_gen.py +1 -1
- quantalogic/tools/action_gen_safe.py +340 -0
- quantalogic/tools/tool.py +531 -109
- quantalogic/tools/write_file_tool.py +7 -8
- {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/METADATA +3 -2
- {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/RECORD +47 -42
- {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/WHEEL +1 -1
- quantalogic-0.80.dist-info/entry_points.txt +3 -0
- 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/entry_points.txt +0 -6
- {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/LICENSE +0 -0
quantalogic/codeact/cli.py
CHANGED
@@ -1,232 +1,50 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
"""Command-line interface entry point for Quantalogic Agent."""
|
2
|
+
|
3
|
+
import importlib
|
4
|
+
from pathlib import Path
|
3
5
|
|
4
6
|
import typer
|
5
7
|
from loguru import logger
|
8
|
+
from rich.console import Console
|
6
9
|
|
7
|
-
from quantalogic.
|
8
|
-
|
9
|
-
from .agent import Agent # Import only Agent from agent.py
|
10
|
-
from .constants import DEFAULT_MODEL, LOG_FILE
|
11
|
-
from .events import ( # Import events from events.py
|
12
|
-
ActionExecutedEvent,
|
13
|
-
ActionGeneratedEvent,
|
14
|
-
ErrorOccurredEvent,
|
15
|
-
StepCompletedEvent,
|
16
|
-
StepStartedEvent,
|
17
|
-
StreamTokenEvent,
|
18
|
-
TaskCompletedEvent,
|
19
|
-
TaskStartedEvent,
|
20
|
-
ThoughtGeneratedEvent,
|
21
|
-
ToolExecutionCompletedEvent,
|
22
|
-
ToolExecutionErrorEvent,
|
23
|
-
ToolExecutionStartedEvent,
|
24
|
-
)
|
25
|
-
from .tools_manager import Tool, get_default_tools
|
26
|
-
from .utils import XMLResultHandler
|
10
|
+
from quantalogic.codeact.plugin_manager import PluginManager
|
27
11
|
|
28
12
|
app = typer.Typer(no_args_is_help=True)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
typer.echo(typer.style(f"Error Occurred{' at Step ' + str(event.step_number) if event.step_number else ''}:", fg=typer.colors.RED, bold=True))
|
65
|
-
typer.echo(f"Message: {event.error_message}")
|
66
|
-
typer.echo(f"Timestamp: {event.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
|
67
|
-
typer.echo("-" * 50)
|
68
|
-
|
69
|
-
async def on_task_completed(self, event: TaskCompletedEvent):
|
70
|
-
if event.final_answer:
|
71
|
-
typer.echo(typer.style("Task Completed Successfully:", fg=typer.colors.GREEN, bold=True))
|
72
|
-
typer.echo(f"Final Answer:\n{event.final_answer}")
|
73
|
-
else:
|
74
|
-
typer.echo(typer.style("Task Did Not Complete Successfully:", fg=typer.colors.RED, bold=True))
|
75
|
-
typer.echo(f"Reason: {event.reason}")
|
76
|
-
typer.echo(f"Timestamp: {event.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
|
77
|
-
typer.echo("-" * 50)
|
78
|
-
|
79
|
-
async def on_step_started(self, event: StepStartedEvent):
|
80
|
-
typer.echo(typer.style(f"Starting Step {event.step_number}", fg=typer.colors.YELLOW, bold=True))
|
81
|
-
typer.echo("-" * 50)
|
82
|
-
|
83
|
-
async def on_tool_execution_started(self, event: ToolExecutionStartedEvent):
|
84
|
-
typer.echo(typer.style(f"Tool {event.tool_name} started execution at step {event.step_number}", fg=typer.colors.CYAN))
|
85
|
-
typer.echo(f"Parameters: {event.parameters_summary}")
|
86
|
-
|
87
|
-
async def on_tool_execution_completed(self, event: ToolExecutionCompletedEvent):
|
88
|
-
typer.echo(typer.style(f"Tool {event.tool_name} completed execution at step {event.step_number}", fg=typer.colors.GREEN))
|
89
|
-
typer.echo(f"Result: {event.result_summary}")
|
90
|
-
|
91
|
-
async def on_tool_execution_error(self, event: ToolExecutionErrorEvent):
|
92
|
-
typer.echo(typer.style(f"Tool {event.tool_name} encountered an error at step {event.step_number}", fg=typer.colors.RED))
|
93
|
-
typer.echo(f"Error: {event.error}")
|
94
|
-
|
95
|
-
async def on_stream_token(self, event: StreamTokenEvent):
|
96
|
-
"""Handle streaming token events for real-time output with buffering."""
|
97
|
-
self._token_buffer += event.token
|
98
|
-
# Check for natural breakpoints to flush the buffer
|
99
|
-
if "\n" in event.token or event.token.strip() in [".", ";", ":", "{", "}", "]", ")"]:
|
100
|
-
# Print the accumulated buffer with proper formatting
|
101
|
-
lines = self._token_buffer.split("\n")
|
102
|
-
for line in lines[:-1]: # Print all complete lines
|
103
|
-
if line.strip():
|
104
|
-
typer.echo(f"{line}", nl=False)
|
105
|
-
typer.echo("") # Add newline after each complete line
|
106
|
-
# Keep the last incomplete line in the buffer
|
107
|
-
self._token_buffer = lines[-1]
|
108
|
-
# Flush buffer if it gets too long (e.g., > 100 chars)
|
109
|
-
if len(self._token_buffer) > 100:
|
110
|
-
typer.echo(f"{self._token_buffer}", nl=False)
|
111
|
-
self._token_buffer = ""
|
112
|
-
|
113
|
-
async def flush_buffer(self):
|
114
|
-
"""Flush any remaining tokens in the buffer."""
|
115
|
-
if self._token_buffer.strip():
|
116
|
-
typer.echo(f"{self._token_buffer}")
|
117
|
-
self._token_buffer = ""
|
118
|
-
|
119
|
-
async def __call__(self, event):
|
120
|
-
"""Dispatch events to their respective handlers."""
|
121
|
-
if isinstance(event, TaskStartedEvent):
|
122
|
-
await self.on_task_started(event)
|
123
|
-
elif isinstance(event, ThoughtGeneratedEvent):
|
124
|
-
await self.on_thought_generated(event)
|
125
|
-
elif isinstance(event, ActionGeneratedEvent):
|
126
|
-
await self.on_action_generated(event)
|
127
|
-
await self.flush_buffer() # Flush after action is fully generated
|
128
|
-
elif isinstance(event, ActionExecutedEvent):
|
129
|
-
await self.on_action_executed(event)
|
130
|
-
elif isinstance(event, StepCompletedEvent):
|
131
|
-
await self.on_step_completed(event)
|
132
|
-
elif isinstance(event, ErrorOccurredEvent):
|
133
|
-
await self.on_error_occurred(event)
|
134
|
-
elif isinstance(event, TaskCompletedEvent):
|
135
|
-
await self.on_task_completed(event)
|
136
|
-
await self.flush_buffer() # Flush at task completion
|
137
|
-
elif isinstance(event, StepStartedEvent):
|
138
|
-
await self.on_step_started(event)
|
139
|
-
elif isinstance(event, ToolExecutionStartedEvent):
|
140
|
-
await self.on_tool_execution_started(event)
|
141
|
-
elif isinstance(event, ToolExecutionCompletedEvent):
|
142
|
-
await self.on_tool_execution_completed(event)
|
143
|
-
elif isinstance(event, ToolExecutionErrorEvent):
|
144
|
-
await self.on_tool_execution_error(event)
|
145
|
-
elif isinstance(event, StreamTokenEvent):
|
146
|
-
await self.on_stream_token(event)
|
147
|
-
|
148
|
-
async def run_react_agent(
|
149
|
-
task: str,
|
150
|
-
model: str,
|
151
|
-
max_iterations: int,
|
152
|
-
success_criteria: Optional[str] = None,
|
153
|
-
tools: Optional[List[Union[Tool, Callable]]] = None,
|
154
|
-
personality: Optional[str] = None,
|
155
|
-
backstory: Optional[str] = None,
|
156
|
-
sop: Optional[str] = None,
|
157
|
-
debug: bool = False,
|
158
|
-
streaming: bool = False # New parameter for enabling streaming
|
159
|
-
) -> None:
|
160
|
-
"""Run the Agent with detailed event monitoring."""
|
161
|
-
# Configure logging: disable stderr output unless debug is enabled
|
162
|
-
logger.remove() # Remove default handler
|
163
|
-
if debug:
|
164
|
-
logger.add(typer.stderr, level="INFO")
|
165
|
-
logger.add(LOG_FILE, level="INFO") # Always log to file
|
166
|
-
|
167
|
-
tools = tools if tools is not None else get_default_tools(model)
|
168
|
-
|
169
|
-
processed_tools = []
|
170
|
-
for tool in tools:
|
171
|
-
if isinstance(tool, Tool):
|
172
|
-
processed_tools.append(tool)
|
173
|
-
elif callable(tool):
|
174
|
-
processed_tools.append(create_tool(tool))
|
175
|
-
else:
|
176
|
-
logger.warning(f"Invalid tool type: {type(tool)}. Skipping.")
|
177
|
-
typer.echo(typer.style(f"Warning: Invalid tool type {type(tool)} skipped.", fg=typer.colors.YELLOW))
|
178
|
-
|
179
|
-
agent = Agent(
|
180
|
-
model=model,
|
181
|
-
tools=processed_tools,
|
182
|
-
max_iterations=max_iterations,
|
183
|
-
personality=personality,
|
184
|
-
backstory=backstory,
|
185
|
-
sop=sop
|
186
|
-
)
|
187
|
-
|
188
|
-
progress_monitor = ProgressMonitor()
|
189
|
-
# Use solve with tools enabled for multi-step tasks
|
190
|
-
solve_agent = agent # Store reference to add observer
|
191
|
-
solve_agent.add_observer(progress_monitor, [
|
192
|
-
"TaskStarted", "ThoughtGenerated", "ActionGenerated", "ActionExecuted",
|
193
|
-
"StepCompleted", "ErrorOccurred", "TaskCompleted", "StepStarted",
|
194
|
-
"ToolExecutionStarted", "ToolExecutionCompleted", "ToolExecutionError",
|
195
|
-
"StreamToken" # Added to observe streaming tokens
|
196
|
-
])
|
197
|
-
|
198
|
-
typer.echo(typer.style(f"Starting task: {task}", fg=typer.colors.GREEN, bold=True))
|
199
|
-
# Pass the streaming flag to the solve method
|
200
|
-
history = await agent.solve(task, success_criteria, streaming=streaming)
|
201
|
-
|
202
|
-
@app.command()
|
203
|
-
def react(
|
204
|
-
task: str = typer.Argument(..., help="The task to solve"),
|
205
|
-
model: str = typer.Option(DEFAULT_MODEL, help="The litellm model to use"),
|
206
|
-
max_iterations: int = typer.Option(5, help="Maximum reasoning steps"),
|
207
|
-
success_criteria: Optional[str] = typer.Option(None, help="Optional criteria to determine task completion"),
|
208
|
-
personality: Optional[str] = typer.Option(None, help="Agent personality (e.g., 'witty')"),
|
209
|
-
backstory: Optional[str] = typer.Option(None, help="Agent backstory"),
|
210
|
-
sop: Optional[str] = typer.Option(None, help="Standard operating procedure"),
|
211
|
-
debug: bool = typer.Option(False, help="Enable debug logging to stderr"),
|
212
|
-
streaming: bool = typer.Option(False, help="Enable streaming output for real-time token generation")
|
213
|
-
) -> None:
|
214
|
-
"""CLI command to run the Agent with detailed event monitoring."""
|
215
|
-
try:
|
216
|
-
asyncio.run(run_react_agent(
|
217
|
-
task, model, max_iterations, success_criteria,
|
218
|
-
personality=personality, backstory=backstory, sop=sop, debug=debug,
|
219
|
-
streaming=streaming # Pass the streaming flag
|
220
|
-
))
|
221
|
-
except Exception as e:
|
222
|
-
logger.error(f"Agent failed: {e}")
|
223
|
-
typer.echo(typer.style(f"Error: {e}", fg=typer.colors.RED))
|
224
|
-
raise typer.Exit(code=1)
|
13
|
+
console = Console()
|
14
|
+
|
15
|
+
# Initialize PluginManager at module level to avoid duplicate loading
|
16
|
+
plugin_manager = PluginManager()
|
17
|
+
plugin_manager.load_plugins() # This is now synchronous
|
18
|
+
|
19
|
+
# Dynamically load CLI commands from cli_commands directory
|
20
|
+
cli_commands_dir = Path(__file__).parent / "cli_commands"
|
21
|
+
for file in cli_commands_dir.glob("*.py"):
|
22
|
+
if file.stem != "__init__":
|
23
|
+
module_name = f"quantalogic.codeact.cli_commands.{file.stem}"
|
24
|
+
try:
|
25
|
+
module = importlib.import_module(module_name)
|
26
|
+
command_name = file.stem.replace("_", "-")
|
27
|
+
# Handle Typer app instances (e.g., list_toolboxes.py)
|
28
|
+
if hasattr(module, "app") and isinstance(module.app, typer.Typer):
|
29
|
+
if len(module.app.registered_commands) == 1:
|
30
|
+
# If there's exactly one command, register it directly
|
31
|
+
command = module.app.registered_commands[0]
|
32
|
+
app.command(name=command_name)(command.callback)
|
33
|
+
logger.debug(f"Loaded direct command: {command_name}")
|
34
|
+
else:
|
35
|
+
# Otherwise, treat it as a subcommand group
|
36
|
+
app.add_typer(module.app, name=command_name)
|
37
|
+
logger.debug(f"Loaded command group: {command_name}")
|
38
|
+
# Handle direct function commands (e.g., task.py)
|
39
|
+
elif hasattr(module, file.stem) and callable(getattr(module, file.stem)):
|
40
|
+
app.command(name=command_name)(getattr(module, file.stem))
|
41
|
+
logger.debug(f"Loaded direct command: {command_name}")
|
42
|
+
except ImportError as e:
|
43
|
+
logger.error(f"Failed to load command module {module_name}: {e}")
|
44
|
+
|
45
|
+
# Load plugin CLI commands dynamically using the module-level plugin_manager
|
46
|
+
for cmd_name, cmd_func in plugin_manager.cli_commands.items():
|
47
|
+
app.command(name=cmd_name)(cmd_func)
|
225
48
|
|
226
49
|
if __name__ == "__main__":
|
227
|
-
app()
|
228
|
-
|
229
|
-
|
230
|
-
# Example how to use the CLI
|
231
|
-
# python -m quantalogic.codeact.cli "Write a poem, then evaluate the quality, in French, afficher le poem et la critique. Tout doit être Français. Si le poême n'est pas de bonne qualité, ré-écrit le poême" --streaming
|
232
|
-
|
50
|
+
app()
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
import typer
|
4
|
+
from jinja2 import Environment, FileSystemLoader
|
5
|
+
from loguru import logger
|
6
|
+
from rich.console import Console
|
7
|
+
|
8
|
+
app = typer.Typer()
|
9
|
+
|
10
|
+
console = Console()
|
11
|
+
|
12
|
+
@app.command()
|
13
|
+
def create_toolbox(
|
14
|
+
name: str = typer.Argument(..., help="Name of the new toolbox (e.g., 'my-toolbox')")
|
15
|
+
) -> None:
|
16
|
+
"""Create a starter toolbox project with the given name using Jinja2 templates."""
|
17
|
+
toolbox_dir = Path(name)
|
18
|
+
if toolbox_dir.exists():
|
19
|
+
logger.error(f"Directory '{name}' already exists.")
|
20
|
+
console.print(f"[red]Error: Directory '{name}' already exists.[/red]")
|
21
|
+
raise typer.Exit(code=1)
|
22
|
+
|
23
|
+
# Set up Jinja2 environment
|
24
|
+
template_dir = Path(__file__).parent.parent / "templates" / "toolbox"
|
25
|
+
env = Environment(loader=FileSystemLoader(template_dir), trim_blocks=True, lstrip_blocks=True)
|
26
|
+
|
27
|
+
# Create directory structure
|
28
|
+
toolbox_dir.mkdir()
|
29
|
+
package_name = name.replace("-", "_")
|
30
|
+
package_dir = toolbox_dir / package_name
|
31
|
+
package_dir.mkdir()
|
32
|
+
(package_dir / "__init__.py").touch()
|
33
|
+
|
34
|
+
# Render and write template files
|
35
|
+
context = {"name": name, "package_name": package_name}
|
36
|
+
for template_name in ["pyproject.toml.j2", "tools.py.j2", "README.md.j2"]:
|
37
|
+
template = env.get_template(template_name)
|
38
|
+
content = template.render(**context)
|
39
|
+
output_file = toolbox_dir / template_name.replace(".j2", "")
|
40
|
+
if template_name == "tools.py.j2":
|
41
|
+
output_file = package_dir / "tools.py"
|
42
|
+
output_file.write_text(content.strip())
|
43
|
+
|
44
|
+
logger.info(f"Created starter toolbox project: {name}")
|
45
|
+
console.print(f"[green]Created starter toolbox project: {name}[/green]")
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import subprocess
|
2
|
+
|
3
|
+
import typer
|
4
|
+
from rich.console import Console
|
5
|
+
|
6
|
+
app = typer.Typer()
|
7
|
+
|
8
|
+
console = Console()
|
9
|
+
|
10
|
+
@app.command()
|
11
|
+
def install_toolbox(
|
12
|
+
toolbox_name: str = typer.Argument(..., help="Name of the toolbox to install (PyPI package or local wheel file)")
|
13
|
+
) -> None:
|
14
|
+
"""Install a toolbox using uv pip install."""
|
15
|
+
try:
|
16
|
+
subprocess.run(["uv", "pip", "install", toolbox_name], check=True)
|
17
|
+
console.print(f"[green]Toolbox '{toolbox_name}' installed successfully[/green]")
|
18
|
+
except subprocess.CalledProcessError as e:
|
19
|
+
console.print(f"[red]Failed to install toolbox: {e}[/red]")
|
20
|
+
raise typer.Exit(code=1)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import typer
|
2
|
+
from rich.console import Console
|
3
|
+
|
4
|
+
from quantalogic.codeact.cli import plugin_manager # Import shared plugin_manager from cli.py
|
5
|
+
|
6
|
+
app = typer.Typer()
|
7
|
+
|
8
|
+
console = Console()
|
9
|
+
|
10
|
+
@app.command()
|
11
|
+
def list_executors() -> None:
|
12
|
+
"""List all available executors."""
|
13
|
+
console.print("[bold cyan]Available Executors:[/bold cyan]")
|
14
|
+
for name in plugin_manager.executors.keys():
|
15
|
+
console.print(f"- {name}")
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import typer
|
2
|
+
from rich.console import Console
|
3
|
+
|
4
|
+
from quantalogic.codeact.cli import plugin_manager # Import shared plugin_manager from cli.py
|
5
|
+
|
6
|
+
app = typer.Typer()
|
7
|
+
|
8
|
+
console = Console()
|
9
|
+
|
10
|
+
@app.command()
|
11
|
+
def list_reasoners() -> None:
|
12
|
+
"""List all available reasoners."""
|
13
|
+
console.print("[bold cyan]Available Reasoners:[/bold cyan]")
|
14
|
+
for name in plugin_manager.reasoners.keys():
|
15
|
+
console.print(f"- {name}")
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import typer
|
2
|
+
from loguru import logger
|
3
|
+
from rich.console import Console
|
4
|
+
|
5
|
+
from quantalogic.codeact.cli import plugin_manager # Import shared plugin_manager from cli.py
|
6
|
+
|
7
|
+
app = typer.Typer()
|
8
|
+
|
9
|
+
console = Console()
|
10
|
+
|
11
|
+
@app.command()
|
12
|
+
def list_toolboxes(
|
13
|
+
detail: bool = typer.Option(False, "--detail", "-d", help="Show detailed documentation for each tool")
|
14
|
+
) -> None:
|
15
|
+
"""List all loaded toolboxes and their associated tools from entry points.
|
16
|
+
|
17
|
+
When --detail flag is used, displays the full documentation for each tool using its to_docstring() method.
|
18
|
+
"""
|
19
|
+
logger.debug("Listing toolboxes from entry points")
|
20
|
+
tools = plugin_manager.tools.get_tools()
|
21
|
+
|
22
|
+
if not tools:
|
23
|
+
console.print("[yellow]No toolboxes found.[/yellow]")
|
24
|
+
else:
|
25
|
+
console.print("[bold cyan]Available Toolboxes and Tools:[/bold cyan]")
|
26
|
+
toolbox_dict = {}
|
27
|
+
for tool in tools:
|
28
|
+
toolbox_name = tool.toolbox_name if tool.toolbox_name else 'Unknown Toolbox'
|
29
|
+
if toolbox_name not in toolbox_dict:
|
30
|
+
toolbox_dict[toolbox_name] = []
|
31
|
+
toolbox_dict[toolbox_name].append(tool)
|
32
|
+
|
33
|
+
for toolbox_name, tools_list in toolbox_dict.items():
|
34
|
+
console.print(f"[bold green]Toolbox: {toolbox_name}[/bold green]")
|
35
|
+
for tool in sorted(tools_list, key=lambda x: x.name):
|
36
|
+
if detail:
|
37
|
+
# Use to_docstring() method for detailed documentation
|
38
|
+
docstring = tool.to_docstring()
|
39
|
+
console.print(f" - [bold]{tool.name}[/bold]")
|
40
|
+
console.print(f" Documentation:\n {docstring.replace('\n', '\n ')}")
|
41
|
+
console.print("")
|
42
|
+
else:
|
43
|
+
console.print(f" - {tool.name}")
|
44
|
+
console.print("")
|
45
|
+
|
46
|
+
if __name__ == "__main__":
|
47
|
+
app()
|
@@ -0,0 +1,215 @@
|
|
1
|
+
import asyncio
|
2
|
+
import json
|
3
|
+
import sys
|
4
|
+
|
5
|
+
import typer
|
6
|
+
from loguru import logger
|
7
|
+
from rich.console import Console
|
8
|
+
|
9
|
+
from quantalogic.codeact.agent import Agent, AgentConfig
|
10
|
+
from quantalogic.codeact.constants import DEFAULT_MODEL, LOG_FILE
|
11
|
+
from quantalogic.codeact.events import (
|
12
|
+
ActionExecutedEvent,
|
13
|
+
ActionGeneratedEvent,
|
14
|
+
ErrorOccurredEvent,
|
15
|
+
StepCompletedEvent,
|
16
|
+
StepStartedEvent,
|
17
|
+
StreamTokenEvent,
|
18
|
+
TaskCompletedEvent,
|
19
|
+
TaskStartedEvent,
|
20
|
+
ThoughtGeneratedEvent,
|
21
|
+
ToolExecutionCompletedEvent,
|
22
|
+
ToolExecutionErrorEvent,
|
23
|
+
ToolExecutionStartedEvent,
|
24
|
+
)
|
25
|
+
from quantalogic.codeact.tools_manager import get_default_tools
|
26
|
+
from quantalogic.codeact.utils import process_tools
|
27
|
+
from quantalogic.codeact.xml_utils import XMLResultHandler
|
28
|
+
|
29
|
+
console = Console()
|
30
|
+
|
31
|
+
class ProgressMonitor:
|
32
|
+
"""Handles progress monitoring for agent events."""
|
33
|
+
def __init__(self):
|
34
|
+
self._token_buffer = ""
|
35
|
+
|
36
|
+
async def on_task_started(self, event: TaskStartedEvent):
|
37
|
+
console.print(f"[bold green]Task Started: {event.task_description}[/bold green]")
|
38
|
+
console.print(f"Timestamp: {event.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
|
39
|
+
console.print("-" * 50)
|
40
|
+
|
41
|
+
async def on_thought_generated(self, event: ThoughtGeneratedEvent):
|
42
|
+
console.print(f"[bold cyan]Step {event.step_number} - Thought Generated:[/bold cyan]")
|
43
|
+
console.print(f"Thought: {event.thought}")
|
44
|
+
console.print(f"Generation Time: {event.generation_time:.2f} seconds")
|
45
|
+
console.print("-" * 50)
|
46
|
+
|
47
|
+
async def on_action_generated(self, event: ActionGeneratedEvent):
|
48
|
+
console.print(f"[bold blue]Step {event.step_number} - Action Generated:[/bold blue]")
|
49
|
+
console.print(f"Action Code:\n{event.action_code}")
|
50
|
+
console.print(f"Generation Time: {event.generation_time:.2f} seconds")
|
51
|
+
console.print("-" * 50)
|
52
|
+
|
53
|
+
async def on_action_executed(self, event: ActionExecutedEvent):
|
54
|
+
summary = XMLResultHandler.format_result_summary(event.result_xml)
|
55
|
+
console.print(f"[bold magenta]Step {event.step_number} - Action Executed:[/bold magenta]")
|
56
|
+
console.print(f"Result Summary:\n{summary}")
|
57
|
+
console.print(f"Execution Time: {event.execution_time:.2f} seconds")
|
58
|
+
console.print("-" * 50)
|
59
|
+
|
60
|
+
async def on_step_completed(self, event: StepCompletedEvent):
|
61
|
+
console.print(f"[bold yellow]Step {event.step_number} - Completed[/bold yellow]")
|
62
|
+
console.print("-" * 50)
|
63
|
+
|
64
|
+
async def on_error_occurred(self, event: ErrorOccurredEvent):
|
65
|
+
console.print(f"[bold red]Error Occurred{' at Step ' + str(event.step_number) if event.step_number else ''}:[/bold red]")
|
66
|
+
console.print(f"Message: {event.error_message}")
|
67
|
+
console.print(f"Timestamp: {event.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
|
68
|
+
console.print("-" * 50)
|
69
|
+
|
70
|
+
async def on_task_completed(self, event: TaskCompletedEvent):
|
71
|
+
if event.final_answer:
|
72
|
+
console.print("[bold green]Task Completed Successfully:[/bold green]")
|
73
|
+
console.print(f"Final Answer:\n{event.final_answer}")
|
74
|
+
else:
|
75
|
+
console.print("[bold red]Task Did Not Complete Successfully:[/bold red]")
|
76
|
+
console.print(f"Reason: {event.reason}")
|
77
|
+
console.print(f"Timestamp: {event.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
|
78
|
+
console.print("-" * 50)
|
79
|
+
|
80
|
+
async def on_step_started(self, event: StepStartedEvent):
|
81
|
+
console.print(f"[bold yellow]Starting Step {event.step_number}[/bold yellow]")
|
82
|
+
console.print("-" * 50)
|
83
|
+
|
84
|
+
async def on_tool_execution_started(self, event: ToolExecutionStartedEvent):
|
85
|
+
console.print(f"[cyan]Tool {event.tool_name} started execution at step {event.step_number}[/cyan]")
|
86
|
+
console.print(f"Parameters: {event.parameters_summary}")
|
87
|
+
|
88
|
+
async def on_tool_execution_completed(self, event: ToolExecutionCompletedEvent):
|
89
|
+
console.print(f"[green]Tool {event.tool_name} completed execution at step {event.step_number}[/green]")
|
90
|
+
console.print(f"Result: {event.result_summary}")
|
91
|
+
|
92
|
+
async def on_tool_execution_error(self, event: ToolExecutionErrorEvent):
|
93
|
+
console.print(f"[red]Tool {event.tool_name} encountered an error at step {event.step_number}[/red]")
|
94
|
+
console.print(f"Error: {event.error}")
|
95
|
+
|
96
|
+
async def on_stream_token(self, event: StreamTokenEvent):
|
97
|
+
console.print(event.token, end="")
|
98
|
+
|
99
|
+
async def flush_buffer(self):
|
100
|
+
pass
|
101
|
+
|
102
|
+
async def __call__(self, event):
|
103
|
+
if isinstance(event, TaskStartedEvent):
|
104
|
+
await self.on_task_started(event)
|
105
|
+
elif isinstance(event, ThoughtGeneratedEvent):
|
106
|
+
await self.on_thought_generated(event)
|
107
|
+
elif isinstance(event, ActionGeneratedEvent):
|
108
|
+
await self.on_action_generated(event)
|
109
|
+
await self.flush_buffer()
|
110
|
+
elif isinstance(event, ActionExecutedEvent):
|
111
|
+
await self.on_action_executed(event)
|
112
|
+
elif isinstance(event, StepCompletedEvent):
|
113
|
+
await self.on_step_completed(event)
|
114
|
+
elif isinstance(event, ErrorOccurredEvent):
|
115
|
+
await self.on_error_occurred(event)
|
116
|
+
elif isinstance(event, TaskCompletedEvent):
|
117
|
+
await self.on_task_completed(event)
|
118
|
+
await self.flush_buffer()
|
119
|
+
elif isinstance(event, StepStartedEvent):
|
120
|
+
await self.on_step_started(event)
|
121
|
+
elif isinstance(event, ToolExecutionStartedEvent):
|
122
|
+
await self.on_tool_execution_started(event)
|
123
|
+
elif isinstance(event, ToolExecutionCompletedEvent):
|
124
|
+
await self.on_tool_execution_completed(event)
|
125
|
+
elif isinstance(event, ToolExecutionErrorEvent):
|
126
|
+
await self.on_tool_execution_error(event)
|
127
|
+
elif isinstance(event, StreamTokenEvent):
|
128
|
+
await self.on_stream_token(event)
|
129
|
+
|
130
|
+
async def run_react_agent(
|
131
|
+
task: str,
|
132
|
+
model: str,
|
133
|
+
max_iterations: int,
|
134
|
+
success_criteria: str = None,
|
135
|
+
tools=None,
|
136
|
+
personality=None,
|
137
|
+
backstory=None,
|
138
|
+
sop=None,
|
139
|
+
debug: bool = False,
|
140
|
+
streaming: bool = False,
|
141
|
+
reasoner_name=None,
|
142
|
+
executor_name=None,
|
143
|
+
tools_config=None,
|
144
|
+
profile=None,
|
145
|
+
customizations=None
|
146
|
+
) -> None:
|
147
|
+
"""Run the Agent with detailed event monitoring."""
|
148
|
+
logger.remove()
|
149
|
+
logger.add(sys.stderr, level="DEBUG" if debug else "INFO")
|
150
|
+
logger.add(LOG_FILE, level="DEBUG" if debug else "INFO")
|
151
|
+
|
152
|
+
tools = tools if tools is not None else get_default_tools(model, tools_config=tools_config)
|
153
|
+
processed_tools = process_tools(tools)
|
154
|
+
|
155
|
+
# Create AgentConfig with all parameters
|
156
|
+
config = AgentConfig(
|
157
|
+
model=model,
|
158
|
+
max_iterations=max_iterations,
|
159
|
+
tools=processed_tools,
|
160
|
+
personality=personality,
|
161
|
+
backstory=backstory,
|
162
|
+
sop=sop,
|
163
|
+
reasoner_name=reasoner_name if reasoner_name else "default",
|
164
|
+
executor_name=executor_name if executor_name else "default",
|
165
|
+
tools_config=tools_config,
|
166
|
+
profile=profile,
|
167
|
+
customizations=customizations
|
168
|
+
)
|
169
|
+
|
170
|
+
agent = Agent(config=config)
|
171
|
+
|
172
|
+
progress_monitor = ProgressMonitor()
|
173
|
+
solve_agent = agent
|
174
|
+
solve_agent.add_observer(progress_monitor, [
|
175
|
+
"TaskStarted", "ThoughtGenerated", "ActionGenerated", "ActionExecuted",
|
176
|
+
"StepCompleted", "ErrorOccurred", "TaskCompleted", "StepStarted",
|
177
|
+
"ToolExecutionStarted", "ToolExecutionCompleted", "ToolExecutionError",
|
178
|
+
"StreamToken"
|
179
|
+
])
|
180
|
+
|
181
|
+
console.print(f"[bold green]Starting task: {task}[/bold green]")
|
182
|
+
_history = await agent.solve(task, success_criteria, streaming=streaming,
|
183
|
+
reasoner_name=reasoner_name, executor_name=executor_name)
|
184
|
+
|
185
|
+
def task(
|
186
|
+
task: str = typer.Argument(..., help="The task to solve"),
|
187
|
+
model: str = typer.Option(DEFAULT_MODEL, help="The litellm model to use"),
|
188
|
+
max_iterations: int = typer.Option(5, help="Maximum reasoning steps"),
|
189
|
+
success_criteria: str = typer.Option(None, help="Optional criteria to determine task completion"),
|
190
|
+
personality: str = typer.Option(None, help="Agent personality (e.g., 'witty')"),
|
191
|
+
backstory: str = typer.Option(None, help="Agent backstory"),
|
192
|
+
sop: str = typer.Option(None, help="Standard operating procedure"),
|
193
|
+
debug: bool = typer.Option(False, help="Enable debug logging to stderr"),
|
194
|
+
streaming: bool = typer.Option(False, help="Enable streaming output for real-time token generation"),
|
195
|
+
reasoner: str = typer.Option(None, help="Name of the reasoner to use"),
|
196
|
+
executor: str = typer.Option(None, help="Name of the executor to use"),
|
197
|
+
tools_config: str = typer.Option(None, help="JSON/YAML string for tools_config"),
|
198
|
+
profile: str = typer.Option(None, help="Agent profile (e.g., 'math_expert')"),
|
199
|
+
customizations: str = typer.Option(None, help="JSON/YAML string for customizations")
|
200
|
+
) -> None:
|
201
|
+
"""CLI command to run the Agent with detailed event monitoring."""
|
202
|
+
try:
|
203
|
+
# Parse optional JSON/YAML strings
|
204
|
+
tools_config_list = json.loads(tools_config) if tools_config else None
|
205
|
+
customizations_dict = json.loads(customizations) if customizations else None
|
206
|
+
asyncio.run(run_react_agent(
|
207
|
+
task, model, max_iterations, success_criteria,
|
208
|
+
personality=personality, backstory=backstory, sop=sop, debug=debug,
|
209
|
+
streaming=streaming, reasoner_name=reasoner, executor_name=executor,
|
210
|
+
tools_config=tools_config_list, profile=profile, customizations=customizations_dict
|
211
|
+
))
|
212
|
+
except Exception as e:
|
213
|
+
logger.error(f"Agent failed: {e}")
|
214
|
+
typer.echo(f"Error: {e}", err=True)
|
215
|
+
raise typer.Exit(code=1)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import typer
|
2
|
+
from rich.console import Console
|
3
|
+
|
4
|
+
from quantalogic.codeact.cli import plugin_manager # Import shared plugin_manager from cli.py
|
5
|
+
|
6
|
+
app = typer.Typer()
|
7
|
+
|
8
|
+
console = Console()
|
9
|
+
|
10
|
+
@app.command()
|
11
|
+
def tool_info(tool_name: str = typer.Argument(..., help="Name of the tool")) -> None:
|
12
|
+
"""Display information about a specific tool."""
|
13
|
+
tools = plugin_manager.tools.get_tools()
|
14
|
+
tool = next((t for t in tools if t.name == tool_name), None)
|
15
|
+
if tool:
|
16
|
+
console.print(f"[bold green]Tool: {tool.name}[/bold green]")
|
17
|
+
console.print(f"Description: {tool.description}")
|
18
|
+
console.print(f"Toolbox: {tool.toolbox_name or 'N/A'}")
|
19
|
+
console.print("Arguments:")
|
20
|
+
for arg in tool.arguments:
|
21
|
+
console.print(f" - {arg.name} ({arg.arg_type}): {arg.description} {'(required)' if arg.required else ''}")
|
22
|
+
console.print(f"Return Type: {tool.return_type}")
|
23
|
+
else:
|
24
|
+
console.print(f"[red]Tool '{tool_name}' not found[/red]")
|