# AI CRM · Недвижимость `01 · ai-crm · Delivered` Production AI-агент с MCP-style 19-endpoint tool API, audit trail и operator handoff. Квалифицирует лидов на недвижимости, делает research, бронирует просмотры. **Скоуп:** Соло · 7 недель **Роль:** Fullstack-платформа с автономным Telegram-агентом **Видео:** [YouTube](https://www.youtube.com/watch?v=0pK0MmDL0rc) · [RuTube](https://rutube.ru/video/private/23fbb6db856a945cc2357befda717669/?p=JVCWjl-ogS4oKbeKhEywnw) ## Разбор видео Production AI-агент ведёт чат с клиентом в Telegram, операторы супервизят через админку с capability-моделью прав — Канбан-пайплайн, календарь, квалификация лидов, глубокий ресёрч проектов, бронирование просмотров, передача оператору. Каждый AI-вызов трекается: токены и стоимость видны по клиенту, отдельно для AI, общающегося с клиентом, и помощника оператора. CRM с AI-риелтором для агентств недвижимости — клиенты переписываются с ним в Telegram, а команда видит всю работу в одном окне. Дашборд показывает сделки, лидов и нагрузку команды. Канбан-доска ведёт пайплайн — перетащите карточку между этапами, и история сохранится сама. Каждая карточка живёт и в календаре. Фильтруйте по оператору или доске, переключайтесь между месяцем, неделей или днём, открывайте любое событие — детали и связанные карточки на месте. Из карточки переходим сразу в чат. AI квалифицирует лида, находит подходящие проекты, проводит глубокий ресёрч и отправляет интерактивную карточку объекта прямо в Telegram. Клиент смотрит фото, юниты и отчёты не выходя из приложения. Когда клиент готов, AI бронирует просмотр и сохраняет заметку с контекстом для команды. Когда за дело берётся человек — AI останавливается. У операторов есть свой AI-помощник внутри системы — спросите про клиента, и он вернётся с кратким резюме и готовым ответом, опираясь на всю историю переписки. Каждый запрос отслеживается: токены и стоимость видны по каждому клиенту, отдельно по AI, который общается с клиентом, и отдельно по помощнику оператора. Система также поддерживает светлую и тёмную темы и набор акцентных цветов. --- ## Контекст > Звонки и просмотры ведут операторы. Всё остальное делает AI. Агентства недвижимости, работающие на Telegram-inbound, принимают сообщения круглосуточно. Объём — больше того, что небольшая команда операторов способна вытянуть. Каталоги в тысячи проектов и десятки тысяч юнитов лежат за пределом того, что человек держит в голове в диалоге. Готовые чат-боты отвечают одним FAQ и встают; никто не доводит покупателя от первого сообщения до забронированной встречи. Разделение фиксированное: операторы ведут звонки и просмотры; всё остальное — квалификация, глубокий research по проекту и району, бронирование под расписание операторов — работает без них. ## Факты | | | |---|---| | **Скоуп** | 7 недель соло | | **Поверхности** | CRM-админка + Telegram WebApp (1 репо · 2 Vite entry) | | **Каталог** | Тысячи проектов · десятки тысяч юнитов · синк из GenieMap · raw_payload сохранён | | **Agent tools** | 19 собственных эндпоинтов + 5 callback’ов · Bearer-auth · единый envelope | | **Модель прав** | 15 capability-ключей | | **Статус** | Delivered · 20 pytest-модулей · structlog · respx | ## Архитектура ### Жизненный цикл сообщения ```text 1 Клиент Сообщение в Telegram │ 2 tg_bot │ POST /api/v1/conversations/messages/ [BOT_SHARED_TOKEN] ▼ 3 Backend (Django/DRF) │ upsert Conversation + Message[RECEIVED] │ Celery.dispatch_to_n8n_workflow.delay() ▼ 4 Celery worker │ POST {N8N_BASE_URL}/{webhook} [N8N_API_KEY] ▼ 5 n8n ──► LLM │ tool_call (projects-search, send-webapp, …) ▼ Agent Tools API [Bearer] │ data {data, meta, errors} ▼ LLM ──► финальный текст ответа │ 6 Backend ◄── callback /api/v1/integrations/n8n/ [N8N_CALLBACK_TOKEN] │ Message[AWAITING_CALLBACK → READY_TO_SEND] ▼ 7 Celery worker │ POST /bridge/send [TG_BRIDGE_TOKEN] ▼ 8 Telegram → Клиент Message[SENT] ``` **Состояния сообщения (7).** Линейный путь: RECEIVED → PROCESSING → AWAITING_CALLBACK → READY_TO_SEND → SENT. Ветки: AWAITING_OPERATOR (AI-toggle off в середине диалога), FAILED (любая n8n-ошибка). **Зачем отдельный bridge.** Telegram-сессию бота держит один процесс — tg_bot. Backend не открывает свою сессию; исходящее уходит через HTTP-bridge внутри того же контейнера. Backend остаётся stateless относительно Telegram. **Где живёт AI.** Внутри шага 5 — LLM tool-loop живёт в n8n. Backend обслуживает и callback’и, и сами tool-вызовы LLM в процессе loop’а. (Зачем тут n8n — см. §04-Decisions.) ### Компонентная схема ```text External tg_bot frontend (admin · webapp) ──────── ────── ─────────────────────────── Telegram ◄─────► aiogram + bridge React · Vite (×2 entries) │ │ BOT_SHARED ──┤ │ HTTPS · /api/v1 TG_BRIDGE ──┤ │ ▼ ▼ ┌───────────────────────────────────────────┐ │ Backend (Django · DRF · structlog) │ │ Единый envelope: {data, meta, errors} │ └────┬─────────┬──────────┬─────────────────┘ │ │ │ ▼ ▼ ▼ Postgres 16 Redis 7 Celery worker + beat │ N8N_API_KEY ▼ n8n + n8n-worker (LLM tool-loops) │ N8N_CALLBACK_TOKEN ▼ Backend.callback ``` **Девять Docker-сервисов.** postgres · redis · backend · celery · celery-beat · tg_bot · frontend · n8n · n8n-worker. (n8n-worker — отдельный процесс-исполнитель; разгружает оркестратор от долгих flow.) **Модель доверия.** Каждый межсервисный вызов несёт явный shared secret — bot-bridge, n8n outbound, n8n callback, agent-tool Bearer. Ни один сервис не доверяет другому только по тому, что они в одном compose-файле. **Разделение фронта.** Один Vite-репо, два entry HTML — /admin (CRM) и /webapp/:projectId (Telegram WebApp). Компоненты и API-клиент общие. WebApp работает внутри Telegram через WebApp SDK; контроль доступа — на тех же backend endpoint-правилах, что и admin. ### Доменное ядро ```text CATALOG CONVERSATIONS ─────── ───────────── Project Conversation ├── Unit (×N) · status ├── ResearchBlock × 4 └── Message (×N, 7-state lifecycle) │ · core Note (manual + 3 system cards) │ · market & demand OperatorAssistantSession │ · legal & ops │ · dynamics & news ├── Amenity (×N) └── District (FK) KANBAN ────── USERS Board ───── └── Column User · capability strings └── Card · telegram_link └── Activity UserInvite CALENDAR GENIEMAP ──────── ──────── Calendar ProviderSyncLog ├── CalendarEvent · raw_payload preserved └── ScheduleRule · scheduled syncs ``` **37 моделей в 7 apps.** Домен разнесён по bounded context’ам: catalog · conversations · kanban · calendar · users · geniemap · auth. 5 apps без моделей (core, agent_tools, dashboard, webapp, echo). Никакой god-table — каждая сущность отвечает за одну работу. **Status-enum’ы везде, не bool-флаги.** У каждого домена свой типизированный status — Project (launch · available · sold_out · cancelled), Unit (available · reserved · sold · on_hold), Message (7-стейтная machine), Kanban Card (open · closed_won · closed_lost). Одна проецируемая поверхность на домен вместо парада `archived: bool`, `is_active`, `is_draft`. **raw_payload сохранён.** Каждая сущность, синканная из GenieMap, хранит исходный JSON рядом с нормализованными колонками. Переживает любой mapping drift, делает re-ingest дешёвым. ### Топологии агентов ![Четыре research-агента-специалиста (core, market & demand, legal & ops, dynamics & news), у каждого свой LLM, общий tool web-search.](https://ilyadev.xyz/private/airea-n8n-specialists.png) *Кластер специалистов, общий tool* ![Один chat-агент с двенадцатью tools — projects/districts search и research, calendar availability и create, send-webapp, admin-notify, context-save, user-note CRUD, web-search.](https://ilyadev.xyz/private/airea-n8n-omnimodel.png) *Omnimodel, двенадцать tools* **За счёт чего это работает.** 19 эндпоинтов модель-агностичны и оркестратор-агностичны. Протестировано с GPT и Gemini против того же tool API. ## Ключевые инженерные решения ### 01 · Capability-overrides поверх role-пресетов **Решение.** Три role-пресета (OWNER · ADMIN · OPERATOR) задают дефолтный набор прав; per-user extra_capabilities и revoked_capabilities дают точечные override. Проверки идут через hasCapability(), не role == "admin". **Почему.** Реальные комбо прав не ложатся в 3-ролевой enum — «дашборд — да, управление пользователями — нет, AI-toggle — да» норма. Чистый role-table взорвался бы; чистые capabilities теряют удобство дефолта. Override-модель сохраняет и то, и другое. **Цена.** Два слоя при дебаге доступов — пресет + override’ы. Дефолтные пресеты пришлось зафиксировать в коде ради UX out-of-the-box. ### 02 · 19 собственных agent tools на Bearer + единый envelope **Решение.** Все AI tool-вызовы идут через /api/v1/agent/tools/... со своим Bearer-токеном; каждый ответ — единый envelope {data, meta, errors}. **Почему.** n8n работает без пользовательского контекста — сессионная авторизация не подходит. Bearer-edge даёт LLM минимум прав, а единый envelope делает tool-loop стабильным. **Цена.** Несколько эндпоинтов дублируется по аудитории (оператор vs AI) — две поверхности вместо одной с двойной авторизацией. ### 03 · n8n ради no-code surface **Решение.** Логика агента живёт в 4 n8n-workflow; LLM tool-вызовы расходятся в собственный tool-API, callback возвращается через webhook. **Почему.** Constraint: no-code surface для правок agent-flow. n8n на тот момент был очевидным кандидатом: популярный, зрелый, с нативным tool-calling и webhook-примитивами. Альтернатива — кастомный admin-UI поверх in-process tool-loop — удвоила бы scope MVP. **Цена.** Логика промптов и оркестрации размазана между n8n-flow и собственным tool-API; n8n-worker — отдельный сервис. Ретроспективная оценка — в §06-Lessons. ### 04 · Research как state-machine из 4 блоков **Решение.** Research проекта разбит на 4 блока (core, market & demand, legal & ops, dynamics & news), каждый цикличен EMPTY → PENDING → READY | FAILED. Callback’и от n8n приносят каждый блок в backend по мере готовности, и research-панель проекта рендерит их блок за блоком по мере поступления. **Почему.** Глубокий research — несколько минут; блокировать tool-loop LLM нельзя, чат в Telegram замёрзнет. Поблочный UX показывает частичные результаты. **Цена.** Каждый блок — со своим промптом и источниками поиска; поддержка research-пайплайна растёт линейно от числа блоков. ## Стек | | | |---|---| | **Backend** | Python · Django · DRF · Celery · structlog · aiogram | | **Frontend** | React · Vite · Tailwind 4 · Radix UI · MapLibre GL · react-router 7 | | **Инфра** | PostgreSQL · Redis · Docker Compose | | **AI · оркестр.** | Gemini, OpenAI · n8n (model-agnostic) | | **Agent surface** | 19 собственных tool-эндпоинтов + 5 callback’ов · Bearer-auth · envelope {data, meta, errors} | | **Объём** | 12 Django-apps · 224 фронт-модуля · 20 pytest-модулей · ~56K LOC | ## Уроки и статус ### Оставлю как есть - Capability-overrides поверх role-пресетов — базовая роль даёт здравый дефолт, per-user extra/revoked-ключи закрывают длинный хвост без новых ролей. - Единый envelope `{data, meta, errors}` на каждый API-ответ — скучный tool-loop это фича, а не баг. - UX операторской консоли — канбан + чат + календарь в одном шелле, с on-request передачей живому оператору внутри того же треда. - Дисциплина audit-trail — X-Request-ID, проброшенный через structlog на каждом межсервисном шаге. Без него debug LLM tool-loop разваливается. ### Поменял бы - Django был выбран по рефлексу. Реально использованная поверхность — ORM, роутинг, организация по apps — осталась тонкой, а DRF добавлял boilerplate на каждый endpoint, и class-based permissions конфликтовали с capability-строками. FastAPI + Pydantic + async SQLAlchemy лучше легли бы на API-only форму, которой проект в итоге оказался. - n8n себя не отбил — большинство правок флоу всё равно жили в промптах или в tool API. Сегодня сделал бы in-process tool-loop и тонкий кастомный UI ровно под те части, которые реально редактируются. - 9 Docker-сервисов — много для MVP. Сегодня: n8n + n8n-worker уходят (in-process tool-loop), celery-beat сворачивается в celery как periodic, frontend в проде — статика за backend, а не dev-контейнер. Получается 4 сервиса: postgres, redis, backend, celery. Собран, развёрнут, передан клиенту по итогу контракта; code walkthrough по запросу. --- Источник: https://ilyadev.xyz/cases/ai-crm (HTML) · /cases/ai-crm.ru.txt (этот файл) Дальше: 02 — AI-агент для ресторанного склада → https://ilyadev.xyz/cases/ai-warehouse.ru.txt Индекс: https://ilyadev.xyz/llms-ru.txt — полный список кейсов Автор: Илья Казанцев — https://ilyadev.xyz/index.ru.txt