package graphs import ( "context" "encoding/json" "fmt" "strings" "agent-desk/internal/models" "agent-desk/internal/services" ) type AnalyzeConversationInput struct { Goal string `json:"goal"` ObservedIssue string `json:"observedIssue"` NeedTicket bool `json:"needTicket"` NeedHumanHandoff bool `json:"needHumanHandoff"` NeedQualityCheck bool `json:"needQualityCheck"` AdditionalContext string `json:"additionalContext"` } type AnalyzeConversationResult struct { Summary string `json:"summary"` UserIntent string `json:"userIntent"` RiskLevel string `json:"riskLevel"` RiskSignals []string `json:"riskSignals,omitempty"` RecommendedNextAction string `json:"recommendedNextAction"` RecommendedQuestions []string `json:"recommendedQuestions,omitempty"` ConversationFacts []string `json:"conversationFacts,omitempty"` } type AnalyzeConversationGraph struct { conversation models.Conversation } func NewAnalyzeConversationGraph(conversation models.Conversation) *AnalyzeConversationGraph { return &AnalyzeConversationGraph{conversation: conversation} } func (g *AnalyzeConversationGraph) Run(_ context.Context, argumentsInJSON string) (string, error) { input, err := g.parseInput(argumentsInJSON) if err != nil { return "", err } messages, _, _ := services.MessageService.FindByConversationIDCursor(g.conversation.ID, 0, 8, "", "") result := buildAnalyzeConversationResult(g.conversation, messages, input) buf, err := json.Marshal(result) if err != nil { return "", err } return string(buf), nil } func (g *AnalyzeConversationGraph) parseInput(argumentsInJSON string) (AnalyzeConversationInput, error) { var input AnalyzeConversationInput if strings.TrimSpace(argumentsInJSON) == "" { return input, nil } if err := json.Unmarshal([]byte(argumentsInJSON), &input); err != nil { return input, fmt.Errorf("invalid analyze conversation arguments: %w", err) } input.Goal = strings.TrimSpace(input.Goal) input.ObservedIssue = strings.TrimSpace(input.ObservedIssue) input.AdditionalContext = strings.TrimSpace(input.AdditionalContext) return input, nil } func buildAnalyzeConversationResult(conversation models.Conversation, messages []models.Message, input AnalyzeConversationInput) AnalyzeConversationResult { joined := strings.ToLower(buildConversationCorpus(conversation, messages, input)) signals := collectRiskSignals(joined, input) intent := detectUserIntent(joined, input) recommendedAction := recommendNextAction(intent, signals, input) result := AnalyzeConversationResult{ Summary: buildConversationSummary(conversation, messages, input), UserIntent: intent, RiskLevel: deriveRiskLevel(signals), RiskSignals: signals, RecommendedNextAction: recommendedAction, RecommendedQuestions: recommendQuestions(intent, signals, input), ConversationFacts: buildConversationAnalysisFacts(conversation, messages), } return result } func buildConversationSummary(conversation models.Conversation, messages []models.Message, input AnalyzeConversationInput) string { parts := make([]string, 0, 4) if input.ObservedIssue != "" { parts = append(parts, "当前问题:"+input.ObservedIssue) } else if strings.TrimSpace(conversation.LastMessageSummary) != "" { parts = append(parts, "当前问题:"+strings.TrimSpace(conversation.LastMessageSummary)) } if digest := buildRecentMessageDigest(messages); digest != "" { parts = append(parts, "最近对话:"+digest) } if input.AdditionalContext != "" { parts = append(parts, "补充信息:"+input.AdditionalContext) } return strings.TrimSpace(strings.Join(parts, "\n")) } func buildConversationCorpus(conversation models.Conversation, messages []models.Message, input AnalyzeConversationInput) string { parts := make([]string, 0, len(messages)+4) parts = append(parts, strings.TrimSpace(conversation.LastMessageSummary)) parts = append(parts, input.Goal, input.ObservedIssue, input.AdditionalContext) for i := range messages { parts = append(parts, strings.TrimSpace(messages[i].Content)) } return strings.Join(parts, "\n") } func collectRiskSignals(joined string, input AnalyzeConversationInput) []string { signals := make([]string, 0, 6) add := func(signal string) { for _, item := range signals { if item == signal { return } } signals = append(signals, signal) } if containsAny(joined, "投诉", "举报", "差评", "曝光", "媒体", "起诉", "律师", "12315") { add("complaint_escalation") } if containsAny(joined, "退款", "赔偿", "损失", "扣款", "重复扣费", "金额") { add("financial_risk") } if containsAny(joined, "生气", "愤怒", "垃圾", "太差", "一直没人", "再不处理") { add("negative_sentiment") } if containsAny(joined, "人工", "转人工", "真人", "客服") || input.NeedHumanHandoff { add("handoff_requested") } if containsAny(joined, "工单", "报障", "售后", "登记", "记录问题") || input.NeedTicket { add("ticket_expected") } if input.NeedQualityCheck { add("quality_review_requested") } return signals } func detectUserIntent(joined string, input AnalyzeConversationInput) string { switch { case input.NeedHumanHandoff || containsAny(joined, "人工", "转人工", "真人"): return "handoff_request" case input.NeedTicket || containsAny(joined, "工单", "报障", "售后", "登记问题"): return "ticket_request" case containsAny(joined, "投诉", "举报", "差评", "赔偿"): return "complaint" default: return "general_support" } } func deriveRiskLevel(signals []string) string { if len(signals) == 0 { return "low" } if containsSignal(signals, "complaint_escalation") || containsSignal(signals, "financial_risk") { return "high" } if containsSignal(signals, "handoff_requested") || containsSignal(signals, "negative_sentiment") { return "medium" } return "low" } func recommendNextAction(intent string, signals []string, input AnalyzeConversationInput) string { switch { case input.NeedQualityCheck: return "quality_review" case containsSignal(signals, "handoff_requested") || intent == "handoff_request": return "handoff_to_human" case containsSignal(signals, "ticket_expected") || intent == "ticket_request": return "prepare_ticket" case containsSignal(signals, "complaint_escalation"): return "handoff_to_human" default: return "continue_answering" } } func recommendQuestions(intent string, signals []string, input AnalyzeConversationInput) []string { questions := make([]string, 0, 3) if containsSignal(signals, "ticket_expected") && strings.TrimSpace(input.ObservedIssue) == "" { questions = append(questions, "Please confirm the specific issue, error message, and expected outcome.") } if containsSignal(signals, "handoff_requested") { questions = append(questions, "Please confirm whether the user explicitly requested human support and why the issue needs human handling.") } if intent == "complaint" { questions = append(questions, "Please confirm the complaint, impact, and the user's most important desired outcome.") } return questions } func buildConversationAnalysisFacts(conversation models.Conversation, messages []models.Message) []string { facts := make([]string, 0, 4) if strings.TrimSpace(conversation.LastMessageSummary) != "" { facts = append(facts, "最近摘要:"+strings.TrimSpace(conversation.LastMessageSummary)) } if digest := buildRecentMessageDigest(messages); digest != "" { facts = append(facts, "最近消息:"+digest) } return facts } func containsAny(value string, keywords ...string) bool { for _, keyword := range keywords { if keyword != "" && strings.Contains(value, strings.ToLower(keyword)) { return true } } return false } func containsSignal(signals []string, target string) bool { for _, item := range signals { if item == target { return true } } return false }