Личная бухгалтерия на LLM: парсим хаотичные сообщения из Telegram в строгую базу Notion через n8n и Structured Output Parser
1. Введение: почему классический учет расходов не приживается
Практически любой эксперимент с ведением личного бюджета упирается в одно и то же узкое горлышко - человеческий фактор. Первые несколько дней есть запал, но как только начинается рутина, система дает сбой.
Готовые банковские приложения анализируют только безналичные платежи по картам, при этом операции по СБП или чеки от самозанятых часто уходят в «Прочие переводы». Сторонние финансовые менеджеры требуют открывать отдельный интерфейс, выбирать счет, пролистывать списки категорий и вбивать суммы вручную. Когда стоишь на кассе с покупками, торопишься в метро или рассчитываешься в кафе, открывать приложение просто лень. Чеки откладываются на потом, а к вечеру детали стираются из памяти.
Notion выглядит идеальным кандидатом для кастомной финансовой отчетности: настраиваемые свойства, фильтры, связи с проектами и сводные дашборды. Однако мобильное приложение Notion долго грузится при нестабильном интернете, а добавление строки на ходу превращается в раздражающую микрохирургию по экрану смартфона.
Задача состояла в том, чтобы убрать любое трение: свести весь учет к отправке одного короткого сообщения на человеческом языке в Telegram (например, Кроссовки "Спортмастер" 2399 или молоко "Пятерочка" 100), а всю классификацию, валидацию и форматирование переложить на автоматизированный пайплайн.
2. Архитектура решения и стек
Вся логика построена на базе n8n без сторонних платных middleware-сервисов:
- Telegram Bot API (Trigger): мгновенная точка входа. Telegram закреплен в быстром доступе и стабильно отправляет сообщения даже при слабом EDGE.
- n8n (Self-hosted): оркестратор бизнес-логики. Обрабатывает вебхуки Telegram, выполняет фильтрацию, маршрутизирует данные в LLM и пишет в базу.
- LangChain-стек внутри n8n (AI Agent + DeepSeek Chat Model + Structured Output Parser): вместо ненадежных сырых HTTP-запросов используется агентная сборка n8n с моделью DeepSeek Chat Model и парсером структурированного вывода.
- Notion API (Create a database page): целевое хранилище - база «Мои расходы» с колонками «Что купили» (Title), «Категория» (Select), «Сумма (RUB)» (Number) и «Дата» (Date).
3. Разбор «под капотом»: фильтрация, промпт и грабли
В реальном сценарии данные никогда не приходят в идеальном виде. Пользователь может отправить приветствие, скинуть стикер, забыть написать сумму или ввести текст с кавычками и опечатками.
Шаг 1. Маршрутизация и первичный фильтр (узел If)
Чтобы исключить холостые вызовы LLM и не спамить в Notion пустыми страницами, первым звеном после Telegram Trigger стоит узел If. Он проверяет наличие текста и базовую сигнатуру расхода (например, наличие числовых символов).
- Если проверка не пройдена (False): сценарий уходит в узел Send a text message, который возвращает пользователю подсказку:⚠ Не удалось распознать расход. Пожалуйста, напишите название расхода, а в конце через пробел сумму цифрами без дополнительных знаков и точек. Примеры: "Такси 500", "Обед в столовой 570", "Бензин АИ 95 1500"
- Если проверка пройдена (True): полезная нагрузка передается в агентный блок.
Шаг 2. Извлечение сущностей через AI Agent и Structured Output Parser
Ключевая проблема при работе с моделями вроде DeepSeek - галлюцинации и лишний текст в ответе. Если просить модель отдать JSON обычным промптом, она периодически заворачивает его в markdown-блоки ```json, вставляет вводные слова или искажает ключи.
Для решения этой задачи в n8n задействован узел Structured Output Parser, подключенный к AI Agent. Он генерирует жесткую JSON-схему, обязывая DeepSeek Chat Model возвращать строго типизированный объект:
{
"item": "Кроссовки",
"category": "Одежда и покупки",
"amount": 2399
}
Модель берет на себя интеллектуальное сопоставление категорий (In-Context Learning):
- Ввод: Кроссовки "Спортмастер" 2399 → Поле Что купили: Кроссовки → Категория: Одежда и покупки→ Сумма: 2399
- Ввод: Столовая 2010→ Поле Что купили: Столовая→ Категория: Кафе и перекусы→ Сумма: 2010
- Ввод: молоко "Пятерочка" 100→ Поле Что купили: молоко→ Категория: Еда и продукты→ Сумма: 100
- Ввод: Аптека «Миницен» 2349→ Поле Что купили: Аптека «Миницен»→ Категория: Спорт и здоровье→ Сумма: 2349
Шаг 3. Запись в Notion (Create a database page)
Спарсенные значения без промежуточных костылей напрямую маппятся в ноду Notion:
- Title ➔ {{ $json.output.item }}
- Категория (Select) ➔ {{ $json.output.category }}
- Сумма (RUB) (Number) ➔ {{ $json.output.amount }}
- Дата (Date) ➔ системное время триггера {{ $now }} (с фиксацией даты и времени внесения транзакции).
Благодаря типизации через Structured Output Parser база защищена от появления пустых строк, невалидных форматов чисел или битых тегов категорий.
Готовый JSON-шаблон сценария для импорта в n8n, схему маппинга и инструкцию по развертыванию выложил в открытом виде на GitHub. Забирайте и адаптируйте под свои задачи.
Если возникнут вопросы по настройке узлов или логике парсера — пишите в комментариях или заглядывайте в мой Telegram-канал Build > Запуск, где я разбираю другие кейсы автоматизации и разработки.