Недавно в популярном Facebook-посте: «GPT работает всё хуже. Просишь пересчитать формулу на 600 грамм, он бодро выдаёт две по 300. Пора, видимо, валить».
Проблема знакомая каждому, кто пытался использовать LLM для расчётов. Но это не деградация конкретной модели. Это фундаментальное ограничение архитектуры. И у него есть решение.
Почему LLM не умеют считать
Transformer предсказывает следующий токен на основе вероятностного распределения. Когда вы просите модель умножить 18 на 38.76, она не вызывает калькулятор. Она генерирует последовательность символов, которая «похожа» на правильный ответ.
Иногда совпадает. Иногда нет. И вы никогда не знаете заранее, когда модель решит, что результат «примерно 680» вместо 697.68.
Это не баг. Это следствие архитектуры. Модель обучена на текстах, а не на арифметических операциях. Она «видела» миллионы примеров умножения в обучающих данных и научилась воспроизводить паттерн. Но воспроизведение паттерна и выполнение операции — разные вещи.
Конкретный пример. Просим модель посчитать коммунальные платежи:
Модель «вспоминает» примерные тарифы из обучающих данных (часто устаревшие или из другого региона)
Выполняет умножение путём предсказания токенов
Красиво оформляет результат
Вы получаете число, которое выглядит правдоподобно, но может быть неверным на 10-30%
Проверить сложно. Вы ведь и обратились к ИИ потому что не хотели считать вручную.
Идея: пусть модель программирует, а не считает
Решение оказалось простым. LLM отлично генерирует код. Код отлично выполняет арифметику. Значит, нужно убрать модель из цепочки вычислений и оставить её там, где она сильна: в понимании задачи и генерации программы.

Когда Python выполняет 18 * 38.76, результат всегда 697.68. Не «примерно 700». Не «около 680». Точное число. Каждый раз.
Модель не считает. Модель программирует. А программа считает.

Реализация
Мы применили этот подход в боте для бухгалтерских расчётов. Вот как выглядит процесс:
Пользователь отправляет задачу в мессенджер: «Томск, ХВ 320, ГВ 229, эл 7422, пред: ХВ 302, ГВ 222, эл 7133»
LLM получает системный промпт с контекстом задачи и актуальными тарифами региона
Модель генерирует Python-скрипт: вычисление расхода, умножение на тарифы, формирование таблицы
Скрипт выполняется в изолированной Docker-песочнице
Результат: текстовый ответ с разбивкой по услугам + Excel-файл
Пример сгенерированного кода (упрощённый, в качестве примера):
# Показания счётчиков cold_current, cold_prev = 320, 302 hot_current, hot_prev = 229, 222 elec_current, elec_prev = 7422, 7133 # Расход cold_usage = cold_current - cold_prev # 18 м³ hot_usage = hot_current - hot_prev # 7 м³ elec_usage = elec_current - elec_prev # 289 кВт·ч # Тарифы Томск 2025 (из конфига, не из модели) tariffs = { 'cold_water': 38.76, 'hot_water': 142.63, 'electricity': 4.94, 'drainage': 27.04, } # Расчёт cold_cost = cold_usage * tariffs['cold_water'] # 697.68 hot_cost = hot_usage * tariffs['hot_water'] # 998.41 elec_cost = elec_usage * tariffs['electricity'] # 1427.66 drain_cost = (cold_usage + hot_usage) * tariffs['drainage'] # 676.00 total = cold_cost + hot_cost + elec_cost + drain_cost # 3799.75 # Формирование Excel import openpyxl wb = openpyxl.Workbook() ws = wb.active ws.append(['Услуга', 'Расход', 'Тариф, ₽', 'Сумма, ₽']) ws.append(['ХВС', f'{cold_usage} м³', tariffs['cold_water'], cold_cost]) ws.append(['ГВС', f'{hot_usage} м³', tariffs['hot_water'], hot_cost]) ws.append(['Электричество', f'{elec_usage} кВт·ч', tariffs['electricity'], elec_cost]) ws.append(['Водоотведение', f'{cold_usage + hot_usage} м³', tariffs['drainage'], drain_cost]) ws.append(['ИТОГО', '', '', total]) wb.save('communal.xlsx')
Ключевой момент: тарифы инжектируются в контекст из конфига, а не берутся из «памяти» модели. Когда тариф меняется, обновляется одна строка в конфиге. Модель не нужно переобучать или даже перезагружать.

Выбор моделей
Используем Qwen и DeepSeek. Выбор прагматический:
Qwen (Alibaba): бесплатный tier на Alibaba Cloud, достаточное качество генерации кода, стабильный API. Для задач типа «посчитай коммуналку» хватает с запасом.
DeepSeek V3: платный, но с кэшированием промптов даёт хорошую маржу. Используем для более сложных задач: анализ смет, многостраничные документы.
Почему не GPT-4 или Claude? Для задачи «сгенерируй Python-скрипт на 20 строк для расчёта коммуналки» они избыточны. Разница в качестве генерации кода для таких задач минимальна, а разница в цене и доступности — существенна.
Песочница
Каждый пользователь получает изолированный Docker-контейнер. Это важно по двум причинам:
Безопасность. LLM генерирует произвольный Python-код. Без песочницы это дыра в безопасности размером с Марианскую впадину.
Изоляция данных. Один пользователь не видит данные другого. Контейнер создаётся на время задачи, после выполнения файлы извлекаются и контейнер уничтожается.
Таймаут выполнения: 30 секунд. Если модель сгенерировала бесконечный цикл (бывает), контейнер убивается, пользователь получает сообщение «попробуйте переформулировать задачу».
Грабли
Подход «LLM генерирует код» звучит элегантно. На практике грабли расставлены густо.
Грабля 1: галлюцинация тарифов
Первая версия просила модель самостоятельно определить тарифы ЖКХ для города. Модель уверенно выдавала числа. Красиво отформатированные. Абсолютно неправильные.
Тариф на холодную воду в Томске: 38.76 руб/м³. Модель выдала 42.50. Откуда? Из обучающих данных за 2023 год, возможно из другого региона. Выглядело правдоподобно. Было неверным.
Решение: тарифы хранятся в конфиге и инжектируются в системный промпт. Модель не придумывает тарифы, а подставляет предоставленные. Галлюцинации ушли.
Тарифы берём из официальных источников: vodokanal.tomsk.ru, tomskenergosbyt.ru, данные ТомскРТС. Модель сама не ходит в интернет за тарифами. Мы парсим источники, обновляем конфиг, инжектируем в промпт. Модель получает готовые числа и подставляет их в код.
Это принципиально. Если дать модели доступ в интернет и попросить "найди тариф на холодную воду в Томске", она может вытащить число с форума 2019 года, из статьи про другой регион или вообще сгаллюцинировать. А если у модели нет доступа в интернет (как у большинства code-генераторов), она возьмёт тариф из обучающих данных, которые устарели на год-два.
Единственный надёжный путь: тарифы как данные, не как знания модели.
Грабля 2: модель «округляет в уме»
Даже с явной инструкцией «напиши Python-код», некоторые модели (особенно на бесплатных тирах) иногда ленятся и выдают результат напрямую, без кода. «Расход воды: 18 м³, тариф 38.76, итого примерно 697 рублей». Без скрипта. С округлением.
Решение: валидация ответа. Если в ответе модели нет блока кода (python), запрос повторяется с усиленной инструкцией: «Ты ОБЯЗАН написать Python-скрипт. Не считай в уме. Не округляй. Напиши код.» Со второй попытки срабатывает в 99% случаев.
Грабля 3: кракозябры в Excel
Модель генерирует код создания Excel через openpyxl. Всё работает. Открываешь файл: кракозябры вместо кириллицы.
Решение: явное указание encoding='utf-8' в системном промпте как обязательное требование. Плюс пост-валидация: если в сгенерированном коде нет utf-8 при работе с файлами, добавляем автоматически перед выполнением.
Грабля 4: import несуществующих библиотек
Модель иногда генерирует import pandas или import numpy для задачи, где достаточно стандартной библиотеки. В песочнице pandas может не быть (зачем тащить 50 МБ ради сложения двух чисел?).
Решение: базовый набор библиотек в Docker-образе (openpyxl, json, csv, math, datetime). В системном промпте явно: «Используй только стандартную библиотеку Python и openpyxl. Не используй pandas, numpy, scipy.»
Грабля 5: счётчик токенов считал не то
Это не про LLM, а про UX. Счётчик показывал пользователю количество символов в ответе (включая весь DOM интерфейса), а не реальное потребление токенов API. Вместо 320 токенов за задачу пользователь видел 128 000. Представьте реакцию.
Решение: считать токены на стороне бэкенда, из ответа API. Показывать пользователю реальное потребление с конвертацией в рубли.
Грабля 6: stderr критичен
Когда скрипт падает с ошибкой, первая версия возвращала только exit code. Модель не видела traceback и не могла исправить ошибку. Вместо этого она генерировала новый скрипт с нуля, часто с той же ошибкой.
Решение: полный stderr возвращается модели. Она читает traceback, понимает проблему, исправляет конкретную строку. Количество «мусорных» итераций упало в 5 раз. Это оказался самый высокоэффективный фикс из всех.
Усложнение: анализ смет
Бухгалтерские расчёты — задачи с фиксированной структурой. Коммуналка — это всегда расход * тариф. Счёт — это всегда шаблон с реквизитами. Модели легко генерировать код для таких задач.
Проверка смет оказалась значительно сложнее.
Задача: пользователь отправляет Excel-файл сметы на ремонт, указывает город. Нужно проверить каждую позицию на завышение, сравнить с рыночными ценами, найти арифметические ошибки.
Здесь модель генерирует более сложный скрипт: парсинг Excel (структура у каждой сметы своя), поиск актуальных цен, сравнение, формирование отчёта с цветовой разметкой.
Реальный тест: смета на ремонт ванной, Томск. Результат:
Завышение 54 168 руб. (25.9%)
8 позиций завышены более чем на 50%
Обнаружены арифметические ошибки (расхождения 1-4 рубля на позицию)
Excel с цветовой разметкой: зелёный (норма), жёлтый (умеренное завышение), красный (значительное)

Принцип тот же: LLM программирует, Python считает. Но объём генерируемого кода вырос с 20 строк (коммуналка) до 150-200 строк (анализ сметы). И количество граблей — пропорционально.
Точность
Арифметическая точность: 100%. Считает Python, не LLM. Тут ошибок нет по определению.
Точность интерпретации задачи: ~95%. Оставшиеся 5% — это случаи когда модель неправильно понимает контекст. Пользователь пишет «ХВ 320», модель интерпретирует как расход 320 м³, а не показание счётчика 320. Решается уточняющими вопросами и примерами в промпте.
Точность тарифов: зависит от актуальности конфига. Тарифы ЖКХ меняются раз в полгода. Пока обновляем вручную. В планах — парсинг с официальных сайтов ресурсоснабжающих организаций.
Выводы
LLM плохо считают. Это не деградация, не баг, не проблема конкретного провайдера. Это архитектурное ограничение Transformer: предсказание токенов ≠ вычисление.
Решение: вынести вычисления из модели. Пусть LLM делает то, что умеет хорошо (понимание задачи, генерация кода), а арифметику оставить интерпретатору Python.
Подход работает для любых расчётных задач: бухгалтерия, сметы, налоги, аналитика. Везде, где нужен точный результат с файлом на выходе.
Главные уроки:
Тарифы и справочники — в контекст, не в модель. Всё, что модель может нагаллюцинировать, нужно предоставить явно.
Валидация ответа обязательна. Если модель не сгенерировала код, а «посчитала в уме» — повторный запрос.
stderr спасает. Без полного вывода ошибок модель не может дебажить свой же код. Это был самый результативный фикс.
Песочница не опциональна. LLM генерирует произвольный код. Выполнять его без изоляции — приглашение к катастрофе.
Бесплатные модели достаточны. Для генерации Python-скриптов на 20-200 строк разница между GPT-4 и Qwen минимальна. А разница в стоимости — на порядки.
Если у кого-то есть опыт с аналогичным подходом (LLM как генератор кода для расчётных задач), интересно сравнить грабли. Какие модели лучше справляются с генерацией вычислительных скриптов? Как решаете проблему устаревших данных в контексте?
Комментарии (9)

Wesha
26.03.2026 14:11И на какие только жертвы люди только не пойдут, лишь бы программировать не учиться...

ignatenkosergey Автор
26.03.2026 14:11Бухгалтер Марина из Томска не должна учить Python чтобы посчитать коммуналку. Как и председатель ТСЖ не должен учить pandas чтобы проверить смету.
Это не замена программированию. Это программирование, спрятанное за интерфейс для тех, кому нужен результат, а не процесс. Как Excel в своё время: формулы под капотом, таблица снаружи.

Wesha
26.03.2026 14:11Это программирование, спрятанное за интерфейс для тех, кому нужен результат, а не процесс.
Это «программирование» для тех, кому не нужен правильный результат, а нужен результат, идентичный
натуральнымправильным (ЕВПОЧЯ).
ignatenkosergey Автор
26.03.2026 14:11В этом и суть подхода из статьи. LLM не считает - считает Python. Когда интерпретатор выполняет 18 * 38.76, результат 697.68. Не "идентичный натуральным правильным", а правильный. Детерминированно. Каждый раз.
Галлюцинирует модель - когда считает сама. Код - не галлюцинирует. Именно поэтому мы вынесли арифметику из модели в песочницу. Об этом вся статья.

Kuddesnik
26.03.2026 14:11А не оверхед ли использовать нейросеть для подсчёта коммунальных услуг? Судя по Excel таблице на скриншотах можно было просто обойтись формулами.

ignatenkosergey Автор
26.03.2026 14:11Формулами можно посчитать коммуналку, если вы знаете тарифы, структуру расчёта и готовы сами создать таблицу. Но формулы не ответят на вопрос "почему за горячую воду столько?" и не объяснят откуда взялся двухкомпонентный тариф ГВС.
Ключевое отличие не в арифметике. А в том что пользователь пишет запрос в свободной форме, получает результат, и может задать уточняющий вопрос: "Почему водоотведение посчитано от суммы ХВС и ГВС?", "А если у меня электроплита, не газовая?", "Сравни с прошлым месяцем".
Со сметой это ещё нагляднее. Председатель ТСЖ загружает смету и спрашивает: "Почему позиция 4 завышена на 142%? Откуда рыночная цена? Какой источник?" И получает ответ с разбивкой и ссылками. Формулы в Excel на это не способны.
Коммуналка в статье - простой пример для иллюстрации подхода. Реальная ценность - в диалоге, а не в вычислении.
DerTosser
Предлагаемое это более сложный путь, чем встроить в бинарник модели калькулятор и при вычислениях дёргать его апишечку или вызывать напрямую. По сути это как дать школьнику калькулятор и научить его жать на кнопки, вместо того чтобы обучить его самого счёту. Но ведь люди, при всей их аналоговости и обладающие схожей бионейросетью как-то считают? В ранних статьях, где-то между ChatGPT 3.5 и 4.0 проскакивало что её специально не обучали математике и она сама вывела закономерности и научилась считать. Просто не стоит зацикливаться на LLM архитектуре, её потолок уже виден. Сейчас всё что делается это раздувание контекста, увеличение количества параметров, увеличение вычислительного кластера, кое-какие алгоритмы оптимизации, сжатия, ограниченное число активных слоёв (MoE)... и на этом всё. Если революционных идей нет, то хотя бы пробовать комбинировать уже существующие, такие как Diffusion LLM, Diffusion forcing или мифические символические ИИ.
ignatenkosergey Автор
Вы правы, встроенный калькулятор в модель - это более чистое решение на уровне архитектуры. И оно уже существует: tool use / function calling в GPT-4, Claude, Gemini. Модель сама решает когда вызвать калькулятор.
Но в моём случае задача шире чем 18 * 38.76. Расчёт коммуналки - это 15-20 переменных, условная логика (двухкомпонентный тариф ГВС, разные тарифы по диапазонам потребления), формирование Excel с форматированием. Калькулятор через API это не закроет. А полноценный Python-скрипт - закроет.
По поводу "научить модель считать" - согласен, потолок текущей архитектуры виден. Но мне не нужно ждать революции в архитектуре. Мне нужно чтобы бухгалтерский расчёт работал точно сегодня. Генерация кода - это прагматичный обход ограничения, не попытка его решить.
Diffusion LLM и символические подходы интересны, но когда они дойдут до продакшена - вопрос открытый. А коммуналку люди платят каждый месяц.
DerTosser
Согласен, если нужно прямо здесь и сейчас, то да, такое лучше чем просто калькулятор или вообще ничего. К тому же учитывая что обучить модель с нуля и заставить модель писать код на питоне это задачи совсем разного уровня стоимости, то ваше решение оптимально.