Система переменных и секретов
Централизованное управление переменными и секретами с изоляцией per-company.
Архитектура
Хранение
Storage (одна таблица variables):
company:ssd:var:telegram_bot_token = {"value": "123:ABC...", "secret": true}
company:ssd:var:bot_name = {"value": "My Bot", "secret": false}
company:acme:var:telegram_bot_token = {"value": "456:XYZ...", "secret": true}
Таблица БД:
- variables - единая таблица для всех компаний
- Изоляция через префикс ключа company:{company_id}:var:{key}
- Автоматическая изоляция через Storage маршрутизацию между компаниями
Синтаксис ссылок
@var:key - ссылка на переменную компании
# Хардкод
"token": "8395450365:AAHSUMIq84eZpjIRwKljo"
# Ссылка на переменную
"token": "@var:telegram_bot_token"
API
Сохранение переменной
POST /api/v1/admin/variables
Content-Type: application/json
{
"key": "telegram_bot_token",
"value": "8395450365:AAHSUMIReZpjIRwKljo",
"secret": true
}
Получение списка переменных
GET /api/v1/admin/variables
Response:
{
"telegram_bot_token": {
"value": "***", # Скрыто для секретов
"secret": true
},
"bot_name": {
"value": "My Bot",
"secret": false
}
}
Получение переменной
Удаление переменной
Scope переменных
1. Системные переменные (автоматические):
{company_name},{company_id},{company_subdomain}- из активной компании{user_name},{user_id}- из текущего пользователя{current_date},{current_time},{current_datetime}- текущая дата/время{current_year},{current_month},{current_day}- компоненты даты- Доступны всегда во всех flow
- Резолвятся при компиляции графа (статические)
2. Company Variables (хранилище секретов):
telegram_bot_token,weather_api_key,api_endpointи т.д.- Создаются через UI
/variablesили API - НЕ доступны напрямую в промпте
- Используются через
@var:keyссылки в flow.variables или flow.store - Изоляция per-company автоматическая
3. Flow переменные (статические):
- Объявляются в
FlowConfig.variables - Резолвятся ОДИН РАЗ при компиляции графа
- Доступны всем агентам в flow через
{variable} - Можно использовать хардкод или
@var:keyссылки - Используй для: конфигурация, константы, настройки
4. Session Store (динамические):
- Объявляются в
FlowConfig.storeилиAgentConfig.store(начальные значения) - Резолвятся ДИНАМИЧЕСКИ перед каждым вызовом LLM
- Доступны через
{store.key}илиsession_get("key") - Автоматически персистятся в БД (PostgreSQL checkpointer)
- Общие для ВСЕХ агентов в цепочке (широкая память)
- Используй для: накопление данных, счетчики, флаги, история диалога
5. Local переменные (статические, редкие):
- Объявляются в
AgentConfig.local_variables - Доступны только конкретному агенту
- Перекрывают flow.variables
- Редактируются через админку агентов
/frontend/models/agent
Использование в конфигурации
FlowConfig.platforms
Хардкод токена:
FlowConfig(
flow_id="weather_flow",
platforms={
"telegram": {
"username": "weather_bot",
"token": "8395450365:AAHUJq84eZpjIRwKljo"
}
}
)
Ссылка на переменную:
FlowConfig(
flow_id="weather_flow",
platforms={
"telegram": {
"username": "weather_bot",
"token": "@var:telegram_bot_token" # Резолвится автоматически
}
}
)
Смешанное использование:
FlowConfig(
flow_id="weather_flow",
platforms={
"telegram": {
"username": "@var:tg_bot_name", # Ссылка
"token": "@var:telegram_bot_token" # Ссылка
},
"api": {
"key": "hardcoded_key_123" # Хардкод
}
}
)
FlowConfig.variables
Переменные flow доступные в промптах агентов:
FlowConfig(
flow_id="support_flow",
variables={
"bot_name": "Support Bot", # Хардкод
"support_email": "@var:company_support_email", # Ссылка
"greeting": "Привет! Я @var:bot_name", # Смешанное
"timeout_minutes": "30"
}
)
Использование в промптах:
AgentConfig(
agent_id="support_agent",
prompt="""
Ты {bot_name}.
При необходимости переводи пользователя на {support_email}.
Таймаут: {timeout_minutes} минут.
"""
)
FlowConfig.store и AgentConfig.store
Начальные значения для Session Store:
FlowConfig(
flow_id="weather_flow",
store={
"max_requests_per_session": 10, # Хардкод
"show_welcome": True, # Хардкод
"api_key": "@var:weather_api_key", # Ссылка
"units": {
"temperature": "celsius", # Вложенные данные
"wind": "ms"
}
}
)
AgentConfig(
agent_id="weather_agent",
store={
"requests_count": 0,
"show_tips": True,
"preferred_units": "celsius"
}
)
Работа с Session Store в промптах и тулах:
# В промпте агента (динамическая подстановка)
prompt = """
Запросов в диалоге: {#messages.count}
Последний город: {?store.last_city|не было}
Счетчик: {?store.requests_count|0}
"""
# В туле (изменение store)
@tool
def save_city(city: str) -> str:
state = get_state()
state["store"]["last_city"] = city
state["store"]["requests_count"] = state["store"].get("requests_count", 0) + 1
return f"Сохранено: {city}"
# Или через session_tools
session_set("last_city", "Москва")
session_get("last_city") # → "Москва"
AgentConfig.prompt
Унифицированный синтаксис переменных:
AgentConfig(
prompt="""
Ты {bot_name} компании {?company_name|Weather Service}.
СТАТИЧЕСКИЕ (резолвятся при компиляции):
- Email: {?support_email|support@company.com}
- Таймаут: {?timeout|30} минут
- Настройка: {settings.language}
ДИНАМИЧЕСКИЕ (резолвятся перед каждым вызовом LLM):
- Последний запрос: {?store.last_city|нет}
- Всего запросов: {?store.count|0}
- Запросов в диалоге: {#messages.count}
{?store.last_city:
КОНТЕКСТ: Ранее вы интересовались {store.last_city}.
}
"""
)
Унифицированный синтаксис в промптах
Все переменные (статические и динамические) используют единый синтаксис:
Базовый синтаксис
{variable} # Обычная подстановка
{?variable} # Опциональная (пустая строка если нет)
{?variable|default} # Опциональная со значением по умолчанию
{dict.nested.key} # Вложенные данные
{#special.function} # Специальные функции (только для state)
Примеры
# Обычная подстановка
"Компания: {company_name}" → "Компания: ООО Доставка"
"Город: {store.last_city}" → "Город: Москва"
# Опциональная (без дефолта)
"Email: {?user_email}" → "Email: " (пусто если нет)
"Склад: {?store.warehouse_name}" → "Склад: " (пусто если нет)
# Опциональная с дефолтом
"Email: {?user_email|не указан}" → "Email: не указан"
"Таймаут: {?timeout|30} сек" → "Таймаут: 30 сек"
"Склад: {?store.warehouse|НЕТ}" → "Склад: НЕТ"
# Вложенные данные
"Язык: {settings.language}" → "Язык: ru"
"Температура: {store.units.temp}" → "Температура: celsius"
# Специальные функции (state)
"Сообщений: {#messages.count}" → "Сообщений: 5"
"Ключи: {#store.keys}" → "Ключи: warehouse_id, courier_id"
"Пустой: {#store.empty}" → "Пустой: false"
Условные блоки
prompt = """
{?store.warehouse_id:
✅ Склад определен: {store.warehouse_name} (ID: {store.warehouse_id})
Переходим к следующему этапу.
|
⏳ Склад еще не определен.
Используй warehouse_agent для определения склада.
}
"""
Комбинирование статических и динамических
prompt = """
Ты {bot_name} компании {company_name}.
Дата: {current_date}
СТАТИЧЕСКИЕ НАСТРОЙКИ:
- Email поддержки: {?support_email|support@company.com}
- Таймаут: {?timeout|30} минут
ДИНАМИЧЕСКИЕ ДАННЫЕ СЕССИИ:
- Последний запрос: {?store.last_query|нет}
- Всего запросов: {?store.requests_count|0}
- Сообщений в истории: {#messages.count}
{?store.last_query:
КОНТЕКСТ: Ранее вы спрашивали: "{store.last_query}"
}
"""
Где применяются переменные
1. Platform tokens (автоматическая резолюция)
# TelegramInterface.get_bot_token_for_flow()
platform_config = {
"username": "my_bot",
"token": "@var:telegram_bot_token"
}
# Автоматически резолвится в:
token = "8395450365:AAHSUMIRKYfq84eZpjIRwKljo"
2. Flow variables (доступны в context.flow_variables)
FlowConfig.variables = {
"bot_name": "Support Bot",
"timeout": "@var:default_timeout"
}
# В агенте через context:
context.flow_variables["bot_name"] # "Support Bot"
context.flow_variables["timeout"] # Резолвлено из @var:default_timeout
3. Agent prompts (подстановка через {key})
AgentConfig.prompt = "Я {bot_name}, работаю {timeout} минут"
# Автоматически подставляется из context.flow_variables
4. Session Store (динамические переменные сессии)
Автоматическая персистентность:
# В туле
@tool
def save_warehouse(warehouse_id: str, warehouse_name: str) -> str:
state = get_state()
state["store"]["warehouse_id"] = warehouse_id
state["store"]["warehouse_name"] = warehouse_name
# ✅ Автоматически сохранится в БД через checkpointer
return "Сохранено"
# В следующем вызове (тот же thread_id)
state["store"]["warehouse_id"] # → "12345" (восстановлено из БД!)
Общий store для всех агентов:
# Координатор устанавливает
state["store"]["user_context"] = {"name": "Виктор", "city": "Москва"}
# Субагент 1 видит и добавляет
state["store"]["user_context"] # → {"name": "Виктор", "city": "Москва"}
state["store"]["warehouse_id"] = "12345"
# Субагент 2 видит ВСЕ
state["store"]["user_context"] # → {"name": "Виктор", "city": "Москва"}
state["store"]["warehouse_id"] # → "12345"
state["store"]["courier_id"] = "789"
# Координатор видит результаты ВСЕХ субагентов
state["store"] # → {user_context, warehouse_id, courier_id}
Умное слияние (merge_store):
# Вызов 1
state["store"] = {
"settings": {"language": "ru", "units": "celsius"},
"counter": 1
}
# Вызов 2 (тот же thread_id)
state["store"]["settings"]["theme"] = "dark" # Добавляем в вложенный dict
state["store"]["counter"] = 2 # Перезаписываем простое значение
# Результат (merge_store):
{
"settings": {
"language": "ru", # ← Осталось из Вызова 1
"units": "celsius", # ← Осталось из Вызова 1
"theme": "dark" # ← Добавлено в Вызове 2
},
"counter": 2 # ← Перезаписано
}
Доступ в промптах:
prompt = """
СЕССИОННЫЕ ДАННЫЕ:
- Последний город: {?store.last_city|не было запросов}
- Склад: {?store.warehouse_name|не определен}
- Счетчик запросов: {?store.requests_count|0}
- Сообщений: {#messages.count}
{?store.last_city:
Ранее вы интересовались {store.last_city}.
}
"""
Приоритет резолюции
Когда используется {key} в промпте:
- state.store - переменные сессии (runtime)
- flow.variables - переменные flow
- company.variables - переменные компании (через @var:)
Примеры сценариев
Сценарий 1: Один токен для нескольких ботов
# Сохраняем токен один раз
POST /api/v1/admin/variables
{
"key": "main_telegram_token",
"value": "123:ABC...",
"secret": true
}
# Используем в разных flow
FlowConfig(flow_id="support_flow", platforms={
"telegram": {"username": "support_bot", "token": "@var:main_telegram_token"}
})
FlowConfig(flow_id="sales_flow", platforms={
"telegram": {"username": "sales_bot", "token": "@var:main_telegram_token"}
})
Сценарий 2: Переиспользование настроек
# Сохраняем общие настройки
POST /api/v1/admin/variables
{
"key": "company_name",
"value": "SSD Company",
"secret": false
}
# Используем во всех flow
FlowConfig.variables = {
"greeting": "Привет от @var:company_name!",
"support_email": "@var:support_email"
}
Сценарий 3: Разные токены для разных компаний
company:ssd:var:telegram_bot_token = "123:ABC..."
company:acme:var:telegram_bot_token = "456:XYZ..."
# Оба flow используют @var:telegram_bot_token
# Но получают разные значения в зависимости от компании
Текущая реализация
Что работает:
✅ VariablesService - сохранение/получение/резолюция
✅ Таблица variables - единая для всех компаний с изоляцией через префикс
✅ API endpoints - полное CRUD управление (/api/v1/admin/variables)
✅ Резолюция @var:key в platforms - автоматически в TelegramInterface
✅ Резолюция FlowConfig.variables - автоматически в TaskProcessor
✅ Резолюция FlowConfig.store - автоматически при миграции
✅ Резолюция AgentConfig.store - автоматически при миграции
✅ Вложенные структуры - dict/list с @var:key внутри
✅ Per-company изоляция - автоматическая через Storage
✅ Унифицированный синтаксис - {?var|default} для всех типов переменных
✅ State variables - динамическая подстановка {store.key} в промптах
✅ Специальные функции - {#messages.count}, {#store.keys}
✅ Session Store персистентность - автоматическое сохранение в PostgreSQL
✅ Общий store между агентами - все агенты в цепочке видят один store
✅ UI для Session Store - категория "Session Store" в Prompt Editor
Что можно улучшить:
⏳ Миграция старых token:telegram:{username} в company variables
⏳ Валидация переменных (проверка что @var:key существует)
⏳ Предпросмотр резолвнутых значений в UI
Миграция со старого формата
Старый формат (токены):
Новый формат (переменные):
company:ssd:var:telegram_bot_token = {"value": "123:ABC...", "secret": true}
FlowConfig.platforms = {
"telegram": {
"username": "my_bot",
"token": "@var:telegram_bot_token"
}
}
Обратная совместимость:
TelegramInterface.get_bot_token_for_flow() поддерживает оба формата:
- Сначала проверяет
platform_config.get("token") - Если есть
@var:- резолвит через VariablesService - Если нет - ищет legacy
token:telegram:{username}
Безопасность
Секреты в БД:
secret: true- помечает переменную как секрет- В API списке показывается как
"***" - В UI скрывается паролем
type="password"
Per-company изоляция:
- Компания
ssdвидит толькоssd_variables - Компания
acmeвидит толькоacme_variables - Автоматическая изоляция через Storage маршрутизацию