package services import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" "time" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) var umindEmbeddingsHTTPClient = &http.Client{Timeout: 30 * time.Second} // GenerarEmbeddings pide los vectores de una tanda de textos en una sola // llamada (más barato y rápido que uno por uno durante la ingesta). Usa el // endpoint de embeddings compatible con OpenAI — Anthropic no ofrece // embeddings, por eso esta config debe ser una con provider "openai" (u otro // compatible con ese formato de respuesta). func GenerarEmbeddings(ai *models.AiConfig, textos []string) ([][]float32, error) { if ai == nil { return nil, fmt.Errorf("no hay una configuración de IA para generar embeddings") } if len(textos) == 0 { return nil, nil } baseURL := ai.BaseURL if baseURL == "" { baseURL = ProviderDefaultURL(ai.Provider) } baseURL = strings.TrimRight(baseURL, "/") model := ai.ModelName if model == "" { model = "text-embedding-3-small" } reqBody, _ := json.Marshal(map[string]interface{}{ "model": model, "input": textos, }) req, err := http.NewRequest("POST", baseURL+"/embeddings", bytes.NewReader(reqBody)) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+ai.ClaveEnClaro()) req.Header.Set("Content-Type", "application/json") resp, err := umindEmbeddingsHTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("no se pudo conectar con el servicio de embeddings: %w", err) } defer resp.Body.Close() raw, _ := io.ReadAll(io.LimitReader(resp.Body, 4*1024*1024)) if resp.StatusCode < 200 || resp.StatusCode >= 300 { detalle := strings.TrimSpace(string(raw)) if len(detalle) > 300 { detalle = detalle[:300] } return nil, fmt.Errorf("el servicio de embeddings respondió %d: %s", resp.StatusCode, detalle) } var out struct { Data []struct { Embedding []float32 `json:"embedding"` Index int `json:"index"` } `json:"data"` } if err := json.Unmarshal(raw, &out); err != nil { return nil, fmt.Errorf("respuesta inesperada del servicio de embeddings") } if len(out.Data) != len(textos) { return nil, fmt.Errorf("el servicio de embeddings devolvió %d vectores para %d textos", len(out.Data), len(textos)) } vectores := make([][]float32, len(textos)) for _, d := range out.Data { if d.Index < 0 || d.Index >= len(vectores) { continue } vectores[d.Index] = d.Embedding } return vectores, nil } // GenerarEmbedding es el atajo para un solo texto (ej: la consulta del usuario en RAG). func GenerarEmbedding(ai *models.AiConfig, texto string) ([]float32, error) { vectores, err := GenerarEmbeddings(ai, []string{texto}) if err != nil { return nil, err } if len(vectores) == 0 { return nil, fmt.Errorf("no se generó ningún embedding") } return vectores[0], nil }