- Linux-сервер (Ubuntu 22.04 LTS или совместимый дистрибутив), root/sudo, статический IP
- Docker Engine и плагин Docker Compose v2 (проверка: docker compose version)
- git, curl, jq, python3, zip/unzip
- Домен <ваш-домен> и TLS-сертификат (например, Let's Encrypt) для HTTPS
- Проект Google Cloud с агентом Dialogflow CX и роль Dialogflow API Admin (или Owner)
- Установленный gcloud CLI для вызова REST API Dialogflow
- Экспорт агента из Dialogflow CX — архив .blob

A: <ваш-домен> → IP сервера. Без неё не выпустится сертификат../secrets/gcp-sa.json с правами 600.df -h /)..blob — ZIP-архив с описанием агента (файл agent.json, интенты, флоу); reverse proxy — сервер-«приёмная», который принимает HTTPS на 443 и передаёт запрос внутрь на порт приложения.Если сервер у вас домашний, а не облачный, сначала пройдите как подключить ИИ к домашнему серверу — там разобраны проброс портов и базовая изоляция сервисов.
Что сделать: установите Docker, утилиты и создайте каталог проекта с файлом переменных окружения.
Зачем: все компоненты Lampline распространяются как контейнеры; переменные окружения описывают домен, токен API и доступ к Google Cloud, чтобы не хардкодить секреты в конфиг.
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg git jq python3 unzip
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker
mkdir -p ~/lampline/secrets ~/lampline/data
cd ~/lampline
Пример: файл ~/lampline/.env:
LAMPLINE_DOMAIN=<ваш-домен>
LAMPLINE_PUBLIC_URL=https://<ваш-домен>
LAMPLINE_API_TOKEN=<токен-из-панели-или-сгенерированный>
LAMPLINE_HTTP_PORT=8000
GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/gcp-sa.json
DIALOGFLOW_PROJECT_ID=<project-id>
DIALOGFLOW_LOCATION=<region, например europe-west1>
Как проверить:
docker compose version
ss -ltnp | grep -E ':(80|443|8000)\b' || echo "порты свободны"
ls -l secrets/gcp-sa.json
Ожидаемый результат: строка вида Docker Compose version v2.x, сообщение порты свободны и файл ключа с правами -rw-------. Имена переменных LAMPLINE_* — плейсхолдеры: точный список сверьте с документацией Lampline, раздел конфигурации.
Почему это работает: Docker читает .env при каждом docker compose up, поэтому смена домена или токена не требует пересборки образа — достаточно перезапуска.
Что сделать: опишите сервис в docker-compose.yml, поднимите контейнер и заверните его в reverse proxy с TLS.
Зачем: Compose фиксирует порты, тома и монтирование ключа Google в одном файле — это воспроизводимо при переустановке и переносе на другой сервер.
services:
lampline:
image: <registry>/lampline:<тег-из-документации>
env_file: .env
ports:
- "8000:8000"
volumes:
- ./data:/var/lib/lampline
- ./secrets/gcp-sa.json:/run/secrets/gcp-sa.json:ro
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8000/"]
interval: 30s
timeout: 5s
retries: 3
cd ~/lampline
docker compose pull
docker compose up -d
docker compose ps
Пример: успешный docker compose ps показывает lampline ... Up (healthy) — статус healthy означает, что внутренний healthcheck прошёл и приложение отвечает на HTTP.
Дальше настройте Nginx как reverse proxy: proxy_pass http://127.0.0.1:8000; для <ваш-домен> и сертификат от Let's Encrypt (например, certbot --nginx -d <ваш-домен>). Имя образа, тег и внутренний порт сверьте с документацией Lampline — универсальных значений здесь нет.
Как проверить:
curl -sS -o /dev/null -w "%{http_code}\n" https://<ваш-домен>/
docker compose logs --tail=50 lampline | grep -i -E "error|listen"
Ожидаемый результат: код 200 или 401 (если включена авторизация — это нормально) и строка лога о старте сервера без error. Код 502 означает, что Nginx жив, а контейнер — нет.

Что сделать: выгрузите агента из Dialogflow CX в архив .blob, проверьте его локально и загрузите в Lampline.
Зачем: экспорт — единственный поддерживаемый способ перенести интенты, флоу и сущности без ручного пересоздания; локальная проверка отсекает битый архив до загрузки.
Экспорт в консоли: откройте агента → Manage → Export agent → выберите формат экспорта → скачайте файл. Точные названия пунктов меню сверьте с актуальной документацией Dialogflow CX — Google периодически их меняет.
Проверка архива перед импортом:
import json, zipfile
with zipfile.ZipFile("agent.blob") as z:
names = z.namelist()
print(len(names), "файлов в архиве")
agent = json.loads(z.read("agent.json"))
print("Агент:", agent["displayName"])
print("Язык:", agent.get("defaultLanguageCode"))
Пример вывода: 214 файлов в архиве, Агент: SupportBot, Язык: ru. Если agent.json в архиве нет — распечатайте names[:20] и сверьте структуру с документацией формата экспорта.
Загрузка в Lampline (путь эндпоинта и имя поля возьмите из документации Lampline):
curl -sS -X POST "https://<ваш-домен>/api/agents/import" \
-H "Authorization: Bearer ${LAMPLINE_API_TOKEN}" \
-F "file=@agent.blob" | jq
Если Lampline ожидает не файл, а уже восстановленного агента через API Google, используйте штатный метод restore Dialogflow CX — он принимает содержимое архива в base64:
TOKEN=$(gcloud auth print-access-token)
curl -sS -X POST \
"https://${DIALOGFLOW_LOCATION}-dialogflow.googleapis.com/v3/projects/${DIALOGFLOW_PROJECT_ID}/locations/${DIALOGFLOW_LOCATION}/agents/${DIALOGFLOW_AGENT_ID}:restore" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"agentContent\":\"$(base64 -w0 agent.blob)\"}"
Почему это работает: :restore — стандартный метод Dialogflow CX, который принимает тот же архив, что отдаёт экспорт, и разворачивает его в указанном агенте; base64 -w0 даёт одну строку без переносов, иначе JSON сломается.
Как проверить:
curl -sS "https://<ваш-домен>/api/agents" \
-H "Authorization: Bearer ${LAMPLINE_API_TOKEN}" | jq '.[].name'
Ожидаемый результат: в списке появилось имя вашего агента (например, SupportBot), а на странице агента в UI число интентов совпадает с числом файлов интентов в архиве. Расхождение — повод смотреть лог импорта.

Что сделать: прогоните тестовую реплику через голосовой канал и сверьте ответ агента с исходным сценарием в Dialogflow.
Зачем: успешный импорт не гарантирует, что маршруты (routes) и сущности доехали — проверяется только живым запросом.
curl -sS -X POST "https://<ваш-домен>/api/agents/SupportBot/query" \
-H "Authorization: Bearer ${LAMPLINE_API_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"text":"Как перевыпустить пропуск?"}' | jq
Пример ожидаемого ответа: {"reply":"Уточните номер пропуска","intent":"pass_reissue","confidence":0.87} — имя интента должно совпадать с тем, что вы видите в Dialogflow CX. Если в ответе intent: "Default Fallback Intent" — маршруты не перенеслись.
| Что проверяем | Где смотреть | Ожидаемый результат |
|---|---|---|
| Контейнер | `docker compose ps` | `Up (healthy)` |
| HTTPS | `curl -I https://<ваш-домен>` | `HTTP/2 200` или `401` |
| Агент в списке | `GET /api/agents` | имя агента из Dialogflow |
| Интенты | UI агента | число = числу файлов в архиве |
| Диалог | `POST /query` | ответ по нужному интенту |
Для постоянного наблюдения за сервером и контейнерами удобно поднять бота — инструкция в гайде как настроить мониторинг VPS через Telegram-бота с Mini App. Точные пути эндпоинтов query и agents сверьте с документацией Lampline.
Проблема. Импорт отклонён с отказом доступа к Dialogflow
Permission 'dialogflow.agents.restore' denied on resourceDialogflow API Admin в проекте ${DIALOGFLOW_PROJECT_ID} или ключ от другого проекта.gcloud projects get-iam-policy ${DIALOGFLOW_PROJECT_ID} \
--flatten="bindings[].members" \
--filter="bindings.members:<sa-email>" --format="value(bindings.role)"
python3 -c "import json;print(json.load(open('secrets/gcp-sa.json'))['project_id'])"
Нужна строка с ролью roles/dialogflow.admin, а project_id в ключе должен совпадать с DIALOGFLOW_PROJECT_ID.
Проблема. Архив не принимается как содержимое агента
INVALID_ARGUMENT: Agent content is not a valid blobunzip -l agent.blob | head
base64 -w0 agent.blob | wc -l # должно быть 1 (одна строка)
Проблема. Контейнер падает сразу после старта
standard_init_linux.go:228: exec user process caused: exec format erroruname -m
docker compose down && docker compose pull && docker compose up -d
Если uname -m вернул aarch64, а в реестре только amd64-образ, добавьте в сервис platform: linux/amd64 — но это медленнее, чем нативный образ.
Вы развернули Lampline в Docker, закрыли его HTTPS-прокси и перенесли агента из Dialogflow CX архивом .blob, проверив импорт тестовым запросом. Дальше настройте резервное копирование каталога ./data и подключите голосовой канал (STT/TTS) — параметры сверьте с документацией Lampline.
Нужна ли GPU для self-hosted голосового агента?
Для контейнерного развёртывания Lampline GPU не требуется; она нужна только локальным моделям распознавания и синтеза речи. Требования к ресурсам под конкретный голосовой движок сверьте с документацией.
Чем .blob отличается от обычного JSON-экспорта?
.blob — это ZIP-архив: внутри agent.json с настройками агента и отдельные файлы интентов, флоу и сущностей. Именно его принимает метод :restore.
Можно ли тем же способом перенести агента из Dialogflow ES?
Нет. ES и CX — разные форматы и разные API; архив CX не развернётся в ES-агенте. Порядок миграции между версиями сверьте с документацией Google.
Что будет, если импортировать агента повторно?
Повторный импорт перезаписывает содержимое целевого агента. Безопаснее загружать в нового агента и переключать трафик после проверки.