Все записи
7 мин

Системный промпт на 16к символов и ноль денег на API: режу токены и включаю prompt caching

claude codeнейросетикартара

Если у вас приложение, которое на каждый запрос гонит в нейросеть здоровенный системный промпт, и денег на API при этом почти нет, то экономить надо двумя руками сразу, и я расскажу как. Коротко так: я взялся честно срезать системный промпт на 14-16 тысяч символов без потери качества, и не только за счёт самих формулировок, а на уровне всей системы, и параллельно включил prompt caching, чтобы повторяющуюся часть промпта провайдер не тарифицировал по полной каждый раз. И самое важное, что я понял по дороге: кеш можно включить, отрапортовать «сделано», а экономии при этом ноль, потому что прокси может молча его не отдавать, и проверять это надо руками по цифрам, а не на веру. Дальше по порядку, как было.

Почему я вообще полез резать промпт

Контекст тут простой и жёсткий до неприличия: на API нет денег. Не «давай оптимизируем когда-нибудь», а буквально баланс на нуле, и каждый лишний кусок текста, который улетает в нейросеть на каждом сообщении, это деньги, которых нет. У меня в Картаре системный промпт у AI-персонажа разросся до десятков тысяч символов, потому что туда годами доливали правила поведения, тон, ограничения, примеры, и всё это честно отрабатывало, но платить за это по новой при каждом запросе уже было нечем.

И тут есть ловушка, в которую легко свалиться: начать резать промпт тупо по живому, выкидывать абзацы, и в итоге получить персонажа, который стал отвечать хуже, потерял голос, начал нести что-то не своё. А голос у этого персонажа выстраивался долго и отдельно, я как раз перед этим его переделывал, так что просто взять и обкорнать промпт ножницами было нельзя. Поэтому задачу я и сформулировал именно так, дословно: «есть там где возможность 14-16к символов поуменьшить? только максимально честно и максимум без потери качества? какие вообще варианты? не только с промтами работать а в целом с системой?». Вот это «не только с промтами, а в целом с системой» и есть главная мысль.

Резать не текст, а архитектуру

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

То есть честное сокращение на 14-16 тысяч символов это не про то, чтобы найти красивые синонимы покороче, а про то, чтобы разобрать монолитную простыню на куски и спросить про каждый: а оно вообще должно ехать в нейросеть, и если да, то на каждом ли запросе? Вот это и есть «работать с системой, а не с промтами», и именно так режется по-честному, без просадки качества, потому что ты убираешь не смысл, а дублирование и балласт.

Главный приём от безденежья: тестировать не на проде, а на заглушке

Тут отдельно расскажу про вещь, до которой доходишь именно когда денег нет, и которая на самом деле полезна всем. Чтобы прогнать тесты на сокращённом промпте и проверить, что я ничего не сломал, надо много раз дёрнуть генерацию. А каждый такой прогон по живому провайдеру это деньги, которых, напомню, нет. Замкнутый круг: чтобы проверить экономию, надо потратить.

Разрывается он просто: тесты гоняем не по реальному провайдеру, а по локальному HTTP-стабу, маленькому фейковому серверу, который притворяется нейросетью и отдаёт заранее заготовленные ответы. Поднимается он на случайном свободном порту прямо в тесте, проверяет, что мой код правильно собирает запрос, правильно разбирает ответ, правильно считает токены, и при этом не жжёт ни копейки, потому что наружу не ходит вообще. И вот это, кстати, хороший принцип сам по себе, безотносительно денег: твои собственные тесты не должны зависеть от чужого платного API и его настроения, иначе ты привязываешь свою разработку к чужому кошельку и чужим тормозам.

Prompt caching, который включил, обрадовался и чуть не обжёгся

Вторая половина экономии это prompt caching. Идея у него такая: если большой кусок промпта от запроса к запросу не меняется, провайдер может его закешировать и считать повторные обращения к нему сильно дешевле, чем если бы ты слал этот текст заново как новый. На бумаге это прямая экономия на той самой статической простыне, которую я как раз и пытался ужать, но «на бумаге» тут ключевое слово: я уже отдельно ловил миф про «-90% на кэше», где обещанная экономия на деле вышла вдвое меньше, так что красивым цифрам из документации я давно не верю.

Сказал я про это коротко и по делу: «Ты блять это включи и сам затестируй». И вот тут начинается самое интересное и поучительное. Включить кеширование это полдела. Вопрос в том, доходит ли оно реально, а это видно только по цифрам usage, которые провайдер возвращает вместе с ответом: сколько токенов ушло на создание кеша и сколько прочиталось из кеша. Если эти поля приходят и в них есть цифры, кеш живой и экономия идёт. А если их нет или они по нулям, ты, считай, ничего не включил, просто думаешь, что включил.

И когда я в первый раз реально увидел, что кеш срабатывает и токены читаются из него, реакция была честная: «Ого. Нифига себе.» А следом сразу же отрезвляющий вопрос, который и спас всю затею: «Какие вообще варианты есть что кеш этот может работать не правильно?». Потому что я хожу в нейросеть не напрямую, а через шлюз-прокси, который проксирует разные модели через один ключ, и таких прокси-слоёв между тобой и моделью может быть несколько. И каждый из них может молча проглотить или не пробросить эти самые поля про кеш. Тогда у тебя будет ровно та ситуация, которой я и боялся: на словах кеширование включено, в коде флаг стоит, а денег он не экономит ни рубля, потому что до тебя цифры usage просто не доходят.

Поэтому проверка тут не «поставил галочку и забыл», а «дёрни реальный запрос дважды и посмотри глазами, пришли ли поля про создание и чтение кеша, и растёт ли чтение на повторном вызове». Только это и есть доказательство, что экономия настоящая, а не воображаемая. На чужой инфраструктуре, тем более через прокси, верить отчёту «caching: enabled» нельзя, надо смотреть фактический счёт по токенам — у меня уже было, как провайдер молча затупил и платные расклады превратились в мусор, так что молчаливым полям от прокси я больше не доверяю.

Заодно вынес мёртвый код

Раз уж я всё равно полез разбирать систему по кускам, грех было не зачистить и мёртвый код, который накопился по дороге. Там лежал legacy-модуль старого чата, который давно никем не дёргался, и таких точно дохлых строк набралось около полутысячи. Вынес я их отдельными chore-коммитами и прогнал через своего агента-чистильщика мёртвого кода, чтобы не выкосить случайно что-то живое, что просто выглядит ненужным.

Это, на первый взгляд, к экономии токенов отношения не имеет, но на самом деле имеет самое прямое. Когда ты режешь систему ради денег, тебе критически важно понимать, что в ней вообще работает, а что просто валяется балластом. Мёртвый код врёт тебе про устройство системы: ты смотришь и думаешь, что вот этот путь используется, тратишь время на его оптимизацию, а он давно труп. Так что чистка перед оптимизацией это не уборка для красоты, а способ не оптимизировать то, чего на самом деле уже нет.

Что в итоге и что унёс с собой

Если совсем по делу. Цель была срезать 14-16 тысяч символов системного промпта максимально честно, без потери качества, и режется это не переписыванием текста, а разбором всей системы: что статично, что нужно не всегда, что вообще можно унести в код. Тесты при нулевом балансе гонял по локальному HTTP-стабу, чтобы проверять экономию, не тратя на это деньги. Prompt caching включил, но настоящая работа была не во включении, а в проверке по живым цифрам usage, что кеш реально доходит через прокси, а не теряется по дороге. И попутно вынес около полутысячи строк мёртвого кода, чтобы оптимизировать живую систему, а не её призраков.

Весь этот код, само собой, писал не я руками, я не умею и не делаю вид, что умею, я оркестрировал Claude Code: ставил задачу, гонял по шагам, требовал именно «честно и без потери качества» и сам проверял цифры. Это вообще отдельная история, как я перестал доверять единственному внешнему API и начал считать каждый токен, я её подробно рассказывал в посте про то, как два проекта встали разом, потому что кончились деньги на OpenAI. Тут логика та же, только доведённая до следующего шага: мало переехать на дешёвый шлюз, надо ещё и не лить деньги на воздух внутри него.

И главный вывод, который я унёс: когда нейросети для бизнеса работают на копейках, экономия живёт не там, где красиво, а там, где скучно. Не в том, чтобы покрасивее переписать промпт, а в том, чтобы разобрать систему на части, гонять тесты бесплатно на заглушке и не верить на слово ни себе, ни прокси, пока не увидишь экономию в реальных цифрах. Вот и делайте выводы.