# agents.py from typing import Optional, Dict, Any from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, SystemMessage from langchain_community.chat_models import ChatOllama import json from config import MODEL_NAME, TEMPERATURE, LLM_HOST from models import CodeSolution, ReviewResult, ReviewComment, Task import re class DeveloperAgent: """Агент-разработчик: генерирует код по требованию""" def __init__(self): self.llm = ChatOllama( model=MODEL_NAME, temperature=TEMPERATURE, base_url=LLM_HOST ) def develop(self, task: Task, previous_comments: list = None) -> CodeSolution: """Генерирует код с улучшенным парсингом JSON""" system_prompt = """Ты - опытный разработчик. Твоя задача - написать качественный код. Ты должен: 1. Писать чистый, читаемый код с комментариями 2. Добавлять обработку ошибок 3. Писать юнит-тесты (pytest/unittest) 4. Следовать PEP8 (для Python) или стандартам языка ВАЖНО: Возвращай ТОЛЬКО валидный JSON без лишних символов. ЗАПРЕЩЕНЫ trailing commas (запятые перед закрывающей скобкой). Формат ответа (пример): { "files": { "main.py": "print('hello')" }, "description": "Краткое описание", "tests": { "test_main.py": "def test_hello(): assert True" } } Не добавляй пояснений вне JSON. """ user_prompt = f""" Требование: {task.requirement} Текущая итерация: {task.iteration} """ if previous_comments: user_prompt += f""" Замечания с предыдущего ревью: {json.dumps(previous_comments, indent=2, ensure_ascii=False)} Пожалуйста, исправь код согласно замечаниям. """ messages = [ SystemMessage(content=system_prompt), HumanMessage(content=user_prompt) ] response = self.llm.invoke(messages) raw_content = response.content # Пытаемся извлечь JSON из разных форматов parsed_data = self._extract_json(raw_content) if parsed_data is None: # Если не удалось, пробуем повторно с explicit инструкцией print("⚠️ Не удалось распарсить JSON, повторный запрос с исправлением...") correction_prompt = f""" Предыдущий ответ содержал невалидный JSON (возможно, trailing commas). Пожалуйста, исправь и верни ТОЛЬКО валидный JSON в формате: {{ "files": {{"filename": "content"}}, "description": "text", "tests": {{"testfile": "test_content"}} }} Твоя предыдущая попытка: {raw_content[:1000]} Требование: {task.requirement} """ messages2 = [ SystemMessage(content="Ты - разработчик. Верни только валидный JSON без лишних символов."), HumanMessage(content=correction_prompt) ] response2 = self.llm.invoke(messages2) parsed_data = self._extract_json(response2.content) if parsed_data is None: raise ValueError(f"Failed to parse JSON after retry. Raw: {response2.content[:500]}") return CodeSolution( files=parsed_data.get("files", {}), description=parsed_data.get("description", ""), tests=parsed_data.get("tests", {}) ) def _extract_json(self, text: str) -> Optional[dict]: """Извлекает и очищает JSON из текста ответа LLM""" # 1. Извлечь блок между ```json и ``` или просто ``` if "```json" in text: text = text.split("```json")[1].split("```")[0] elif "```" in text: text = text.split("```")[1].split("```")[0] text = text.strip() # 2. Удалить trailing commas в объектах и массивах # Удаляем запятую перед } или ] text = re.sub(r',\s*}', '}', text) text = re.sub(r',\s*]', ']', text) # 3. Удалить комментарии (// ...) если есть text = re.sub(r'//.*?(\n|$)', '', text) # 4. Попробовать распарсить try: return json.loads(text) except json.JSONDecodeError as e: print(f"JSON decode error: {e}") # 5. Еще одна попытка: найти любой JSON объект в строке match = re.search(r'\{.*\}|\[.*\]', text, re.DOTALL) if match: try: return json.loads(match.group()) except: pass return None class ReviewerAgent: """Агент-ревьювер: проверяет качество кода""" def __init__(self): self.llm = ChatOllama( model=MODEL_NAME, temperature=0.1, base_url=LLM_HOST ) def review(self, task: Task, solution: CodeSolution) -> ReviewResult: """Проверяет код и возвращает замечания""" system_prompt = """Ты - строгий ревьювер кода. Твоя задача: 1. Проверить соответствие требованиям 2. Найти логические ошибки и баги 3. Оценить читаемость и стиль кода 4. Проверить покрытие тестами 5. Найти проблемы безопасности 6. Предложить улучшения Оценивай код критически. Будь конкретен в замечаниях, указывай файлы и строки. Верни JSON с полями: - status: "approved", "changes_requested", или "rejected" - comments: массив объектов с полями: file, line, severity, text - summary: краткое резюме ревью """ # Формируем представление кода для ревью code_for_review = "" for filename, content in solution.files.items(): code_for_review += f"\n--- {filename} ---\n{content}\n" if solution.tests: code_for_review += "\n--- TESTS ---\n" for testname, test_content in solution.tests.items(): code_for_review += f"\n--- {testname} ---\n{test_content}\n" user_prompt = f""" Требование: {task.requirement} Код на ревью: {code_for_review} Описание решения разработчика: {solution.description} Проведи ревью. Будь строг, но справедлив. """ messages = [ SystemMessage(content=system_prompt), HumanMessage(content=user_prompt) ] response = self.llm.invoke(messages) # Парсим JSON ответ try: content = response.content if "```json" in content: content = content.split("```json")[1].split("```")[0] elif "```" in content: content = content.split("```")[1].split("```")[0] data = json.loads(content.strip()) comments = [] for comment_data in data.get("comments", []): comments.append(ReviewComment( line=comment_data.get("line"), file=comment_data.get("file"), severity=comment_data.get("severity", "major"), text=comment_data.get("text", "") )) return ReviewResult( status=data.get("status", "changes_requested"), comments=comments, summary=data.get("summary", "") ) except Exception as e: raise ValueError(f"Failed to parse reviewer response: {e}\nResponse: {response.content}")