retrieval.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. package rag
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. "time"
  7. "github.com/2930134478/AI-CS/backend/repository"
  8. "github.com/2930134478/AI-CS/backend/service/embedding"
  9. )
  10. // RetrievalService RAG 检索服务
  11. type RetrievalService struct {
  12. vectorStoreService *VectorStoreService
  13. embeddingProvider embedding.EmbeddingProvider
  14. docRepo *repository.DocumentRepository // 按发布状态过滤
  15. kbRepo *repository.KnowledgeBaseRepository // 按知识库「参与 RAG」过滤
  16. cache *Cache
  17. reranker *SimpleReranker
  18. metrics *Metrics
  19. }
  20. // NewRetrievalService 创建 RAG 检索服务实例(仅已发布文档且所属知识库已开启 RAG 的参与检索)
  21. func NewRetrievalService(vectorStoreService *VectorStoreService, embeddingProvider embedding.EmbeddingProvider, docRepo *repository.DocumentRepository, kbRepo *repository.KnowledgeBaseRepository) *RetrievalService {
  22. return &RetrievalService{
  23. vectorStoreService: vectorStoreService,
  24. embeddingProvider: embeddingProvider,
  25. docRepo: docRepo,
  26. kbRepo: kbRepo,
  27. cache: NewCache(),
  28. reranker: NewSimpleReranker(),
  29. metrics: NewMetrics(),
  30. }
  31. }
  32. // EnableCache 启用检索缓存(ttl 单位为秒)
  33. func (s *RetrievalService) EnableCache(ttl time.Duration) {
  34. s.cache.SetTTL(int(ttl.Seconds()))
  35. }
  36. // Retrieve 执行 RAG 检索
  37. func (s *RetrievalService) Retrieve(ctx context.Context, query string, topK int, knowledgeBaseID *uint) ([]SearchResult, error) {
  38. startTime := time.Now()
  39. cacheHit := false
  40. var results []SearchResult
  41. var err error
  42. // 检查缓存
  43. if s.cache != nil {
  44. if cached, ok := s.cache.Get(query, topK, knowledgeBaseID); ok {
  45. results = cached
  46. cacheHit = true
  47. }
  48. }
  49. // 如果缓存未命中,执行检索
  50. if !cacheHit {
  51. svc, err := s.embeddingProvider.Get(ctx)
  52. if err != nil {
  53. s.metrics.RecordQuery(false, time.Since(startTime), false)
  54. return nil, fmt.Errorf("获取嵌入服务失败: %w", err)
  55. }
  56. // 向量化查询
  57. queryVectors, err := svc.EmbedTexts(ctx, []string{query})
  58. if err != nil {
  59. s.metrics.RecordQuery(false, time.Since(startTime), false)
  60. return nil, fmt.Errorf("查询向量化失败: %w", err)
  61. }
  62. if len(queryVectors) == 0 {
  63. s.metrics.RecordQuery(false, time.Since(startTime), false)
  64. return nil, fmt.Errorf("未返回查询向量")
  65. }
  66. // 转换知识库 ID
  67. var kbIDStr *string
  68. if knowledgeBaseID != nil {
  69. str := ConvertKnowledgeBaseID(*knowledgeBaseID)
  70. kbIDStr = &str
  71. }
  72. // 多取一些结果,过滤未发布文档后仍能凑满 topK
  73. searchLimit := topK * 3
  74. if searchLimit < 10 {
  75. searchLimit = 10
  76. }
  77. results, err = s.vectorStoreService.SearchVectors(ctx, queryVectors[0], searchLimit, kbIDStr)
  78. if err != nil {
  79. s.metrics.RecordQuery(false, time.Since(startTime), false)
  80. return nil, fmt.Errorf("向量检索失败: %w", err)
  81. }
  82. // 仅保留「已发布」的文档参与 RAG;未在 documents 表中的条目(如 FAQ)视为可展示
  83. results = s.filterByPublished(ctx, results, topK)
  84. // 缓存过滤后的结果
  85. if s.cache != nil {
  86. s.cache.Set(query, topK, knowledgeBaseID, results)
  87. }
  88. }
  89. // 记录指标
  90. s.metrics.RecordQuery(err == nil, time.Since(startTime), cacheHit)
  91. return results, err
  92. }
  93. // RetrieveWithRerank 执行带重排序的 RAG 检索
  94. func (s *RetrievalService) RetrieveWithRerank(ctx context.Context, query string, topK int, knowledgeBaseID *uint) ([]SearchResult, error) {
  95. // 先执行基础检索
  96. results, err := s.Retrieve(ctx, query, topK, knowledgeBaseID)
  97. if err != nil {
  98. return nil, err
  99. }
  100. // 重排序
  101. if s.reranker != nil {
  102. reranked, err := s.reranker.Rerank(ctx, query, results)
  103. if err != nil {
  104. // 重排序失败不影响主流程,返回原始结果
  105. return results, nil
  106. }
  107. return reranked, nil
  108. }
  109. return results, nil
  110. }
  111. // filterByPublished 仅保留「已发布」且所属知识库已开启 RAG 的文档;FAQ 保留;取前 topK 条
  112. func (s *RetrievalService) filterByPublished(ctx context.Context, results []SearchResult, topK int) []SearchResult {
  113. if s.docRepo == nil || len(results) == 0 {
  114. if len(results) > topK {
  115. return results[:topK]
  116. }
  117. return results
  118. }
  119. docIDs := make([]uint, 0, len(results))
  120. seen := make(map[uint]struct{})
  121. for _, r := range results {
  122. id, err := strconv.ParseUint(r.DocumentID, 10, 32)
  123. if err != nil {
  124. continue
  125. }
  126. uid := uint(id)
  127. if _, ok := seen[uid]; !ok {
  128. seen[uid] = struct{}{}
  129. docIDs = append(docIDs, uid)
  130. }
  131. }
  132. docs, err := s.docRepo.GetByIDs(docIDs)
  133. if err != nil {
  134. return results
  135. }
  136. unpublished := make(map[uint]struct{})
  137. docIDToKBID := make(map[uint]uint)
  138. for _, d := range docs {
  139. if d.Status != "published" {
  140. unpublished[d.ID] = struct{}{}
  141. }
  142. docIDToKBID[d.ID] = d.KnowledgeBaseID
  143. }
  144. // 知识库未参与 RAG 的集合
  145. disabledKBIDs := make(map[uint]struct{})
  146. if s.kbRepo != nil && len(docIDToKBID) > 0 {
  147. kbIDSet := make(map[uint]struct{})
  148. for _, kbID := range docIDToKBID {
  149. kbIDSet[kbID] = struct{}{}
  150. }
  151. kbIDs := make([]uint, 0, len(kbIDSet))
  152. for id := range kbIDSet {
  153. kbIDs = append(kbIDs, id)
  154. }
  155. if kbs, err := s.kbRepo.GetByIDs(kbIDs); err == nil {
  156. for _, kb := range kbs {
  157. if !kb.RAGEnabled {
  158. disabledKBIDs[kb.ID] = struct{}{}
  159. }
  160. }
  161. }
  162. }
  163. filtered := make([]SearchResult, 0, len(results))
  164. for _, r := range results {
  165. id, err := strconv.ParseUint(r.DocumentID, 10, 32)
  166. if err != nil {
  167. filtered = append(filtered, r)
  168. continue
  169. }
  170. uid := uint(id)
  171. if _, ok := unpublished[uid]; ok {
  172. continue
  173. }
  174. if kbID, inDoc := docIDToKBID[uid]; inDoc {
  175. if _, disabled := disabledKBIDs[kbID]; disabled {
  176. continue
  177. }
  178. }
  179. filtered = append(filtered, r)
  180. if len(filtered) >= topK {
  181. break
  182. }
  183. }
  184. return filtered
  185. }
  186. // GetMetrics 获取性能指标
  187. func (s *RetrievalService) GetMetrics() map[string]interface{} {
  188. return s.metrics.GetStats()
  189. }