DEV Community

Cover image for Бухгалтерия на естественном языке: каким должен быть MCP-сервер
Andrej Kirejeŭ
Andrej Kirejeŭ

Posted on Edited on

Бухгалтерия на естественном языке: каким должен быть MCP-сервер

Если бы Beatles пели не о любви, а о бухгалтерском учёте, то получилось бы что-то вроде: All you need is report. Всем от бухгалтерии интересно что-то узнать: налоговой — размер выручки, работнику — почему так мало начислили за прошлый месяц, а руководителю — начал ли хоть кто-то из хронических дебиторов рассчитываться с нами.

Мы занимаемся автоматизацией бухучёта с середины девяностых. Технологии менялись, но добыча нужных цифр всегда была процессом длительным, часто грязным (кто менял ленточки на Epson-ах или досыпал тонер в картриджи HP — знает) и дорогим: надо позвать программиста, объяснить ему, какой отчёт нужен, подождать пару недель и получить на выходе не то, что ожидалось.

ChatGPT изменил современный мир и то, как мы общаемся с компьютером. Пользователь больше не хочет продираться через минное поле выпадающих списков, чекбоксов и радиокнопок. Он хочет просто спросить обычным языком и получить ответ с текстом и цифрами, а если понадобится, то и с таблицами и графиками. Вопросы у пользователя могут быть как простыми: "Какое сальдо по расчётам с поставщиками на 1 октября?" или "покажи карточку счёта 50 за сентябрь", так и требующими рассуждений и анализа: "Почему упала прибыль в прошлом квартале, что происходит на рынке и что нам делать?" На простые вопросы модель обязана отвечать точно. На сложные — хотя бы обоснованно.

Мостиком между моделью и учётной базой служит Model Context Protocol (MCP) — открытый протокол, через который языковая модель подключается к внешним данным и инструментам. Тем, кто застал девяностые, проще всего представить его как ODBC для нейросетей: один разъём, к которому подключается что угодно. Дело за малым — написать сервер, который будет висеть на другом конце провода. О том, каким он должен быть, и пойдёт речь.

К конкретной учётной системе цикл не привязан. Схема данных условная, план счетов типовой — тот самый, по которому сдают экзамен в вузе (счёт 50 из примера выше — это, конечно, "Касса"). С терминологией сложнее: каждая система норовит назвать одно и то же по-своему. Документ где-то проводят, где-то отрабатывают в учёте, где-то формируют по нему проводки по типовой хозяйственной операции. Объекты аналитического учёта зовутся то субконто, то аналитиками, то аналитическими счетами, то классификаторами. Карточку счёта иногда величают картой, анализ счёта — шахматкой. Мы будем держаться языка учебника, а синонимы приводить там, где без них можно запутаться. Механизмы своей системы читатель, надеемся, узнает и без них.

План цикла

  1. Часть 1 (эта статья). Два пути: частный и универсальный сервер. Соблазн прямого доступа.
  2. Часть 2. Условная схема данных. Где ошибается модель.
  3. Часть 3. Архитектура сервера. Инструменты. Знание предметной области.
  4. Часть 4. Сложные вопросы. Чему нас научила разработка. Заключение.

Два пути: частный и универсальный сервер

Прежде чем браться за сервер, придётся выбрать дорогу. Их две.

Частный сервер — набор инструментов под конкретные вопросы: дай дебиторов, дай список сотрудников, дай начисления по сотруднику, дай остатки на складе. Он похож на кофейный автомат: нажал кнопку "капучино" — получил капучино.

Универсальный сервер устроен иначе. Его инструменты параметризованы не вопросами, а понятиями бухгалтерского учёта: счёт, период, объект аналитики, вид документа. Это уже не автомат, а бариста с кофемашиной, молоком и сиропами: из одних и тех же ингредиентов он соберёт и то, чего нет в меню.

Частный сервер подкупает простотой. Написать его легко, протестировать ещё легче. Для чат-бота, через который сотрудник узнаёт остаток своего отпуска, ничего другого и не нужно. Трудности обычно появляются на втором-третьем уточняющем вопросе.

Представим разговор с финансовым директором. В частном сервере есть инструменты get_employees(), get_accruals(employee) и get_revenue(period).

Вопрос Частный сервер Универсальный сервер
Покажи начисления Иванову за сентябрь get_accruals("Иванов") — ответ получен Карточка счёта 70 "Расчёты с персоналом по оплате труда" с отбором по работнику
Сколько премий начислено отделу продаж в III квартале? Инструмента для отдела и вида начисления нет. Модель вызывает get_accruals для каждого из 40 сотрудников и складывает 40 ответов в уме, путаясь в видах начислений Обороты по кредиту счёта 70 с группировкой по подразделению; вид начисления — из документов начисления заработной платы. Два вызова, итоги считает сервер
А по сравнению со II кварталом? Ещё 40 вызовов Тот же вызов со сравнением периодов
Как это соотносится с выручкой отдела? get_revenue об отделах не знает. Ответа нет Выручка по кредиту счёта 90 "Продажи" в разрезе подразделений — показатель из справочника показателей
Почему премии выросли, а выручка нет? Ответа нет Анализ счёта, документы начисления премий и их основания

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

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

Дальше речь пойдёт только об универсальном сервере. Но универсальный не значит "дай модели SQL, пусть разбирается". Почему — в следующем разделе.

Соблазн прямого доступа

Самый короткий путь к универсальному серверу — дать модели описание таблиц и инструмент для выполнения SQL-запросов. SQL современные модели пишут неплохо, схема базы известна. Что тут может пойти не так?

flowchart LR
    U[Бухгалтер] -->|вопрос| L[Языковая модель]
    L -->|SQL| S[MCP-сервер<br/>run_sql]
    S --> D[(Учётная база)]
    D --> S --> L -->|ответ| U

Рис. 1 Наивная архитектура: модель сама пишет запросы к учётной базе.

Первые ответы обычно выглядят убедительно. Проблема в том, что часть из них неверна, а по виду это не определить: запрос синтаксически корректен, выполняется без ошибок и возвращает правдоподобное число. Для бухгалтерии это хуже отказа. Если число хотя бы раз разошлось с оборотно-сальдовой ведомостью, бухгалтер начинает перепроверять каждый ответ, и пользы от сервера становится немного.

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

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

Top comments (0)