package facade import ( "encoding/json" "errors" "eta/eta_api/models/rag" localService "eta/eta_api/services/llm" "eta/eta_api/services/llm/facade/bus_response" "eta/eta_api/utils" "eta/eta_api/utils/llm" "eta/eta_api/utils/llm/eta_llm/eta_llm_http" "eta/eta_api/utils/ws" "fmt" "github.com/gorilla/websocket" "github.com/rdlucklib/rdluck_tools/uuid" "gorm.io/gorm" "os" "strings" "time" ) var ( llmService, _ = llm.GetInstance(llm.ETA_LLM_CLIENT) ) func generateSessionCode() (code string) { return fmt.Sprintf("%s%s", "llm_session_", uuid.NewUUID().Hex32()) } // AddSession 创建会话session func AddSession(userId int, conn *websocket.Conn) { sessionId := generateSessionCode() session := ws.NewSession(userId, sessionId, conn) ws.Manager().AddSession(session) } // LLMKnowledgeBaseSearchDocs 搜索知识库 func LLMKnowledgeBaseSearchDocs(search LLMKnowledgeSearch) (resp bus_response.SearchDocsEtaResponse, err error) { docs, err := llmService.SearchKbDocs(search.Query, search.KnowledgeBaseName) if err != nil { return } for _, doc := range docs.([]eta_llm_http.SearchDocsResponse) { resp.Content = resp.Content + doc.PageContent } resp.Docs = docs.([]eta_llm_http.SearchDocsResponse) return } // AIGCBaseOnPromote aigc 生成内容 func AIGCBaseOnPromote(aigc AIGC) (resp bus_response.AIGCEtaResponse, err error) { mapping, queryErr := rag.GetArticleKbMapping(aigc.ArticleId) if queryErr != nil && !errors.Is(queryErr, gorm.ErrRecordNotFound) { utils.FileLog.Error("获取文章知识库信息失败,err: %v", queryErr) err = fmt.Errorf("获取文章知识库信息失败,err: %v", queryErr) return } else { var kbId string var file *os.File if mapping.Id == 0 || mapping.KbId == "" { article, fileErr := rag.GetArticleById(aigc.ArticleId) if fileErr != nil { // 找不到就处理失败 utils.FileLog.Error("公众号文章不存在") err = fmt.Errorf("公众号文章不存在") return } if article.TextContent == "" { utils.FileLog.Error("暂不支持纯文本以外的内容生成") err = fmt.Errorf("暂不支持纯文本以外的内容生成") return } // 文章加入到知识库 path, fileErr := localService.CreateArticleFile(article) if fileErr != nil { utils.FileLog.Error("创建文章文件失败,err: %v", fileErr) err = fmt.Errorf("创建文章文件失败,err: %v", fileErr) return } defer func() { _ = os.Remove(path) }() file, err = os.Open(path) if err != nil { utils.FileLog.Error("打开文件失败,err:", err) return } uploadResp, httpErr := llmService.UploadFileToTemplate([]*os.File{file}, nil) if httpErr != nil { utils.FileLog.Error("上传文件失败,err:", err.Error()) err = fmt.Errorf("上传文件失败,err:%v", httpErr) return } data := uploadResp.(eta_llm_http.UploadDocsResponse) //保存映射关系到数据库 if data.Id == "" { utils.FileLog.Error("上传文件失败,向量库Id获取失败") err = fmt.Errorf("上传文件失败,向量库Id获取失败") return } err = rag.CreateArticleKbMapping(rag.ArticleKbMapping{ WechatArticleId: aigc.ArticleId, KbId: data.Id, CreatedTime: time.Now(), }) if err != nil { utils.FileLog.Warn("创建文章知识库映射关系失败,err:", err.Error()) } kbId = data.Id } else { kbId = mapping.KbId } //知识库对话 response, httpErr := llmService.FileChat(aigc.Promote, kbId, nil) if httpErr != nil { utils.FileLog.Error("内容生成失败,err:", err.Error()) err = fmt.Errorf("内容生成失败,err:%v", httpErr) return } gcResp, gcErr := dealFileChatResp(response) if gcErr != nil { utils.FileLog.Error("内容生成失败,err:%v", gcErr.Error()) err = fmt.Errorf("内容生成失败,err:%v", gcErr) return } if gcResp.Code == 200 { resp = gcResp.Data return } if gcResp.Code == 404 { response, httpErr = llmService.FileChat(aigc.Promote, kbId, nil) if httpErr != nil { utils.FileLog.Error("内容生成失败,err:%v", httpErr.Error()) err = fmt.Errorf("内容生成失败,err:%v", httpErr) return } gcResp, gcErr = dealFileChatResp(response) if gcErr != nil { utils.FileLog.Error("内容生成失败,err:%v", gcErr.Error()) err = fmt.Errorf("内容生成失败,err:%v", gcErr) return } if gcResp.Code == 200 { resp = gcResp.Data return } utils.FileLog.Error("内容生成失败,err:%v", gcResp.Code, gcResp.Msg) err = fmt.Errorf("内容生成失败,code:%v,err:%v", gcResp.Code, gcResp.Msg) return } else { utils.FileLog.Error("内容生成失败,code:%v,msg:%v", gcResp.Code, gcResp.Msg) err = fmt.Errorf("内容生成失败,err:%v", gcResp.Msg) return } } } type LLMKnowledgeSearch struct { Query string `json:"Query"` KnowledgeBaseName string `json:"KnowledgeBaseName"` } type AIGC struct { Promote string ArticleId int } func dealFileChatResp(response eta_llm_http.BaseResponse) (httpResponse bus_response.FileChatBaseResponse, err error) { if !response.Success { utils.FileLog.Error("内容生成失败,code:%v,msg:%v", response.Ret, response.Msg) err = fmt.Errorf("内容生成失败,code:%v,msg:%v", response.Ret, response.Msg) return } else { var dataStr string // 按行分割输入 lines := strings.Split(string(response.Data), "\n") // 遍历每一行,提取以 "data:" 开头的内容 for _, line := range lines { if !strings.HasPrefix(line, ": ping") && strings.TrimSpace(line) != "" { // 去掉 "data:" 前缀 dataStr += line } } // 去除 "data: " 前缀 if strings.HasPrefix(dataStr, "data: ") { dataStr = strings.TrimPrefix(dataStr, "data: ") var streamResponse bus_response.AIGCEtaResponse parseErr := json.Unmarshal([]byte(dataStr), &streamResponse) if parseErr != nil { utils.FileLog.Error("内容生成失败,code:%v,msg:%v", parseErr) err = fmt.Errorf("内容生成失败,err:%v", parseErr) return } httpResponse.Data = streamResponse httpResponse.Msg = "返回成功" httpResponse.Code = 200 return } else { parseErr := json.Unmarshal([]byte(dataStr), &httpResponse) if parseErr != nil { utils.FileLog.Error("内容生成失败,code:%v,msg:%v", parseErr) err = fmt.Errorf("内容生成失败,err:%v", parseErr) return } return } } }