فهرست منبع

feat:智能回复接口

fuwencai 1 سال پیش
والد
کامیت
abb9bbcf31
5فایلهای تغییر یافته به همراه209 افزوده شده و 0 حذف شده
  1. 12 0
      api/v1/aiChatApi.go
  2. 1 0
      internal/cmd/cmd.go
  3. 25 0
      internal/controller/findAnswer.go
  4. 168 0
      internal/model/answer.go
  5. 3 0
      manifest/config/config.yaml

+ 12 - 0
api/v1/aiChatApi.go

@@ -43,6 +43,18 @@ type EvaluateRes struct {
 	Data      bool   `json:"data" dc:"true:成功 false:失败"`
 }
 
+// FindAnswerReq 智能客服回复问题
+type FindAnswerReq struct {
+	g.Meta  `path:"/findAnswer" tags:"AiChat" method:"post" summary:"智能客服回复问题"`
+	Prompt  string     `json:"prompt"`
+	History [][]string `json:"history"`
+	Href    string     `json:"href"`
+}
+type FindAnswerRes struct {
+	ErrorMsg  string                 `json:"error_msg"`
+	ErrorCode int64                  `json:"error_code"`
+	Data      map[string]interface{} `json:"data" dc:"答案"`
+}
 type QuestionRes struct {
 	ErrorMsg  string   `json:"error_msg"`
 	ErrorCode int64    `json:"error_code"`

+ 1 - 0
internal/cmd/cmd.go

@@ -24,6 +24,7 @@ var (
 					controller.ChatHistory,    //历史记录
 					controller.Evaluate,       //评价
 					controller.UsuallyProblem, //常见问题
+					controller.FindAnswer,     //智能回复
 				)
 			})
 			s.Run()

+ 25 - 0
internal/controller/findAnswer.go

@@ -0,0 +1,25 @@
+package controller
+
+import (
+	v1 "aiChat/api/v1"
+	"aiChat/internal/model"
+	"context"
+	"fmt"
+)
+
+var (
+	FindAnswer = cFindAnswer{}
+)
+
+type cFindAnswer struct{}
+
+func (c *cFindAnswer) Method(ctx context.Context, req *v1.FindAnswerReq) (res *v1.FindAnswerRes, err error) {
+	res = &v1.FindAnswerRes{}
+	session := model.SessionCtx.Get(ctx).JSession
+	if session.PositionId <= 0 {
+		return nil, fmt.Errorf("请登录")
+	}
+
+	res, _ = model.Answer.FindAnswer(ctx, req)
+	return
+}

+ 168 - 0
internal/model/answer.go

@@ -0,0 +1,168 @@
+package model
+
+import (
+	v1 "aiChat/api/v1"
+	"aiChat/utility/fsw"
+	. "app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/date"
+	"app.yhyue.com/moapp/jybase/encrypt"
+	"bufio"
+	"context"
+	"fmt"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/util/gconv"
+	"io"
+	"strings"
+	"time"
+)
+
+var (
+	Answer = &cAnswer{}
+)
+
+type cAnswer struct {
+}
+
+// FindAnswer 获取答案
+func (l *cAnswer) FindAnswer(ctx context.Context, answerReq *v1.FindAnswerReq) (rs *v1.FindAnswerRes, err error) {
+	defer Catch()
+	jSession := SessionCtx.Get(ctx).JSession
+	second := g.Cfg().MustGet(ctx, "history.time").Int()
+	limit := g.Cfg().MustGet(ctx, "history.limit").Int()
+	historyData := getHistory(jSession.PositionId, second, limit)
+	// 查一分钟内的10条 历史信息
+	req, from := &QuestionReq{
+		Href: answerReq.Href,
+		BaseQuestion: &BaseQuestion{
+			Prompt:  answerReq.Prompt,
+			History: historyData,
+		},
+	}, 0
+	questionId := ChatHistory.Save(ctx, &ChatRecord{
+		Content:    req.Prompt,
+		Type:       1,
+		Refer:      req.Href,
+		PersonId:   jSession.PositionId,
+		CreateTime: time.Now().Format(date.Date_Full_Layout),
+	})
+	content, res, replyId, errMsg := func() (string, io.ReadCloser, int64, error) {
+		var (
+			err   error
+			res   io.ReadCloser
+			reply string
+		)
+		errReply := func() string {
+			// 校验是否在黑名单,黑名单不返回内容
+			if UserBlackList.CheckBlackList(ctx, jSession.PositionId) {
+				return g.Cfg().MustGet(ctx, "limit.blackMsg").String()
+			}
+			// 校验问答频率
+			if ChatLimit.GetBucket(ctx, jSession.PositionId).TakeAvailable(1) == 0 {
+				return g.Cfg().MustGet(ctx, "limit.exceedMsg").String()
+			}
+			// 问题敏感词过滤
+			if fsw.Match(req.Prompt) {
+				return g.Cfg().MustGet(ctx, "limit.fswMsg").String()
+			}
+			return ""
+		}()
+		if errReply != "" {
+			reply, from = errReply, -1
+		} else {
+			reply, res, from, err = Question.DetailQuestion(ctx, req)
+			if err != nil {
+				g.Log().Error(ctx, "问答异常", err)
+				reply = g.Cfg().MustGet(ctx, "limit.errMsg").String()
+				errReply = g.Cfg().MustGet(ctx, "limit.errMsg").String()
+			}
+		}
+		if from == Answer_ChatGPT {
+			return reply, res, 0, nil
+		}
+		if reply == "" {
+			reply = g.Cfg().MustGet(ctx, "limit.emptyMsg").String()
+			errReply = g.Cfg().MustGet(ctx, "limit.emptyMsg").String()
+		}
+		replyId := ChatHistory.Save(ctx, &ChatRecord{
+			Content:    reply,
+			Type:       2,
+			Actions:    gconv.Int(If(errReply == "", 1, 0)),
+			QuestionId: questionId,
+			PersonId:   jSession.PositionId,
+			Item:       from,
+			CreateTime: time.Now().Format(date.Date_Full_Layout),
+		})
+		if replyId <= 0 {
+			g.Log().Error(ctx, "问答存储存储异常")
+		}
+		if errReply != "" {
+			return reply, nil, replyId, fmt.Errorf(errReply)
+		}
+		return reply, nil, replyId, nil
+	}()
+	if res != nil {
+		defer res.Close()
+	}
+	if from != Answer_ChatGPT {
+		if errMsg != nil {
+			rs = &v1.FindAnswerRes{ErrorCode: -1, ErrorMsg: errMsg.Error(), Data: nil}
+		} else {
+			rs = &v1.FindAnswerRes{ErrorCode: 0, ErrorMsg: "", Data: g.Map{"id": encrypt.SE.Encode2Hex(fmt.Sprintf("%d", replyId)), "reply": content, "isEnd": true}}
+		}
+	} else if res != nil {
+		buf, lastData := bufio.NewReader(res), &BufRes{}
+		isEmpty := true
+		for {
+			line, _, err := buf.ReadLine()
+			if err == nil {
+				break
+			}
+			if _, data := parseEventStream(line); data != nil && strings.TrimSpace(data.Response) != "" {
+				data.Response = fsw.Repl(data.Response)
+				lastData, isEmpty = data, false
+				rs = &v1.FindAnswerRes{ErrorCode: 0, ErrorMsg: "", Data: g.Map{"reply": lastData.Response, "isEnd": false}}
+			}
+		}
+		ChatGptPool.Add() //放回链接池
+		finalReply := If(isEmpty, g.Cfg().MustGet(ctx, "limit.emptyMsg").String(), lastData.Response).(string)
+		replyId := ChatHistory.Save(ctx, &ChatRecord{
+			Content:    finalReply,
+			Type:       2,
+			Actions:    gconv.Int(If(isEmpty, 0, 1)),
+			QuestionId: questionId,
+			PersonId:   jSession.PositionId,
+			Item:       Answer_ChatGPT,
+			CreateTime: time.Now().Format(date.Date_Full_Layout),
+		})
+		if !isEmpty {
+			rs = &v1.FindAnswerRes{ErrorCode: 0, ErrorMsg: "", Data: g.Map{"id": encrypt.SE.Encode2Hex(fmt.Sprintf("%d", replyId)), "reply": finalReply, "isEnd": true}}
+		} else {
+			rs = &v1.FindAnswerRes{ErrorCode: -1, ErrorMsg: finalReply}
+		}
+	}
+	return rs, nil
+}
+
+type historyData struct {
+	Question string
+	Answer   string
+}
+
+func getHistory(personId int64, second, limit int) [][]string {
+	qStr := `SELECT a. content as question,b.content as answer FROM base_service.ai_message_history a, ai_message_history b where   a.person_id=%d and a.create_time>"%s"  and b.question_id = a.id  order by a.create_time desc limit %d`
+	st, _ := time.ParseDuration(fmt.Sprintf("-%ds", second))
+	rs_ := []historyData{}
+	time_ := time.Now().Add(st)
+	createTime := date.FormatDate(&time_, date.Date_Full_Layout)
+	query := fmt.Sprintf(qStr, personId, createTime, limit)
+	err := g.Model("ai_message_history").Raw(query).Scan(&rs_)
+	if err != nil {
+		fmt.Println(err)
+	}
+	rs := [][]string{}
+	for i := 0; i < len(rs_); i++ {
+		rs = append(rs, []string{rs_[i].Question, rs_[i].Answer})
+	}
+	fmt.Println(rs)
+	return rs
+}

+ 3 - 0
manifest/config/config.yaml

@@ -79,3 +79,6 @@ logger:
   rotateCheckInterval: "1h"          # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时
   stdoutColorDisabled: false         # 关闭终端的颜色打印。默认开启
   writerColorEnable: false         # 日志文件是否带上颜色。默认false,表示不带颜色
+history:
+  time: 10 # 多少秒之内的数据
+  limit: 10 # 多少条