algomath-extract 1.0.0
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.
- package/README.md +260 -0
- package/bin/algo-extract.js +143 -0
- package/bin/algo-generate.js +102 -0
- package/bin/algo-help.js +136 -0
- package/bin/algo-list.js +56 -0
- package/bin/algo-run.js +141 -0
- package/bin/algo-status.js +88 -0
- package/bin/algo-verify.js +189 -0
- package/bin/install.js +349 -0
- package/package.json +57 -0
- package/requirements.txt +20 -0
- package/src/__pycache__/intent.cpython-313.pyc +0 -0
- package/src/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/src/cli/cli_entry.py +106 -0
- package/src/cli/commands.py +339 -0
- package/src/execution/__init__.py +74 -0
- package/src/execution/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/display.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/errors.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/executor.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/sandbox.cpython-313.pyc +0 -0
- package/src/execution/display.py +261 -0
- package/src/execution/errors.py +158 -0
- package/src/execution/executor.py +253 -0
- package/src/execution/sandbox.py +333 -0
- package/src/extraction/__init__.py +102 -0
- package/src/extraction/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/boundaries.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/errors.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/llm_extraction.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/notation.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/parser.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/pdf_processor.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/prompts.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/review.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/schema.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/validation.cpython-313.pyc +0 -0
- package/src/extraction/boundaries.py +281 -0
- package/src/extraction/errors.py +156 -0
- package/src/extraction/llm_extraction.py +225 -0
- package/src/extraction/notation.py +240 -0
- package/src/extraction/parser.py +402 -0
- package/src/extraction/pdf_processor.py +281 -0
- package/src/extraction/prompts.py +90 -0
- package/src/extraction/review.py +298 -0
- package/src/extraction/schema.py +173 -0
- package/src/extraction/validation.py +202 -0
- package/src/generation/__init__.py +79 -0
- package/src/generation/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/code_generator.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/errors.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/hybrid.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/llm_generator.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/persistence.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/prompts.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/review.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/templates.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/types.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/validation.cpython-313.pyc +0 -0
- package/src/generation/code_generator.py +375 -0
- package/src/generation/errors.py +84 -0
- package/src/generation/hybrid.py +210 -0
- package/src/generation/llm_generator.py +223 -0
- package/src/generation/persistence.py +221 -0
- package/src/generation/prompts.py +202 -0
- package/src/generation/review.py +254 -0
- package/src/generation/templates.py +208 -0
- package/src/generation/types.py +196 -0
- package/src/generation/validation.py +278 -0
- package/src/intent.py +323 -0
- package/src/verification/__init__.py +63 -0
- package/src/verification/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/checker.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/comparison.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/explainer.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/static_analysis.cpython-313.pyc +0 -0
- package/src/verification/checker.py +220 -0
- package/src/verification/comparison.py +492 -0
- package/src/verification/explainer.py +414 -0
- package/src/verification/static_analysis.py +540 -0
- package/src/workflows/__init__.py +21 -0
- package/src/workflows/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/extract.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/generate.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/run.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/verify.cpython-313.pyc +0 -0
- package/src/workflows/extract.py +181 -0
- package/src/workflows/generate.py +155 -0
- package/src/workflows/run.py +187 -0
- package/src/workflows/verify.py +334 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""Code generator for algorithms.
|
|
2
|
+
|
|
3
|
+
This module provides the TemplateCodeGenerator class for
|
|
4
|
+
converting structured algorithms into Python code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from src.extraction.schema import Algorithm, Step, StepType
|
|
13
|
+
from src.generation.templates import TemplateRegistry
|
|
14
|
+
from src.generation.types import TypeInferrer, ValidationResult, FunctionSignature
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class GeneratedCode:
|
|
19
|
+
"""
|
|
20
|
+
Represents generated Python code with metadata.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
source: Complete Python source code
|
|
24
|
+
algorithm_name: Name of the source algorithm
|
|
25
|
+
imports: List of required imports
|
|
26
|
+
validation_result: Syntax validation results
|
|
27
|
+
"""
|
|
28
|
+
source: str
|
|
29
|
+
algorithm_name: str
|
|
30
|
+
imports: List[str] = field(default_factory=list)
|
|
31
|
+
validation_result: Optional[ValidationResult] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TemplateCodeGenerator:
|
|
35
|
+
"""
|
|
36
|
+
Generate Python code from algorithms using templates.
|
|
37
|
+
|
|
38
|
+
Uses template-based generation for predictable constructs
|
|
39
|
+
like assignments, loops, and conditionals.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, template_registry: Optional[TemplateRegistry] = None):
|
|
43
|
+
"""
|
|
44
|
+
Initialize the code generator.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
template_registry: Optional custom template registry
|
|
48
|
+
"""
|
|
49
|
+
self.templates = template_registry or TemplateRegistry()
|
|
50
|
+
self.type_inferrer = TypeInferrer()
|
|
51
|
+
self._step_map: Dict[int, Step] = {}
|
|
52
|
+
|
|
53
|
+
def generate(self, algorithm: Algorithm) -> GeneratedCode:
|
|
54
|
+
"""
|
|
55
|
+
Generate complete Python code from algorithm.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
algorithm: Algorithm to generate code for
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
GeneratedCode with source and metadata
|
|
62
|
+
"""
|
|
63
|
+
# Build step lookup
|
|
64
|
+
self._step_map = {step.id: step for step in algorithm.steps}
|
|
65
|
+
|
|
66
|
+
# Generate code sections
|
|
67
|
+
imports = self._generate_imports(algorithm)
|
|
68
|
+
signature = self._generate_signature(algorithm)
|
|
69
|
+
docstring = self._generate_docstring(algorithm)
|
|
70
|
+
body = self._generate_body(algorithm.steps)
|
|
71
|
+
guard = self._generate_execution_guard(algorithm)
|
|
72
|
+
|
|
73
|
+
# Assemble source
|
|
74
|
+
source_lines = []
|
|
75
|
+
if imports:
|
|
76
|
+
source_lines.append(imports)
|
|
77
|
+
source_lines.append("")
|
|
78
|
+
|
|
79
|
+
source_lines.append(signature)
|
|
80
|
+
source_lines.append(f' """{docstring}"""')
|
|
81
|
+
source_lines.append("")
|
|
82
|
+
|
|
83
|
+
if body:
|
|
84
|
+
source_lines.append(body)
|
|
85
|
+
else:
|
|
86
|
+
source_lines.append(" pass")
|
|
87
|
+
|
|
88
|
+
source = '\n'.join(source_lines)
|
|
89
|
+
|
|
90
|
+
# Validate syntax
|
|
91
|
+
validation = self._validate_syntax(source)
|
|
92
|
+
|
|
93
|
+
# Add execution guard
|
|
94
|
+
if guard:
|
|
95
|
+
source += '\n\n' + guard
|
|
96
|
+
|
|
97
|
+
# Extract imports
|
|
98
|
+
import_list = self._extract_imports(imports)
|
|
99
|
+
|
|
100
|
+
return GeneratedCode(
|
|
101
|
+
source=source,
|
|
102
|
+
algorithm_name=algorithm.name,
|
|
103
|
+
imports=import_list,
|
|
104
|
+
validation_result=validation
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def _generate_imports(self, algorithm: Algorithm) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Generate import section based on detected types.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
algorithm: Algorithm to analyze
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Import statements
|
|
116
|
+
"""
|
|
117
|
+
imports = []
|
|
118
|
+
|
|
119
|
+
# Always include typing
|
|
120
|
+
imports.append("from typing import List, Dict, Optional, Union, Tuple, Any")
|
|
121
|
+
|
|
122
|
+
# Check if numpy is needed
|
|
123
|
+
for inp in algorithm.inputs:
|
|
124
|
+
name = inp.get("name", "")
|
|
125
|
+
if self.type_inferrer.infer_variable_type(name, inp) == "np.ndarray":
|
|
126
|
+
imports.append("import numpy as np")
|
|
127
|
+
break
|
|
128
|
+
|
|
129
|
+
# Check if math is needed
|
|
130
|
+
if any("math." in step.description or "sqrt" in step.description
|
|
131
|
+
for step in algorithm.steps):
|
|
132
|
+
imports.append("import math")
|
|
133
|
+
|
|
134
|
+
return '\n'.join(imports)
|
|
135
|
+
|
|
136
|
+
def _generate_signature(self, algorithm: Algorithm) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Generate function signature.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
algorithm: Algorithm to generate signature for
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Function definition line
|
|
145
|
+
"""
|
|
146
|
+
sig = self.type_inferrer.infer_function_signature(algorithm)
|
|
147
|
+
|
|
148
|
+
params_str = sig.format_params()
|
|
149
|
+
return f"def {sig.name}({params_str}) -> {sig.return_type}:"
|
|
150
|
+
|
|
151
|
+
def _generate_docstring(self, algorithm: Algorithm) -> str:
|
|
152
|
+
"""
|
|
153
|
+
Generate Google-style docstring.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
algorithm: Algorithm to generate docstring for
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Docstring content
|
|
160
|
+
"""
|
|
161
|
+
lines = []
|
|
162
|
+
|
|
163
|
+
# Summary
|
|
164
|
+
lines.append(algorithm.description or f"{algorithm.name} implementation.")
|
|
165
|
+
lines.append("")
|
|
166
|
+
|
|
167
|
+
# Args section
|
|
168
|
+
if algorithm.inputs:
|
|
169
|
+
lines.append("Args:")
|
|
170
|
+
for inp in algorithm.inputs:
|
|
171
|
+
name = inp.get("name", "")
|
|
172
|
+
desc = inp.get("description", "")
|
|
173
|
+
ptype = self.type_inferrer.infer_variable_type(name, inp)
|
|
174
|
+
lines.append(f" {name} ({ptype}): {desc}")
|
|
175
|
+
lines.append("")
|
|
176
|
+
|
|
177
|
+
# Returns section
|
|
178
|
+
if algorithm.outputs:
|
|
179
|
+
lines.append("Returns:")
|
|
180
|
+
for out in algorithm.outputs:
|
|
181
|
+
name = out.get("name", "")
|
|
182
|
+
desc = out.get("description", "")
|
|
183
|
+
lines.append(f" {name}: {desc}")
|
|
184
|
+
lines.append("")
|
|
185
|
+
|
|
186
|
+
return '\n'.join(lines)
|
|
187
|
+
|
|
188
|
+
def _generate_body(self, steps: List[Step], indent: int = 4) -> str:
|
|
189
|
+
"""
|
|
190
|
+
Generate function body from steps.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
steps: Algorithm steps
|
|
194
|
+
indent: Indentation level
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Body code string
|
|
198
|
+
"""
|
|
199
|
+
lines = []
|
|
200
|
+
|
|
201
|
+
for step in steps:
|
|
202
|
+
code = self._format_step(step, indent)
|
|
203
|
+
if code:
|
|
204
|
+
lines.append(code)
|
|
205
|
+
|
|
206
|
+
if not lines:
|
|
207
|
+
return ""
|
|
208
|
+
|
|
209
|
+
return '\n'.join(lines)
|
|
210
|
+
|
|
211
|
+
def _format_step(self, step: Step, indent: int = 4) -> str:
|
|
212
|
+
"""
|
|
213
|
+
Format a single step into Python code.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
step: Step to format
|
|
217
|
+
indent: Indentation level
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Formatted code
|
|
221
|
+
"""
|
|
222
|
+
indent_str = ' ' * indent
|
|
223
|
+
|
|
224
|
+
if step.type == StepType.ASSIGNMENT:
|
|
225
|
+
target = step.outputs[0] if step.outputs else "result"
|
|
226
|
+
expression = step.expression or "None"
|
|
227
|
+
return f"{indent_str}{target} = {expression}"
|
|
228
|
+
|
|
229
|
+
elif step.type == StepType.LOOP_FOR:
|
|
230
|
+
iter_var = step.iter_var or "i"
|
|
231
|
+
iter_range = step.iter_range or "range(n)"
|
|
232
|
+
|
|
233
|
+
lines = [f"{indent_str}for {iter_var} in {iter_range}:"]
|
|
234
|
+
|
|
235
|
+
# Format body
|
|
236
|
+
body_code = self._format_body(step.body, indent + 4)
|
|
237
|
+
if body_code:
|
|
238
|
+
lines.append(body_code)
|
|
239
|
+
else:
|
|
240
|
+
lines.append(f"{' ' * (indent + 4)}pass")
|
|
241
|
+
|
|
242
|
+
return '\n'.join(lines)
|
|
243
|
+
|
|
244
|
+
elif step.type == StepType.LOOP_WHILE:
|
|
245
|
+
condition = step.condition or "True"
|
|
246
|
+
|
|
247
|
+
lines = [f"{indent_str}while {condition}:"]
|
|
248
|
+
|
|
249
|
+
body_code = self._format_body(step.body, indent + 4)
|
|
250
|
+
if body_code:
|
|
251
|
+
lines.append(body_code)
|
|
252
|
+
else:
|
|
253
|
+
lines.append(f"{' ' * (indent + 4)}pass")
|
|
254
|
+
|
|
255
|
+
return '\n'.join(lines)
|
|
256
|
+
|
|
257
|
+
elif step.type == StepType.CONDITIONAL:
|
|
258
|
+
condition = step.condition or "True"
|
|
259
|
+
|
|
260
|
+
lines = [f"{indent_str}if {condition}:"]
|
|
261
|
+
|
|
262
|
+
# Format if body
|
|
263
|
+
if_body = self._format_body(step.body, indent + 4)
|
|
264
|
+
if if_body:
|
|
265
|
+
lines.append(if_body)
|
|
266
|
+
else:
|
|
267
|
+
lines.append(f"{' ' * (indent + 4)}pass")
|
|
268
|
+
|
|
269
|
+
# Format else body if present
|
|
270
|
+
if step.else_body:
|
|
271
|
+
lines.append(f"{indent_str}else:")
|
|
272
|
+
else_body = self._format_body(step.else_body, indent + 4)
|
|
273
|
+
if else_body:
|
|
274
|
+
lines.append(else_body)
|
|
275
|
+
else:
|
|
276
|
+
lines.append(f"{' ' * (indent + 4)}pass")
|
|
277
|
+
|
|
278
|
+
return '\n'.join(lines)
|
|
279
|
+
|
|
280
|
+
elif step.type == StepType.RETURN:
|
|
281
|
+
expression = step.expression or "None"
|
|
282
|
+
return f"{indent_str}return {expression}"
|
|
283
|
+
|
|
284
|
+
elif step.type == StepType.CALL:
|
|
285
|
+
call_target = step.call_target or "function"
|
|
286
|
+
arguments = ", ".join(step.arguments) if step.arguments else ""
|
|
287
|
+
return f"{indent_str}{call_target}({arguments})"
|
|
288
|
+
|
|
289
|
+
elif step.type == StepType.COMMENT:
|
|
290
|
+
annotation = step.annotation or step.description
|
|
291
|
+
return f"{indent_str}# {annotation}"
|
|
292
|
+
|
|
293
|
+
return f"{indent_str}# TODO: {step.description}"
|
|
294
|
+
|
|
295
|
+
def _format_body(self, body_step_ids: List[int], indent: int) -> str:
|
|
296
|
+
"""
|
|
297
|
+
Format body steps.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
body_step_ids: List of step IDs in body
|
|
301
|
+
indent: Indentation level
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Formatted body code
|
|
305
|
+
"""
|
|
306
|
+
lines = []
|
|
307
|
+
|
|
308
|
+
for step_id in body_step_ids:
|
|
309
|
+
step = self._step_map.get(step_id)
|
|
310
|
+
if step:
|
|
311
|
+
code = self._format_step(step, indent)
|
|
312
|
+
if code:
|
|
313
|
+
lines.append(code)
|
|
314
|
+
|
|
315
|
+
if not lines:
|
|
316
|
+
return ""
|
|
317
|
+
|
|
318
|
+
return '\n'.join(lines)
|
|
319
|
+
|
|
320
|
+
def _generate_execution_guard(self, algorithm: Algorithm) -> str:
|
|
321
|
+
"""
|
|
322
|
+
Generate execution guard for example usage.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
algorithm: Algorithm to generate guard for
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Execution guard code
|
|
329
|
+
"""
|
|
330
|
+
sig = self.type_inferrer.infer_function_signature(algorithm)
|
|
331
|
+
|
|
332
|
+
lines = [
|
|
333
|
+
'if __name__ == "__main__":',
|
|
334
|
+
' # Example usage',
|
|
335
|
+
' pass',
|
|
336
|
+
]
|
|
337
|
+
|
|
338
|
+
return '\n'.join(lines)
|
|
339
|
+
|
|
340
|
+
def _validate_syntax(self, code: str) -> ValidationResult:
|
|
341
|
+
"""
|
|
342
|
+
Validate Python syntax using ast module.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
code: Code to validate
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
ValidationResult with errors if any
|
|
349
|
+
"""
|
|
350
|
+
try:
|
|
351
|
+
ast.parse(code)
|
|
352
|
+
return ValidationResult(is_valid=True, errors=[], warnings=[])
|
|
353
|
+
except SyntaxError as e:
|
|
354
|
+
return ValidationResult(
|
|
355
|
+
is_valid=False,
|
|
356
|
+
errors=[{
|
|
357
|
+
'message': f"Syntax error: {e.msg}",
|
|
358
|
+
'line': e.lineno,
|
|
359
|
+
'type': 'syntax',
|
|
360
|
+
'text': e.text
|
|
361
|
+
}],
|
|
362
|
+
warnings=[]
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
def _extract_imports(self, imports_section: str) -> List[str]:
|
|
366
|
+
"""Extract module names from imports section."""
|
|
367
|
+
imports = []
|
|
368
|
+
for line in imports_section.split('\n'):
|
|
369
|
+
if line.startswith('import '):
|
|
370
|
+
imports.append(line.replace('import ', ''))
|
|
371
|
+
elif line.startswith('from '):
|
|
372
|
+
parts = line.split()
|
|
373
|
+
if len(parts) >= 2:
|
|
374
|
+
imports.append(parts[1])
|
|
375
|
+
return imports
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Error classes for code generation.
|
|
2
|
+
|
|
3
|
+
This module defines the error hierarchy for generation failures.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GenerationError(Exception):
|
|
10
|
+
"""Base class for generation errors."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, message: str,
|
|
13
|
+
line_number: Optional[int] = None,
|
|
14
|
+
context: Optional[str] = None):
|
|
15
|
+
self.message = message
|
|
16
|
+
self.line_number = line_number
|
|
17
|
+
self.context = context
|
|
18
|
+
super().__init__(self.format_message())
|
|
19
|
+
|
|
20
|
+
def format_message(self) -> str:
|
|
21
|
+
"""Format error message for display."""
|
|
22
|
+
if self.line_number:
|
|
23
|
+
return f"Generation error at line {self.line_number}: {self.message}"
|
|
24
|
+
return f"Generation error: {self.message}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SyntaxGenerationError(GenerationError):
|
|
28
|
+
"""Raised when generated code has syntax errors."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ImportGenerationError(GenerationError):
|
|
33
|
+
"""Raised when generated code has unresolved imports."""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class UnsupportedConstructError(GenerationError):
|
|
38
|
+
"""Raised when step contains unsupported construct."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class LLMGenerationError(GenerationError):
|
|
43
|
+
"""Raised when LLM fails to generate code."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ValidationError(GenerationError):
|
|
48
|
+
"""Raised when generated code fails validation."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def format_error_for_user(error: GenerationError) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Format error message for user display.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
error: GenerationError to format
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Formatted error string
|
|
61
|
+
"""
|
|
62
|
+
lines = []
|
|
63
|
+
lines.append(f"Code Generation Error: {error.message}")
|
|
64
|
+
|
|
65
|
+
if error.line_number:
|
|
66
|
+
lines.append(f" Location: Line {error.line_number}")
|
|
67
|
+
|
|
68
|
+
if error.context:
|
|
69
|
+
lines.append(f" Context: {error.context}")
|
|
70
|
+
|
|
71
|
+
lines.append("\n Suggestion: Check the algorithm steps and try again.")
|
|
72
|
+
|
|
73
|
+
return '\n'.join(lines)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
"GenerationError",
|
|
78
|
+
"SyntaxGenerationError",
|
|
79
|
+
"ImportGenerationError",
|
|
80
|
+
"UnsupportedConstructError",
|
|
81
|
+
"LLMGenerationError",
|
|
82
|
+
"ValidationError",
|
|
83
|
+
"format_error_for_user",
|
|
84
|
+
]
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Hybrid code generator with fallback hierarchy.
|
|
2
|
+
|
|
3
|
+
This module provides the HybridCodeGenerator class that combines
|
|
4
|
+
template-based and LLM-based generation with automatic fallback.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
from src.extraction.schema import Algorithm, Step, StepType
|
|
11
|
+
from src.generation.code_generator import GeneratedCode, TemplateCodeGenerator
|
|
12
|
+
from src.generation.errors import LLMGenerationError
|
|
13
|
+
from src.generation.llm_generator import LLMCodeGenerator
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HybridGenerationResult:
|
|
17
|
+
"""
|
|
18
|
+
Result of hybrid code generation with metadata.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
generated: GeneratedCode object
|
|
22
|
+
strategy: Which strategy was used
|
|
23
|
+
fallback_used: Whether fallback was triggered
|
|
24
|
+
steps_generated: Number of steps
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, generated: GeneratedCode, strategy: str,
|
|
27
|
+
fallback_used: bool = False, steps_generated: int = 0):
|
|
28
|
+
self.generated = generated
|
|
29
|
+
self.strategy = strategy
|
|
30
|
+
self.fallback_used = fallback_used
|
|
31
|
+
self.steps_generated = steps_generated
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class HybridCodeGenerator:
|
|
35
|
+
"""
|
|
36
|
+
Hybrid generator with Template → LLM → Stub fallback hierarchy.
|
|
37
|
+
|
|
38
|
+
Automatically selects the best generation strategy based on
|
|
39
|
+
step complexity and availability.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self,
|
|
43
|
+
template_gen: Optional[TemplateCodeGenerator] = None,
|
|
44
|
+
llm_gen: Optional[LLMCodeGenerator] = None):
|
|
45
|
+
"""
|
|
46
|
+
Initialize hybrid generator.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
template_gen: Optional template generator
|
|
50
|
+
llm_gen: Optional LLM generator
|
|
51
|
+
"""
|
|
52
|
+
self.template_gen = template_gen or TemplateCodeGenerator()
|
|
53
|
+
self.llm_gen = llm_gen or LLMCodeGenerator()
|
|
54
|
+
self.used_llm = False
|
|
55
|
+
self.used_template = False
|
|
56
|
+
|
|
57
|
+
def generate(self, algorithm: Algorithm,
|
|
58
|
+
strategy: str = "auto") -> GeneratedCode:
|
|
59
|
+
"""
|
|
60
|
+
Generate code using hybrid approach.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
algorithm: Algorithm to generate code for
|
|
64
|
+
strategy: "auto" | "template_only" | "llm_only"
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
GeneratedCode with source and metadata
|
|
68
|
+
"""
|
|
69
|
+
if strategy == "template_only":
|
|
70
|
+
return self._template_generate(algorithm)
|
|
71
|
+
elif strategy == "llm_only":
|
|
72
|
+
return self._llm_generate(algorithm)
|
|
73
|
+
else: # auto
|
|
74
|
+
return self._hybrid_generate(algorithm)
|
|
75
|
+
|
|
76
|
+
def _hybrid_generate(self, algorithm: Algorithm) -> GeneratedCode:
|
|
77
|
+
"""
|
|
78
|
+
Hybrid generation with automatic fallback.
|
|
79
|
+
|
|
80
|
+
Flow:
|
|
81
|
+
1. Try template generation for all steps
|
|
82
|
+
2. If any step fails, try LLM for that step
|
|
83
|
+
3. If LLM fails, use stub
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
algorithm: Algorithm to generate code for
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
GeneratedCode
|
|
90
|
+
"""
|
|
91
|
+
# Check if all steps can be handled by templates
|
|
92
|
+
template_compatible = all(
|
|
93
|
+
self._is_template_compatible(step)
|
|
94
|
+
for step in algorithm.steps
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if template_compatible:
|
|
98
|
+
self.used_template = True
|
|
99
|
+
return self.template_gen.generate(algorithm)
|
|
100
|
+
|
|
101
|
+
# Some steps need LLM
|
|
102
|
+
try:
|
|
103
|
+
self.used_llm = True
|
|
104
|
+
return self.llm_gen.generate(algorithm)
|
|
105
|
+
except LLMGenerationError:
|
|
106
|
+
# Fall back to stub
|
|
107
|
+
return self.llm_gen._generate_stub(algorithm)
|
|
108
|
+
|
|
109
|
+
def _template_generate(self, algorithm: Algorithm) -> GeneratedCode:
|
|
110
|
+
"""Generate using template generator only."""
|
|
111
|
+
self.used_template = True
|
|
112
|
+
return self.template_gen.generate(algorithm)
|
|
113
|
+
|
|
114
|
+
def _llm_generate(self, algorithm: Algorithm) -> GeneratedCode:
|
|
115
|
+
"""Generate using LLM generator only."""
|
|
116
|
+
self.used_llm = True
|
|
117
|
+
return self.llm_gen.generate_with_fallback(algorithm)
|
|
118
|
+
|
|
119
|
+
def _is_template_compatible(self, step: Step) -> bool:
|
|
120
|
+
"""
|
|
121
|
+
Check if step can be handled by templates.
|
|
122
|
+
|
|
123
|
+
Templates handle: ASSIGNMENT, LOOP_FOR, LOOP_WHILE,
|
|
124
|
+
CONDITIONAL, RETURN, CALL, COMMENT
|
|
125
|
+
|
|
126
|
+
LLM handles: Complex expressions, custom logic
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
step: Step to check
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
True if template-compatible
|
|
133
|
+
"""
|
|
134
|
+
# Check step type
|
|
135
|
+
if step.type not in [
|
|
136
|
+
StepType.ASSIGNMENT,
|
|
137
|
+
StepType.LOOP_FOR,
|
|
138
|
+
StepType.LOOP_WHILE,
|
|
139
|
+
StepType.CONDITIONAL,
|
|
140
|
+
StepType.RETURN,
|
|
141
|
+
StepType.CALL,
|
|
142
|
+
StepType.COMMENT
|
|
143
|
+
]:
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
return self._is_simple_expression(step)
|
|
147
|
+
|
|
148
|
+
def _is_simple_expression(self, step: Step) -> bool:
|
|
149
|
+
"""
|
|
150
|
+
Check if expression is simple enough for templates.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
step: Step to check
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True if expression is simple
|
|
157
|
+
"""
|
|
158
|
+
if step.expression:
|
|
159
|
+
# Check for complex patterns
|
|
160
|
+
complex_patterns = [
|
|
161
|
+
r'sum\s+of', # Summation
|
|
162
|
+
r'product\s+of', # Product
|
|
163
|
+
r'argmin|argmax', # Optimization
|
|
164
|
+
r'minimum|maximum', # Min/max
|
|
165
|
+
r'forall|exists', # Quantifiers
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
for pattern in complex_patterns:
|
|
169
|
+
if re.search(pattern, step.expression, re.IGNORECASE):
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
def get_generation_strategy(self) -> str:
|
|
175
|
+
"""Return which strategy was used for last generation."""
|
|
176
|
+
if self.used_template:
|
|
177
|
+
return "template"
|
|
178
|
+
elif self.used_llm:
|
|
179
|
+
return "llm"
|
|
180
|
+
else:
|
|
181
|
+
return "stub"
|
|
182
|
+
|
|
183
|
+
def generate_for_workflow(self, algorithm: Algorithm) -> Dict[str, Any]:
|
|
184
|
+
"""
|
|
185
|
+
Generate code with metadata for workflow.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
algorithm: Algorithm to generate code for
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Dict with:
|
|
192
|
+
- generated: GeneratedCode object
|
|
193
|
+
- strategy: "template" | "llm" | "stub"
|
|
194
|
+
- fallback_used: bool
|
|
195
|
+
- steps_generated: int
|
|
196
|
+
"""
|
|
197
|
+
generated = self.generate(algorithm)
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
'generated': generated,
|
|
201
|
+
'strategy': self.get_generation_strategy(),
|
|
202
|
+
'fallback_used': self.used_llm and not self.used_template,
|
|
203
|
+
'steps_generated': len(algorithm.steps),
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
__all__ = [
|
|
208
|
+
"HybridCodeGenerator",
|
|
209
|
+
"HybridGenerationResult",
|
|
210
|
+
]
|