feat(captain): align playground fallback

This commit is contained in:
2026-06-05 13:09:23 +08:00
parent e1b376cd76
commit 67d182b132
5 changed files with 319 additions and 31 deletions
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/gochat/gochat/internal/llm"
"github.com/gochat/gochat/internal/model"
@@ -59,6 +60,17 @@ type UpdateAssistantRequest struct {
Status string `json:"status"`
}
type PlaygroundMessage struct {
Role string `json:"role"`
Content string `json:"content"`
AgentName string `json:"agent_name,omitempty"`
}
type PlaygroundRequest struct {
MessageContent string `json:"message_content"`
MessageHistory []PlaygroundMessage `json:"message_history"`
}
// --- CRUD Operations ---
// Create creates a new CaptainAssistant.
@@ -309,6 +321,118 @@ func (s *CaptainAssistantService) GenerateResponse(ctx context.Context, assistan
return resp.Choices[0].Message.Content, nil
}
const captainPlaygroundFallbackMessage = "Captain assistant response generation is not configured for this account."
// GeneratePlaygroundResponse follows Chatwoot Captain assistant playground behavior.
func (s *CaptainAssistantService) GeneratePlaygroundResponse(ctx context.Context, accountID, assistantID uint, req PlaygroundRequest) (map[string]any, error) {
assistant, err := s.assistantRepo.GetByAccountAndID(ctx, accountID, assistantID)
if err != nil {
return nil, fmt.Errorf("assistant not found: %w", err)
}
if s.captainV2Enabled(ctx, accountID) {
history := playgroundMessageHistory(req.MessageHistory, req.MessageContent)
content, err := s.generatePlaygroundLLMResponse(ctx, assistant, history)
if err != nil {
return nil, err
}
return map[string]any{"response": content}, nil
}
history := append([]PlaygroundMessage{}, req.MessageHistory...)
content, err := s.generatePlaygroundLLMResponse(ctx, assistant, appendAdditionalPlaygroundMessage(history, req.MessageContent))
if err != nil {
return nil, err
}
return map[string]any{"content": content}, nil
}
func (s *CaptainAssistantService) generatePlaygroundLLMResponse(ctx context.Context, assistant *model.CaptainAssistant, history []PlaygroundMessage) (string, error) {
if s.llmProvider == nil {
return captainPlaygroundFallbackMessage, nil
}
cfg, _ := assistant.GetConfig()
messages := []llm.ChatMessage{{Role: "system", Content: buildSystemPrompt(assistant, cfg)}}
for _, message := range history {
if message.Role == "" || message.Content == "" {
continue
}
messages = append(messages, llm.ChatMessage{Role: message.Role, Content: message.Content})
}
resp, err := s.llmProvider.ChatCompletion(ctx, llm.ChatRequest{
Model: cfg.Model,
Messages: messages,
Temperature: cfg.Temperature,
MaxTokens: 1024,
})
if err != nil {
applogger.L().Errorf("GeneratePlaygroundResponse LLM call: %v", err)
return "", fmt.Errorf("llm generation failed: %w", err)
}
if len(resp.Choices) == 0 {
return "", fmt.Errorf("no response from LLM")
}
return resp.Choices[0].Message.Content, nil
}
func (s *CaptainAssistantService) captainV2Enabled(ctx context.Context, accountID uint) bool {
flags, err := s.assistantRepo.GetAccountFeatureFlags(ctx, accountID)
if err != nil {
return false
}
return featureFlagStringEnabled(flags, "captain_integration_v2")
}
func featureFlagStringEnabled(raw, flag string) bool {
raw = strings.TrimSpace(raw)
if raw == "" {
return false
}
var objectFlags map[string]bool
if err := json.Unmarshal([]byte(raw), &objectFlags); err == nil {
return objectFlags[flag]
}
var arrayFlags []string
if err := json.Unmarshal([]byte(raw), &arrayFlags); err == nil {
for _, item := range arrayFlags {
if item == flag {
return true
}
}
return false
}
for _, item := range strings.Split(raw, ",") {
if strings.TrimSpace(item) == flag {
return true
}
}
return false
}
func playgroundMessageHistory(history []PlaygroundMessage, current string) []PlaygroundMessage {
result := append([]PlaygroundMessage{}, history...)
if strings.TrimSpace(current) == "" {
return result
}
currentMessage := PlaygroundMessage{Role: "user", Content: current}
if len(result) > 0 {
last := result[len(result)-1]
if last.Role == currentMessage.Role && last.Content == currentMessage.Content && last.AgentName == "" {
return result
}
}
return append(result, currentMessage)
}
func appendAdditionalPlaygroundMessage(history []PlaygroundMessage, current string) []PlaygroundMessage {
if strings.TrimSpace(current) == "" {
return history
}
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)