Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Telegram Agent — ИИ-администратор медицинского центра

Телеграм-бот, который принимает записи к специалистам на естественном языке. Пользователь пишет «Хочу к стоматологу в Казани на завтра утром», а LLM-агент сам находит врача, проверяет свободные слоты в расписании и создаёт запись — без кнопок, меню и пошаговых форм.

Ключевая идея проекта: бизнес-логика живёт в обычном REST API, а языковая модель получает к ней доступ через MCP (Model Context Protocol) — как к набору инструментов. Модель не знает про базу данных и не пишет SQL; она вызывает те же операции, которые доступны любому другому клиенту API.

Стек: Python 3.11 · aiogram 3 · LangChain + Ollama (qwen3:8b, локальная LLM) · MCP (mcp, mcp-use) · Django 4.2 + Django REST Framework · SQLite · Docker Compose


Что бот умеет

  • Найти специалиста по специализации, городу или категории услуг
  • Показать свободные слоты врача на конкретную дату (сетка по 30 минут, с учётом графика работы и уже занятых часов)
  • Записать на приём — время окончания рассчитывается автоматически из длительности услуги
  • Подтвердить, отменить или закрыть запись (pending → confirmed → completed / cancelled)
  • Ответить на вопросы об услугах, ценах, расписании и категориях
  • Держится в рамках роли: системный промпт ограничивает агента темой медцентра и записей

Каталог, расписания и записи параллельно доступны через админку Django и Swagger UI.


Как это устроено

flowchart LR
    subgraph bot["telegram_bot/"]
        BOT["bot.py<br/>aiogram, polling"]
        AGENT["MCPAgent<br/>до 30 шагов"]
        MCP["mcp_server.py<br/>6 tools + 12 resources"]
        BOT <--> AGENT
        AGENT <-->|"MCP / stdio"| MCP
    end

    subgraph api["api/"]
        DRF["Django REST API<br/>6 ViewSet'ов, бизнес-правила"]
        DB[("SQLite")]
        DRF <--> DB
    end

    TG(["Telegram"]) <--> BOT
    AGENT <-->|"промпт / ответ"| OLLAMA["Ollama<br/>qwen3:8b"]
    MCP <-->|"REST / JSON"| DRF
Loading

Полный цикл одного запроса:

sequenceDiagram
    actor U as Пользователь
    participant B as bot.py
    participant A as MCPAgent
    participant M as MCP-сервер
    participant D as Django API

    U->>B: «Запиши к стоматологу на завтра в 11:00»
    B->>A: текст + Telegram ID отправителя
    A->>M: search_specialists(specialization, city)
    M->>D: GET /api/specialists/?search=...
    D-->>M: список специалистов
    M-->>A: результат
    A->>M: get_available_slots(specialist_id, date)
    M->>D: GET /api/specialists/{id}/available_slots/
    D-->>M: свободные слоты
    M-->>A: результат
    A->>M: create_appointment(...)
    M->>D: GET /api/services/{id}/ — длительность, расчёт end_time
    M->>D: POST /api/appointments/
    Note over D: validate(): пересечения<br/>и график работы
    D-->>M: созданная запись
    M-->>A: результат
    A-->>B: текст ответа
    B->>U: «Записал: завтра 11:00–11:30»
Loading

1. Бот (telegram_bot/bot.py) — тонкий слой. Принимает сообщение, добавляет к нему Telegram ID отправителя (чтобы агент мог связать собеседника с клиентом в базе), отдаёт MCPAgent. Ответ модели чистится от блока рассуждений <think>...</think>, которые генерирует qwen3, и уходит пользователю.

2. АгентMCPAgent из mcp-use поверх ChatOllama. До 30 шагов цикла «рассуждение → вызов инструмента → анализ результата». Модель работает локально в Ollama: данные пациентов не уходят во внешние API.

3. MCP-сервер (telegram_bot/mcp_server.py) — мост между моделью и API, запускается через stdio.

  • 6 tools (действия): search_specialists, get_available_slots, create_appointment, confirm_appointment, cancel_appointment, complete_appointment
  • 12 resources (чтение): специалисты, их услуги и расписания, услуги, категории, записи, клиенты
  • Docstring каждого инструмента — это и есть его спецификация для модели: по ним LLM понимает, что принимает аргумент date в формате YYYY-MM-DD, а start_timeHH:MM.
  • Самая содержательная логика здесь — create_appointment: инструмент сам подтягивает длительность услуги и вычисляет end_time, чтобы модели не приходилось считать время.

4. Django REST API (api/) — источник правды и место, где живут инварианты.

  • Модели: ServiceCategory, Specialist, SpecialistSchedule, Service, Client, Appointment
  • GET /api/specialists/{id}/available_slots/?date=... — генерация слотов: берётся график на нужный день недели, режется на интервалы по 30 минут, из них вычитаются пересечения с активными записями
  • AppointmentSerializer.validate() — двойная защита: запись не создастся, если время пересекается с существующей (pending/confirmed) или выходит за пределы графика работы специалиста. Даже если модель ошибётся, API её остановит.
  • Swagger/ReDoc через drf-yasg

Почему MCP, а не function calling напрямую. Инструменты описаны один раз на стороне сервера и не привязаны ни к боту, ни к конкретной модели. Тот же mcp_server.py можно подключить к другому клиенту (Claude Desktop, IDE, веб-чат), а модель в боте заменить одной строкой — контракт не меняется.


Схема данных

Модель Назначение
ServiceCategory Категории услуг (Красота, Здоровье, Фитнес)
Specialist Врач/мастер: специализация, город, фото, привязка к User
SpecialistSchedule График по дням недели (unique_together на специалиста и день)
Service Услуга: цена, длительность в минутах, специалист, категория
Client Клиент: телефон, email, город, telegram_id
Appointment Запись: клиент, услуга, специалист, дата, интервал, статус

Запуск

Требования

  • Docker и Docker Compose
  • Ollama с загруженной моделью:
    ollama pull qwen3:8b
  • Бот в Telegram, созданный через @BotFather

1. Переменные окружения

Создайте .env в корне проекта:

BOT_TOKEN=<токен от BotFather>
OLLAMA_URL=http://host.docker.internal:11434

host.docker.internal позволяет контейнеру достучаться до Ollama, запущенной на хосте (macOS/Windows). На Linux используйте http://172.17.0.1:11434 или запустите Ollama в той же Docker-сети.

2. Поднять сервисы

docker compose up --build

3. Применить миграции и наполнить базу

docker compose exec api python manage.py migrate
docker compose exec api python manage.py create_test_data

Команда create_test_data создаёт категории, специалистов с расписаниями, услуги и тестовых клиентов, а также администратора admin / admin.

4. Проверить

Локальный запуск без Docker

python -m venv venv && source venv/bin/activate
pip install -r requirements.txt

# терминал 1 — API
cd api && python manage.py migrate && python manage.py create_test_data
python manage.py runserver 8000

# терминал 2 — бот (MCP-сервер поднимается им автоматически через stdio)
cd telegram_bot && python bot.py

Примеры диалога

👤 Есть стоматологи в Казани?
🤖 Да, принимает Иванов Пётр Сергеевич — стоматолог, Казань.
   Услуги: консультация (30 мин, 1500 ₽), лечение кариеса (60 мин, 4500 ₽).

👤 Какие есть окна на 15 июня?
🤖 Свободно: 09:00–09:30, 09:30–10:00, 11:00–11:30, 14:00–14:30.

👤 Запиши на консультацию в 11:00
🤖 Записал: 15 июня, 11:00–11:30, консультация у Иванова П. С.
   Статус — ожидает подтверждения.

Технические решения и компромиссы

Что сделано осознанно:

  • Локальная LLM. Медицинские данные не покидают инфраструктуру — для домена это не опция, а требование.
  • Валидация на стороне API, а не промпта. LLM недетерминирована; пересечения слотов и выход за график ловятся в сериализаторе, где ошибиться нельзя.
  • Расчёт end_time внутри MCP-инструмента. Арифметика со временем — плохая задача для модели, поэтому она отдана коду.
  • Разделение tools и resources. Действия, меняющие состояние, отделены от чтения — модель не может «случайно» что-то создать, читая справочник.

Что осталось за рамками текущей версии:

  • Аутентификация: permission_classes = [AllowAny] и placeholder-токен в MCP-сервере — рабочая конфигурация для демо, но перед продакшеном нужен реальный Token/JWT-слой.
  • mcp_server.py ходит на http://localhost:8000 — при запуске в Docker Compose адрес нужно поменять на http://api:8000 (или вынести в переменную окружения).
  • SQLite и runserver — dev-конфигурация; для боевого контура нужны PostgreSQL и ASGI-сервер.
  • telegram_id есть в модели Client, но не попал в ClientSerializer — связка «Telegram-пользователь ↔ клиент в базе» пока делается через передачу ID в промпте.
  • Нет истории диалога между сообщениями: каждый запрос обрабатывается независимо.

Структура проекта

.
├── api/                        # Django REST API
│   ├── boba/                   # settings, urls, swagger
│   └── booking/
│       ├── models.py           # 6 моделей предметной области
│       ├── serializers.py      # валидация пересечений и графика
│       ├── views.py            # ViewSet'ы, генерация слотов, экшены статусов
│       └── management/commands/create_test_data.py
├── telegram_bot/
│   ├── bot.py                  # aiogram + MCPAgent + Ollama
│   └── mcp_server.py           # 6 tools + 12 resources поверх REST API
└── docker-compose.yaml

About

AI assistant for appointments in Telegram (cousework)

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages