// Package embed produces dense vector embeddings for brain content. // // Wire format is Ollama's `/api/embed`, with the canonical request shape // `{"model": "...", "input": "..."}` and a 2-D `embeddings` response. // Default deployment runs `nomic-embed-text` on iguana, which returns // 768-dim vectors compatible with the brain_embeddings table schema. package embed import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "strings" "time" ) // Client posts embedding requests to an Ollama-compatible endpoint. type Client struct { URL string Model string HTTP *http.Client } // New constructs a Client. Returns nil when url is empty so callers can // treat a missing BRAIN_EMBED_URL as "feature disabled" via a single nil // check. func New(url, model string) *Client { if url == "" { return nil } return &Client{ URL: strings.TrimRight(url, "/"), Model: model, HTTP: &http.Client{Timeout: 30 * time.Second}, } } // Embed returns the embedding vector for text. Empty text is rejected // up-front to keep upstream errors from masking caller mistakes. func (c *Client) Embed(ctx context.Context, text string) ([]float32, error) { if strings.TrimSpace(text) == "" { return nil, fmt.Errorf("embed: empty text") } reqBody, _ := json.Marshal(map[string]any{ "model": c.Model, "input": text, }) req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.URL+"/api/embed", bytes.NewReader(reqBody)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") resp, err := c.HTTP.Do(req) if err != nil { return nil, err } defer func() { _ = resp.Body.Close() }() if resp.StatusCode/100 != 2 { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("embed: status %d: %s", resp.StatusCode, string(body)) } var out struct { Embeddings [][]float32 `json:"embeddings"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return nil, fmt.Errorf("embed: decode: %w", err) } if len(out.Embeddings) == 0 || len(out.Embeddings[0]) == 0 { return nil, fmt.Errorf("embed: empty embeddings in response") } return out.Embeddings[0], nil }