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"\n Original 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