1+ import logging
12import re
3+ from typing import Union , Type
24
35import dspy
4- from dspy .signatures .signature import ensure_signature
6+ from dspy .signatures .signature import ensure_signature , Signature
57
68from dspy .primitives .program import Module
79from dspy .primitives .python_interpreter import PythonInterpreter
810
11+ logger = logging .getLogger (__name__ )
912
1013class ProgramOfThought (Module ):
11- def __init__ (self , signature , max_iters = 3 ):
14+ """
15+ A DSPy module that runs Python programs to solve a problem.
16+ This module reuires deno to be installed. Please install deno following https://docs.deno.com/runtime/getting_started/installation/
17+
18+ Example:
19+ ```
20+ import dspy
21+
22+ lm = dspy.LM('openai/gpt-4o-mini')
23+ dspy.configure(lm=lm)
24+ pot = dspy.ProgramOfThought("question -> answer")
25+ pot(question="what is 1+1?")
26+ ```
27+ """
28+
29+ def __init__ (self , signature : Union [str , Type [Signature ]], max_iters = 3 ):
30+ """
31+ Args:
32+ signature: The signature of the module.
33+ max_iters: The maximum number of iterations to retry code generation and execution.
34+ """
1235 super ().__init__ ()
1336 self .signature = signature = ensure_signature (signature )
1437 self .max_iters = max_iters
@@ -56,6 +79,10 @@ def __init__(self, signature, max_iters=3):
5679 self ._generate_instruction ("answer" ),
5780 ),
5881 )
82+ # Currently, the interpreter class checks the deno availability at execution time.
83+ # We may consider checking it at the initialization time for better instruction.
84+ self .interpreter = PythonInterpreter ()
85+
5986 def _generate_signature (self , mode ):
6087 signature_dict = dict (self .input_fields )
6188 fields_for_mode = {
@@ -125,7 +152,7 @@ def _generate_instruction(self, mode):
125152 return "\n " .join (instr )
126153
127154
128- def parse_code (self , code_data ):
155+ def _parse_code (self , code_data ):
129156 code = (
130157 code_data .get ("generated_code" , "" ).split ("---" , 1 )[0 ].split ("\n \n \n " , 1 )[0 ]
131158 )
@@ -148,35 +175,42 @@ def parse_code(self, code_data):
148175 )
149176 return code_block , None
150177
151- def execute_code (self , code ):
178+ def _execute_code (self , code ):
179+ """
180+ Execute the code using PythonInterpreter and return the output or error.
181+ """
152182 if not code :
153- return code , None , "Error: Empty code before execution."
154- interpreter = PythonInterpreter ()
183+ return None , "Error: Empty code before execution."
184+
155185 try :
156- output = str (interpreter .execute (code ))
157- return code , output , None
186+ output = str (self . interpreter .execute (code ))
187+ return output , None
158188 except Exception as e :
159- return code , None , str (e )
189+ return None , str (e )
190+
160191 def forward (self , ** kwargs ):
161192 input_kwargs = {
162193 field_name : kwargs [field_name ] for field_name in self .input_fields
163194 }
164195 code_data = self .code_generate (** input_kwargs )
165- parsed_code , error = self .parse_code (code_data )
166- # FIXME: Don't try to execute the code if it didn't parse
167- code , output , error = self .execute_code (parsed_code )
168- hop = 0
169- while hop < self .max_iters and error :
170- print ("Error in code execution" )
196+ output = None
197+ code , error = self ._parse_code (code_data )
198+ if not error :
199+ output , error = self ._execute_code (code )
200+ hop = 1
201+ # Retying code generation and execution until no error or reach max_iters
202+ while error is not None :
203+ logger .error (f"Error in code execution: { error } " )
204+ if hop == self .max_iters :
205+ self .interpreter .shutdown ()
206+ raise RuntimeError (f"Max hops reached. Failed to run ProgramOfThought: { error } " )
171207 input_kwargs .update ({"previous_code" : code , "error" : error })
172208 code_data = self .code_regenerate (** input_kwargs )
173- parsed_code , error = self .parse_code (code_data )
174- # FIXME: Don't try to execute the code if it didn't parse
175- code , output , error = self .execute_code ( parsed_code )
209+ code , error = self ._parse_code (code_data )
210+ if not error :
211+ output , error = self ._execute_code ( code )
176212 hop += 1
177- if hop == self .max_iters :
178- print ("Max hops reached. Error persists." )
179- return None
180213 input_kwargs .update ({"final_generated_code" : code , "code_output" : output })
181214 answer_gen_result = self .generate_answer (** input_kwargs )
182- return answer_gen_result
215+ self .interpreter .shutdown ()
216+ return answer_gen_result
0 commit comments