quantalogic 0.60.0__py3-none-any.whl → 0.61.0__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_config.py +5 -5
- quantalogic/agent_factory.py +2 -2
- quantalogic/codeact/__init__.py +0 -0
- quantalogic/codeact/agent.py +499 -0
- quantalogic/codeact/cli.py +232 -0
- quantalogic/codeact/constants.py +9 -0
- quantalogic/codeact/events.py +78 -0
- quantalogic/codeact/llm_util.py +76 -0
- quantalogic/codeact/prompts/error_format.j2 +11 -0
- quantalogic/codeact/prompts/generate_action.j2 +26 -0
- quantalogic/codeact/prompts/generate_program.j2 +39 -0
- quantalogic/codeact/prompts/response_format.j2 +11 -0
- quantalogic/codeact/tools_manager.py +135 -0
- quantalogic/codeact/utils.py +135 -0
- quantalogic/coding_agent.py +2 -2
- quantalogic/python_interpreter/__init__.py +23 -0
- quantalogic/python_interpreter/assignment_visitors.py +63 -0
- quantalogic/python_interpreter/base_visitors.py +20 -0
- quantalogic/python_interpreter/class_visitors.py +22 -0
- quantalogic/python_interpreter/comprehension_visitors.py +172 -0
- quantalogic/python_interpreter/context_visitors.py +59 -0
- quantalogic/python_interpreter/control_flow_visitors.py +88 -0
- quantalogic/python_interpreter/exception_visitors.py +109 -0
- quantalogic/python_interpreter/exceptions.py +39 -0
- quantalogic/python_interpreter/execution.py +202 -0
- quantalogic/python_interpreter/function_utils.py +386 -0
- quantalogic/python_interpreter/function_visitors.py +209 -0
- quantalogic/python_interpreter/import_visitors.py +28 -0
- quantalogic/python_interpreter/interpreter_core.py +358 -0
- quantalogic/python_interpreter/literal_visitors.py +74 -0
- quantalogic/python_interpreter/misc_visitors.py +148 -0
- quantalogic/python_interpreter/operator_visitors.py +108 -0
- quantalogic/python_interpreter/scope.py +10 -0
- quantalogic/python_interpreter/visit_handlers.py +110 -0
- quantalogic/tools/__init__.py +5 -4
- quantalogic/tools/action_gen.py +366 -0
- quantalogic/tools/python_tool.py +13 -0
- quantalogic/tools/{search_definition_names.py → search_definition_names_tool.py} +2 -2
- quantalogic/tools/tool.py +116 -22
- quantalogic/utils/__init__.py +0 -1
- quantalogic/utils/test_python_interpreter.py +119 -0
- {quantalogic-0.60.0.dist-info → quantalogic-0.61.0.dist-info}/METADATA +7 -2
- {quantalogic-0.60.0.dist-info → quantalogic-0.61.0.dist-info}/RECORD +46 -14
- quantalogic/utils/python_interpreter.py +0 -905
- {quantalogic-0.60.0.dist-info → quantalogic-0.61.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.60.0.dist-info → quantalogic-0.61.0.dist-info}/WHEEL +0 -0
- {quantalogic-0.60.0.dist-info → quantalogic-0.61.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,232 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Callable, List, Optional, Union
|
3
|
+
|
4
|
+
import typer
|
5
|
+
from loguru import logger
|
6
|
+
|
7
|
+
from quantalogic.tools import create_tool
|
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
|
27
|
+
|
28
|
+
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)
|
225
|
+
|
226
|
+
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
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from pydantic import BaseModel, Field
|
5
|
+
|
6
|
+
|
7
|
+
class Event(BaseModel):
|
8
|
+
event_type: str
|
9
|
+
timestamp: datetime = Field(default_factory=datetime.now)
|
10
|
+
|
11
|
+
|
12
|
+
class TaskStartedEvent(Event):
|
13
|
+
task_description: str
|
14
|
+
|
15
|
+
|
16
|
+
class ThoughtGeneratedEvent(Event):
|
17
|
+
step_number: int
|
18
|
+
thought: str
|
19
|
+
generation_time: float
|
20
|
+
|
21
|
+
|
22
|
+
class ActionGeneratedEvent(Event):
|
23
|
+
step_number: int
|
24
|
+
action_code: str
|
25
|
+
generation_time: float
|
26
|
+
|
27
|
+
|
28
|
+
class ActionExecutedEvent(Event):
|
29
|
+
step_number: int
|
30
|
+
result_xml: str
|
31
|
+
execution_time: float
|
32
|
+
|
33
|
+
|
34
|
+
class StepCompletedEvent(Event):
|
35
|
+
step_number: int
|
36
|
+
thought: str
|
37
|
+
action: str
|
38
|
+
result: str
|
39
|
+
is_complete: bool
|
40
|
+
final_answer: str | None = None
|
41
|
+
|
42
|
+
|
43
|
+
class ErrorOccurredEvent(Event):
|
44
|
+
error_message: str
|
45
|
+
step_number: int | None = None
|
46
|
+
|
47
|
+
|
48
|
+
class TaskCompletedEvent(Event):
|
49
|
+
final_answer: str | None
|
50
|
+
reason: str
|
51
|
+
|
52
|
+
|
53
|
+
class StepStartedEvent(Event):
|
54
|
+
step_number: int
|
55
|
+
|
56
|
+
|
57
|
+
class ToolExecutionStartedEvent(Event):
|
58
|
+
step_number: int
|
59
|
+
tool_name: str
|
60
|
+
parameters_summary: dict # Summary of tool parameters (e.g., {"param1": "value1"})
|
61
|
+
|
62
|
+
|
63
|
+
class ToolExecutionCompletedEvent(Event):
|
64
|
+
step_number: int
|
65
|
+
tool_name: str
|
66
|
+
result_summary: str # Summary of the execution result
|
67
|
+
|
68
|
+
|
69
|
+
class ToolExecutionErrorEvent(Event):
|
70
|
+
step_number: int
|
71
|
+
tool_name: str
|
72
|
+
error: str # Error message if execution fails
|
73
|
+
|
74
|
+
|
75
|
+
# In quantalogic/codeact/events.py
|
76
|
+
class StreamTokenEvent(Event):
|
77
|
+
token: str
|
78
|
+
step_number: Optional[int] = None
|
@@ -0,0 +1,76 @@
|
|
1
|
+
from typing import Callable, List, Optional
|
2
|
+
|
3
|
+
import litellm
|
4
|
+
|
5
|
+
|
6
|
+
async def litellm_completion(
|
7
|
+
model: str,
|
8
|
+
messages: List[dict],
|
9
|
+
max_tokens: int,
|
10
|
+
temperature: float,
|
11
|
+
stream: bool = False,
|
12
|
+
step: Optional[int] = None,
|
13
|
+
notify_event: Optional[Callable] = None,
|
14
|
+
**kwargs
|
15
|
+
) -> str:
|
16
|
+
"""
|
17
|
+
A wrapper for litellm.acompletion that supports streaming and non-streaming modes.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
model (str): The model to use (e.g., "gemini/gemini-2.0-flash").
|
21
|
+
messages (List[dict]): The conversation history as a list of message dictionaries.
|
22
|
+
max_tokens (int): Maximum number of tokens to generate.
|
23
|
+
temperature (float): Sampling temperature for the model.
|
24
|
+
stream (bool): If True, stream tokens; if False, return the full response.
|
25
|
+
step (Optional[int]): Step number for event tracking (used in streaming mode).
|
26
|
+
notify_event (Optional[Callable]): Callback to trigger events during streaming.
|
27
|
+
**kwargs: Additional arguments to pass to litellm.acompletion.
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
str: The generated response (full text in both modes).
|
31
|
+
|
32
|
+
Raises:
|
33
|
+
ValueError: If notify_event is missing when stream=True.
|
34
|
+
Exception: If the completion request fails.
|
35
|
+
"""
|
36
|
+
from .events import StreamTokenEvent # Local import to avoid circular dependency
|
37
|
+
|
38
|
+
if stream:
|
39
|
+
if notify_event is None:
|
40
|
+
raise ValueError("notify_event callback is required when streaming is enabled.")
|
41
|
+
|
42
|
+
full_response = ""
|
43
|
+
try:
|
44
|
+
response = await litellm.acompletion(
|
45
|
+
model=model,
|
46
|
+
messages=messages,
|
47
|
+
max_tokens=max_tokens,
|
48
|
+
temperature=temperature,
|
49
|
+
stream=True,
|
50
|
+
**kwargs
|
51
|
+
)
|
52
|
+
async for chunk in response:
|
53
|
+
if chunk.choices[0].delta.content:
|
54
|
+
token = chunk.choices[0].delta.content
|
55
|
+
full_response += token
|
56
|
+
await notify_event(StreamTokenEvent(
|
57
|
+
event_type="StreamToken",
|
58
|
+
token=token,
|
59
|
+
step_number=step
|
60
|
+
))
|
61
|
+
return full_response
|
62
|
+
except Exception as e:
|
63
|
+
raise Exception(f"Streaming completion failed: {e}")
|
64
|
+
else:
|
65
|
+
try:
|
66
|
+
response = await litellm.acompletion(
|
67
|
+
model=model,
|
68
|
+
messages=messages,
|
69
|
+
max_tokens=max_tokens,
|
70
|
+
temperature=temperature,
|
71
|
+
stream=False,
|
72
|
+
**kwargs
|
73
|
+
)
|
74
|
+
return response.choices[0].message.content
|
75
|
+
except Exception as e:
|
76
|
+
raise Exception(f"Completion failed: {e}")
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<Action>
|
2
|
+
<Thought>Failed to generate a valid action due to: {{ error }}</Thought>
|
3
|
+
<Error>
|
4
|
+
<Message>{{ error }}</Message>
|
5
|
+
</Error>
|
6
|
+
<Code><![CDATA[
|
7
|
+
import asyncio
|
8
|
+
async def main():
|
9
|
+
print("Error: Action generation failed")
|
10
|
+
]]></Code>
|
11
|
+
</Action>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Solve the following task: '{{ task }}'
|
2
|
+
This is step {{ current_step }} out of {{ max_iterations }} allowed steps.
|
3
|
+
Previous steps:
|
4
|
+
{{ history_str }}
|
5
|
+
|
6
|
+
Generate a Python program with an async main() function that uses the available tools to take the next step toward solving the task.
|
7
|
+
|
8
|
+
Essential Requirements:
|
9
|
+
- Check context_vars for previous results: context_vars.get("variable_name")
|
10
|
+
- Build upon previous steps when applicable, using variables listed in 'Available variables' from the history
|
11
|
+
- Return "Task completed: [final answer]" if solved
|
12
|
+
- Return intermediate results otherwise
|
13
|
+
- Handle potential errors in tool arguments
|
14
|
+
- Maintain proper async/await patterns
|
15
|
+
- Important: When defining variables in the code, prefix them with 'step{{ current_step }}_' to ensure uniqueness across steps. For example, use 'step{{ current_step }}_intermediate_value'.
|
16
|
+
|
17
|
+
The program must:
|
18
|
+
1. Use only the provided async tools
|
19
|
+
2. Follow a subset of Python 3.10+ syntax, doesn't allow unsafe operations such as eval() or exec()
|
20
|
+
3. Include type annotations for variables, give very explicit names to variables
|
21
|
+
4. Output progress information
|
22
|
+
|
23
|
+
Example Context Usage:
|
24
|
+
previous_value = context_vars.get('step1_intermediate_result')
|
25
|
+
if previous_value:
|
26
|
+
next_step = await process_tool(input=previous_value)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
You are a Python code generator. Your task is to create a Python program that solves the following task:
|
2
|
+
|
3
|
+
"{{ task_description }}"
|
4
|
+
|
5
|
+
You have access to the following pre-defined async tool functions, as defined with their signatures and descriptions:
|
6
|
+
|
7
|
+
{{ tool_docstrings }}
|
8
|
+
|
9
|
+
If applicable use tools to assess the situation and generate a Python program that solves the task step by step.
|
10
|
+
|
11
|
+
If applicable use tools to verify the task is completed.
|
12
|
+
|
13
|
+
Instructions:
|
14
|
+
1. Generate a simple vanilla Python program, avoid complex logic, return the program as a single string.
|
15
|
+
2. Include only the import for asyncio (import asyncio).
|
16
|
+
3. Define an async function named main() that solves the task.
|
17
|
+
4. Use the pre-defined tool functions directly by calling them with await and the appropriate arguments as specified in their descriptions.
|
18
|
+
5. Do not redefine the tool functions within the program; assume they are already available in the namespace.
|
19
|
+
6. Return the program as a plain string (no markdown or extra text).
|
20
|
+
7. Strictly exclude asyncio.run(main()) or any code outside the main() function definition, including any 'if __name__ == "__main__":' block, as the runtime will handle execution of main().
|
21
|
+
8. Express all string variables as multiline strings string, always start a string at the beginning of a line.
|
22
|
+
9. Always return a string from main(); use "Task completed: [result]" if the task is solved, otherwise return intermediate results.
|
23
|
+
10. Access variables from previous steps using the 'context_vars' dictionary (e.g., previous_value = context_vars.get("var_name", default_value))
|
24
|
+
11. Be careful to avoid programs that cannot terminate.
|
25
|
+
|
26
|
+
|
27
|
+
Example task: "Calculate running total by adding 5 to previous sum stored in context"
|
28
|
+
Example output:
|
29
|
+
import asyncio
|
30
|
+
|
31
|
+
async def main():
|
32
|
+
# Retrieve previous total or default to 0
|
33
|
+
current_total = int(context_vars.get('running_total', 0))
|
34
|
+
|
35
|
+
# Execute tool operation
|
36
|
+
new_total = await add_tool(a=current_total, b=5)
|
37
|
+
|
38
|
+
# Return formatted result
|
39
|
+
return f"Task completed: {new_total}"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<Action>
|
2
|
+
<Thought>Generated a Python program to take the next step toward solving: {{ task }} Based on history: {{ history_str }}</Thought>
|
3
|
+
<Code><![CDATA[
|
4
|
+
{{ program }}
|
5
|
+
]]></Code>
|
6
|
+
<Metadata>
|
7
|
+
<StepNumber>{{ current_step }}</StepNumber>
|
8
|
+
<MaxIterations>{{ max_iterations }}</MaxIterations>
|
9
|
+
<Task>{{ task }}</Task>
|
10
|
+
</Metadata>
|
11
|
+
</Action>
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import asyncio
|
2
|
+
from contextlib import AsyncExitStack
|
3
|
+
from typing import List
|
4
|
+
|
5
|
+
import litellm
|
6
|
+
from loguru import logger
|
7
|
+
|
8
|
+
from quantalogic.tools import (
|
9
|
+
EditWholeContentTool,
|
10
|
+
ExecuteBashCommandTool,
|
11
|
+
GrepAppTool,
|
12
|
+
InputQuestionTool,
|
13
|
+
JinjaTool,
|
14
|
+
ListDirectoryTool,
|
15
|
+
ReadFileBlockTool,
|
16
|
+
ReadFileTool,
|
17
|
+
ReadHTMLTool,
|
18
|
+
ReplaceInFileTool,
|
19
|
+
RipgrepTool,
|
20
|
+
SearchDefinitionNamesTool,
|
21
|
+
TaskCompleteTool,
|
22
|
+
Tool,
|
23
|
+
ToolArgument,
|
24
|
+
WriteFileTool,
|
25
|
+
create_tool,
|
26
|
+
)
|
27
|
+
|
28
|
+
from .utils import log_async_tool, log_tool_method
|
29
|
+
|
30
|
+
|
31
|
+
@create_tool
|
32
|
+
@log_async_tool("Adding")
|
33
|
+
async def add_tool(a: int, b: int) -> str:
|
34
|
+
"""Adds two numbers and returns the sum as a string."""
|
35
|
+
return str(a + b)
|
36
|
+
|
37
|
+
@create_tool
|
38
|
+
@log_async_tool("Multiplying")
|
39
|
+
async def multiply_tool(x: int, y: int) -> str:
|
40
|
+
"""Multiplies two numbers and returns the product as a string."""
|
41
|
+
return str(x * y)
|
42
|
+
|
43
|
+
@create_tool
|
44
|
+
@log_async_tool("Concatenating")
|
45
|
+
async def concat_tool(s1: str, s2: str) -> str:
|
46
|
+
"""Concatenates two strings and returns the result."""
|
47
|
+
return s1 + s2
|
48
|
+
|
49
|
+
class AgentTool(Tool):
|
50
|
+
"""Tool for generating text using a language model."""
|
51
|
+
def __init__(self, model: str = "gemini/gemini-2.0-flash"):
|
52
|
+
super().__init__(
|
53
|
+
name="agent_tool",
|
54
|
+
description="Generates text using a language model.",
|
55
|
+
arguments=[
|
56
|
+
ToolArgument(name="system_prompt", arg_type="string",
|
57
|
+
description="System prompt to guide the model", required=True),
|
58
|
+
ToolArgument(name="prompt", arg_type="string",
|
59
|
+
description="User prompt to generate a response", required=True),
|
60
|
+
ToolArgument(name="temperature", arg_type="float",
|
61
|
+
description="Temperature for generation (0 to 1)", required=True)
|
62
|
+
],
|
63
|
+
return_type="string"
|
64
|
+
)
|
65
|
+
self.model = model
|
66
|
+
|
67
|
+
@log_tool_method
|
68
|
+
async def async_execute(self, **kwargs) -> str:
|
69
|
+
system_prompt = kwargs["system_prompt"]
|
70
|
+
prompt = kwargs["prompt"]
|
71
|
+
temperature = float(kwargs["temperature"])
|
72
|
+
|
73
|
+
if not 0 <= temperature <= 1:
|
74
|
+
raise ValueError("Temperature must be between 0 and 1")
|
75
|
+
|
76
|
+
logger.info(f"Generating with {self.model}, temp={temperature}")
|
77
|
+
async with AsyncExitStack() as stack:
|
78
|
+
await stack.enter_async_context(asyncio.timeout(30))
|
79
|
+
response = await litellm.acompletion(
|
80
|
+
model=self.model,
|
81
|
+
messages=[
|
82
|
+
{"role": "system", "content": system_prompt},
|
83
|
+
{"role": "user", "content": prompt}
|
84
|
+
],
|
85
|
+
temperature=temperature,
|
86
|
+
max_tokens=1000
|
87
|
+
)
|
88
|
+
return response.choices[0].message.content.strip()
|
89
|
+
|
90
|
+
class RetrieveStepTool(Tool):
|
91
|
+
"""Tool to retrieve information from a specific previous step."""
|
92
|
+
def __init__(self, history_store: List[dict]):
|
93
|
+
super().__init__(
|
94
|
+
name="retrieve_step",
|
95
|
+
description="Retrieve the thought, action, and result from a specific step.",
|
96
|
+
arguments=[
|
97
|
+
ToolArgument(name="step_number", arg_type="int",
|
98
|
+
description="The step number to retrieve (1-based)", required=True)
|
99
|
+
],
|
100
|
+
return_type="string"
|
101
|
+
)
|
102
|
+
self.history_store = history_store
|
103
|
+
|
104
|
+
@log_tool_method
|
105
|
+
async def async_execute(self, **kwargs) -> str:
|
106
|
+
step_number = kwargs["step_number"]
|
107
|
+
if step_number < 1 or step_number > len(self.history_store):
|
108
|
+
return f"Error: Step {step_number} does not exist."
|
109
|
+
step = self.history_store[step_number - 1]
|
110
|
+
return (
|
111
|
+
f"Step {step_number}:\n"
|
112
|
+
f"Thought: {step['thought']}\n"
|
113
|
+
f"Action: {step['action']}\n"
|
114
|
+
f"Result: {step['result']}"
|
115
|
+
)
|
116
|
+
|
117
|
+
def get_default_tools(model: str) -> List[Tool]:
|
118
|
+
"""Return list of default tools."""
|
119
|
+
return [
|
120
|
+
#EditWholeContentTool(),
|
121
|
+
#ExecuteBashCommandTool(),
|
122
|
+
GrepAppTool(),
|
123
|
+
InputQuestionTool(),
|
124
|
+
JinjaTool(),
|
125
|
+
ListDirectoryTool(),
|
126
|
+
ReadFileBlockTool(),
|
127
|
+
ReadFileTool(),
|
128
|
+
ReadHTMLTool(),
|
129
|
+
#ReplaceInFileTool(),
|
130
|
+
# RipgrepTool(),
|
131
|
+
# SearchDefinitionNamesTool(),
|
132
|
+
#TaskCompleteTool(),
|
133
|
+
WriteFileTool(),
|
134
|
+
AgentTool(model=model),
|
135
|
+
]
|