Skip to content

Commit dd7a58e

Browse files
committed
sync: revert to the main
1 parent 131d0d9 commit dd7a58e

2 files changed

Lines changed: 228 additions & 222 deletions

File tree

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# pytest: ollama, e2e, slow, qualitative
2+
#!/usr/bin/env python3
3+
"""
4+
Example: Using Mellea's decompose functionality programmatically
5+
6+
This script demonstrates how to use the decompose pipeline from Python code
7+
to break down a complex task into subtasks with generated prompts.
8+
"""
9+
10+
import json
11+
import subprocess
12+
import textwrap
13+
from pathlib import Path
14+
15+
from jinja2 import Environment, FileSystemLoader
16+
17+
# Import the decompose pipeline from the CLI module
18+
from cli.decompose.pipeline import DecompBackend, DecompPipelineResult, decompose
19+
20+
21+
def run_decompose(task_prompt: str) -> DecompPipelineResult:
22+
"""
23+
Run the decompose pipeline on a task prompt.
24+
25+
Args:
26+
task_prompt: The task description to decompose
27+
28+
Returns:
29+
Dictionary containing decomposition results
30+
"""
31+
print("Running decomposition pipeline...\n")
32+
33+
result = decompose(
34+
task_prompt=task_prompt,
35+
model_id="granite3.3:8b", # Note micro will not properly create tags, need 8b
36+
backend=DecompBackend.ollama, # Use Ollama backend
37+
backend_req_timeout=300, # 5 minute timeout
38+
)
39+
40+
return result
41+
42+
43+
def save_decompose_json(
44+
result: DecompPipelineResult,
45+
output_dir: Path,
46+
filename: str = "python_decompose_result.json",
47+
) -> Path:
48+
"""
49+
Save decomposition results to a JSON file.
50+
51+
Args:
52+
result: Decomposition results dictionary
53+
output_dir: Directory to save the file
54+
filename: Name of the output file
55+
56+
Returns:
57+
Path to the saved JSON file
58+
"""
59+
json_output_file = output_dir / filename
60+
61+
with open(json_output_file, "w") as f:
62+
json.dump(result, f, indent=2)
63+
64+
print(f"💾 JSON results saved to: {json_output_file}")
65+
return json_output_file
66+
67+
68+
def generate_python_script(
69+
result: DecompPipelineResult,
70+
output_dir: Path,
71+
filename: str = "python_decompose_result.py",
72+
) -> Path:
73+
"""
74+
Generate an executable Python script from decomposition results.
75+
76+
Args:
77+
result: Decomposition results dictionary
78+
output_dir: Directory to save the file
79+
filename: Name of the output Python file
80+
81+
Returns:
82+
Path to the generated Python script
83+
"""
84+
print("\n📝 Generating executable Python script...")
85+
86+
# Load the template from the CLI decompose directory
87+
cli_decompose_dir = (
88+
Path(__file__).parent.parent.parent.parent.parent / "cli" / "decompose"
89+
)
90+
environment = Environment(
91+
loader=FileSystemLoader(cli_decompose_dir), autoescape=False
92+
)
93+
m_template = environment.get_template("m_decomp_result_v1.py.jinja2")
94+
95+
# Render the template with the decomposition results
96+
python_script_content = m_template.render(
97+
subtasks=result["subtasks"],
98+
user_inputs=[], # No user inputs for this simple example
99+
)
100+
101+
# Save the generated Python script
102+
py_output_file = output_dir / filename
103+
with open(py_output_file, "w") as f:
104+
f.write(python_script_content + "\n")
105+
106+
print(f"💾 Generated Python script saved to: {py_output_file}")
107+
return py_output_file
108+
109+
110+
def run_generated_script(
111+
script_path: Path, output_dir: Path, timeout: int = 600
112+
) -> Path | None:
113+
"""
114+
Execute the generated Python script to produce final output.
115+
116+
Args:
117+
script_path: Path to the Python script to execute
118+
output_dir: Directory to save the final output
119+
timeout: Maximum execution time in seconds
120+
121+
Returns:
122+
Path to the final output file if successful, None otherwise
123+
"""
124+
print("\n🚀 Running the generated script to produce final output...")
125+
print(" (This may take a few minutes as it calls the LLM for each subtask)")
126+
127+
try:
128+
result_output = subprocess.run(
129+
["python3", str(script_path)],
130+
capture_output=True,
131+
text=True,
132+
timeout=timeout,
133+
cwd=output_dir,
134+
)
135+
136+
if result_output.returncode == 0:
137+
# Save the final output
138+
final_output_file = output_dir / "python_decompose_final_output.txt"
139+
with open(final_output_file, "w") as f:
140+
f.write(result_output.stdout)
141+
142+
print(f"✅ Final output saved to: {final_output_file}")
143+
print("\n" + "=" * 70)
144+
print("Final Output:")
145+
print("=" * 70)
146+
preview = result_output.stdout
147+
print(preview)
148+
return final_output_file
149+
else:
150+
print(
151+
f"❌ Script execution failed with return code {result_output.returncode}"
152+
)
153+
print(f"Error: {result_output.stderr}")
154+
return None
155+
except subprocess.TimeoutExpired:
156+
print(f"⏱️ Script execution timed out after {timeout} seconds")
157+
return None
158+
except Exception as e:
159+
print(f"❌ Error running script: {e}")
160+
return None
161+
162+
163+
def display_results(result: DecompPipelineResult):
164+
"""
165+
Display decomposition results in a formatted way.
166+
167+
Args:
168+
result: Decomposition results dictionary
169+
"""
170+
print("=" * 70)
171+
print("Decomposition Results")
172+
print("=" * 70)
173+
174+
print(f"\n📋 Subtasks Identified ({len(result['subtask_list'])}):")
175+
for i, subtask in enumerate(result["subtask_list"], 1):
176+
print(f" {subtask}")
177+
178+
print(f"\n🔍 Constraints Identified ({len(result['identified_constraints'])}):")
179+
for i, constraint in enumerate(result["identified_constraints"], 1):
180+
print(f" {i}. {constraint['constraint']}")
181+
print(f" Validation: {constraint['val_strategy']}")
182+
183+
print(f"\n🎯 Detailed Subtasks ({len(result['subtasks'])}):")
184+
for i, subtask_detail in enumerate(result["subtasks"], 1):
185+
print(f"\n Subtask {subtask_detail['subtask']}")
186+
print(f" Tag: {subtask_detail['tag']}")
187+
print(f" Dependencies: {subtask_detail['depends_on'] or 'None'}")
188+
print(f" Input Variables: {subtask_detail['input_vars_required'] or 'None'}")
189+
print(f" Constraints: {len(subtask_detail['constraints'])}")
190+
191+
192+
def main():
193+
# Define a simple task prompt to decompose
194+
task_prompt = textwrap.dedent("""
195+
Write a short blog post about the benefits of morning exercise.
196+
Include a catchy title, an introduction paragraph, three main benefits
197+
with explanations, and a conclusion that encourages readers to start
198+
their morning exercise routine.
199+
""").strip()
200+
201+
print("=" * 70)
202+
print("Mellea Decompose Example")
203+
print("=" * 70)
204+
print(f"\nOriginal Task:\n\n{task_prompt.strip()}\n")
205+
206+
# Step 1: Run decomposition
207+
result = run_decompose(task_prompt)
208+
209+
# Step 2: Display results
210+
display_results(result)
211+
212+
# Step 3: Save JSON results
213+
output_dir = Path(__file__).parent
214+
save_decompose_json(result, output_dir)
215+
216+
# Step 4: Generate Python script
217+
script_path = generate_python_script(result, output_dir)
218+
219+
# Step 5: Run the generated script (optional)
220+
run_generated_script(script_path, output_dir)
221+
222+
print("\n" + "=" * 70)
223+
print("✅ Decomposition complete!")
224+
print("=" * 70)
225+
226+
227+
if __name__ == "__main__":
228+
main()

0 commit comments

Comments
 (0)