0f38d8d588
Includes deep-agents-ui integration, rework detection via Gitea, tool-call sanitization fixes, and startup scripts.
151 lines
6.1 KiB
Python
151 lines
6.1 KiB
Python
"""
|
|
Инструмент 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}"
|