Coverage for app \ engines \ base.py: 30%

99 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-30 09:36 +0100

1""" 

2Base class for all SLM engines 

3 

4Engines are neural network-based components that use 

5Small Language Models for code understanding and generation. 

6""" 

7from abc import ABC, abstractmethod 

8from typing import Dict, Any, Optional 

9import logging 

10 

11from app.models.schemas import TaskType, Language 

12 

13logger = logging.getLogger(__name__) 

14 

15 

16class BaseEngine(ABC): 

17 """Base class for all SLM engines""" 

18 

19 def __init__(self, name: str, model_path: Optional[str] = None): 

20 self.name = name 

21 self.model_path = model_path 

22 self.model = None 

23 self.initialized = False 

24 logger.info(f"Creating engine: {name}") 

25 

26 @abstractmethod 

27 async def initialize(self): 

28 """ 

29 Initialize the engine and load model 

30 

31 Should set self.initialized = True when done 

32 """ 

33 pass 

34 

35 @abstractmethod 

36 async def process( 

37 self, 

38 task: TaskType, 

39 code: str, 

40 language: Language, 

41 context: Optional[str] = None, 

42 trace: Optional[str] = None, 

43 **kwargs 

44 ) -> Dict[str, Any]: 

45 """ 

46 Process a task using the SLM 

47 

48 Args: 

49 task: Type of task 

50 code: Source code 

51 language: Programming language 

52 context: Additional context 

53 trace: Error trace (if applicable) 

54 **kwargs: Additional parameters 

55 

56 Returns: 

57 Dict with: 

58 - success: bool 

59 - result: str (generated/fixed code or explanation) 

60 - explanation: Optional[str] 

61 - suggestions: Optional[List[str]] 

62 """ 

63 pass 

64 

65 @abstractmethod 

66 async def shutdown(self): 

67 """Cleanup and free resources""" 

68 pass 

69 

70 def build_prompt(self, task: TaskType, code: str, context: Optional[str] = None) -> str: 

71 """ 

72 Build prompt for SLM based on task type 

73  

74 Args: 

75 task: Type of task 

76 code: Source code 

77 context: Additional context 

78  

79 Returns: 

80 Formatted prompt string 

81 """ 

82 if task == TaskType.FIX: 

83 return self._build_fix_prompt(code, context) 

84 elif task == TaskType.EXPLAIN: 

85 return self._build_explain_prompt(code, context) 

86 elif task == TaskType.REFACTOR: 

87 return self._build_refactor_prompt(code, context) 

88 elif task == TaskType.TEST: 

89 return self._build_test_prompt(code, context) 

90 elif task == TaskType.BOILERPLATE: 

91 return self._build_boilerplate_prompt(context) 

92 elif task == TaskType.TRANSLATE: 

93 return self._build_translate_prompt(code, context) 

94 else: 

95 return f"Code:\n{code}\n\nTask: {task}" 

96 

97 def get_stop_tokens(self, task: TaskType) -> list: 

98 """ 

99 Get stop tokens to prevent over-generation 

100  

101 Args: 

102 task: Type of task 

103  

104 Returns: 

105 List of stop tokens 

106 """ 

107 # Common stop tokens 

108 common_stops = ["```", "\n\n\n", "# Example", "# Test"] 

109 

110 # Task-specific stops 

111 task_stops = { 

112 TaskType.FIX: ["# Fixed code:", "# Original:"], 

113 TaskType.BOILERPLATE: ["# Usage:", "# Example usage:"], 

114 TaskType.TEST: ["# Run tests:"], 

115 TaskType.EXPLAIN: ["# Code:", "# Summary:"], 

116 } 

117 

118 return common_stops + task_stops.get(task, []) 

119 

120 def _build_fix_prompt(self, code: str, context: Optional[str]) -> str: 

121 """Build prompt for code fixing""" 

122 prompt = """Fix the following Python code. Return ONLY the corrected code without explanation. 

123 

124Rules: 

125- Fix syntax errors 

126- Fix logic errors 

127- Maintain original functionality 

128- Keep the same structure 

129- Do not add comments unless necessary 

130 

131""" 

132 if context: 

133 prompt += f"Additional context: {context}\n\n" 

134 

135 prompt += f"Code to fix:\n```python\n{code}\n```\n\n" 

136 prompt += "Fixed code:\n```python\n" 

137 return prompt 

138 

139 def _build_explain_prompt(self, code: str, context: Optional[str]) -> str: 

140 """Build prompt for explaining code""" 

141 prompt = """Explain the following Python code in detail. 

142 

143Rules: 

144- Provide a high-level summary. 

145- Break down the code into logical sections and explain each. 

146- Highlight key concepts and potential improvements. 

147- Use clear and concise language. 

148 

149""" 

150 if context: 

151 prompt += f"Focus on: {context}\n\n" 

152 

153 prompt += f"Code to explain:\n```python\n{code}\n```\n\n" 

154 prompt += "Explanation:\n" 

155 return prompt 

156 

157 def _build_refactor_prompt(self, code: str, context: Optional[str]) -> str: 

158 """Build prompt for refactoring code""" 

159 prompt = """Refactor the following Python code to improve readability, maintainability, and performance. Return ONLY the refactored code without explanation. 

160 

161Rules: 

162- Maintain original functionality. 

163- Apply Python best practices and idioms. 

164- Improve variable names, function structure, and overall design. 

165- Do not add comments unless necessary. 

166 

167""" 

168 if context: 

169 prompt += f"Refactoring requirements: {context}\n\n" 

170 

171 prompt += f"Original code:\n```python\n{code}\n```\n\n" 

172 prompt += "Refactored code:\n```python\n" 

173 return prompt 

174 

175 def _build_test_prompt(self, code: str, context: Optional[str]) -> str: 

176 """Build prompt for generating unit tests""" 

177 prompt = """Generate comprehensive unit tests for the following Python code using `pytest`. Return ONLY the test code without explanation. 

178 

179Rules: 

180- Cover normal cases, edge cases, and error cases. 

181- Use descriptive test function names. 

182- Include assertions for expected behavior. 

183- Use `pytest.fixture` for setup if needed. 

184 

185""" 

186 if context: 

187 prompt += f"Test requirements: {context}\n\n" 

188 

189 prompt += f"Code to test:\n```python\n{code}\n```\n\n" 

190 prompt += "Test code:\n```python\n" 

191 return prompt 

192 

193 def _build_translate_prompt(self, code: str, context: Optional[str]) -> str: 

194 """Build prompt for translating code""" 

195 prompt = """Translate the following code. Return ONLY the translated code without explanation. 

196 

197""" 

198 if context: 

199 prompt += f"{context}\n\n" 

200 

201 prompt += f"Code to translate:\n```\n{code}\n```\n\n" 

202 prompt += "Translated code:\n```\n" 

203 return prompt 

204 

205 def _build_boilerplate_prompt(self, context: Optional[str]) -> str: 

206 """Build prompt for boilerplate generation""" 

207 prompt = """Generate clean, well-structured Python code based on the description below. 

208 

209Requirements: 

210- Follow PEP 8 style guide 

211- Include docstrings 

212- Handle edge cases 

213- Use type hints where appropriate 

214- Keep it simple and readable 

215 

216""" 

217 if context: 

218 prompt += f"Description: {context}\n\n" 

219 else: 

220 prompt += "Description: Create a basic implementation\n\n" 

221 

222 prompt += "Code:\n```python\n" 

223 return prompt 

224 

225 def _default_prompt( 

226 self, 

227 code: str, 

228 language: Language, 

229 context: Optional[str], 

230 trace: Optional[str] 

231 ) -> str: 

232 """Default prompt""" 

233 return f"Process the following {language} code:\n\n```{language}\n{code}\n```" 

234 

235 def _format_result( 

236 self, 

237 success: bool, 

238 result: Optional[str] = None, 

239 explanation: Optional[str] = None, 

240 suggestions: Optional[list] = None 

241 ) -> Dict[str, Any]: 

242 """Helper to format results consistently""" 

243 return { 

244 "success": success, 

245 "result": result, 

246 "explanation": explanation, 

247 "suggestions": suggestions or [] 

248 } 

249 

250 def _extract_code_from_response(self, response: str) -> str: 

251 """Extract code from model response (handles markdown code blocks)""" 

252 import re 

253 

254 # Look for ```language\ncode\n``` pattern 

255 pattern = r'```(?:\w+)?\n(.*?)\n?```' 

256 matches = re.findall(pattern, response, re.DOTALL) 

257 

258 if matches: 

259 # Return first code block 

260 return matches[0].strip() 

261 

262 # If no code blocks, check if response looks like code already 

263 # (happens when prompt ends with ``` and model just generates code) 

264 lines = response.strip().split('\n') 

265 

266 # Skip empty lines at start/end 

267 while lines and not lines[0].strip(): 

268 lines.pop(0) 

269 while lines and not lines[-1].strip(): 

270 lines.pop() 

271 

272 # If we have content, return it 

273 if lines: 

274 return '\n'.join(lines) 

275 

276 # Empty response 

277 return ""