openai.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. // OpenAIEmbeddingService OpenAI 嵌入服务实现
  14. type OpenAIEmbeddingService struct {
  15. apiURL string
  16. apiKey string
  17. model string
  18. dimension int
  19. }
  20. // NewOpenAIEmbeddingService 创建 OpenAI 嵌入服务实例
  21. func NewOpenAIEmbeddingService(apiURL, apiKey, model string) *OpenAIEmbeddingService {
  22. if apiURL == "" {
  23. apiURL = "https://api.openai.com/v1"
  24. }
  25. if model == "" {
  26. model = "text-embedding-3-small"
  27. }
  28. dimension := 1536 // text-embedding-3-small 的默认维度
  29. if model == "text-embedding-3-large" {
  30. dimension = 3072
  31. }
  32. return &OpenAIEmbeddingService{
  33. apiURL: apiURL,
  34. apiKey: apiKey,
  35. model: model,
  36. dimension: dimension,
  37. }
  38. }
  39. // EmbedText 向量化单个文本
  40. func (s *OpenAIEmbeddingService) EmbedText(ctx context.Context, text string) ([]float32, error) {
  41. vectors, err := s.EmbedTexts(ctx, []string{text})
  42. if err != nil {
  43. return nil, err
  44. }
  45. if len(vectors) == 0 {
  46. return nil, fmt.Errorf("未返回向量")
  47. }
  48. return vectors[0], nil
  49. }
  50. // EmbedTexts 批量向量化文本
  51. func (s *OpenAIEmbeddingService) EmbedTexts(ctx context.Context, texts []string) ([][]float32, error) {
  52. if len(texts) == 0 {
  53. return nil, nil
  54. }
  55. // 支持填完整路径或仅填 base:若已以 /embeddings 结尾则不再追加,否则追加 /embeddings
  56. url := strings.TrimSuffix(s.apiURL, "/")
  57. if url != "" && !strings.HasSuffix(strings.ToLower(url), "/embeddings") {
  58. url = url + "/embeddings"
  59. } else if url == "" {
  60. url = s.apiURL + "/embeddings"
  61. }
  62. // 构建请求体
  63. requestBody := map[string]interface{}{
  64. "input": texts,
  65. "model": s.model,
  66. }
  67. jsonData, err := json.Marshal(requestBody)
  68. if err != nil {
  69. return nil, fmt.Errorf("序列化请求失败: %w", err)
  70. }
  71. // 创建 HTTP 请求
  72. req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
  73. if err != nil {
  74. return nil, fmt.Errorf("创建请求失败: %w", err)
  75. }
  76. req.Header.Set("Content-Type", "application/json")
  77. req.Header.Set("Authorization", "Bearer "+s.apiKey)
  78. // 发送请求
  79. client := &http.Client{Timeout: 30 * time.Second}
  80. resp, err := client.Do(req)
  81. if err != nil {
  82. return nil, fmt.Errorf("发送请求失败: %w", err)
  83. }
  84. defer resp.Body.Close()
  85. // 读取响应
  86. body, err := io.ReadAll(resp.Body)
  87. if err != nil {
  88. return nil, fmt.Errorf("读取响应失败: %w", err)
  89. }
  90. if resp.StatusCode != http.StatusOK {
  91. return nil, fmt.Errorf("OpenAI API 返回错误状态码 %d: %s", resp.StatusCode, string(body))
  92. }
  93. // 解析响应(若返回 HTML 则提示检查 API 地址/密钥)
  94. var response struct {
  95. Data []struct {
  96. Embedding []float64 `json:"embedding"`
  97. } `json:"data"`
  98. }
  99. if err := json.Unmarshal(body, &response); err != nil {
  100. if len(body) > 0 && body[0] == '<' {
  101. snippet := string(body)
  102. if len(snippet) > 200 {
  103. snippet = snippet[:200] + "..."
  104. }
  105. log.Printf("[嵌入] OpenAI 返回了 HTML 而非 JSON,请检查 API 地址与密钥。响应片段: %s", snippet)
  106. return nil, fmt.Errorf("嵌入 API 返回了 HTML 而非 JSON,请检查「设置 - 知识库向量模型」中的 API 地址与密钥: %w", err)
  107. }
  108. return nil, fmt.Errorf("解析响应失败: %w", err)
  109. }
  110. // 转换为 float32
  111. result := make([][]float32, len(response.Data))
  112. for i, item := range response.Data {
  113. result[i] = make([]float32, len(item.Embedding))
  114. for j, v := range item.Embedding {
  115. result[i][j] = float32(v)
  116. }
  117. }
  118. return result, nil
  119. }
  120. // GetDimension 获取向量维度
  121. func (s *OpenAIEmbeddingService) GetDimension() int {
  122. return s.dimension
  123. }
  124. // GetModelName 获取模型名称
  125. func (s *OpenAIEmbeddingService) GetModelName() string {
  126. return s.model
  127. }