11import logging
22import re
33from typing import Union , Type
4+ import json
45
56import dspy
67from dspy .signatures .signature import ensure_signature , Signature
1011
1112logger = logging .getLogger (__name__ )
1213
14+
1315class ProgramOfThought (Module ):
1416 """
1517 A DSPy module that runs Python programs to solve a problem.
@@ -39,28 +41,6 @@ def __init__(self, signature: Union[str, Type[Signature]], max_iters=3):
3941 self .input_fields = signature .input_fields
4042 self .output_fields = signature .output_fields
4143
42- assert len (self .output_fields ) == 1 , "PoT only supports one output field."
43-
44- self .output_field_name = next (iter (self .output_fields ))
45- inputs_ = ", " .join (
46- [f"`{ field_name } `" for field_name in self .input_fields .keys ()],
47- )
48- outputs_ = f"`{ self .output_field_name } `"
49-
50- assert len (self .output_fields ) == 1 , "PoT only supports one output field."
51-
52- instr = []
53- instr .append (
54- f"You will be given { inputs_ } and you will respond with { outputs_ } ." ,
55- )
56- instr .append (
57- f"Generating executable Python code that programmatically computes the correct { outputs_ } ." ,
58- )
59- instr .append (
60- f"After you're done with the computation, make sure the last line in your code evaluates to the correct value for { outputs_ } ." ,
61- )
62- instr = "\n " .join (instr )
63-
6444 self .code_generate = dspy .ChainOfThought (
6545 dspy .Signature (
6646 self ._generate_signature ("generate" ).fields ,
@@ -79,8 +59,7 @@ def __init__(self, signature: Union[str, Type[Signature]], max_iters=3):
7959 self ._generate_instruction ("answer" ),
8060 ),
8161 )
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.
62+ # It will raises exception when dspy cannot find available deno instance by now.
8463 self .interpreter = PythonInterpreter ()
8564
8665 def _generate_signature (self , mode ):
@@ -119,25 +98,28 @@ def _generate_signature(self, mode):
11998 prefix = "Code Output:" ,
12099 desc = "output of previously-generated python code" ,
121100 ),
122- self . output_field_name : self . signature . fields [ self . output_field_name ],
123- } ,
101+ }
102+ | self . signature . output_fields ,
124103 }
125104 signature_dict .update (fields_for_mode [mode ])
126105 return dspy .Signature (signature_dict )
127106
128107 def _generate_instruction (self , mode ):
129108 mode_inputs = ", " .join (
130- [
131- f"`{ field_name } `"
132- for field_name in self ._generate_signature (mode ).input_fields
133- ],
109+ [f"`{ field_name } `" for field_name in self ._generate_signature (mode ).input_fields ],
110+ )
111+ mode_outputs = ", " .join (
112+ [f"`{ field_name } `" for field_name in self ._generate_signature (mode ).output_fields ],
113+ )
114+ final_outputs = ", " .join (
115+ [f"`{ field_name } `" for field_name in self .output_fields ],
134116 )
135- mode_outputs = f"`{ self .output_field_name } `"
136117 if mode == "generate" :
137118 instr = [
138119 f"You will be given { mode_inputs } and you will respond with { mode_outputs } ." ,
139120 f"Generating executable Python code that programmatically computes the correct { mode_outputs } ." ,
140- f"After you're done with the computation, make sure the last line in your code evaluates to the correct value for { mode_outputs } ." ,
121+ "After you're done with the computation and think you have the answer, make sure to provide your answer by calling the preloaded function `final_answer()`." ,
122+ f'You should structure your answer in a dict object, like {{"field_a": answer_a, ...}}, evaluates to the correct value mapping for { final_outputs } .' ,
141123 ]
142124 elif mode == "regenerate" :
143125 instr = [
@@ -151,11 +133,8 @@ def _generate_instruction(self, mode):
151133
152134 return "\n " .join (instr )
153135
154-
155136 def _parse_code (self , code_data ):
156- code = (
157- code_data .get ("generated_code" , "" ).split ("---" , 1 )[0 ].split ("\n \n \n " , 1 )[0 ]
158- )
137+ code = code_data .get ("generated_code" , "" ).split ("---" , 1 )[0 ].split ("\n \n \n " , 1 )[0 ]
159138 code_match = re .search (r"```python[ \n](.*?)[ \n]```?" , code , re .DOTALL )
160139 code_block = (code_match .group (1 ) if code_match else code ).replace ("\\ n" , "\n " )
161140 if not code_block :
@@ -168,10 +147,14 @@ def _parse_code(self, code_data):
168147 code_block += "\n " + last_line_match .group (1 )
169148 else :
170149 code_block = re .sub (
171- r"([a-zA-Z_]\w* *=.*?)(?=[a-zA-Z_]\w* *=)" , r"\1\n" , code_block ,
150+ r"([a-zA-Z_]\w* *=.*?)(?=[a-zA-Z_]\w* *=)" ,
151+ r"\1\n" ,
152+ code_block ,
172153 )
173154 code_block = re .sub (
174- r"([a-zA-Z_]\w* *=.*?)([a-zA-Z_]\w*)$" , r"\1\n\2" , code_block ,
155+ r"([a-zA-Z_]\w* *=.*?)([a-zA-Z_]\w*)$" ,
156+ r"\1\n\2" ,
157+ code_block ,
175158 )
176159 return code_block , None
177160
@@ -181,17 +164,16 @@ def _execute_code(self, code):
181164 """
182165 if not code :
183166 return None , "Error: Empty code before execution."
184-
167+
185168 try :
186- output = str (self .interpreter .execute (code ))
169+ # Since it's more complex structure now, just blindly use json to represents all.
170+ output = json .dumps (self .interpreter .execute (code ))
187171 return output , None
188172 except Exception as e :
189173 return None , str (e )
190174
191175 def forward (self , ** kwargs ):
192- input_kwargs = {
193- field_name : kwargs [field_name ] for field_name in self .input_fields
194- }
176+ input_kwargs = {field_name : kwargs [field_name ] for field_name in self .input_fields }
195177 code_data = self .code_generate (** input_kwargs )
196178 output = None
197179 code , error = self ._parse_code (code_data )
0 commit comments