From 31238476b19ec7e259f0cdfcd2728b68c97bcfe8 Mon Sep 17 00:00:00 2001 From: Rogee Date: Sat, 1 Aug 2026 17:21:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(captain):=20=E4=BF=AE=E5=A4=8D=20playground?= =?UTF-8?q?=20500=20=E2=80=94=20GetConfig=20nil=20pointer=20panic=20+=20?= =?UTF-8?q?=E6=B8=85=E7=90=86=E9=87=8D=E5=A4=8D=20buildSystemPrompt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:captain_assistants.config 中 temperature 字段为字符串 "0.5", json.Unmarshal 到 float64 字段失败,GetConfig() 返回 nil, 调用方吞掉错误后 BuildAssistantPrompt 解引用 nil cfg 导致 panic。 修复: - GetConfig() unmarshal 失败时返回空 &cfg{} 而非 nil,防止 nil dereference - 删除 captain_assistant_service.go 中重复的 buildSystemPrompt 函数, 统一使用 SystemPromptBuilder.BuildAssistantPrompt - generatePlaygroundLLMResponse / GenerateResponse 增加 ErrProviderNotConfigured 检查,返回 fallback 消息而非 500 --- backend/internal/model/captain_models.go | 6 +++- .../service/captain_assistant_service.go | 33 ++++++++----------- 2 files changed, 18 insertions(+), 21 deletions(-) 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 -}