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.
- 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/tool.py +531 -109
- {quantalogic-0.61.3.dist-info → quantalogic-0.80.dist-info}/METADATA +3 -3
- {quantalogic-0.61.3.dist-info → quantalogic-0.80.dist-info}/RECORD +45 -22
- {quantalogic-0.61.3.dist-info → quantalogic-0.80.dist-info}/WHEEL +1 -1
- quantalogic-0.80.dist-info/entry_points.txt +3 -0
- quantalogic-0.61.3.dist-info/entry_points.txt +0 -6
- {quantalogic-0.61.3.dist-info → quantalogic-0.80.dist-info}/LICENSE +0 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
"""History management module for storing and formatting agent steps."""
|
2
|
+
|
3
|
+
from typing import Dict, List
|
4
|
+
|
5
|
+
from loguru import logger
|
6
|
+
from lxml import etree
|
7
|
+
|
8
|
+
from .xml_utils import XMLResultHandler
|
9
|
+
|
10
|
+
|
11
|
+
class HistoryManager:
|
12
|
+
"""Manages the storage and formatting of agent step history with persistent context."""
|
13
|
+
|
14
|
+
def __init__(self, max_tokens: int = 64*1024, system_prompt: str = "", task_description: str = ""):
|
15
|
+
"""
|
16
|
+
Initialize the HistoryManager with a token limit and persistent context.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
max_tokens (int): Maximum number of tokens for history formatting (default: 16000).
|
20
|
+
system_prompt (str): Persistent system-level instructions for the agent (default: "").
|
21
|
+
task_description (str): Persistent description of the current task (default: "").
|
22
|
+
"""
|
23
|
+
self.max_tokens: int = max_tokens
|
24
|
+
self.system_prompt: str = system_prompt
|
25
|
+
self.task_description: str = task_description
|
26
|
+
self.store: List[Dict] = []
|
27
|
+
logger.debug(f"Initialized HistoryManager with system_prompt: '{system_prompt}', task_description: '{task_description}'")
|
28
|
+
|
29
|
+
def add_step(self, step_data: Dict) -> None:
|
30
|
+
"""
|
31
|
+
Add a step to the history store.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
step_data (Dict): Dictionary containing step details (step_number, thought, action, result).
|
35
|
+
"""
|
36
|
+
self.store.append(step_data)
|
37
|
+
logger.debug(f"Added step {step_data['step_number']} to history")
|
38
|
+
|
39
|
+
def format_history(self, max_iterations: int) -> str:
|
40
|
+
"""
|
41
|
+
Format the history with available variables, truncating to fit within max_tokens.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
max_iterations (int): Maximum allowed iterations for context.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
str: Formatted string of previous steps, or "No previous steps" if empty.
|
48
|
+
"""
|
49
|
+
included_steps: List[str] = []
|
50
|
+
total_tokens: int = 0
|
51
|
+
for step in reversed(self.store):
|
52
|
+
try:
|
53
|
+
root = etree.fromstring(step['result'])
|
54
|
+
vars_elem = root.find("Variables")
|
55
|
+
available_vars = (
|
56
|
+
[var.get('name') for var in vars_elem.findall("Variable")]
|
57
|
+
if vars_elem is not None else []
|
58
|
+
)
|
59
|
+
except etree.XMLSyntaxError:
|
60
|
+
available_vars = []
|
61
|
+
|
62
|
+
step_str: str = (
|
63
|
+
f"===== Step {step['step_number']} of {max_iterations} max =====\n"
|
64
|
+
f"Thought:\n{step['thought']}\n\n"
|
65
|
+
f"Action:\n{step['action']}\n\n"
|
66
|
+
f"Result:\n{XMLResultHandler.format_result_summary(step['result']) if step.get('result') else 'No result available'}\n"
|
67
|
+
f"Available variables: {', '.join(available_vars) or 'None'}"
|
68
|
+
)
|
69
|
+
step_tokens: int = len(step_str.split())
|
70
|
+
if total_tokens + step_tokens > self.max_tokens:
|
71
|
+
break
|
72
|
+
included_steps.append(step_str)
|
73
|
+
total_tokens += step_tokens
|
74
|
+
return "\n".join(reversed(included_steps)) or "No previous steps"
|
75
|
+
|
76
|
+
def get_full_context(self, max_iterations: int) -> str:
|
77
|
+
"""
|
78
|
+
Return the full context including system prompt, task description, and formatted history.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
max_iterations (int): Maximum allowed iterations for history formatting.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
str: Combined string of system prompt, task description, and history.
|
85
|
+
"""
|
86
|
+
context_parts = []
|
87
|
+
if self.system_prompt:
|
88
|
+
context_parts.append(f"System Prompt:\n{self.system_prompt}")
|
89
|
+
if self.task_description:
|
90
|
+
context_parts.append(f"Task Description:\n{self.task_description}")
|
91
|
+
history_str = self.format_history(max_iterations)
|
92
|
+
if history_str != "No previous steps":
|
93
|
+
context_parts.append(f"History:\n{history_str}")
|
94
|
+
return "\n\n".join(context_parts)
|
quantalogic/codeact/llm_util.py
CHANGED
@@ -13,27 +13,8 @@ async def litellm_completion(
|
|
13
13
|
notify_event: Optional[Callable] = None,
|
14
14
|
**kwargs
|
15
15
|
) -> str:
|
16
|
-
"""
|
17
|
-
|
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
|
16
|
+
"""A wrapper for litellm.acompletion that supports streaming and non-streaming modes."""
|
17
|
+
from .events import StreamTokenEvent
|
37
18
|
|
38
19
|
if stream:
|
39
20
|
if notify_event is None:
|
@@ -73,4 +54,4 @@ async def litellm_completion(
|
|
73
54
|
)
|
74
55
|
return response.choices[0].message.content
|
75
56
|
except Exception as e:
|
76
|
-
raise Exception(f"Completion failed: {e}")
|
57
|
+
raise Exception(f"Completion failed: {e}")
|
@@ -0,0 +1,92 @@
|
|
1
|
+
"""Plugin management module for dynamically loading components."""
|
2
|
+
|
3
|
+
from importlib.metadata import entry_points
|
4
|
+
from typing import List
|
5
|
+
|
6
|
+
from loguru import logger
|
7
|
+
|
8
|
+
from quantalogic.tools import Tool
|
9
|
+
|
10
|
+
from .executor import Executor
|
11
|
+
from .reasoner import Reasoner
|
12
|
+
from .tools_manager import ToolRegistry
|
13
|
+
|
14
|
+
|
15
|
+
class PluginManager:
|
16
|
+
"""Manages dynamic loading of plugins for tools, reasoners, executors, and CLI commands."""
|
17
|
+
_instance = None
|
18
|
+
|
19
|
+
def __new__(cls):
|
20
|
+
if cls._instance is None:
|
21
|
+
cls._instance = super(PluginManager, cls).__new__(cls)
|
22
|
+
cls._instance.tools = ToolRegistry()
|
23
|
+
cls._instance.reasoners = {"default": Reasoner}
|
24
|
+
cls._instance.executors = {"default": Executor}
|
25
|
+
cls._instance.cli_commands = {}
|
26
|
+
cls._instance._plugins_loaded = False
|
27
|
+
return cls._instance
|
28
|
+
|
29
|
+
def load_plugins(self, force: bool = False) -> None:
|
30
|
+
"""Load all plugins from registered entry points, handling duplicates gracefully.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
force: If True, reload plugins even if they were already loaded
|
34
|
+
"""
|
35
|
+
if self._plugins_loaded and not force:
|
36
|
+
logger.debug("Plugins already loaded, skipping entire load process")
|
37
|
+
return
|
38
|
+
|
39
|
+
# Clear existing plugins only if forcing reload
|
40
|
+
if force:
|
41
|
+
logger.info("Forcing plugin reload, clearing existing registrations")
|
42
|
+
self.tools = ToolRegistry()
|
43
|
+
self.reasoners = {"default": Reasoner}
|
44
|
+
self.executors = {"default": Executor}
|
45
|
+
self.cli_commands = {}
|
46
|
+
self._plugins_loaded = False
|
47
|
+
|
48
|
+
logger.debug("Loading plugins")
|
49
|
+
for group, store in [
|
50
|
+
("quantalogic.tools", self.tools.load_toolboxes),
|
51
|
+
("quantalogic.reasoners", self.reasoners),
|
52
|
+
("quantalogic.executors", self.executors),
|
53
|
+
("quantalogic.cli", self.cli_commands),
|
54
|
+
]:
|
55
|
+
try:
|
56
|
+
eps = entry_points(group=group)
|
57
|
+
logger.debug(f"Found {len(eps)} entry points for group {group}")
|
58
|
+
for ep in eps:
|
59
|
+
try:
|
60
|
+
loaded = ep.load()
|
61
|
+
if group == "quantalogic.tools":
|
62
|
+
# Load static tools first
|
63
|
+
store()
|
64
|
+
# Then initialize dynamic MCP tools if applicable
|
65
|
+
if ep.name == "quantalogic_toolbox_mcp":
|
66
|
+
import asyncio
|
67
|
+
|
68
|
+
from quantalogic_toolbox_mcp.tools import initialize_toolbox
|
69
|
+
asyncio.run(initialize_toolbox(self.tools))
|
70
|
+
else:
|
71
|
+
store[ep.name] = loaded
|
72
|
+
logger.info(f"Loaded plugin {ep.name} for {group}")
|
73
|
+
except Exception as e:
|
74
|
+
logger.warning(f"Skipping plugin {ep.name} for {group} due to error: {e}")
|
75
|
+
except Exception as e:
|
76
|
+
logger.error(f"Failed to retrieve entry points for {group}: {e}")
|
77
|
+
self._plugins_loaded = True
|
78
|
+
logger.info("Plugin loading completed")
|
79
|
+
|
80
|
+
def get_tools(self, force_reload: bool = False) -> List[Tool]:
|
81
|
+
"""Return all registered tools.
|
82
|
+
|
83
|
+
Args:
|
84
|
+
force_reload: If True, reload plugins before getting tools
|
85
|
+
"""
|
86
|
+
if force_reload:
|
87
|
+
self.load_plugins(force=True)
|
88
|
+
else:
|
89
|
+
# Ensure plugins are loaded at least once, but don’t reload
|
90
|
+
if not self._plugins_loaded:
|
91
|
+
self.load_plugins()
|
92
|
+
return self.tools.get_tools()
|
@@ -1,26 +1,77 @@
|
|
1
|
-
|
1
|
+
Before generating the code, follow these steps:
|
2
|
+
|
3
|
+
1. **Review the History**: Examine the <History> section below to understand previous thoughts, actions, and results.
|
4
|
+
2. **Identify Available Variables**: Check the 'Currently available variables' list and 'Available variables' in the history. These are stored in the `context_vars` dictionary and can be accessed using `context_vars.get('variable_name', default_value)`.
|
5
|
+
3. **Understand Variable Types**: Variables in `context_vars` are objects returned by tools, not just their string representations. Refer to tool documentation (e.g., `create_project_plan` returns `PlanResult` with `task_id`, `task_description`, `subtasks`) to access attributes directly (e.g., `plan.task_id`) rather than parsing strings.
|
6
|
+
4. **Use Previous Results**: Incorporate relevant variables from prior steps to avoid redundant calculations and ensure continuity. For example, if `step1_plan` is a `PlanResult`, retrieve it with `plan = context_vars.get('step1_plan')` and use `plan.task_id`.
|
7
|
+
5. **Plan Your Approach**: Based on the history and variables, determine the next logical step toward solving the task.
|
8
|
+
|
9
|
+
Currently available variables from previous steps: {{ available_vars | join(', ') }}
|
10
|
+
|
11
|
+
Solve the following task:
|
12
|
+
|
13
|
+
<Task>
|
14
|
+
{{ task }}
|
15
|
+
</Task>
|
16
|
+
|
2
17
|
This is step {{ current_step }} out of {{ max_iterations }} allowed steps.
|
18
|
+
|
3
19
|
Previous steps:
|
20
|
+
|
21
|
+
<History>
|
4
22
|
{{ history_str }}
|
23
|
+
</History>
|
24
|
+
|
25
|
+
Task:
|
5
26
|
|
6
27
|
Generate a Python program with an async main() function that uses the available tools to take the next step toward solving the task.
|
7
28
|
|
8
29
|
Essential Requirements:
|
9
|
-
|
10
|
-
-
|
11
|
-
-
|
12
|
-
-
|
13
|
-
-
|
14
|
-
-
|
15
|
-
-
|
30
|
+
|
31
|
+
- **Always check the 'Available variables' from the history** before proceeding. These are listed in the <History> section and stored in the `context_vars` dictionary as objects. Access them using `context_vars.get("variable_name", default_value)` to retrieve results from previous steps.
|
32
|
+
- **Handle Variable Types Correctly**: Variables in `context_vars` are the actual objects returned by tools (e.g., `PlanResult` from `create_project_plan`). Use attribute access (e.g., `plan.task_id`) instead of string methods like `split()` unless the variable is explicitly a string. Check tool documentation for return types.
|
33
|
+
- **Build upon previous steps** by using these variables when they are relevant. This avoids redundant work and ensures continuity. For example, if a previous step stored a `PlanResult` as `step1_plan`, retrieve it with `plan = context_vars.get('step1_plan')` and use `plan.task_id`.
|
34
|
+
- When defining new variables, **prefix them with 'step{{ current_step }}_'** to ensure uniqueness (e.g., `step{{ current_step }}_result`). This prevents overwriting variables from earlier steps.
|
35
|
+
- Return `"Task completed: [final answer]"` if the task is fully solved in this step.
|
36
|
+
- Otherwise, return intermediate results as a string to be stored for future steps.
|
37
|
+
- Handle potential errors in tool arguments gracefully, using try/except blocks if needed.
|
38
|
+
- Use proper async/await syntax for all asynchronous operations.
|
39
|
+
|
40
|
+
Variable Naming Convention:
|
41
|
+
- Prefix all new variables with 'step{{ current_step }}_' (e.g., `step{{ current_step }}_result = ...`).
|
42
|
+
- This ensures variables are unique across steps and traceable to their origin.
|
43
|
+
|
44
|
+
Example of using variables from previous steps:
|
45
|
+
|
46
|
+
Task: "Retrieve the plan from the previous step"
|
47
|
+
History:
|
48
|
+
"===== Step 1 of 5 max =====
|
49
|
+
Thought: Create a project plan
|
50
|
+
Action: <code>import asyncio\nasync def main():\n step1_plan = await quantalogic_planning_toolbox.create_project_plan(task_description='Write an article')\n return f'{step1_plan}'</code>
|
51
|
+
Result: task_id='abc123' task_description='Write an article' subtasks=[...]
|
52
|
+
Available variables: step1_plan"
|
53
|
+
|
54
|
+
Code:
|
55
|
+
import asyncio
|
56
|
+
|
57
|
+
async def main():
|
58
|
+
# Retrieve previous plan as an object, not a string
|
59
|
+
step1_plan = context_vars.get('step1_plan')
|
60
|
+
if step1_plan:
|
61
|
+
step2_task_id: str = step1_plan.task_id # Access attribute directly
|
62
|
+
step2_retrieved_plan = await quantalogic_planning_toolbox.retrieve_project_plan(task_id=step2_task_id)
|
63
|
+
return f"Retrieved plan: {step2_retrieved_plan}"
|
64
|
+
return "Error: No plan found from previous step"
|
65
|
+
|
66
|
+
Error Reflection:
|
67
|
+
- If there were errors in previous steps, review the error messages in the <History> section and adjust your code to avoid similar issues.
|
68
|
+
- For example, if a variable was missing, check `context_vars` with a default value.
|
69
|
+
- If a tool call failed, verify the arguments and ensure they match the tool's requirements.
|
70
|
+
- If an error like "'PlanResult' object has no attribute 'split'" occurred, it means you tried to treat an object as a string. Use attribute access instead (e.g., `.task_id`).
|
16
71
|
|
17
72
|
The program must:
|
73
|
+
|
18
74
|
1. Use only the provided async tools
|
19
75
|
2. Follow a subset of Python 3.10+ syntax, doesn't allow unsafe operations such as eval() or exec()
|
20
76
|
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)
|
77
|
+
4. Output progress information
|
@@ -2,38 +2,51 @@ You are a Python code generator. Your task is to create a Python program that so
|
|
2
2
|
|
3
3
|
"{{ task_description }}"
|
4
4
|
|
5
|
-
You have access to the following pre-defined async tool functions,
|
5
|
+
You have access to the following pre-defined async tool functions, grouped by toolbox:
|
6
6
|
|
7
|
-
{
|
7
|
+
{% for toolbox_name, docstrings in tools_by_toolbox.items() %}
|
8
|
+
### {{ toolbox_name }}
|
9
|
+
{% for docstring in docstrings %}
|
10
|
+
{{ docstring }}
|
8
11
|
|
9
|
-
|
12
|
+
{% endfor %}
|
13
|
+
{% endfor %}
|
10
14
|
|
11
|
-
If applicable use tools to
|
15
|
+
If applicable, use tools to assess the situation and generate a Python program that solves the task step by step.
|
16
|
+
|
17
|
+
If applicable, use tools to verify the task is completed.
|
12
18
|
|
13
19
|
Instructions:
|
14
|
-
1. Generate a simple
|
20
|
+
1. Generate a very simple Python program, avoid complex logic, return the program as a single string. No more than 3 functions called.
|
15
21
|
2. Include only the import for asyncio (import asyncio).
|
16
22
|
3. Define an async function named main() that solves the task.
|
17
|
-
4. Use the pre-defined tool functions
|
18
|
-
5. Do not redefine the tool functions within the program; assume they are already available in the namespace.
|
23
|
+
4. Use the pre-defined tool functions by calling them with await and prefixing them with their toolbox name (e.g., `await {{ toolbox_name }}.tool_name(arg1, arg2)`). For core tools, use `default.tool_name` (e.g., `await default.agent_tool(...)`).
|
24
|
+
5. Do not redefine the tool functions within the program; assume they are already available in the namespace under their toolbox names (e.g., `default.agent_tool` for core tools).
|
19
25
|
6. Return the program as a plain string (no markdown or extra text).
|
20
26
|
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
|
27
|
+
8. Express all string variables as multiline strings, always start a string at the beginning of a line.
|
22
28
|
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
|
29
|
+
10. Access variables from previous steps using the `context_vars` dictionary:
|
30
|
+
- Use `context_vars.get("variable_name", default_value)` to safely retrieve variables (e.g., `previous_sum = context_vars.get("step1_sum", 0)`).
|
31
|
+
- Always specify a default value to handle cases where the variable might not exist.
|
32
|
+
- Check the history for 'Available variables' to identify relevant previous results.
|
33
|
+
- Use these variables to build on prior work rather than starting from scratch.
|
24
34
|
11. Be careful to avoid programs that cannot terminate.
|
35
|
+
12. When defining new variables, prefix them with 'step<current_step>_' (e.g., `step1_result`) to ensure uniqueness across steps.
|
36
|
+
13. Never use dangerous functions like eval, or any other unsafe operations.
|
37
|
+
14. If a return result is tool complex use an intermediate result to store it.
|
38
|
+
15. VERY IMPORTANT: If the return type of a function is Any or not specified don't call another function after this just return the result, the result will be handled by the runtime.
|
25
39
|
|
26
|
-
|
27
|
-
Example task: "Calculate running total by adding 5 to previous sum stored in context"
|
40
|
+
Example task: "Translate the poem 'The sun rises' into Spanish using the agent_tool"
|
28
41
|
Example output:
|
29
42
|
import asyncio
|
30
43
|
|
31
44
|
async def main():
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
return f"Task completed: {
|
45
|
+
step1_poem: str = "The sun rises"
|
46
|
+
step1_system_prompt: str = "You are a translation expert."
|
47
|
+
step1_translation: str = await default.agent_tool(
|
48
|
+
system_prompt=step1_system_prompt,
|
49
|
+
prompt=f"Translate '{step1_poem}' into Spanish",
|
50
|
+
temperature=0.7
|
51
|
+
)
|
52
|
+
return f"Task completed: {step1_translation}"
|