ai-courseware/eino-project/internal/biz/customer.go

512 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.AnalyzeIntentqwen3: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 ""
}