pdd-cli 0.0.24__py3-none-any.whl → 0.0.25__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.
Potentially problematic release.
This version of pdd-cli might be problematic. Click here for more details.
- pdd/__init__.py +7 -1
- pdd/bug_main.py +5 -1
- pdd/bug_to_unit_test.py +16 -5
- pdd/change.py +2 -1
- pdd/change_main.py +407 -189
- pdd/cli.py +853 -301
- pdd/code_generator.py +2 -1
- pdd/conflicts_in_prompts.py +2 -1
- pdd/construct_paths.py +377 -222
- pdd/context_generator.py +2 -1
- pdd/continue_generation.py +3 -2
- pdd/crash_main.py +55 -20
- pdd/detect_change.py +2 -1
- pdd/fix_code_loop.py +465 -160
- pdd/fix_code_module_errors.py +7 -4
- pdd/fix_error_loop.py +9 -9
- pdd/fix_errors_from_unit_tests.py +207 -365
- pdd/fix_main.py +31 -4
- pdd/fix_verification_errors.py +60 -34
- pdd/fix_verification_errors_loop.py +842 -768
- pdd/fix_verification_main.py +412 -0
- pdd/generate_output_paths.py +427 -189
- pdd/generate_test.py +3 -2
- pdd/increase_tests.py +2 -2
- pdd/llm_invoke.py +14 -3
- pdd/preprocess.py +3 -3
- pdd/process_csv_change.py +466 -154
- pdd/prompts/extract_prompt_update_LLM.prompt +11 -5
- pdd/prompts/extract_unit_code_fix_LLM.prompt +2 -2
- pdd/prompts/fix_code_module_errors_LLM.prompt +29 -0
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +5 -5
- pdd/prompts/generate_test_LLM.prompt +9 -3
- pdd/prompts/update_prompt_LLM.prompt +3 -3
- pdd/split.py +6 -5
- pdd/split_main.py +13 -4
- pdd/trace_main.py +7 -0
- pdd/xml_tagger.py +2 -1
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/METADATA +4 -4
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/RECORD +43 -42
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/WHEEL +1 -1
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.24.dist-info → pdd_cli-0.0.25.dist-info}/top_level.txt +0 -0
pdd/process_csv_change.py
CHANGED
|
@@ -1,182 +1,494 @@
|
|
|
1
|
-
# process_csv_change.py
|
|
2
|
-
|
|
3
|
-
from typing import List, Dict, Tuple
|
|
4
|
-
import os
|
|
5
1
|
import csv
|
|
6
|
-
|
|
7
|
-
import
|
|
2
|
+
import os
|
|
3
|
+
from typing import List, Dict, Tuple, Optional
|
|
8
4
|
|
|
9
5
|
from rich.console import Console
|
|
10
|
-
from rich.pretty import Pretty
|
|
11
|
-
from rich.panel import Panel
|
|
12
6
|
|
|
13
|
-
|
|
7
|
+
# Use relative imports for internal modules within the package
|
|
8
|
+
from .change import change
|
|
9
|
+
from .get_extension import get_extension
|
|
10
|
+
# Assuming EXTRACTION_STRENGTH and DEFAULT_STRENGTH might be needed later,
|
|
11
|
+
# or just acknowledging their existence as per the prompt.
|
|
12
|
+
# from .. import EXTRACTION_STRENGTH, DEFAULT_STRENGTH
|
|
13
|
+
|
|
14
|
+
# No changes needed in the code_under_test based on these specific errors.
|
|
14
15
|
|
|
15
16
|
console = Console()
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
def resolve_prompt_path(prompt_name: str, csv_file: str, code_directory: str) -> Optional[str]:
|
|
19
|
+
"""
|
|
20
|
+
Attempts to find a prompt file by trying several possible locations.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
prompt_name: The name or path of the prompt file from the CSV
|
|
24
|
+
csv_file: Path to the CSV file (for relative path resolution)
|
|
25
|
+
code_directory: Path to the code directory (as another potential source)
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Resolved path to the prompt file if found, None otherwise
|
|
29
|
+
"""
|
|
30
|
+
# Ensure paths are absolute for reliable checking
|
|
31
|
+
abs_code_directory = os.path.abspath(code_directory)
|
|
32
|
+
abs_csv_dir = os.path.abspath(os.path.dirname(csv_file))
|
|
33
|
+
abs_cwd = os.path.abspath(os.getcwd())
|
|
34
|
+
|
|
35
|
+
# List of locations to try, in order of priority
|
|
36
|
+
possible_locations = [
|
|
37
|
+
prompt_name, # Try exactly as specified first (could be absolute or relative to CWD)
|
|
38
|
+
os.path.join(abs_cwd, os.path.basename(prompt_name)), # Try in current directory (basename only)
|
|
39
|
+
os.path.join(abs_csv_dir, os.path.basename(prompt_name)), # Try relative to CSV (basename only)
|
|
40
|
+
os.path.join(abs_code_directory, os.path.basename(prompt_name)), # Try in code directory (basename only)
|
|
41
|
+
os.path.join(abs_csv_dir, prompt_name), # Try relative to CSV (full prompt_name path)
|
|
42
|
+
os.path.join(abs_code_directory, prompt_name) # Try relative to code_dir (full prompt_name path)
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# Try each location, normalizing and checking existence/type
|
|
46
|
+
checked_locations = set()
|
|
47
|
+
for location in possible_locations:
|
|
48
|
+
try:
|
|
49
|
+
# Normalize path to handle relative parts like '.' or '..' and make absolute
|
|
50
|
+
normalized_location = os.path.abspath(location)
|
|
51
|
+
if normalized_location in checked_locations:
|
|
52
|
+
continue
|
|
53
|
+
checked_locations.add(normalized_location)
|
|
54
|
+
|
|
55
|
+
# Check if it exists and is a file
|
|
56
|
+
if os.path.exists(normalized_location) and os.path.isfile(normalized_location):
|
|
57
|
+
return normalized_location
|
|
58
|
+
except Exception:
|
|
59
|
+
# Ignore errors during path resolution (e.g., invalid characters)
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# If we get here, file was not found
|
|
63
|
+
return None
|
|
20
64
|
|
|
21
65
|
def process_csv_change(
|
|
22
66
|
csv_file: str,
|
|
23
67
|
strength: float,
|
|
24
68
|
temperature: float,
|
|
25
69
|
code_directory: str,
|
|
26
|
-
language: str,
|
|
27
|
-
extension: str,
|
|
70
|
+
language: str, # Default language if not specified in prompt filename
|
|
71
|
+
extension: str, # Default extension (unused if language suffix found)
|
|
28
72
|
budget: float
|
|
29
|
-
) -> Tuple[bool, List[Dict[str, str]], float, str]:
|
|
73
|
+
) -> Tuple[bool, List[Dict[str, str]], float, Optional[str]]:
|
|
30
74
|
"""
|
|
31
|
-
|
|
75
|
+
Reads a CSV file, processes each row to modify associated code files using an LLM,
|
|
76
|
+
and returns the results.
|
|
32
77
|
|
|
33
78
|
Args:
|
|
34
|
-
csv_file
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
79
|
+
csv_file: Path to the input CSV file. Must contain 'prompt_name' and
|
|
80
|
+
'change_instructions' columns.
|
|
81
|
+
strength: Strength parameter for the LLM model (0.0 to 1.0).
|
|
82
|
+
temperature: Temperature parameter for the LLM model (0.0 to 1.0).
|
|
83
|
+
code_directory: Path to the directory containing the code files.
|
|
84
|
+
language: Default programming language if the prompt filename doesn't
|
|
85
|
+
specify one (e.g., '_python').
|
|
86
|
+
extension: Default file extension (including '.') if language cannot be inferred.
|
|
87
|
+
Note: This is less likely to be used if `get_extension` covers the default language.
|
|
88
|
+
budget: Maximum allowed cost for all LLM operations. Must be non-negative.
|
|
41
89
|
|
|
42
90
|
Returns:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
91
|
+
A tuple containing:
|
|
92
|
+
- success (bool): True if all rows attempted were processed without errors
|
|
93
|
+
(even if skipped due to missing data) and budget was not exceeded.
|
|
94
|
+
False otherwise (including partial success due to budget or errors).
|
|
95
|
+
- list_of_jsons (List[Dict[str, str]]): A list of dictionaries, where each
|
|
96
|
+
dictionary contains 'file_name' (original prompt name from CSV) and 'modified_prompt'.
|
|
97
|
+
- total_cost (float): The total cost incurred for the LLM operations.
|
|
98
|
+
- model_name (Optional[str]): The name of the LLM model used for the first successful change.
|
|
99
|
+
Returns "N/A" if no changes were successfully processed.
|
|
100
|
+
Returns None if an input validation error occurred before processing.
|
|
48
101
|
"""
|
|
49
102
|
list_of_jsons: List[Dict[str, str]] = []
|
|
50
103
|
total_cost: float = 0.0
|
|
51
|
-
model_name: str =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if not
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
104
|
+
model_name: Optional[str] = None
|
|
105
|
+
overall_success: bool = True # Assume success until an error occurs or budget exceeded
|
|
106
|
+
|
|
107
|
+
# --- Input Validation ---
|
|
108
|
+
if not os.path.exists(csv_file) or not os.path.isfile(csv_file): # Check it's a file too
|
|
109
|
+
console.print(f"[bold red]Error:[/bold red] CSV file not found or is not a file: '{csv_file}'")
|
|
110
|
+
return False, [], 0.0, None # Return None for model_name on early exit
|
|
111
|
+
if not os.path.isdir(code_directory):
|
|
112
|
+
console.print(f"[bold red]Error:[/bold red] Code directory not found or is not a directory: '{code_directory}'")
|
|
113
|
+
return False, [], 0.0, None # Return None for model_name on early exit
|
|
114
|
+
if not 0.0 <= strength <= 1.0:
|
|
115
|
+
console.print(f"[bold red]Error:[/bold red] 'strength' must be between 0.0 and 1.0. Given: {strength}")
|
|
116
|
+
return False, [], 0.0, None # Return None for model_name on early exit
|
|
117
|
+
if not 0.0 <= temperature <= 1.0: # Added temperature validation (assuming 0-1 range)
|
|
118
|
+
console.print(f"[bold red]Error:[/bold red] 'temperature' must be between 0.0 and 1.0. Given: {temperature}")
|
|
119
|
+
return False, [], 0.0, None # Return None for model_name on early exit
|
|
120
|
+
if budget < 0.0:
|
|
121
|
+
console.print(f"[bold red]Error:[/bold red] 'budget' must be non-negative. Given: {budget}")
|
|
122
|
+
return False, [], 0.0, None # Return None for model_name on early exit
|
|
123
|
+
# --- End Input Validation ---
|
|
124
|
+
|
|
125
|
+
console.print(f"[cyan]Starting CSV processing:[/cyan] '{os.path.abspath(csv_file)}'")
|
|
126
|
+
console.print(f"[cyan]Code directory:[/cyan] '{os.path.abspath(code_directory)}'")
|
|
127
|
+
console.print(f"[cyan]Budget:[/cyan] ${budget:.2f}")
|
|
128
|
+
|
|
129
|
+
processed_rows = 0
|
|
130
|
+
successful_changes = 0
|
|
72
131
|
|
|
73
132
|
try:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
133
|
+
header_valid = True
|
|
134
|
+
with open(csv_file, mode='r', newline='', encoding='utf-8') as file:
|
|
135
|
+
header_valid = True # Flag to track header status
|
|
136
|
+
reader = None # Initialize reader to None
|
|
137
|
+
|
|
138
|
+
# Read the header line manually
|
|
139
|
+
header_line = file.readline()
|
|
140
|
+
if not header_line:
|
|
141
|
+
# Handle empty file
|
|
142
|
+
console.print("[yellow]Warning:[/yellow] CSV file is empty.")
|
|
143
|
+
header_valid = False # Treat as invalid header for flow control
|
|
144
|
+
# No rows will be processed, overall_success remains True initially
|
|
145
|
+
else:
|
|
146
|
+
# Parse the header line
|
|
147
|
+
actual_fieldnames = [col.strip() for col in header_line.strip().split(',')]
|
|
148
|
+
|
|
149
|
+
# Check if required columns are present
|
|
150
|
+
required_cols = {'prompt_name', 'change_instructions'}
|
|
151
|
+
missing_cols = required_cols - set(actual_fieldnames)
|
|
152
|
+
if missing_cols:
|
|
153
|
+
console.print(f"[bold red]Error:[/bold red] CSV file must contain 'prompt_name' and 'change_instructions' columns. Missing: {missing_cols}")
|
|
154
|
+
overall_success = False # Mark overall failure
|
|
155
|
+
header_valid = False # Mark header as invalid
|
|
156
|
+
else:
|
|
157
|
+
# Header is valid and has required columns, initialize DictReader for remaining lines
|
|
158
|
+
reader = csv.DictReader(file, fieldnames=actual_fieldnames)
|
|
159
|
+
|
|
160
|
+
# Only loop if the header was valid and reader was initialized
|
|
161
|
+
if header_valid and reader:
|
|
162
|
+
for i, row in enumerate(reader):
|
|
163
|
+
row_num = i + 1
|
|
164
|
+
processed_rows += 1
|
|
165
|
+
console.print(f"\n[cyan]Processing row {row_num}...[/cyan]")
|
|
166
|
+
|
|
167
|
+
prompt_name_from_csv = row.get('prompt_name', '').strip()
|
|
168
|
+
change_instructions = row.get('change_instructions', '').strip()
|
|
169
|
+
|
|
170
|
+
if not prompt_name_from_csv:
|
|
171
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Missing 'prompt_name' in row {row_num}. Skipping.")
|
|
172
|
+
overall_success = False # Mark as not fully successful if skips occur
|
|
108
173
|
continue
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
174
|
+
if not change_instructions:
|
|
175
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Missing 'change_instructions' in row {row_num}. Skipping.")
|
|
176
|
+
overall_success = False # Mark as not fully successful if skips occur
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# Try to resolve the prompt file path
|
|
180
|
+
resolved_prompt_path = resolve_prompt_path(prompt_name_from_csv, csv_file, code_directory)
|
|
181
|
+
if not resolved_prompt_path:
|
|
182
|
+
console.print(f"[bold red]Error:[/bold red] Prompt file for '{prompt_name_from_csv}' not found in any location (row {row_num}).")
|
|
183
|
+
console.print(f" [dim]Searched: as is, CWD, CSV dir, code dir (using basename and full name)[/dim]")
|
|
184
|
+
overall_success = False
|
|
115
185
|
continue
|
|
116
186
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
console.print(f" [dim]Prompt name from CSV:[/dim] {prompt_name_from_csv}")
|
|
188
|
+
console.print(f" [dim]Resolved prompt path:[/dim] {resolved_prompt_path}")
|
|
189
|
+
|
|
190
|
+
# --- Step 2a: Initialize variables ---
|
|
191
|
+
input_prompt: Optional[str] = None
|
|
192
|
+
input_code: Optional[str] = None
|
|
193
|
+
input_code_path: Optional[str] = None
|
|
194
|
+
|
|
195
|
+
# Read the input prompt from the resolved path
|
|
196
|
+
try:
|
|
197
|
+
with open(resolved_prompt_path, 'r', encoding='utf-8') as f:
|
|
198
|
+
input_prompt = f.read()
|
|
199
|
+
except IOError as e:
|
|
200
|
+
console.print(f"[bold red]Error:[/bold red] Could not read prompt file '{resolved_prompt_path}' (row {row_num}): {e}")
|
|
201
|
+
overall_success = False
|
|
202
|
+
continue # Skip to next row
|
|
203
|
+
|
|
204
|
+
# Parse prompt_name to determine input_code_name
|
|
205
|
+
try:
|
|
206
|
+
# i. remove the path and suffix _language.prompt from the prompt_name
|
|
207
|
+
prompt_filename = os.path.basename(resolved_prompt_path) # Use basename of resolved path
|
|
208
|
+
base_name, ext = os.path.splitext(prompt_filename) # Removes .prompt (or other ext)
|
|
209
|
+
|
|
210
|
+
# Ensure it actually ends with .prompt before stripping language
|
|
211
|
+
if ext.lower() != '.prompt':
|
|
212
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Prompt file '{prompt_filename}' does not end with '.prompt'. Attempting to parse language anyway (row {row_num}).")
|
|
213
|
+
# Keep base_name as is, don't assume .prompt was the only extension part
|
|
214
|
+
|
|
215
|
+
file_stem = base_name
|
|
216
|
+
actual_language = language # Default language
|
|
217
|
+
language_from_suffix = False # Track if language came from suffix
|
|
218
|
+
|
|
219
|
+
# Check for _language suffix
|
|
220
|
+
if '_' in base_name:
|
|
221
|
+
parts = base_name.rsplit('_', 1)
|
|
222
|
+
# Check if the suffix looks like a language identifier (simple check: alpha only)
|
|
223
|
+
if len(parts) == 2 and parts[1].isalpha():
|
|
224
|
+
file_stem = parts[0]
|
|
225
|
+
# Use capitalize for consistency, matching get_extension examples
|
|
226
|
+
actual_language = parts[1].capitalize()
|
|
227
|
+
language_from_suffix = True # Set flag
|
|
228
|
+
console.print(f" [dim]Inferred language from filename:[/dim] {actual_language}")
|
|
229
|
+
else:
|
|
230
|
+
console.print(f" [dim]Suffix '_{parts[1]}' not recognized as language, using default:[/dim] {language}")
|
|
231
|
+
else:
|
|
232
|
+
console.print(f" [dim]Using default language:[/dim] {language}")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ii. use get_extension to infer the extension
|
|
236
|
+
try:
|
|
237
|
+
# print(f"DEBUG: Trying get_extension for language: '{actual_language}'") # Keep commented
|
|
238
|
+
# Use the capitalized version for lookup
|
|
239
|
+
code_extension = get_extension(actual_language.capitalize())
|
|
240
|
+
console.print(f" [dim]Inferred extension for {actual_language}:[/dim] '{code_extension}'")
|
|
241
|
+
except ValueError: # Handle case where get_extension doesn't know the language
|
|
242
|
+
# print(f"DEBUG: get_extension failed. Falling back to default extension parameter: '{extension}'") # Keep commented
|
|
243
|
+
if language_from_suffix:
|
|
244
|
+
# Suffix was present but get_extension failed for it! Error out for this row.
|
|
245
|
+
console.print(f"[bold red]Error:[/bold red] Language '{actual_language}' found in prompt suffix, but its extension is unknown (row {row_num}). Skipping.")
|
|
246
|
+
overall_success = False # Mark failure
|
|
247
|
+
continue # Skip to next row
|
|
248
|
+
else:
|
|
249
|
+
# No suffix, and get_extension failed for the default language.
|
|
250
|
+
# Fallback to the 'extension' parameter as a last resort (current behavior).
|
|
251
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Could not determine extension for default language '{actual_language}'. Using default extension parameter '{extension}' (row {row_num}).")
|
|
252
|
+
code_extension = extension # Fallback to the provided default extension parameter
|
|
253
|
+
# Do not mark overall_success as False for this warning, it's a fallback mechanism
|
|
254
|
+
# print(f"DEBUG: Determined code extension: '{code_extension}'") # Keep commented
|
|
255
|
+
|
|
256
|
+
# iii. add the suffix extension to the prompt_name (stem)
|
|
257
|
+
input_code_filename = file_stem + code_extension
|
|
258
|
+
|
|
259
|
+
# iv. Construct code file path: place it directly in code_directory.
|
|
260
|
+
input_code_path = os.path.join(code_directory, input_code_filename)
|
|
261
|
+
console.print(f" [dim]Derived target code path:[/dim] {input_code_path}")
|
|
262
|
+
print(f"DEBUG: Attempting to access code file path: '{input_code_path}'") # Added log
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# Read the input code from the input_code_path
|
|
266
|
+
if not os.path.exists(input_code_path) or not os.path.isfile(input_code_path):
|
|
267
|
+
console.print(f"[bold red]Error:[/bold red] Derived code file not found or is not a file: '{input_code_path}' (row {row_num})")
|
|
268
|
+
overall_success = False
|
|
269
|
+
continue # Skip to next row
|
|
270
|
+
with open(input_code_path, 'r', encoding='utf-8') as f:
|
|
271
|
+
input_code = f.read()
|
|
272
|
+
|
|
273
|
+
except Exception as e:
|
|
274
|
+
console.print(f"[bold red]Error:[/bold red] Failed to parse filenames or read code file for row {row_num}: {e}")
|
|
275
|
+
overall_success = False
|
|
276
|
+
continue # Skip to next row
|
|
277
|
+
|
|
278
|
+
# Ensure we have all necessary components before calling change
|
|
279
|
+
# (Should be guaranteed by checks above, but added defensively)
|
|
280
|
+
if input_prompt is None or input_code is None or change_instructions is None:
|
|
281
|
+
console.print(f"[bold red]Internal Error:[/bold red] Missing required data (prompt, code, or instructions) for row {row_num}. Skipping.")
|
|
282
|
+
overall_success = False
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
# --- Step 2b: Call the change function ---
|
|
286
|
+
try:
|
|
287
|
+
# Check budget *before* making the potentially expensive call
|
|
288
|
+
if total_cost >= budget:
|
|
289
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Budget (${budget:.2f}) already met or exceeded before processing row {row_num}. Stopping.")
|
|
290
|
+
overall_success = False # Mark as incomplete due to budget
|
|
291
|
+
break # Exit the loop
|
|
292
|
+
|
|
293
|
+
console.print(f" [dim]Calling LLM for change... (Budget remaining: ${budget - total_cost:.2f})[/dim]")
|
|
294
|
+
modified_prompt, cost, current_model_name = change(
|
|
295
|
+
input_prompt=input_prompt,
|
|
296
|
+
input_code=input_code,
|
|
297
|
+
change_prompt=change_instructions,
|
|
298
|
+
strength=strength,
|
|
299
|
+
temperature=temperature
|
|
300
|
+
)
|
|
301
|
+
console.print(f" [dim]Change cost:[/dim] ${cost:.6f}")
|
|
302
|
+
console.print(f" [dim]Model used:[/dim] {current_model_name}")
|
|
303
|
+
|
|
304
|
+
# --- Step 2c: Add cost ---
|
|
305
|
+
new_total_cost = total_cost + cost
|
|
306
|
+
|
|
307
|
+
# --- Step 2d: Check budget *after* call ---
|
|
308
|
+
console.print(f" [dim]Cumulative cost:[/dim] ${new_total_cost:.6f} / ${budget:.2f}")
|
|
309
|
+
if new_total_cost > budget:
|
|
310
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Budget exceeded (${budget:.2f}) after processing row {row_num}. Change from this row NOT saved. Stopping.")
|
|
311
|
+
total_cost = new_total_cost # Record the cost even if result isn't saved
|
|
312
|
+
overall_success = False # Mark as incomplete due to budget
|
|
313
|
+
break # Exit the loop
|
|
314
|
+
else:
|
|
315
|
+
total_cost = new_total_cost # Update cost only if within budget
|
|
316
|
+
|
|
317
|
+
# --- Step 2e: Add successful result ---
|
|
318
|
+
# Capture model name on first successful call within budget
|
|
319
|
+
if model_name is None and current_model_name:
|
|
320
|
+
model_name = current_model_name
|
|
321
|
+
# Warn if model name changes on subsequent calls
|
|
322
|
+
elif current_model_name and model_name != current_model_name:
|
|
323
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Model name changed from '{model_name}' to '{current_model_name}' in row {row_num}.")
|
|
324
|
+
# Keep the first model_name
|
|
325
|
+
|
|
326
|
+
list_of_jsons.append({
|
|
327
|
+
"file_name": prompt_name_from_csv, # Use original prompt name from CSV as key
|
|
328
|
+
"modified_prompt": modified_prompt
|
|
329
|
+
})
|
|
330
|
+
successful_changes += 1
|
|
331
|
+
console.print(f" [green]Successfully processed change for:[/green] {prompt_name_from_csv}")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
except Exception as e:
|
|
335
|
+
console.print(f"[bold red]Error:[/bold red] Failed during 'change' call for '{prompt_name_from_csv}' (row {row_num}): {e}")
|
|
336
|
+
overall_success = False
|
|
337
|
+
# Continue to the next row even if one fails
|
|
338
|
+
|
|
339
|
+
except FileNotFoundError:
|
|
340
|
+
# This case should be caught by the initial validation, but included for robustness
|
|
341
|
+
console.print(f"[bold red]Error:[/bold red] CSV file not found at '{csv_file}'")
|
|
342
|
+
return False, [], 0.0, None
|
|
343
|
+
except IOError as e:
|
|
344
|
+
console.print(f"[bold red]Error:[/bold red] Could not read CSV file '{csv_file}': {e}")
|
|
345
|
+
return False, [], 0.0, None
|
|
179
346
|
except Exception as e:
|
|
180
|
-
console.print(f"[bold red]
|
|
181
|
-
|
|
182
|
-
return
|
|
347
|
+
console.print(f"[bold red]An unexpected error occurred during CSV processing:[/bold red] {e}")
|
|
348
|
+
# Return potentially partial results, but mark as failure
|
|
349
|
+
return False, list_of_jsons, total_cost, model_name if model_name else "N/A"
|
|
350
|
+
|
|
351
|
+
# --- Step 3: Return results ---
|
|
352
|
+
console.print("\n[bold cyan]=== Processing Summary ===[/bold cyan]")
|
|
353
|
+
if processed_rows == 0 and overall_success:
|
|
354
|
+
# This case is handled by the empty file check earlier, but keep for clarity
|
|
355
|
+
console.print("[yellow]No rows found in CSV file.[/yellow]")
|
|
356
|
+
elif not overall_success:
|
|
357
|
+
console.print("[yellow]Processing finished with errors, skips, or budget exceeded.[/yellow]")
|
|
358
|
+
else:
|
|
359
|
+
console.print("[green]CSV processing finished successfully.[/green]")
|
|
360
|
+
|
|
361
|
+
console.print(f"[bold]Total Rows Processed:[/bold] {processed_rows}")
|
|
362
|
+
console.print(f"[bold]Successful Changes:[/bold] {successful_changes}")
|
|
363
|
+
console.print(f"[bold]Total Cost:[/bold] ${total_cost:.6f}")
|
|
364
|
+
console.print(f"[bold]Model Used (first success):[/bold] {model_name if model_name else 'N/A'}")
|
|
365
|
+
console.print(f"[bold]Overall Success Status:[/bold] {overall_success}")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# --- Summary printing block should be right above here ---
|
|
369
|
+
|
|
370
|
+
final_model_name = model_name if model_name else "N/A"
|
|
371
|
+
# If overall_success is False AND header_valid is False, it means we failed on header validation.
|
|
372
|
+
if not overall_success and not header_valid:
|
|
373
|
+
final_model_name = None
|
|
374
|
+
|
|
375
|
+
return overall_success, list_of_jsons, total_cost, final_model_name
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# Example usage (assuming this file is part of a package structure)
|
|
379
|
+
# Keep the example usage block as is for basic testing/demonstration
|
|
380
|
+
if __name__ == '__main__':
|
|
381
|
+
# This block is for demonstration/testing purposes.
|
|
382
|
+
# In a real package, you'd import and call process_csv_change from another module.
|
|
383
|
+
|
|
384
|
+
# Create dummy files and directories for testing
|
|
385
|
+
if not os.path.exists("temp_code_dir"):
|
|
386
|
+
os.makedirs("temp_code_dir")
|
|
387
|
+
if not os.path.exists("temp_prompt_dir"):
|
|
388
|
+
os.makedirs("temp_prompt_dir")
|
|
389
|
+
|
|
390
|
+
# Dummy CSV
|
|
391
|
+
csv_content = """prompt_name,change_instructions
|
|
392
|
+
temp_prompt_dir/func1_python.prompt,"Add error handling for negative numbers"
|
|
393
|
+
temp_prompt_dir/script2_javascript.prompt,"Convert to async/await"
|
|
394
|
+
temp_prompt_dir/invalid_file.prompt,"This will fail code file lookup"
|
|
395
|
+
temp_prompt_dir/config_yaml.prompt,"Increase timeout value"
|
|
396
|
+
temp_prompt_dir/missing_instr.prompt,
|
|
397
|
+
missing_prompt.prompt,"This prompt file won't be found"
|
|
398
|
+
temp_prompt_dir/budget_breaker_python.prompt,"This might break the budget"
|
|
399
|
+
"""
|
|
400
|
+
with open("temp_changes.csv", "w") as f:
|
|
401
|
+
f.write(csv_content)
|
|
402
|
+
|
|
403
|
+
# Dummy prompt files
|
|
404
|
+
with open("temp_prompt_dir/func1_python.prompt", "w") as f:
|
|
405
|
+
f.write("Create a Python function for factorial.")
|
|
406
|
+
with open("temp_prompt_dir/script2_javascript.prompt", "w") as f:
|
|
407
|
+
f.write("Write a JS script using callbacks.")
|
|
408
|
+
with open("temp_prompt_dir/invalid_file.prompt", "w") as f: # Code file missing
|
|
409
|
+
f.write("Some prompt.")
|
|
410
|
+
with open("temp_prompt_dir/config_yaml.prompt", "w") as f:
|
|
411
|
+
f.write("Describe the YAML config.")
|
|
412
|
+
with open("temp_prompt_dir/missing_instr.prompt", "w") as f: # Instructions missing in CSV
|
|
413
|
+
f.write("Prompt with missing instructions.")
|
|
414
|
+
# missing_prompt.prompt does not exist
|
|
415
|
+
with open("temp_prompt_dir/budget_breaker_python.prompt", "w") as f:
|
|
416
|
+
f.write("Prompt for budget breaker.")
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# Dummy code files
|
|
420
|
+
with open("temp_code_dir/func1.py", "w") as f:
|
|
421
|
+
f.write("def factorial(n):\n if n == 0: return 1\n return n * factorial(n-1)\n")
|
|
422
|
+
with open("temp_code_dir/script2.js", "w") as f:
|
|
423
|
+
f.write("function fetchData(url, callback) { /* ... */ }")
|
|
424
|
+
# No code file for invalid_file
|
|
425
|
+
with open("temp_code_dir/config.yaml", "w") as f:
|
|
426
|
+
f.write("timeout: 10s\nretries: 3\n")
|
|
427
|
+
with open("temp_code_dir/budget_breaker.py", "w") as f:
|
|
428
|
+
f.write("print('Hello')")
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
# Dummy internal modules (replace with actual imports if running within package)
|
|
432
|
+
# Mocking the internal functions for standalone execution
|
|
433
|
+
def mock_change(input_prompt, input_code, change_prompt, strength, temperature):
|
|
434
|
+
# Simulate cost and model name
|
|
435
|
+
cost = 0.01 + (0.0001 * len(change_prompt))
|
|
436
|
+
model = "mock-gpt-4"
|
|
437
|
+
# Simulate success or failure based on input
|
|
438
|
+
if "invalid" in input_prompt.lower():
|
|
439
|
+
raise ValueError("Simulated model error for invalid input.")
|
|
440
|
+
modified_prompt = f"MODIFIED: {input_prompt[:30]}... based on '{change_prompt[:30]}...'"
|
|
441
|
+
return modified_prompt, cost, model
|
|
442
|
+
|
|
443
|
+
def mock_get_extension(language_name):
|
|
444
|
+
lang_map = {
|
|
445
|
+
"Python": ".py",
|
|
446
|
+
"Javascript": ".js", # Note: Case difference from prompt example
|
|
447
|
+
"Yaml": ".yaml",
|
|
448
|
+
"Makefile": "" # Example from prompt
|
|
449
|
+
}
|
|
450
|
+
# Match behavior of raising ValueError if unknown, case-sensitive
|
|
451
|
+
if language_name in lang_map:
|
|
452
|
+
return lang_map[language_name]
|
|
453
|
+
else:
|
|
454
|
+
raise ValueError(f"Unknown language: {language_name}")
|
|
455
|
+
|
|
456
|
+
# Replace the actual imports with mocks for the example
|
|
457
|
+
change_original = change
|
|
458
|
+
get_extension_original = get_extension
|
|
459
|
+
change = mock_change
|
|
460
|
+
get_extension = mock_get_extension
|
|
461
|
+
|
|
462
|
+
console.print("\n[bold magenta]=== Running Example Usage ===[/bold magenta]")
|
|
463
|
+
|
|
464
|
+
# Call the function
|
|
465
|
+
success_status, results, final_cost, final_model = process_csv_change(
|
|
466
|
+
csv_file="temp_changes.csv",
|
|
467
|
+
strength=0.6,
|
|
468
|
+
temperature=0.1,
|
|
469
|
+
code_directory="temp_code_dir",
|
|
470
|
+
language="UnknownLang", # Default language
|
|
471
|
+
extension=".txt", # Default extension (used if get_extension fails)
|
|
472
|
+
budget=0.05 # Set a budget likely to be exceeded
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
console.print("\n[bold magenta]=== Example Usage Results ===[/bold magenta]")
|
|
476
|
+
# console.print(f"Overall Success: {success_status}") # Printed in summary now
|
|
477
|
+
# console.print(f"Total Cost: ${final_cost:.6f}") # Printed in summary now
|
|
478
|
+
# console.print(f"Model Name: {final_model}") # Printed in summary now
|
|
479
|
+
console.print("Modified Prompts JSON (results list):")
|
|
480
|
+
console.print(results)
|
|
481
|
+
|
|
482
|
+
# Restore original functions if needed elsewhere
|
|
483
|
+
change = change_original
|
|
484
|
+
get_extension = get_extension_original
|
|
485
|
+
|
|
486
|
+
# Cleanup dummy files (optional)
|
|
487
|
+
# import shutil
|
|
488
|
+
# try:
|
|
489
|
+
# os.remove("temp_changes.csv")
|
|
490
|
+
# shutil.rmtree("temp_code_dir")
|
|
491
|
+
# shutil.rmtree("temp_prompt_dir")
|
|
492
|
+
# console.print("\n[bold magenta]=== Cleaned up temporary files ===[/bold magenta]")
|
|
493
|
+
# except OSError as e:
|
|
494
|
+
# console.print(f"\n[yellow]Warning: Could not clean up all temp files: {e}[/yellow]")
|