Почему ваш ИИ-агент падает на реальных сайтах
Ваш агент работает на страницах, на которых вы его проверяли. Потом вы направляете его в открытый веб — и ответы становятся расплывчатыми, или уверенно неверными, или он сообщает, что страница пуста, хотя вы видите содержимое в браузере.
Первый порыв — править промпт. Обычно с промптом всё в порядке. Отказ произошёл до того, как модель что-либо увидела, — в слое, который скачивает URL и превращает его в текст.
Мы измерили этот слой на реальных страницах. Вот пять способов, которыми он ломается, и как каждый из них выглядит.
1. На странице нет текста, пока не выполнится JavaScript
atlassian.com/software/jira/pricing, скачанная обычным способом, — это 1,37 МБ HTML, дающие один токен читаемого содержимого. Не абзац: один токен, перевод строки.
Цены там есть. Они внутри <script>, в виде состояния приложения, и ждут, когда браузер построит из них страницу.
Скачайте тот же URL headless-браузером — и получится 3 048 токенов, начинающихся с:
## **Transparent pricing for every team.**
Тот же URL. Тот же день. Ноль содержимого или вся страница целиком — исключительно в зависимости от того, выполнили вы JavaScript или нет.
Это отказ, который выглядит как проблема модели, но ею не является. Ваш агент сказал «на странице не упоминаются цены», потому что в выданных ему байтах это было правдой.
2. Но рендеринг — не то решение, которого вам хотелось бы
Очевидный ответ — рендерить всё. Попробуйте на stripe.com/docs:
FetchError: Rendered fetch failed: Navigation timeout of 25000 ms exceeded
Страница, которую простая загрузка возвращает меньше чем за секунду, так и не догрузилась в headless-браузере за 25 секунд. Нерендеренная версия при всех своих недостатках хотя бы вернулась.
Рендеринг стоит вам процесса браузера на страницу, от нескольких сотен миллисекунд до нескольких секунд задержки и нового класса отказов: таймауты, заблокированные ресурсы, страницы, которые никогда не бросают load. Если ваш агент рендерит по умолчанию, на части веба он медленнее и менее надёжен, а не более.
Рабочая форма такая: сначала дешёвая загрузка, проверить, получили ли вы вообще хоть что-то, и переходить к рендерингу только для страниц, вернувшихся пустыми. Именно эту проверку обычно и пропускают.
3. Вы платите за разметку, которой не пользуетесь
По двенадцати известным страницам, которые мы измерили, медиана того, что доходит от сервера до модели, меньше 1%. shopify.com/pricing отправляет 410 515 токенов и выдаёт 2 479.
Если ваш агент запихивает сырой HTML в окно контекста, вы оплачиваете обёртки <div> и встроенные SVG по цене входных токенов и вытесняете страницы, которые тоже хотели прочитать. Извлекайте перед отправкой. А лучше — спросите, отдаст ли сайт Markdown напрямую. Некоторые уже отдают: из двенадцати проверенных страниц пять отдавали то или иное представление в Markdown, одна из них — наша.
4. Извлечение оставляет не ту часть
Извлечение — эвристика. Оно ищет статью и отбрасывает мебель, а на страницах, не похожих на статью, угадывает плохо.
stripe.com/docs сокращается до 1 057 символов, начинающихся так:
$ stripe payment_intents create --amount 1099 --currency "usd"
Это настоящий фрагмент страницы. Это и пример из терминала вместо фразы о том, чем занимается Stripe. Агент, которому дали только это, вынужден выводить продукт из куска кода.
Отсюда: никогда не считайте, что удачное извлечение — это полезное извлечение. Дешёвая проверка: если в извлечённом нет текста, похожего на предложения, или он короче нескольких сотен символов на странице, приславшей полмегабайта, считайте это неудачным чтением и переходите к следующему шагу, а не передавайте модели как факт.
5. Один и тот же URL возвращает разные страницы
Простая загрузка shopify.com/pricing вернула английский. Рендеринг того же URL с той же машины вернул испанский: Pago mensual32 € EUR/mes там, где простая загрузка говорила Pay monthly€32 EUR/mo.
Здесь ничего не сломано. Headless-браузер отправил другие сигналы Accept-Language и геолокации, чем наша простая загрузка, и получил другое, корректное представление. Но если вы кэшируете только по URL или сравниваете сегодняшнее чтение со вчерашним, эта разница будет выглядеть как смена цен на сайте.
Отправляйте явный Accept-Language. Стройте ключ кэша по тому, что вы действительно запросили.
Отказ, которого не было
Мы ожидали блокировок. Мы прозондировали все двенадцать страниц дважды — как браузер и как OAI-SearchBot — и сравнили коды ответа через Cloudflare, Vercel и CloudFront.
Ни одна не отнеслась к боту иначе. Ни одна.
Это не значит, что агентов никто не блокирует: блокируют многие, и robots.txt по-прежнему нужно читать и уважать. Это значит, что если ваш агент падает на широкой выборке веба, блокировка вряд ли тому причиной. Пустая комната встречается куда чаще запертой двери.
Слой чтения, который переживает встречу с вебом
1. Сначала просить Markdown → Accept: text/markdown или /страница.md
2. Простая загрузка → дёшево, без JS
3. Проверить, что пришло → похоже на предложения? достаточно ли длинно для присланных байтов?
4. Иначе — эскалация → рендеринг, с таймаутом и бюджетом
5. Честно сдаться → «я не смог прочитать эту страницу»
Шаг 5 важнее, чем кажется. Агента, который сообщает о нечитаемой странице, можно отладить. Агент, который передаёт модели перевод строки и позволяет ей импровизировать, — это тот самый, который выдумает ваши цены.
Коротко
- Большинство отказов агента — отказы чтения, а не рассуждения.
- Пусто-без-JavaScript — самый частый из них, и рендеринг его чинит: за плату и не всегда.
- Проверяйте каждое извлечение, прежде чем ему доверять.
- Внимание достаётся блокировкам. Урон наносит пустота.
Проверить, как читается страница · Что на самом деле видит агент · Отдавать агентам Markdown