wechat_platform.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. package llm
  2. import (
  3. "eta/eta_api/cache"
  4. "eta/eta_api/models"
  5. "eta/eta_api/models/rag"
  6. "eta/eta_api/utils"
  7. "eta/eta_api/utils/llm/eta_llm/eta_llm_http"
  8. "fmt"
  9. "html"
  10. "os"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "time"
  15. )
  16. type WechatArticleOp struct {
  17. Source string
  18. WechatPlatformId int
  19. }
  20. // AddWechatPlatform
  21. // @Description: 添加新的公众号
  22. // @param item
  23. func AddWechatPlatform(item *rag.WechatPlatform) {
  24. var err error
  25. defer func() {
  26. if err != nil {
  27. utils.FileLog.Error("公众号入库后查找最新记录失败,err:%v", err)
  28. }
  29. }()
  30. if item.FakeId != `` {
  31. return
  32. }
  33. if item.ArticleLink == `` {
  34. return
  35. }
  36. articleLink := item.ArticleLink
  37. articleDetail, err := SearchByWechatArticle(item.ArticleLink)
  38. if err != nil {
  39. return
  40. }
  41. if articleDetail.Appuin == `` {
  42. err = fmt.Errorf("文章内未匹配到公众号唯一标识")
  43. return
  44. }
  45. wechatPlatform := new(rag.WechatPlatform)
  46. // 查找是否存在这个公众号id的
  47. wechatPlatformInfo, tmpErr := wechatPlatform.GetByFakeID(articleDetail.Appuin)
  48. if tmpErr != nil && !utils.IsErrNoRow(tmpErr) {
  49. err = tmpErr
  50. return
  51. }
  52. if tmpErr == nil {
  53. // 如果找到了,那么需要将当前的给移除掉
  54. err = item.Del()
  55. if err != nil {
  56. return
  57. }
  58. // 并将查出来的微信公众号摘出来的数据重新赋值
  59. item = wechatPlatformInfo
  60. } else if utils.IsErrNoRow(tmpErr) {
  61. // 如果没找到,那么就变更当前的信息
  62. item.FakeId = articleDetail.Appuin
  63. item.Nickname = articleDetail.Nickname
  64. //item.Alias = req.Alias
  65. item.RoundHeadImg = articleDetail.RoundHeadImg
  66. //item.ServiceType = req.ServiceType
  67. item.Signature = articleDetail.ProfileSignature
  68. //item.Verified = verified
  69. item.ModifyTime = time.Now()
  70. err = item.Update([]string{rag.WechatPlatformColumns.FakeID, rag.WechatPlatformColumns.Nickname, rag.WechatPlatformColumns.RoundHeadImg, rag.WechatPlatformColumns.Signature, rag.WechatPlatformColumns.ModifyTime})
  71. if err != nil {
  72. return
  73. }
  74. }
  75. // 把刚搜索的文章加入到指标库
  76. AddWechatArticle(item, articleLink, articleDetail, nil)
  77. BeachAddWechatArticle(item, 10)
  78. fmt.Println("公众号入库完成")
  79. return
  80. }
  81. // AddWechatArticle
  82. // @Description: 添加公众号文章入库
  83. // @author: Roc
  84. // @datetime 2025-03-05 13:24:14
  85. // @param item *rag.WechatPlatform
  86. // @param link string
  87. // @param articleDetail WechatArticleDataResp
  88. func AddWechatArticle(item *rag.WechatPlatform, articleLink string, articleDetail WechatArticleDataResp, articleMenu *ArticleMenu) {
  89. var err error
  90. defer func() {
  91. if err != nil {
  92. utils.FileLog.Error("公众号文章入库失败,文章链接:%s ,err:%v", articleLink, err)
  93. }
  94. }()
  95. obj := new(rag.WechatArticle)
  96. _, err = obj.GetByLink(articleLink)
  97. if err == nil {
  98. // 文章已经入库了,不需要重复入库
  99. return
  100. }
  101. // 如果不是 ErrNoRow 的时候,那么就是查询数据库出问题了,需要直接返回
  102. if !utils.IsErrNoRow(err) {
  103. return
  104. }
  105. // 这个时候,说明数据库中没有这个文章,那么需要文章入库
  106. err = nil
  107. var publishAt time.Time
  108. if articleDetail.CreateAt != `` {
  109. createAtInt, tmpErr := strconv.Atoi(articleDetail.CreateAt)
  110. if tmpErr == nil {
  111. publishAt = time.Unix(int64(createAtInt), 1000)
  112. }
  113. } else if articleMenu != nil {
  114. publishAt = time.Unix(int64(articleMenu.UpdateTime), 1000)
  115. }
  116. obj = &rag.WechatArticle{
  117. WechatArticleId: 0,
  118. WechatPlatformId: item.WechatPlatformId,
  119. FakeId: item.FakeId,
  120. Title: articleDetail.Title,
  121. Link: articleLink,
  122. CoverUrl: articleDetail.CoverUrl,
  123. Description: articleDetail.Desc,
  124. Content: html.EscapeString(articleDetail.HtmlContent),
  125. TextContent: articleDetail.TextContent,
  126. Country: articleDetail.CountryName,
  127. Province: articleDetail.ProvinceName,
  128. City: articleDetail.CityName,
  129. //Abstract: "",
  130. //ArticleCreateTime: createAt,
  131. ModifyTime: time.Now(),
  132. CreateTime: time.Now(),
  133. }
  134. if !publishAt.IsZero() {
  135. obj.ArticleCreateTime = publishAt
  136. }
  137. if articleMenu != nil {
  138. obj.Title = articleMenu.Title
  139. //obj.Link = articleMenu.Link
  140. obj.CoverUrl = articleMenu.Cover
  141. obj.Description = articleMenu.Digest
  142. }
  143. err = obj.Create()
  144. // 文章入库成功后,需要将相关信息入摘要库
  145. go cache.AddWechatArticleLlmOpToCache(item.WechatPlatformId, ``)
  146. }
  147. // BeachAddWechatArticle
  148. // @Description: 批量添加公众号文章
  149. // @param item
  150. // @param num
  151. // @return err
  152. func BeachAddWechatArticle(item *rag.WechatPlatform, num int) {
  153. var err error
  154. defer func() {
  155. //fmt.Println("公众号文章批量入库完成")
  156. if err != nil {
  157. utils.FileLog.Error("公众号文章批量入库失败,err:%v", err)
  158. fmt.Println("公众号文章批量入库失败,err:", err)
  159. }
  160. }()
  161. if item.FakeId == `` {
  162. return
  163. }
  164. wechatArticleObj := new(rag.WechatArticle)
  165. // 获取公众号的文章列表
  166. articleListResp, err := SearchByWechatArticleList(item.FakeId, num)
  167. if err != nil {
  168. return
  169. }
  170. for _, articleMenu := range articleListResp.List {
  171. // 判断文章是否已经入库,如果已经入库了,那么就过滤,不去重复查询微信了
  172. _, err = wechatArticleObj.GetByLink(articleMenu.Link)
  173. if err == nil {
  174. // 文章已经入库了,不需要重复入库
  175. continue
  176. }
  177. if !utils.IsErrNoRow(err) {
  178. return
  179. }
  180. err = nil
  181. articleDetail, tmpErr := SearchByWechatArticle(articleMenu.Link)
  182. if tmpErr != nil {
  183. err = tmpErr
  184. return
  185. }
  186. // 把刚搜索的文章加入到指标库
  187. AddWechatArticle(item, articleMenu.Link, articleDetail, &articleMenu)
  188. time.Sleep(10 * time.Second)
  189. }
  190. return
  191. }
  192. // GenerateArticleAbstract
  193. // @Description: 文章摘要生成
  194. // @author: Roc
  195. // @datetime 2025-03-10 16:17:53
  196. // @param item *rag.WechatArticle
  197. func GenerateArticleAbstract(item *rag.WechatArticle) {
  198. var err error
  199. defer func() {
  200. if err != nil {
  201. utils.FileLog.Error("文章转临时文件失败,err:%v", err)
  202. fmt.Println("文章转临时文件失败,err:", err)
  203. }
  204. }()
  205. abstractObj := rag.WechatArticleAbstract{}
  206. _, err = abstractObj.GetByWechatArticleId(item.WechatArticleId)
  207. if err == nil {
  208. // 摘要已经生成,不需要重复生成
  209. return
  210. }
  211. if !utils.IsErrNoRow(err) {
  212. return
  213. }
  214. // 生成临时文件
  215. dateDir := time.Now().Format("20060102")
  216. uploadDir := utils.STATIC_DIR + "ai/" + dateDir
  217. err = os.MkdirAll(uploadDir, utils.DIR_MOD)
  218. if err != nil {
  219. err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
  220. return
  221. }
  222. randStr := utils.GetRandStringNoSpecialChar(28)
  223. fileName := randStr + `.md`
  224. tmpFilePath := uploadDir + "/" + fileName
  225. err = utils.SaveToFile(item.TextContent, tmpFilePath)
  226. if err != nil {
  227. err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
  228. return
  229. }
  230. defer func() {
  231. os.Remove(tmpFilePath)
  232. }()
  233. // 上传临时文件到LLM
  234. tmpFileResp, err := UploadTempDocs(tmpFilePath)
  235. if err != nil {
  236. err = fmt.Errorf("上传临时文件到LLM失败,Err:" + err.Error())
  237. return
  238. }
  239. if tmpFileResp.Data.Id == `` {
  240. err = fmt.Errorf("上传临时文件到LLM失败,Err:上传失败")
  241. return
  242. }
  243. tmpDocId := tmpFileResp.Data.Id
  244. //tmpDocId := `c4d2ee902808408c8b8ed398b33be103` // 钢材
  245. //tmpDocId := `2dde8afe62d24525a814e74e0a5e35e4` // 钢材
  246. //tmpDocId := `7634cc1086c04b3687682220a2cf1a48` //
  247. historyList := make([]eta_llm_http.HistoryContent, 0)
  248. questionObj := rag.Question{}
  249. questionList, err := questionObj.GetListByCondition(``, []interface{}{}, 0, 100)
  250. if err != nil {
  251. err = fmt.Errorf("获取问题列表失败,Err:" + err.Error())
  252. return
  253. }
  254. addArticleChatRecordList := make([]*rag.WechatArticleChatRecord, 0)
  255. var abstract string
  256. //开始对话
  257. for _, question := range questionList {
  258. originalAnswer, tmpAnswer, tmpErr := getAnswerByContent(tmpDocId, question.QuestionContent, historyList)
  259. if tmpErr != nil {
  260. err = fmt.Errorf("LLM对话失败,Err:" + tmpErr.Error())
  261. return
  262. }
  263. abstract = tmpAnswer
  264. historyList = append(historyList, eta_llm_http.HistoryContent{
  265. Role: `user`,
  266. Content: question.QuestionContent,
  267. }, eta_llm_http.HistoryContent{
  268. Role: `assistant`,
  269. Content: tmpAnswer,
  270. })
  271. // 待入库的数据
  272. addArticleChatRecordList = append(addArticleChatRecordList, &rag.WechatArticleChatRecord{
  273. WechatArticleChatRecordId: 0,
  274. WechatArticleId: item.WechatArticleId,
  275. ChatUserType: "user",
  276. Content: question.QuestionContent,
  277. SendTime: time.Now(),
  278. CreatedTime: time.Now(),
  279. UpdateTime: time.Now(),
  280. }, &rag.WechatArticleChatRecord{
  281. WechatArticleChatRecordId: 0,
  282. WechatArticleId: item.WechatArticleId,
  283. ChatUserType: "assistant",
  284. Content: originalAnswer,
  285. SendTime: time.Now(),
  286. CreatedTime: time.Now(),
  287. UpdateTime: time.Now(),
  288. })
  289. }
  290. // 添加问答记录
  291. if len(addArticleChatRecordList) > 0 {
  292. recordObj := rag.WechatArticleChatRecord{}
  293. err = recordObj.CreateInBatches(addArticleChatRecordList)
  294. if err != nil {
  295. return
  296. }
  297. }
  298. if abstract != `` {
  299. abstractItem := &rag.WechatArticleAbstract{
  300. WechatArticleAbstractId: 0,
  301. WechatArticleId: item.WechatArticleId,
  302. Content: abstract,
  303. Version: 0,
  304. VectorKey: "",
  305. ModifyTime: time.Now(),
  306. CreateTime: time.Now(),
  307. }
  308. err = abstractItem.Create()
  309. if err != nil {
  310. return
  311. }
  312. AbstractToKnowledge(item, abstractItem)
  313. }
  314. }
  315. func getAnswerByContent(docId, question string, historyList []eta_llm_http.HistoryContent) (originalAnswer, answer string, err error) {
  316. originalAnswer, result, err := ChatByFile(docId, question, historyList)
  317. fmt.Println(result)
  318. if err != nil {
  319. err = fmt.Errorf("LLM对话失败,Err:" + err.Error())
  320. return
  321. }
  322. // 提取 </think> 后面的内容
  323. thinkEndIndex := strings.Index(result.Answer, "</think>")
  324. if thinkEndIndex != -1 {
  325. answer = strings.TrimSpace(result.Answer[thinkEndIndex+len("</think>"):])
  326. } else {
  327. answer = result.Answer
  328. }
  329. answer = strings.TrimSpace(answer)
  330. return
  331. }
  332. // ArticleToKnowledge
  333. // @Description: 原文入向量库
  334. // @author: Roc
  335. // @datetime 2025-03-10 16:13:16
  336. // @param item *rag.WechatArticle
  337. func ArticleToKnowledge(item *rag.WechatArticle) {
  338. if item.TextContent == `` {
  339. return
  340. }
  341. var err error
  342. defer func() {
  343. if err != nil {
  344. utils.FileLog.Error("上传文章原文到知识库失败,err:%v", err)
  345. fmt.Println("上传文章原文到知识库失败,err:", err)
  346. }
  347. }()
  348. // 生成临时文件
  349. //dateDir := time.Now().Format("20060102")
  350. //uploadDir := utils.STATIC_DIR + "ai/article/" + dateDir
  351. uploadDir := utils.STATIC_DIR + "ai/article"
  352. err = os.MkdirAll(uploadDir, utils.DIR_MOD)
  353. if err != nil {
  354. err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
  355. return
  356. }
  357. fileName := RemoveSpecialChars(item.Title) + `.md`
  358. tmpFilePath := uploadDir + "/" + fileName
  359. err = utils.SaveToFile(item.TextContent, tmpFilePath)
  360. if err != nil {
  361. err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
  362. return
  363. }
  364. defer func() {
  365. os.Remove(tmpFilePath)
  366. }()
  367. knowledgeArticleName := models.BusinessConfMap[models.KnowledgeArticleName]
  368. // 上传临时文件到LLM
  369. uploadFileResp, err := UploadDocsToKnowledge(tmpFilePath, knowledgeArticleName)
  370. if err != nil {
  371. err = fmt.Errorf("上传文章原文到知识库失败,Err:" + err.Error())
  372. return
  373. }
  374. if len(uploadFileResp.FailedFiles) > 0 {
  375. for _, v := range uploadFileResp.FailedFiles {
  376. err = fmt.Errorf("上传文章原文到知识库失败,Err:" + v)
  377. }
  378. }
  379. item.VectorKey = tmpFilePath
  380. item.ModifyTime = time.Now()
  381. err = item.Update([]string{"vector_key", "modify_time"})
  382. }
  383. // AbstractToKnowledge
  384. // @Description: 摘要入向量库
  385. // @author: Roc
  386. // @datetime 2025-03-10 16:14:59
  387. // @param wechatArticleItem *rag.WechatArticle
  388. // @param item *rag.WechatArticleAbstract
  389. func AbstractToKnowledge(wechatArticleItem *rag.WechatArticle, item *rag.WechatArticleAbstract) {
  390. if item.Content == `` {
  391. return
  392. }
  393. // 已经生成了,那就不处理了
  394. if item.VectorKey != `` {
  395. return
  396. }
  397. var err error
  398. defer func() {
  399. if err != nil {
  400. utils.FileLog.Error("摘要入向量库失败,err:%v", err)
  401. fmt.Println("摘要入向量库失败,err:", err)
  402. }
  403. }()
  404. // 生成临时文件
  405. //dateDir := time.Now().Format("20060102")
  406. //uploadDir := utils.STATIC_DIR + "ai/article/" + dateDir
  407. uploadDir := utils.STATIC_DIR + "ai/abstract"
  408. err = os.MkdirAll(uploadDir, utils.DIR_MOD)
  409. if err != nil {
  410. err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
  411. return
  412. }
  413. fileName := RemoveSpecialChars(wechatArticleItem.Title) + `.md`
  414. tmpFilePath := uploadDir + "/" + fileName
  415. err = utils.SaveToFile(item.Content, tmpFilePath)
  416. if err != nil {
  417. err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
  418. return
  419. }
  420. defer func() {
  421. os.Remove(tmpFilePath)
  422. }()
  423. knowledgeArticleName := models.BusinessConfMap[models.KnowledgeBaseName]
  424. // 上传临时文件到LLM
  425. uploadFileResp, err := UploadDocsToKnowledge(tmpFilePath, knowledgeArticleName)
  426. if err != nil {
  427. err = fmt.Errorf("上传文章原文到知识库失败,Err:" + err.Error())
  428. return
  429. }
  430. if len(uploadFileResp.FailedFiles) > 0 {
  431. for _, v := range uploadFileResp.FailedFiles {
  432. err = fmt.Errorf("上传文章原文到知识库失败,Err:" + v)
  433. }
  434. }
  435. item.VectorKey = tmpFilePath
  436. item.ModifyTime = time.Now()
  437. err = item.Update([]string{"vector_key", "modify_time"})
  438. }
  439. func RemoveSpecialChars(text string) string {
  440. // 匹配非中文、非字母、非数字、非中文标点的字符
  441. reg := regexp.MustCompile(`[^\p{Han}\p{L}\p{N}\x{3000}-\x{303F}]`)
  442. return reg.ReplaceAllString(text, "")
  443. }