ollamaembed

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 6, 2026 License: MIT Imports: 11 Imported by: 0

README

ollamaembed

Go Reference CI Go Report Card

A small, zero-dependency Go client for text→vector embeddings via Ollama's local HTTP API — plus the vector helpers and curated model catalog that go with it. The building blocks for semantic / RAG search in Go, with no SDK, no CGo, no bundled model.

  • Embedder interface + OllamaEmbedderEmbed(ctx, text) ([]float32, error), context-aware, with model list / pull management built in.
  • Vector mathCosine, Dot, Normalize (operate on []float32).
  • Curated Catalog — recommended embedding models with dimensions/size, plus CatalogLookup / BareName helpers.
  • stdlib only (net/http, encoding/json, math), go 1.21+.
go get github.com/richardwooding/ollamaembed

Named ollamaembed (not embed) so it never collides with the standard library's embed package.

Embed text

emb := ollamaembed.NewOllama("http://localhost:11434", "nomic-embed-text")

vec, err := emb.Embed(ctx, "the quick brown fox")
if err != nil {
    log.Fatal(err) // clear errors when Ollama is down or the model isn't pulled
}
ollamaembed.Normalize(vec) // unit vector → cosine == dot

Set up Ollama once: install from ollama.com, then ollama pull nomic-embed-text (or mxbai-embed-large, all-minilm, …).

Rank by similarity

query := mustEmbed(emb, ctx, "seafaring adventure")
for _, doc := range docs {
    v := mustEmbed(emb, ctx, doc.Text)
    ollamaembed.Normalize(v)
    doc.Score = ollamaembed.Cosine(query, v) // higher = more similar
}

(For sub-linear top-K at scale, feed the vectors into your ANN index of choice; this package is the embedding + vector-math layer.)

Discover & manage models

// What's installed locally?
models, _ := emb.ListLocal(ctx)

// Curated recommendations (no network):
for _, m := range ollamaembed.Catalog {
    fmt.Printf("%s — %dd, %s\n", m.Name, m.Dimensions, m.Description)
}

// Pull one, streaming progress:
err := emb.Pull(ctx, "nomic-embed-text", func(p ollamaembed.PullProgress) {
    fmt.Printf("\r%s %d/%d", p.Status, p.Completed, p.Total)
})

License

MIT — see LICENSE.


Extracted from file-search-on, where it powers the similarity ranking and the search_semantic tool.

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

Examples

Constants

This section is empty.

Variables

View Source
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.

View Source
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".

View Source
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

func BareName(name string) string

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

func Cosine(a, b []float32) float64

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.

func Dot

func Dot(a, b []float32) float64

Dot returns the dot product of two vectors. Used as a fast path when both vectors are pre-normalised (then dot == cosine). Returns 0 on length mismatch.

func Normalize

func Normalize(v []float32)

Normalize L2-normalises v in place. After this call, Dot(v, w) for any normalised w equals the cosine similarity. No-op when v has zero norm.

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

func (o *OllamaEmbedder) Embed(ctx context.Context, text string) ([]float32, error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL