Includes deep-agents-ui integration, rework detection via Gitea, tool-call sanitization fixes, and startup scripts.
24 KiB
Архитектура папки src/agent
Обзор проекта
Проект brojs-agent — это мультиагентная система для автоматизации выполнения заданий курса KFU-26-1 на платформе BroJS (platform.brojs.ru). Система использует OpenRouter (облачный LLM) и координирует несколько специализированных агентов через фреймворк deepagents и LangGraph.
Основная задача
- Получить список незакрытых заданий из журнала BroJS
- Запустить агента для выполнения каждого задания
- Агент пишет код, создаёт репозиторий на Gitea, тестирует, отправляет ответ
- При отклонении (пересдача) — клонирует код, исправляет по замечаниям, пересдаёт
Структура файлов
src/agent/
├── agent.py # 🎯 Инициализация всех агентов (главный орк, ДЗ, пересдача)
├── constants.py # ⚙️ Константы: ID курса, пути, Gitea-параметры
├── llm.py # 🧠 Инициализация LLM через OpenRouter
├── mcp_client.py # 📡 Загрузка инструментов Journal через MCP
├── gitea_tools.py # 🔧 REST-инструменты для git.brojs.ru
├── tools.py # 🌐 Git и веб-инструменты
├── prompts.py # 📝 Системные промпты для всех агентов
├── subagents.py # 🤖 Спецификации субагентов
├── runner_tools.py # ⚙️ Инструменты запуска заданий
├── solve_tools.py # 💻 Инструменты для решения (генерация кода, валидация)
├── solve_prompts.py # 📄 Промпты для решения заданий
├── middlewares/ # 🛡️ Middleware для обработки ошибок и валидации
│ ├── retry_on_rate_limit.py # Retry при ошибках 429
│ ├── sanitize_tool_calls.py # Фильтрация незнакомых инструментов
│ └── validate_journal_workflow.py # Валидация workflow Journal
├── graph/ # 📊 LangGraph pipeline
│ ├── pipeline.py # Основной pipeline последовательного выполнения
│ └── __init__.py
├── agent_workspace/ # 💾 Рабочая папка агента
│ ├── AGENTS.md # Память агента между запусками
│ └── large_tool_results/ # Кэш больших результатов
└── __pycache__/
Компоненты системы
1️⃣ LLM (Большая языковая модель)
Файл: llm.py
ChatOpenAI(
model="openai/gpt-oss-20b:free",
base_url="https://openrouter.ai/api/v1",
api_key=os.getenv("OPENAI_API_KEY"), # sk-or-v1-...
temperature=0.0,
)
- Провайдер: OpenRouter (облачный сервис)
- Модель:
gpt-oss-20b:free(бесплатная, 20B параметров) - Температура: 0.0 (детерминированные ответы)
- Зачем OpenRouter вместо Ollama?
- Нет требований к GPU/RAM
- Совместим с LangChain из коробки
- Работает в CI/CD
- Бесплатный тир
2️⃣ Konstantes & пути
Файл: constants.py
Определяет ключевые параметры:
COURSE_ID = "698b49da77cb6d4d2e43ce78" # ID курса KFU-26-1
GITEA_OWNER = "dapa46" # Владелец репозиториев на git.brojs.ru
AGENT_WORKSPACE_DIR = /path/to/agent_workspace # Рабочая папка
AGENTS_MD_VFS_PATH = "/AGENTS.md" # Виртуальный путь памяти
Функция ensure_agents_md_file() создаёт AGENTS.md при первом запуске — файл служит памятью агента между сообщениями.
3️⃣ MCP: Инструменты Journal
Файл: mcp_client.py
MCP (Model Context Protocol) — стандарт для подключения инструментов к LLM.
Как это работает:
- Загрузка асинхронно через
MultiServerMCPClient - HTTP transport к
https://platform.brojs.ru/jrnl-bh/api/mcp - Авторизация Bearer токеном из
.env(JOURNAL_TOKEN=jrnl_...) - Раздает инструменты в две группы:
| Группа | Инструменты | Назначение |
|---|---|---|
| Courses & Lessons | courses_list, lessons_list |
Получить структуру курса |
| Tasks & Submissions | tasks_list, task_text, task_get, task_update_answer, task_submit, task_submission_status |
Работа с заданиями |
Обработка ошибок:
- Exponential backoff при ошибке 429 (rate limit): 15, 30, 60, 120, 240 сек
- Персистентный клиент переиспользуется для всего запуска
- Минимальная пауза 1.5 сек между вызовами (предотвращает burst)
4️⃣ Gitea REST API
Файл: gitea_tools.py
Инструменты для работы с git.brojs.ru:
@tool()
def gitea_list_repos() → str
# Список своих репозиториев
@tool()
def gitea_create_repo(name: str) → str
# Создать новый репозиторий
@tool()
def gitea_get_file(repo: str, path: str) → str
# Прочитать файл из репозитория
@tool()
def gitea_create_file(repo: str, path: str, content: str, message: str) → str
# Создать/обновить файл
@tool()
def gitea_delete_repo(repo: str) → str
# Удалить репозиторий
Авторизация: Token через заголовок Authorization: token {GITEA_TOKEN}
Эндпоинты: REST API v1: /api/v1/repos/{owner}/{repo}/...
5️⃣ Git & Web tools
Файл: tools.py
Git инструменты (работают в agent_workspace):
git_clone(url)— клонировать репозиторийgit_commit(repo, files, message)— коммитить файлыgit_push(repo)— пушить в origingit_get_commit_history(repo)— получить историю
Web инструменты:
web_search(query)— поиск через Tavily (если настроен API ключ)get_page_content(url)— загрузить страницу и вернуть Markdown
6️⃣ Solve tools: Генерация и валидация кода
Файл: solve_tools.py
Специализированные инструменты для решения заданий:
@tool()
async def generate_code_solution(
task_id: str,
task_text: str,
previous_code: str = ""
) → str
# Генерирует/улучшает Python-решение
# Вызывает субагента с prompt из solve_prompts.py
@tool()
async def validate_teacher_comment(
task_id: str,
comment: str
) → dict
# Валидирует замечание преподавателя
# Разбирает комментарий на claim'ы, каждый валидирует отдельно
Кэширование: Результаты кэшируются в agent_workspace/large_tool_results/
7️⃣ Агенты (deepagents)
Файл: agent.py
Система создаёт 3 основных агента:
🎯 Главный агент (agent)
- Инструменты: Gitea, Journal, solve_task
- Роль: Оркестратор — управляет всем процессом
- Субагенты: web_search, homework_doing, journal_bh_tasks_submissions
- Middleware: SanitizeToolCallsMiddleware (фильтрует недоступные инструменты)
💻 Агент ДЗ (homework_direct_agent)
- Инструменты: Git, Gitea, Web, Journal, Solve tools
- Роль: Выполняет одно задание от начала до конца
- Процесс:
- Читает текст задания
- Генерирует решение (код)
- Тестирует локально
- Создаёт/обновляет репозиторий
- Пушит решение
- Отправляет ответ в Journal
- Сдаёт на проверку
- Middleware:
RetryOnRateLimitMiddleware()— retry при 429SanitizeToolCallsMiddleware— фильтрацияValidateJournalWorkflowMiddleware()— проверка workflow
🔄 Агент пересдачи (rework_agent)
- Инструменты: Такие же как homework_direct_agent
- Роль: При отклонении — клонирует, исправляет, пересдаёт
- Процесс:
- Клонирует предыдущее решение
- Читает замечания преподавателя
- Исправляет код по замечаниям
- Пушит обновление
- Переотправляет ответ
- Пересдаёт
8️⃣ Субагенты (для делегирования)
Файл: subagents.py
LangGraph поддерживает делегирование задач субагентам. Определены 3 субагента:
subagent_specs = [
{
"name": "web_search",
"description": "Ищет информацию в интернете",
"system_prompt": research_instructions,
# tools добавляются в agent.py
},
{
"name": "homework_doing",
"description": "Выполняет домашние задания",
"system_prompt": homework_doing_instructions,
},
{
"name": "journal_bh_tasks_submissions",
"description": "Работает с Journal: задания и сдачи",
"system_prompt": journal_tasks_submissions_instructions,
},
]
Каждый субагент получает свой набор инструментов и промпт через middleware.
9️⃣ Middleware: фильтрация и обработка ошибок
Папка: middlewares/
retry_on_rate_limit.py
Перехватывает ошибки 429 и retry'т с exponential backoff.
class RetryOnRateLimitMiddleware:
async def before_tool_call(tool, input):
# Ищет 429 в предыдущих ошибках
if rate_limit_error:
await asyncio.sleep(backoff)
return retry_tool_call()
sanitize_tool_calls.py
Фильтрует вызовы инструментов — дозволяет только известные.
class SanitizeToolCallsMiddleware:
def __init__(self, known_tools: set[str]):
self.known_tools = known_tools
async def before_tool_call(tool, input):
if tool.name not in self.known_tools:
raise ValueError(f"Unknown tool: {tool.name}")
validate_journal_workflow.py
Проверяет корректность workflow при работе с Journal:
- После получения задания → обновить ответ
- Перед сдачей → проверить статус
- После сдачи → подтвердить
🔟 LangGraph Pipeline
Файл: graph/pipeline.py
LangGraph — это граф для управления состоянием и последовательностью выполнения.
Состояние (PipelineState):
class PipelineState(TypedDict):
tasks: list[TaskInfo] # Список всех заданий
current_index: int # Индекс текущего задания
results: list[dict] # Результаты выполнения
errors: list[str] # Ошибки
Узлы графа:
fetch_tasks→ Получить список незакрытых заданий из Journalprocess_task→ Запуститьhomework_direct_agentна текущее заданиеcheck_result→ Проверить результат (успех/ошибка)next_or_done→ Перейти к следующему заданию или завершить
Переходы:
START → fetch_tasks → process_task → check_result → (success) → next_or_done
→ (error) → next_or_done
→ (need_rework) → run_rework_agent → next_or_done
1️⃣1️⃣ Промпты (системные инструкции)
Файл: prompts.py
Содержит системные промпты для каждого агента:
main_agent_instructions
Инструкции для главного оркестратора:
- Управляет процессом в целом
- Делегирует задачи субагентам
- Координирует работу
homework_doing_instructions
Инструкции для агента выполнения ДЗ:
- Как читать задание
- Как генерировать код
- Как тестировать
- Как создавать репозиторий
- Как отправлять ответ
rework_instructions
Инструкции для агента пересдачи:
- Как интерпретировать замечания
- Как исправлять код
- Как пересдавать
research_instructions
Инструкции для веб-поиска:
- Искать только из реально открытых страниц
- Не выдумывать факты
- Подтверждать URL
Поток выполнения
📊 Общий сценарий
1. Пользователь запускает pipeline.py
↓
2. pipeline.fetch_tasks()
→ Запрашивает Journal: "Дай мне все незакрытые задания курса KFU-26-1"
→ Получает список TaskInfo (id, title, status)
↓
3. Для каждого задания (while current_index < len(tasks)):
↓
4. pipeline.process_task()
→ Вызывает homework_direct_agent.invoke({
"messages": [HumanMessage("Выполни задание XYZ")]
})
↓
5. homework_direct_agent работает:
5a. Читает текст задания через task_text()
5b. Вызывает generate_code_solution() → получает Python-код
5c. Тестирует код локально (git_clone → test.py → git_push)
5d. Обновляет ответ в Journal через task_update_answer()
5e. Отправляет на проверку через task_submit()
↓
6. pipeline.check_result()
→ Проверяет: успех? ошибка? нужна пересдача?
↓
7. Если успех → перейти к следующему заданию
Если ошибка → залогировать и перейти
Если пересдача → запустить rework_agent
↓
8. pipeline.next_or_done()
→ current_index += 1
→ Если есть ещё задания → goto 4
→ Иначе → завершить, вернуть results
Интеграции
🔌 Внешние сервисы
| Сервис | Назначение | Авторизация | Endpoint |
|---|---|---|---|
| OpenRouter | LLM (генерация кода) | OPENAI_API_KEY=sk-or-v1-... |
https://openrouter.ai/api/v1 |
| BroJS Journal | Задания и сдачи | JOURNAL_TOKEN=jrnl_... |
https://platform.brojs.ru/jrnl-bh/api/mcp |
| Gitea (git.brojs.ru) | Хранилище кода | GITEA_TOKEN=... |
https://git.brojs.ru/api/v1 |
| Tavily | Веб-поиск (опционально) | TAVILY_API_KEY=tvly-... |
https://api.tavily.com |
🛠️ Локальные компоненты
- agent_workspace/ — кэш результатов, память агента (AGENTS.md)
- Python subprocess — выполнение git команд, тестирование кода
Конфигурация (.env)
# Обязательные
JOURNAL_TOKEN=jrnl_8b251e8e5b345e310269a697968abecad08043a2a30f07229cefe488e59fb951
GITEA_TOKEN=f5892316061c124f447710bb242f2f90c565a411
OPENAI_API_KEY=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
# Опциональные
TAVILY_API_KEY=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 # для web_search
Где получить:
JOURNAL_TOKEN→ platform.brojs.ru → Settings → Access TokensGITEA_TOKEN→ git.brojs.ru → Settings → Applications → Access TokensOPENAI_API_KEY→ openrouter.ai → API Keys (форматsk-or-v1-...)TAVILY_API_KEY→ tavily.com → API Keys
Ключевые паттерны
🎯 Паттерн: Tool Middleware Pipeline
# В deepagents каждый вызов инструмента проходит через цепь middleware:
инструмент → middleware1.before → middleware2.before → ... → ИСПОЛНЕНИЕ
↓
результат ← middleware1.after ← middleware2.after ← ... ← результат
🔄 Паттерн: Субагент как инструмент
# Главный агент не делает работу сам, а вызывает:
agent.invoke()
→ Видит "нужно выполнить ДЗ"
→ Вызывает инструмент: homework_doing (это субагент)
→ Субагент имеет свой LLM, свой prompt, свои инструменты
→ Возвращает результат главному агенту
📝 Паттерн: Виртуальная файловая система (VFS)
# Агент видит файловую систему как:
/AGENTS.md # памятью агента (бинд к agent_workspace/AGENTS.md)
/skills/... # bundled skills (бинд к src/agent/skills/)
~/project/... # рабочая папка (бинд к agent_workspace/)
Это позволяет агенту иметь консистентный вид файловой системы, хотя на самом деле это виртуальная проекция.
💾 Паттерн: Асинхронная загрузка MCP
# MCP может быть долгой операцией (сетевой запрос)
# Решение: asyncio.run() в отдельном потоке при наличии event loop
try:
asyncio.get_running_loop() # есть ли event loop?
# Да → запустить в ThreadPoolExecutor
with ThreadPoolExecutor() as pool:
result = pool.submit(lambda: asyncio.run(fetch())).result()
except RuntimeError:
# Нет → просто asyncio.run()
result = asyncio.run(fetch())
Отладка
📋 Логирование
Система выводит логи при инициализации:
=== Загружено: journal=X, gitea=5, git=5 ===
При ошибке MCP:
MCP 'journal-bh-professor': не удалось загрузить инструменты — ...
🧪 Тестирование компонентов
# Импорт отдельного агента
from src.agent.agent import homework_direct_agent
# Запуск на тестовом задании
result = homework_direct_agent.invoke({
"messages": [HumanMessage("Выполни задание ...")]
})
🔍 Проверка инструментов
# Какие инструменты есть у агента?
print(homework_direct_agent.tools)
# Какие middleware установлены?
# Нужно смотреть в create_deep_agent(middleware=[...])
Возможные ошибки и решения
| Ошибка | Причина | Решение |
|---|---|---|
ImportError: No module named 'src.agent.agent' |
Неправильный PYTHONPATH | cd /path/to/project && python -m src.agent.graph.pipeline |
MCP journal: не удалось загрузить |
Токен неправильный/устарел | Сгенерировать новый JOURNAL_TOKEN на platform.brojs.ru |
429 Too Many Requests |
Rate limit OpenRouter | Middleware автоматически retry'т с backoff |
gitea_create_repo: 401 Unauthorized |
Неправильный Gitea токен | Проверить GITEA_TOKEN в .env |
generate_code_solution timeout |
Генерация долгая | Увеличить timeout в LLM конфиге |
Резюме: Как это работает вместе
┌─────────────────────────────────────────────────────────┐
│ PIPELINE (LangGraph) │
│ Последовательно: fetch_tasks → process_task → check │
└──────────────────────┬──────────────────────────────────┘
│
├─→ JOURNAL (MCP)
│ Получить задания, отправить ответы
│
├─→ HOMEWORK_DIRECT_AGENT
│ ├─ generate_code_solution (LLM)
│ ├─ gitea_* (REST API)
│ ├─ git_* (subprocess)
│ └─ task_submit (Journal)
│
├─→ REWORK_AGENT
│ └─ Аналогично, но для пересдачи
│
└─→ MIDDLEWARES
├─ SanitizeToolCallsMiddleware
├─ RetryOnRateLimitMiddleware
└─ ValidateJournalWorkflowMiddleware
┌─────────────────────┐
│ OpenRouter LLM │
│ gpt-oss-20b:free │
└─────────────────────┘
(Генерация кода и рассуждения)
Система автоматизирует весь цикл выполнения и сдачи заданий, обрабатывая ошибки, пересдачи и координируя несколько специализированных агентов через единый фреймворк.