healing-agent 0.2.4__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.
- healing_agent/__init__.py +15 -0
- healing_agent/agent_tools/tool_check_env_variables.py +19 -0
- healing_agent/agent_tools/tool_json_fixer.py +80 -0
- healing_agent/agent_tools/tool_list_files_functions.py +116 -0
- healing_agent/ai_broker.py +202 -0
- healing_agent/ai_code_fixer.py +174 -0
- healing_agent/ai_hint_generator.py +89 -0
- healing_agent/code_backup.py +36 -0
- healing_agent/code_replacer.py +160 -0
- healing_agent/config_loader.py +39 -0
- healing_agent/configurator.py +34 -0
- healing_agent/exception_handler.py +271 -0
- healing_agent/exception_saver.py +37 -0
- healing_agent/healing_agent.py +154 -0
- healing_agent/healing_agent_config.py +78 -0
- healing_agent-0.2.4.dist-info/METADATA +159 -0
- healing_agent-0.2.4.dist-info/RECORD +19 -0
- healing_agent-0.2.4.dist-info/WHEEL +4 -0
- healing_agent-0.2.4.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .healing_agent import healing_agent as _healing_agent
|
|
2
|
+
|
|
3
|
+
# Make the module callable by implementing __call__
|
|
4
|
+
class HealingAgentModule:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.healing_agent = _healing_agent
|
|
7
|
+
|
|
8
|
+
def __call__(self, *args, **kwargs):
|
|
9
|
+
return self.healing_agent(*args, **kwargs)
|
|
10
|
+
|
|
11
|
+
# Replace the module with our callable instance
|
|
12
|
+
import sys
|
|
13
|
+
sys.modules[__name__] = HealingAgentModule()
|
|
14
|
+
|
|
15
|
+
__all__ = ['healing_agent']
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Experimental and not used.
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
def get_environment_variables() -> dict:
|
|
6
|
+
"""
|
|
7
|
+
Get all environment variables and return them as a dictionary.
|
|
8
|
+
|
|
9
|
+
Returns:
|
|
10
|
+
dict: Dictionary containing all environment variables as key-value pairs
|
|
11
|
+
"""
|
|
12
|
+
env_vars = {}
|
|
13
|
+
for key, value in os.environ.items():
|
|
14
|
+
env_vars[key] = value
|
|
15
|
+
return env_vars
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
print(get_environment_variables())
|
|
19
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Experimental and not used.
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
from ..ai_code_fixer import get_ai_response
|
|
6
|
+
|
|
7
|
+
def fix_json_ai(context: Dict[str, Any], config: Dict[str, Any]) -> Any:
|
|
8
|
+
"""
|
|
9
|
+
Attempt to fix JSON data using AI.
|
|
10
|
+
"""
|
|
11
|
+
try:
|
|
12
|
+
json_data = context['error']['json_details']['response_text']
|
|
13
|
+
|
|
14
|
+
# Extract the first 1000 characters of the JSON data
|
|
15
|
+
json_snippet = json_data[:1000]
|
|
16
|
+
|
|
17
|
+
# Prepare the prompt for AI
|
|
18
|
+
prompt = f"""
|
|
19
|
+
Fix the following JSON data:
|
|
20
|
+
|
|
21
|
+
{json_snippet}
|
|
22
|
+
|
|
23
|
+
Return only the fixed JSON data without any explanations or markdown formatting.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# Get the fix from AI
|
|
27
|
+
fixed_json_str = get_ai_response(prompt, config)
|
|
28
|
+
|
|
29
|
+
# Parse the fixed JSON string
|
|
30
|
+
fixed_data = json.loads(fixed_json_str)
|
|
31
|
+
|
|
32
|
+
return fixed_data
|
|
33
|
+
except Exception as e:
|
|
34
|
+
print(f"♣ JSON fixing with AI failed: {str(e)}")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
def fix_json_lint(context: Dict[str, Any], config: Dict[str, Any]) -> Any:
|
|
38
|
+
"""
|
|
39
|
+
Attempt to fix JSON data using a JSON linter.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
json_data = context['error']['json_details']['response_text']
|
|
43
|
+
# Use a JSON linter library to identify and fix syntax errors
|
|
44
|
+
# Example using jsonlint:
|
|
45
|
+
# fixed_data = jsonlint.parse(json_data)
|
|
46
|
+
# return fixed_data
|
|
47
|
+
pass
|
|
48
|
+
except Exception as e:
|
|
49
|
+
print(f"♣ JSON linting failed: {str(e)}")
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
def fix_json_fallback(context: Dict[str, Any], config: Dict[str, Any]) -> Any:
|
|
53
|
+
"""
|
|
54
|
+
Attempt to fix JSON data using fallback parsing options.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
json_data = context['error']['json_details']['response_text']
|
|
58
|
+
# Use fallback parsing options, e.g., allow trailing commas
|
|
59
|
+
fixed_data = json.loads(json_data, strict=False)
|
|
60
|
+
return fixed_data
|
|
61
|
+
except json.JSONDecodeError as e:
|
|
62
|
+
print(f"♣ Fallback JSON parsing failed: {str(e)}")
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def fix_json(context: Dict[str, Any], config: Dict[str, Any]) -> Any:
|
|
66
|
+
"""
|
|
67
|
+
Attempt to fix JSON data using the selected strategy.
|
|
68
|
+
"""
|
|
69
|
+
strategy = config.get('JSON_FIX_STRATEGY', 'ai')
|
|
70
|
+
|
|
71
|
+
if strategy == 'ai':
|
|
72
|
+
return fix_json_ai(context, config)
|
|
73
|
+
elif strategy == 'lint':
|
|
74
|
+
return fix_json_lint(context, config)
|
|
75
|
+
elif strategy == 'fallback':
|
|
76
|
+
return fix_json_fallback(context, config)
|
|
77
|
+
else:
|
|
78
|
+
print(f"♣ Unknown JSON fix strategy: {strategy}")
|
|
79
|
+
return None
|
|
80
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Experimental and not used.
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import ast
|
|
5
|
+
import json
|
|
6
|
+
from typing import Dict, List, Optional, Union
|
|
7
|
+
|
|
8
|
+
def get_function_details(file_path: str) -> List[Dict[str, str]]:
|
|
9
|
+
"""
|
|
10
|
+
Extract function names and docstrings from a Python file.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
file_path: Path to the Python file
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
List of dictionaries containing function names and their docstrings
|
|
17
|
+
"""
|
|
18
|
+
functions = []
|
|
19
|
+
try:
|
|
20
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
21
|
+
tree = ast.parse(f.read())
|
|
22
|
+
|
|
23
|
+
for node in ast.walk(tree):
|
|
24
|
+
if isinstance(node, ast.FunctionDef):
|
|
25
|
+
docstring = ast.get_docstring(node)
|
|
26
|
+
functions.append({
|
|
27
|
+
"name": node.name,
|
|
28
|
+
"docstring": docstring if docstring else "No description available"
|
|
29
|
+
})
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"Error parsing {file_path}: {str(e)}")
|
|
32
|
+
return []
|
|
33
|
+
|
|
34
|
+
return functions
|
|
35
|
+
|
|
36
|
+
def list_directory_contents(
|
|
37
|
+
directory: str,
|
|
38
|
+
recursive: bool = False,
|
|
39
|
+
include_functions: bool = False
|
|
40
|
+
) -> Dict[str, Union[List[str], Dict[str, List[Dict[str, str]]]]]:
|
|
41
|
+
"""
|
|
42
|
+
List all files in a directory with optional recursive search and Python function details.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
directory: Path to the directory to scan
|
|
46
|
+
recursive: Whether to scan subdirectories recursively
|
|
47
|
+
include_functions: Whether to extract function details from Python files
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Dictionary containing:
|
|
51
|
+
- files: List of all files
|
|
52
|
+
- python_files: Dictionary of Python files and their function details (if include_functions=True)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
if not os.path.exists(directory):
|
|
56
|
+
raise ValueError(f"Directory {directory} does not exist")
|
|
57
|
+
|
|
58
|
+
result = {
|
|
59
|
+
"files": [],
|
|
60
|
+
"python_files": {}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
def scan_directory(path: str):
|
|
64
|
+
for entry in os.scandir(path):
|
|
65
|
+
if entry.is_file():
|
|
66
|
+
file_path = os.path.relpath(entry.path, directory)
|
|
67
|
+
result["files"].append(file_path)
|
|
68
|
+
|
|
69
|
+
if include_functions and file_path.endswith('.py'):
|
|
70
|
+
functions = get_function_details(entry.path)
|
|
71
|
+
if functions:
|
|
72
|
+
result["python_files"][file_path] = functions
|
|
73
|
+
|
|
74
|
+
elif entry.is_dir() and recursive:
|
|
75
|
+
scan_directory(entry.path)
|
|
76
|
+
|
|
77
|
+
scan_directory(directory)
|
|
78
|
+
|
|
79
|
+
# Sort files for consistent output
|
|
80
|
+
result["files"].sort()
|
|
81
|
+
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
def get_directory_info(
|
|
85
|
+
directory: str = ".",
|
|
86
|
+
recursive: bool = False,
|
|
87
|
+
include_functions: bool = False,
|
|
88
|
+
pretty_print: bool = False
|
|
89
|
+
) -> str:
|
|
90
|
+
"""
|
|
91
|
+
Get directory information in JSON format.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
directory: Path to the directory to scan (defaults to current directory)
|
|
95
|
+
recursive: Whether to scan subdirectories recursively
|
|
96
|
+
include_functions: Whether to extract function details from Python files
|
|
97
|
+
pretty_print: Whether to format the JSON output with indentation
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
JSON string containing directory information
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
contents = list_directory_contents(
|
|
104
|
+
directory,
|
|
105
|
+
recursive=recursive,
|
|
106
|
+
include_functions=include_functions
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return json.dumps(contents, indent=2 if pretty_print else None)
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
error_response = {
|
|
113
|
+
"error": str(e),
|
|
114
|
+
"directory": directory
|
|
115
|
+
}
|
|
116
|
+
return json.dumps(error_response, indent=2 if pretty_print else None)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
import time
|
|
3
|
+
import httpx
|
|
4
|
+
import requests
|
|
5
|
+
import openai
|
|
6
|
+
from functools import wraps
|
|
7
|
+
|
|
8
|
+
def handle_connection_errors(provider_name: str):
|
|
9
|
+
"""Simple decorator to handle connection errors with basic logging"""
|
|
10
|
+
def decorator(func):
|
|
11
|
+
@wraps(func)
|
|
12
|
+
def wrapper(*args, **kwargs):
|
|
13
|
+
try:
|
|
14
|
+
return func(*args, **kwargs)
|
|
15
|
+
except (
|
|
16
|
+
httpx.ConnectError,
|
|
17
|
+
requests.exceptions.ConnectionError,
|
|
18
|
+
requests.exceptions.Timeout,
|
|
19
|
+
ConnectionError,
|
|
20
|
+
TimeoutError
|
|
21
|
+
) as e:
|
|
22
|
+
print(f"♣ Connection error in {provider_name}: {str(e)}")
|
|
23
|
+
# Wait briefly before retrying
|
|
24
|
+
time.sleep(2)
|
|
25
|
+
try:
|
|
26
|
+
return func(*args, **kwargs)
|
|
27
|
+
except Exception as retry_error:
|
|
28
|
+
print(f"♣ Retry failed for {provider_name}: {str(retry_error)}")
|
|
29
|
+
raise
|
|
30
|
+
except openai.APIConnectionError as e:
|
|
31
|
+
if 'OpenAI' in provider_name or 'Azure' in provider_name:
|
|
32
|
+
print(f"♣ Connection error in {provider_name}: {str(e)}")
|
|
33
|
+
# Wait briefly before retrying
|
|
34
|
+
time.sleep(2)
|
|
35
|
+
try:
|
|
36
|
+
return func(*args, **kwargs)
|
|
37
|
+
except Exception as retry_error:
|
|
38
|
+
print(f"♣ Retry failed for {provider_name}: {str(retry_error)}")
|
|
39
|
+
raise
|
|
40
|
+
else:
|
|
41
|
+
raise
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"♣ Unexpected error in {provider_name}: {str(e)}")
|
|
44
|
+
raise
|
|
45
|
+
return wrapper
|
|
46
|
+
return decorator
|
|
47
|
+
|
|
48
|
+
@handle_connection_errors("Azure")
|
|
49
|
+
def _get_azure_response(prompt: str, config: Dict, system_prompt: str) -> str:
|
|
50
|
+
"""Handle Azure OpenAI API requests"""
|
|
51
|
+
import openai
|
|
52
|
+
client = openai.AzureOpenAI(
|
|
53
|
+
api_key=config['api_key'],
|
|
54
|
+
api_version=config['api_version'],
|
|
55
|
+
azure_endpoint=config['endpoint']
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
response = client.chat.completions.create(
|
|
60
|
+
model=config['deployment_name'],
|
|
61
|
+
messages=[
|
|
62
|
+
{"role": "system", "content": system_prompt},
|
|
63
|
+
{"role": "user", "content": prompt}
|
|
64
|
+
],
|
|
65
|
+
timeout=config.get('timeout', 30)
|
|
66
|
+
)
|
|
67
|
+
return response.choices[0].message.content.strip()
|
|
68
|
+
except openai.APIError as e:
|
|
69
|
+
print(f"♣ Azure API error: {str(e)}")
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
@handle_connection_errors("OpenAI")
|
|
73
|
+
def _get_openai_response(prompt: str, config: Dict, system_prompt: str) -> str:
|
|
74
|
+
"""Handle OpenAI direct API requests"""
|
|
75
|
+
import openai
|
|
76
|
+
client = openai.OpenAI(
|
|
77
|
+
api_key=config['api_key'],
|
|
78
|
+
organization=config.get('organization_id')
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
response = client.chat.completions.create(
|
|
83
|
+
model=config['model'],
|
|
84
|
+
messages=[
|
|
85
|
+
{"role": "system", "content": system_prompt},
|
|
86
|
+
{"role": "user", "content": prompt}
|
|
87
|
+
],
|
|
88
|
+
timeout=config.get('timeout', 30)
|
|
89
|
+
)
|
|
90
|
+
return response.choices[0].message.content.strip()
|
|
91
|
+
except openai.APIError as e:
|
|
92
|
+
print(f"♣ OpenAI API error: {str(e)}")
|
|
93
|
+
raise
|
|
94
|
+
|
|
95
|
+
@handle_connection_errors("Anthropic")
|
|
96
|
+
def _get_anthropic_response(prompt: str, config: Dict) -> str:
|
|
97
|
+
"""Handle Anthropic API requests"""
|
|
98
|
+
import anthropic
|
|
99
|
+
client = anthropic.Anthropic(api_key=config['api_key'])
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
response = client.messages.create(
|
|
103
|
+
model=config['model'],
|
|
104
|
+
max_tokens=1000,
|
|
105
|
+
messages=[{
|
|
106
|
+
"role": "user",
|
|
107
|
+
"content": prompt
|
|
108
|
+
}],
|
|
109
|
+
timeout=config.get('timeout', 30)
|
|
110
|
+
)
|
|
111
|
+
return response.content[0].text
|
|
112
|
+
except Exception as e:
|
|
113
|
+
print(f"♣ Anthropic API error: {str(e)}")
|
|
114
|
+
raise
|
|
115
|
+
|
|
116
|
+
@handle_connection_errors("Ollama")
|
|
117
|
+
def _get_ollama_response(prompt: str, config: Dict) -> str:
|
|
118
|
+
"""Handle Ollama API requests"""
|
|
119
|
+
try:
|
|
120
|
+
response = requests.post(
|
|
121
|
+
f"{config['host']}/api/generate",
|
|
122
|
+
json={
|
|
123
|
+
"model": config['model'],
|
|
124
|
+
"prompt": prompt,
|
|
125
|
+
"stream": False
|
|
126
|
+
},
|
|
127
|
+
timeout=config.get('timeout', 120)
|
|
128
|
+
)
|
|
129
|
+
response.raise_for_status()
|
|
130
|
+
return response.json()['response']
|
|
131
|
+
except requests.exceptions.RequestException as e:
|
|
132
|
+
print(f"♣ Ollama API error: {str(e)}")
|
|
133
|
+
raise
|
|
134
|
+
|
|
135
|
+
@handle_connection_errors("LiteLLM")
|
|
136
|
+
def _get_litellm_response(prompt: str, config: Dict, system_prompt: str) -> str:
|
|
137
|
+
"""Handle LiteLLM API requests"""
|
|
138
|
+
import litellm
|
|
139
|
+
if config.get('api_base'):
|
|
140
|
+
litellm.api_base = config['api_base']
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
response = litellm.completion(
|
|
144
|
+
model=config['model'],
|
|
145
|
+
messages=[
|
|
146
|
+
{"role": "system", "content": system_prompt},
|
|
147
|
+
{"role": "user", "content": prompt}
|
|
148
|
+
],
|
|
149
|
+
api_key=config['api_key'],
|
|
150
|
+
timeout=config.get('timeout', 30)
|
|
151
|
+
)
|
|
152
|
+
if not response or not response.choices:
|
|
153
|
+
raise ValueError("Invalid response from LiteLLM API - no choices returned")
|
|
154
|
+
|
|
155
|
+
if not response.choices[0].message or not response.choices[0].message.content:
|
|
156
|
+
raise ValueError("Invalid response format - missing message content")
|
|
157
|
+
|
|
158
|
+
return response.choices[0].message.content.strip()
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(f"♣ LiteLLM API error: {str(e)}")
|
|
162
|
+
raise
|
|
163
|
+
|
|
164
|
+
def get_ai_response(prompt: str, config: Dict, system_role: str = "code_fixer") -> str:
|
|
165
|
+
"""
|
|
166
|
+
Get response from configured AI provider.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
prompt (str): The prompt to send to the AI
|
|
170
|
+
config (Dict): Configuration dictionary
|
|
171
|
+
system_role (str): Role for system prompt - "code_fixer", "analyzer", or "report"
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
str: The AI generated response
|
|
175
|
+
"""
|
|
176
|
+
system_prompts = {
|
|
177
|
+
"code_fixer": "You are a Python code fixing assistant. Provide only the corrected code without explanations.",
|
|
178
|
+
"analyzer": "You are a Python error analysis assistant. Provide clear and concise explanation of the error and suggestions to fix it.",
|
|
179
|
+
"report": "You are a Python error reporting assistant. Provide a detailed report of the error, its cause, and the applied fix."
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
system_prompt = system_prompts.get(system_role, system_prompts["code_fixer"])
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
provider = config.get('AI_PROVIDER', 'azure').lower()
|
|
186
|
+
|
|
187
|
+
if provider == 'azure':
|
|
188
|
+
return _get_azure_response(prompt, config['AZURE'], system_prompt)
|
|
189
|
+
elif provider == 'openai':
|
|
190
|
+
return _get_openai_response(prompt, config['OPENAI'], system_prompt)
|
|
191
|
+
elif provider == 'anthropic':
|
|
192
|
+
return _get_anthropic_response(prompt, config['ANTHROPIC'])
|
|
193
|
+
elif provider == 'ollama':
|
|
194
|
+
return _get_ollama_response(prompt, config['OLLAMA'])
|
|
195
|
+
elif provider == 'litellm':
|
|
196
|
+
return _get_litellm_response(prompt, config['LITELLM'], system_prompt)
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError(f"Unsupported AI provider: {provider}")
|
|
199
|
+
|
|
200
|
+
except Exception as e:
|
|
201
|
+
print(f"♣ Error getting AI response: {str(e)}")
|
|
202
|
+
raise
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
from .ai_broker import get_ai_response
|
|
4
|
+
|
|
5
|
+
def ensure_healing_agent_decorator(code: str) -> str:
|
|
6
|
+
"""
|
|
7
|
+
Ensures the code has the @healing_agent decorator, adds it if missing.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
code (str): The code to check/modify
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
str: Code with @healing_agent decorator
|
|
14
|
+
"""
|
|
15
|
+
# Check the first 5 lines for the @healing_agent decorator
|
|
16
|
+
lines = code.split('\n')[:5]
|
|
17
|
+
for line in lines:
|
|
18
|
+
if line.strip() == '@healing_agent':
|
|
19
|
+
return code # Decorator already present
|
|
20
|
+
|
|
21
|
+
# If not found, add the decorator at the top
|
|
22
|
+
first_line = code.split('\n')[0]
|
|
23
|
+
indent = len(first_line) - len(first_line.lstrip())
|
|
24
|
+
decorator = ' ' * indent + '@healing_agent\n'
|
|
25
|
+
return decorator + code
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def prepare_fix_prompt(context: Dict[str, Any]) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Prepare the prompt for AI based on the context.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
context (Dict[str, Any]): The error context
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
str: Formatted prompt for the AI
|
|
37
|
+
"""
|
|
38
|
+
# Extract function info if available
|
|
39
|
+
function_info = context.get('function_info', {})
|
|
40
|
+
function_args = context.get('function_arguments', {})
|
|
41
|
+
error_info = context.get('error', {})
|
|
42
|
+
|
|
43
|
+
# Build argument info string
|
|
44
|
+
arg_info = ""
|
|
45
|
+
if function_args:
|
|
46
|
+
arg_info = "\nFunction was called with arguments:\n"
|
|
47
|
+
for arg_name, arg_data in function_args.items():
|
|
48
|
+
arg_info += f"{arg_name}: {arg_data.get('value')} (type: {arg_data.get('type')})\n"
|
|
49
|
+
|
|
50
|
+
# Build function info string
|
|
51
|
+
func_info = ""
|
|
52
|
+
if function_info:
|
|
53
|
+
func_info = f"""
|
|
54
|
+
Function Name: {function_info.get('name')}
|
|
55
|
+
Function Signature: {function_info.get('signature')}
|
|
56
|
+
Module: {function_info.get('module')}
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
# Build error details string
|
|
60
|
+
error_details = ""
|
|
61
|
+
if error_info.get('exception_attrs'):
|
|
62
|
+
error_details = "\nDetailed Error Information:\n"
|
|
63
|
+
for attr, value in error_info['exception_attrs'].items():
|
|
64
|
+
error_details += f"{attr}: {value}\n"
|
|
65
|
+
|
|
66
|
+
# Add traceback frames for context
|
|
67
|
+
traceback_info = ""
|
|
68
|
+
if error_info.get('traceback_frames'):
|
|
69
|
+
traceback_info = "\nTraceback Frames:\n"
|
|
70
|
+
for frame in error_info['traceback_frames']:
|
|
71
|
+
traceback_info += f"File: {frame['filename']}, Line {frame['line_number']}, in {frame['function']}\n"
|
|
72
|
+
traceback_info += f"Code: {frame['code']}\n"
|
|
73
|
+
|
|
74
|
+
# Include AI hint if available
|
|
75
|
+
ai_hint = ""
|
|
76
|
+
if context.get('ai_hint'):
|
|
77
|
+
ai_hint = f"\nAI Analysis:\n{context['ai_hint']}"
|
|
78
|
+
|
|
79
|
+
return f"""
|
|
80
|
+
Fix the following Python code that produced an error, or at least handle the exceptions, add more info that could help debugging next time:
|
|
81
|
+
|
|
82
|
+
Original Code:
|
|
83
|
+
{context['function_info']['source_code']}
|
|
84
|
+
|
|
85
|
+
Error Type: {context['error']['type']}
|
|
86
|
+
Error Message: {context['error']['message']}
|
|
87
|
+
Error Line Number: {context['error'].get('line_number')}
|
|
88
|
+
Error Line: {context['error'].get('error_line')}
|
|
89
|
+
|
|
90
|
+
{error_details}
|
|
91
|
+
{traceback_info}
|
|
92
|
+
{func_info}{arg_info}{ai_hint}
|
|
93
|
+
|
|
94
|
+
Return only the fixed code without any explanations or markdown formatting.
|
|
95
|
+
Ensure the fixed code maintains the same function name and signature.
|
|
96
|
+
Add appropriate error handling where necessary.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def validate_fixed_code(fixed_code: str) -> bool:
|
|
100
|
+
"""
|
|
101
|
+
Validate the fixed code is syntactically correct.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
fixed_code (str): The code to validate
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
bool: True if valid, False otherwise
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
# Check if the code is syntactically valid Python
|
|
111
|
+
compile(fixed_code, '<string>', 'exec')
|
|
112
|
+
|
|
113
|
+
# Basic checks for common issues
|
|
114
|
+
if not fixed_code.strip():
|
|
115
|
+
print("♣ Generated code is empty")
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
if "def " not in fixed_code:
|
|
119
|
+
print("♣ Generated code doesn't contain function definition")
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
except SyntaxError as e:
|
|
125
|
+
print(f"♣ Syntax error in generated code: {str(e)}")
|
|
126
|
+
return False
|
|
127
|
+
except Exception as e:
|
|
128
|
+
print(f"♣ Validation error: {str(e)}")
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
def fix(context: Dict[str, Any], config: Dict[str, Any]) -> str:
|
|
132
|
+
"""
|
|
133
|
+
Fix buggy code using AI based on the provided context.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
context (Dict[str, Any]): Dictionary containing:
|
|
137
|
+
- original_code (str): The original buggy code
|
|
138
|
+
- error_info (Dict): Error details including type, message, traceback
|
|
139
|
+
- stack_trace (str): Full stack trace of the error
|
|
140
|
+
- function_name (str): Name of the function that caused the error
|
|
141
|
+
- additional_context (Dict): Any additional context that might help
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
str: The fixed version of the code
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
# Prepare the prompt for AI
|
|
148
|
+
prompt = prepare_fix_prompt(context)
|
|
149
|
+
|
|
150
|
+
# Get the fix from AI with code_fixer role
|
|
151
|
+
fixed_code = get_ai_response(prompt, config, "code_fixer")
|
|
152
|
+
|
|
153
|
+
# Remove markdown code block formatting if present
|
|
154
|
+
fixed_code = re.sub(r'^```python\n|^```\n|```$', '', fixed_code, flags=re.MULTILINE)
|
|
155
|
+
|
|
156
|
+
# Ensure healing_agent decorator is present
|
|
157
|
+
fixed_code = ensure_healing_agent_decorator(fixed_code)
|
|
158
|
+
|
|
159
|
+
# Validate the fixed code
|
|
160
|
+
if validate_fixed_code(fixed_code):
|
|
161
|
+
|
|
162
|
+
return fixed_code
|
|
163
|
+
else:
|
|
164
|
+
print("♣ Generated fix failed validation")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
except Exception as e:
|
|
168
|
+
print(f"♣ Error during code fixing: {str(e)}")
|
|
169
|
+
print(f"♣ Error type: {type(e).__name__}")
|
|
170
|
+
print(f"♣ Error details: {repr(e)}")
|
|
171
|
+
print(f"♣ Error traceback:")
|
|
172
|
+
import traceback
|
|
173
|
+
traceback.print_exc()
|
|
174
|
+
return
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
from .ai_broker import get_ai_response
|
|
3
|
+
|
|
4
|
+
def generate_hint(context: Dict[str, Any], config: Dict[str, Any]) -> str:
|
|
5
|
+
"""
|
|
6
|
+
Generate an AI-powered hint based on the exception context.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
context (Dict[str, Any]): The exception context
|
|
10
|
+
config (Dict[str, Any]): The configuration dictionary
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
str: The generated AI hint
|
|
14
|
+
"""
|
|
15
|
+
# Extract error information
|
|
16
|
+
error = context['error']
|
|
17
|
+
error_type = error['type']
|
|
18
|
+
error_message = error['message']
|
|
19
|
+
function_name = error['function_name']
|
|
20
|
+
traceback = error['traceback']
|
|
21
|
+
traceback_frames = error['traceback_frames']
|
|
22
|
+
exception_attrs = error['exception_attrs']
|
|
23
|
+
|
|
24
|
+
# Extract any additional error details if present
|
|
25
|
+
error_details = ""
|
|
26
|
+
for detail_type in ['json_details', 'connection_details', 'timeout_details',
|
|
27
|
+
'http_details', 'value_details', 'key_details',
|
|
28
|
+
'type_details', 'file_details']:
|
|
29
|
+
if detail_type in error:
|
|
30
|
+
error_details += f"\n{detail_type}:\n{error[detail_type]}"
|
|
31
|
+
|
|
32
|
+
# Get function info
|
|
33
|
+
function_info = context.get('function_info', {})
|
|
34
|
+
function_source = function_info.get('source_code', '')
|
|
35
|
+
function_module = function_info.get('module', '')
|
|
36
|
+
|
|
37
|
+
# Get function arguments
|
|
38
|
+
function_arguments = context.get('function_arguments', {})
|
|
39
|
+
|
|
40
|
+
# Get environment info
|
|
41
|
+
python_version = context.get('python_version', '')
|
|
42
|
+
platform = context.get('platform', '')
|
|
43
|
+
|
|
44
|
+
# Prepare the prompt for AI
|
|
45
|
+
prompt = f"""
|
|
46
|
+
An exception occurred in a Python program:
|
|
47
|
+
|
|
48
|
+
ENVIRONMENT:
|
|
49
|
+
Python Version: {python_version}
|
|
50
|
+
Platform: {platform}
|
|
51
|
+
|
|
52
|
+
ERROR DETAILS:
|
|
53
|
+
Error Type: {error_type}
|
|
54
|
+
Error Message: {error_message}
|
|
55
|
+
Function Name: {function_name}
|
|
56
|
+
Module: {function_module}
|
|
57
|
+
|
|
58
|
+
Source Code:
|
|
59
|
+
{function_source}
|
|
60
|
+
|
|
61
|
+
Function Arguments:
|
|
62
|
+
{function_arguments}
|
|
63
|
+
|
|
64
|
+
Exception Attributes:
|
|
65
|
+
{exception_attrs}
|
|
66
|
+
|
|
67
|
+
Traceback:
|
|
68
|
+
{traceback}
|
|
69
|
+
|
|
70
|
+
Detailed Traceback Frames:
|
|
71
|
+
{traceback_frames}
|
|
72
|
+
|
|
73
|
+
Additional Error Details:
|
|
74
|
+
{error_details}
|
|
75
|
+
|
|
76
|
+
Based on all the provided context, generate a helpful hint or suggestion for resolving the issue. Consider:
|
|
77
|
+
1. The exact error type and message
|
|
78
|
+
2. The function's source code
|
|
79
|
+
3. The values of arguments passed to the function
|
|
80
|
+
4. Any additional error-specific details
|
|
81
|
+
5. The full execution context from the traceback
|
|
82
|
+
|
|
83
|
+
Provide the hint in a concise and clear manner, avoiding any code snippets or markdown formatting.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
# Get the AI-generated hint with analyzer role
|
|
87
|
+
hint = get_ai_response(prompt, config, system_role="analyzer")
|
|
88
|
+
|
|
89
|
+
return hint
|