Files
Last_one/AGENT_ARCHITECTURE.md
dapa46 0f38d8d588 Add Deep Agents UI, pipeline fixes, and agent improvements.
Includes deep-agents-ui integration, rework detection via Gitea,
tool-call sanitization fixes, and startup scripts.
2026-06-18 19:33:26 +03:00

24 KiB
Raw Permalink Blame History

Архитектура папки src/agent

Обзор проекта

Проект brojs-agent — это мультиагентная система для автоматизации выполнения заданий курса KFU-26-1 на платформе BroJS (platform.brojs.ru). Система использует OpenRouter (облачный LLM) и координирует несколько специализированных агентов через фреймворк deepagents и LangGraph.

Основная задача

  1. Получить список незакрытых заданий из журнала BroJS
  2. Запустить агента для выполнения каждого задания
  3. Агент пишет код, создаёт репозиторий на Gitea, тестирует, отправляет ответ
  4. При отклонении (пересдача) — клонирует код, исправляет по замечаниям, пересдаёт

Структура файлов

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.

Как это работает:

  1. Загрузка асинхронно через MultiServerMCPClient
  2. HTTP transport к https://platform.brojs.ru/jrnl-bh/api/mcp
  3. Авторизация Bearer токеном из .env (JOURNAL_TOKEN=jrnl_...)
  4. Раздает инструменты в две группы:
Группа Инструменты Назначение
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) — пушить в origin
  • git_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
  • Роль: Выполняет одно задание от начала до конца
  • Процесс:
    1. Читает текст задания
    2. Генерирует решение (код)
    3. Тестирует локально
    4. Создаёт/обновляет репозиторий
    5. Пушит решение
    6. Отправляет ответ в Journal
    7. Сдаёт на проверку
  • Middleware:
    • RetryOnRateLimitMiddleware() — retry при 429
    • SanitizeToolCallsMiddleware — фильтрация
    • ValidateJournalWorkflowMiddleware() — проверка workflow

🔄 Агент пересдачи (rework_agent)

  • Инструменты: Такие же как homework_direct_agent
  • Роль: При отклонении — клонирует, исправляет, пересдаёт
  • Процесс:
    1. Клонирует предыдущее решение
    2. Читает замечания преподавателя
    3. Исправляет код по замечаниям
    4. Пушит обновление
    5. Переотправляет ответ
    6. Пересдаёт

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]             # Ошибки

Узлы графа:

  1. fetch_tasks → Получить список незакрытых заданий из Journal
  2. process_task → Запустить homework_direct_agent на текущее задание
  3. check_result → Проверить результат (успех/ошибка)
  4. 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 Tokens
  • GITEA_TOKEN → git.brojs.ru → Settings → Applications → Access Tokens
  • OPENAI_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   │
                 └─────────────────────┘
                 (Генерация кода и рассуждения)

Система автоматизирует весь цикл выполнения и сдачи заданий, обрабатывая ошибки, пересдачи и координируя несколько специализированных агентов через единый фреймворк.