amd-gaia 0.14.3__py3-none-any.whl → 0.15.1__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.
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
- amd_gaia-0.15.1.dist-info/RECORD +178 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2177 -2177
- gaia/agents/base/api_agent.py +120 -120
- gaia/agents/base/console.py +1841 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +83 -83
- gaia/agents/blender/agent.py +556 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +835 -835
- gaia/agents/chat/app.py +1058 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1729 -1729
- gaia/agents/chat/tools/shell_tools.py +436 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2036 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +642 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1506 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1974 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +173 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +430 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5481 -5621
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/llm/__init__.py +9 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3236 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +120 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +163 -163
- gaia/talk/app.py +289 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.14.3.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -729
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/top_level.txt +0 -0
|
@@ -1,769 +1,769 @@
|
|
|
1
|
-
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
"""Consolidated code tools mixin combining generation, analysis, and helper methods."""
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import logging
|
|
7
|
-
import os
|
|
8
|
-
import shlex
|
|
9
|
-
import subprocess
|
|
10
|
-
import tempfile
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any, Dict, List, Optional
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class CodeToolsMixin:
|
|
18
|
-
"""Consolidated mixin providing code generation, analysis, and helper methods.
|
|
19
|
-
|
|
20
|
-
This mixin provides tools for:
|
|
21
|
-
- Generating Python functions, classes, and tests
|
|
22
|
-
- Parsing and analyzing Python code structure
|
|
23
|
-
- Validating Python syntax
|
|
24
|
-
- Extracting code symbols (functions, classes, imports)
|
|
25
|
-
- Running pylint analysis on code
|
|
26
|
-
|
|
27
|
-
Tools provided:
|
|
28
|
-
- generate_function: Generate Python function with docstring and body
|
|
29
|
-
- generate_class: Generate Python class with methods
|
|
30
|
-
- generate_test: Generate comprehensive unit tests for code
|
|
31
|
-
- parse_python_code: Parse and extract structure from Python code
|
|
32
|
-
- validate_syntax: Validate Python code syntax
|
|
33
|
-
- list_symbols: List symbols (functions, classes) in Python code
|
|
34
|
-
- analyze_with_pylint: Run pylint analysis on Python code or files
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
def register_code_tools(self) -> None:
|
|
38
|
-
"""Register all code-related tools."""
|
|
39
|
-
from gaia.agents.base.tools import tool
|
|
40
|
-
|
|
41
|
-
# ============================================================
|
|
42
|
-
# CODE GENERATION TOOLS
|
|
43
|
-
# ============================================================
|
|
44
|
-
|
|
45
|
-
@tool
|
|
46
|
-
def generate_function(
|
|
47
|
-
name: str,
|
|
48
|
-
params: str = "",
|
|
49
|
-
docstring: str = "Function description.",
|
|
50
|
-
body: str = "pass",
|
|
51
|
-
return_type: str = None,
|
|
52
|
-
write_to_file: bool = True,
|
|
53
|
-
) -> Dict[str, Any]:
|
|
54
|
-
"""Generate a Python function and optionally save to file.
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
name: Function name
|
|
58
|
-
params: Parameter list (e.g., "x, y=0")
|
|
59
|
-
docstring: Function documentation
|
|
60
|
-
body: Function implementation
|
|
61
|
-
return_type: Optional return type hint
|
|
62
|
-
write_to_file: Whether to save to a file (default: True)
|
|
63
|
-
|
|
64
|
-
Returns:
|
|
65
|
-
Dictionary with file path or generated code
|
|
66
|
-
"""
|
|
67
|
-
try:
|
|
68
|
-
# Add return type hint if provided
|
|
69
|
-
signature = f"def {name}({params})"
|
|
70
|
-
if return_type:
|
|
71
|
-
signature += f" -> {return_type}"
|
|
72
|
-
signature += ":"
|
|
73
|
-
|
|
74
|
-
# Format the body with proper indentation
|
|
75
|
-
body_lines = body.split("\n")
|
|
76
|
-
needs_indent = body_lines and not body_lines[0].startswith(" ")
|
|
77
|
-
|
|
78
|
-
if needs_indent:
|
|
79
|
-
indented_lines = []
|
|
80
|
-
for line in body_lines:
|
|
81
|
-
if line.strip():
|
|
82
|
-
indented_lines.append(f" {line}")
|
|
83
|
-
else:
|
|
84
|
-
indented_lines.append("")
|
|
85
|
-
indented_body = "\n".join(indented_lines)
|
|
86
|
-
else:
|
|
87
|
-
indented_body = body
|
|
88
|
-
|
|
89
|
-
code = f'''{signature}
|
|
90
|
-
"""{docstring}
|
|
91
|
-
"""
|
|
92
|
-
{indented_body}
|
|
93
|
-
'''
|
|
94
|
-
|
|
95
|
-
# Validate the generated code
|
|
96
|
-
validation = self._validate_python_syntax(code)
|
|
97
|
-
|
|
98
|
-
result = {
|
|
99
|
-
"status": "success",
|
|
100
|
-
"is_valid": validation["is_valid"],
|
|
101
|
-
"errors": validation.get("errors", []),
|
|
102
|
-
"code": code,
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if write_to_file and validation["is_valid"]:
|
|
106
|
-
filename = f"{name}_generated.py"
|
|
107
|
-
filepath = os.path.abspath(filename)
|
|
108
|
-
|
|
109
|
-
with open(filepath, "w", encoding="utf-8") as f:
|
|
110
|
-
f.write(code)
|
|
111
|
-
|
|
112
|
-
result["file_path"] = filepath
|
|
113
|
-
result["message"] = f"Function '{name}' written to {filepath}"
|
|
114
|
-
|
|
115
|
-
return result
|
|
116
|
-
except Exception as e:
|
|
117
|
-
return {"status": "error", "error": str(e)}
|
|
118
|
-
|
|
119
|
-
@tool
|
|
120
|
-
def generate_class(
|
|
121
|
-
name: str,
|
|
122
|
-
docstring: str = "Class description.",
|
|
123
|
-
base_classes: str = "",
|
|
124
|
-
methods: List[Dict[str, str]] = None,
|
|
125
|
-
write_to_file: bool = True,
|
|
126
|
-
) -> Dict[str, Any]:
|
|
127
|
-
"""Generate a Python class and optionally save to file.
|
|
128
|
-
|
|
129
|
-
Args:
|
|
130
|
-
name: Class name
|
|
131
|
-
docstring: Class documentation
|
|
132
|
-
base_classes: Optional base classes (e.g., "BaseClass, Mixin")
|
|
133
|
-
methods: List of methods to include
|
|
134
|
-
write_to_file: Whether to save to a file (default: True)
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
Dictionary with generated class code
|
|
138
|
-
"""
|
|
139
|
-
try:
|
|
140
|
-
# Build class signature
|
|
141
|
-
if base_classes:
|
|
142
|
-
signature = f"class {name}({base_classes}):"
|
|
143
|
-
else:
|
|
144
|
-
signature = f"class {name}:"
|
|
145
|
-
|
|
146
|
-
code = f'''{signature}
|
|
147
|
-
"""{docstring}
|
|
148
|
-
"""
|
|
149
|
-
'''
|
|
150
|
-
|
|
151
|
-
if not methods:
|
|
152
|
-
code += '''
|
|
153
|
-
def __init__(self):
|
|
154
|
-
"""Initialize the class."""
|
|
155
|
-
pass
|
|
156
|
-
'''
|
|
157
|
-
else:
|
|
158
|
-
for method in methods:
|
|
159
|
-
method_name = method.get("name", "method")
|
|
160
|
-
method_params = method.get("params", "self")
|
|
161
|
-
method_doc = method.get("docstring", "Method description.")
|
|
162
|
-
method_body = method.get("body", "pass")
|
|
163
|
-
|
|
164
|
-
if not method_params.startswith("self"):
|
|
165
|
-
method_params = (
|
|
166
|
-
f"self, {method_params}" if method_params else "self"
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
body_lines = []
|
|
170
|
-
for line in method_body.split("\n"):
|
|
171
|
-
if line.strip():
|
|
172
|
-
body_lines.append(f" {line}")
|
|
173
|
-
else:
|
|
174
|
-
body_lines.append("")
|
|
175
|
-
indented_body = "\n".join(body_lines)
|
|
176
|
-
|
|
177
|
-
code += f'''
|
|
178
|
-
def {method_name}({method_params}):
|
|
179
|
-
"""{method_doc}
|
|
180
|
-
"""
|
|
181
|
-
{indented_body}
|
|
182
|
-
'''
|
|
183
|
-
|
|
184
|
-
validation = self._validate_python_syntax(code)
|
|
185
|
-
|
|
186
|
-
result = {
|
|
187
|
-
"status": "success",
|
|
188
|
-
"is_valid": validation["is_valid"],
|
|
189
|
-
"errors": validation.get("errors", []),
|
|
190
|
-
"code": code,
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if write_to_file and validation["is_valid"]:
|
|
194
|
-
filename = f"{name.lower()}_generated.py"
|
|
195
|
-
filepath = os.path.abspath(filename)
|
|
196
|
-
|
|
197
|
-
with open(filepath, "w", encoding="utf-8") as f:
|
|
198
|
-
f.write(code)
|
|
199
|
-
|
|
200
|
-
result["file_path"] = filepath
|
|
201
|
-
result["message"] = f"Class '{name}' written to {filepath}"
|
|
202
|
-
|
|
203
|
-
return result
|
|
204
|
-
except Exception as e:
|
|
205
|
-
return {"status": "error", "error": str(e)}
|
|
206
|
-
|
|
207
|
-
@tool
|
|
208
|
-
def generate_test(
|
|
209
|
-
class_name: str = None,
|
|
210
|
-
module_name: str = None,
|
|
211
|
-
function_name: str = None,
|
|
212
|
-
source_code: str = None,
|
|
213
|
-
test_cases: List[str] = None,
|
|
214
|
-
source_file: str = None,
|
|
215
|
-
write_to_file: bool = True,
|
|
216
|
-
) -> Dict[str, Any]:
|
|
217
|
-
"""Generate comprehensive Python unit tests and optionally save to file.
|
|
218
|
-
|
|
219
|
-
Args:
|
|
220
|
-
class_name: Name for the test class (optional)
|
|
221
|
-
module_name: Module being tested (optional)
|
|
222
|
-
function_name: Specific function to test (optional)
|
|
223
|
-
source_code: Source code to analyze for test generation (optional)
|
|
224
|
-
test_cases: Manual list of test case names (optional)
|
|
225
|
-
source_file: Path to source file being tested (optional)
|
|
226
|
-
write_to_file: Whether to save to a file (default: True)
|
|
227
|
-
|
|
228
|
-
Returns:
|
|
229
|
-
Dictionary with file path or generated test code
|
|
230
|
-
"""
|
|
231
|
-
try:
|
|
232
|
-
functions_to_test = []
|
|
233
|
-
if source_code:
|
|
234
|
-
parsed = self._parse_python_code(source_code)
|
|
235
|
-
functions_to_test = [
|
|
236
|
-
s for s in parsed.symbols if s.type == "function"
|
|
237
|
-
]
|
|
238
|
-
|
|
239
|
-
if not module_name and functions_to_test:
|
|
240
|
-
module_name = "tested_module"
|
|
241
|
-
|
|
242
|
-
if not class_name:
|
|
243
|
-
if function_name:
|
|
244
|
-
class_name = f"{function_name.title()}"
|
|
245
|
-
elif functions_to_test:
|
|
246
|
-
class_name = "GeneratedTests"
|
|
247
|
-
else:
|
|
248
|
-
class_name = "Tests"
|
|
249
|
-
|
|
250
|
-
class_name = class_name or "Tests"
|
|
251
|
-
module_name = module_name or "module"
|
|
252
|
-
|
|
253
|
-
code = f'''import unittest
|
|
254
|
-
from unittest.mock import patch, MagicMock
|
|
255
|
-
import sys
|
|
256
|
-
import os
|
|
257
|
-
|
|
258
|
-
# Add parent directory to path for imports
|
|
259
|
-
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
260
|
-
|
|
261
|
-
class Test{class_name}(unittest.TestCase):
|
|
262
|
-
"""Test cases for {module_name}."""
|
|
263
|
-
|
|
264
|
-
def setUp(self):
|
|
265
|
-
"""Set up test fixtures."""
|
|
266
|
-
self.test_data = {{}}
|
|
267
|
-
|
|
268
|
-
def tearDown(self):
|
|
269
|
-
"""Clean up after tests."""
|
|
270
|
-
pass
|
|
271
|
-
'''
|
|
272
|
-
|
|
273
|
-
if function_name and functions_to_test:
|
|
274
|
-
func = next(
|
|
275
|
-
(f for f in functions_to_test if f.name == function_name), None
|
|
276
|
-
)
|
|
277
|
-
if func:
|
|
278
|
-
code += f'''
|
|
279
|
-
def test_{func.name}_basic(self):
|
|
280
|
-
"""Test basic functionality of {func.name}."""
|
|
281
|
-
# Test with typical inputs
|
|
282
|
-
# TODO: Add assertions based on function behavior
|
|
283
|
-
pass
|
|
284
|
-
|
|
285
|
-
def test_{func.name}_edge_cases(self):
|
|
286
|
-
"""Test edge cases for {func.name}."""
|
|
287
|
-
# Test boundary conditions
|
|
288
|
-
# TODO: Add edge case tests
|
|
289
|
-
pass
|
|
290
|
-
|
|
291
|
-
def test_{func.name}_invalid_input(self):
|
|
292
|
-
"""Test {func.name} with invalid inputs."""
|
|
293
|
-
# Test error handling
|
|
294
|
-
# TODO: Add error handling tests
|
|
295
|
-
pass
|
|
296
|
-
'''
|
|
297
|
-
elif functions_to_test:
|
|
298
|
-
for func in functions_to_test[:5]:
|
|
299
|
-
code += f'''
|
|
300
|
-
def test_{func.name}(self):
|
|
301
|
-
"""Test {func.name} function."""
|
|
302
|
-
# TODO: Implement test for {func.name}
|
|
303
|
-
# Function signature: {func.signature if func.signature else func.name + '()'}
|
|
304
|
-
pass
|
|
305
|
-
'''
|
|
306
|
-
elif test_cases:
|
|
307
|
-
for test_name in test_cases:
|
|
308
|
-
code += f'''
|
|
309
|
-
def test_{test_name}(self):
|
|
310
|
-
"""Test {test_name}."""
|
|
311
|
-
# TODO: Implement test
|
|
312
|
-
self.fail("Not implemented")
|
|
313
|
-
'''
|
|
314
|
-
else:
|
|
315
|
-
code += '''
|
|
316
|
-
def test_basic_functionality(self):
|
|
317
|
-
"""Test basic functionality."""
|
|
318
|
-
# TODO: Implement basic test
|
|
319
|
-
self.assertTrue(True, "Basic test placeholder")
|
|
320
|
-
|
|
321
|
-
def test_edge_cases(self):
|
|
322
|
-
"""Test edge cases."""
|
|
323
|
-
# TODO: Implement edge case tests
|
|
324
|
-
self.assertTrue(True, "Edge case test placeholder")
|
|
325
|
-
|
|
326
|
-
def test_error_handling(self):
|
|
327
|
-
"""Test error handling."""
|
|
328
|
-
# TODO: Implement error handling tests
|
|
329
|
-
with self.assertRaises(Exception):
|
|
330
|
-
# Code that should raise exception
|
|
331
|
-
pass
|
|
332
|
-
'''
|
|
333
|
-
|
|
334
|
-
code += """
|
|
335
|
-
if __name__ == "__main__":
|
|
336
|
-
unittest.main(verbosity=2)
|
|
337
|
-
"""
|
|
338
|
-
|
|
339
|
-
validation = self._validate_python_syntax(code)
|
|
340
|
-
|
|
341
|
-
result = {
|
|
342
|
-
"status": "success",
|
|
343
|
-
"is_valid": validation["is_valid"],
|
|
344
|
-
"errors": validation.get("errors", []),
|
|
345
|
-
"code": code, # Always include generated code
|
|
346
|
-
"functions_tested": (
|
|
347
|
-
[f.name for f in functions_to_test] if functions_to_test else []
|
|
348
|
-
),
|
|
349
|
-
"test_class": f"Test{class_name}",
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if write_to_file and validation["is_valid"]:
|
|
353
|
-
if source_file:
|
|
354
|
-
base_name = os.path.splitext(os.path.basename(source_file))[0]
|
|
355
|
-
filename = f"test_{base_name}.py"
|
|
356
|
-
elif module_name:
|
|
357
|
-
filename = f"test_{module_name.lower()}.py"
|
|
358
|
-
elif function_name:
|
|
359
|
-
filename = f"test_{function_name.lower()}.py"
|
|
360
|
-
else:
|
|
361
|
-
filename = f"test_{class_name.lower() if class_name else 'generated'}.py"
|
|
362
|
-
|
|
363
|
-
filepath = os.path.abspath(filename)
|
|
364
|
-
|
|
365
|
-
with open(filepath, "w", encoding="utf-8") as f:
|
|
366
|
-
f.write(code)
|
|
367
|
-
|
|
368
|
-
result["file_path"] = filepath
|
|
369
|
-
result["message"] = f"Test file written to {filepath}"
|
|
370
|
-
|
|
371
|
-
return result
|
|
372
|
-
except Exception as e:
|
|
373
|
-
return {"status": "error", "error": str(e)}
|
|
374
|
-
|
|
375
|
-
@tool
|
|
376
|
-
def list_symbols(code: str, symbol_type: str = None) -> Dict[str, Any]:
|
|
377
|
-
"""List symbols (functions, classes, variables) in Python code.
|
|
378
|
-
|
|
379
|
-
Args:
|
|
380
|
-
code: Python source code
|
|
381
|
-
symbol_type: Optional filter ('function', 'class', 'variable', 'import')
|
|
382
|
-
|
|
383
|
-
Returns:
|
|
384
|
-
Dictionary with list of symbols
|
|
385
|
-
"""
|
|
386
|
-
parsed = self._parse_python_code(code)
|
|
387
|
-
|
|
388
|
-
symbols = parsed.symbols or []
|
|
389
|
-
if symbol_type:
|
|
390
|
-
symbols = [s for s in symbols if s.type == symbol_type]
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
"status": "success",
|
|
394
|
-
"total_symbols": len(symbols),
|
|
395
|
-
"symbols": [
|
|
396
|
-
{
|
|
397
|
-
"name": s.name,
|
|
398
|
-
"type": s.type,
|
|
399
|
-
"line": s.line,
|
|
400
|
-
"signature": s.signature,
|
|
401
|
-
"docstring": s.docstring,
|
|
402
|
-
}
|
|
403
|
-
for s in symbols
|
|
404
|
-
],
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
# ============================================================
|
|
408
|
-
# CODE ANALYSIS TOOLS
|
|
409
|
-
# ============================================================
|
|
410
|
-
|
|
411
|
-
@tool
|
|
412
|
-
def parse_python_code(code: str) -> Dict[str, Any]:
|
|
413
|
-
"""Parse Python code and extract structure.
|
|
414
|
-
|
|
415
|
-
Args:
|
|
416
|
-
code: Python source code
|
|
417
|
-
|
|
418
|
-
Returns:
|
|
419
|
-
Dictionary with parsed code information
|
|
420
|
-
"""
|
|
421
|
-
parsed = self._parse_python_code(code)
|
|
422
|
-
return {
|
|
423
|
-
"status": "success",
|
|
424
|
-
"is_valid": parsed.is_valid,
|
|
425
|
-
"symbols": (
|
|
426
|
-
[
|
|
427
|
-
{
|
|
428
|
-
"name": s.name,
|
|
429
|
-
"type": s.type,
|
|
430
|
-
"line": s.line,
|
|
431
|
-
"signature": s.signature,
|
|
432
|
-
"docstring": s.docstring,
|
|
433
|
-
}
|
|
434
|
-
for s in parsed.symbols
|
|
435
|
-
]
|
|
436
|
-
if parsed.symbols
|
|
437
|
-
else []
|
|
438
|
-
),
|
|
439
|
-
"imports": parsed.imports or [],
|
|
440
|
-
"errors": parsed.errors or [],
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
@tool
|
|
444
|
-
def validate_syntax(code: str) -> Dict[str, Any]:
|
|
445
|
-
"""Validate Python code syntax.
|
|
446
|
-
|
|
447
|
-
Args:
|
|
448
|
-
code: Python code to validate
|
|
449
|
-
|
|
450
|
-
Returns:
|
|
451
|
-
Dictionary with validation result
|
|
452
|
-
"""
|
|
453
|
-
return self._validate_python_syntax(code)
|
|
454
|
-
|
|
455
|
-
@tool
|
|
456
|
-
def analyze_with_pylint(
|
|
457
|
-
file_path: str = None, code: str = None, confidence: str = "HIGH"
|
|
458
|
-
) -> Dict[str, Any]:
|
|
459
|
-
"""Analyze Python code with pylint.
|
|
460
|
-
|
|
461
|
-
Args:
|
|
462
|
-
file_path: Path to Python file to analyze (optional)
|
|
463
|
-
code: Python code string to analyze (optional)
|
|
464
|
-
confidence: Minimum confidence level
|
|
465
|
-
|
|
466
|
-
Returns:
|
|
467
|
-
Dictionary with pylint analysis results
|
|
468
|
-
"""
|
|
469
|
-
try:
|
|
470
|
-
if file_path:
|
|
471
|
-
path = Path(file_path)
|
|
472
|
-
if not path.exists():
|
|
473
|
-
return {
|
|
474
|
-
"status": "error",
|
|
475
|
-
"error": f"Path not found: {file_path}",
|
|
476
|
-
}
|
|
477
|
-
target_file = str(path)
|
|
478
|
-
|
|
479
|
-
if path.is_dir():
|
|
480
|
-
import glob
|
|
481
|
-
|
|
482
|
-
py_files = glob.glob(str(path / "**/*.py"), recursive=True)
|
|
483
|
-
if not py_files:
|
|
484
|
-
return {
|
|
485
|
-
"status": "error",
|
|
486
|
-
"error": f"No Python files found in directory: {file_path}",
|
|
487
|
-
}
|
|
488
|
-
target_file = str(path)
|
|
489
|
-
elif code:
|
|
490
|
-
with tempfile.NamedTemporaryFile(
|
|
491
|
-
mode="w", suffix=".py", delete=False
|
|
492
|
-
) as f:
|
|
493
|
-
f.write(code)
|
|
494
|
-
target_file = f.name
|
|
495
|
-
else:
|
|
496
|
-
return {
|
|
497
|
-
"status": "error",
|
|
498
|
-
"error": "Either file_path or code must be provided",
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
cmd = [
|
|
502
|
-
"python",
|
|
503
|
-
"-m",
|
|
504
|
-
"pylint",
|
|
505
|
-
"--output-format=json",
|
|
506
|
-
f"--confidence={confidence}",
|
|
507
|
-
"--disable=import-error,no-member",
|
|
508
|
-
target_file,
|
|
509
|
-
]
|
|
510
|
-
|
|
511
|
-
result = subprocess.run(
|
|
512
|
-
cmd, capture_output=True, text=True, timeout=30, check=False
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
if result.returncode == 2 and "error: argument" in result.stderr:
|
|
516
|
-
return {
|
|
517
|
-
"status": "error",
|
|
518
|
-
"error": f"Pylint command failed: {result.stderr.strip()}",
|
|
519
|
-
"command": " ".join(shlex.quote(str(c)) for c in cmd),
|
|
520
|
-
"stdout": result.stdout,
|
|
521
|
-
"stderr": result.stderr,
|
|
522
|
-
"return_code": result.returncode,
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
issues = []
|
|
526
|
-
if result.stdout:
|
|
527
|
-
try:
|
|
528
|
-
issues = json.loads(result.stdout)
|
|
529
|
-
except json.JSONDecodeError:
|
|
530
|
-
if result.stderr:
|
|
531
|
-
logger.warning(f"Pylint stderr: {result.stderr}")
|
|
532
|
-
if not result.stdout.strip():
|
|
533
|
-
return {
|
|
534
|
-
"status": "error",
|
|
535
|
-
"error": f"Pylint produced no output. stderr: {result.stderr}",
|
|
536
|
-
"command": " ".join(shlex.quote(str(c)) for c in cmd),
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if code and os.path.exists(target_file):
|
|
540
|
-
os.unlink(target_file)
|
|
541
|
-
|
|
542
|
-
has_issues = len(issues) > 0
|
|
543
|
-
|
|
544
|
-
errors = [i for i in issues if i.get("type") == "error"]
|
|
545
|
-
warnings = [i for i in issues if i.get("type") == "warning"]
|
|
546
|
-
conventions = [i for i in issues if i.get("type") == "convention"]
|
|
547
|
-
refactors = [i for i in issues if i.get("type") == "refactor"]
|
|
548
|
-
|
|
549
|
-
return {
|
|
550
|
-
"status": "success",
|
|
551
|
-
"total_issues": len(issues),
|
|
552
|
-
"errors": len(errors),
|
|
553
|
-
"warnings": len(warnings),
|
|
554
|
-
"conventions": len(conventions),
|
|
555
|
-
"refactors": len(refactors),
|
|
556
|
-
"issues": issues[:50],
|
|
557
|
-
"clean": not has_issues,
|
|
558
|
-
"command": " ".join(shlex.quote(str(c)) for c in cmd),
|
|
559
|
-
"stdout": result.stdout,
|
|
560
|
-
"stderr": result.stderr if result.stderr else None,
|
|
561
|
-
"return_code": result.returncode,
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
except subprocess.TimeoutExpired:
|
|
565
|
-
return {"status": "error", "error": "Pylint analysis timed out"}
|
|
566
|
-
except subprocess.CalledProcessError as e:
|
|
567
|
-
return {"status": "error", "error": f"Pylint failed: {e.stderr}"}
|
|
568
|
-
except ImportError:
|
|
569
|
-
return {
|
|
570
|
-
"status": "error",
|
|
571
|
-
"error": "pylint is not installed. Install with: uv pip install pylint",
|
|
572
|
-
}
|
|
573
|
-
except Exception as e:
|
|
574
|
-
return {"status": "error", "error": str(e)}
|
|
575
|
-
|
|
576
|
-
# ============================================================
|
|
577
|
-
# HELPER METHODS (non-tool methods for internal use)
|
|
578
|
-
# ============================================================
|
|
579
|
-
|
|
580
|
-
def _generate_code_for_file(
|
|
581
|
-
self, filename: str, purpose: str, context: str = ""
|
|
582
|
-
) -> str:
|
|
583
|
-
"""Generate code for a specific file using LLM with streaming preview.
|
|
584
|
-
|
|
585
|
-
Args:
|
|
586
|
-
filename: Name of the file to generate
|
|
587
|
-
purpose: Description of what this file should do
|
|
588
|
-
context: Additional context about the project
|
|
589
|
-
|
|
590
|
-
Returns:
|
|
591
|
-
Generated code as string
|
|
592
|
-
"""
|
|
593
|
-
prompt = f"""Generate complete, production-ready Python code for: {filename}
|
|
594
|
-
|
|
595
|
-
Purpose: {purpose}
|
|
596
|
-
|
|
597
|
-
{context}
|
|
598
|
-
|
|
599
|
-
Requirements:
|
|
600
|
-
1. Include proper copyright header
|
|
601
|
-
2. Add comprehensive docstrings
|
|
602
|
-
3. Use type hints
|
|
603
|
-
4. Follow best practices
|
|
604
|
-
5. Include error handling
|
|
605
|
-
6. Make it complete and runnable
|
|
606
|
-
|
|
607
|
-
Generate ONLY the code, no explanations."""
|
|
608
|
-
|
|
609
|
-
try:
|
|
610
|
-
# Use streaming for live preview
|
|
611
|
-
self.console.start_file_preview(filename, max_lines=15)
|
|
612
|
-
|
|
613
|
-
code_chunks = []
|
|
614
|
-
max_retries = 2
|
|
615
|
-
retry_count = 0
|
|
616
|
-
|
|
617
|
-
while retry_count <= max_retries:
|
|
618
|
-
try:
|
|
619
|
-
max_tokens = 4096 if retry_count == 0 else 2048
|
|
620
|
-
|
|
621
|
-
for chunk in self.chat.send_stream(prompt, max_tokens=max_tokens):
|
|
622
|
-
if chunk.is_complete:
|
|
623
|
-
continue
|
|
624
|
-
chunk_text = chunk.text
|
|
625
|
-
code_chunks.append(chunk_text)
|
|
626
|
-
self.console.update_file_preview(chunk_text)
|
|
627
|
-
|
|
628
|
-
break # Success
|
|
629
|
-
|
|
630
|
-
except Exception as e:
|
|
631
|
-
retry_count += 1
|
|
632
|
-
if "timeout" in str(e).lower() or "timed out" in str(e).lower():
|
|
633
|
-
if retry_count <= max_retries:
|
|
634
|
-
self.console.print_warning(
|
|
635
|
-
f"⚠️ Generation timeout for {filename}, retrying ({retry_count}/{max_retries})..."
|
|
636
|
-
)
|
|
637
|
-
if retry_count == 2:
|
|
638
|
-
prompt = f"Generate minimal working code for {filename}. Purpose: {purpose}"
|
|
639
|
-
else:
|
|
640
|
-
self.console.print_error(
|
|
641
|
-
f"❌ Generation failed after {max_retries} retries for {filename}"
|
|
642
|
-
)
|
|
643
|
-
code_chunks = [
|
|
644
|
-
self._get_timeout_placeholder(filename, purpose)
|
|
645
|
-
]
|
|
646
|
-
break
|
|
647
|
-
else:
|
|
648
|
-
raise
|
|
649
|
-
|
|
650
|
-
self.console.stop_file_preview()
|
|
651
|
-
|
|
652
|
-
code = "".join(code_chunks).strip()
|
|
653
|
-
|
|
654
|
-
# Extract code from markdown blocks if present
|
|
655
|
-
if "```python" in code:
|
|
656
|
-
code = code.split("```python")[1].split("```")[0].strip()
|
|
657
|
-
elif "```" in code:
|
|
658
|
-
code = code.split("```")[1].split("```")[0].strip()
|
|
659
|
-
|
|
660
|
-
return code
|
|
661
|
-
|
|
662
|
-
except Exception as e:
|
|
663
|
-
logger.error(f"Failed to generate code for {filename}: {e}")
|
|
664
|
-
return self._get_timeout_placeholder(filename, purpose)
|
|
665
|
-
|
|
666
|
-
def _fix_code_with_llm(
|
|
667
|
-
self,
|
|
668
|
-
code: str,
|
|
669
|
-
file_path: str,
|
|
670
|
-
error_msg: str,
|
|
671
|
-
context: str = "",
|
|
672
|
-
max_attempts: int = 3,
|
|
673
|
-
) -> Optional[str]:
|
|
674
|
-
"""Fix code using LLM based on error message.
|
|
675
|
-
|
|
676
|
-
Args:
|
|
677
|
-
code: Code with errors
|
|
678
|
-
file_path: Path to the file (used to detect language)
|
|
679
|
-
error_msg: Error message from linting/execution
|
|
680
|
-
context: Additional context
|
|
681
|
-
max_attempts: Maximum number of fix attempts
|
|
682
|
-
|
|
683
|
-
Returns:
|
|
684
|
-
Fixed code or None if unable to fix
|
|
685
|
-
"""
|
|
686
|
-
# Detect language from file extension
|
|
687
|
-
is_typescript = file_path.endswith((".ts", ".tsx"))
|
|
688
|
-
is_python = file_path.endswith(".py")
|
|
689
|
-
is_css = file_path.endswith(".css")
|
|
690
|
-
lang = (
|
|
691
|
-
"typescript"
|
|
692
|
-
if is_typescript
|
|
693
|
-
else "python" if is_python else "css" if is_css else "unknown"
|
|
694
|
-
)
|
|
695
|
-
lang_label = (
|
|
696
|
-
"TypeScript"
|
|
697
|
-
if is_typescript
|
|
698
|
-
else "Python" if is_python else "CSS" if is_css else "unknown"
|
|
699
|
-
)
|
|
700
|
-
|
|
701
|
-
for attempt in range(max_attempts):
|
|
702
|
-
prompt = f"""Fix the following {lang_label} code error:
|
|
703
|
-
|
|
704
|
-
File path: {file_path}
|
|
705
|
-
Error: {error_msg}
|
|
706
|
-
|
|
707
|
-
Code:
|
|
708
|
-
```{lang}
|
|
709
|
-
{code}
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
{context}
|
|
713
|
-
|
|
714
|
-
Return ONLY the corrected code, no explanations."""
|
|
715
|
-
|
|
716
|
-
try:
|
|
717
|
-
response = self.chat.send(prompt, timeout=600)
|
|
718
|
-
fixed_code = response.text.strip()
|
|
719
|
-
|
|
720
|
-
# Extract code from markdown blocks
|
|
721
|
-
if f"```{lang}" in fixed_code:
|
|
722
|
-
fixed_code = (
|
|
723
|
-
fixed_code.split(f"```{lang}")[1].split("```")[0].strip()
|
|
724
|
-
)
|
|
725
|
-
elif "```" in fixed_code:
|
|
726
|
-
fixed_code = fixed_code.split("```")[1].split("```")[0].strip()
|
|
727
|
-
|
|
728
|
-
# Validate the fix (only for Python - TypeScript validated later)
|
|
729
|
-
if is_python:
|
|
730
|
-
validation = self.syntax_validator.validate_dict(fixed_code)
|
|
731
|
-
if validation["is_valid"]:
|
|
732
|
-
return fixed_code
|
|
733
|
-
else:
|
|
734
|
-
# For TypeScript, return the fix and let tsc validate
|
|
735
|
-
if fixed_code and fixed_code != code:
|
|
736
|
-
return fixed_code
|
|
737
|
-
|
|
738
|
-
except Exception as e:
|
|
739
|
-
logger.warning(f"Fix attempt {attempt + 1} failed: {e}")
|
|
740
|
-
|
|
741
|
-
return None
|
|
742
|
-
|
|
743
|
-
def _get_timeout_placeholder(self, filename: str, purpose: str) -> str:
|
|
744
|
-
"""Generate placeholder code when LLM times out.
|
|
745
|
-
|
|
746
|
-
Args:
|
|
747
|
-
filename: Name of the file
|
|
748
|
-
purpose: Purpose of the file
|
|
749
|
-
|
|
750
|
-
Returns:
|
|
751
|
-
Placeholder code
|
|
752
|
-
"""
|
|
753
|
-
return f'''# Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
754
|
-
# SPDX-License-Identifier: MIT
|
|
755
|
-
"""
|
|
756
|
-
{filename}
|
|
757
|
-
|
|
758
|
-
{purpose}
|
|
759
|
-
|
|
760
|
-
TODO: Implementation needed - LLM generation timed out.
|
|
761
|
-
"""
|
|
762
|
-
|
|
763
|
-
def main():
|
|
764
|
-
"""Main entry point."""
|
|
765
|
-
print("TODO: Implement {filename}")
|
|
766
|
-
|
|
767
|
-
if __name__ == "__main__":
|
|
768
|
-
main()
|
|
769
|
-
'''
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Consolidated code tools mixin combining generation, analysis, and helper methods."""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import shlex
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CodeToolsMixin:
|
|
18
|
+
"""Consolidated mixin providing code generation, analysis, and helper methods.
|
|
19
|
+
|
|
20
|
+
This mixin provides tools for:
|
|
21
|
+
- Generating Python functions, classes, and tests
|
|
22
|
+
- Parsing and analyzing Python code structure
|
|
23
|
+
- Validating Python syntax
|
|
24
|
+
- Extracting code symbols (functions, classes, imports)
|
|
25
|
+
- Running pylint analysis on code
|
|
26
|
+
|
|
27
|
+
Tools provided:
|
|
28
|
+
- generate_function: Generate Python function with docstring and body
|
|
29
|
+
- generate_class: Generate Python class with methods
|
|
30
|
+
- generate_test: Generate comprehensive unit tests for code
|
|
31
|
+
- parse_python_code: Parse and extract structure from Python code
|
|
32
|
+
- validate_syntax: Validate Python code syntax
|
|
33
|
+
- list_symbols: List symbols (functions, classes) in Python code
|
|
34
|
+
- analyze_with_pylint: Run pylint analysis on Python code or files
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def register_code_tools(self) -> None:
|
|
38
|
+
"""Register all code-related tools."""
|
|
39
|
+
from gaia.agents.base.tools import tool
|
|
40
|
+
|
|
41
|
+
# ============================================================
|
|
42
|
+
# CODE GENERATION TOOLS
|
|
43
|
+
# ============================================================
|
|
44
|
+
|
|
45
|
+
@tool
|
|
46
|
+
def generate_function(
|
|
47
|
+
name: str,
|
|
48
|
+
params: str = "",
|
|
49
|
+
docstring: str = "Function description.",
|
|
50
|
+
body: str = "pass",
|
|
51
|
+
return_type: str = None,
|
|
52
|
+
write_to_file: bool = True,
|
|
53
|
+
) -> Dict[str, Any]:
|
|
54
|
+
"""Generate a Python function and optionally save to file.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: Function name
|
|
58
|
+
params: Parameter list (e.g., "x, y=0")
|
|
59
|
+
docstring: Function documentation
|
|
60
|
+
body: Function implementation
|
|
61
|
+
return_type: Optional return type hint
|
|
62
|
+
write_to_file: Whether to save to a file (default: True)
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dictionary with file path or generated code
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
# Add return type hint if provided
|
|
69
|
+
signature = f"def {name}({params})"
|
|
70
|
+
if return_type:
|
|
71
|
+
signature += f" -> {return_type}"
|
|
72
|
+
signature += ":"
|
|
73
|
+
|
|
74
|
+
# Format the body with proper indentation
|
|
75
|
+
body_lines = body.split("\n")
|
|
76
|
+
needs_indent = body_lines and not body_lines[0].startswith(" ")
|
|
77
|
+
|
|
78
|
+
if needs_indent:
|
|
79
|
+
indented_lines = []
|
|
80
|
+
for line in body_lines:
|
|
81
|
+
if line.strip():
|
|
82
|
+
indented_lines.append(f" {line}")
|
|
83
|
+
else:
|
|
84
|
+
indented_lines.append("")
|
|
85
|
+
indented_body = "\n".join(indented_lines)
|
|
86
|
+
else:
|
|
87
|
+
indented_body = body
|
|
88
|
+
|
|
89
|
+
code = f'''{signature}
|
|
90
|
+
"""{docstring}
|
|
91
|
+
"""
|
|
92
|
+
{indented_body}
|
|
93
|
+
'''
|
|
94
|
+
|
|
95
|
+
# Validate the generated code
|
|
96
|
+
validation = self._validate_python_syntax(code)
|
|
97
|
+
|
|
98
|
+
result = {
|
|
99
|
+
"status": "success",
|
|
100
|
+
"is_valid": validation["is_valid"],
|
|
101
|
+
"errors": validation.get("errors", []),
|
|
102
|
+
"code": code,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if write_to_file and validation["is_valid"]:
|
|
106
|
+
filename = f"{name}_generated.py"
|
|
107
|
+
filepath = os.path.abspath(filename)
|
|
108
|
+
|
|
109
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
110
|
+
f.write(code)
|
|
111
|
+
|
|
112
|
+
result["file_path"] = filepath
|
|
113
|
+
result["message"] = f"Function '{name}' written to {filepath}"
|
|
114
|
+
|
|
115
|
+
return result
|
|
116
|
+
except Exception as e:
|
|
117
|
+
return {"status": "error", "error": str(e)}
|
|
118
|
+
|
|
119
|
+
@tool
|
|
120
|
+
def generate_class(
|
|
121
|
+
name: str,
|
|
122
|
+
docstring: str = "Class description.",
|
|
123
|
+
base_classes: str = "",
|
|
124
|
+
methods: List[Dict[str, str]] = None,
|
|
125
|
+
write_to_file: bool = True,
|
|
126
|
+
) -> Dict[str, Any]:
|
|
127
|
+
"""Generate a Python class and optionally save to file.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
name: Class name
|
|
131
|
+
docstring: Class documentation
|
|
132
|
+
base_classes: Optional base classes (e.g., "BaseClass, Mixin")
|
|
133
|
+
methods: List of methods to include
|
|
134
|
+
write_to_file: Whether to save to a file (default: True)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Dictionary with generated class code
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
# Build class signature
|
|
141
|
+
if base_classes:
|
|
142
|
+
signature = f"class {name}({base_classes}):"
|
|
143
|
+
else:
|
|
144
|
+
signature = f"class {name}:"
|
|
145
|
+
|
|
146
|
+
code = f'''{signature}
|
|
147
|
+
"""{docstring}
|
|
148
|
+
"""
|
|
149
|
+
'''
|
|
150
|
+
|
|
151
|
+
if not methods:
|
|
152
|
+
code += '''
|
|
153
|
+
def __init__(self):
|
|
154
|
+
"""Initialize the class."""
|
|
155
|
+
pass
|
|
156
|
+
'''
|
|
157
|
+
else:
|
|
158
|
+
for method in methods:
|
|
159
|
+
method_name = method.get("name", "method")
|
|
160
|
+
method_params = method.get("params", "self")
|
|
161
|
+
method_doc = method.get("docstring", "Method description.")
|
|
162
|
+
method_body = method.get("body", "pass")
|
|
163
|
+
|
|
164
|
+
if not method_params.startswith("self"):
|
|
165
|
+
method_params = (
|
|
166
|
+
f"self, {method_params}" if method_params else "self"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
body_lines = []
|
|
170
|
+
for line in method_body.split("\n"):
|
|
171
|
+
if line.strip():
|
|
172
|
+
body_lines.append(f" {line}")
|
|
173
|
+
else:
|
|
174
|
+
body_lines.append("")
|
|
175
|
+
indented_body = "\n".join(body_lines)
|
|
176
|
+
|
|
177
|
+
code += f'''
|
|
178
|
+
def {method_name}({method_params}):
|
|
179
|
+
"""{method_doc}
|
|
180
|
+
"""
|
|
181
|
+
{indented_body}
|
|
182
|
+
'''
|
|
183
|
+
|
|
184
|
+
validation = self._validate_python_syntax(code)
|
|
185
|
+
|
|
186
|
+
result = {
|
|
187
|
+
"status": "success",
|
|
188
|
+
"is_valid": validation["is_valid"],
|
|
189
|
+
"errors": validation.get("errors", []),
|
|
190
|
+
"code": code,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if write_to_file and validation["is_valid"]:
|
|
194
|
+
filename = f"{name.lower()}_generated.py"
|
|
195
|
+
filepath = os.path.abspath(filename)
|
|
196
|
+
|
|
197
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
198
|
+
f.write(code)
|
|
199
|
+
|
|
200
|
+
result["file_path"] = filepath
|
|
201
|
+
result["message"] = f"Class '{name}' written to {filepath}"
|
|
202
|
+
|
|
203
|
+
return result
|
|
204
|
+
except Exception as e:
|
|
205
|
+
return {"status": "error", "error": str(e)}
|
|
206
|
+
|
|
207
|
+
@tool
|
|
208
|
+
def generate_test(
|
|
209
|
+
class_name: str = None,
|
|
210
|
+
module_name: str = None,
|
|
211
|
+
function_name: str = None,
|
|
212
|
+
source_code: str = None,
|
|
213
|
+
test_cases: List[str] = None,
|
|
214
|
+
source_file: str = None,
|
|
215
|
+
write_to_file: bool = True,
|
|
216
|
+
) -> Dict[str, Any]:
|
|
217
|
+
"""Generate comprehensive Python unit tests and optionally save to file.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
class_name: Name for the test class (optional)
|
|
221
|
+
module_name: Module being tested (optional)
|
|
222
|
+
function_name: Specific function to test (optional)
|
|
223
|
+
source_code: Source code to analyze for test generation (optional)
|
|
224
|
+
test_cases: Manual list of test case names (optional)
|
|
225
|
+
source_file: Path to source file being tested (optional)
|
|
226
|
+
write_to_file: Whether to save to a file (default: True)
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Dictionary with file path or generated test code
|
|
230
|
+
"""
|
|
231
|
+
try:
|
|
232
|
+
functions_to_test = []
|
|
233
|
+
if source_code:
|
|
234
|
+
parsed = self._parse_python_code(source_code)
|
|
235
|
+
functions_to_test = [
|
|
236
|
+
s for s in parsed.symbols if s.type == "function"
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
if not module_name and functions_to_test:
|
|
240
|
+
module_name = "tested_module"
|
|
241
|
+
|
|
242
|
+
if not class_name:
|
|
243
|
+
if function_name:
|
|
244
|
+
class_name = f"{function_name.title()}"
|
|
245
|
+
elif functions_to_test:
|
|
246
|
+
class_name = "GeneratedTests"
|
|
247
|
+
else:
|
|
248
|
+
class_name = "Tests"
|
|
249
|
+
|
|
250
|
+
class_name = class_name or "Tests"
|
|
251
|
+
module_name = module_name or "module"
|
|
252
|
+
|
|
253
|
+
code = f'''import unittest
|
|
254
|
+
from unittest.mock import patch, MagicMock
|
|
255
|
+
import sys
|
|
256
|
+
import os
|
|
257
|
+
|
|
258
|
+
# Add parent directory to path for imports
|
|
259
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
260
|
+
|
|
261
|
+
class Test{class_name}(unittest.TestCase):
|
|
262
|
+
"""Test cases for {module_name}."""
|
|
263
|
+
|
|
264
|
+
def setUp(self):
|
|
265
|
+
"""Set up test fixtures."""
|
|
266
|
+
self.test_data = {{}}
|
|
267
|
+
|
|
268
|
+
def tearDown(self):
|
|
269
|
+
"""Clean up after tests."""
|
|
270
|
+
pass
|
|
271
|
+
'''
|
|
272
|
+
|
|
273
|
+
if function_name and functions_to_test:
|
|
274
|
+
func = next(
|
|
275
|
+
(f for f in functions_to_test if f.name == function_name), None
|
|
276
|
+
)
|
|
277
|
+
if func:
|
|
278
|
+
code += f'''
|
|
279
|
+
def test_{func.name}_basic(self):
|
|
280
|
+
"""Test basic functionality of {func.name}."""
|
|
281
|
+
# Test with typical inputs
|
|
282
|
+
# TODO: Add assertions based on function behavior
|
|
283
|
+
pass
|
|
284
|
+
|
|
285
|
+
def test_{func.name}_edge_cases(self):
|
|
286
|
+
"""Test edge cases for {func.name}."""
|
|
287
|
+
# Test boundary conditions
|
|
288
|
+
# TODO: Add edge case tests
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
def test_{func.name}_invalid_input(self):
|
|
292
|
+
"""Test {func.name} with invalid inputs."""
|
|
293
|
+
# Test error handling
|
|
294
|
+
# TODO: Add error handling tests
|
|
295
|
+
pass
|
|
296
|
+
'''
|
|
297
|
+
elif functions_to_test:
|
|
298
|
+
for func in functions_to_test[:5]:
|
|
299
|
+
code += f'''
|
|
300
|
+
def test_{func.name}(self):
|
|
301
|
+
"""Test {func.name} function."""
|
|
302
|
+
# TODO: Implement test for {func.name}
|
|
303
|
+
# Function signature: {func.signature if func.signature else func.name + '()'}
|
|
304
|
+
pass
|
|
305
|
+
'''
|
|
306
|
+
elif test_cases:
|
|
307
|
+
for test_name in test_cases:
|
|
308
|
+
code += f'''
|
|
309
|
+
def test_{test_name}(self):
|
|
310
|
+
"""Test {test_name}."""
|
|
311
|
+
# TODO: Implement test
|
|
312
|
+
self.fail("Not implemented")
|
|
313
|
+
'''
|
|
314
|
+
else:
|
|
315
|
+
code += '''
|
|
316
|
+
def test_basic_functionality(self):
|
|
317
|
+
"""Test basic functionality."""
|
|
318
|
+
# TODO: Implement basic test
|
|
319
|
+
self.assertTrue(True, "Basic test placeholder")
|
|
320
|
+
|
|
321
|
+
def test_edge_cases(self):
|
|
322
|
+
"""Test edge cases."""
|
|
323
|
+
# TODO: Implement edge case tests
|
|
324
|
+
self.assertTrue(True, "Edge case test placeholder")
|
|
325
|
+
|
|
326
|
+
def test_error_handling(self):
|
|
327
|
+
"""Test error handling."""
|
|
328
|
+
# TODO: Implement error handling tests
|
|
329
|
+
with self.assertRaises(Exception):
|
|
330
|
+
# Code that should raise exception
|
|
331
|
+
pass
|
|
332
|
+
'''
|
|
333
|
+
|
|
334
|
+
code += """
|
|
335
|
+
if __name__ == "__main__":
|
|
336
|
+
unittest.main(verbosity=2)
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
validation = self._validate_python_syntax(code)
|
|
340
|
+
|
|
341
|
+
result = {
|
|
342
|
+
"status": "success",
|
|
343
|
+
"is_valid": validation["is_valid"],
|
|
344
|
+
"errors": validation.get("errors", []),
|
|
345
|
+
"code": code, # Always include generated code
|
|
346
|
+
"functions_tested": (
|
|
347
|
+
[f.name for f in functions_to_test] if functions_to_test else []
|
|
348
|
+
),
|
|
349
|
+
"test_class": f"Test{class_name}",
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if write_to_file and validation["is_valid"]:
|
|
353
|
+
if source_file:
|
|
354
|
+
base_name = os.path.splitext(os.path.basename(source_file))[0]
|
|
355
|
+
filename = f"test_{base_name}.py"
|
|
356
|
+
elif module_name:
|
|
357
|
+
filename = f"test_{module_name.lower()}.py"
|
|
358
|
+
elif function_name:
|
|
359
|
+
filename = f"test_{function_name.lower()}.py"
|
|
360
|
+
else:
|
|
361
|
+
filename = f"test_{class_name.lower() if class_name else 'generated'}.py"
|
|
362
|
+
|
|
363
|
+
filepath = os.path.abspath(filename)
|
|
364
|
+
|
|
365
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
366
|
+
f.write(code)
|
|
367
|
+
|
|
368
|
+
result["file_path"] = filepath
|
|
369
|
+
result["message"] = f"Test file written to {filepath}"
|
|
370
|
+
|
|
371
|
+
return result
|
|
372
|
+
except Exception as e:
|
|
373
|
+
return {"status": "error", "error": str(e)}
|
|
374
|
+
|
|
375
|
+
@tool
|
|
376
|
+
def list_symbols(code: str, symbol_type: str = None) -> Dict[str, Any]:
|
|
377
|
+
"""List symbols (functions, classes, variables) in Python code.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
code: Python source code
|
|
381
|
+
symbol_type: Optional filter ('function', 'class', 'variable', 'import')
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Dictionary with list of symbols
|
|
385
|
+
"""
|
|
386
|
+
parsed = self._parse_python_code(code)
|
|
387
|
+
|
|
388
|
+
symbols = parsed.symbols or []
|
|
389
|
+
if symbol_type:
|
|
390
|
+
symbols = [s for s in symbols if s.type == symbol_type]
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
"status": "success",
|
|
394
|
+
"total_symbols": len(symbols),
|
|
395
|
+
"symbols": [
|
|
396
|
+
{
|
|
397
|
+
"name": s.name,
|
|
398
|
+
"type": s.type,
|
|
399
|
+
"line": s.line,
|
|
400
|
+
"signature": s.signature,
|
|
401
|
+
"docstring": s.docstring,
|
|
402
|
+
}
|
|
403
|
+
for s in symbols
|
|
404
|
+
],
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
# ============================================================
|
|
408
|
+
# CODE ANALYSIS TOOLS
|
|
409
|
+
# ============================================================
|
|
410
|
+
|
|
411
|
+
@tool
|
|
412
|
+
def parse_python_code(code: str) -> Dict[str, Any]:
|
|
413
|
+
"""Parse Python code and extract structure.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
code: Python source code
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Dictionary with parsed code information
|
|
420
|
+
"""
|
|
421
|
+
parsed = self._parse_python_code(code)
|
|
422
|
+
return {
|
|
423
|
+
"status": "success",
|
|
424
|
+
"is_valid": parsed.is_valid,
|
|
425
|
+
"symbols": (
|
|
426
|
+
[
|
|
427
|
+
{
|
|
428
|
+
"name": s.name,
|
|
429
|
+
"type": s.type,
|
|
430
|
+
"line": s.line,
|
|
431
|
+
"signature": s.signature,
|
|
432
|
+
"docstring": s.docstring,
|
|
433
|
+
}
|
|
434
|
+
for s in parsed.symbols
|
|
435
|
+
]
|
|
436
|
+
if parsed.symbols
|
|
437
|
+
else []
|
|
438
|
+
),
|
|
439
|
+
"imports": parsed.imports or [],
|
|
440
|
+
"errors": parsed.errors or [],
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
@tool
|
|
444
|
+
def validate_syntax(code: str) -> Dict[str, Any]:
|
|
445
|
+
"""Validate Python code syntax.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
code: Python code to validate
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
Dictionary with validation result
|
|
452
|
+
"""
|
|
453
|
+
return self._validate_python_syntax(code)
|
|
454
|
+
|
|
455
|
+
@tool
|
|
456
|
+
def analyze_with_pylint(
|
|
457
|
+
file_path: str = None, code: str = None, confidence: str = "HIGH"
|
|
458
|
+
) -> Dict[str, Any]:
|
|
459
|
+
"""Analyze Python code with pylint.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
file_path: Path to Python file to analyze (optional)
|
|
463
|
+
code: Python code string to analyze (optional)
|
|
464
|
+
confidence: Minimum confidence level
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Dictionary with pylint analysis results
|
|
468
|
+
"""
|
|
469
|
+
try:
|
|
470
|
+
if file_path:
|
|
471
|
+
path = Path(file_path)
|
|
472
|
+
if not path.exists():
|
|
473
|
+
return {
|
|
474
|
+
"status": "error",
|
|
475
|
+
"error": f"Path not found: {file_path}",
|
|
476
|
+
}
|
|
477
|
+
target_file = str(path)
|
|
478
|
+
|
|
479
|
+
if path.is_dir():
|
|
480
|
+
import glob
|
|
481
|
+
|
|
482
|
+
py_files = glob.glob(str(path / "**/*.py"), recursive=True)
|
|
483
|
+
if not py_files:
|
|
484
|
+
return {
|
|
485
|
+
"status": "error",
|
|
486
|
+
"error": f"No Python files found in directory: {file_path}",
|
|
487
|
+
}
|
|
488
|
+
target_file = str(path)
|
|
489
|
+
elif code:
|
|
490
|
+
with tempfile.NamedTemporaryFile(
|
|
491
|
+
mode="w", suffix=".py", delete=False
|
|
492
|
+
) as f:
|
|
493
|
+
f.write(code)
|
|
494
|
+
target_file = f.name
|
|
495
|
+
else:
|
|
496
|
+
return {
|
|
497
|
+
"status": "error",
|
|
498
|
+
"error": "Either file_path or code must be provided",
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
cmd = [
|
|
502
|
+
"python",
|
|
503
|
+
"-m",
|
|
504
|
+
"pylint",
|
|
505
|
+
"--output-format=json",
|
|
506
|
+
f"--confidence={confidence}",
|
|
507
|
+
"--disable=import-error,no-member",
|
|
508
|
+
target_file,
|
|
509
|
+
]
|
|
510
|
+
|
|
511
|
+
result = subprocess.run(
|
|
512
|
+
cmd, capture_output=True, text=True, timeout=30, check=False
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
if result.returncode == 2 and "error: argument" in result.stderr:
|
|
516
|
+
return {
|
|
517
|
+
"status": "error",
|
|
518
|
+
"error": f"Pylint command failed: {result.stderr.strip()}",
|
|
519
|
+
"command": " ".join(shlex.quote(str(c)) for c in cmd),
|
|
520
|
+
"stdout": result.stdout,
|
|
521
|
+
"stderr": result.stderr,
|
|
522
|
+
"return_code": result.returncode,
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
issues = []
|
|
526
|
+
if result.stdout:
|
|
527
|
+
try:
|
|
528
|
+
issues = json.loads(result.stdout)
|
|
529
|
+
except json.JSONDecodeError:
|
|
530
|
+
if result.stderr:
|
|
531
|
+
logger.warning(f"Pylint stderr: {result.stderr}")
|
|
532
|
+
if not result.stdout.strip():
|
|
533
|
+
return {
|
|
534
|
+
"status": "error",
|
|
535
|
+
"error": f"Pylint produced no output. stderr: {result.stderr}",
|
|
536
|
+
"command": " ".join(shlex.quote(str(c)) for c in cmd),
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if code and os.path.exists(target_file):
|
|
540
|
+
os.unlink(target_file)
|
|
541
|
+
|
|
542
|
+
has_issues = len(issues) > 0
|
|
543
|
+
|
|
544
|
+
errors = [i for i in issues if i.get("type") == "error"]
|
|
545
|
+
warnings = [i for i in issues if i.get("type") == "warning"]
|
|
546
|
+
conventions = [i for i in issues if i.get("type") == "convention"]
|
|
547
|
+
refactors = [i for i in issues if i.get("type") == "refactor"]
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
"status": "success",
|
|
551
|
+
"total_issues": len(issues),
|
|
552
|
+
"errors": len(errors),
|
|
553
|
+
"warnings": len(warnings),
|
|
554
|
+
"conventions": len(conventions),
|
|
555
|
+
"refactors": len(refactors),
|
|
556
|
+
"issues": issues[:50],
|
|
557
|
+
"clean": not has_issues,
|
|
558
|
+
"command": " ".join(shlex.quote(str(c)) for c in cmd),
|
|
559
|
+
"stdout": result.stdout,
|
|
560
|
+
"stderr": result.stderr if result.stderr else None,
|
|
561
|
+
"return_code": result.returncode,
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
except subprocess.TimeoutExpired:
|
|
565
|
+
return {"status": "error", "error": "Pylint analysis timed out"}
|
|
566
|
+
except subprocess.CalledProcessError as e:
|
|
567
|
+
return {"status": "error", "error": f"Pylint failed: {e.stderr}"}
|
|
568
|
+
except ImportError:
|
|
569
|
+
return {
|
|
570
|
+
"status": "error",
|
|
571
|
+
"error": "pylint is not installed. Install with: uv pip install pylint",
|
|
572
|
+
}
|
|
573
|
+
except Exception as e:
|
|
574
|
+
return {"status": "error", "error": str(e)}
|
|
575
|
+
|
|
576
|
+
# ============================================================
|
|
577
|
+
# HELPER METHODS (non-tool methods for internal use)
|
|
578
|
+
# ============================================================
|
|
579
|
+
|
|
580
|
+
def _generate_code_for_file(
|
|
581
|
+
self, filename: str, purpose: str, context: str = ""
|
|
582
|
+
) -> str:
|
|
583
|
+
"""Generate code for a specific file using LLM with streaming preview.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
filename: Name of the file to generate
|
|
587
|
+
purpose: Description of what this file should do
|
|
588
|
+
context: Additional context about the project
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
Generated code as string
|
|
592
|
+
"""
|
|
593
|
+
prompt = f"""Generate complete, production-ready Python code for: {filename}
|
|
594
|
+
|
|
595
|
+
Purpose: {purpose}
|
|
596
|
+
|
|
597
|
+
{context}
|
|
598
|
+
|
|
599
|
+
Requirements:
|
|
600
|
+
1. Include proper copyright header
|
|
601
|
+
2. Add comprehensive docstrings
|
|
602
|
+
3. Use type hints
|
|
603
|
+
4. Follow best practices
|
|
604
|
+
5. Include error handling
|
|
605
|
+
6. Make it complete and runnable
|
|
606
|
+
|
|
607
|
+
Generate ONLY the code, no explanations."""
|
|
608
|
+
|
|
609
|
+
try:
|
|
610
|
+
# Use streaming for live preview
|
|
611
|
+
self.console.start_file_preview(filename, max_lines=15)
|
|
612
|
+
|
|
613
|
+
code_chunks = []
|
|
614
|
+
max_retries = 2
|
|
615
|
+
retry_count = 0
|
|
616
|
+
|
|
617
|
+
while retry_count <= max_retries:
|
|
618
|
+
try:
|
|
619
|
+
max_tokens = 4096 if retry_count == 0 else 2048
|
|
620
|
+
|
|
621
|
+
for chunk in self.chat.send_stream(prompt, max_tokens=max_tokens):
|
|
622
|
+
if chunk.is_complete:
|
|
623
|
+
continue
|
|
624
|
+
chunk_text = chunk.text
|
|
625
|
+
code_chunks.append(chunk_text)
|
|
626
|
+
self.console.update_file_preview(chunk_text)
|
|
627
|
+
|
|
628
|
+
break # Success
|
|
629
|
+
|
|
630
|
+
except Exception as e:
|
|
631
|
+
retry_count += 1
|
|
632
|
+
if "timeout" in str(e).lower() or "timed out" in str(e).lower():
|
|
633
|
+
if retry_count <= max_retries:
|
|
634
|
+
self.console.print_warning(
|
|
635
|
+
f"⚠️ Generation timeout for {filename}, retrying ({retry_count}/{max_retries})..."
|
|
636
|
+
)
|
|
637
|
+
if retry_count == 2:
|
|
638
|
+
prompt = f"Generate minimal working code for {filename}. Purpose: {purpose}"
|
|
639
|
+
else:
|
|
640
|
+
self.console.print_error(
|
|
641
|
+
f"❌ Generation failed after {max_retries} retries for {filename}"
|
|
642
|
+
)
|
|
643
|
+
code_chunks = [
|
|
644
|
+
self._get_timeout_placeholder(filename, purpose)
|
|
645
|
+
]
|
|
646
|
+
break
|
|
647
|
+
else:
|
|
648
|
+
raise
|
|
649
|
+
|
|
650
|
+
self.console.stop_file_preview()
|
|
651
|
+
|
|
652
|
+
code = "".join(code_chunks).strip()
|
|
653
|
+
|
|
654
|
+
# Extract code from markdown blocks if present
|
|
655
|
+
if "```python" in code:
|
|
656
|
+
code = code.split("```python")[1].split("```")[0].strip()
|
|
657
|
+
elif "```" in code:
|
|
658
|
+
code = code.split("```")[1].split("```")[0].strip()
|
|
659
|
+
|
|
660
|
+
return code
|
|
661
|
+
|
|
662
|
+
except Exception as e:
|
|
663
|
+
logger.error(f"Failed to generate code for {filename}: {e}")
|
|
664
|
+
return self._get_timeout_placeholder(filename, purpose)
|
|
665
|
+
|
|
666
|
+
def _fix_code_with_llm(
|
|
667
|
+
self,
|
|
668
|
+
code: str,
|
|
669
|
+
file_path: str,
|
|
670
|
+
error_msg: str,
|
|
671
|
+
context: str = "",
|
|
672
|
+
max_attempts: int = 3,
|
|
673
|
+
) -> Optional[str]:
|
|
674
|
+
"""Fix code using LLM based on error message.
|
|
675
|
+
|
|
676
|
+
Args:
|
|
677
|
+
code: Code with errors
|
|
678
|
+
file_path: Path to the file (used to detect language)
|
|
679
|
+
error_msg: Error message from linting/execution
|
|
680
|
+
context: Additional context
|
|
681
|
+
max_attempts: Maximum number of fix attempts
|
|
682
|
+
|
|
683
|
+
Returns:
|
|
684
|
+
Fixed code or None if unable to fix
|
|
685
|
+
"""
|
|
686
|
+
# Detect language from file extension
|
|
687
|
+
is_typescript = file_path.endswith((".ts", ".tsx"))
|
|
688
|
+
is_python = file_path.endswith(".py")
|
|
689
|
+
is_css = file_path.endswith(".css")
|
|
690
|
+
lang = (
|
|
691
|
+
"typescript"
|
|
692
|
+
if is_typescript
|
|
693
|
+
else "python" if is_python else "css" if is_css else "unknown"
|
|
694
|
+
)
|
|
695
|
+
lang_label = (
|
|
696
|
+
"TypeScript"
|
|
697
|
+
if is_typescript
|
|
698
|
+
else "Python" if is_python else "CSS" if is_css else "unknown"
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
for attempt in range(max_attempts):
|
|
702
|
+
prompt = f"""Fix the following {lang_label} code error:
|
|
703
|
+
|
|
704
|
+
File path: {file_path}
|
|
705
|
+
Error: {error_msg}
|
|
706
|
+
|
|
707
|
+
Code:
|
|
708
|
+
```{lang}
|
|
709
|
+
{code}
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
{context}
|
|
713
|
+
|
|
714
|
+
Return ONLY the corrected code, no explanations."""
|
|
715
|
+
|
|
716
|
+
try:
|
|
717
|
+
response = self.chat.send(prompt, timeout=600)
|
|
718
|
+
fixed_code = response.text.strip()
|
|
719
|
+
|
|
720
|
+
# Extract code from markdown blocks
|
|
721
|
+
if f"```{lang}" in fixed_code:
|
|
722
|
+
fixed_code = (
|
|
723
|
+
fixed_code.split(f"```{lang}")[1].split("```")[0].strip()
|
|
724
|
+
)
|
|
725
|
+
elif "```" in fixed_code:
|
|
726
|
+
fixed_code = fixed_code.split("```")[1].split("```")[0].strip()
|
|
727
|
+
|
|
728
|
+
# Validate the fix (only for Python - TypeScript validated later)
|
|
729
|
+
if is_python:
|
|
730
|
+
validation = self.syntax_validator.validate_dict(fixed_code)
|
|
731
|
+
if validation["is_valid"]:
|
|
732
|
+
return fixed_code
|
|
733
|
+
else:
|
|
734
|
+
# For TypeScript, return the fix and let tsc validate
|
|
735
|
+
if fixed_code and fixed_code != code:
|
|
736
|
+
return fixed_code
|
|
737
|
+
|
|
738
|
+
except Exception as e:
|
|
739
|
+
logger.warning(f"Fix attempt {attempt + 1} failed: {e}")
|
|
740
|
+
|
|
741
|
+
return None
|
|
742
|
+
|
|
743
|
+
def _get_timeout_placeholder(self, filename: str, purpose: str) -> str:
|
|
744
|
+
"""Generate placeholder code when LLM times out.
|
|
745
|
+
|
|
746
|
+
Args:
|
|
747
|
+
filename: Name of the file
|
|
748
|
+
purpose: Purpose of the file
|
|
749
|
+
|
|
750
|
+
Returns:
|
|
751
|
+
Placeholder code
|
|
752
|
+
"""
|
|
753
|
+
return f'''# Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
754
|
+
# SPDX-License-Identifier: MIT
|
|
755
|
+
"""
|
|
756
|
+
{filename}
|
|
757
|
+
|
|
758
|
+
{purpose}
|
|
759
|
+
|
|
760
|
+
TODO: Implementation needed - LLM generation timed out.
|
|
761
|
+
"""
|
|
762
|
+
|
|
763
|
+
def main():
|
|
764
|
+
"""Main entry point."""
|
|
765
|
+
print("TODO: Implement {filename}")
|
|
766
|
+
|
|
767
|
+
if __name__ == "__main__":
|
|
768
|
+
main()
|
|
769
|
+
'''
|