512 lines
16 KiB
Go
512 lines
16 KiB
Go
package biz
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
contextpkg "eino-project/internal/domain/context"
|
||
"eino-project/internal/domain/monitor"
|
||
"eino-project/internal/domain/vector"
|
||
|
||
"github.com/go-kratos/kratos/v2/log"
|
||
)
|
||
|
||
// CustomerRepo 智能客服数据仓库接口
|
||
type CustomerRepo interface {
|
||
CheckSystemHealth(ctx context.Context) map[string]ServiceStatus
|
||
}
|
||
|
||
// CustomerUseCase 智能客服业务逻辑
|
||
type CustomerUseCase struct {
|
||
repo CustomerRepo
|
||
knowledgeSearcher vector.KnowledgeSearcher
|
||
docProcessor vector.DocumentProcessor
|
||
log *log.Helper
|
||
contextManager contextpkg.ContextManager
|
||
monitor monitor.Monitor
|
||
}
|
||
|
||
// NewCustomerUseCase 创建客户服务用例
|
||
func NewCustomerUseCase(repo CustomerRepo, logger log.Logger, knowledgeSearcher vector.KnowledgeSearcher, docProcessor vector.DocumentProcessor, contextManager contextpkg.ContextManager, monitor monitor.Monitor) *CustomerUseCase {
|
||
return &CustomerUseCase{
|
||
repo: repo,
|
||
log: log.NewHelper(logger),
|
||
knowledgeSearcher: knowledgeSearcher,
|
||
docProcessor: docProcessor,
|
||
contextManager: contextManager,
|
||
monitor: monitor,
|
||
}
|
||
}
|
||
|
||
// SystemStatus 系统状态信息
|
||
type SystemStatus struct {
|
||
Status bool `json:"status"`
|
||
Services map[string]ServiceStatus `json:"services"`
|
||
Version string `json:"version"`
|
||
}
|
||
|
||
// ServiceStatus 服务状态
|
||
type ServiceStatus struct {
|
||
Name string `json:"name"`
|
||
Status bool `json:"status"`
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
// Session 会话信息
|
||
type Session struct {
|
||
SessionID string `json:"session_id"`
|
||
Title string `json:"title"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
}
|
||
|
||
// ChatMessage 聊天消息
|
||
type ChatMessage struct {
|
||
SessionID string `json:"session_id"`
|
||
Message string `json:"message"`
|
||
Timestamp time.Time `json:"timestamp"`
|
||
Type string `json:"type"`
|
||
}
|
||
|
||
// KnowledgeDocument 知识库文档
|
||
type KnowledgeDocument struct {
|
||
ID string `json:"id"`
|
||
FileName string `json:"file_name"`
|
||
FileType string `json:"file_type"`
|
||
ContentPreview string `json:"content_preview"`
|
||
UploadTime time.Time `json:"upload_time"`
|
||
Status string `json:"status"`
|
||
}
|
||
|
||
// OrderDetails 订单详情
|
||
type OrderDetails struct {
|
||
OrderID string `json:"order_id"`
|
||
Status string `json:"status"`
|
||
Product string `json:"product"`
|
||
Amount float64 `json:"amount"`
|
||
CreateTime time.Time `json:"create_time"`
|
||
NeedAI bool `json:"need_ai"`
|
||
}
|
||
|
||
// GetSystemStatus 获取系统状态
|
||
func (uc *CustomerUseCase) GetSystemStatus(ctx context.Context) (*SystemStatus, error) {
|
||
uc.log.WithContext(ctx).Info("Getting system status")
|
||
|
||
// 通过数据仓库检查系统健康状态
|
||
services := uc.repo.CheckSystemHealth(ctx)
|
||
|
||
// 计算整体系统状态
|
||
overallStatus := true
|
||
for _, service := range services {
|
||
if !service.Status {
|
||
overallStatus = false
|
||
break
|
||
}
|
||
}
|
||
|
||
return &SystemStatus{
|
||
Status: overallStatus,
|
||
Services: services,
|
||
Version: "1.0.0",
|
||
}, nil
|
||
}
|
||
|
||
// CreateSession 创建新会话
|
||
func (uc *CustomerUseCase) CreateSession(ctx context.Context, title string) (*Session, error) {
|
||
uc.log.WithContext(ctx).Infof("Creating new session with title: %s", title)
|
||
|
||
// 生成会话ID(简单实现)
|
||
sessionID := generateSessionID()
|
||
|
||
session := &Session{
|
||
SessionID: sessionID,
|
||
Title: title,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
|
||
return session, nil
|
||
}
|
||
|
||
// GetSessions 获取会话列表
|
||
func (uc *CustomerUseCase) GetSessions(ctx context.Context) ([]*Session, error) {
|
||
uc.log.WithContext(ctx).Info("Getting sessions list")
|
||
|
||
// Mock 数据
|
||
sessions := []*Session{
|
||
{
|
||
SessionID: "session_001",
|
||
Title: "产品咨询 - iPhone 15 Pro",
|
||
CreatedAt: time.Now().Add(-2 * time.Hour),
|
||
},
|
||
{
|
||
SessionID: "session_002",
|
||
Title: "订单问题 - 退换货流程",
|
||
CreatedAt: time.Now().Add(-1 * time.Hour),
|
||
},
|
||
{
|
||
SessionID: "session_003",
|
||
Title: "技术支持 - 软件安装问题",
|
||
CreatedAt: time.Now().Add(-30 * time.Minute),
|
||
},
|
||
}
|
||
|
||
return sessions, nil
|
||
}
|
||
|
||
// ProcessChat 处理聊天消息
|
||
func (uc *CustomerUseCase) ProcessChat(ctx context.Context, sessionID, message string) (*ChatMessage, error) {
|
||
uc.log.WithContext(ctx).Infof("Processing chat message for session: %s", sessionID)
|
||
|
||
// 简单的消息处理逻辑
|
||
chatMessage := &ChatMessage{
|
||
SessionID: sessionID,
|
||
Message: "收到您的消息:" + message + "。我正在为您处理,请稍候...",
|
||
Timestamp: time.Now(),
|
||
Type: "text",
|
||
}
|
||
|
||
return chatMessage, nil
|
||
}
|
||
|
||
// ListKnowledge 获取知识库文档列表
|
||
func (uc *CustomerUseCase) ListKnowledge(ctx context.Context) ([]*KnowledgeDocument, error) {
|
||
uc.log.WithContext(ctx).Info("Getting knowledge documents list")
|
||
|
||
// Mock 数据
|
||
documents := []*KnowledgeDocument{
|
||
{
|
||
ID: "doc_001",
|
||
FileName: "产品使用手册.pdf",
|
||
FileType: "pdf",
|
||
ContentPreview: "本手册详细介绍了产品的使用方法和注意事项...",
|
||
UploadTime: time.Now().Add(-24 * time.Hour),
|
||
Status: "active",
|
||
},
|
||
{
|
||
ID: "doc_002",
|
||
FileName: "常见问题解答.txt",
|
||
FileType: "txt",
|
||
ContentPreview: "Q: 如何重置密码?A: 请点击登录页面的忘记密码链接...",
|
||
UploadTime: time.Now().Add(-12 * time.Hour),
|
||
Status: "active",
|
||
},
|
||
{
|
||
ID: "doc_003",
|
||
FileName: "售后服务政策.docx",
|
||
FileType: "docx",
|
||
ContentPreview: "我们提供7天无理由退货,30天质量保证服务...",
|
||
UploadTime: time.Now().Add(-6 * time.Hour),
|
||
Status: "active",
|
||
},
|
||
}
|
||
|
||
return documents, nil
|
||
}
|
||
|
||
// QueryOrder 查询订单信息
|
||
func (uc *CustomerUseCase) QueryOrder(ctx context.Context, orderID string) (*OrderDetails, error) {
|
||
uc.log.WithContext(ctx).Infof("Querying order: %s", orderID)
|
||
|
||
// Mock 订单数据
|
||
orders := map[string]*OrderDetails{
|
||
"ORD001": {
|
||
OrderID: "ORD001",
|
||
Status: "已发货",
|
||
Product: "iPhone 15 Pro 256GB 深空黑色",
|
||
Amount: 8999.00,
|
||
CreateTime: time.Now().Add(-48 * time.Hour),
|
||
NeedAI: false,
|
||
},
|
||
"ORD002": {
|
||
OrderID: "ORD002",
|
||
Status: "处理中",
|
||
Product: "MacBook Pro 14英寸 M3芯片",
|
||
Amount: 14999.00,
|
||
CreateTime: time.Now().Add(-24 * time.Hour),
|
||
NeedAI: true,
|
||
},
|
||
"ORD003": {
|
||
OrderID: "ORD003",
|
||
Status: "已完成",
|
||
Product: "AirPods Pro 第二代",
|
||
Amount: 1899.00,
|
||
CreateTime: time.Now().Add(-72 * time.Hour),
|
||
NeedAI: false,
|
||
},
|
||
}
|
||
|
||
order, exists := orders[orderID]
|
||
if !exists {
|
||
return nil, ErrOrderNotFound
|
||
}
|
||
|
||
return order, nil
|
||
}
|
||
|
||
// generateSessionID 生成会话ID
|
||
func generateSessionID() string {
|
||
return "session_" + time.Now().Format("20060102150405")
|
||
}
|
||
|
||
// SearchKnowledge 搜索知识库
|
||
func (uc *CustomerUseCase) SearchKnowledge(ctx context.Context, query string, limit int) ([]vector.SearchResult, error) {
|
||
uc.log.WithContext(ctx).Infof("Searching knowledge for query: %s", query)
|
||
|
||
if limit <= 0 {
|
||
limit = 5
|
||
}
|
||
|
||
results, err := uc.knowledgeSearcher.SearchKnowledge(ctx, query, limit)
|
||
if err != nil {
|
||
uc.log.WithContext(ctx).Errorf("Failed to search knowledge: %v", err)
|
||
return nil, fmt.Errorf("知识库搜索失败: %w", err)
|
||
}
|
||
|
||
uc.log.WithContext(ctx).Infof("Found %d knowledge results", len(results))
|
||
return results, nil
|
||
}
|
||
|
||
// ProcessKnowledgeUpload 处理知识库文档上传
|
||
func (uc *CustomerUseCase) ProcessKnowledgeUpload(ctx context.Context, content, title, category string) error {
|
||
uc.log.WithContext(ctx).Infof("Processing knowledge upload: %s", title)
|
||
|
||
if strings.TrimSpace(content) == "" {
|
||
return fmt.Errorf("文档内容不能为空")
|
||
}
|
||
|
||
if strings.TrimSpace(title) == "" {
|
||
return fmt.Errorf("文档标题不能为空")
|
||
}
|
||
|
||
// 处理文档并存储到向量数据库
|
||
documents, err := uc.docProcessor.ProcessKnowledgeDocument(ctx, content, title, category)
|
||
if err != nil {
|
||
uc.log.WithContext(ctx).Errorf("Failed to process knowledge document: %v", err)
|
||
return fmt.Errorf("文档处理失败: %w", err)
|
||
}
|
||
|
||
uc.log.WithContext(ctx).Infof("Successfully processed knowledge document '%s' into %d chunks", title, len(documents))
|
||
return nil
|
||
}
|
||
|
||
// GetKnowledgeSummary 获取知识摘要
|
||
func (uc *CustomerUseCase) GetKnowledgeSummary(ctx context.Context, query string) (string, error) {
|
||
uc.log.WithContext(ctx).Infof("Getting knowledge summary for query: %s", query)
|
||
|
||
summary, err := uc.knowledgeSearcher.GetKnowledgeSummary(ctx, query)
|
||
if err != nil {
|
||
uc.log.WithContext(ctx).Errorf("Failed to get knowledge summary: %v", err)
|
||
return "", fmt.Errorf("获取知识摘要失败: %w", err)
|
||
}
|
||
|
||
return summary, nil
|
||
}
|
||
|
||
// AnalyzeUserIntent 已废弃:改为使用 AIService.AnalyzeIntent(qwen3:8b)
|
||
// 保留函数以避免外部调用崩溃,但将其标记为 deprecated,并直接返回 general_inquiry
|
||
// Deprecated: use AIService.AnalyzeIntent instead.
|
||
func (uc *CustomerUseCase) AnalyzeUserIntent(ctx context.Context, message string) (string, error) {
|
||
uc.log.WithContext(ctx).Warn("AnalyzeUserIntent (rule-based) is deprecated, use AIService.AnalyzeIntent (LLM) instead")
|
||
return "general_inquiry", nil
|
||
}
|
||
|
||
// ProcessIntelligentChat 处理智能聊天(结合知识库)
|
||
func (uc *CustomerUseCase) ProcessIntelligentChat(ctx context.Context, message, sessionID string) (string, error) {
|
||
startTime := time.Now()
|
||
|
||
// 记录监控指标
|
||
defer func() {
|
||
duration := time.Since(startTime)
|
||
uc.monitor.RecordRequest(ctx, "intelligent_chat", duration, true)
|
||
uc.monitor.RecordKnowledgeOperation(ctx, "query")
|
||
}()
|
||
|
||
// 获取或创建对话上下文
|
||
convCtx, err := uc.contextManager.GetContext(ctx, sessionID)
|
||
if err != nil {
|
||
// 创建新的上下文
|
||
convCtx, err = uc.contextManager.CreateContext(ctx, sessionID, "default_user")
|
||
if err != nil {
|
||
uc.log.Errorf("Failed to create context: %v", err)
|
||
}
|
||
}
|
||
|
||
// 添加用户消息到上下文
|
||
if convCtx != nil {
|
||
userMsg := contextpkg.Message{
|
||
Role: "user",
|
||
Content: message,
|
||
}
|
||
uc.contextManager.AddMessage(ctx, sessionID, userMsg)
|
||
}
|
||
|
||
// 搜索相关知识
|
||
searchResults, err := uc.knowledgeSearcher.SearchKnowledge(ctx, message, 3)
|
||
if err != nil {
|
||
uc.log.Errorf("Failed to search knowledge: %v", err)
|
||
uc.monitor.RecordVectorOperation(ctx, "search", false)
|
||
return "抱歉,暂时无法获取相关信息,请稍后再试。", nil
|
||
}
|
||
uc.monitor.RecordVectorOperation(ctx, "search", true)
|
||
|
||
// 构建上下文信息
|
||
var contextInfo string
|
||
if len(searchResults) > 0 {
|
||
contextInfo = "基于以下相关信息:\n"
|
||
for _, result := range searchResults {
|
||
contextInfo += fmt.Sprintf("- %s (相关度: %.2f)\n", result.Document.Content, result.Score)
|
||
}
|
||
contextInfo += "\n"
|
||
}
|
||
|
||
// 获取历史对话上下文
|
||
var historyContext string
|
||
if convCtx != nil {
|
||
recentMessages, err := uc.contextManager.GetRecentMessages(ctx, sessionID, 5)
|
||
if err == nil && len(recentMessages) > 1 {
|
||
historyContext = "基于我们之前的对话:\n"
|
||
for _, msg := range recentMessages[:len(recentMessages)-1] { // 排除当前消息
|
||
historyContext += fmt.Sprintf("%s: %s\n", msg.Role, msg.Content)
|
||
}
|
||
historyContext += "\n"
|
||
}
|
||
}
|
||
|
||
// 生成智能回复
|
||
response := ""
|
||
if historyContext != "" {
|
||
response += historyContext
|
||
}
|
||
if contextInfo != "" {
|
||
response += contextInfo
|
||
}
|
||
|
||
response += "根据您的问题,我建议您:"
|
||
|
||
// 基于意图和知识库内容生成回复
|
||
if strings.Contains(message, "订单") {
|
||
response += "\n1. 检查订单状态和物流信息\n2. 如有问题可申请退换货\n3. 联系客服获取详细帮助"
|
||
} else if strings.Contains(message, "技术") || strings.Contains(message, "问题") || strings.Contains(message, "错误") {
|
||
response += "\n1. 查看相关技术文档和FAQ\n2. 尝试重启相关服务或清除缓存\n3. 检查网络连接和系统配置\n4. 如问题持续,请联系技术支持"
|
||
} else if strings.Contains(message, "产品") || strings.Contains(message, "功能") {
|
||
response += "\n1. 查看产品说明和使用指南\n2. 观看相关教程视频\n3. 尝试产品演示功能\n4. 联系销售顾问了解更多"
|
||
} else {
|
||
response += "\n1. 查看帮助文档和常见问题\n2. 使用搜索功能查找相关信息\n3. 联系在线客服获取实时帮助"
|
||
}
|
||
|
||
// 添加AI回复到上下文
|
||
if convCtx != nil {
|
||
assistantMsg := contextpkg.Message{
|
||
Role: "assistant",
|
||
Content: response,
|
||
}
|
||
uc.contextManager.AddMessage(ctx, sessionID, assistantMsg)
|
||
}
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// handleOrderInquiry 处理订单咨询
|
||
func (uc *CustomerUseCase) handleOrderInquiry(ctx context.Context, message string) string {
|
||
// 尝试从消息中提取订单号
|
||
orderID := extractOrderID(message)
|
||
if orderID != "" {
|
||
order, err := uc.QueryOrder(ctx, orderID)
|
||
if err == nil {
|
||
return fmt.Sprintf("您的订单 %s 状态为:%s。产品:%s,金额:%.2f元。",
|
||
order.OrderID, order.Status, order.Product, order.Amount)
|
||
}
|
||
}
|
||
|
||
return "请提供您的订单号,我来帮您查询订单状态。订单号通常以 ORD 开头。"
|
||
}
|
||
|
||
// handleProductInquiry 处理产品咨询
|
||
func (uc *CustomerUseCase) handleProductInquiry(ctx context.Context, message string) string {
|
||
// 搜索相关知识
|
||
results, err := uc.SearchKnowledge(ctx, message, 3)
|
||
if err != nil || len(results) == 0 {
|
||
return "抱歉,我暂时没有找到相关的产品信息。请您详细描述您的问题,我会尽力为您解答。"
|
||
}
|
||
|
||
// 组合知识库内容
|
||
var knowledgeContent []string
|
||
for _, result := range results {
|
||
if result.Score > 0.6 { // 只使用高相关性的内容
|
||
knowledgeContent = append(knowledgeContent, result.Document.Content)
|
||
}
|
||
}
|
||
|
||
if len(knowledgeContent) == 0 {
|
||
return "抱歉,我没有找到与您问题高度相关的信息。请您提供更多详细信息。"
|
||
}
|
||
|
||
response := "根据我们的产品知识库,为您找到以下相关信息:\n\n"
|
||
response += strings.Join(knowledgeContent, "\n\n")
|
||
|
||
if len(response) > 500 {
|
||
response = response[:500] + "...\n\n如需了解更多详细信息,请告诉我您具体想了解哪个方面。"
|
||
}
|
||
|
||
return response
|
||
}
|
||
|
||
// handleTechnicalSupport 处理技术支持
|
||
func (uc *CustomerUseCase) handleTechnicalSupport(ctx context.Context, message string) string {
|
||
// 搜索技术相关知识
|
||
results, err := uc.SearchKnowledge(ctx, message+" 技术支持 解决方案", 3)
|
||
if err != nil || len(results) == 0 {
|
||
return "我理解您遇到了技术问题。请详细描述您的问题,包括:\n1. 具体的错误信息\n2. 操作步骤\n3. 使用的设备和系统\n我会为您提供针对性的解决方案。"
|
||
}
|
||
|
||
// 组合技术支持内容
|
||
var supportContent []string
|
||
for _, result := range results {
|
||
if result.Score > 0.5 {
|
||
supportContent = append(supportContent, result.Document.Content)
|
||
}
|
||
}
|
||
|
||
if len(supportContent) == 0 {
|
||
return "请提供更多关于您技术问题的详细信息,我会为您查找相应的解决方案。"
|
||
}
|
||
|
||
response := "根据您的问题,我为您找到以下解决方案:\n\n"
|
||
response += strings.Join(supportContent, "\n\n")
|
||
|
||
if len(response) > 600 {
|
||
response = response[:600] + "...\n\n如果以上方案无法解决您的问题,请联系我们的技术支持团队。"
|
||
}
|
||
|
||
return response
|
||
}
|
||
|
||
// handleGeneralInquiry 处理一般咨询
|
||
func (uc *CustomerUseCase) handleGeneralInquiry(ctx context.Context, message string) string {
|
||
// 搜索相关知识
|
||
results, err := uc.SearchKnowledge(ctx, message, 2)
|
||
if err != nil || len(results) == 0 {
|
||
return "您好!我是智能客服助手,很高兴为您服务。请告诉我您需要什么帮助,我可以协助您:\n• 查询订单状态\n• 产品使用指导\n• 技术问题解答\n• 售后服务咨询"
|
||
}
|
||
|
||
// 使用最相关的知识回答
|
||
if results[0].Score > 0.7 {
|
||
return "根据您的问题,我为您找到以下信息:\n\n" + results[0].Document.Content
|
||
}
|
||
|
||
return "我理解您的问题。请您提供更多详细信息,这样我能为您提供更准确的帮助。"
|
||
}
|
||
|
||
// extractOrderID 从消息中提取订单号
|
||
func extractOrderID(message string) string {
|
||
// 简单的订单号提取逻辑
|
||
words := strings.Fields(strings.ToUpper(message))
|
||
for _, word := range words {
|
||
if strings.HasPrefix(word, "ORD") && len(word) >= 6 {
|
||
return word
|
||
}
|
||
}
|
||
return ""
|
||
}
|