Задайте вопрос о своих данных обычными словами — на русском, казахском или английском — и получите ответ с графиком и кодом, по которому этот ответ посчитан.
Модель не считает сама — она пишет код, который считает. Языковая модель плохо выполняет арифметику и склонна выдумывать числа. Здесь она генерирует код на pandas, код выполняется на реальных данных, и результат получается воспроизводимым и проверяемым.
Python · Pandas · Plotly · Streamlit · Ollama · Gemini API · Docker · ReAct-агент
- Три языка запроса — русский, казахский, английский.
- Генерация кода анализа вместо прямого ответа модели.
- Проверка безопасности сгенерированного кода до выполнения.
- Автоматические графики — тип выбирается по форме результата.
- Показ кода — каждый ответ сопровождается кодом, которым он получен.
- Отказ вместо догадки — если данных для расчёта нет, система сообщает об этом.
- Загрузка своих данных — XLSX и CSV.
- Два режима генерации — локальный (офлайн) и облачный.
- Агентный режим — многошаговый анализ с вызовом инструментов (ReAct).
| Режим | Как работает | Когда применять | Время |
|---|---|---|---|
| Быстрый ответ | Один шаг: вопрос → код → результат | Конкретные цифры: «какая выручка», «какой магазин лидирует» | ~30 с |
| Глубокий анализ | Агент делает несколько шагов, выбирая инструменты | Вопросы «почему» и «что изменилось» | 2–6 мин |
Разделение сделано намеренно: агентный цикл требует нескольких обращений к модели, и для вопроса вида «какая общая выручка» это неоправданно дорого. Пользователь выбирает режим под характер задачи.
Реализован цикл ReAct: рассуждение → действие → наблюдение. На каждом шаге модель получает историю предыдущих шагов и решает, какой инструмент вызвать дальше.
Доступные инструменты:
| Инструмент | Назначение |
|---|---|
run_analysis |
произвольное вычисление на pandas через песочницу |
compare_periods |
сравнение двух периодов с разбивкой по категории |
find_anomalies |
поиск выбросов во временном ряду |
describe_column |
просмотр содержимого колонки |
finish |
завершение анализа с формулировкой ответа |
Инструменты описаны словами, а не сигнатурами: модель видит только текст описания и по нему решает, что применить. Добавление нового инструмента требует одной функции и одной записи в реестре.
Устойчивость цикла. Реализованы четыре механизма:
- Предел шагов — защита от бесконечного цикла.
- Блокировка повторов — один и тот же вызов с теми же аргументами не выполняется дважды: он даст тот же результат и лишь потратит шаг.
- Ошибки как подсказки — инструменты не выбрасывают исключений, а возвращают текст с примером корректного вызова. Модель лучше реагирует на образец, чем на описание сбоя.
- Запрет на выдумывание — финальный ответ формулируется только из полученных результатов.
Прозрачность. Интерфейс показывает полную историю: рассуждение модели, вызванный инструмент, аргументы и результат каждого шага. Видно, на каких данных основан вывод.
Последовательность прогонов на вопросе «Почему упала прибыль в 2026 году?»:
| Конфигурация | Шагов | Результат |
|---|---|---|
qwen2.5:3b, без защиты от петель |
3 | неверные аргументы, вывод не соответствует данным |
qwen2.5-coder:3b, без защиты |
6 (лимит) | четыре одинаковых вызова подряд, финал выдуман |
qwen2.5-coder:3b + защита + подсказки |
2 | верные аргументы, цифры из данных, анализ поверхностный |
Каждая правка давала измеримое улучшение, но осталось ограничение, которое промптом не снимается: модель на 3 млрд параметров не выстраивает многошаговый план. Она либо останавливается после первого результата, либо перебирает один инструмент. Ответ на вопрос «почему» подменяется констатацией «упала на N%».
Это третий случай в проекте, когда качество упирается в размер модели (первые два — самопроверка результата и точность интерпретации формулировок). Архитектура агента при этом работает корректно: инструменты вызываются, ошибки обрабатываются, цикл не зацикливается, вывод строится из собранных данных.
Данные (XLSX / CSV)
│
▼
[1] Загрузка ──► [2] Профилирование схемы
│
▼
Вопрос пользователя (RU / KZ / EN)
│
▼
[3] Генерация кода анализа на pandas
│
▼
[4] Проверка безопасности (разбор AST + белый список)
│
▼
[5] Выполнение в изолированном окружении
│
▼
[6] Самопроверка результата (для облачных моделей)
│
├── не прошла ──► повторная генерация с замечанием
│
▼
[7] Результат + график + показ кода
Агентный режим — альтернативный путь:
Вопрос ──► выбор инструмента ──► вызов ──► наблюдение
▲ │
└────────── цикл ReAct ──────────┘
(до 6 шагов)
│
▼
finish: ответ + история шагов
В запрос уходит только описание структуры: названия колонок, типы, диапазоны значений, несколько строк-примеров. Сами 23 тысячи строк никуда не передаются.
Три следствия: система одинаково работает на тысяче строк и на миллионе; конфиденциальные данные не покидают контур при использовании облачной модели; расход контекста не зависит от объёма таблицы.
Код, сгенерированный моделью, выполняется на машине пользователя — это требует защиты. Реализованы две независимые линии:
Проверка до выполнения. Код разбирается в синтаксическое дерево
(ast), и каждый узел проверяется по белому списку. Запрещены импорты,
файловые и сетевые операции, eval/exec, доступ к служебным атрибутам,
объявление функций и классов.
Изолированное окружение. Выполнение идёт с подменённым __builtins__,
содержащим только безопасные функции. Даже если проверка что-то пропустит,
опасных вызовов в окружении просто нет.
Перекрыты в том числе известные приёмы обхода песочницы:
().__class__.__bases__[0].__subclasses__() и __import__('os').system().
ai-business-analyst/
├── data/ # демонстрационный набор
├── src/
│ ├── loader.py # чтение файлов, профилирование схемы
│ ├── llm.py # провайдер-независимый слой (Ollama / Gemini)
│ ├── codegen.py # генерация кода анализа
│ ├── sandbox.py # проверка AST и безопасное выполнение
│ ├── verifier.py # самопроверка результата
│ ├── charts.py # автоматический выбор графика
│ ├── tools.py # инструменты агента
│ ├── agent.py # цикл ReAct
│ ├── pipeline.py # сборка быстрого режима
│ └── app.py # веб-интерфейс
├── eval/
│ ├── test_questions.json # 28 вопросов с эталонными вычислениями
│ ├── evaluate.py # замер качества
│ └── results.json # результаты прогона
├── docs/screenshots/
├── Dockerfile
└── docker-compose.yml
Требуется установленная Ollama:
ollama pull qwen2.5-coder:3b
pip install -r requirements.txt
streamlit run src/app.pyПриложение открывается на http://localhost:8501.
Замеры в разделе «Оценка качества» получены на
qwen2.5:3b, замеры агентного режима — наqwen2.5-coder:3b. Модель задаётся переменнойOLLAMA_MODELв.env.
Для облачного режима укажите в .env:
LLM_PROVIDER=gemini
GOOGLE_API_KEY=ваш_ключ
docker-compose up --buildКонфигурация Docker составлена, но не проверялась запуском: разработка велась на машине с отключённой аппаратной виртуализацией и без прав администратора.
28 вопросов в восьми категориях. Для каждого вопроса задан эталонный код, который вычисляет правильный ответ на тех же данных: система сравнивается не с записанными числами, а с результатом эталонного вычисления. При смене данных эталоны пересчитываются автоматически.
Прогон на локальной модели qwen2.5:3b:
| Категория | Всего | Верно | Точность |
|---|---|---|---|
| Фильтрация | 3 | 3 | 100% |
| Производные величины | 2 | 2 | 100% |
| Агрегация | 6 | 5 | 83% |
| Группировка | 4 | 3 | 75% |
| Вне данных (ожидается отказ) | 4 | 3 | 75% |
| Динамика | 3 | 2 | 67% |
| На казахском | 3 | 2 | 67% |
| На английском | 3 | 2 | 67% |
| Итого | 28 | 23 | 82% |
Дополнительно:
| Показатель | Значение |
|---|---|
| Код выполнился без ошибок | 89% |
| Среднее число попыток | 1,04 |
| Среднее время на вопрос | 31 с (CPU, 8 ГБ ОЗУ) |
Все пять ошибок относятся к одному классу — неточная интерпретация формулировки, а не сбой вычисления:
| Вопрос | Что сделала модель |
|---|---|
| «общая прибыль» | группировка по дате вместо суммы |
| «по кварталам» | номер квартала без учёта года |
| «по каждой категории» (каз.) | группировка по товарам |
| «total profit» (англ.) | группировка по магазинам |
| «сколько уволилось» | придумала вычисление вместо отказа |
Последний случай самый значимый: система выдала правдоподобный ответ на вопрос, данных для которого нет. Это ровно тот риск, ради которого существует самопроверка.
Механизм верификации реализован, но замеры показали, что на модели в 3 млрд параметров он ухудшает результат: на контрольной выборке из шести вопросов он испортил два корректных ответа и один раз ошибочно забраковал верный. Проверка чужого рассуждения оказалась сложнее для компактной модели, чем генерация кода.
Поэтому верификация включается автоматически только для облачного
провайдера, где модель достаточно велика. Код механизма сохранён
и активируется сменой переменной LLM_PROVIDER.
Потолок компактной модели. Точность 82% достигнута на qwen2.5:3b —
единственной модели, укладывающейся в 8 ГБ ОЗУ. Ошибки интерпретации
формулировок, неработающая верификация и поверхностное планирование
агента упираются в размер модели, а не в архитектуру системы.
Методика сравнения результатов. Скрипт оценки не приводит DataFrame к Series: если система вернула верные значения в другой форме, засчитывается провал. Одна из пяти ошибок в прогоне относится к этому случаю, то есть фактическая точность несколько выше измеренной.
Один источник данных за раз. Объединение нескольких таблиц не реализовано.
Только табличные данные. Извлечение таблиц из PDF и DOCX заявлено в планах, но в текущей версии не реализовано.
Интерфейс на Streamlit. Разделение на FastAPI-бэкенд и React-фронтенд рассматривалось, но признано нецелесообразным: оно не улучшает качество анализа, а усложняет развёртывание демонстрационной версии.
Без авторизации и ролей. Стандартная задача, не относящаяся к предметной области проекта.
- Гибридный подход: SQL вместо pandas для больших наборов
- Объединение нескольких таблиц в одном запросе
- Извлечение таблиц из PDF и DOCX
- Прогнозирование и выявление аномалий отдельным маршрутом
Демонстрационный набор — вымышленная розничная сеть в Казахстане: 23 123 строки, 7 магазинов в 4 городах, период с августа 2024 по июль 2026. В данные намеренно заложены сезонность, разнонаправленные тренды по точкам, сжатие маржи, аномальные периоды и небольшой процент ошибок ввода — чтобы аналитические вопросы имели содержательные ответы.
Все названия, тарифы и показатели вымышлены.

