惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
V
Visual Studio Blog
罗磊的独立博客
Last Week in AI
Last Week in AI
爱范儿
爱范儿
The Cloudflare Blog
有赞技术团队
有赞技术团队
博客园 - 三生石上(FineUI控件)
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
IT之家
IT之家
博客园 - 【当耐特】
雷峰网
雷峰网
博客园_首页
Jina AI
Jina AI
Hugging Face - Blog
Hugging Face - Blog
阮一峰的网络日志
阮一峰的网络日志
量子位
V
V2EX

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Если клиент не идёт в больничку — телефонный бот для теле...
Илья Сивков · 2026-06-18 · via Все публикации подряд на Хабре

Если клиент не идёт в больничку — телефонный бот для телемедицины

Средний

5 мин

69

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

А грянувший гром стоит очень дорого как самим людям, так и их страховым компаниям. Поэтому они долбят своих клиентов (особенно тех, у кого есть хронические болезни), чтобы те регулярно показывались врачу. Долбят, разумеется, по телефону. Вот этот-то процесс мне поручили попробовать автоматизировать.

Архитектура

По сути, наш бот - это LLM, который разговаривает и слушает через Twilio.

Архитектура телефонного бота

Архитектура телефонного бота

Когда-то в 2019 я уже работал с Twilio. Тогда нужно было подключить к нему гугловский DialogFlow. Веселье было на полную: SIP-клиент на Python, работа с аудиопотоком через расширение на сях (без плюсов, struct/malloc/free), ручной вызов API для STT/TTS и прочие радости.

Сейчас всё упростилось до нельзя - <ConversationRelay> рулит: Twilio само переводит речь в текст и обратно, мы получаем и отправляем текст через WebSocket. Мало того, можно отправлять ответ частями (см. ниже про стриминг), а также “договаривать” (вроде “Минутку, проверяю”, потом вызываем API и сообщаем результат).

Для работы с этим хозяйством есть классическая связка “API Gateway плюс Lambda”. Лямбд здесь, на самом деле, две. Первая принимает обычные REST-запросы и отвечает за hook-и вроде начала звонка или брошенной трубки. Вторая - это сама “говорилка”. Именно здесь живёт “птичка” LangGraph, которая работает с LLM, checkpoint-ами и tools.

Есть ещё и третья лябмда. Она асинхронно дёргает наши внутренние API для проверки и бронирования времени. Про асинхронность подробнее будет ниже.

Все необходимые данные хранятся в DynamoDB. Данных этих два вида - запросы на асинхронный вызов и checkpoint-ы от LangGraph. Написать свой Saver под “динамку” - это был тот ещё квест.

К LLM мы обращаемся через Bedrock. В качестве модели мы взяли Amazon Nova. Есть модели куда мощнее, но нам хватает и этой.

Это хозяйство описано через стэк CloueFormation на AWS CDK. Немного веселья с Github Actions и всё красиво деплоится на push в master. CI/CD, однако!

Диалог

Задача нашего бота - назначить визит лаборанта, а перед этим уточнить некоторые сведения. План беседы такой:

  • Проверяем, что на проводе тот, кто нам нужен,

  • Предлагаем записаться,

  • Если нужно, немного уговариваем,

  • Уточняем возраст, состояние здоровья и таблетки,

  • Обсуждаем удобную дату и время встречи,

  • Назначаем встречу.

Разумеется, всё что угодно может пойти не так:

  • Ответил другой человек - тогда просим пригласить нужного нам или сказать, когда перезвонить,

  • Человек не хочет записываться - уговариваем, приводим простую статистику,

  • Просит время, чтобы (например) посмотреть, как именно называются таблетки - даём время.

Обсуждение удобного времени - отдельная история. Здесь мы обрабатываем такие случаи:

  • Человек не знает, что предложить - предлагаем сами ближайшее свободное время, два-три слота

  • Если предложенное время не подходит, ищем свободное позже.

  • Люди редко мыслят датами. Обычно это “завтра вечером”, “на той недели кроме вторника” и т.п. - переводим в диапазоны даты-времени,

  • Если человек уже согласился на какое-то время, но потом вспомнил, что тогда не может - предлагаем дальше.

Промпты

Весь диалог у нас разделён на части: приветствие, уговоры (если надо), сбор сведений и обсуждение времени. Для каждой части есть свой промпт. В состоянии графа есть поле с id текущего промпта. Именно этот промпт мы отправляем в LLM, чтобы получить очередной ответ.

Сами промпты состоят из нескольких частей:

  • Контекст - что делает агент в этой части диалога,

  • Шаги - что сказать или спросить,

  • Переходы - к какой части диалога перейти потом,

  • Правила - указания по построению фраз, запреты на упоминание AI и т.п.,

  • Примеры - два-три варианта именно этой части беседы с разными исходами.

Для переходов мы инструктируем модель отвечать текстом вроде goto:booker. Эта команда затем считывается в коде.

Вызовы API и асинхронные задачи

Структура обработчика сообщений

Структура обработчика сообщений

В нашем боте есть два вызова внешних API: проверка доступного времени и назначение встречи. Второй работает мгновенно, а вот первый может думать несколько секунд. Раньше запрос был только из веб-морды, там было вполне допустимо покрутить спиннер. В телефоне такие лаги - ещё не катастрофа, но уже намёк. И мало того, что человек долго ждёт - он ещё и не может ничего спросить, даже “Ну, что там?”.

Чтобы решить эту проблему, мы сделали асинхронный вызов API через механизм задач (jobs). Работает он так:

  • Когда бот хочет проверить расписание, он говорит в трубку “Сейчас посмотрю” и тут же вызывает tool, который создаёт в DynamoDB задачу с нужными параметрами,

  • DynamoDB через Stream вызывает лямбду, которая дёргает внутренний API, ждёт ответ и сохраняет результат,

  • Если в это время человек спросит, что да как, бот вызовет tool проверки состояния задач и ответит, что ещё проверяет,

  • Когда ответ API пришёл, лямбда с ботом получает событие из DynamoDB Stream и сообщает результат. Здесь как раз полезны веб-сокеты: бот может начать говорить, не дожидаясь очередной реплики пользователя.

  • Пока API думает, бот говорит в трубку “Я тут, проверяю” примерно раз в десять секунд. Сделано это через отправку вопроса “нучёкак” по таймеру.

Стрим токенов

Модели сейчас быстрые, но не мгновенные. Чтобы пользователь не ждал, пока модель родит ответ целиком, мы используем поток токенов. Идея тут простая: как только модель выдала хоть что-то, мы отправляем это в сокет Twilio с пометкой “будет ещё”. Когда модель закончила свою тираду, сообщаем об этом в сокет.

Вроде всё здорово, и даже из коробки, но есть два момента:

  • у нас есть команды смены промптов, которые не надо зачитывать,

  • LLM в режиме стриминга не убирает размышления,а обрамляет их в тег <thinking>.

Поэтому мы добавили фильтр, который не пропускает в сокет мысли модели и наши команды. Интересно, что LLM выдаёт текст с почти непредсказуемым разбиением. Например, ‘try!<’, ‘think’, ‘ing’, ‘>Now’. Получилась неплохая задачка в духе LeetCode на buffer/flush и регулярки.

Генерация ответа

Процесс обработки реплики пользователя

Процесс обработки реплики пользователя

Работа с LLM и tools проводится через LangGraph. Сначала мы проверяем, есть ли у нас незавершённые асинхронные вызовы. Напомню, что в LangGraph чат хранит не только реплики, но и вызовы и результаты работы tools. Поэтому эти сведения можно узнать прямо из сообщений, которые у нас уже есть. Если незавершённые вызовы есть, проверяем их статус.

Далее выбираем нужный промпт, подставляем туда некоторые сведения вроде имени пользователя, и отправляем в LLM. Ответ модели в большинстве случаев отправляем в стрим Twilio. Исключение составляют команды перехода к другой части диалога. Например, после приветствия человек согласился обсудить приём. Тогда в качестве ответа модель вернёт команду перехода к сбору сведений. В этом случае мы берём соответствующий промпт и вызываем LLM ещё раз. Результат уже точно будет сообщением (в нашем примере - просьба назвать возраст), отправляем его в стрим Twilio.

К тому моменту, как дело дойдёт до выбора времени, диалог может быть достаточно длинным. Поэтому перед началом мы сжимаем все предыдущие сообщения в один абзац. Он сообщает модели нечто вроде “Ты говоришь с женщиной по имени Гюльчатай, ей 55 лет, у неё…” ну и т.д. Этот кусок мы вставляем в промпт в качестве вводных.

Заключение

Кроме описанного выше есть ещё веб-морда для звонков по WebRTC. Вместе с ней получился вполне приличный прототип. Направлений развития здесь пруд пруди:

  • Автоматическое определение языка - если человек вместо “здравствуйте” предложил что-то там опустить в чай, то предложить ему перейти на китайский,

  • Интеграция с внешними сервисами - например, некто ходит к врачам только в определённую фазу луны. Можно вызвать API лунного календаря и узнать ближайшие даты,

  • Разные цели диалога и набор шагов - естественный шаг к multi-tenancy

Но это уже в следующей жизни…