Не отдавайте модели SQL: форма tools надёжнее длинного промпта

Соло-разработчик, который собирает персональное приложение для данных о здоровье для одного аккаунта из белого списка, столкнулся с парадоксом: навык Claude Code удобен в терминале, но не открывается на iOS — а вопросы про сон и тренировки возникают «на кухне в шесть утра», не за столом. В эссе Matt Stratton он показывает, почему прямой SQL к «грязной» БД и длинный system prompt с перечислением ловушек — оба плохих интерфейса к LLM; надёжнее проектировать read-only tools так, чтобы ошибка была структурно невозможна.
Шесть ловушек, которых не видно в схеме
Автор ведёт личный проект на health data. В данных шесть семантических ловушек — каждая уже давала неверный ответ:
- Partial Day — «сегодня» ещё накапливает метрики; сравнение с завершённым днём выглядит как ложное ускорение.
- Gap ≠ zero — пропущенный день не равен нулевому потреблению.
- Дублирование тренировок Apple — Apple «тенево» копирует сессии Liftosaur; наивный запрос к workouts view примерно удваивает объём.
energy_balanceзавышает дефицит ~2,7× — за 30 полных дней средний intake 1 602 kcal против 3 216 burned (net −1 614/день) предсказывает −3,2 lb/неделю, тогда как вес снижался на 1,2 lb/неделю.- Одиночное взвешивание — шум — дневные колебания веса отражают воду, не тренд.
reps: 0— подход, который пытались выполнить и не смогли, а не пропущенный сет.
Дать модели SQL и read-only connection к маленькой БД дёшево — но она «наступает» на все шесть, потому что ловушки невидимы из схемы. Перечислить их в промпте проще; модель тогда обходит их в большинстве случаев. Автор называет этот исход хуже, чем стабильное попадание во все шесть через SQL.
Почему «правильно в 90% случаев» хуже стабильной ошибки
Контраст здесь не про точность ради точности — про доверие к агентному инструменту.
Инструмент, который всегда ошибается, ловят в первый день и выбрасывают. Инструмент, который прав в ~90+% случаев, начинают доверять — а редкая ошибка приходит в той же уверенной форме, что и верный ответ.
Пользователь не отличит редкий промах: он не знает ответа заранее. Для персональных данных это особенно коварно — модель звучит убедительно даже тогда, когда опирается на устаревшую строку в system prompt. Именно поэтому автор отвергает схему «SQL + схема + инструкции в промпте» как основной интерфейс к LLM.
Два режима: детерминированный coach и ask view с Claude
Вместо единого SQL-шлюза Stratton разделил продукт на два намеренно разных режима:
| Режим | Механизм | LLM |
|---|---|---|
| coach view | Чистые функции в lib/signals/, unit-тесты на фикстурах |
нет |
| ask view | Claude server-side, 13 read-only tools | да |
Мотивация веб-приложения — перенести сценарий «спросить БД и рассудить над ответом» с терминального coaching skill на мобильный контекст. В coach-ветке сигналы — protein adherence, deficit vs scale, weight trend, overreaching, stalled lifts, data freshness; статус unknown отделён от ok.
В ask-ветке write tool отсутствует вовсе — не «отключён», а не существует. Изменения программы остаются в Liftosaur, макро-цели — в MacroFactor. Промпт по-прежнему описывает все шесть ловушек — модели легче понять, почему окно обрезано. Но если промпт удалить завтра, ответы станут хуже без ошибок в этих шести конкретных способах.
Как форма tools закрывает ловушки
Структурные ограничения вместо памяти модели:
- оконные запросы заканчиваются
AND observed_on < today_local()— Partial Day недостижим; - пропуски остаются отсутствующими строками, без zero-fill;
- ни один tool не обращается к Apple workout view — дублирование недостижимо через API;
energy_balanceнельзя получить без «reality check» в том же payload.
Отдельный урок — metric_catalog. Список метрик читал каталог: 38 из 81; 43 «uncatalogued» включали 3 865 дней walking/running distance. Фикс: drive from observations_daily с LEFT JOIN на каталог → catalogued: false вместо исчезновения метрики. Lookup table — не индекс реальности, если ничто не принуждает к синхронизации.
На границе tool layer автор округляет float до двух знаков — примерно треть меньше payload у get_nutrition за 7 дней. Это инженерия интерфейса агента, а не украшение промпта.
Когда промпт стареет быстрее, чем кажется
Эмерджентное поведение тоже проверяли: из 9 «намеренно коварных» probe-вопросов 3 ответа содержали рассуждения вне промпта — предупреждение про Apple-оценку VO2max, разделение T1/T2 без exposed tier hint, проверка coverage перед ответом про сон.
Но ответ про сон выдал «~7% coverage» — неверно: за 30 дней ~70% после смены ношения часов в июле. Источник — устаревшая строка в lib/coach/context.ts и system prompt, написанная месяцами ранее. Тест assert.match(prompt, /7% coverage/) удерживал ошибку. Фикс: убрать хардкод, полагаться на list_metrics; новый тест запрещает проценты coverage в промпте.
Это аргумент поста в действии: инструкции в промпте и тесты, которые их «защищают», могут закрепить ложь. Форма tools и живые данные — надёжнее для агента, который отвечает уверенно.
Что вынести соло-разработчику с LLM-агентом
Девять probe-вопросов обошлись в 37 центов суммарно; end-to-end latency — 6,9–16,8 с, TTFT 1,7–10,1 с. Кэшированный префикс — 7 069 токенов system prompt плюс 13 tool definitions; cacheWrite = 0 после первого turn — контрольная метрика стабильности префикса.
Практический вывод для того, кто один строит агентный слой поверх своих данных:
- не отдавайте модели сырой SQL как единственный путь к «грязным» данным с невидимой семантикой;
- не полагайтесь на длинный промпт как на страховку — он даёт ложное доверие;
- проектируйте read-only tools так, чтобы типовая ошибка была недостижима, а промпт оставался объяснением, а не ограждением;
- следите, чтобы unit-тесты не фиксировали устаревшие факты в system prompt.
Автор честно отмечает пробелы: ask UI ни разу не рендерился в браузере, путь unknown в сигналах не проверен на live data. Cross-domain корреляции (сон → пропуски лифтов, deficit → recovery) не shipped — событий в данных пока мало. Это зрелая позиция: агентный слой не обязан отвечать на всё, пока в данных нет основания.
Источники
- Matt Stratton, «Don't Give the Model SQL», Dev.to, 17 августа 2026 — https://dev.to/mattstratton/dont-give-the-model-sql-5h32 (дата доступа: 2026-08-17 UTC)