ГлавнаяБлогИИ-финансовый директор: автономный агент для малого бизнеса
AI / Нейросети

ИИ-финансовый директор: автономный агент для малого бизнеса

Как построить автономного ИИ-финансового директора для малого бизнеса: архитектура на ADK, реальные грабли и 40 проверок без сбоев. Начните сейчас!

Al
Редакция Algolitalgolit.ru
12 мин чтения25 августа 2026 г.

ИИ-финансовый директор: автономный агент для малого бизнеса

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

Это не очередной чат-бот с инструментами. Это полноценный финансовый отдел для бизнеса, который не может нанять команду. Владелец общается с ИИ-CFO, а за кулисами три специалиста — казначей, бухгалтер и аналитик — каждое утро проверяют книги, закрывают месяц и готовят управленческую отчётность. Владелец никогда не говорит со специалистами напрямую, и это ключевое архитектурное решение.

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

На момент написания статьи система провела 40 автоматических проверок за 8 дней подряд с нулём сбоев, работая с реальной песочницей QuickBooks.

Выбор правильного подхода в ADK

Google Agent Development Kit предлагает три модели оркестрации, и самое интересное — решить, что куда поместить. Я перепробовал два варианта и чуть не попал в ловушку.

Ежедневная проверка — это граф, а не чат

Первая версия позволяла каждому агенту вызывать инструменты для получения данных. Но промпт должен был просить данные и надеяться, что агент их получит. Если агент пропускал вызов инструмента, он проверял ничего и сообщал, что всё в порядке. Это ужасный сбой: его невозможно отличить от успеха.

Поэтому сбор данных стал обычными Python-функциями, встроенными в граф:

review_workflow = Workflow(
    name="cfo_daily_review",
    edges=[
        (START, fetch_performance, gather),
        (START, fetch_trend, gather),
        (START, fetch_cash, gather),
        (START, fetch_receivables, gather),
        (START, fetch_ledger_quality, gather),
        (START, fetch_open_findings, gather),
        (gather, treasurer, judged),
        (gather, accountant, judged),
        (gather, analyst, judged),
        (judged, store_findings),
    ],
)

Шесть вызовов, ноль вызовов модели, и они выполняются, потому что подключены к START, а не потому что модель решила их вызвать. Специалисты начинают работу только после того, как факты собраны.

Обратите внимание на два объединения: gather собирает все данные, чтобы каждый специалист видел полную картину, а judged ждёт всех трёх специалистов, чтобы ничего не сохранять при частичном обзоре. Это урок, который я усвоил на горьком опыте, когда тайм-аут одного специалиста приводил к ложному закрытию его находок.

Ещё одна тихая победа: Runner(node=workflow) запускается с new_message=None. В системе нет фальшивого промпта "пожалуйста, запусти проверку". Граф просто работает.

Чат в режиме single_turn — это продукт

Суб-агенты ADK по умолчанию используют режим chat. В этом режиме координатор получает только transfer_to_agent — последовательную передачу всего диалога одному специалисту, который никогда не возвращается. Подумайте, что это значит: владелец задаёт вопрос о деньгах, и казначей, которого он никогда не видел, отвечает и удерживает диалог.

Решение: режим single_turn. В этом режиме ADK даёт CFO один инструмент делегирования на каждого специалиста, запускает нужные подмножества параллельно, возвращает результаты, и CFO отвечает своим голосом. Специалисты никогда не обращаются к пользователю напрямую.

cfo = Agent(
    name="cfo",
    sub_agents=[chat_treasurer, chat_accountant, chat_analyst],
    ...
)

chat_treasurer = Agent(
    name="treasurer",
    mode="single_turn",  # <- весь дизайн в одном флаге
    tools=[get_cash_position, get_receivables, get_open_findings],
    ...
)

Мне нравится, что продуктовое решение — владелец нанял CFO, а не комитет — обеспечивается фреймворком, а не вежливым промптом.

Настоящая пауза: как сделать ожидание реальным

Закрытие месяца — главный рабочий процесс, и его ключевая особенность — остановка и ожидание человека, возможно, несколько дней. Многие системы имитируют это: флаг статуса, цикл опроса, задача, которая просыпается и спрашивает "уже одобрено?". ADK 2 предоставляет настоящий примитив:

@node(rerun_on_resume=True)
async def await_approval(ctx, node_input):
    case = _load_case()
    answer = ctx.resume_inputs.get(case.interrupt_id)
    if answer is None:
        # ... сохранить предложения, составить письмо об одобрении ...
        yield RequestInput(
            interrupt_id=case.interrupt_id,
            message=f"Одобрить корректировки закрытия {case.period_label}?",
            response_schema={"type": "string"},
        )
        return  # выполнение действительно останавливается здесь
    approved = str(answer).strip().lower() in ("approve", "approved", "yes")
    yield Event(output={"approved": approved, "plan": node_input})

Перед тем как строить что-то поверх, я провёл спайк как жёсткий тест в двух реально отдельных процессах:

процесс 1   [prepare] сканирование периода (дорогая работа)
            [ask_approval] пауза на 'close-approval'
            ⏸ ПАУЗА

процесс 2   --- ВОЗОБНОВЛЕНИЕ (2 предыдущих события) ---
            [ask_approval] возобновлён с ответом 'approve'
            [act] РЕШЕНО -> 'approve'
            ✓ возобновлён из холодного процесса и завершён

Важная строка — та, которая не напечаталась. prepare не запускался повторно. Рабочий процесс возобновился на том узле, где остановился, зная только session id и interrupt id — ровно то положение, в котором окажется ссылка одобрения по электронной почте через два дня.

Два совета для тех, кто строит такое:

  • Используйте стабильный interrupt_id. Выводите его из вашего кейса (например, f"{case_id}-approval"), никогда не из генератора. Процесс, который возобновляет, никогда не встречался с процессом, который поставил на паузу; он должен уметь назвать прерывание.
  • Ведите свою запись параллельно. ADK управляет выполнением, но я также храню CloseCase в Firestore — индекс, журнал аудита и read-модель для UI. Это также позволяет обнаружить потерянную сессию, а не молча её игнорировать.

Баги, которые научили уважать однонаправленное возобновление

Во время съёмки демо я нажал кнопку "Не сейчас" на странице одобрения, ожидая вернуться позже. Закрытие продолжилось и пометило месяц закрытым. Ничего не дошло до QuickBooks — защита сработала. Но месяц был подписан, и аналитик подготовил управленческую отчётность для книг, которые никогда не были исправлены.

Причина: возобновление однонаправленно. Любой ответ перезапускает выполнение, и граф идёт до конца. "Отклонить" было не способом остаться на паузе, а способом выполнить весь рабочий процесс с установленным флагом.

Исправление — перестать относиться к этому как к решению:

// "Не сейчас" означает вернуться позже, поэтому НЕ возобновляем workflow
if (decision !== 'approve') {
    location.href = signedIn ? '/inside' : '/login';
    return;
}

И эндпоинт теперь отказывается от любого другого ответа, так что ни один клиент не может подписать месяц без одобрения. Есть два варианта, а не три, потому что фреймворк поддерживает только два.

Реальные книги кусаются: ошибки, которые я нашёл

Я мог бы замокать бухгалтерскую систему, но не стал, и почти всё, что я узнал, пришло из-за того, что API не соглашался со мной.

Коллизия имён счетов

В плане счетов "Sprinklers and Drip Systems" существует дважды: как доход и как расход. Сопоставление только по имени привело к тому, что строка счёта попала в счёт дохода, создав отрицательную выручку. Я этого не нашёл — агент нашёл в утренней проверке: "Отрицательная выручка −$288.00". Это было странное и довольно хорошее ощущение. Исправление — фильтр AccountType при каждой проводке затрат.

Обратные слэши вместо удвоенных кавычек

Язык запросов QuickBooks похож на SQL, но не является им. 'Amy''s' возвращает 400, а 'Amy\'s' — совпадение. Подвох в том, что неудачный поиск не вызывает исключение — поэтому поиск поставщика молча ничего не нашёл и создал дубликат поставщика.

Параметры запроса не должны жить в пути

Я написал client.post(f"/bill?operation=delete", ...). httpx заменяет строку запроса URL своими params, поэтому operation=delete был удалён, и каждое удаление стало молчаливым no-op с возвратом 200. Удаления теперь проверяют status == "Deleted" в ответе.

Закрывающие записи датируются концом периода, а не сегодняшним днём

Если датировать сегодняшним днём, июльские корректировки ничего не исправляли в июле и молча добавляли $2,000 в август. Ни одну из этих ошибок нельзя было найти рассуждениями. Все они нашлись запуском системы на реальных данных каждый день.

Как уменьшить работу модели до минимума

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

  • Каждое число вычисляется в коде. Проценты концентрации, возрастные группы, обнаружение дубликатов счетов, порог существенности — вся арифметика в Python, передаётся специалисту. Модель решает, имеет ли что-то значение. Её никогда не просят определить, истинно ли что-то, потому что её никогда не просят произвести цифру.
  • Разрешение объявляется, а не выводится. Первая версия считала молчание специалиста о находке как "исправлено". Это закрыло три живых просроченных счёта, которые всё ещё были не оплачены. Теперь специалист должен назвать, что он считает разрешённым, а молчание записывается как наблюдение.
  • Неудачный специалист ничего не закрывает. Каждый владеет набором типов находок; только те, чей владелец успешно завершился, могут быть закрыты.
  • Находки дедуплицируются по sha256(kind + ":" + subject), поэтому счётчики значат что-то: overdue_invoice/1024 виден 43 раза, открыт 5 дней; revenue_gap/net_income — 38 раз, открыт 5 дней.

Дашборд можно собрать за полдня. А запись, показывающая один и тот же счёт, наблюдаемый 43 раза за семь дней подряд, нельзя собрать задним числом.

Где модель действительно ошиблась

Три раза, и все три были пойманы чтением её вывода, а не какой-то системой защиты:

  1. CFO правильно процитировал цифры, но затем утверждал причину, которую книги не устанавливали: "мы покупаем материалы и забываем выставлять счета". Его правила теперь разделяют, что он знает, и что он теоретизирует, и он говорит, что есть что.
  2. Аналитик написал £, потому что вывел валюту из бизнеса по ландшафтному дизайну. Я исправил промпт. Он сделал это снова. Я исправил промпт сильнее. Затем я сдался и исправил в коде, потому что str.translate не может подвести, а правило промпта, которое уже дважды провалилось, не заслуживает третьего шанса.
  3. Аналитик однажды описал "полностью статичный месяц, пустой журнал" для месяца с доходом $6,150 — потому что он был узлом цепочки и получил только список действий. Теперь он получает скорректированный P&L.

Последний случай самый поучительный. Это не была ошибка модели. Это была моя ошибка.

Практический вывод: что делать прямо сейчас

Начните с малого: возьмите один бизнес-процесс, например, ежедневную проверку кассы, и реализуйте его как граф с жёстко заданным сбором данных. Убедитесь, что каждый числовой факт вычисляется кодом, а модель только интерпретирует. Затем добавьте человеческое одобрение через RequestInput, и только потом расширяйте до полного закрытия месяца. Помните: автономия — это не безграничные полномочия. Останавливайтесь перед изменениями книг, и вы получите систему, которой можно доверять.

#ИИ-агенты#автономные системы#ADK#финансы#малый бизнес
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →