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

568 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Архитектура папки `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](llm.py)
```python
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](constants.py)
Определяет ключевые параметры:
```python
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_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](gitea_tools.py)
Инструменты для работы с git.brojs.ru:
```python
@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](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](solve_tools.py)
Специализированные инструменты для решения заданий:
```python
@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](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](subagents.py)
LangGraph поддерживает делегирование задач субагентам. Определены 3 субагента:
```python
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/](middlewares/)
#### `retry_on_rate_limit.py`
Перехватывает ошибки 429 и retry'т с exponential backoff.
```python
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`
Фильтрует вызовы инструментов — дозволяет только известные.
```python
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](graph/pipeline.py)
**LangGraph** — это граф для управления состоянием и последовательностью выполнения.
#### Состояние (`PipelineState`):
```python
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](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`)
```bash
# Обязательные
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
```python
# В deepagents каждый вызов инструмента проходит через цепь middleware:
инструмент middleware1.before middleware2.before ... ИСПОЛНЕНИЕ
результат middleware1.after middleware2.after ... результат
```
### 🔄 Паттерн: Субагент как инструмент
```python
# Главный агент не делает работу сам, а вызывает:
agent.invoke()
Видит "нужно выполнить ДЗ"
Вызывает инструмент: homework_doing (это субагент)
Субагент имеет свой LLM, свой prompt, свои инструменты
Возвращает результат главному агенту
```
### 📝 Паттерн: Виртуальная файловая система (VFS)
```python
# Агент видит файловую систему как:
/AGENTS.md # памятью агента (бинд к agent_workspace/AGENTS.md)
/skills/... # bundled skills (бинд к src/agent/skills/)
~/project/... # рабочая папка (бинд к agent_workspace/)
```
Это позволяет агенту иметь консистентный вид файловой системы, хотя на самом деле это виртуальная проекция.
### 💾 Паттерн: Асинхронная загрузка MCP
```python
# 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': не удалось загрузить инструменты — ...
```
### 🧪 Тестирование компонентов
```python
# Импорт отдельного агента
from src.agent.agent import homework_direct_agent
# Запуск на тестовом задании
result = homework_direct_agent.invoke({
"messages": [HumanMessage("Выполни задание ...")]
})
```
### 🔍 Проверка инструментов
```python
# Какие инструменты есть у агента?
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 │
└─────────────────────┘
(Генерация кода и рассуждения)
```
Система автоматизирует весь цикл выполнения и сдачи заданий, обрабатывая ошибки, пересдачи и координируя несколько специализированных агентов через единый фреймворк.