Files
tf_provider/docs/20_discovery/natural-language-infrastructure.md
T
2026-06-30 15:45:24 +04:00

9.1 KiB
Raw Blame History

Natural Language Infrastructure (NLI) Pattern

Статус: Реализовано
Компонент: Tubulus Resource + Gemini AI
Файлы: internal/provider/tubulus_ai.go, internal/provider/tubulus_resource.go

Концепция

Вместо того, чтобы пользователь писал технические параметры в HCL, он может указать инструкцию на естественном языке, которую AI (Gemini) преобразует в конфигурацию на этапе terraform plan.

Архитектура

┌──────────────────┐
│  User Instruction│  "создай на 30 секунд, 
│  (Natural Lang)  │   запиши пароль_123"
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  ModifyPlan()    │  Terraform Plugin Framework
│  (Hook)          │  Перехватывает план до показа
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  askGemini()     │  Gemini 2.0 Flash API
│  (AI Parser)     │  Возвращает структурированный JSON
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  Plan Injection  │  plan.DurationMs = 30000
│  (Computed)      │  plan.BodyMessage = "пароль_123"
└──────────────────┘

Пример использования

resource "nubes_tubulus_instance" "test" {
  display_name = "My Test"
  description  = "AI-driven resource"
  
  # Естественный язык вместо технических параметров
  instruction = "поработай секунд 20, положи 'hello world' и вали на 1 этапе"
  
  # Эти поля рассчитаются автоматически через AI:
  # duration_ms      = 20000
  # body_message     = "hello world"
  # where_fail       = 1
  # fail_in_progress = true
}

Системный промпт (ключевые элементы)

  1. Типизация с примерами:

    "duration_ms": (integer) Длительность в миллисекундах
    Примеры: "5 секунд" → 5000, "минута" → 60000, "быстро" → 1000
    
  2. Строгие ограничения:

    "where_fail": ТОЛЬКО [0, 1, 2, 3]
    0 = не ломается, 1 = prepare, 2 = data_fill, 3 = after_vault
    
  3. Логические зависимости:

    Если fail_in_progress=true, но этап не указан — ставь where_fail=2
    
  4. Дефолты для null:

    Если НЕ указан текст явно для body_message — оставь null (не "ai_generated")
    
  5. Понимание разговорного языка:

    "долго" = 120000, "очень долго" = 300000, "полминутки" = 30000
    

Технические детали

ModifyPlan Lifecycle

func (r *TubulusResource) ModifyPlan(ctx, req, resp) {
    // 1. Проверка: это удаление?
    if req.Plan.Raw.IsNull() { return }
    
    // 2. Извлечение плана
    var plan TubulusResourceModel
    resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
    
    // 3. Проверка: есть инструкция?
    if plan.Instruction.IsNull() { return }
    
    // 4. СТАБИЛИЗАЦИЯ: уже вызывали AI?
    // (Предотвращает "plan inconsistency" между plan/apply)
    if !plan.DurationMs.IsUnknown() && 
       !plan.BodyMessage.IsUnknown() {
        return
    }
    
    // 5. Вызов Gemini
    aiConfig, err := r.askGemini(ctx, plan.Instruction.ValueString())
    
    // 6. Инъекция значений
    if aiConfig.DurationMs != nil {
        plan.DurationMs = types.Int64Value(*aiConfig.DurationMs)
    }
    
    // 7. Обновление плана
    resp.Plan.Set(ctx, &plan)
}

Стабилизация (Critical!)

Проблема: Terraform вызывает ModifyPlan() дважды:

  • 1-й раз: при terraform plan (показ пользователю)
  • 2-й раз: при terraform apply (финальная проверка)

Если Gemini возвращает разные значения → ошибка:

Error: Provider produced inconsistent final plan

Решение: Проверяем, что значения ещё Unknown:

if !plan.BodyMessage.IsUnknown() {
    return  // Уже вызывали AI, не повторяем
}

Null vs Empty String

Важно: Если AI не вернул body_message, устанавливаем null, а не пустую строку:

if aiConfig.BodyMessage != nil {
    plan.BodyMessage = types.StringValue(*aiConfig.BodyMessage)
} else {
    plan.BodyMessage = types.StringNull()  // ← Критично!
}

Иначе: план при apply будет видеть "" вместо null и выдаст inconsistency.

Тестовые кейсы (100% Success Rate)

Инструкция Duration BodyMessage WhereFail FailInProgress
"сделай быстро" 1000 null 0 false
"долго долго" 300000 null 0 false
"напиши привет" 5000 "привет" 0 false
"сломай сразу" 5000 null 0 true (at_start)
"зделай нормална на 10 сикунд" 10000 null 0 false
"30 сек, упади в середине, пароль_123" 30000 "пароль_123" 2 true
"duration 15000ms, fail at stage 3" 15000 null 3 true
"просто сделай что-нибудь" 5000 null 0 false

Расширяемость

Этот паттерн можно применить к любому ресурсу провайдера:

PostgreSQL NLI

# resource "nubes_postgres_instance" "db" {
  instruction = "создай базу на 50 гигов с репликой в двух зонах"
  # AI → cpu=2, ram=4096, disk=50000, replicas=2
}

VM NLI

# resource "nubes_vm_instance" "server" {
  instruction = "подними ubuntu 22.04 с 8 ядрами и 16 гигами памяти"
  # AI → os="ubuntu-22.04", cpu=8, ram=16384
}

Edge Gateway NLI

# resource "nubes_edge_gateway" "firewall" {
  instruction = "настрой файрвол: блокируй китай и открой 80, 443"
  # AI → firewall_rules=[...], allowed_ports=[80,443]
}

Преимущества

  1. Снижение порога входа: Новички могут писать конфигурации без изучения схемы ресурсов
  2. Скорость разработки: "Создай Postgres" вместо 30 строк HCL
  3. Читаемость: instruction = "быстрая база" понятнее, чем cpu=1, ram=2048, disk=10000
  4. Прозрачность: Результат виден в terraform plan (не чёрный ящик)
  5. Аудит: Все AI вызовы логируются → можно отследить "что просил vs что получил"

Ограничения

  1. Latency: Каждый terraform plan вызывает Gemini (~1-2 секунды)
  2. Cost: Free Tier Gemini → 15 requests/min. Для крупных проектов нужен Billing
  3. Determinism: AI может интерпретировать один запрос по-разному (минимизировано через примеры в промпте)
  4. Offline: Требует интернет для доступа к Gemini API

Улучшения (TODO)

  • Caching: Кэшировать (instruction → JSON) локально, чтобы повторный plan не вызывал API
  • Offline Mode: Fallback на дефолтные значения, если Gemini недоступен
  • Multi-language: Поддержка английского, немецкого и т.д.
  • Validation: Предупреждение, если AI вернул некорректные значения (например, where_fail=99)

Выводы

Natural Language Infrastructure — это первый шаг к "инфраструктуре как разговор". Вместо того, чтобы быть промежуточным звеном между человеком и API, Terraform становится интерфейсом, а AI — компилятором.

Эта реализация доказывает, что Plugin Framework достаточно гибок для таких экспериментов, и открывает дверь к новому способу управления облаком.