flyteplugins-codegen 2.0.6__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.
- flyteplugins/codegen/__init__.py +18 -0
- flyteplugins/codegen/auto_coder_agent.py +1088 -0
- flyteplugins/codegen/core/__init__.py +19 -0
- flyteplugins/codegen/core/types.py +337 -0
- flyteplugins/codegen/data/__init__.py +27 -0
- flyteplugins/codegen/data/extraction.py +281 -0
- flyteplugins/codegen/data/schema.py +270 -0
- flyteplugins/codegen/execution/__init__.py +7 -0
- flyteplugins/codegen/execution/agent.py +671 -0
- flyteplugins/codegen/execution/docker.py +206 -0
- flyteplugins/codegen/generation/__init__.py +41 -0
- flyteplugins/codegen/generation/llm.py +1269 -0
- flyteplugins/codegen/generation/prompts.py +136 -0
- flyteplugins_codegen-2.0.6.dist-info/METADATA +441 -0
- flyteplugins_codegen-2.0.6.dist-info/RECORD +17 -0
- flyteplugins_codegen-2.0.6.dist-info/WHEEL +5 -0
- flyteplugins_codegen-2.0.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import tempfile
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import flyte
|
|
10
|
+
from flyte.errors import InvalidPackageError
|
|
11
|
+
from flyte.io import File
|
|
12
|
+
from flyte.sandbox import ImageConfig
|
|
13
|
+
from flyte.syncify import syncify
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
_PYTEST_COMMAND = r"""
|
|
18
|
+
set -o pipefail
|
|
19
|
+
|
|
20
|
+
EXIT_CODE=1
|
|
21
|
+
|
|
22
|
+
cleanup() {
|
|
23
|
+
echo "$EXIT_CODE" > /var/outputs/exit_code
|
|
24
|
+
sync
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
trap cleanup EXIT
|
|
28
|
+
|
|
29
|
+
# $1 = solution file, $2 = test file
|
|
30
|
+
PYTHONPATH=/var/inputs python -m pytest "$2" -v --tb=short \
|
|
31
|
+
2>&1 | tee /var/outputs/result
|
|
32
|
+
|
|
33
|
+
EXIT_CODE=${PIPESTATUS[0]}
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class RunResult:
|
|
39
|
+
"""Result from running tests in a container."""
|
|
40
|
+
|
|
41
|
+
output: str
|
|
42
|
+
exit_code: str
|
|
43
|
+
tests_passed: bool
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@flyte.trace
|
|
47
|
+
async def build_image(
|
|
48
|
+
language: str,
|
|
49
|
+
base_pkgs: list[str],
|
|
50
|
+
detected_packages: list[str],
|
|
51
|
+
detected_system_packages: list[str],
|
|
52
|
+
previously_installed_packages: list[str],
|
|
53
|
+
previously_installed_system_packages: list[str],
|
|
54
|
+
additional_commands: list[str],
|
|
55
|
+
image_name: str,
|
|
56
|
+
current_image: Optional[str],
|
|
57
|
+
image_config: Optional[ImageConfig],
|
|
58
|
+
) -> str:
|
|
59
|
+
"""Build image with packages using incremental builds when possible.
|
|
60
|
+
|
|
61
|
+
Uses flyte.Image for fresh builds. For incremental builds (when current_image
|
|
62
|
+
exists), adds only new packages as layers on top of the existing image.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
language: Programming language (e.g. "python").
|
|
66
|
+
base_pkgs: Base packages required.
|
|
67
|
+
detected_packages: Language packages detected from code.
|
|
68
|
+
detected_system_packages: System packages detected from code.
|
|
69
|
+
previously_installed_packages: Packages already in current_image.
|
|
70
|
+
previously_installed_system_packages: System packages already in current_image.
|
|
71
|
+
additional_commands: Additional RUN commands for image build.
|
|
72
|
+
image_name: Name for the image.
|
|
73
|
+
current_image: Current image URI (if exists) for incremental builds.
|
|
74
|
+
image_config: Image configuration.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Built image URI string.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
InvalidPackageError: If a system package doesn't exist in apt repos.
|
|
81
|
+
"""
|
|
82
|
+
all_packages = base_pkgs + detected_packages
|
|
83
|
+
new_packages = [pkg for pkg in all_packages if pkg not in previously_installed_packages]
|
|
84
|
+
new_system_packages = [pkg for pkg in detected_system_packages if pkg not in previously_installed_system_packages]
|
|
85
|
+
|
|
86
|
+
if current_image and (new_packages or new_system_packages):
|
|
87
|
+
logger.info(
|
|
88
|
+
f"Incrementally updating image '{image_name}': adding system={new_system_packages}, language={new_packages}"
|
|
89
|
+
)
|
|
90
|
+
image = flyte.Image.from_base(current_image).clone(name=image_name)
|
|
91
|
+
if new_system_packages:
|
|
92
|
+
image = image.with_apt_packages(*new_system_packages)
|
|
93
|
+
if new_packages and language == "python":
|
|
94
|
+
image = image.with_pip_packages(*new_packages)
|
|
95
|
+
else:
|
|
96
|
+
logger.info(
|
|
97
|
+
f"Building image '{image_name}' with packages: system={detected_system_packages}, language={all_packages}"
|
|
98
|
+
)
|
|
99
|
+
config = image_config or ImageConfig()
|
|
100
|
+
image = flyte.Image.from_debian_base(
|
|
101
|
+
install_flyte=False,
|
|
102
|
+
registry=config.registry,
|
|
103
|
+
registry_secret=config.registry_secret,
|
|
104
|
+
python_version=config.python_version,
|
|
105
|
+
name=image_name,
|
|
106
|
+
)
|
|
107
|
+
apt_packages = list(detected_system_packages)
|
|
108
|
+
if "gcc" not in apt_packages:
|
|
109
|
+
apt_packages.extend(["gcc", "g++", "make"])
|
|
110
|
+
if apt_packages:
|
|
111
|
+
image = image.with_apt_packages(*apt_packages)
|
|
112
|
+
if all_packages and language == "python":
|
|
113
|
+
image = image.with_pip_packages(*all_packages)
|
|
114
|
+
if additional_commands:
|
|
115
|
+
image = image.with_commands(additional_commands)
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
result = await flyte.build.aio(image)
|
|
119
|
+
return result.uri
|
|
120
|
+
except Exception as e:
|
|
121
|
+
error_msg = str(e)
|
|
122
|
+
if "Unable to locate package" in error_msg or "has no installation candidate" in error_msg:
|
|
123
|
+
match = re.search(r"(?:Unable to locate package|Package '?)([\w.+-]+)", error_msg)
|
|
124
|
+
if match:
|
|
125
|
+
logger.error(f"Image build failed: Invalid system package '{match.group(1)}'")
|
|
126
|
+
raise InvalidPackageError(match.group(1), error_msg) from e
|
|
127
|
+
logger.error(f"Image build failed: {error_msg}")
|
|
128
|
+
raise
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@syncify
|
|
132
|
+
async def run_tests(
|
|
133
|
+
code: str,
|
|
134
|
+
tests: str,
|
|
135
|
+
image: str,
|
|
136
|
+
name: str,
|
|
137
|
+
resources: Optional[flyte.Resources] = None,
|
|
138
|
+
retries: int = 0,
|
|
139
|
+
timeout: Optional[int] = None,
|
|
140
|
+
env_vars: Optional[dict[str, str]] = None,
|
|
141
|
+
secrets: Optional[list] = None,
|
|
142
|
+
cache: str = "auto",
|
|
143
|
+
_attempt: int = 1,
|
|
144
|
+
) -> RunResult:
|
|
145
|
+
"""Run pytest tests against code in an isolated container.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
code: Complete Python code to test (including imports).
|
|
149
|
+
tests: Test code string (pytest-compatible).
|
|
150
|
+
image: Pre-built image URI.
|
|
151
|
+
name: Base name for the container task.
|
|
152
|
+
resources: CPU / memory resources for the container.
|
|
153
|
+
retries: Number of task retries on failure.
|
|
154
|
+
timeout: Task timeout in seconds.
|
|
155
|
+
env_vars: Environment variables available inside the container.
|
|
156
|
+
secrets: Flyte secrets to mount.
|
|
157
|
+
cache: Cache behaviour — ``"auto"``, ``"override"``, or ``"disable"``.
|
|
158
|
+
_attempt: Differentiates repeated calls with the same base name.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
:class:`RunResult` with ``output``, ``exit_code``, and ``tests_passed``.
|
|
162
|
+
"""
|
|
163
|
+
code_file = tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False)
|
|
164
|
+
code_file.write(f"{code}\n")
|
|
165
|
+
code_file.close()
|
|
166
|
+
|
|
167
|
+
test_file = tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False)
|
|
168
|
+
test_file.write(tests)
|
|
169
|
+
test_file.close()
|
|
170
|
+
|
|
171
|
+
sandbox = flyte.sandbox.create(
|
|
172
|
+
name=f"{name}-{_attempt}",
|
|
173
|
+
command=["/bin/bash", "-c", _PYTEST_COMMAND],
|
|
174
|
+
arguments=["_", "/var/inputs/solution.py", "/var/inputs/test_solution.py"],
|
|
175
|
+
inputs={"solution.py": File, "test_solution.py": File},
|
|
176
|
+
outputs={"exit_code": str, "result": str},
|
|
177
|
+
resources=resources,
|
|
178
|
+
retries=retries,
|
|
179
|
+
timeout=timeout,
|
|
180
|
+
env_vars=env_vars,
|
|
181
|
+
secrets=secrets,
|
|
182
|
+
cache=cache,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
test_exit_code, test_output = await sandbox.run.aio(
|
|
187
|
+
image=image,
|
|
188
|
+
**{
|
|
189
|
+
"solution.py": await File.from_local(
|
|
190
|
+
code_file.name,
|
|
191
|
+
hash_method=hashlib.sha256(code.encode()).hexdigest(),
|
|
192
|
+
),
|
|
193
|
+
"test_solution.py": await File.from_local(
|
|
194
|
+
test_file.name,
|
|
195
|
+
hash_method=hashlib.sha256(tests.encode()).hexdigest(),
|
|
196
|
+
),
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
tests_passed = test_exit_code.strip() == "0"
|
|
200
|
+
return RunResult(output=test_output, exit_code=test_exit_code, tests_passed=tests_passed)
|
|
201
|
+
finally:
|
|
202
|
+
for path in (code_file.name, test_file.name):
|
|
203
|
+
try:
|
|
204
|
+
os.unlink(path)
|
|
205
|
+
except OSError:
|
|
206
|
+
pass
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""LLM-based code generation functionality."""
|
|
2
|
+
|
|
3
|
+
from flyteplugins.codegen.generation.llm import (
|
|
4
|
+
detect_and_track_packages,
|
|
5
|
+
diagnose_and_plan_environment_fix,
|
|
6
|
+
diagnose_error,
|
|
7
|
+
extract_error_messages_from_pytest,
|
|
8
|
+
fix_failing_tests,
|
|
9
|
+
generate_code,
|
|
10
|
+
generate_plan,
|
|
11
|
+
generate_tests,
|
|
12
|
+
verify_logic_fixes_applied,
|
|
13
|
+
verify_test_fixes_applied,
|
|
14
|
+
)
|
|
15
|
+
from flyteplugins.codegen.generation.prompts import (
|
|
16
|
+
DEFAULT_SYSTEM_PROMPT,
|
|
17
|
+
FILE_EXTENSIONS,
|
|
18
|
+
PACKAGE_MANAGER_MAP,
|
|
19
|
+
STRUCTURED_OUTPUT_REQUIREMENTS,
|
|
20
|
+
TEST_FRAMEWORKS,
|
|
21
|
+
build_enhanced_prompt,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"DEFAULT_SYSTEM_PROMPT",
|
|
26
|
+
"FILE_EXTENSIONS",
|
|
27
|
+
"PACKAGE_MANAGER_MAP",
|
|
28
|
+
"STRUCTURED_OUTPUT_REQUIREMENTS",
|
|
29
|
+
"TEST_FRAMEWORKS",
|
|
30
|
+
"build_enhanced_prompt",
|
|
31
|
+
"detect_and_track_packages",
|
|
32
|
+
"diagnose_and_plan_environment_fix",
|
|
33
|
+
"diagnose_error",
|
|
34
|
+
"extract_error_messages_from_pytest",
|
|
35
|
+
"fix_failing_tests",
|
|
36
|
+
"generate_code",
|
|
37
|
+
"generate_plan",
|
|
38
|
+
"generate_tests",
|
|
39
|
+
"verify_logic_fixes_applied",
|
|
40
|
+
"verify_test_fixes_applied",
|
|
41
|
+
]
|