86 lines
3.1 KiB
Go
86 lines
3.1 KiB
Go
// internal/ai/analyzer.go
|
|
// Дата: 2026-03-12
|
|
// Оркестратор AI-анализа: читает уровень, формирует промпт,
|
|
// вызывает LLM-провайдер, форматирует вывод для пользователя.
|
|
|
|
package ai
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Analyzer — основной компонент AI-анализа.
|
|
// Создаётся один раз при старте sless-plan, используется для одного анализа.
|
|
type Analyzer struct {
|
|
provider LLMProvider
|
|
level int
|
|
}
|
|
|
|
// NewAnalyzer создаёт Analyzer из конфига.
|
|
// Возвращает ошибку если провайдер не может быть инициализирован.
|
|
func NewAnalyzer(cfg Config) (*Analyzer, error) {
|
|
if cfg.HintLevel == 0 {
|
|
// Уровень 0 — AI выключен, провайдер не нужен
|
|
return &Analyzer{level: 0}, nil
|
|
}
|
|
|
|
provider, err := NewProvider(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ошибка инициализации AI провайдера: %w", err)
|
|
}
|
|
|
|
return &Analyzer{
|
|
provider: provider,
|
|
level: cfg.HintLevel,
|
|
}, nil
|
|
}
|
|
|
|
// IsEnabled возвращает true если уровень > 0 и провайдер инициализирован.
|
|
func (a *Analyzer) IsEnabled() bool {
|
|
return a.level > 0 && a.provider != nil
|
|
}
|
|
|
|
// Analyze отправляет вывод terraform plan в LLM и возвращает отформатированный результат.
|
|
// planText — текстовый вывод terraform plan.
|
|
func (a *Analyzer) Analyze(ctx context.Context, planText string) (string, error) {
|
|
if !a.IsEnabled() {
|
|
return "", nil
|
|
}
|
|
|
|
// Ограничиваем размер payload — большие планы обрезаем
|
|
// чтобы не превысить лимит токенов LLM и не отправлять лишнее
|
|
planText = truncatePlan(planText, 8000)
|
|
|
|
prompt := BuildPrompt(a.level, planText)
|
|
result, err := a.provider.Analyze(ctx, prompt)
|
|
if err != nil {
|
|
return "", fmt.Errorf("AI анализ не удался: %w", err)
|
|
}
|
|
|
|
return FormatResult(a.level, a.provider.Name(), result), nil
|
|
}
|
|
|
|
// FormatResult оборачивает ответ LLM в читаемый блок для вывода в терминал.
|
|
func FormatResult(level int, providerName, result string) string {
|
|
separator := strings.Repeat("─", 60)
|
|
header := fmt.Sprintf("AI Analysis (level %d: %s) via %s", level, LevelName(level), providerName)
|
|
return fmt.Sprintf("\n%s\n %s\n%s\n%s\n%s\n",
|
|
separator,
|
|
header,
|
|
separator,
|
|
result,
|
|
separator,
|
|
)
|
|
}
|
|
|
|
// truncatePlan обрезает текст плана до maxChars символов с пометкой об обрезке.
|
|
// Нужно чтобы не превышать лимиты токенов LLM и не отправлять лишние данные.
|
|
func truncatePlan(text string, maxChars int) string {
|
|
if len(text) <= maxChars {
|
|
return text
|
|
}
|
|
return text[:maxChars] + fmt.Sprintf("\n\n[... обрезано, показаны первые %d символов из %d ...]", maxChars, len(text))
|
|
}
|