fix(captain): 修复 playground 500 — GetConfig nil pointer panic + 清理重复 buildSystemPrompt

根因: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
This commit is contained in:
Rogee
2026-08-01 17:21:01 +08:00
parent bb4c4bb807
commit 31238476b1
2 changed files with 18 additions and 21 deletions
+5 -1
View File
@@ -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 {
@@ -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
}