NumbyAI: Категоризация транзакций через локальную LLM Ollama | AiManual
AiManual Logo Ai / Manual.
14 Мар 2026 Гайд

NumbyAI: Готовый пайплайн для категоризации транзакций через локальную LLM в Ollama

Пошаговый гайд по созданию пайплайна для автоматической категоризации банковских транзакций с использованием локальной LLM qwen3.5:9b в Ollama. Готовое решение

Почему ручная сортировка транзакций — это ад, и как его избежать

Выгрузил CSV из банка. 500 строк. "Пятёрочка", "Лента", "Яндекс.Еда", "Кофейня на углу". Каждую надо засунуть в категорию "Продукты" или "Общепит". Делаешь это в пятый раз за год. Хочется выть. Тратишь час, нервы, а через месяц забываешь, почему эту транзакцию в "Развлечения" определил.

Платные сервисы вроде Copilot или PocketGuard делают это за тебя. Но они отправляют твои финансовые данные бог знает куда. И стоят денег. Логичное решение — заставить нейросеть работать локально. На своём компьютере. Без интернета. Бесплатно.

Вот для этого и сделан NumbyAI. Это не стартап, а готовый пайплайн. Скелет. Берёшь его, подкручиваешь под свои категории, и он пожирает CSV-файл, выдавая на выходе тот же файл, но с новой колонкой category. Всё.

Что внутри пайплайна и почему он именно такой

NumbyAI — это конвейер. Сырые данные идут по нему, проходят через несколько фильтров и выходят очищенными. Главный фильтр — локальная большая языковая модель. Но не одна.

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

  • Двигатель: Ollama. Самый простой способ запустить модель локально в 2026 году. Не надо возиться с llama.cpp или vLLM, если ты не исследователь. Скачал, запустил, работает. Если сомневаешься в выборе, мой разбор Ollama против других фреймворков поможет.
  • Модель: Qwen2.5-7B-Instruct или Qwen2.5-14B-Instruct. На март 2026 года это одни из самых сбалансированных моделей по соотношению "разум/скорость" для задачи классификации. Qwen3.5, о котором часто пишут, — это уже прошлое поколение; бери актуальное. Модель понимает контекст, русский и может форматировать ответ как JSON.
  • Скелет: Python-скрипт. Никаких навороченных веб-интерфейсов. Всё в терминале. Берёт файл transactions.csv, создаёт transactions_categorized.csv.

Собираем движок: от установки Ollama до первого промпта

1 Ставим Ollama и качаем модель

Ollama — это как менеджер пакетов для LLM. Установка элементарная.

# На Linux/macOS
curl -fsSL https://ollama.ai/install.sh | sh

# После установки запускаем демона
ollama serve

# В другом окне терминала качаем модель.
# Qwen2.5-7B достаточно для начала. 14B — если есть запас видеопамяти.
ollama pull qwen2.5:7b-instruct
💡
Проверь, что модель работает: ollama run qwen2.5:7b-instruct и задай простой вопрос. Если видишь ответ — всё в порядке. Для задач классификации инструктивные модели (instruct) работают лучше базовых.

2 Готовим CSV с транзакциями

Банки выдают выписки в разных форматах. NumbyAI ожидает простой CSV с колонками date, description, amount. Всё лишнее нужно удалить.

# Пример raw_transactions.csv до обработки
"Дата операции","Описание","Сумма"
"14.03.2026","ООО \"ПЯТЕРОЧКА\" МОСКВА","-1250.50"
"13.03.2026","Yandex Taxi","-389.00"

# После скрипта cleanup.py получаем transactions.csv
date,description,amount
2026-03-14,ПЯТЕРОЧКА МОСКВА,-1250.50
2026-03-13,Yandex Taxi,-389.00

Дату приведи к формату YYYY-MM-DD. Описание очисти от лишних слов вроде "ООО" и кавычек. Сумму — число, отрицательное для расходов.

3 Пишем сердце пайплайна: Python-скрипт с гибридной логикой

Скрипт делает три вещи: применяет правила, отправляет сложное описание в LLM, фильтрует неуверенные ответы.

import pandas as pd
import requests
import json
import re
from typing import Optional

# Конфигурация
OLLAMA_URL = "http://localhost:11434/api/generate"
MODEL_NAME = "qwen2.5:7b-instruct"

# 1. СЛОЙ ПРАВИЛ
RULES = [
    (r"(?i)пятерочка|магнит|перекресток|лента", "Продукты"),
    (r"(?i)яндекс.*такси|uber|bolt", "Транспорт"),
    (r"(?i)яндекс.*еда|delivery club", "Общепит"),
    (r"(?i)steam|psn|epic games", "Развлечения"),
]

def apply_rules(description: str) -> Optional[str]:
    """Пытаемся определить категорию по жёстким правилам."""
    for pattern, category in RULES:
        if re.search(pattern, description):
            return category
    return None

# 2. СЛОЙ LLM
PROMPT_TEMPLATE = """
Ты — финансовый ассистент. Определи категорию расходов для транзакции.
Описание: {description}

Выбери одну категорию из списка: [Продукты, Общепит, Транспорт, Развлечения, Здоровье, Образование, Коммуналка, Одежда, Другое].
Ответ дай строго в формате JSON: {"category": "название", "confidence": 0.95}
"""

def ask_llm(description: str) -> dict:
    prompt = PROMPT_TEMPLATE.format(description=description)
    payload = {
        "model": MODEL_NAME,
        "prompt": prompt,
        "stream": False,
        "options": {"temperature": 0.1}  # Низкая температура для консистентности
    }
    try:
        resp = requests.post(OLLAMA_URL, json=payload, timeout=60)
        resp.raise_for_status()
        result = resp.json()
        # Извлекаем JSON из ответа модели
        response_text = result["response"]
        # Ищем JSON в тексте (модель иногда добавляет пояснения)
        json_match = re.search(r'\{.*\}', response_text, re.DOTALL)
        if json_match:
            return json.loads(json_match.group())
        else:
            return {"category": "Другое", "confidence": 0.0}
    except Exception as e:
        print(f"Ошибка запроса к LLM: {e}")
        return {"category": "Другое", "confidence": 0.0}

# 3. СЛОЙ ПОСТ-ОБРАБОТКИ
CONFIDENCE_THRESHOLD = 0.7  # Всё, что ниже — отправляем в "Другое"

def process_transactions(input_csv: str, output_csv: str):
    df = pd.read_csv(input_csv)
    categories = []
    confidences = []

    for _, row in df.iterrows():
        desc = row["description"]
        # Сначала правила
        rule_category = apply_rules(desc)
        if rule_category:
            categories.append(rule_category)
            confidences.append(1.0)  # Правилам доверяем полностью
            continue

        # Если правилам не подошло — спрашиваем LLM
        llm_result = ask_llm(desc)
        category = llm_result.get("category", "Другое")
        confidence = llm_result.get("confidence", 0.0)

        # Фильтр достоверности
        if confidence < CONFIDENCE_THRESHOLD:
            category = "Другое"

        categories.append(category)
        confidences.append(confidence)

    df["category"] = categories
    df["confidence"] = confidences
    df.to_csv(output_csv, index=False)
    print(f"Готово! Обработано {len(df)} транзакций. Результат в {output_csv}")

if __name__ == "__main__":
    process_transactions("transactions.csv", "transactions_categorized.csv")

Не копируй этот код слепо. Температура (temperature) 0.1 — специально для категоризации, чтобы модель не креативила. Таймаут 60 секунд — некоторые транзакции модель будет думать дольше. Если обрабатываешь тысячи строк, добавь паузы между запросами, чтобы не перегрузить Ollama.

Тонкая настройка: как заставить пайплайн работать на 98% точно

Сырой скрипт из коробки даст точность 70-80%. Чтобы выжать больше, нужны хитрости.

1. Итеративная настройка правил

Запусти пайплайн на своих данных. Посмотри, какие транзакции определены неверно. Добавь для них правила. "Сбербанк Онлайн" — это не "Другое", а "Комиссии". "Аптека №36" — "Здоровье". Чем больше правил, тем меньше нагрузка на LLM и быстрее работа. Это называется гибридный подход, и он подробно описан в кейсах по локальным LLM.

2. Кастомизация промпта — это ключ

Промпт выше — базовый. Если категории специфические ("Инвестиции", "Криптовалюта", "Подписки на SaaS"), их надо явно прописать в списке. Можешь даже дать примеры в самом промпте (few-shot learning).

PROMPT_TEMPLATE_V2 = """
Примеры:
- "Оплата Spotify" -> категория "Подписки"
- "Перевод на брокерский счёт" -> категория "Инвестиции"

Теперь определи категорию для: {description}
Категории: [Продукты, Подписки, Инвестиции, Транспорт, Другое].
Ответ: JSON {"category": "...", "confidence": ...}
"""

3. Баланс между скоростью и точностью

Ollama по умолчанию использует GPU, если он есть. На CPU обработка одной транзакции может занимать 10-20 секунд. Если транзакций 500, это 2-3 часа. Решение — батчи. Но Ollama не поддерживает батч-обработку из коробки. Придётся запускать несколько параллельных запросов (асинхронно) или использовать более продвинутый бэкенд, например, vLLM. Но это уже следующий уровень сложности.

4. Обработка низкодостоверных предсказаний

Порог уверенности 0.7 выбран произвольно. После первого прогона посмотри в файле с результатами колонку confidence. Если видишь, что много транзакций с уверенностью 0.4-0.6 попали в "Продукты", но они на самом деле "Общепит", — значит, нужно либо снизить порог, либо улучшить промпт. Все такие спорные транзакции можно выгрузить в отдельный CSV для ручной проверки и потом добавить в правила.

Где всё ломается и как чинить

Проблема Причина Решение
Ollama падает при долгой работе Утечка памяти. Модель 7B в GPU может съесть всю видеопамять. Запускать Ollama с флагом --num-gpu-layers 20 (меньше слоёв на GPU) или использовать модель поменьше (например, Qwen2.5-3B).
Модель возвращает не JSON, а текст Промпт недостаточно строгий или температура высокая. Добавь в промпт: "Ответ дай ТОЛЬКО JSON, без дополнительных объяснений". Установи "temperature": 0.
Транзакции определяются, но медленно Каждая транзакция — отдельный HTTP-запрос. Накладные расходы. Объединяй несколько описаний в один промпт (мультиклассификация). Или используй асинхронные запросы (asyncio, aiohttp).
Категория "Другое" разбухает Порог уверенности слишком высок или в списке категорий нет нужной. Добавь новые категории. Снизь порог до 0.5. Проанализируй, что попадает в "Другое", и создай правила.

Что дальше? NumbyAI как трамплин

Этот пайплайн — каркас. Его можно расширять в любую сторону.

  • Веб-интерфейс: Добавить FastAPI и простую страничку, куда drag-n-drop'ом кидаешь CSV. Но зачем? Скрипт в терминале делает то же самое.
  • Автоматизация: Подключить пайплайн к Google Sheets через API. Выписка из банка приходит на почту, парсер её обрабатывает, NumbyAI категоризирует, результат записывается в таблицу. Полная автоматизация финансового учёта.
  • Обучение на своих данных: Если накопилось 1000 размеченных транзакций, можно дообучить модель (LoRA) именно на своих категориях. Но это уже для энтузиастов. В 2026 году есть инструменты проще, чем два года назад.

Главный неочевидный совет: не гонись за 100% автоматизацией. Оставь 5% самых странных транзакций для ручной обработки. Так ты сохранишь связь со своими финансами. Иначе превратишься в человека, который не помнит, на что тратит деньги, потому что за него всё делает скрипт.

Локальные LLM — не магия. Это инструмент. NumbyAI показывает, как заточить этот инструмент под конкретную, скучную, но важную задачу. Собрал, настроил, забыл. И высвободил время для чего-то более интересного.

Подписаться на канал