Abstract

ИИбан Бот — мультиагентная система на CrewAI. Deputy Secretary отвечает в Telegram ЛС, Garden Manager (4 агента) ведёт цифровой сад в Matrix. Один LLM на vLLM (Gemma 4 12B), память на ChromaDB. Работает на локальном GPU, без облака.


Архитектура

crewai-bot.service (один процесс, systemd)
├── Telegram Deputy Secretary (CrewAI, 1 агент)
│   ├── Батчинг 30 сек — один ответ на все сообщения
│   ├── ChromaDB: per_user, conversations, garden_notes
│   ├── radio_poster — авто-постинг радио в канал
│   └── garden_poster — авто-постинг заметок 3x/день
├── Matrix Garden Manager (CrewAI, 4 агента)
│   ├── Router → Searcher/Writer/Publisher
│   └── garden_pipeline — drafts/ → classify → format → link → publish
└── Obsidian headless (systemd, Xvfb :99)
    └── obsidian-cli socket → garden_tools

Один LLM — /models/gemma-4-12b через vLLM (OpenAI-compatible API). Эмбеддинги — nomic-embed-text через Ollama. Память — ChromaDB, 3 коллекции:

КоллекцияЧто хранит
per_userФакты о людях (фильтр по user_id)
conversationsСемантический поиск по прошлым диалогам
garden_notesRAG по заметкам сайта

global collection удалена

При рефакторинге убрана — создавалась, но не использовалась. 4 коллекции → 3.


Агенты

Deputy Secretary (Telegram)

Отвечает в Telegram ЛС от имени mrgnl

Кратко, прямо, с сарказмом. Друг, не корпоративный бот. Подпись: — ии товарищ mrgnl.ru

Инструменты (5):

  • recall_user_memories — вспомнить всё о собеседнике
  • save_user_memory — сохранить новый факт
  • search_user_memories — семантический поиск по памяти
  • get_now_playing — что играет на радио
  • send_report — написать mrgnl в контрольный чат

max_iter: 3 (до 3 LLM-вызовов на сообщение).

Батчинг 30 секунд — Deputy ждёт 30 секунд тишины после последнего сообщения. Все сообщения за это время объединяются в один батч → один ответ.

Как батчинг работает

  1. Человек шлёт «привет» → буфер: [привет]
  2. Через 5 сек: «как дела?» → буфер: [привет, как дела?]
  3. Через 10 сек: «что делаешь?» → буфер: [привет, как дела?, что делаешь?]
  4. 30 секунд тишины → один ответ на всё

Garden Manager (Matrix, 4 агента)

Принимает текст в Matrix DM от имени @aiban и публикует заметки в цифровой сад. 4 узких агента через CrewAI sequential process:

АгентToolsmax_iterЗадача
Garden Router01Читает сообщение, определяет intent
Garden Searcher33search, list, show
Garden Writer55create_draft, add_to_draft, show_draft, edit_note, cancel_draft
Garden Publisher33publish_draft, delete_note, update_links

Router → специалист. Каждый агент видит только свои tools — 16K контекста хватает с запасом.

Почему 4 агента вместо 1

Раньше был 1 агент с 12 tools в 16K контексте. Gemma 12B путалась, вызывала не те tools, зависала. Разбили на 4 узких — каждый со своими 2-5 tools. 12B справляется.


Сценарии использования

1. Кто-то написал в Telegram ЛС

Человек пишет mrgnl в Telegram → Deputy видит → вспоминает факты о человеке → отвечает от имени mrgnl → сохраняет реплику в историю.

Задержка ответа имитирует живого человека:

СитуацияЗадержка
Незнакомец2–4 минуты (даёт mrgnl время ответить самому)
Знакомый30–90 секунд
Активный диалог5–15 секунд

Если mrgnl ответил сам — Deputy молчит.

2. Фото в ЛС

Человек присылает фото → vLLM vision описывает картинку → Deputy реагирует естественно (эмоция, 1–2 предложения).

3. Публикация заметки (Matrix)

mrgnl пишет @aiban в Matrix DM → Garden Router определяет intent → передаёт нужному агенту:

  1. Writer — создаёт драфт в content/drafts/, оформляет в Markdown
  2. Searcher — ищет дубли через RAG по существующим заметкам
  3. Publisher — публикует: Quartz build → rsync на сервер → переиндексация RAG
  4. Отвечает с URL

4. Радио

Deputy знает про radio.mrgnl.ru. Если спросят «что слушаешь?» — вызовет get_now_playing, ответит небрежно. Не упоминает радио каждый раз.

5. Авто-постинг

garden_poster.py — фоновый поток, 3 раза в день (09:00, 15:00, 21:00):

  1. Берёт случайную заметку из сада (без index.md, drafts/)
  2. vLLM генерирует тизер (1-2 предложения на русском)
  3. Постит в Telegram-канал: заголовок + тизер + ссылка на notes.mrgnl.ru
  4. Запоминает последние 10 заметок — без повторов

radio_poster — авто-постинг треков из Маргинал в Telegram-канал.


Obsidian headless

Obsidian запущен на сервере через systemd + Xvfb:

[Service]
Type=simple
Environment=DISPLAY=:99
ExecStart=/home/orca/.local/share/obsidian/app/obsidian --no-sandbox /home/orca/digital-garden
Restart=always

Xvfb на display :99, Obsidian без GUI, CLI socket работает. Все garden_tools (search, list, show, create, edit, publish) функциональны.


Оптимизации для 12B + 16K контекста

12B путается в длинных цепочках tool calls

Каждая оптимизация — компромисс между качеством и ресурсами. Контекст на вызов: ~3000 токенов из 16384. Запас есть.

ПараметрЗначениеПочему
max_iter3 (Deputy), 5 (Writer)12B путается в длинных цепочках tool calls
max_tokens1024Короткие ответы, меньше жрать контекст
History6 сообщений~1200 токенов вместо ~2000
Dialog analysisубранБыл +1 LLM вызов, модель и так понимает контекст
Tools у Deputy56+ tools — модель путается, вызывает не те
Garden Manager4 агента × 2-5 toolsВместо 1 агента с 12 tools

Чистка мёртвого кода

При рефакторинге удалено ~40% кодовой базы:

УдаленоЧто
agents/_voice.pyВесь файл — voice listener никогда не активировался
garden_manager agent12 tools, мёртвый — заменён на Garden Crew
create_garden_crew()Мёртвая фабрика
ChromaDB global collectionСоздавалась, не использовалась
26 CRUD-функцийstate.py — определены, не вызывались
7 SQLite таблицschema оставлена для совместимости

Подробнее — статья про рефакторинг.


Связанные статьи

  • ИИбан — продуктовое описание ИИбана (без технических деталей)
  • vLLM — LLM бэкенд
  • CrewAI — фреймворк агентов
  • CrewAI Bot рефакторинг — подробности рефакторинга (4 агента, батчинг, чистка)
  • ИИбан-DJ — ИИ-диджей радио (другая система, общий LLM)
  • Маргинал — радио
  • Docker — контейнеры