Вы добавили в продукт виджет с ИИ-чатом. Он отвечает по центру помощи, забирает на себя заметную долю обращений, а все переписки лежат списком. Вы читаете их по пятницам после обеда.
И у каждой написано Аноним.
Вы видите, что кто-то за полдня трижды спросил, как сменить email для счетов. Но не видите, что это был один и тот же человек, что он на вашем самом дорогом тарифе и что в понедельник он сдался и ушёл.
Этот пробел и закрывает идентификация.
Что анонимный лог показывает, а что нет
Анонимная переписка полезна. Она показывает, о чём спрашивают, на какие вопросы документация не отвечает и какие страницы сбивают людей с толку. На этом строится весь цикл «найти вопрос без ответа — написать недостающую статью», и он работает, не зная ни одного имени.
Чего она не умеет — связать разговор с тем, что вы ещё знаете о человеке.
Частота по человеку, а не по сессии. Десять разговоров об одной функции — это могут быть десять растерянных пользователей или один очень настойчивый. Эти ситуации требуют совсем разной реакции, а анонимный лог их не различает.
Кого это касается. Фраза «три человека спросили про лимит запросов к API» читается иначе, когда один из них — ваш крупнейший клиент.
Продолжение разговора. Сотрудник поддержки, открывая тикет, не видит, что ИИ час назад уже ответил этому клиенту, и клиент объясняет всё заново.
Сигнал оттока. Повторяющиеся раздражённые вопросы от одного аккаунта за две недели до отмены подписки — один из самых ясных сигналов, какие вы вообще получите. Без идентификации его не видно.
Очевидная идея и чем она опасна
Очевидный шаг — сообщить виджету, кто вошёл в систему. Ваше приложение это уже знает.
KookeeChat.identify({ id: user.id, email: user.email, name: user.name });
Опасность в том, что вы сделаете дальше. Так и хочется рассудить: виджет теперь знает, кто это, значит, ассистент может отвечать на вопросы про его аккаунт. Про его счета, статистику использования, открытые тикеты.
Не делайте этого. Всё, что отправляет браузер, человек в этом браузере может подменить. Достаточно открыть инструменты разработчика и вызвать identify() с чужим email. Если ассистент доверяет этому полю, вы построили утечку данных с чат-интерфейсом сверху.
Это не теоретический класс ошибок. Это та же ошибка, что и доверие к id пользователя, который клиент сам прислал в запросе к API, и исправляется она так же.
Идентификация — для меток, аутентификация — для данных
Выход в том, чтобы разделить две вещи, которые кажутся одной.
Идентификация — это метка. Она говорит, чей это, скорее всего, разговор, чтобы вы могли группировать переписки, искать по ним и читать их. Её никто не проверял, и она ничего не решает.
Аутентификация — отдельно, и она у вас уже есть. Ваше приложение аутентифицирует пользователей. Ваш API проверяет сессии. Появление чат-виджета ничего из этого не меняет.
Поэтому, когда ассистенту действительно нужны личные данные, он не должен брать их из поля идентификации. Он должен вызвать функцию, которая выполняется в браузере самого посетителя, с его собственной cookie сессии, и обращается к вашему API:
KookeeChat.init({
apiKey: 'YOUR_API_KEY',
visitor: { id: user.id, email: user.email }, // метка для панели управления
tools: [
{
name: 'get_my_invoices',
description: "Look up the signed-in customer's recent invoices.",
parameters: { type: 'object', properties: {} },
handler: () => fetch('/api/my/invoices', { credentials: 'include' }).then((r) => r.json()),
},
],
});
Присмотритесь: безопасным этот код делает не поле visitor, а credentials: 'include'. Ваш сервер получает обычный аутентифицированный запрос и применяет те же правила, что и везде. Если кто-то подделает identify(), он всё равно получит свои счета, потому что решает cookie.
Поле идентификации в этом фрагменте делает куда более скромную работу: подписывает строку в вашей панели управления.
О чём все забывают: выход из аккаунта
Вот ошибка, которую вы выпустите, если реализуете только основной сценарий.
Человек входит в приложение с библиотечного компьютера, задаёт ассистенту три вопроса о своём аккаунте и выходит. Следующий садится за компьютер, открывает чат, а там предыдущий разговор: виджет хранил его в браузере, и никто не сказал виджету, что что-то изменилось.
Идентификацию мало один раз задать. Она меняется, и важное событие — именно смена:
// при входе
KookeeChat.identify({ id: user.id, email: user.email, name: user.name });
// при выходе — не пропускайте
KookeeChat.reset();
Чат-виджет с идентификацией должен считать смену человека жёсткой границей: выбросить переписку, начать новую сессию, ничего не оставить. В Kookee это происходит автоматически, когда идентификация меняется на другого человека или на «никого». Но виджет реагирует только на то, что вы ему сообщили, поэтому вызов reset() при выходе пишете вы.
Есть тонкость, о которой стоит знать: на ней спотыкаются даже те, кто правильно обработал выход. Большинство приложений при первом рендере не знают, кто пользователь. Они запрашивают сессию и только потом могут кого-то идентифицировать. Если для виджета «я пока не знаю» и «никто не вошёл» выглядят одинаково, каждая загрузка страницы выбрасывает разговор раньше, чем закончится проверка сессии. Поэтому эти состояния нужно различать: пока идёт загрузка, оставляйте значение undefined, а когда точно известно, что никто не вошёл, передайте явный null.
Что передавать
Меньше, чем кажется.
Нужно что-то стабильное, по чему группировать. Id пользователя из вашей базы для этого подходит идеально и ничего не говорит тому, у кого нет вашей базы. Email в поддержке полезнее: по нему вы ищете, когда клиент пишет. Но это ещё и персональные данные со сроком хранения.
Разумный вариант по умолчанию:
Всегда передавайте id. Это ключ группировки, и по нему ничего нельзя понять.
Передавайте email, если ваша поддержка ищет по нему. Обычно ищет.
Передавайте имя для читаемости. С ним список в панели управления понятен с одного взгляда.
Больше ничего не передавайте. Тариф, роль и возраст аккаунта кажутся полезными, но им место в контексте, который вы даёте ассистенту, а не в метке. Смешав одно с другим, вы получите хранилище персональных данных, которое не собирались строить.
И опишите это в политике конфиденциальности. Переписки в чате и так персональные данные. Email не меняет их категорию, но делает это заметнее и для регулятора, и для ваших пользователей.
Нужно ли ассистенту знать имя?
Здесь напрашивается идея: если виджет знает, что посетитель — Алиса, ассистент может начать с «Привет, Алиса».
Kookee сознательно так не делает. Идентификация хранится вместе с разговором и никогда не отправляется модели. Персональные данные попадают в запрос к модели, только если вы так решили.
Если приветствие нужно, его можно получить, но попросить о нём придётся явно, через отдельное поле контекста — для того, что должен знать ассистент:
KookeeChat.init({
apiKey: 'YOUR_API_KEY',
visitor: { id: user.id, email: user.email, name: user.name }, // панель управления
appContext: { firstName: user.firstName, plan: user.plan }, // ассистент
});
Два поля для двух адресатов. Это выглядит как дублирование до того дня, когда вас спросят, какие персональные данные покидают вашу инфраструктуру, и вы сможете ответить точно.
Что это даёт
Когда у разговоров есть идентификация, на обычные вопросы появляются ответы.
Найдите переписки по email клиента перед звонком о продлении, и вы узнаете, что его сбивало с толку в этом квартале. Заметьте, что один аккаунт четыре раза спросил одно и то же, и вы нашли пробел в документации или в онбординге. Передайте сотруднику поддержки разговор с ИИ, который был до тикета, и клиенту не придётся повторяться.
Для этого ассистенту не нужно становиться умнее. Нужно, чтобы в логе перестало быть написано Аноним.
Виджет ИИ-чата Kookee поддерживает идентификацию посетителей и в React-пакете, и в обычном теге script. Все правила, включая то, когда именно разговор удаляется, — в статье Как узнать, кто пишет в чат.