AI-агент, который принимает звонки пациентов клиники круглосуточно
<p>Небольшая клиника на 10–15 кабинетов теряет пациентов не из-за качества приёма, а из-за того, что в 20:15 или в воскресенье утром никто не берёт трубку. Голосовая почта не конвертит — человек, которому нужна запись сегодня-завтра, просто набирает следующую клинику в выдаче. Мы в ITFresh закрываем это архитектурой на стыке телефонии, распознавания речи и LLM: звонок принимает не голосовое меню с тремя уровнями вложенности, а диалоговый агент, который слышит живую речь, уточняет симптом/врача/удобное время, пишет слот в МИС и кладёт напоминание за сутки и за два часа. Ниже — наша методология сборки такой системы от телефонной инфраструктуры до комплаенса по 152-ФЗ, без воды и без пересказа чужих кейсов.</p>
Почему это не «чат-бот на сайте», а задача телефонии
Когда собственник клиники говорит «хочу AI-бота», в 80% случаев речь идёт про виджет на сайте. Виджет не решает основную проблему: 60–70% первичных обращений в медицинскую организацию до сих пор идут звонком, а не через форму — так исторически устроен рынок медуслуг в РФ, пациент хочет услышать голос и назвать симптом словами, а не заполнять поля. Значит, точка входа — АТС, а не сайт.
Отсюда и требования к архитектуре, которые сильно отличаются от типового чат-бота:
- Задержка ответа должна укладываться в разговорный темп — пациент терпит паузу до 1–1,5 секунды, дальше он либо перебивает, либо кладёт трубку;
- Система обязана понимать перебивание (barge-in) — если пациент начал говорить поверх синтезированной речи, воспроизведение должно остановиться немедленно, а не доиграть фразу;
- Голос обрабатывает персональные данные о здоровье — это врачебная тайна по ст. 13 323-ФЗ, а не просто ПДн по 152-ФЗ, требования к хранению строже;
- Живой администратор должен иметь возможность перехватить звонок в любой момент диалога, а не только по кнопке «0» в самом начале.
Поэтому мы строим не надстройку над CRM-виджетом, а полноценный голосовой шлюз на базе АТС с интеграцией STT/TTS и LLM-ядра, который замещает часть работы администратора, а не имитирует её текстом.
Архитектура: пять слоёв между звонком пациента и записью в МИС
Мы собираем систему из пяти слоёв, каждый заменяем независимо — это принципиально: если завтра клиника захочет сменить телефонного провайдера или МИС, остальные слои не трогаем.
| Слой | Наш выбор | Версия / параметр | Зачем именно так |
|---|---|---|---|
| Телефония / АТС | Asterisk + FreePBX | Asterisk 22, FreePBX 17 (Distro 12.7.8-2408-1.sng12) | ARI (Asterisk REST Interface) вместо устаревшего AGI — REST-события канала, легко подружить с внешним AI-сервисом на Python/Node |
| Распознавание речи (STT) | Yandex SpeechKit STT v3, потоковый режим | языковая модель ru-ru, session_options с partial_results=true | данные не покидают юрисдикцию РФ — важно для 152-ФЗ и 323-ФЗ; поддержка медицинской терминологии на русском лучше, чем у большинства зарубежных STT |
| Диалоговое ядро | Claude API (Anthropic) | Claude Sonnet 5 — основной диалог, Claude Haiku 4.5 — предварительная классификация intent/срочности | tool use (function calling) для структурированных действий (создать слот, найти врача), prompt caching для системного промпта с расписанием клиники |
| Синтез речи (TTS) | Yandex SpeechKit TTS | POST tts.api.cloud.yandex.net/speech/v1/tts:synthesize, лимит тела запроса 15 КБ | низкая задержка первого байта аудио, голоса с медицинской дикцией без «роботности», которая пугает пожилых пациентов |
| МИС / CRM | API клиники (обычно МИС на базе 1С или профильная медицинская МИС) | REST/OData, идемпотентные вызовы create_appointment с ключом дедупликации | единственный слой, где реально хранится ФИО+диагноз+телефон — LLM его не видит напрямую, подробности в разделе про комплаенс |
Ключевое архитектурное решение, которое мы отстаиваем перед клиентами: LLM — это процессор диалога, а не хранилище данных пациента. Все персональные данные и медицинские сведения живут в МИС и в локальной прослойке-шлюзе, а Claude получает только то, что нужно для текущего шага диалога, через tool calls с токенизированными идентификаторами.
Телефония: почему ARI, а не AGI, и как выглядит маршрут звонка
Классический путь интеграции с Asterisk — AGI-скрипт, который блокирует канал и работает построчным протоколом через stdin/stdout. Для голосового AI-агента это неудобно: AGI не даёт нормально обрабатывать событие «пациент начал говорить поверх ответа бота» без городить костыли поверх ChannelTalkingStart. Мы используем ARI: Asterisk публикует события канала (StasisStart, ChannelTalkingStarted, ChannelTalkingFinished, PlaybackStarted) в WebSocket, а внешний AI-сервис управляет каналом через REST-вызовы (answer, play, record, hangup, bridge). Это тот же паттерн, который сейчас рекомендует сама документация Asterisk для новых интеграций с внешними AI-сервисами — REST и событийная модель ложатся на архитектуру AI-пайплайна естественно, в отличие от AGI.
Минимальный маршрут звонка на входящий DID клиники в dialplan (extensions.conf) выглядит так:
[from-trunk-clinic]
exten => _X.,1,NoOp(Входящий звонок в клинику от ${CALLERID(num)})
same => n,Answer()
same => n,Set(CHANNEL(hangup_handler_push)=hangup-log,s,1)
same => n,Stasis(ai_reception_app,${EXTEN},${CALLERID(num)})
same => n,Hangup()
Приложение ai_reception_app — это наш ARI-клиент на Python (aioari), он же держит WebSocket на события Stasis и параллельно ведёт потоковую сессию к STT. Перехват живым администратором реализован через отдельную очередь FreePBX (Queue) с приоритетным каналом: если AI-сервис по внутреннему API получает команду escalate, он вызывает ARI channels/{id}/redirect на добавочный очереди администратора, передавая накопленный контекст диалога текстом через X-заголовок SIP INFO — администратор видит на экране, о чём уже говорили, а не начинает разговор с нуля.
Распознавание и синтез речи: параметры, которые реально влияют на конверсию
Для потокового распознавания мы используем SpeechKit STT v3 — в актуальной документации Яндекса это рекомендованная версия для новых проектов (v2 оставлена для обратной совместимости). Сессия открывается сообщением с session_options, где для медицинского ресепшена мы включаем два параметра осознанно:
- partial_results — получение промежуточных гипотез распознавания по мере речи пациента, а не только финального результата после паузы; это нужно, чтобы бот мог среагировать на barge-in и остановить проигрывание TTS до того, как пациент договорит фразу целиком;
- profanity filter выключен — в медицинском контексте отключение фильтра важно, потому что пациенты описывают боль и симптомы разговорной, иногда грубой лексикой, и фильтрация может исказить смысл жалобы, которую бот передаёт в карточку записи.
Языковая модель выбирается параметром model в session_options — для клиник мы тестировали как общую модель, так и подключение специализированного словаря терминов (наименования специальностей врачей, часто встречающиеся препараты и процедуры клиники) через списки хинтов распознавания; по нашей практике внедрений это снижает долю переспросов примерно на треть на первых 2–3 неделях эксплуатации, дальше выравнивается за счёт дообучения промпта диалогового ядра на реальных звонках.
Для синтеза речи (TTS) используем тот же контур SpeechKit — единый REST-эндпоинт POST https://tts.api.cloud.yandex.net/speech/v1/tts:synthesize, параметры передаются URL-encoded, тело ответа — поток аудио в выбранном формате (обычно opus для передачи в канал Asterisk без лишнего перекодирования). Голос подбираем не самый «бодрый» из каталога, а более медленный и нейтральный — для клиник с высокой долей пациентов 55+ агрессивный темп речи снижает распознаваемость бота собеседником и увеличивает число переспросов «повторите, пожалуйста».
Диалоговое ядро на Claude: system prompt, function calling, эскалация
Основной диалог ведёт Claude Sonnet 5 — баланс стоимости и задержки, который в текущей линейке (Opus 4.8 / Sonnet 5 / Haiku 4.5) оптимален для разговорного сценария: Opus избыточен по цене для короткого диалога «запись на приём», Haiku местами хуже держит контекст многошагового уточнения (специальность врача → дата → время → подтверждение). Haiku 4.5 мы используем отдельным вызовом на первом шаге — быстрая грубая классификация: это запись на приём, вопрос про цену, экстренный случай (требует немедленной эскалации на живого администратора, а не диалога с ботом) или жалоба — и в зависимости от класса выбираем дальнейший маршрут.
Запись слота, поиск свободного времени врача и подтверждение оформлены как tool use (function calling), а не как текст, который бот пытается распарсить сам. Модель обязана вызвать инструмент структурированным JSON, что резко снижает долю «галлюцинированных» записей на несуществующее время:
{
"name": "book_appointment",
"description": "Создаёт черновик записи на приём в МИС клиники по токенизированному ID пациента",
"input_schema": {
"type": "object",
"properties": {
"patient_ref": {"type": "string", "description": "токен пациента, не ФИО"},
"specialty": {"type": "string"},
"doctor_id": {"type": "string"},
"slot_start_iso": {"type": "string"},
"symptom_note": {"type": "string"},
"confidence": {"type": "number"}
},
"required": ["patient_ref", "specialty", "slot_start_iso"]
}
}
Системный промпт с расписанием, списком врачей и правилами клиники — статичная часть на 3–5 тысяч токенов, которая повторяется в каждом запросе звонка; здесь prompt caching (кэширование по официальной документации Anthropic снижает стоимость кэшированного входа на 90%) даёт заметную экономию именно на телефонии, где за смену идут десятки коротких диалогов с одним и тем же контекстом клиники.
Эскалация на живого администратора — не факультативная кнопка, а обязательный порог в логике маршрутизации. Мы закладываем следующие условия (значения ниже — оценка по практике внедрений, калибруются под конкретную клинику в первые недели):
| Условие | Порог | Действие |
|---|---|---|
| confidence инструмента book_appointment | < 0,7 | не создавать слот автоматически, предложить пациенту подтвердить голосом ещё раз |
| число подряд идущих переспросов STT | ≥ 2 по одному вопросу | перевод на очередь администратора с контекстом диалога |
| intent-классификатор (Haiku) вернул класс | «экстренный случай» | немедленный редирект на дежурного, без прохождения диалогового сценария записи |
| суммарная задержка ответа (STT+LLM+TTS) | > 2,5 с на трёх ответах подряд | деградация на резервный сценарий «оставьте номер, перезвоним» + алерт в мониторинг |
Комплаенс: 152-ФЗ, врачебная тайна и где физически живут данные пациента
Голосовой агент в медицине работает не с обычными персональными данными, а со сведениями, составляющими врачебную тайну по ст. 13 323-ФЗ «Об основах охраны здоровья граждан» — факт обращения, диагноз, симптомы. Это накладывает более жёсткие требования, чем стандартный 152-ФЗ: согласие на обработку нужно получать явно (в начале звонка бот проговаривает короткое уведомление о записи разговора и обработке данных для целей записи на приём — это же требование по факту записи разговоров, которое действует для колл-центров в целом), а раскрытие сведений третьим лицам ограничено кругом лиц, допущенных к медицинской документации.
Из этого вытекает конкретное архитектурное решение, которое мы закладываем в каждый проект: Claude API физически размещён за пределами РФ, поэтому ему нельзя передавать напрямую ФИО, телефон и диагноз пациента — это трансграничная передача ПДн, для медицинских данных избыточный риск, даже если формально согласие получено. Мы разрезаем поток данных на шлюзе перед LLM:
- шлюз (наш Python-сервис между ARI и Claude API) при первом звонке ищет пациента по номеру телефона в МИС и присваивает диалогу временный токен
patient_ref— именно он идёт в tool calls, а не телефон и не ФИО; - текст жалобы/симптома в диалоге с LLM максимально обезличивается там, где это не мешает логике записи — специальность врача и повод визита передаются, но не хранящиеся в МИС диагнозы из прошлых визитов;
- окончательное связывание токена с карточкой пациента и запись диагностических деталей происходит только на стороне шлюза при вызове API МИС — вне контура LLM;
- полная аудиозапись звонка хранится на инфраструктуре клиники (или на нашем контуре на территории РФ), не передаётся во внешние STT/TTS сверх необходимого для распознавания потока, и не логируется у поставщика LLM сверх стандартной политики хранения запросов API.
Отдельно фиксируем в договоре с клиникой срок хранения аудиозаписей и текстовых логов диалогов — по нашей практике ориентируемся на 3 года как разумный срок для медицинских организаций (пересекается со сроками хранения части медицинской документации), но точный срок клиника определяет сама в положении об обработке персональных данных и согласовывает с юристом; мы не выдаём это как нормативное требование, а как параметр конфигурации retention в шлюзе.
Интеграция с МИС и напоминания: цикл жизни записи
Запись, созданная ботом через book_appointment, попадает в МИС не сразу как подтверждённая, а как черновик со статусом «создано голосовым агентом» — это осознанное решение: администратор клиники в рабочее время видит очередь черновиков и подтверждает их одним кликом, что даёт человеку контроль над финальным слотом (важно для клиник, где расписание врача меняется чаще, чем обновляется API).
Дальше вступает контур напоминаний — отдельный сервис-крон, который не имеет отношения к звонку в моменте, но использует ту же телефонную и мессенджер-инфраструктуру:
- за 24 часа до приёма — SMS или сообщение в WhatsApp/Telegram (в зависимости от того, откуда пациент исторически отвечает лучше по данным клиники) с возможностью ответить «отмена» текстом, что освобождает слот автоматически;
- за 2 часа до приёма — либо повторное текстовое напоминание, либо (для пациентов старшего возраста без активных мессенджеров) исходящий звонок того же голосового агента с коротким скриптом подтверждения — тут диалог тривиальный и укладывается в один tool call
confirm_or_cancel, поэтому можно смело гонять его на Haiku 4.5 вместо Sonnet, экономя на массовых исходящих; - при неявке (no-show) без отмены — слот автоматически помечается в МИС для контроля администратором, статистика no-show по врачам и дням недели уходит в еженедельный отчёт — это уже не AI-функционал, а обычная выгрузка, но она появляется бесплатно, потому что вся история записей теперь структурирована ботом, а не разбросана по звонкам и бумажным журналам администратора.
Именно связка «приём звонка + напоминание + учёт неявок» даёт основной экономический эффект внедрения — ночные и вечерние звонки перестают уходить конкурентам, а часть нагрузки на администратора по обзвону с напоминаниями снимается полностью.
Мониторинг и отказоустойчивость: что мы обязательно ставим на прод
Голосовой AI-контур в медицине не может работать по принципу «упало — починим завтра»: пропущенный ночной звонок в стоматологии с острой болью — это либо потерянный пациент, либо (в худшем случае) репутационный риск для клиники. Поэтому мы всегда закладываем два уровня деградации.
Первый уровень — частичная деградация внутри диалога: если STT-сессия рвётся или Claude API отвечает с ошибкой (429/5xx), шлюз не роняет звонок, а переключает канал на DTMF-меню («нажмите 1, чтобы оставить номер, мы перезвоним») — это тот самый резервный сценарий из таблицы порогов выше. Логика ретраев к Claude API реализована с экспоненциальным бэкоффом и лимитом в 2 повторных попытки — больше не имеет смысла держать пациента на линии молча.
Второй уровень — полный отказ AI-контура (например, отвалился сам шлюз-сервис): dialplan в FreePBX содержит fallback-маршрут прямо на очередь администратора клиники, минуя Stasis-приложение, включаемый по признаку недоступности AI-сервиса в realtime-мониторинге (проверка health-эндпоинта каждые 15 секунд).
Что мы выводим в мониторинг (в наших проектах — Zabbix, как и на прочей инфраструктуре клиента, чтобы не плодить отдельную панель только под один сервис):
- число одновременных Stasis-каналов и очередь ожидания на линии;
- перцентиль p95 суммарной задержки ответа бота (STT+LLM+TTS) по скользящему окну 15 минут;
- долю звонков, ушедших в эскалацию на администратора, и долю ушедших в DTMF-фолбэк — резкий рост любой из метрик сигнализирует о деградации STT или LLM ещё до того, как это заметят пациенты;
- долю подтверждённых администратором черновиков записи от общего числа созданных ботом — если она проседает, значит бот стал предлагать нерелевантные слоты, и нужно пересматривать промпт или интеграцию расписания.
Как мы внедряем: этапы, а не «коробочное решение за неделю»
Мы принципиально не продаём это как типовую коробку — расписание врачей, МИС, телефонный провайдер и даже манера речи администраторов у каждой клиники свои, и от этого зависит настройка промпта и порогов эскалации. Реальный проект идёт в четыре этапа.
- Аудит телефонии и МИС (обычно 3–5 рабочих дней) — смотрим, на чём стоит текущая АТС клиники (часто это уже FreePBX у SIP-провайдера, реже облачная АТС без доступа к dialplan — тогда сначала переносим номер на управляемый нами FreePBX), проверяем, есть ли у МИС API для создания записи или придётся работать через промежуточную выгрузку файлов.
- Пилот на одном входящем номере — бот работает параллельно с администратором только в нерабочие часы (вечер/ночь/выходные), все записи идут черновиками, мы вручную разбираем расшифровки первых 150–200 звонков и дотягиваем системный промпт под реальную лексику пациентов клиники (характерные названия услуг, разговорные обозначения процедур, которые не совпадают с официальным прайсом).
- Перевод на постоянную эксплуатацию — бот принимает все входящие на выделенный номер (или на основной номер клиники целиком, если аудит на первом этапе это позволил), включается контур напоминаний, администратор клиники получает обучение по работе с очередью черновиков записи.
- Поддержка и калибровка порогов — в первый месяц пороги эскалации (таблица выше) практически всегда требуют корректировки — у одной клиники окажется слишком много ложных эскалаций из-за акцента пациентов старшего возраста, у другой — слишком мало, и бот начнёт записывать людей на несуществующие приёмы. Мы держим проект на сопровождении минимум квартал после запуска именно из-за этой калибровки.
Частые вопросы
- Нужно ли полностью заменять администратора клиники голосовым AI-агентом?
Нет, и мы такое не продаём. Бот закрывает нерабочие часы и пиковую нагрузку, а созданные им записи попадают администратору черновиками на подтверждение. В рабочее время звонки по умолчанию продолжает принимать человек — AI подключается как параллельный канал, а не замена.
- Можно ли обойтись без Asterisk/FreePBX, если у клиники уже облачная АТС?
Зависит от того, даёт ли облачный провайдер доступ к событиям канала и возможность перехвата звонка внешним сервисом (webhook или REST API уровня ARI). Если нет — на первом этапе аудита мы переносим номер на управляемый FreePBX 17, это обычно занимает 1–2 дня и не требует смены оператора связи, только SIP-транка.
- Что произойдёт с данными пациента, если Claude API за рубежом окажется недоступен?
Звонок не потеряется: при ошибке LLM-контура канал переключается на DTMF-фолбэк («оставьте номер») или сразу на очередь администратора, в зависимости от настроенного порога. Персональные данные при этом не покидали контур клиники и на момент сбоя — в диалоге с LLM использовались только обезличенные токены, а не ФИО и телефон.
- Сколько времени занимает калибровка под конкретную клинику?
Пилотный этап на одном номере в нерабочие часы — обычно 3–4 недели, дальше перевод на полную эксплуатацию. Активная калибровка порогов эскалации и промпта продолжается весь первый квартал сопровождения, дальше система работает стабильно с редкими точечными правками при изменении расписания врачей или прайса.
- Как это соотносится с врачебной тайной и 152-ФЗ, если разговор идёт с ботом, а не с человеком?
Требования не снижаются оттого, что на линии бот, а не администратор — факт обращения и симптомы всё так же составляют врачебную тайну по 323-ФЗ. Поэтому архитектура разделяет диалоговый слой (LLM видит только обезличенные токены) и слой хранения (МИС и локальный шлюз на инфраструктуре в РФ, где реально лежат ФИО, телефон и медицинские детали). Финальное положение об обработке персональных данных клиника всё равно согласовывает со своим юристом — мы даём архитектуру, а не юридическое заключение.