package service import ( "context" "encoding/json" "fmt" "net/http" "time" "github.com/gorilla/websocket" pb "eino-project/api/customer/v1" "eino-project/internal/biz" "eino-project/internal/domain/agent" "eino-project/internal/domain/llm" "eino-project/internal/domain/monitor" "eino-project/internal/domain/session" wf "eino-project/internal/domain/workflow" "github.com/cloudwego/eino/adk" "github.com/go-kratos/kratos/v2/log" ) // CustomerService 智能客服服务实现 type CustomerService struct { pb.UnimplementedCustomerServiceServer customerUseCase *biz.CustomerUseCase sessionManager session.SessionManager monitor monitor.Monitor log *log.Helper chatWorkflow wf.ChatWorkflow // productAgent 商品查询Agent(Eino ADK ChatModelAgent实现,绑定工具并自动选择调用) productAgent adk.Agent } // NewCustomerService 创建智能客服服务 func NewCustomerService( customerUseCase *biz.CustomerUseCase, sessionManager session.SessionManager, monitor monitor.Monitor, logger log.Logger, models llm.LLM, ) *CustomerService { return &CustomerService{ customerUseCase: customerUseCase, sessionManager: sessionManager, monitor: monitor, log: log.NewHelper(logger), // 构建商品查询 ChatModelAgent,绑定工具并让模型自动选择调用 productAgent: agent.NewProductChatAgent(context.Background(), models), } } func (s *CustomerService) SetChatWorkflow(w wf.ChatWorkflow) { s.chatWorkflow = w } // SystemStatus 系统状态检查 func (s *CustomerService) SystemStatus(ctx context.Context, req *pb.SystemStatusRequest) (*pb.SystemStatusResponse, error) { s.log.WithContext(ctx).Info("SystemStatus called") status, err := s.customerUseCase.GetSystemStatus(ctx) if err != nil { return nil, err } // 转换业务层数据到 protobuf 格式 services := make(map[string]*pb.ServiceStatus) for key, service := range status.Services { services[key] = &pb.ServiceStatus{ Name: service.Name, Status: service.Status, Message: service.Message, } } return &pb.SystemStatusResponse{ Status: status.Status, Services: services, Version: status.Version, }, nil } // Chat 处理聊天消息 func (s *CustomerService) Chat(ctx context.Context, req *pb.ChatRequest) (*pb.ChatResponse, error) { startTime := time.Now() s.log.WithContext(ctx).Infof("Chat called with message: %s", req.Message) // 记录监控指标 var success bool if s.monitor != nil { defer func() { duration := time.Since(startTime) s.monitor.RecordRequest(ctx, "chat", duration, success) }() } // 添加用户消息到会话 _, err := s.sessionManager.AddMessage(ctx, req.SessionId, "user", req.Message) if err != nil { s.log.Errorf("Failed to add user message: %v", err) // 如果会话不存在,创建新会话 if req.SessionId == "" { session, createErr := s.sessionManager.CreateSession(ctx, "default_user", "新对话") if createErr != nil { return nil, fmt.Errorf("failed to create session: %w", createErr) } req.SessionId = session.ID } // 重试添加消息 _, err = s.sessionManager.AddMessage(ctx, req.SessionId, "user", req.Message) if err != nil { return nil, fmt.Errorf("failed to add user message: %w", err) } } // 可在工作流内部进行意图识别 var aiResponse string // 使用 ChatWorkflow 路由处理(意图判断 + 调用相应 Agent/Workflow) if s.chatWorkflow != nil { aiResponse, err = s.chatWorkflow.Chat(ctx, req.Message, req.SessionId) } else { aiResponse = "" err = fmt.Errorf("chat workflow not configured") } if err != nil { s.log.Errorf("AI service error: %v", err) // 降级到业务层处理 chatMessage, bizErr := s.customerUseCase.ProcessChat(ctx, req.SessionId, req.Message) if bizErr != nil { return nil, fmt.Errorf("both AI and business logic failed: AI error: %v, Biz error: %v", err, bizErr) } aiResponse = chatMessage.Message } // 添加AI回复到会话 _, err = s.sessionManager.AddMessage(ctx, req.SessionId, "assistant", aiResponse) if err != nil { s.log.Errorf("Failed to add AI message: %v", err) } success = true return &pb.ChatResponse{ SessionId: req.SessionId, Timestamp: time.Now().Format("2006-01-02 15:04:05"), Type: "response", Payload: &pb.ChatPayload{ DataType: "text", Content: &pb.ChatContent{ Chunk: aiResponse, FullMessage: aiResponse, IsFinal: true, }, }, }, nil } // GetSessions 获取会话列表 func (s *CustomerService) GetSessions(ctx context.Context, req *pb.GetSessionsRequest) (*pb.GetSessionsResponse, error) { s.log.WithContext(ctx).Info("GetSessions called") // 使用会话管理器获取会话列表 sessions, err := s.sessionManager.ListSessions(ctx, "default_user") // 暂时使用默认用户ID if err != nil { return nil, fmt.Errorf("failed to list sessions: %w", err) } // 转换会话管理器数据到 protobuf 格式 pbSessions := make([]*pb.Session, 0, len(sessions)) for _, session := range sessions { pbSessions = append(pbSessions, &pb.Session{ SessionId: session.ID, Title: session.Title, CreatedAt: session.CreatedAt.Format("2006-01-02 15:04:05"), }) } return &pb.GetSessionsResponse{ Sessions: pbSessions, }, nil } // CreateSession 创建新会话 func (s *CustomerService) CreateSession(ctx context.Context, req *pb.CreateSessionRequest) (*pb.CreateSessionResponse, error) { s.log.WithContext(ctx).Infof("CreateSession called with title: %s", req.Title) // 使用会话管理器创建新会话 session, err := s.sessionManager.CreateSession(ctx, "default_user", req.Title) // 暂时使用默认用户ID if err != nil { return nil, fmt.Errorf("failed to create session: %w", err) } return &pb.CreateSessionResponse{ SessionId: session.ID, Title: session.Title, CreatedAt: session.CreatedAt.Format("2006-01-02 15:04:05"), }, nil } // UploadKnowledge 上传知识库文档 func (s *CustomerService) UploadKnowledge(ctx context.Context, req *pb.UploadKnowledgeRequest) (*pb.UploadKnowledgeResponse, error) { s.log.WithContext(ctx).Infof("UploadKnowledge called with filename: %s", req.FileName) // 调用业务层处理文档上传和向量化 err := s.customerUseCase.ProcessKnowledgeUpload(ctx, string(req.Content), req.FileName, req.FileType) if err != nil { s.log.Errorf("Failed to process knowledge upload: %v", err) return &pb.UploadKnowledgeResponse{ Success: false, FileId: "", }, err } // 生成文件ID fileID := "file_" + req.FileName return &pb.UploadKnowledgeResponse{ Success: true, FileId: fileID, }, nil } // ListKnowledge 获取知识库文档列表 func (s *CustomerService) ListKnowledge(ctx context.Context, req *pb.ListKnowledgeRequest) (*pb.ListKnowledgeResponse, error) { s.log.WithContext(ctx).Info("ListKnowledge called") documents, err := s.customerUseCase.ListKnowledge(ctx) if err != nil { return nil, err } // 转换业务层数据到 protobuf 格式 pbDocuments := make([]*pb.KnowledgeDocument, 0, len(documents)) for _, doc := range documents { pbDocuments = append(pbDocuments, &pb.KnowledgeDocument{ Id: doc.ID, FileName: doc.FileName, FileType: doc.FileType, ContentPreview: doc.ContentPreview, UploadTime: doc.UploadTime.Format("2006-01-02 15:04:05"), Status: doc.Status, }) } return &pb.ListKnowledgeResponse{ Documents: pbDocuments, }, nil } // DeleteKnowledge 删除知识库文档 func (s *CustomerService) DeleteKnowledge(ctx context.Context, req *pb.DeleteKnowledgeRequest) (*pb.DeleteKnowledgeResponse, error) { s.log.WithContext(ctx).Infof("DeleteKnowledge called with id: %s", req.Id) // 简单实现,实际应该调用业务层处理文档删除 return &pb.DeleteKnowledgeResponse{ Success: true, }, nil } // QueryOrder 查询订单信息 func (s *CustomerService) QueryOrder(ctx context.Context, req *pb.QueryOrderRequest) (*pb.QueryOrderResponse, error) { s.log.WithContext(ctx).Infof("QueryOrder called with orderID: %s", req.OrderId) orderDetails, err := s.customerUseCase.QueryOrder(ctx, req.OrderId) if err != nil { return nil, err } return &pb.QueryOrderResponse{ OrderId: orderDetails.OrderID, Status: orderDetails.Status, NeedAi: orderDetails.NeedAI, Details: &pb.OrderDetails{ Product: orderDetails.Product, Amount: orderDetails.Amount, CreateTime: orderDetails.CreateTime.Format("2006-01-02 15:04:05"), }, }, nil } // StreamChat 流式聊天处理 (gRPC流式接口) func (s *CustomerService) StreamChat(req *pb.StreamChatRequest, stream pb.CustomerService_StreamChatServer) error { s.log.Infof("StreamChat called with message: %s, session: %s", req.Message, req.SessionId) ctx := stream.Context() // 添加用户消息到会话 _, err := s.sessionManager.AddMessage(ctx, req.SessionId, "user", req.Message) if err != nil { s.log.Errorf("Failed to add user message: %v", err) // 如果会话不存在,创建新会话 if req.SessionId == "" { session, createErr := s.sessionManager.CreateSession(ctx, "default_user", "新对话") if createErr != nil { return fmt.Errorf("failed to create session: %w", createErr) } req.SessionId = session.ID } // 重试添加消息 _, err = s.sessionManager.AddMessage(ctx, req.SessionId, "user", req.Message) if err != nil { return fmt.Errorf("failed to add user message: %w", err) } } // 使用工作流进行流式对话 var fullResponse string var responseChan <-chan string if s.chatWorkflow != nil { responseChan, err = s.chatWorkflow.Stream(ctx, req.Message, req.SessionId) } else { err = fmt.Errorf("chat workflow not configured") } if err != nil { s.log.Errorf("AI stream service error: %v", err) return s.fallbackStreamResponse(req, stream) } for chunk := range responseChan { fullResponse += chunk response := &pb.StreamChatResponse{ SessionId: req.SessionId, Timestamp: time.Now().Format("2006-01-02 15:04:05"), Type: "chunk", Content: chunk, IsFinal: false, } if err := stream.Send(response); err != nil { s.log.Errorf("Failed to send stream response: %v", err) return err } } // 发送最终响应 response := &pb.StreamChatResponse{ SessionId: req.SessionId, Timestamp: time.Now().Format("2006-01-02 15:04:05"), Type: "final", Content: "", IsFinal: true, } if err := stream.Send(response); err != nil { s.log.Errorf("Failed to send final response: %v", err) return err } // 添加完整的AI回复到会话 if fullResponse != "" { _, err = s.sessionManager.AddMessage(ctx, req.SessionId, "assistant", fullResponse) if err != nil { s.log.Errorf("Failed to add AI message: %v", err) } } return nil } // fallbackStreamResponse 降级流式响应 func (s *CustomerService) fallbackStreamResponse(req *pb.StreamChatRequest, stream pb.CustomerService_StreamChatServer) error { responses := []string{ "正在思考您的问题", "根据您的描述", "我建议您可以尝试以下解决方案", "如果问题仍然存在,请联系技术支持", } for i, content := range responses { select { case <-stream.Context().Done(): return stream.Context().Err() default: } response := &pb.StreamChatResponse{ SessionId: req.SessionId, Timestamp: time.Now().Format("2006-01-02 15:04:05"), Type: "chunk", Content: content, IsFinal: i == len(responses)-1, } if err := stream.Send(response); err != nil { s.log.Errorf("Failed to send stream response: %v", err) return err } // 模拟处理延迟 time.Sleep(500 * time.Millisecond) } return nil } // HandleStreamChat HTTP SSE流式聊天处理器 func (s *CustomerService) HandleStreamChat(w http.ResponseWriter, r *http.Request) { s.log.Info("HandleStreamChat called") // 设置SSE响应头 w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "Cache-Control") // 解析请求参数 if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Message string `json:"message"` SessionID string `json:"session_id"` Model string `json:"model,omitempty"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } // 获取flusher用于实时推送 flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "Streaming unsupported", http.StatusInternalServerError) return } ctx := r.Context() // 添加用户消息到会话 _, err := s.sessionManager.AddMessage(ctx, req.SessionID, "user", req.Message) if err != nil { s.log.Errorf("Failed to add user message: %v", err) // 如果会话不存在,创建新会话 if req.SessionID == "" { session, createErr := s.sessionManager.CreateSession(ctx, "default_user", "新对话") if createErr != nil { http.Error(w, "Failed to create session", http.StatusInternalServerError) return } req.SessionID = session.ID } // 重试添加消息 _, err = s.sessionManager.AddMessage(ctx, req.SessionID, "user", req.Message) if err != nil { http.Error(w, "Failed to add user message", http.StatusInternalServerError) return } } // 使用工作流进行流式对话 var fullResponse string responseChan, err := s.chatWorkflow.Stream(ctx, req.Message, req.SessionID) if err != nil { s.log.Errorf("AI stream service error: %v", err) // 降级到模拟响应 s.fallbackSSEResponse(w, flusher, req.SessionID, r.Context()) return } for chunk := range responseChan { fullResponse += chunk // 构造SSE响应数据 response := map[string]interface{}{ "session_id": req.SessionID, "timestamp": time.Now().Format("2006-01-02 15:04:05"), "type": "chunk", "content": chunk, "is_final": false, } jsonData, err := json.Marshal(response) if err != nil { s.log.Errorf("Failed to marshal response: %v", err) continue } // 发送SSE数据 fmt.Fprintf(w, "data: %s\n\n", jsonData) flusher.Flush() } // 发送结束标记 endResponse := map[string]interface{}{ "type": "end", "session_id": req.SessionID, "timestamp": time.Now().Format("2006-01-02 15:04:05"), } jsonData, err := json.Marshal(endResponse) if err != nil { s.log.Errorf("Failed to marshal end response: %v", err) } else { fmt.Fprintf(w, "data: %s\n\n", jsonData) flusher.Flush() } // 添加完整的AI回复到会话 if fullResponse != "" { _, err = s.sessionManager.AddMessage(ctx, req.SessionID, "assistant", fullResponse) if err != nil { s.log.Errorf("Failed to add AI message: %v", err) } } } // fallbackSSEResponse 降级SSE响应 func (s *CustomerService) fallbackSSEResponse(w http.ResponseWriter, flusher http.Flusher, sessionID string, ctx context.Context) { responses := []string{ "正在分析您的问题...", "根据您的描述,我理解您遇到的情况。", "让我为您提供一些建议和解决方案:", "1. 首先,请检查相关设置是否正确", "2. 如果问题持续,建议重启相关服务", "3. 必要时可以联系技术支持获得进一步帮助", "希望这些建议对您有帮助!", } for i, content := range responses { select { case <-ctx.Done(): return default: } // 构造SSE响应数据 response := map[string]interface{}{ "session_id": sessionID, "timestamp": time.Now().Format("2006-01-02 15:04:05"), "type": "chunk", "content": content, "is_final": i == len(responses)-1, } jsonData, err := json.Marshal(response) if err != nil { s.log.Errorf("Failed to marshal response: %v", err) continue } // 发送SSE数据 fmt.Fprintf(w, "data: %s\n\n", jsonData) flusher.Flush() // 模拟处理延迟 time.Sleep(800 * time.Millisecond) } // 发送结束标记 fmt.Fprintf(w, "data: {\"type\":\"end\",\"session_id\":\"%s\"}\n\n", sessionID) flusher.Flush() } // HandleWebSocketChat WebSocket 聊天处理器 func (s *CustomerService) HandleWebSocketChat(conn *websocket.Conn) { defer conn.Close() ctx := context.Background() for { mt, msg, err := conn.ReadMessage() if err != nil { return } if mt != websocket.TextMessage { continue } var req struct { Message string `json:"message"` SessionID string `json:"session_id"` } if json.Unmarshal(msg, &req) != nil { continue } ch, err := s.chatWorkflow.Stream(ctx, req.Message, req.SessionID) if err != nil { _ = conn.WriteMessage(websocket.TextMessage, []byte(`{"type":"error"}`)) continue } for chunk := range ch { resp := map[string]interface{}{"type": "chunk", "content": chunk, "session_id": req.SessionID} b, _ := json.Marshal(resp) _ = conn.WriteMessage(websocket.TextMessage, b) } _ = conn.WriteMessage(websocket.TextMessage, []byte(`{"type":"end"}`)) } } // HandleOrderDiagnosis 订单诊断工作流入口 func (s *CustomerService) HandleOrderDiagnosis(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { OrderID string `json:"order_id"` Message string `json:"message"` SessionID string `json:"session_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } ctx := r.Context() // 简化:直接调用工作流的 Chat(可替换为订单专用流水线) resp, err := s.chatWorkflow.Chat(ctx, fmt.Sprintf("订单诊断: %s\n%s", req.OrderID, req.Message), req.SessionID) if err != nil { http.Error(w, "Workflow error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"order_id": req.OrderID, "result": resp}) }