import_service_batch.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. package service
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "time"
  7. "github.com/2930134478/AI-CS/backend/models"
  8. "github.com/2930134478/AI-CS/backend/service/rag"
  9. )
  10. // BatchEmbeddingResult 批量向量化结果
  11. type BatchEmbeddingResult struct {
  12. FailedDocs []uint `json:"failed_docs"`
  13. Errors []string `json:"errors"`
  14. }
  15. // BatchEmbedDocuments 批量向量化文档
  16. // 用于优化导入性能,将多个文档一次性向量化
  17. func (s *ImportService) BatchEmbedDocuments(ctx context.Context, docIDs []uint) (*BatchEmbeddingResult, error) {
  18. if len(docIDs) == 0 {
  19. return &BatchEmbeddingResult{}, nil
  20. }
  21. log.Printf("[导入] 批量向量化开始 doc_ids=%v", docIDs)
  22. result := &BatchEmbeddingResult{
  23. FailedDocs: []uint{},
  24. Errors: []string{},
  25. }
  26. // 获取文档
  27. docs, err := s.docRepo.GetByIDs(docIDs)
  28. if err != nil {
  29. return result, fmt.Errorf("获取文档失败: %w", err)
  30. }
  31. // 准备向量化数据
  32. documentIDs := make([]uint, 0, len(docs))
  33. knowledgeBaseIDs := make([]uint, 0, len(docs))
  34. contents := make([]string, 0, len(docs))
  35. docMap := make(map[uint]*models.Document)
  36. for _, doc := range docs {
  37. if doc.EmbeddingStatus == "completed" {
  38. continue // 跳过已向量化的文档
  39. }
  40. // 更新状态为处理中
  41. docCopy := doc
  42. docCopy.EmbeddingStatus = "processing"
  43. if err := s.docRepo.Update(&docCopy); err != nil {
  44. log.Printf("更新文档 %d 状态失败: %v", doc.ID, err)
  45. }
  46. documentIDs = append(documentIDs, doc.ID)
  47. knowledgeBaseIDs = append(knowledgeBaseIDs, doc.KnowledgeBaseID)
  48. contents = append(contents, doc.Content)
  49. docMap[doc.ID] = &docCopy
  50. }
  51. if len(documentIDs) == 0 {
  52. return result, nil
  53. }
  54. // 批量向量化
  55. // 使用独立的 context,避免 HTTP 请求超时导致向量化失败
  56. // 向量化可能需要较长时间(特别是 Milvus LoadCollection 操作)
  57. embedCtx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
  58. defer cancel()
  59. err = s.batchEmbedDocumentsInternal(embedCtx, documentIDs, knowledgeBaseIDs, contents, docMap, result)
  60. if err != nil {
  61. log.Printf("[导入] 批量向量化失败: %v", err)
  62. return result, err
  63. }
  64. log.Printf("[导入] 批量向量化成功 %d 条文档", len(documentIDs))
  65. return result, err
  66. }
  67. // batchEmbedDocumentsInternal 内部批量向量化实现
  68. func (s *ImportService) batchEmbedDocumentsInternal(
  69. ctx context.Context,
  70. documentIDs []uint,
  71. knowledgeBaseIDs []uint,
  72. contents []string,
  73. docMap map[uint]*models.Document,
  74. result *BatchEmbeddingResult,
  75. ) error {
  76. // 获取 documentEmbeddingService(通过类型断言)
  77. embeddingService, ok := s.documentEmbeddingService.(*rag.DocumentEmbeddingService)
  78. if !ok {
  79. return fmt.Errorf("documentEmbeddingService 类型错误")
  80. }
  81. // 批量向量化
  82. err := embeddingService.EmbedDocuments(ctx, documentIDs, knowledgeBaseIDs, contents)
  83. if err != nil {
  84. // 批量失败,标记所有文档为失败
  85. for _, docID := range documentIDs {
  86. if doc, ok := docMap[docID]; ok {
  87. doc.EmbeddingStatus = "failed"
  88. s.docRepo.Update(doc)
  89. result.FailedDocs = append(result.FailedDocs, docID)
  90. result.Errors = append(result.Errors, fmt.Sprintf("文档 %d: %v", docID, err))
  91. }
  92. }
  93. return fmt.Errorf("批量向量化失败: %w", err)
  94. }
  95. // 更新所有文档状态为已完成
  96. for _, docID := range documentIDs {
  97. if doc, ok := docMap[docID]; ok {
  98. doc.EmbeddingStatus = "completed"
  99. if err := s.docRepo.Update(doc); err != nil {
  100. log.Printf("更新文档 %d 状态失败: %v", docID, err)
  101. result.FailedDocs = append(result.FailedDocs, docID)
  102. result.Errors = append(result.Errors, fmt.Sprintf("文档 %d: 更新状态失败: %v", docID, err))
  103. }
  104. }
  105. }
  106. return nil
  107. }