138 lines
5.7 KiB
Python
138 lines
5.7 KiB
Python
from typing import Optional, Dict, Any
|
||
from langchain_core.messages import HumanMessage, SystemMessage
|
||
from langchain_ollama 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,
|
||
num_predict=8192
|
||
)
|
||
|
||
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
|