Gradio получает экран входа за 30 минут: трейсы Langfuse разделяются по пользователям
Gradio за полчаса получает экран входа, а Langfuse начинает разделять трейсы по пользователям, и всё это без переписывания интерфейса, буквально двумя параметрами в методе запуска.
Как только локальным MCP-агентом пользуется больше одного человека, история, чекпоинты и трассировки превращаются в кашу без привязки к конкретному пользователю. Аутентификация в Gradio решает проблему на уровне интерфейса, а связка с Langfuse и LangGraph разносит данные автоматически.
Разработчик Владимир опубликовал на Хабре практическое продолжение цикла про локального MCP-агента (программу, которая сама вызывает внешние инструменты по заданному сценарию). В предыдущих частях он собрал агента на LangGraph с Gradio-интерфейсом, чекпоинтами и трассировкой. Проблема возникла, когда агентом стали пользоваться коллеги: контейнер в связке Windows-WSL-Docker показывал один и тот же IP-адрес, и отличить пользователей по сети не получилось. Решение: прикрутить к Gradio аутентификацию и прокинуть имя пользователя в трейсы Langfuse и чекпоинты LangGraph.
Что понадобится?
- Работающий Gradio-интерфейс (версия с поддержкой параметра
authвdemo.launch) - Настроенный LangGraph-агент с трассировкой через Langfuse
- Docker Desktop на Windows с WSL (или любая среда, где запускается контейнер)
- Текстовый файл для хранения логинов и паролей (формат
логин:пароль, по одной паре на строку) - Примерно 30 минут на интеграцию
Как устроено хранение паролей?
В полноценных системах пароль не хранится в открытом виде. Вместо него сохраняют хеш (результат необратимого преобразования), часто с «солью» (случайной добавкой). При входе система хеширует введённый пароль тем же способом и сравнивает хеши, а не сами пароли. Даже при утечке базы злоумышленник не получает готовый список.
Но здесь задача проще: Gradio-интерфейс для двух-трёх коллег. Полноценного окна регистрации у Gradio нет, пользователей нужно заводить вручную или подключать внешнюю аутентификацию. Автор выбрал минимальный путь: логины и пароли лежат в текстовом файле.
# Формат: логин:пароль (по одному на строку)
admin:admin
Пошаговая инструкция
1. Создайте файл с учётными записями.
Положите в проект текстовый файл, где каждая строка содержит пару логин:пароль. Строки, начинающиеся с #, считаются комментариями и пропускаются. Строки без разделителя : тоже игнорируются.
2. Напишите функцию парсинга и проверки.
Функция get_gradio_auth читает файл при старте, собирает словарь «логин: пароль» и возвращает замыкание для проверки:
import secrets
from pathlib import Path
from core import settings
def get_gradio_auth():
if not settings.service.auth_enabled:
return None
path = Path(settings.service.auth_users_file)
if not path.is_file():
raise RuntimeError(f'Файл учётных записей не найден: {path}')
credentials = {}
for line in path.read_text(encoding='utf-8').splitlines():
if not line or line.startswith('#') or ':' not in line:
continue
user, psw = line.split(':', 1)
if (user := user.strip()) and (psw := psw.strip()):
credentials[user] = psw
if not credentials:
raise RuntimeError(
f'Файл учётных записей пуст или некорректен: {path}'
)
def checker(username: str, password: str) -> bool:
stored = credentials.get(username)
return stored is not None and secrets.compare_digest(stored, password)
return checker
Обратите внимание: для сравнения пароля используется secrets.compare_digest, а не обычный оператор ==. Обычное сравнение прерывается при первом несовпавшем символе, и по разнице во времени ответа теоретически можно подбирать пароль посимвольно (timing attack, атака по времени). secrets.compare_digest тратит одинаковое время независимо от того, где именно строки расходятся. Для файла с тремя паролями это избыточная мера, но привычка полезная.
3. Передайте функцию в demo.launch.
В классе вашего приложения добавьте два параметра при запуске Gradio:
self.demo.launch(
server_name=settings.service.host,
server_port=settings.service.port,
show_error=True,
auth=get_gradio_auth(),
auth_message='<p>Введите выданный Вам логин и пароль</p>'
)
Параметр auth принимает либо функцию проверки (как здесь), либо кортеж «логин, пароль» напрямую. Параметр auth_message задаёт текст на экране входа.
4. Прокиньте имя пользователя в методы графа.
Теперь Gradio знает, кто вошёл. Осталось передать request.username в вызовы LangGraph, чтобы чекпоинты и трейсы разделились по пользователям:
async def _continue(self, history: list, request_id: str,
request: gr.Request):
phase = await self.agent.get_phase(
request_id, username=request.username
)
if phase is None or phase == 'done':
history.append({
'role': 'assistant',
'content': 'Нет активных задач для продолжения.'
})
return history, request_id
result = await self.agent.resume(
user_messages='Продолжить',
request_id=request_id,
username=request.username
)
5. Сформируйте thread_id с привязкой к пользователю.
Чтобы Langfuse и LangGraph хранили данные раздельно, thread_id строится из имени пользователя и идентификатора запроса:
@staticmethod
def resolve_thread_id(username: str | None, request_id: str) -> str:
return f'{username or "anonymous"}:{request_id}'
Если аутентификация выключена, username будет None, и все данные попадут под метку anonymous. Система продолжит работать, просто без разделения.
После запуска контейнера пользователь видит окно ввода логина и пароля вместо обычного интерфейса. Вводит admin:admin, попадает в знакомый Gradio-чат. Каждый его запрос в Langfuse теперь привязан к thread_id вида admin:req_abc123. Коллега заходит под своим логином, и его трейсы попадают в отдельную ветку: colleague:req_xyz789. Фильтрация в дашборде Langfuse по имени пользователя занимает секунду.
- Файл паролей не попал в контейнер. Если собираете Docker-образ и забыли примонтировать файл или добавить его в
COPY, приложение упадёт при старте с ошибкойRuntimeError. Проверьтеdocker-compose.ymlилиDockerfile. - Кодировка файла. Функция читает файл в UTF-8. Если файл сохранён в другой кодировке (часто бывает на Windows), логины с кириллицей сломаются.
- Пароли в открытом виде в репозитории. Файл
credentials.txtпопадёт в Git, если не добавить его в.gitignore. Для внутреннего инструмента на трёх человек это терпимо, для чего-то серьёзного нужен переход на хеширование или внешний провайдер. request.usernameравенNoneпри выключенной аутентификации. Код обрабатывает этот случай черезanonymous, но если вы завязали на имя пользователя другую логику (права, фильтры), проверяйте явно.
Что делать с этим прямо сейчас, по ролям
Разработчику, который уже собрал MCP-агента на LangGraph и Gradio. Добавление аутентификации занимает буквально два параметра в demo.launch и одну функцию парсинга. Если агент работает в Docker на Windows с WSL и IP-адреса не различаются, это единственный надёжный способ привязать данные к конкретному человеку.
Тимлиду или техлиду, который отдаёт агента команде. Разделение трейсов в Langfuse по пользователям позволяет понять, кто и как использует инструмент, без ручного разбора логов. Это полезно и для отладки, и для оценки, насколько агент реально помогает.
Автору на Дзене, который пишет про ИИ-инструменты. Gradio всё чаще используют для быстрых демо-интерфейсов к нейросетям. Понимание, что туда можно за полчаса добавить экран входа и разделение пользователей, расширяет набор тем: можно делать обзоры не только моделей, но и способов их подачи аудитории.
Решение нарочито простое, и автор это честно признаёт: пароли в текстовом файле, без хеширования, без регистрации. Для внутреннего инструмента на двух-трёх человек это работает. Но если вы планируете открыть доступ шире, стоит сразу заложить хеширование (библиотека bcrypt или passlib) и вынести учётные записи в базу данных. Переход будет безболезненным: менять придётся только функцию get_gradio_auth, интерфейс Gradio останется тем же. Главная ценность этого гайда не в самом коде, а в архитектурном решении: аутентификация прокидывается через request.username сразу в thread_id, и вся цепочка от интерфейса до трассировки получает привязку к пользователю без отдельной системы управления сессиями.
Попробуйте AI-ассистент dzen.guru
Тестируйте промпты и сценарии для ваших ИИ-агентов прямо в нашем инструменте
ПопробоватьВесь процесс укладывается в пять шагов и полчаса работы. Файл паролей, функция проверки, два параметра в demo.launch, username в вызовах графа и составной thread_id. После этого Langfuse покажет, кто именно из команды сломал агента вчера в полночь, а LangGraph вернёт каждому его собственную историю.

Основатель dzen.guru. Эксперт по монетизации и продвижению на Дзен. Автор курса «Старт на Дзен 2026».
Читайте также
Ответственность искусственного интеллекта невозможна: почему за ошибку ИИ некому платить
Компания SENSE опубликовала экспертный разбор Дмитрия Фырнина, управляющего партнёра и технического директора, о том, где проходит реальная граница…

Внедрение искусственного интеллекта буксует: Cloud.ru собирает «карту боли» российского бизнеса
Компания Cloud.ru 11 июня 2025 года запустила на Хабре открытый опрос о барьерах внедрения искусственного интеллекта в российском бизнесе, опираясь на данные…

Structured outputs в OpenAI API: 5 сценариев, когда гарантия формата молча ломается
Компания OpenAI ещё в августе 2024 года добавила в API так называемый strict-режим для структурированных выходов (structured outputs), и за полтора года он…
Комментарии