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": "Проверить состояние"}