diff --git a/backend/internal/model/captain_models.go b/backend/internal/model/captain_models.go index a521aafb..e82b8b24 100644 --- a/backend/internal/model/captain_models.go +++ b/backend/internal/model/captain_models.go @@ -2,6 +2,7 @@ package model import ( "encoding/json" + "fmt" "github.com/pgvector/pgvector-go" ) @@ -118,7 +119,10 @@ func (a *CaptainAssistant) GetConfig() (*AssistantConfig, error) { return &cfg, nil } if err := json.Unmarshal(a.Config, &cfg); err != nil { - return nil, err + // Return an empty config rather than nil so callers that ignore the + // error do not dereference a nil pointer. The raw JSON may contain + // type mismatches (e.g. temperature as string) from older data. + return &cfg, fmt.Errorf("parse assistant config: %w", err) } var fields map[string]json.RawMessage if err := json.Unmarshal(a.Config, &fields); err == nil { diff --git a/backend/internal/service/captain_assistant_service.go b/backend/internal/service/captain_assistant_service.go index 2d6843ab..84848da7 100644 --- a/backend/internal/service/captain_assistant_service.go +++ b/backend/internal/service/captain_assistant_service.go @@ -3,6 +3,7 @@ package service import ( "context" "encoding/json" + "errors" "fmt" "math" "strconv" @@ -26,6 +27,7 @@ type CaptainAssistantService struct { responseRepo *repository.CaptainAssistantResponseRepo llmProvider llm.Provider cache *redis.Client + promptBuilder *SystemPromptBuilder } // NewCaptainAssistantService creates a new CaptainAssistantService. @@ -43,6 +45,7 @@ func NewCaptainAssistantService( documentRepo: documentRepo, responseRepo: responseRepo, llmProvider: llmProvider, + promptBuilder: NewSystemPromptBuilder(), } if len(cache) > 0 { svc.cache = cache[0] @@ -646,7 +649,7 @@ func (s *CaptainAssistantService) GenerateResponse(ctx context.Context, assistan // Build system prompt from assistant config and response guidelines cfg, _ := assistant.GetConfig() ctx = withAssistantGenerationConfig(ctx, cfg) - systemPrompt := buildSystemPrompt(assistant, cfg) + systemPrompt := s.promptBuilder.BuildAssistantPrompt(assistant, cfg) // Build messages for LLM messages := []llm.ChatMessage{ @@ -663,6 +666,10 @@ func (s *CaptainAssistantService) GenerateResponse(ctx context.Context, assistan resp, err := s.llmProvider.ChatCompletion(ctx, req) if err != nil { + if errors.Is(err, llm.ErrProviderNotConfigured) { + applogger.L().Warnf("GenerateResponse: LLM provider not configured, returning fallback") + return captainPlaygroundFallbackMessage, nil + } applogger.L().Errorf("GenerateResponse LLM call: %v", err) return "", fmt.Errorf("llm generation failed: %w", err) } @@ -716,7 +723,7 @@ func (s *CaptainAssistantService) generatePlaygroundLLMResponse(ctx context.Cont cfg, _ := assistant.GetConfig() ctx = withAssistantGenerationConfig(ctx, cfg) - messages := []llm.ChatMessage{{Role: "system", Content: buildSystemPrompt(assistant, cfg)}} + messages := []llm.ChatMessage{{Role: "system", Content: s.promptBuilder.BuildAssistantPrompt(assistant, cfg)}} for _, message := range history { if message.Role == "" || message.Content == "" { continue @@ -731,6 +738,10 @@ func (s *CaptainAssistantService) generatePlaygroundLLMResponse(ctx context.Cont MaxTokens: 1024, }) if err != nil { + if errors.Is(err, llm.ErrProviderNotConfigured) { + applogger.L().Warnf("GeneratePlaygroundResponse: LLM provider not configured, returning fallback") + return captainPlaygroundFallbackMessage, nil + } applogger.L().Errorf("GeneratePlaygroundResponse LLM call: %v", err) return "", fmt.Errorf("llm generation failed: %w", err) } @@ -802,21 +813,3 @@ func appendAdditionalPlaygroundMessage(history []PlaygroundMessage, current stri } return append(history, PlaygroundMessage{Role: "user", Content: current}) } - -// buildSystemPrompt constructs the system prompt from assistant config and guidelines. -func buildSystemPrompt(assistant *model.CaptainAssistant, cfg *model.AssistantConfig) string { - prompt := fmt.Sprintf("You are %s, an AI assistant.", assistant.Name) - if cfg.ProductName != "" { - prompt += fmt.Sprintf(" You represent the product: %s.", cfg.ProductName) - } - if cfg.Instructions != "" { - prompt += fmt.Sprintf("\nInstructions: %s", cfg.Instructions) - } - if len(assistant.ResponseGuidelines) > 0 && string(assistant.ResponseGuidelines) != "null" { - prompt += fmt.Sprintf("\nResponse Guidelines: %s", string(assistant.ResponseGuidelines)) - } - if len(assistant.Guardrails) > 0 && string(assistant.Guardrails) != "null" { - prompt += fmt.Sprintf("\nGuardrails: %s", string(assistant.Guardrails)) - } - return prompt -}