refactor: 将客服后端重构为宿主可嵌入模块
- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。 - 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。 - 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。 - 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
This commit is contained in:
@@ -7,26 +7,26 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.tczkiot.com/wlw/ai-agent/internal/models"
|
||||
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
|
||||
"code.tczkiot.com/wlw/ai-agent/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"`
|
||||
ObservedIssue string `json:"observed_issue"`
|
||||
NeedHumanHandoff bool `json:"need_human_handoff"`
|
||||
NeedQualityCheck bool `json:"need_quality_check"`
|
||||
AdditionalContext string `json:"additional_context"`
|
||||
}
|
||||
|
||||
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"`
|
||||
UserIntent string `json:"user_intent"`
|
||||
RiskLevel string `json:"risk_level"`
|
||||
RiskSignals []string `json:"risk_signals,omitempty"`
|
||||
RecommendedNextAction string `json:"recommended_next_action"`
|
||||
RecommendedQuestions []string `json:"recommended_questions,omitempty"`
|
||||
ConversationFacts []string `json:"conversation_facts,omitempty"`
|
||||
}
|
||||
|
||||
type AnalyzeConversationGraph struct {
|
||||
@@ -130,9 +130,6 @@ func collectRiskSignals(joined string, input AnalyzeConversationInput) []string
|
||||
if containsAny(joined, "人工", "转人工", "真人", "客服") || input.NeedHumanHandoff {
|
||||
add("handoff_requested")
|
||||
}
|
||||
if containsAny(joined, "工单", "报障", "售后", "登记", "记录问题") || input.NeedTicket {
|
||||
add("ticket_expected")
|
||||
}
|
||||
if input.NeedQualityCheck {
|
||||
add("quality_review_requested")
|
||||
}
|
||||
@@ -143,8 +140,6 @@ 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:
|
||||
@@ -171,8 +166,6 @@ func recommendNextAction(intent string, signals []string, input AnalyzeConversat
|
||||
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:
|
||||
@@ -182,9 +175,6 @@ func recommendNextAction(intent string, signals []string, input AnalyzeConversat
|
||||
|
||||
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.")
|
||||
}
|
||||
@@ -222,3 +212,36 @@ func containsSignal(signals []string, target string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func buildRecentMessageDigest(messages []models.Message) string {
|
||||
parts := make([]string, 0, len(messages))
|
||||
for i := range messages {
|
||||
content := strings.TrimSpace(messages[i].Content)
|
||||
if content == "" {
|
||||
continue
|
||||
}
|
||||
parts = append(parts, messageSenderLabel(messages[i].SenderType)+":"+limitAnalysisText(content, 60))
|
||||
}
|
||||
return strings.Join(parts, " | ")
|
||||
}
|
||||
|
||||
func messageSenderLabel(senderType enums.IMSenderType) string {
|
||||
switch senderType {
|
||||
case enums.IMSenderTypeCustomer:
|
||||
return "Customer"
|
||||
case enums.IMSenderTypeAgent:
|
||||
return "Agent"
|
||||
case enums.IMSenderTypeAI:
|
||||
return "AI"
|
||||
default:
|
||||
return "Message"
|
||||
}
|
||||
}
|
||||
|
||||
func limitAnalysisText(value string, max int) string {
|
||||
runes := []rune(strings.TrimSpace(value))
|
||||
if max <= 0 || len(runes) <= max {
|
||||
return string(runes)
|
||||
}
|
||||
return strings.TrimSpace(string(runes[:max])) + "..."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user