220 lines
9.1 KiB
Markdown
220 lines
9.1 KiB
Markdown
# 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 достаточно гибок для таких экспериментов, и открывает дверь к новому способу управления облаком.
|