Files
agents/PlannerAgent.py
T
2026-09-19 13:18:49 +00:00

192 lines
11 KiB
Python

class PlannerAgent:
"""Агент-планировщик: анализирует проект и создаёт план подзадач."""
@staticmethod
def plan(project: Project, user_task: str, errors: List[str] = None) -> List[Dict[str, str]]:
# Получаем суммари из состояния
summaries = project.state.get("file_summaries", {})
if not summaries:
print("⚠️ Нет описаний файлов. Сначала сгенерируйте их.")
return [{"type": "error", "description": "Нет описаний файлов"}]
# Формируем текстовое представление суммари
items = []
for file_path, desc in summaries.items():
items.append(f"- {file_path}: {desc}")
summaries_text = "\n".join(items)
# Берём последние ошибки из состояния (если есть)
errors_context = ""
if errors:
errors_context = "Актуальные ошибки в браузере:\n" + "\n".join(errors[:5])
else:
errors_context = "Ошибок в браузере не зафиксировано."
# last_errors = project.state.get("last_errors", [])
# errors_context = ""
# if last_errors:
# errors_context = "\n".join(last_errors[:3])
prompt = f"""
Ты — главный архитектор. Получена задача: {user_task}
Вот краткое описание каждого важного файла проекта:
{summaries_text}
{errors_context}
Твоя задача — составить план действий. План должен быть списком задач.
Для каждой задачи укажи тип агента:
- "test" — запустить браузер и проверить консоль на ошибки
- "code" — написать или изменить код. Для этой задачи обязательно укажи поле "files" — массив относительных путей к файлам, которые нужно передать кодеру для контекста.
- "browser" — открыть браузер для визуальной проверки (не обязательно)
План должен быть логичным: сначала обычно проверяют текущее состояние (test), затем исправляют ошибки (code), снова проверяют (test) и т.д.
Но ты можешь предложить любую последовательность, которая, по твоему мнению, решит задачу.
Ответ дай строго в формате JSON-списка объектов:
[
{{"type": "test", "description": "описание задачи для тестировщика"}},
{{"type": "code", "description": "описание задачи для кодера", "files": ["путь/к/файлу1", "путь/к/файлу2"]}}
]
Никаких пояснений, только JSON.
"""
messages = [{"role": "system", "content": "Ты — архитектор, выдаёшь только JSON."},
{"role": "user", "content": prompt}]
response = project.ollama_client.chat(messages, temperature=0.3)
try:
start = response.find('[')
end = response.rfind(']') + 1
if start != -1 and end != -1:
json_str = response[start:end]
plan = json.loads(json_str)
if isinstance(plan, list) and all(
isinstance(item, dict) and "type" in item and "description" in item for item in plan):
for item in plan:
if item["type"] == "code" and "files" not in item:
item["files"] = [] # если не указано, ставим пустой массив
return plan
except Exception as e:
print(f"Ошибка парсинга плана: {e}")
# fallback
return [{"type": "code", "description": "Не удалось создать план, попробуйте вручную.", "files": []}]
@staticmethod
def replan(project: Project, user_task: str, errors: List[str]) -> List[Dict[str, str]]:
"""Анализирует ошибки и возвращает список новых задач для их исправления."""
summaries = project.state.get("file_summaries", {})
summaries_text = "\n".join([f"- {p}: {d}" for p, d in summaries.items()])
errors_text = "\n".join(errors) # ограничим для контекста
prompt = f"""
Ты — главный архитектор. Получена задача: {user_task}
Вот описание файлов проекта:
{summaries_text}
При выполнении теста были обнаружены следующие ошибки:
{errors_text}
Проанализируй ошибки. Определи, какие изменения в коде нужно внести, чтобы их исправить.
Составь список задач для кодера (тип "code") с конкретными инструкциями по исправлению.
Если ошибки связаны с отсутствием файлов или неправильными путями, добавь задачу по созданию или перемещению.
Ответ дай строго в формате JSON-списка объектов: [{{"type": "code", "description": "описание"}}]
Никаких пояснений, только JSON.
"""
messages = [{"role": "system", "content": "Ты — архитектор, выдаёшь только JSON."},
{"role": "user", "content": prompt}]
response = project.ollama_client.chat(messages, temperature=0.3)
try:
start = response.find('[')
end = response.rfind(']') + 1
if start != -1 and end != -1:
json_str = response[start:end]
tasks = json.loads(json_str)
if isinstance(tasks, list) and all(
isinstance(item, dict) and "type" in item and "description" in item for item in tasks):
return tasks
except Exception:
pass
return [{"type": "code", "description": "Исправить ошибки в коде (ручное вмешательство)"}]
@staticmethod
def next_task(project: Project, user_task: str) -> dict:
summaries = project.state.get("file_summaries", {})
if not summaries:
return {"type": "error", "description": "Нет описаний файлов"}
completed = project.state.get("completed", [])
last_errors = project.state.get("last_errors", [])
errors_text = ""
if last_errors:
for err in last_errors[:5]:
msg = err.get("message", "")
loc = err.get("location")
if loc:
file_name = loc.get("url", "неизвестный файл")
line = loc.get("line", "?")
col = loc.get("column", "?")
errors_text += f"- {msg} (в {file_name}:{line}:{col})\n"
else:
errors_text += f"- {msg}\n"
if err.get("stack"):
errors_text += f" Стек: {err['stack']}\n"
else:
errors_text = "Ошибок не зафиксировано."
summaries_text = "\n".join([f"- {path}: {desc}" for path, desc in list(summaries.items())[:10]])
completed_text = "\n".join([f"- {task['type']}: {task['description']}" for task in completed]) or "нет"
prompt = f"""
Ты — главный архитектор. Получена задача: {user_task}
Описание файлов проекта:
{summaries_text}
Уже выполненные задачи:
{completed_text}
Актуальные ошибки (с указанием файлов и строк):
{errors_text}
Твоя задача — решить, что делать дальше.
ВНИМАНИЕ: НЕ возвращай finish, пока не будут выполнены как минимум 2 задачи и не будет ошибок.
Если ошибки есть — ты должен предложить задачу типа "code" для их исправления.
Если ошибок нет и уже было выполнено хотя бы 2 задачи, тогда можешь вернуть finish.
Иначе верни одну задачу для следующего шага. Типы задач:
- "test" — запустить браузер и проверить консоль на ошибки (это нужно делать, если неизвестно состояние)
- "code" — написать или изменить код. Укажи поле "files" — массив относительных путей к файлам, которые нужно передать кодеру. Старайся указывать файлы на основе информации об ошибках.
- "browser" — открыть браузер для визуальной проверки
Ответ дай строго в формате JSON-объекта:
{{"type": "test", "description": "описание"}}
или
{{"type": "code", "description": "описание", "files": ["путь/к/файлу"]}}
Никаких пояснений, только JSON.
"""
messages = [{"role": "system", "content": "Ты — архитектор, выдаёшь только JSON."},
{"role": "user", "content": prompt}]
response = project.ollama_client.chat(messages, temperature=0.3)
print(f"Ответ планировщика: {response}") # отладка
if not response:
return {"type": "test", "description": "Проверить состояние"}
try:
start = response.find('{')
end = response.rfind('}') + 1
if start != -1 and end != -1:
json_str = response[start:end]
task = json.loads(json_str)
if "type" in task and "description" in task:
if task["type"] == "code" and "files" not in task:
task["files"] = []
# Принудительно не даём finish, если мало задач выполнено
if task.get("type") == "finish" and len(completed) < 2:
return {"type": "test", "description": "Проверить состояние"}
return task
except Exception as e:
print(f"Парсинг JSON не удался: {e}")
# Если не удалось распарсить, возвращаем тест по умолчанию
return {"type": "test", "description": "Проверить состояние"}