quantalogic 0.61.3__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 (46) 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/tool.py +531 -109
  41. {quantalogic-0.61.3.dist-info → quantalogic-0.80.dist-info}/METADATA +3 -3
  42. {quantalogic-0.61.3.dist-info → quantalogic-0.80.dist-info}/RECORD +45 -22
  43. {quantalogic-0.61.3.dist-info → quantalogic-0.80.dist-info}/WHEEL +1 -1
  44. quantalogic-0.80.dist-info/entry_points.txt +3 -0
  45. quantalogic-0.61.3.dist-info/entry_points.txt +0 -6
  46. {quantalogic-0.61.3.dist-info → quantalogic-0.80.dist-info}/LICENSE +0 -0
@@ -1,232 +1,50 @@
1
- import asyncio
2
- from typing import Callable, List, Optional, Union
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.codeact.agent import Agent # Import only Agent from agent.py
8
- from quantalogic.codeact.constants import DEFAULT_MODEL, LOG_FILE
9
- from quantalogic.codeact.events import ( # Import events from events.py
10
- ActionExecutedEvent,
11
- ActionGeneratedEvent,
12
- ErrorOccurredEvent,
13
- StepCompletedEvent,
14
- StepStartedEvent,
15
- StreamTokenEvent,
16
- TaskCompletedEvent,
17
- TaskStartedEvent,
18
- ThoughtGeneratedEvent,
19
- ToolExecutionCompletedEvent,
20
- ToolExecutionErrorEvent,
21
- ToolExecutionStartedEvent,
22
- )
23
- from quantalogic.tools import create_tool
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
- class ProgressMonitor:
31
- """Handles progress monitoring for agent events."""
32
- def __init__(self):
33
- self._token_buffer = "" # Buffer to accumulate streaming tokens
34
-
35
- async def on_task_started(self, event: TaskStartedEvent):
36
- typer.echo(typer.style(f"Task Started: {event.task_description}", fg=typer.colors.GREEN, bold=True))
37
- typer.echo(f"Timestamp: {event.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
38
- typer.echo("-" * 50)
39
-
40
- async def on_thought_generated(self, event: ThoughtGeneratedEvent):
41
- typer.echo(typer.style(f"Step {event.step_number} - Thought Generated:", fg=typer.colors.CYAN, bold=True))
42
- typer.echo(f"Thought: {event.thought}")
43
- typer.echo(f"Generation Time: {event.generation_time:.2f} seconds")
44
- typer.echo("-" * 50)
45
-
46
- async def on_action_generated(self, event: ActionGeneratedEvent):
47
- typer.echo(typer.style(f"Step {event.step_number} - Action Generated:", fg=typer.colors.BLUE, bold=True))
48
- typer.echo(f"Action Code:\n{event.action_code}")
49
- typer.echo(f"Generation Time: {event.generation_time:.2f} seconds")
50
- typer.echo("-" * 50)
51
-
52
- async def on_action_executed(self, event: ActionExecutedEvent):
53
- summary = XMLResultHandler.format_result_summary(event.result_xml)
54
- typer.echo(typer.style(f"Step {event.step_number} - Action Executed:", fg=typer.colors.MAGENTA, bold=True))
55
- typer.echo(f"Result Summary:\n{summary}")
56
- typer.echo(f"Execution Time: {event.execution_time:.2f} seconds")
57
- typer.echo("-" * 50)
58
-
59
- async def on_step_completed(self, event: StepCompletedEvent):
60
- typer.echo(typer.style(f"Step {event.step_number} - Completed", fg=typer.colors.YELLOW, bold=True))
61
- typer.echo("-" * 50)
62
-
63
- async def on_error_occurred(self, event: ErrorOccurredEvent):
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]")