Documentation
¶
Overview ¶
Package ollamaembed is a small, dependency-free client for text→vector embeddings via Ollama's local HTTP API, plus the vector helpers (Cosine, Dot, Normalize) and a curated model Catalog that typically go with it — the building blocks for semantic / RAG search in Go.
Ollama is the canonical local-model runner: a stable HTTP API on localhost:11434, models chosen via a one-time `ollama pull <name>`, no SDK, no CGo, no bundled model. Create an embedder with NewOllama and call Embed(ctx, text); the package also exposes model management (list / pull) on the OllamaEmbedder.
The Embedder interface is small enough that other sources (OpenAI, llama.cpp HTTP, etc.) can plug in behind the same Embed(ctx, text) shape without rewiring callers.
Example (Cosine) ¶
Example_cosine shows the vector helpers: normalise two vectors, then measure their cosine similarity (== dot product on unit vectors).
package main
import (
"fmt"
"github.com/richardwooding/ollamaembed"
)
func main() {
a := []float32{1, 0, 0}
b := []float32{1, 1, 0}
ollamaembed.Normalize(a)
ollamaembed.Normalize(b)
fmt.Printf("%.3f\n", ollamaembed.Cosine(a, b))
}
Output: 0.707
Index ¶
- Variables
- func BareName(name string) string
- func Cosine(a, b []float32) float64
- func Dot(a, b []float32) float64
- func Normalize(v []float32)
- type CatalogEntry
- type Embedder
- type LocalModel
- type OllamaEmbedder
- func (o *OllamaEmbedder) Embed(ctx context.Context, text string) ([]float32, error)
- func (o *OllamaEmbedder) ListLocal(ctx context.Context) ([]LocalModel, error)
- func (o *OllamaEmbedder) Model() string
- func (o *OllamaEmbedder) Pull(ctx context.Context, name string, onProgress func(PullProgress)) error
- type PullProgress
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Catalog = []CatalogEntry{
{
Name: "all-minilm",
Description: "Smallest. Great for memory-constrained machines; lower quality.",
Size: "~45 MB",
Dimensions: 384,
},
{
Name: "bge-m3",
Description: "Multilingual; supports dense + sparse + colbert outputs.",
Size: "~600 MB",
Dimensions: 1024,
},
{
Name: "mxbai-embed-large",
Description: "Higher dimensionality. Better recall on long-form prose; slower.",
Size: "~670 MB",
Dimensions: 1024,
},
{
Name: "nomic-embed-text",
Description: "Strong general-purpose. Good default for English documents.",
Size: "~270 MB",
Dimensions: 768,
},
{
Name: "snowflake-arctic-embed",
Description: "Snowflake's open embedding family. Solid for short technical text.",
Size: "~330 MB",
Dimensions: 1024,
},
}
Catalog is the curated list of recommended embedding models. Sorted alphabetically by Name so list output is deterministic.
var ErrNoModel = errors.New("no embedding model configured (pass --embedding-model or per-call 'model')")
ErrNoModel is returned by Embed when no model has been configured (neither at server startup nor per-call). Surfaces cleanly in MCP responses as "no embedding model configured".
var ErrNoServer = errors.New("no embedding server configured")
ErrNoServer is returned by Embed when no server URL has been configured. Practically rare since OllamaEmbedder defaults to localhost:11434, but defensive against an empty override.
Functions ¶
func BareName ¶
BareName strips an Ollama tag suffix ("nomic-embed-text:latest" → "nomic-embed-text") so a tagged local model can match a catalog entry. Returns name unchanged when there's no colon.
Example ¶
ExampleBareName strips an Ollama model tag down to its bare name.
package main
import (
"fmt"
"github.com/richardwooding/ollamaembed"
)
func main() {
fmt.Println(ollamaembed.BareName("nomic-embed-text:latest"))
}
Output: nomic-embed-text
func Cosine ¶
Cosine returns the cosine similarity of two vectors, in [-1, 1]. Returns 0 when either vector is empty OR their lengths disagree — defensive against partially-populated cache entries. Vectors don't need to be pre-normalised; the function divides by the L2 norms itself. For pre-normalised vectors, prefer Dot which skips the norm computation.
Types ¶
type CatalogEntry ¶
type CatalogEntry struct {
Name string `json:"name"`
Description string `json:"description"`
Size string `json:"size"`
Dimensions int `json:"dimensions"`
}
CatalogEntry describes one well-known embedding model that file-search-on knows about by name. The catalog is hand-curated and intentionally small — five entries covering the obvious choices. Locally-pulled models that aren't in the catalog still surface in list output; they're just not annotated with description / dims.
Adding a new entry: append to Catalog in alphabetical order (the sanity test enforces ordering). Each entry needs Name (the bare `ollama pull` name, no tag), Description (one user-visible line), Size (human-readable, for display only), and Dimensions (the vector length the model emits — used to flag dimension mismatches before a dimension-validated query lands).
func CatalogLookup ¶
func CatalogLookup(bareName string) *CatalogEntry
CatalogLookup returns the catalog entry for the given bare model name (without ":tag" suffix), or nil when the name isn't in the catalog. Used to annotate ListLocal results.
Example ¶
ExampleCatalogLookup looks up a curated model's metadata (here, the vector dimensions) without contacting Ollama.
package main
import (
"fmt"
"github.com/richardwooding/ollamaembed"
)
func main() {
if e := ollamaembed.CatalogLookup("nomic-embed-text"); e != nil {
fmt.Println(e.Dimensions)
}
}
Output: 768
type Embedder ¶
type Embedder interface {
// Embed returns the embedding vector for the given text. Honours
// ctx; HTTP-backed implementations route ctx through
// http.NewRequestWithContext so cancellation propagates.
Embed(ctx context.Context, text string) ([]float32, error)
// Model returns the model identifier the embedder is configured
// to use. Used in error messages and telemetry.
Model() string
}
Embedder turns text into a fixed-size embedding vector. Implementations are expected to be safe for concurrent Embed calls (the OllamaEmbedder uses an *http.Client which is concurrency-safe).
type LocalModel ¶
type LocalModel struct {
Name string `json:"name"` // e.g. "nomic-embed-text:latest"
Size int64 `json:"size_bytes"` // payload size in bytes
ModifiedAt time.Time `json:"modified_at"` // last-modified timestamp Ollama stamps
Digest string `json:"digest"` // sha256 of the manifest
}
LocalModel is one entry from Ollama's GET /api/tags response.
type OllamaEmbedder ¶
type OllamaEmbedder struct {
// contains filtered or unexported fields
}
OllamaEmbedder calls Ollama's local /api/embed endpoint. Lazy connect — the first Embed call performs the only HTTP round-trip needed to detect Ollama-not-running / model-not-pulled. Server construction does no network I/O.
Wire shape (Ollama 0.1.34+):
POST /api/embed
{"model": "<name>", "input": "..."}
200
{"embeddings": [[float32, ...]], ...}
func NewOllama ¶
func NewOllama(server, model string) *OllamaEmbedder
NewOllama returns an embedder pointed at the given Ollama server. server is the base URL (e.g. "http://localhost:11434"); model is the embedding model name (e.g. "nomic-embed-text"). Empty strings surface as errors on the first Embed call rather than at construction time — callers can pass partial config from MCP startup flags + per-call overrides without preflight checks.
func (*OllamaEmbedder) Embed ¶
Embed POSTs the text to Ollama's /api/embed endpoint and returns the embedding vector. The returned vector is NOT pre-normalised — callers should call Normalize before storing or comparing if they want cosine-similarity semantics.
Errors surface from three places: missing configuration (ErrNoModel / ErrNoServer), transport (the HTTP call itself — server unreachable, timeout, etc.), and protocol (non-200 response, empty embedding array). Each carries a wrapped underlying error so callers can errors.Is past the wrapper.
func (*OllamaEmbedder) ListLocal ¶
func (o *OllamaEmbedder) ListLocal(ctx context.Context) ([]LocalModel, error)
ListLocal returns every model present on the Ollama server (chat AND embedding mixed — Ollama doesn't surface a classification, and we don't try to guess). The caller annotates which ones match the curated catalog via CatalogLookup.
Returns an empty slice (not nil) when Ollama responds with an empty model list. Returns an error wrapped from one of: network failure reaching Ollama, non-200 status, malformed JSON.
func (*OllamaEmbedder) Model ¶
func (o *OllamaEmbedder) Model() string
func (*OllamaEmbedder) Pull ¶
func (o *OllamaEmbedder) Pull(ctx context.Context, name string, onProgress func(PullProgress)) error
Pull downloads the named model via POST /api/pull and streams Ollama's NDJSON progress events to onProgress (which may be nil to discard). Returns nil when Ollama emits the terminating "success" status, or an error from one of:
- missing config (ErrNoServer)
- network failure reaching Ollama
- non-200 HTTP status on the pull request
- an in-stream "error" field on a progress event (Ollama signals mid-pull failures this way, e.g. unknown model)
- ctx cancellation (returns ctx.Err() wrapped)
Cancellation aborts the streaming response read; the partial download is NOT lost because Ollama's pull is layer-based and resumable — a subsequent Pull call against the same name picks up where the cancelled one left off.
Uses a fresh *http.Client with no timeout (separate from the 60s-bounded Embed client) because pulls of large models can take many minutes. ctx is the only deadline.
type PullProgress ¶
type PullProgress struct {
Status string `json:"status"`
Digest string `json:"digest,omitempty"`
Total int64 `json:"total,omitempty"`
Completed int64 `json:"completed,omitempty"`
}
PullProgress is one event from Ollama's POST /api/pull NDJSON stream. Ollama emits multiple events per pull: status lines ("pulling manifest", "downloading sha256:abc..."), layer-progress events with Completed/Total counters, and a terminating "status: success" event. Callers receive every event verbatim; throttling is Ollama's responsibility.