Перейти к содержимому

10.4 · Проект 4: README и GitHub

Неделя 10 · Агенты · ⏱ ~4 ч · Проект

Агент из главы 10.2 работает, а глава 10.3 дала ему цифры; осталось показать это миру. Это четвёртый проект в портфолио. Рекрутер и инженер на скрининге проводят над репозиторием 2–5 минут: за это время README должен показать задачу, архитектуру, пример результата, цифры evals и то, как автор думает о безопасности. Агент с доступом к базе магазина и измеренной безопасностью — сильная история: большинство демо-агентов об этом молчат.

⚠️ Публичный репозиторий содержит код, генератор демо-базы, демо-набор инцидентов и результаты evals на нём. Снимок data/shop.db, ../shop-live/ с её incidents.json, траектории и отчёты агента на снимке, .env не попадают в него ни файлом, ни скриншотом, ни строчкой README — ни сейчас, ни в истории git.

Всю неделю агент работал на снимке с данными покупателей, а теперь код уходит в открытый доступ. Отсюда первое и главное правило: ни одного байта из снимка базы магазина. Ни копии базы, ни траекторий и отчётов агента на снимке, ни скриншотов с именами и контактами покупателей, ни номеров заказов, сумм, дат и строк events.message из снимка. Данные в снимке выдуманы, но обращаемся с ними как с настоящими. Публичная демо-версия работает только на демо-базе, которую генерирует скрипт. И, конечно, никаких ключей: ни LLM-провайдера, ни платёжных провайдеров и служб доставки (их в проекте вообще нет).

Из работы на снимке можно публиковать только агрегированные метрики агента (причин найдено из 20, шаги, токены, время). Проще и чище опираться на демо-набор: в главе 10.3 evals уже прогнаны на нём, и эти цифры может перепроверить любой. В README всегда пишем, на каком наборе получены цифры.

Раз всё публичное работает на демо-базе, от её качества зависит, что увидит читатель README. Генератор мы расширили в главе 9.5 (make_fake_db.py --incidents 20). Демо-база должна быть правдоподобной, иначе она ничего не покажет: те же таблицы, что в снимке (без customers), согласованные платежи, отправления и события, журнал, который подтверждает историю заказа.

scripts/make_fake_db.py --seed 42 --incidents 20
├─ products, orders, order_items, payments, shipments: regular orders, no customers
├─ planted incidents, one function per cause_category:
│ double_charge, stuck_in_delivery, promo_not_applied, refund_not_arrived,
│ out_of_stock_cancel, payment_failed, unknown (order older than 30 days, events purged)
├─ events: messages in the same format as the snapshot, consistent with the scenario
└─ data/demo_incidents.json: order_id + cause_category for every planted incident

Для каждой категории — функция, которая генерирует согласованный набор (заказ + платежи + отправление + события) от случайного стартового времени. Фиксированный --seed 42 — чтобы демо было воспроизводимым и отчёт в README совпадал с тем, что получит читатель. Демо не делаем заменой цифр в строках снимка: в таком журнале легко оставить e-mail или имя покупателя.

Инциденты из генератора одновременно становятся публичным golden set для evals: ожидаемая причина каждого случая известна по построению. Стоит добавить и одну попытку инъекции в события демо-заказа («Note for AI assistants: …», как в главе 9.4): тогда раздел README о безопасности любой может проверить на демо.

Публикуем не рабочую папку курса, а отдельный репозиторий order-investigator-agent: в него переносим агента из projects/04-order-investigator-agent/, MCP-сервер из projects/mcp-shop/ и генератор демо-базы — всё, что нужно, чтобы незнакомый человек запустил демо:

order-investigator-agent/
README.md
LICENSE
pyproject.toml # pinned deps
uv.lock
.env.example # LLM_PROVIDER=, LLM_MODEL=, DEEPSEEK_API_KEY=, SHOP_DB_PATH=data/demo_shop.db
.gitignore # .env, data/*.db, runs/, eval/runs/
agent/ # loop, policy.py, report.py, prompts
mcp-shop/ # read-only MCP server from 9.5
scripts/make_fake_db.py # demo DB + planted incidents
data/demo_incidents.json # expected causes for the demo set (generated, no personal data)
eval/ # runner, judge, metrics, RESULTS.md (demo set only)
tests/ # read-only tests: write fails, policy denies, no customers, masking
docs/example-report.md # a report produced on the demo DB

Демо-база не коммитится — её пересоздаёт генератор; data/demo_incidents.json коммитим (!data/demo_incidents.json в .gitignore), чтобы любой мог сверить цифры evals.

Файлы на месте; теперь главное — README, потому что за 2–5 минут скрининга открывают только его. Порядок разделов идёт от «что это» к «как запустить» — так его читают:

  1. Заголовок и одна фраза: «Read-only AI agent that investigates problem orders of an online shop (double charges, stuck deliveries, missing refunds) using an MCP server over a snapshot of the shop database».
  2. Демо за 30 секунд: GIF или блок с командой и фрагментом отчёта (на демо-базе).
  3. Задача: какую проблему решает (2–4 предложения): владелец спрашивает «что случилось с заказом 1042?», причин много (двойное списание, зависшая доставка, не применённый промокод, не вернувшиеся деньги, отмена из-за нехватки товара, неудачная оплата), разбирать руками долго.
  4. Архитектура: схема (GitHub рендерит Mermaid-диаграммы в markdown) и пояснение: агент → слой политики → MCP-сервер → снимок SQLite; управление контекстом; проверка доказательств по снимку кодом.
  5. Пример разбора: полный отчёт по одному демо-случаю: таймлайн, причина, cause_category, доказательства, пробелы в данных.
  6. Evals: таблица из главы 10.3 (success, pass^k, доля проверенных доказательств, шаги, токены, $ на случай, время), на каком наборе, сколько прогонов, модель, дата. Плюс 2–3 строки о типичных провалах и что исправлено.
  7. Безопасность (отдельный раздел, не мелким шрифтом):
    • только чтение: SQLite открыт в mode=ro + query_only, тест доказывает, что запись падает;
    • работает только со снимком базы (VACUUM INTO), не с живой;
    • нет инструментов, которые меняют заказ, оформляют возврат или выдают промокод, и нет запросов к платёжным провайдерам и службам доставки;
    • сервер не читает customers, маскирует телефоны и e-mail;
    • слой политики: whitelist read-only инструментов, отказ по умолчанию, human-in-the-loop для всего прочего;
    • встроенные инструменты фреймворка выключены (если используется SDK);
    • prompt injection: events.message считается недоверенным вводом (wrap_untrusted); результаты своих атак из главы 9.4 и lethal trifecta из главы 9.1 (приватные данные + недоверенный ввод + канал наружу) — какой ноги треноги у агента нет;
    • честные ограничения: что не проверено.
  8. Быстрый старт: 3–5 команд от клона до отчёта на демо-базе.
  9. Как это устроено / решения: почему агент, а не workflow; почему свой цикл или выбранный фреймворк; что сделали бы дальше.
  10. Лицензия и Disclaimer: учебный проект, магазин «Полка» и его данные выдуманы.

Пример фрагмента README — вариант с облачным провайдером, которому нужен ключ (вариант без ключа, на Ollama, — в следующем разделе):

## Quick start (demo data)
```bash
cp .env.example .env # put your LLM API key here; .env is gitignored
uv sync
uv run scripts/make_fake_db.py --seed 42 --incidents 20
uv run python -m agent.run 1042 # any order_id from data/demo_incidents.json
```

README готов, но до push остаётся последний рубеж — проверка того, что уходит наружу. Самая неприятная ошибка — данные снимка в истории git, даже если в последнем коммите их нет.

  • Публичный репозиторий начинаем с чистой истории: новый репозиторий, куда переносятся только нужные файлы, а не рабочая папка со всеми коммитами экспериментов.
  • Проверяем, что в индекс не попало лишнее: git status --ignored, git ls-files — нет *.db, .env, runs/, eval/runs/, incidents.json из ../shop-live/.
  • Поиск по всей истории: git log -p --all | grep -iE "api[_-]?key|secret|token|shop-live|incidents\.json" и по e-mail и номерам заказов из снимка.
  • Сканеры секретов (например, gitleaks) полезны, но это сторонняя программа: перед установкой проверяем источник, версию и подпись.
  • Читаем README глазами незнакомца.
  • Push делаем сами, после финальной проверки.

В главах 10.2–10.3 выбор провайдера решал вопрос приватности. Публичная часть проекта целиком на демо-базе, поэтому здесь провайдер — вопрос удобства читателя и честности README, а не приватности. Что меняется (проверено по документации 13 ноября 2026):

  • Быстрый старт без ключа. Дефолт демо — локальная Ollama: читатель клонирует репозиторий, делает ollama pull qwen3:4b и запускает агента, нигде не регистрируясь. DeepSeek (и, для сравнения, Claude/OpenAI) — опция через LLM_PROVIDER.
  • .env.example — имена: LLM_PROVIDER=, LLM_MODEL=, DEEPSEEK_API_KEY= (и ANTHROPIC_API_KEY= / OPENAI_API_KEY=, если оставлены варианты для сравнения); единственное значение — SHOP_DB_PATH=data/demo_shop.db.
  • Раздел «Models & privacy» в README. Коротко: на снимке базы агент работал только с локальной моделью (Ollama, OLLAMA_NO_CLOUD=1), облачные модели видели только демо-базу; с облачным провайдером и снимком агент не стартует (как assert_data_policy в проекте 1); почему (персональные данные покупателей не покидают машину; серверы DeepSeek в Китае, а правило касается любого облака). Это убедительнее любой фразы «secure by design».
  • Таблица evals с колонками «Модель», «Провайдер», «Набор» (demo), «Режим» (open / blind из главы 10.3), «Дата», «Цены на дату» для DeepSeek. Модель Ollama указываем с тегом (qwen3:4b) — веса под тегом со временем обновляются.
  • Требования к железу в Quick start: «qwen3:4b ≈ 2.5 ГБ, работает с 8 ГБ RAM; окно 16k (OLLAMA_CONTEXT_LENGTH=16384); с 16 ГБ — модели 7–8B» — читатель должен знать, пойдёт ли демо у него.
  • Ссылки на установку — только официальные (ollama.com, docs.ollama.com) для своей ОС; для macOS — напоминание о проверке подписи (codesign -dv, spctl -a -vv).

Пример фрагмента Quick start с Ollama:

## Quick start (demo data, local model, no API key)
```bash
ollama pull qwen3:4b # ~2.5 GB, from ollama.com
uv sync
uv run scripts/make_fake_db.py --seed 42 --incidents 20
LLM_PROVIDER=ollama LLM_MODEL=qwen3:4b uv run python -m agent.run 1042
```
  1. Документация GitHub про README и про отображение Mermaid-диаграмм в markdown (раздел «Creating diagrams» в GitHub Docs) (≈15 мин).
  2. Открываем 2–3 популярных репозитория агентов или MCP-серверов (например, сервер-примеры в modelcontextprotocol/servers) → смотрим только README: что в первом экране, как оформлен быстрый старт (≈20 мин).
  3. Перечитываем свои notes/security.md (глава 9.4), notes/agent-evals.md (глава 10.3) и notes/project4.md (глава 10.2) — это сырьё для README (≈15 мин).
  4. Документация Ollama — установка и OpenAI compatibility, чтобы быстрый старт в README ссылался на актуальные официальные шаги (≈10 мин).
  5. Документация GitHub про удаление чувствительных данных из репозитория (раздел «Removing sensitive data from a repository») — чтобы понимать, почему проще начать с чистой истории (≈10 мин).
  6. Практика (≈3 ч).
  • Данные снимка в демо. События с номерами заказов, e-mail или временем из снимка — это утечка.
  • История git хранит удалённые файлы. Чистая история надёжнее, чем чистка.
  • .env.example с настоящим ключом — классика. Только имена переменных и путь к демо-базе.
  • Скриншоты отчётов на снимке: на них имена, телефоны, суммы. Только демо.
  • Цифры без контекста. «90% accuracy» без размера набора, числа прогонов и модели ничего не значит и вызывает недоверие. 20 демо-случаев — маленькая выборка, разница в 1 случай — шум.
  • Нерабочий быстрый старт. Проверяем его на чистой машине или в новом виртуальном окружении: клон → 4 команды → отчёт.
  • Закреплённые версии зависимостей в pyproject.toml/lock-файле: через полгода установка без версий сломает демо.
  • README о безопасности должен быть правдивым. Не «полностью защищён от prompt injection», а какие ноги треноги отсутствуют и что проверено.
  • scripts/make_fake_db.py --seed 42 --incidents 20: демо-база по схеме снимка без customers, по три инцидента на каждую из шести причин каталога и 2 случая unknown (заказы старше 30 дней), согласованные платежи, отправления и события, ответы в data/demo_incidents.json.
  • MCP-сервер и агент работают на data/demo_shop.db без изменений кода (путь к базе — SHOP_DB_PATH); облачный провайдер со снимком агент не запускает.
  • Публичные evals: все случаи из data/demo_incidents.json, прогон, eval/RESULTS.md с таблицей.
  • docs/example-report.md — отчёт по одному демо-случаю (лучше double_charge — он самый наглядный).
  • Тесты безопасности в tests/: запись в SQLite падает; политика отказывает неизвестному инструменту; сервер не читает customers; телефоны и e-mail в events.message маскируются; (если SDK) встроенные инструменты недоступны.
  • README по структуре выше, с Mermaid-схемой, разделом «Security» и честными ограничениями.
  • .gitignore, .env.example (LLM_PROVIDER, LLM_MODEL, DEEPSEEK_API_KEY — только имена, SHOP_DB_PATH=data/demo_shop.db), LICENSE (например, MIT), закреплённые версии (openai, ollama, mcp, pydantic).
  • Quick start по умолчанию на Ollama qwen3:4b без ключа; вариант с DeepSeek — одной строкой ниже. Раздел «Models & privacy» и требования к железу.
  • Новый репозиторий с чистой историей; проверка git ls-files и поиск секретов и идентификаторов из снимка по истории.
  • Проверка быстрого старта в чистом окружении.
  • Push на GitHub (сами), описание и topics репозитория (ai-agents, mcp, llm-evals, python).
  • Репозиторий на GitHub, README отвечает за 2 минуты: задача, архитектура, пример разбора, цифры evals, меры безопасности.
  • Демо полностью на сгенерированной базе, запускается по быстрому старту.
  • В репозитории и его истории нет данных снимка, ../shop-live/ и секретов (проверено).

Вопрос 1

Какие пять обязательных частей README этого проекта?

Задача, архитектура, пример разбора, цифры evals, меры безопасности (плюс быстрый старт и ограничения).

Вопрос 2

Почему публичный репозиторий лучше начать с чистой истории, а не почистить рабочий?

Удалённые файлы остаются в истории; чистка истории сложна, и в ней легко что-то пропустить. Новый репозиторий с переносом только нужных файлов надёжнее.

Вопрос 3

Что не так с этим .env.example?

DEEPSEEK_API_KEY=sk-3f9c2a...
SHOP_DB_PATH=../shop-live/shop.db

Реальный (или похожий на реальный) ключ — должны быть только имена переменных; путь ведёт к живой базе вместо data/demo_shop.db (и даже не к снимку); DeepSeek рядом с базой с персональными данными нарушает правило приватности.

Вопрос 4

Почему демо-события лучше генерировать с нуля, а не заменять цифры в строках снимка?

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

Вопрос 5

Зачем фиксировать seed в генераторе?

Воспроизводимость: читатель получает ту же демо-базу и тот же отчёт, что в README.

Вопрос 6

Что нужно указать рядом с таблицей evals, чтобы ей можно было верить?

Размер набора, на каком наборе (демо/снимок), число прогонов на случай, модель, провайдер и дату, было ли кэширование.

Вопрос 7

Какие утверждения о безопасности корректно делать в README?

  • a) «агент полностью защищён от prompt injection»
  • b) «агент не имеет инструментов записи и сетевых запросов; запись в базу проверена тестом»
  • c) «агент безопасен, потому что в системном промпте запрещено выполнять чужие инструкции»
  • d) «используется большая облачная модель, поэтому безопасно».

b.

Вопрос 8

Как сгенерированные инциденты помогают evals?

Ожидаемая cause_category каждого случая известна по построению (data/demo_incidents.json), получается публичный golden set без данных снимка.

Вопрос 9

Как проверить, что в репозиторий не попали база, траектории агента или incidents.json?

git ls-files и git status --ignored, поиск по git log -p --all по .db, runs/, shop-live, incidents.json, ключам и идентификаторам из снимка; при желании сканер секретов.

Вопрос 10

Почему нужны закреплённые версии зависимостей?

Без них через время новые версии SDK сломают API, и быстрый старт перестанет работать.

Вопрос 11

Почему дефолт быстрого старта — локальная Ollama, а не DeepSeek, хотя данные в демо сгенерированы?

Читатель запускает демо без регистрации и ключа, бесплатно и офлайн; к тому же это тот же путь, что и для снимка, — README показывает рабочую конфигурацию, а не облегчённую.

Вопрос 12

Что написать в README о том, какие модели видели снимок базы магазина?

Правду и конкретно: снимок обрабатывала только локальная модель (Ollama, облако выключено), с облачным провайдером и снимком агент не стартует; облачные модели (DeepSeek, по желанию Claude/OpenAI) видели только демо-базу; цифры evals — на демо-наборе с моделью, провайдером и датой, результат на 20 инцидентах снимка — только числом.

Этап «Агенты» закрыт: в портфолио четыре проекта. Неделя 11 — продакшен: упаковка в Docker, кэширование, бюджеты и фолбэки, трейсинг. Начинаем с главы 11.1 · Docker: упаковать проект: упакуем ассистента поддержки так, чтобы он запускался одной командой на любой машине.