# 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" └──────────────────┘ ``` ## Пример использования ```text 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 ```go 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`: ```go if !plan.BodyMessage.IsUnknown() { return // Уже вызывали AI, не повторяем } ``` ### Null vs Empty String **Важно:** Если AI не вернул `body_message`, устанавливаем `null`, а не пустую строку: ```go 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 ```text # resource "nubes_postgres_instance" "db" { instruction = "создай базу на 50 гигов с репликой в двух зонах" # AI → cpu=2, ram=4096, disk=50000, replicas=2 } ``` ### VM NLI ```text # resource "nubes_vm_instance" "server" { instruction = "подними ubuntu 22.04 с 8 ядрами и 16 гигами памяти" # AI → os="ubuntu-22.04", cpu=8, ram=16384 } ``` ### Edge Gateway NLI ```hcl # 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 достаточно гибок для таких экспериментов, и открывает дверь к новому способу управления облаком.