From f110f52e91ed7a75a8117c9e810df13c88257ac0 Mon Sep 17 00:00:00 2001 From: "ki.sagidullin" Date: Sat, 11 Apr 2026 10:20:47 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=87=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B0=D0=B3=D0=B5=D0=BD=D1=82=D1=8B,=20=D0=BD=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=20=D1=82=D1=80=D0=B5=D0=B1=D1=83=D1=8E=D1=82?= =?UTF-8?q?=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agents.py | 119 +++++++++++++++++++++++++++++++++++------------ config.py | 3 +- orchestrator.py | 3 +- requirements.txt | 2 +- 4 files changed, 95 insertions(+), 32 deletions(-) diff --git a/agents.py b/agents.py index c01bba1..508c33a 100644 --- a/agents.py +++ b/agents.py @@ -1,11 +1,12 @@ # agents.py -from typing import Dict, Any +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 +from config import MODEL_NAME, TEMPERATURE, LLM_HOST from models import CodeSolution, ReviewResult, ReviewComment, Task +import re class DeveloperAgent: """Агент-разработчик: генерирует код по требованию""" @@ -14,11 +15,11 @@ class DeveloperAgent: self.llm = ChatOllama( model=MODEL_NAME, temperature=TEMPERATURE, - base_url="http://localhost:11434" + base_url=LLM_HOST ) def develop(self, task: Task, previous_comments: list = None) -> CodeSolution: - """Генерирует код на основе требования и замечаний""" + """Генерирует код с улучшенным парсингом JSON""" system_prompt = """Ты - опытный разработчик. Твоя задача - написать качественный код. Ты должен: @@ -26,17 +27,26 @@ class DeveloperAgent: 2. Добавлять обработку ошибок 3. Писать юнит-тесты (pytest/unittest) 4. Следовать PEP8 (для Python) или стандартам языка - 5. Возвращать решение в строго определённом JSON формате - В ответе должен быть JSON с полями: - - files: dict (имя_файла -> содержимое) - - description: str (описание решения) - - tests: dict (имя_теста -> содержимое теста) + ВАЖНО: Возвращай ТОЛЬКО валидный 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} """ @@ -55,26 +65,77 @@ class DeveloperAgent: ] response = self.llm.invoke(messages) + raw_content = response.content - # Парсим JSON ответ - try: - # Извлекаем JSON из ответа - content = response.content - # Находим JSON в тексте (между ```json и ``` или просто сам JSON) - if "```json" in content: - content = content.split("```json")[1].split("```")[0] - elif "```" in content: - content = content.split("```")[1].split("```")[0] + # Пытаемся извлечь 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"}} + }} - data = json.loads(content.strip()) - return CodeSolution( - files=data.get("files", {}), - description=data.get("description", ""), - tests=data.get("tests", {}) - ) - except Exception as e: - raise ValueError(f"Failed to parse developer response: {e}\nResponse: {response.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: """Агент-ревьювер: проверяет качество кода""" @@ -82,7 +143,7 @@ class ReviewerAgent: self.llm = ChatOllama( model=MODEL_NAME, temperature=0.1, - base_url="http://localhost:11434" + base_url=LLM_HOST ) def review(self, task: Task, solution: CodeSolution) -> ReviewResult: diff --git a/config.py b/config.py index e8951f9..3b1ea97 100644 --- a/config.py +++ b/config.py @@ -6,7 +6,8 @@ load_dotenv() # Конфигурация модели # OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") -MODEL_NAME = "qwen3.5:latest" # или "gpt-3.5-turbo" для тестов +LLM_HOST = "http://192.168.212.100:11434" +MODEL_NAME = "qwen3.5-16k:latest" # или "gpt-3.5-turbo" для тестов TEMPERATURE = 0.3 MAX_ITERATIONS = 3 diff --git a/orchestrator.py b/orchestrator.py index 2bfb77e..73575e8 100644 --- a/orchestrator.py +++ b/orchestrator.py @@ -18,7 +18,8 @@ class Orchestrator: # Создаем граф состояний self.workflow = self._build_workflow() - self.app = self.workflow.compile(checkpointer=MemorySaver()) + self.checkpointer = MemorySaver() + self.app = self.workflow.compile() def _build_workflow(self) -> StateGraph: """Строит граф процесса разработки""" diff --git a/requirements.txt b/requirements.txt index 70a9a75..b45a989 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # requirements.txt -langgraph>=0.2.0 +langgraph>=0.3.0 langchain>=0.2.0 langchain-openai>=0.1.0 langchain-community>=0.0.16