feat: 百科rag
This commit is contained in:
@@ -3,6 +3,7 @@ package plant
|
|||||||
import (
|
import (
|
||||||
"sundynix-go/global"
|
"sundynix-go/global"
|
||||||
"sundynix-go/model/commom/response"
|
"sundynix-go/model/commom/response"
|
||||||
|
"sundynix-go/utils/auth"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -32,7 +33,9 @@ func (a *AiChatApi) ChatStreamPlant(c *gin.Context) {
|
|||||||
header.Set("Transfer-Encoding", "chunked")
|
header.Set("Transfer-Encoding", "chunked")
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
var fullAnswer string
|
||||||
err := aiRagService.PlantChatStreamRAG(c.Request.Context(), query, func(chunk string) error {
|
err := aiRagService.PlantChatStreamRAG(c.Request.Context(), query, func(chunk string) error {
|
||||||
|
fullAnswer += chunk
|
||||||
_, writeErr := w.WriteString("data: " + chunk + "\n\n")
|
_, writeErr := w.WriteString("data: " + chunk + "\n\n")
|
||||||
w.Flush()
|
w.Flush()
|
||||||
return writeErr
|
return writeErr
|
||||||
@@ -47,6 +50,16 @@ func (a *AiChatApi) ChatStreamPlant(c *gin.Context) {
|
|||||||
_, _ = w.WriteString("data: [DONE]\n\n")
|
_, _ = w.WriteString("data: [DONE]\n\n")
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 异步保存问答历史
|
||||||
|
if fullAnswer != "" {
|
||||||
|
userId := auth.GetUserId(c)
|
||||||
|
go func() {
|
||||||
|
if saveErr := chatHistoryService.SaveHistory(userId, query, fullAnswer); saveErr != nil {
|
||||||
|
global.Logger.Error("Save chat history failed", zap.Error(saveErr))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncWikiToQdrant 手动触发全量植物百科同步到 Qdrant
|
// SyncWikiToQdrant 手动触发全量植物百科同步到 Qdrant
|
||||||
@@ -63,3 +76,74 @@ func (a *AiChatApi) SyncWikiToQdrant(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
response.OkWithMsg("同步成功", c)
|
response.OkWithMsg("同步成功", c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChatHistory 获取AI问答历史列表
|
||||||
|
// @Tags Plant-AiChat
|
||||||
|
// @Param current query int false "页码"
|
||||||
|
// @Param pageSize query int false "每页条数"
|
||||||
|
// @Router /plant/chat/history [get]
|
||||||
|
func (a *AiChatApi) GetChatHistory(c *gin.Context) {
|
||||||
|
userId := auth.GetUserId(c)
|
||||||
|
current := 1
|
||||||
|
pageSize := 20
|
||||||
|
if v, ok := c.GetQuery("current"); ok {
|
||||||
|
if n := parseInt(v); n > 0 {
|
||||||
|
current = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := c.GetQuery("pageSize"); ok {
|
||||||
|
if n := parseInt(v); n > 0 {
|
||||||
|
pageSize = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list, total, err := chatHistoryService.GetHistoryList(userId, current, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMsg("查询失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData(map[string]interface{}{
|
||||||
|
"list": list,
|
||||||
|
"total": total,
|
||||||
|
}, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteChatHistory 删除单条历史
|
||||||
|
// @Tags Plant-AiChat
|
||||||
|
// @Router /plant/chat/history/delete [post]
|
||||||
|
func (a *AiChatApi) DeleteChatHistory(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil || req.Id == "" {
|
||||||
|
response.FailWithMsg("参数错误", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userId := auth.GetUserId(c)
|
||||||
|
if err := chatHistoryService.DeleteHistory(userId, req.Id); err != nil {
|
||||||
|
response.FailWithMsg("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMsg("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearChatHistory 清空所有历史
|
||||||
|
// @Tags Plant-AiChat
|
||||||
|
// @Router /plant/chat/history/clear [post]
|
||||||
|
func (a *AiChatApi) ClearChatHistory(c *gin.Context) {
|
||||||
|
userId := auth.GetUserId(c)
|
||||||
|
if err := chatHistoryService.ClearHistory(userId); err != nil {
|
||||||
|
response.FailWithMsg("清空失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMsg("已清空", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt(s string) int {
|
||||||
|
n := 0
|
||||||
|
for _, c := range s {
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
n = n*10 + int(c-'0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,4 +30,5 @@ var (
|
|||||||
callbackService = service.GroupApp.PlantServiceGroup.CallbackService
|
callbackService = service.GroupApp.PlantServiceGroup.CallbackService
|
||||||
exchangeService = service.GroupApp.PlantServiceGroup.ExchangeService
|
exchangeService = service.GroupApp.PlantServiceGroup.ExchangeService
|
||||||
aiRagService = service.GroupApp.PlantServiceGroup.AiRagService
|
aiRagService = service.GroupApp.PlantServiceGroup.AiRagService
|
||||||
|
chatHistoryService = service.GroupApp.PlantServiceGroup.AiChatHistoryService
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ func MigrateTable() {
|
|||||||
|
|
||||||
plant.ExchangeItem{}, //兑换商品
|
plant.ExchangeItem{}, //兑换商品
|
||||||
plant.ExchangeOrder{}, //兑换订单
|
plant.ExchangeOrder{}, //兑换订单
|
||||||
|
|
||||||
|
plant.AiChatHistory{}, //AI问答历史
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.Logger.Error("Migrate table failed,err:", zap.Error(err))
|
global.Logger.Error("Migrate table failed,err:", zap.Error(err))
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package plant
|
||||||
|
|
||||||
|
import "sundynix-go/global"
|
||||||
|
|
||||||
|
// AiChatHistory AI问答历史记录
|
||||||
|
type AiChatHistory struct {
|
||||||
|
global.BaseModel
|
||||||
|
UserId string `json:"userId" gorm:"size:50;index;comment:用户ID"`
|
||||||
|
Question string `json:"question" gorm:"type:text;comment:用户提问"`
|
||||||
|
Answer string `json:"answer" gorm:"type:longtext;comment:AI回答"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AiChatHistory) TableName() string {
|
||||||
|
return "sundynix_ai_chat_history"
|
||||||
|
}
|
||||||
@@ -8,8 +8,11 @@ func (s *AiChatRouter) InitAiChatRouter(Router *gin.RouterGroup) (R gin.IRoutes)
|
|||||||
aiChatRouter := Router.Group("plant/chat")
|
aiChatRouter := Router.Group("plant/chat")
|
||||||
aiChatApi := aiChatApi
|
aiChatApi := aiChatApi
|
||||||
{
|
{
|
||||||
aiChatRouter.GET("stream", aiChatApi.ChatStreamPlant) // SSE 对话流
|
aiChatRouter.GET("stream", aiChatApi.ChatStreamPlant) // SSE 对话流
|
||||||
aiChatRouter.POST("sync", aiChatApi.SyncWikiToQdrant) // 后台知识库同步
|
aiChatRouter.POST("sync", aiChatApi.SyncWikiToQdrant) // 后台知识库同步
|
||||||
|
aiChatRouter.GET("history", aiChatApi.GetChatHistory) // 问答历史列表
|
||||||
|
aiChatRouter.POST("history/delete", aiChatApi.DeleteChatHistory) // 删除单条
|
||||||
|
aiChatRouter.POST("history/clear", aiChatApi.ClearChatHistory) // 清空历史
|
||||||
}
|
}
|
||||||
return aiChatRouter
|
return aiChatRouter
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package plant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sundynix-go/global"
|
||||||
|
plantModel "sundynix-go/model/plant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AiChatHistoryService struct{}
|
||||||
|
|
||||||
|
// SaveHistory 保存一条问答记录
|
||||||
|
func (s *AiChatHistoryService) SaveHistory(userId, question, answer string) error {
|
||||||
|
record := plantModel.AiChatHistory{
|
||||||
|
UserId: userId,
|
||||||
|
Question: question,
|
||||||
|
Answer: answer,
|
||||||
|
}
|
||||||
|
return global.DB.Create(&record).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHistoryList 获取用户问答历史(分页,最新在前)
|
||||||
|
func (s *AiChatHistoryService) GetHistoryList(userId string, current, pageSize int) ([]plantModel.AiChatHistory, int64, error) {
|
||||||
|
var list []plantModel.AiChatHistory
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
db := global.DB.Model(&plantModel.AiChatHistory{}).Where("user_id = ?", userId)
|
||||||
|
db.Count(&total)
|
||||||
|
|
||||||
|
if current <= 0 {
|
||||||
|
current = 1
|
||||||
|
}
|
||||||
|
if pageSize <= 0 || pageSize > 50 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
offset := (current - 1) * pageSize
|
||||||
|
|
||||||
|
err := db.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&list).Error
|
||||||
|
return list, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHistory 删除单条记录
|
||||||
|
func (s *AiChatHistoryService) DeleteHistory(userId, id string) error {
|
||||||
|
return global.DB.Where("id = ? AND user_id = ?", id, userId).Delete(&plantModel.AiChatHistory{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearHistory 清空用户所有历史
|
||||||
|
func (s *AiChatHistoryService) ClearHistory(userId string) error {
|
||||||
|
return global.DB.Where("user_id = ?", userId).Delete(&plantModel.AiChatHistory{}).Error
|
||||||
|
}
|
||||||
@@ -357,7 +357,7 @@ func (s *AiRagService) PlantChatStreamRAG(ctx context.Context, userQuery string,
|
|||||||
global.Logger.Warn("Qdrant connect failed, skipping RAG", zap.Error(connErr))
|
global.Logger.Warn("Qdrant connect failed, skipping RAG", zap.Error(connErr))
|
||||||
}
|
}
|
||||||
|
|
||||||
systemPrompt := "你是一个专业的植物百科助手,请基于以下知识库信息回答用户问题。如果知识库无相关信息,结合你的通用知识作答。\n"
|
systemPrompt := "你是一个专业的植物百科助手。回答规则:1.基于知识库信息回答,不够则结合通用知识。2.严禁使用Markdown语法(不要用#、*、-、```等符号)。3.用纯文本回答,段落之间空一行。4.分类用「一、二、三」或emoji开头,重点内容直接加书名号《》或【】强调。5.回答简洁专业、条理清晰。\n"
|
||||||
if contextText != "" {
|
if contextText != "" {
|
||||||
systemPrompt += "--- 知识库 ---\n" + contextText + "\n--------------\n"
|
systemPrompt += "--- 知识库 ---\n" + contextText + "\n--------------\n"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ type ServiceGroup struct {
|
|||||||
CallbackService
|
CallbackService
|
||||||
ExchangeService
|
ExchangeService
|
||||||
AiRagService
|
AiRagService
|
||||||
|
AiChatHistoryService
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user