bge.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package embedding
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "log"
  9. "net/http"
  10. "strings"
  11. "time"
  12. )
  13. // BGEEmbeddingService BGE 嵌入服务实现
  14. type BGEEmbeddingService struct {
  15. apiURL string
  16. apiKey string
  17. model string
  18. dimension int
  19. }
  20. // NewBGEEmbeddingService 创建 BGE 嵌入服务实例
  21. func NewBGEEmbeddingService(apiURL, apiKey, model string) *BGEEmbeddingService {
  22. if apiURL == "" {
  23. apiURL = "http://localhost:8080"
  24. }
  25. if model == "" {
  26. model = "bge-small-zh-v1.5"
  27. }
  28. return &BGEEmbeddingService{
  29. apiURL: apiURL,
  30. apiKey: apiKey,
  31. model: model,
  32. dimension: 512, // BGE 模型的默认维度
  33. }
  34. }
  35. // EmbedText 向量化单个文本
  36. func (s *BGEEmbeddingService) EmbedText(ctx context.Context, text string) ([]float32, error) {
  37. vectors, err := s.EmbedTexts(ctx, []string{text})
  38. if err != nil {
  39. return nil, err
  40. }
  41. if len(vectors) == 0 {
  42. return nil, fmt.Errorf("未返回向量")
  43. }
  44. return vectors[0], nil
  45. }
  46. // EmbedTexts 批量向量化文本
  47. func (s *BGEEmbeddingService) EmbedTexts(ctx context.Context, texts []string) ([][]float32, error) {
  48. if len(texts) == 0 {
  49. return nil, nil
  50. }
  51. // 诊断日志:确认发请求前我们到底发了几条文本
  52. log.Printf("[嵌入] BGE EmbedTexts 请求: len(texts)=%d, model=%s, apiURL=%s", len(texts), s.model, strings.TrimSuffix(s.apiURL, "/"))
  53. for i, t := range texts {
  54. runeLen := len([]rune(t))
  55. preview := t
  56. if runeLen > 60 {
  57. preview = string([]rune(t)[:60]) + "..."
  58. }
  59. log.Printf("[嵌入] texts[%d] 长度=%d 字符, 预览: %q", i, runeLen, preview)
  60. }
  61. // 支持填完整路径或仅填 base:若已以 /embeddings 结尾则不再追加,否则追加 /embeddings
  62. url := strings.TrimSuffix(s.apiURL, "/")
  63. if url != "" && !strings.HasSuffix(strings.ToLower(url), "/embeddings") {
  64. url = url + "/embeddings"
  65. } else if url == "" {
  66. url = s.apiURL + "/embeddings"
  67. }
  68. // 构建请求体(兼容 HuggingFace Inference API 格式)
  69. requestBody := map[string]interface{}{
  70. "inputs": texts,
  71. }
  72. jsonData, err := json.Marshal(requestBody)
  73. if err != nil {
  74. return nil, fmt.Errorf("序列化请求失败: %w", err)
  75. }
  76. // 创建 HTTP 请求
  77. req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
  78. if err != nil {
  79. return nil, fmt.Errorf("创建请求失败: %w", err)
  80. }
  81. req.Header.Set("Content-Type", "application/json")
  82. if s.apiKey != "" {
  83. req.Header.Set("Authorization", "Bearer "+s.apiKey)
  84. }
  85. // 发送请求
  86. client := &http.Client{Timeout: 30 * time.Second}
  87. resp, err := client.Do(req)
  88. if err != nil {
  89. return nil, fmt.Errorf("BGE 嵌入服务调用失败: %w", err)
  90. }
  91. defer resp.Body.Close()
  92. // 读取响应
  93. body, err := io.ReadAll(resp.Body)
  94. if err != nil {
  95. return nil, fmt.Errorf("读取响应失败: %w", err)
  96. }
  97. if resp.StatusCode != http.StatusOK {
  98. return nil, fmt.Errorf("BGE 嵌入服务调用失败: HuggingFace API 返回错误状态码 %d: %s", resp.StatusCode, string(body))
  99. }
  100. // 解析响应(HuggingFace Inference API 格式);若返回 HTML 则提示检查 API 地址/密钥
  101. var response [][]float64
  102. if err := json.Unmarshal(body, &response); err != nil {
  103. if len(body) > 0 && body[0] == '<' {
  104. snippet := string(body)
  105. if len(snippet) > 200 {
  106. snippet = snippet[:200] + "..."
  107. }
  108. log.Printf("[嵌入] BGE 返回了 HTML 而非 JSON,请检查 API 地址与密钥。响应片段: %s", snippet)
  109. return nil, fmt.Errorf("嵌入 API 返回了 HTML 而非 JSON,请检查「设置 - 知识库向量模型」中的 API 地址与密钥: %w", err)
  110. }
  111. return nil, fmt.Errorf("解析响应失败: %w", err)
  112. }
  113. // 诊断日志:API 实际返回了几个向量
  114. numIn := len(texts)
  115. numOut := len(response)
  116. log.Printf("[嵌入] BGE EmbedTexts 响应: len(texts)=%d -> len(response)=%d (API 返回向量数)", numIn, numOut)
  117. if numOut != numIn {
  118. log.Printf("[嵌入] 数量不一致: 我们发了 %d 条文本,API 返回了 %d 个向量", numIn, numOut)
  119. }
  120. // 转换为 float32
  121. result := make([][]float32, len(response))
  122. for i, item := range response {
  123. result[i] = make([]float32, len(item))
  124. for j, v := range item {
  125. result[i][j] = float32(v)
  126. }
  127. }
  128. return result, nil
  129. }
  130. // GetDimension 获取向量维度
  131. func (s *BGEEmbeddingService) GetDimension() int {
  132. return s.dimension
  133. }
  134. // GetModelName 获取模型名称
  135. func (s *BGEEmbeddingService) GetModelName() string {
  136. return s.model
  137. }