add: documentation
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
# 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 достаточно гибок для таких экспериментов, и открывает дверь к новому способу управления облаком.
|
||||
Reference in New Issue
Block a user