Урок 4. OpenCode — AI-напарник по обучению

🎯 Цели урока

📖 Теория: раздел «Учиться вместе с AI-агентом». Подробно coding-агенты — в модуле M21.

Что такое OpenCode

OpenCode — открытый coding-агент для терминала. Он читает файлы проекта, ищет по коду, запускает команды и вносит правки, а «думает» с помощью любой LLM: облачной, вашего сервера или локальной Ollama. Внутри — тот же цикл, который мы напишем сами в M14: модель получает описание инструментов (tool calling, как в уроке 3), просит вызвать инструмент, получает результат и решает, что делать дальше.

Установка: brew install sst/tap/opencode или curl -fsSL https://opencode.ai/install | bash.

Конфигурация проекта

В корне курса лежит opencode.json. Он описывает два провайдера моделей, оба через OpenAI-совместимый API. Адреса и имена моделей берутся из переменных окружения — те же, что в .env, поэтому в репозитории нет ни одного секрета.

import json
import re

from mlcourse import repo_root

root = repo_root()
config = json.loads((root / "opencode.json").read_text(encoding="utf-8"))
print(json.dumps(config, ensure_ascii=False, indent=2))
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "course": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Модель курса (LLM_BASE_URL из .env)",
      "options": {
        "baseURL": "{env:LLM_BASE_URL}",
        "apiKey": "{env:LLM_API_KEY}"
      },
      "models": {
        "{env:LLM_MODEL}": {
          "name": "Основная модель курса"
        }
      }
    },
    "ollama": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Ollama (локально)",
      "options": {
        "baseURL": "http://localhost:11434/v1"
      },
      "models": {
        "{env:LLM_LOCAL_MODEL}": {
          "name": "Локальная модель курса"
        }
      }
    }
  },
  "model": "course/{env:LLM_MODEL}"
}
  • npm: "@ai-sdk/openai-compatible" — адаптер для любого OpenAI-совместимого сервера;
  • {env:LLM_BASE_URL} — OpenCode подставит значение переменной окружения при запуске;
  • model — модель по умолчанию в формате провайдер/модель.

OpenCode не читает .env сам, поэтому запускать его удобно через make opencode: команда загружает .env в окружение и стартует OpenCode в корне курса.

ПредупреждениеКонтекст

Системный промпт и описания инструментов OpenCode занимают 10–12 тысяч токенов. Модели нужно окно минимум 32K: с 16K агент упирается в ошибку exceeds the available context size. Для Ollama: OLLAMA_CONTEXT_LENGTH=32768 (если хватает памяти), для своего сервера — флаг -c.

Агенты курса

Агент в OpenCode — это системный промпт, набор разрешений и настройки модели. Встроенные агенты — build (делает) и plan (только планирует). Курс добавляет своих, они лежат в .opencode/agent/:

def frontmatter(path):
    """Разобрать YAML-шапку markdown-файла (упрощённо: только поля верхнего уровня)."""
    text = path.read_text(encoding="utf-8")
    head = re.match(r"^---\n(.*?)\n---\n", text, re.S).group(1)
    fields = dict(re.findall(r"^(\w+):\s*(.+)$", head, re.M))
    return fields, text[len(head) + 8 :].strip()


for path in sorted((root / ".opencode" / "agent").glob("*.md")):
    fields, prompt = frontmatter(path)
    print(f"🤖 {path.stem} ({fields.get('mode')}) — {fields['description']}")
    print(f"   промпт: {len(prompt)} символов; начало: {prompt.splitlines()[0][:90]}\n")
🤖 reviewer (subagent) — Ревьюер проектов курса — проверяет код по чек-листу модуля, ничего не меняет
   промпт: 647 символов; начало: Ты — ревьюер проектов курса «AI-инженер». Проверь код студента и верни отчёт на русском:

🤖 tutor (primary) — Репетитор курса — объясняет и подсказывает, но не пишет решения упражнений
   промпт: 1036 символов; начало: Ты — репетитор курса «AI-инженер: от устройства LLM до мультиагентных систем».

tutor — главный агент для учёбы. Ему запрещено редактировать файлы (edit: deny), разрешено запускать тесты, а системный промпт требует объяснять и подсказывать, но не решать упражнения. reviewer — субагент для ревью проектов: основной агент может позвать его на проверку.

Посмотрим на разрешения репетитора целиком:

text = (root / ".opencode" / "agent" / "tutor.md").read_text(encoding="utf-8")
print(text.split("---")[1].strip())
description: Репетитор курса — объясняет и подсказывает, но не пишет решения упражнений
mode: primary
temperature: 0.3
permission:
  edit: deny
  webfetch: allow
  bash:
    "make test*": allow
    "make check*": allow
    "uv run pytest*": allow
    "*": ask

Разрешения — это граница доверия. Агент с правом на bash: "*": allow может выполнить любую команду, в том числе удалить файлы. Для учебного агента безопаснее белый список: тесты можно, остальное — с подтверждением (ask). К безопасности агентов вернёмся в M18 и M23.

Команды курса

Команда — шаблон промпта с параметрами. Лежат в .opencode/command/, вызываются в OpenCode через /:

for path in sorted((root / ".opencode" / "command").glob("*.md")):
    fields, template = frontmatter(path)
    print(f"/{path.stem} — {fields['description']} (агент: {fields.get('agent', 'текущий')})")
/check — Прогнать тесты упражнений модуля и разобрать ошибки (пример — /check 00) (агент: tutor)
/explain — Объяснить тему курса простыми словами (пример — /explain KV-кэш) (агент: tutor)

Команда /check 00 сначала выполняет make test M=00 (конструкция !`команда` вставляет вывод в промпт), а затем просит репетитора разобрать упавшие тесты — без готовых решений.

print((root / ".opencode" / "command" / "check.md").read_text(encoding="utf-8"))
---
description: Прогнать тесты упражнений модуля и разобрать ошибки (пример — /check 00)
agent: tutor
---

Результаты тестов упражнений модуля M$1:

!`make test M=$1 2>&1 | tail -80`

Разбери каждый упавший тест: что он проверяет, что пошло не так и в каком разделе теории модуля
об этом прочитать. Не пиши готовый код решения — дай подсказку и наводящий вопрос.

Как с этим работать

make opencode                 # запуск с моделями из .env

Внутри OpenCode:

Действие Как
переключить агента клавиша Tab (build → plan → tutor …)
разобрать тесты модуля /check 00
объяснить тему /explain KV-кэш
сменить модель /models
новая сессия /new

Без интерфейса, одной командой (удобно для скриптов и CI — подробнее в M21):

opencode run --agent tutor "Почему скорость генерации упирается в память?"

Правила хорошего ученика

  1. Сначала сам. Агент — второй шаг, а не первый.
  2. Спрашивайте «почему», а не «сделай». «Почему падает тест?» полезнее, чем «исправь».
  3. Проверяйте. Модели ошибаются уверенно, а локальная 8B-модель — особенно. Сверяйтесь с теорией и тестами.
  4. Записывайте инсайты. Понятое своими словами запоминается лучше, чем прочитанное.

✅ Итоги