feat(settings): Add separate Auto-Fix Model setting for remediation
Add configurable model specifically for automatic remediation actions: Backend (internal/config/ai.go): - Add AutoFixModel field to AIConfig - Add GetAutoFixModel() getter with fallback chain: AutoFixModel -> PatrolModel -> Model Frontend (AISettings.tsx, types/ai.ts): - Add auto_fix_model to AISettings types - Add Auto-Fix Model dropdown (only shows when patrol_auto_fix enabled) - Falls back to patrol model if not set API (ai_handlers.go): - Add auto_fix_model to response and update request - Handle saving/loading the new field Rationale: - Auto-fix takes real actions, may warrant a more capable model - Patrol observation can use cheaper models for cost savings - Gives users granular control over model costs vs reliability - Model hierarchy: Chat > AutoFix > Patrol > Default
This commit is contained in:
parent
f98fd845e1
commit
de8b36d65d
4 changed files with 78 additions and 0 deletions
|
|
@ -79,6 +79,7 @@ export const AISettings: Component = () => {
|
||||||
model: '',
|
model: '',
|
||||||
chatModel: '', // Empty means use default model
|
chatModel: '', // Empty means use default model
|
||||||
patrolModel: '', // Empty means use default model
|
patrolModel: '', // Empty means use default model
|
||||||
|
autoFixModel: '', // Empty means use patrol model
|
||||||
baseUrl: '', // Legacy - kept for compatibility
|
baseUrl: '', // Legacy - kept for compatibility
|
||||||
clearApiKey: false,
|
clearApiKey: false,
|
||||||
autonomousMode: false,
|
autonomousMode: false,
|
||||||
|
|
@ -105,6 +106,7 @@ export const AISettings: Component = () => {
|
||||||
model: '', // Will be set when provider is configured
|
model: '', // Will be set when provider is configured
|
||||||
chatModel: '',
|
chatModel: '',
|
||||||
patrolModel: '',
|
patrolModel: '',
|
||||||
|
autoFixModel: '',
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
clearApiKey: false,
|
clearApiKey: false,
|
||||||
autonomousMode: false,
|
autonomousMode: false,
|
||||||
|
|
@ -130,6 +132,7 @@ export const AISettings: Component = () => {
|
||||||
model: data.model || '', // User must select a model
|
model: data.model || '', // User must select a model
|
||||||
chatModel: data.chat_model || '',
|
chatModel: data.chat_model || '',
|
||||||
patrolModel: data.patrol_model || '',
|
patrolModel: data.patrol_model || '',
|
||||||
|
autoFixModel: data.auto_fix_model || '',
|
||||||
baseUrl: data.base_url || '',
|
baseUrl: data.base_url || '',
|
||||||
clearApiKey: false,
|
clearApiKey: false,
|
||||||
autonomousMode: data.autonomous_mode || false,
|
autonomousMode: data.autonomous_mode || false,
|
||||||
|
|
@ -280,6 +283,10 @@ export const AISettings: Component = () => {
|
||||||
payload.patrol_model = form.patrolModel;
|
payload.patrol_model = form.patrolModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (form.autoFixModel !== (settings()?.auto_fix_model || '')) {
|
||||||
|
payload.auto_fix_model = form.autoFixModel;
|
||||||
|
}
|
||||||
|
|
||||||
// Include multi-provider credentials if set (non-empty)
|
// Include multi-provider credentials if set (non-empty)
|
||||||
if (form.anthropicApiKey.trim()) {
|
if (form.anthropicApiKey.trim()) {
|
||||||
payload.anthropic_api_key = form.anthropicApiKey.trim();
|
payload.anthropic_api_key = form.anthropicApiKey.trim();
|
||||||
|
|
@ -607,6 +614,48 @@ export const AISettings: Component = () => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Auto-Fix Model Override */}
|
||||||
|
<Show when={form.patrolAutoFix}>
|
||||||
|
<div class={formField}>
|
||||||
|
<label class={labelClass()}>Auto-Fix Model (Remediation)</label>
|
||||||
|
<Show when={availableModels().length > 0} fallback={
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={form.autoFixModel}
|
||||||
|
onInput={(e) => setForm('autoFixModel', e.currentTarget.value)}
|
||||||
|
placeholder="Leave empty to use patrol model"
|
||||||
|
class={controlClass()}
|
||||||
|
disabled={saving()}
|
||||||
|
/>
|
||||||
|
}>
|
||||||
|
<select
|
||||||
|
value={form.autoFixModel}
|
||||||
|
onChange={(e) => setForm('autoFixModel', e.currentTarget.value)}
|
||||||
|
class={controlClass()}
|
||||||
|
disabled={saving()}
|
||||||
|
>
|
||||||
|
<option value="">Use patrol model ({form.patrolModel || form.model || 'not set'})</option>
|
||||||
|
<For each={Array.from(groupModelsByProvider(availableModels()).entries())}>
|
||||||
|
{([provider, models]) => (
|
||||||
|
<optgroup label={PROVIDER_DISPLAY_NAMES[provider] || provider}>
|
||||||
|
<For each={models}>
|
||||||
|
{(model) => (
|
||||||
|
<option value={model.id}>
|
||||||
|
{model.name || model.id.split(':').pop()}
|
||||||
|
</option>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</optgroup>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</select>
|
||||||
|
</Show>
|
||||||
|
<p class={formHelpText}>
|
||||||
|
Model for automatic remediation actions. Consider a more capable model since it takes action.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
{/* AI Provider Configuration - Configure API keys for all providers */}
|
{/* AI Provider Configuration - Configure API keys for all providers */}
|
||||||
<div class={`${formField} p-4 rounded-lg border bg-gradient-to-br from-purple-50 to-indigo-50 dark:from-purple-900/20 dark:to-indigo-900/20 border-purple-200 dark:border-purple-800`}>
|
<div class={`${formField} p-4 rounded-lg border bg-gradient-to-br from-purple-50 to-indigo-50 dark:from-purple-900/20 dark:to-indigo-900/20 border-purple-200 dark:border-purple-800`}>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export interface AISettings {
|
||||||
model: string;
|
model: string;
|
||||||
chat_model?: string; // Model for interactive chat (empty = use default)
|
chat_model?: string; // Model for interactive chat (empty = use default)
|
||||||
patrol_model?: string; // Model for background patrol (empty = use default)
|
patrol_model?: string; // Model for background patrol (empty = use default)
|
||||||
|
auto_fix_model?: string; // Model for auto-fix remediation (empty = use patrol model)
|
||||||
base_url?: string; // DEPRECATED: legacy base URL
|
base_url?: string; // DEPRECATED: legacy base URL
|
||||||
configured: boolean; // true if AI is ready to use
|
configured: boolean; // true if AI is ready to use
|
||||||
autonomous_mode: boolean; // true if AI can execute commands without approval
|
autonomous_mode: boolean; // true if AI can execute commands without approval
|
||||||
|
|
@ -55,6 +56,7 @@ export interface AISettingsUpdateRequest {
|
||||||
// Model overrides for different use cases
|
// Model overrides for different use cases
|
||||||
chat_model?: string; // Model for interactive chat
|
chat_model?: string; // Model for interactive chat
|
||||||
patrol_model?: string; // Model for background patrol
|
patrol_model?: string; // Model for background patrol
|
||||||
|
auto_fix_model?: string; // Model for auto-fix remediation
|
||||||
// Patrol settings for token efficiency
|
// Patrol settings for token efficiency
|
||||||
patrol_schedule_preset?: string; // DEPRECATED: use patrol_interval_minutes
|
patrol_schedule_preset?: string; // DEPRECATED: use patrol_interval_minutes
|
||||||
patrol_interval_minutes?: number; // Custom interval in minutes (0 = disabled, minimum 10)
|
patrol_interval_minutes?: number; // Custom interval in minutes (0 = disabled, minimum 10)
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,7 @@ type AISettingsResponse struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
ChatModel string `json:"chat_model,omitempty"` // Model for interactive chat (empty = use default)
|
ChatModel string `json:"chat_model,omitempty"` // Model for interactive chat (empty = use default)
|
||||||
PatrolModel string `json:"patrol_model,omitempty"` // Model for patrol (empty = use default)
|
PatrolModel string `json:"patrol_model,omitempty"` // Model for patrol (empty = use default)
|
||||||
|
AutoFixModel string `json:"auto_fix_model,omitempty"` // Model for auto-fix (empty = use patrol model)
|
||||||
BaseURL string `json:"base_url,omitempty"` // DEPRECATED: legacy base URL
|
BaseURL string `json:"base_url,omitempty"` // DEPRECATED: legacy base URL
|
||||||
Configured bool `json:"configured"` // true if AI is ready to use
|
Configured bool `json:"configured"` // true if AI is ready to use
|
||||||
AutonomousMode bool `json:"autonomous_mode"` // true if AI can execute without approval
|
AutonomousMode bool `json:"autonomous_mode"` // true if AI can execute without approval
|
||||||
|
|
@ -154,6 +155,7 @@ type AISettingsResponse struct {
|
||||||
// Patrol settings for token efficiency
|
// Patrol settings for token efficiency
|
||||||
PatrolSchedulePreset string `json:"patrol_schedule_preset"` // DEPRECATED: legacy preset
|
PatrolSchedulePreset string `json:"patrol_schedule_preset"` // DEPRECATED: legacy preset
|
||||||
PatrolIntervalMinutes int `json:"patrol_interval_minutes"` // Patrol interval in minutes (0 = disabled)
|
PatrolIntervalMinutes int `json:"patrol_interval_minutes"` // Patrol interval in minutes (0 = disabled)
|
||||||
|
PatrolAutoFix bool `json:"patrol_auto_fix"` // true if patrol can auto-fix issues
|
||||||
AlertTriggeredAnalysis bool `json:"alert_triggered_analysis"` // true if AI analyzes when alerts fire
|
AlertTriggeredAnalysis bool `json:"alert_triggered_analysis"` // true if AI analyzes when alerts fire
|
||||||
AvailableModels []config.ModelInfo `json:"available_models"` // List of models for current provider
|
AvailableModels []config.ModelInfo `json:"available_models"` // List of models for current provider
|
||||||
// Multi-provider credentials - shows which providers are configured
|
// Multi-provider credentials - shows which providers are configured
|
||||||
|
|
@ -176,6 +178,7 @@ type AISettingsUpdateRequest struct {
|
||||||
Model *string `json:"model,omitempty"`
|
Model *string `json:"model,omitempty"`
|
||||||
ChatModel *string `json:"chat_model,omitempty"` // Model for interactive chat
|
ChatModel *string `json:"chat_model,omitempty"` // Model for interactive chat
|
||||||
PatrolModel *string `json:"patrol_model,omitempty"` // Model for background patrol
|
PatrolModel *string `json:"patrol_model,omitempty"` // Model for background patrol
|
||||||
|
AutoFixModel *string `json:"auto_fix_model,omitempty"` // Model for auto-fix remediation
|
||||||
BaseURL *string `json:"base_url,omitempty"` // DEPRECATED: use per-provider URLs
|
BaseURL *string `json:"base_url,omitempty"` // DEPRECATED: use per-provider URLs
|
||||||
AutonomousMode *bool `json:"autonomous_mode,omitempty"`
|
AutonomousMode *bool `json:"autonomous_mode,omitempty"`
|
||||||
CustomContext *string `json:"custom_context,omitempty"` // user-provided infrastructure context
|
CustomContext *string `json:"custom_context,omitempty"` // user-provided infrastructure context
|
||||||
|
|
@ -183,6 +186,7 @@ type AISettingsUpdateRequest struct {
|
||||||
// Patrol settings for token efficiency
|
// Patrol settings for token efficiency
|
||||||
PatrolSchedulePreset *string `json:"patrol_schedule_preset,omitempty"` // DEPRECATED: use patrol_interval_minutes
|
PatrolSchedulePreset *string `json:"patrol_schedule_preset,omitempty"` // DEPRECATED: use patrol_interval_minutes
|
||||||
PatrolIntervalMinutes *int `json:"patrol_interval_minutes,omitempty"` // Custom interval in minutes (0 = disabled, minimum 10)
|
PatrolIntervalMinutes *int `json:"patrol_interval_minutes,omitempty"` // Custom interval in minutes (0 = disabled, minimum 10)
|
||||||
|
PatrolAutoFix *bool `json:"patrol_auto_fix,omitempty"` // true if patrol can auto-fix issues
|
||||||
AlertTriggeredAnalysis *bool `json:"alert_triggered_analysis,omitempty"` // true if AI analyzes when alerts fire
|
AlertTriggeredAnalysis *bool `json:"alert_triggered_analysis,omitempty"` // true if AI analyzes when alerts fire
|
||||||
// Multi-provider credentials
|
// Multi-provider credentials
|
||||||
AnthropicAPIKey *string `json:"anthropic_api_key,omitempty"` // Set Anthropic API key
|
AnthropicAPIKey *string `json:"anthropic_api_key,omitempty"` // Set Anthropic API key
|
||||||
|
|
@ -230,6 +234,7 @@ func (h *AISettingsHandler) HandleGetAISettings(w http.ResponseWriter, r *http.R
|
||||||
Model: settings.GetModel(),
|
Model: settings.GetModel(),
|
||||||
ChatModel: settings.ChatModel,
|
ChatModel: settings.ChatModel,
|
||||||
PatrolModel: settings.PatrolModel,
|
PatrolModel: settings.PatrolModel,
|
||||||
|
AutoFixModel: settings.AutoFixModel,
|
||||||
BaseURL: settings.BaseURL,
|
BaseURL: settings.BaseURL,
|
||||||
Configured: settings.IsConfigured(),
|
Configured: settings.IsConfigured(),
|
||||||
AutonomousMode: settings.AutonomousMode,
|
AutonomousMode: settings.AutonomousMode,
|
||||||
|
|
@ -239,6 +244,7 @@ func (h *AISettingsHandler) HandleGetAISettings(w http.ResponseWriter, r *http.R
|
||||||
// Patrol settings
|
// Patrol settings
|
||||||
PatrolSchedulePreset: settings.PatrolSchedulePreset,
|
PatrolSchedulePreset: settings.PatrolSchedulePreset,
|
||||||
PatrolIntervalMinutes: settings.PatrolIntervalMinutes,
|
PatrolIntervalMinutes: settings.PatrolIntervalMinutes,
|
||||||
|
PatrolAutoFix: settings.PatrolAutoFix,
|
||||||
AlertTriggeredAnalysis: settings.AlertTriggeredAnalysis,
|
AlertTriggeredAnalysis: settings.AlertTriggeredAnalysis,
|
||||||
AvailableModels: nil, // Now populated via /api/ai/models endpoint
|
AvailableModels: nil, // Now populated via /api/ai/models endpoint
|
||||||
// Multi-provider configuration
|
// Multi-provider configuration
|
||||||
|
|
@ -332,6 +338,14 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
|
||||||
settings.PatrolModel = strings.TrimSpace(*req.PatrolModel)
|
settings.PatrolModel = strings.TrimSpace(*req.PatrolModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.AutoFixModel != nil {
|
||||||
|
settings.AutoFixModel = strings.TrimSpace(*req.AutoFixModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PatrolAutoFix != nil {
|
||||||
|
settings.PatrolAutoFix = *req.PatrolAutoFix
|
||||||
|
}
|
||||||
|
|
||||||
if req.BaseURL != nil {
|
if req.BaseURL != nil {
|
||||||
settings.BaseURL = strings.TrimSpace(*req.BaseURL)
|
settings.BaseURL = strings.TrimSpace(*req.BaseURL)
|
||||||
}
|
}
|
||||||
|
|
@ -474,6 +488,7 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
|
||||||
Model: settings.GetModel(),
|
Model: settings.GetModel(),
|
||||||
ChatModel: settings.ChatModel,
|
ChatModel: settings.ChatModel,
|
||||||
PatrolModel: settings.PatrolModel,
|
PatrolModel: settings.PatrolModel,
|
||||||
|
AutoFixModel: settings.AutoFixModel,
|
||||||
BaseURL: settings.BaseURL,
|
BaseURL: settings.BaseURL,
|
||||||
Configured: settings.IsConfigured(),
|
Configured: settings.IsConfigured(),
|
||||||
AutonomousMode: settings.AutonomousMode,
|
AutonomousMode: settings.AutonomousMode,
|
||||||
|
|
@ -482,6 +497,7 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
|
||||||
OAuthConnected: settings.OAuthAccessToken != "",
|
OAuthConnected: settings.OAuthAccessToken != "",
|
||||||
PatrolSchedulePreset: settings.PatrolSchedulePreset,
|
PatrolSchedulePreset: settings.PatrolSchedulePreset,
|
||||||
PatrolIntervalMinutes: settings.PatrolIntervalMinutes,
|
PatrolIntervalMinutes: settings.PatrolIntervalMinutes,
|
||||||
|
PatrolAutoFix: settings.PatrolAutoFix,
|
||||||
AlertTriggeredAnalysis: settings.AlertTriggeredAnalysis,
|
AlertTriggeredAnalysis: settings.AlertTriggeredAnalysis,
|
||||||
AvailableModels: nil, // Now populated via /api/ai/models endpoint
|
AvailableModels: nil, // Now populated via /api/ai/models endpoint
|
||||||
// Multi-provider configuration
|
// Multi-provider configuration
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ type AIConfig struct {
|
||||||
PatrolAnalyzeDocker bool `json:"patrol_analyze_docker,omitempty"` // Include Docker hosts in patrol
|
PatrolAnalyzeDocker bool `json:"patrol_analyze_docker,omitempty"` // Include Docker hosts in patrol
|
||||||
PatrolAnalyzeStorage bool `json:"patrol_analyze_storage,omitempty"` // Include storage in patrol
|
PatrolAnalyzeStorage bool `json:"patrol_analyze_storage,omitempty"` // Include storage in patrol
|
||||||
PatrolAutoFix bool `json:"patrol_auto_fix,omitempty"` // When true, patrol can attempt automatic remediation (default: false, observe only)
|
PatrolAutoFix bool `json:"patrol_auto_fix,omitempty"` // When true, patrol can attempt automatic remediation (default: false, observe only)
|
||||||
|
AutoFixModel string `json:"auto_fix_model,omitempty"` // Model for automatic remediation (defaults to PatrolModel, may want more capable model)
|
||||||
|
|
||||||
// Alert-triggered AI analysis - analyze specific resources when alerts fire
|
// Alert-triggered AI analysis - analyze specific resources when alerts fire
|
||||||
AlertTriggeredAnalysis bool `json:"alert_triggered_analysis,omitempty"` // Enable AI analysis when alerts fire (token-efficient)
|
AlertTriggeredAnalysis bool `json:"alert_triggered_analysis,omitempty"` // Enable AI analysis when alerts fire (token-efficient)
|
||||||
|
|
@ -317,6 +318,16 @@ func (c *AIConfig) GetPatrolModel() string {
|
||||||
return c.GetModel()
|
return c.GetModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAutoFixModel returns the model for automatic remediation actions
|
||||||
|
// Falls back to PatrolModel, then to the main Model if AutoFixModel is not set
|
||||||
|
// Auto-fix may warrant a more capable model since it takes actions
|
||||||
|
func (c *AIConfig) GetAutoFixModel() string {
|
||||||
|
if c.AutoFixModel != "" {
|
||||||
|
return c.AutoFixModel
|
||||||
|
}
|
||||||
|
return c.GetPatrolModel()
|
||||||
|
}
|
||||||
|
|
||||||
// ClearOAuthTokens clears OAuth tokens (used when switching back to API key auth)
|
// ClearOAuthTokens clears OAuth tokens (used when switching back to API key auth)
|
||||||
func (c *AIConfig) ClearOAuthTokens() {
|
func (c *AIConfig) ClearOAuthTokens() {
|
||||||
c.OAuthAccessToken = ""
|
c.OAuthAccessToken = ""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue