""" Инструмент solve_task — надёжное выполнение одного задания. Агент-оркестратор вызывает его для каждого todo-задания. Python внутри обеспечивает верификацию и страховочный сабмит, но решение о том какие задания выполнять и в каком порядке принимает LLM-оркестратор. """ from __future__ import annotations import asyncio from langchain.tools import tool from langchain_core.messages import HumanMessage from src.agent.constants import COURSE_ID @tool async def list_course_tasks(status_filter: str = "all") -> str: """Получить список заданий курса KFU-26-1 из журнала BroJS. Используй этот инструмент для проверки статусов (todo, in_progress, done и т.д.). Не используй read_file/grep для списка заданий. Args: status_filter: all | todo | in_progress | done | ready_for_review или несколько через запятую, например "todo,in_progress" Returns: Список заданий: id, название, статус. """ from src.agent.graph.pipeline import _get_journal_tool, _parse_tasks tool = _get_journal_tool("tasks_list") if not tool: return "ERROR: tasks_list недоступен — проверь JOURNAL_TOKEN в .env" raw = await tool.ainvoke({"courseId": COURSE_ID}) tasks = _parse_tasks(raw) if not tasks: return "Заданий не найдено (пустой ответ журнала)." filters = {s.strip() for s in status_filter.lower().split(",") if s.strip()} if filters and "all" not in filters: tasks = [t for t in tasks if (t.get("status") or "").lower() in filters] lines = [f"Всего: {len(tasks)} заданий (фильтр: {status_filter})", ""] for t in tasks: tid = t.get("id", "") title = (t.get("title") or "")[:60] status = t.get("status") or "?" lines.append(f"- {tid} | {status} | {title}") return "\n".join(lines) @tool async def solve_task(task_id: str) -> str: """Полностью выполнить одно задание курса: написать код, запушить в репо, сдать. Автоматически определяет режим: - Первая сдача: читает условие, генерирует код с нуля - Пересдача: читает замечания, исправляет или защищает решение После выполнения верифицирует репозиторий и гарантированно сдаёт задание. Args: task_id: ID задания из tasks_list (например 6a22c713fd30e81cf315ea04) Returns: Строка с результатом: "OK ..." или "ERROR ..." """ # Ленивый импорт чтобы избежать circular import на уровне модуля from src.agent.agent import homework_direct_agent, rework_agent from src.agent.graph.pipeline import ( MAX_RETRIES, _fix_prompt, _force_submit, _get_task_meta, _invoke_with_retry, _is_submitted, _needs_retry, _task_text, _verify_repo, TaskInfo, ) # Небольшая пауза — снижает давление на rate limit await asyncio.sleep(5) # Определяем: первая сдача или пересдача meta = await _get_task_meta(task_id) repo_url = meta["repo_url"] is_rework = repo_url is not None or meta["has_feedback"] if is_rework: if not repo_url: from src.agent.constants import GITEA_OWNER repo_url = f"https://git.brojs.ru/{GITEA_OWNER}/task-{task_id}" comments = meta["comments"] prompt = ( f"Пересдача задания.\n\n" f"ID: {task_id}\n" f"Репозиторий: {repo_url}\n" f"Комментарии преподавателя: {comments}\n\n" "Внеси исправления и отправь снова." ) agent_to_use = rework_agent else: task_text = await _task_text(task_id) prompt = ( f"Выполни задание.\n\n" f"ID: {task_id}\n\n" f"Текст задания:\n{task_text}\n\n" "Первая сдача. Напиши код с нуля." ) agent_to_use = homework_direct_agent try: result = await _invoke_with_retry( agent_to_use, {"messages": [HumanMessage(content=prompt)]}, {"configurable": {"thread_id": f"runner-{task_id}"}}, ) # Верификация репозитория (только первая сдача) repo_name = f"task-{task_id}" retries = 0 if not is_rework: task_info = TaskInfo(id=task_id, title="", status="") verification = await _verify_repo(repo_name) while _needs_retry(verification) and retries < MAX_RETRIES: retries += 1 fix_msg = _fix_prompt(task_info, repo_name, verification) result = await _invoke_with_retry( agent_to_use, {"messages": [HumanMessage(content=fix_msg)]}, {"configurable": {"thread_id": f"runner-{task_id}-retry-{retries}"}}, ) verification = await _verify_repo(repo_name) # Гарантированный сабмит если агент не сдал сам if not await _is_submitted(task_id): await _force_submit(task_id) mode = "пересдача" if is_rework else "первая сдача" return f"OK: задание {task_id[:8]}... выполнено ({mode}, retries={retries})" except Exception as e: return f"ERROR: задание {task_id[:8]}...: {type(e).__name__}: {e}"