Skip to main content

local-embed

Standalone local embedding server — loads one GGUF (llama-cpp) or HF transformer embedding model once and exposes it as an OpenAI-compatible /v1/embeddings HTTP endpoint plus FastMCP tools, on a single port.

Built for slife (its memdb and memfiles plugins both call this service over HTTP, so the model is never loaded twice in one process tree), but it is a fully standalone package — any OpenAI-compatible client can use it.

                POST http://127.0.0.1:8000/v1/embeddings
   slife memdb ─────────────────────────┐
   slife memfiles ──────────────────────┤
   any OpenAI client ───────────────────┤
                                        ▼
                            ┌────────────────────────────┐
                            │  local-embed               │
                            │  /v1/embeddings  (HTTP)    │
                            │  /v1/models      (HTTP)    │
                            │  /health         (HTTP)    │
                            │  /mcp  (FastMCP tools)     │
                            │  ONE loaded model          │
                            └────────────────────────────┘

Install

# GGUF backend (llama-cpp-python)
uv tool install 'local-embed[gguf]'            # or: uv pip install 'local-embed[gguf]'
# Transformer backend (sentence-transformers)
uv tool install 'local-embed[transformer]'     # or: uv pip install 'local-embed[transformer]'

Python 3.13+. The server core only depends on fastmcp + starlette; the heavy model backends are optional extras.

Run

# GGUF model (recommended — offline, no HF download)
local-embed --backend gguf --model bge-m3 --gguf-path /path/to/bge-m3-q4_k_m.gguf --port 8000

# HF transformer model (downloads from HF hub on first load)
local-embed --backend transformer --model BAAI/bge-m3 --port 8000

By default it binds 127.0.0.1:8000 (local only — never exposed to the network).

Transformer models & the env: section

A transformer model is referenced by its HF repo name (BAAI/bge-m3). The HuggingFace hub resolves that name against its cache (default ~/.cache/huggingface), downloading on first load if missing. To keep the server self-contained — point it at an existing local cache, or force offline — put the env vars in local_embed.json5's env: section (injected into this process before any model loads; an existing shell env var wins):

{
  env: {
    HF_HUB_CACHE: "C:\\Users\\me\\HuggingFace\\hub",  // existing cache
    HF_HUB_OFFLINE: "1"                                // never hit the network
  },
  models: {
    "bge-m3-transformer": { backend: "transformer", model: "BAAI/bge-m3", device: "" }
  }
}

Without HF_HUB_CACHE the model resolves against the default cache — a model already downloaded elsewhere would be silently re-fetched.

Use

curl http://127.0.0.1:8000/v1/embeddings \
  -H 'Content-Type: application/json' \
  -d '{"model": "bge-m3", "input": ["hello world", "another text"]}'

Returns the standard OpenAI shape:

{
  "object": "list",
  "data": [
    {"object": "embedding", "index": 0, "embedding": [0.012, ...]},
    {"object": "embedding", "index": 1, "embedding": [...]}
  ],
  "model": "bge-m3",
  "usage": {"prompt_tokens": 3, "total_tokens": 3}
}

Any OpenAI-compatible client works — point base_url at http://127.0.0.1:8000/v1 (e.g. slife's memdb.embedding api backend, or the openai Python package):

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="local")
vecs = client.embeddings.create(model="bge-m3", input=["hello"])

Other endpoints:

  • GET /v1/models — the loaded model + its real embedding dimension.
  • GET /health{status, backend, model, dimension, loaded}.
  • POST /mcp — FastMCP streamable-HTTP endpoint (tools embed_status and embed).

Dimension

The real output width is only known once the model is loaded (n_embd / get_sentence_embedding_dimension). GET /v1/models reports the real dimension, so a client can size its vector table correctly — a wrong width silently drops every mis-sized embedding.

As a slife external plugin

slife treats every embedding model as a remote OpenAI-compatible endpoint — local-embed is just one such endpoint. The model is determined by the plugin's active model: slife discovers it from GET /v1/models (the entry flagged active: true) on load.

Register local-embed as an external plugin (like mcp-plugin) so slife manages the process, and point slife's embedding config at it with the unified OpenAI format:

plugins: {
  external: [
    { name: "local-embed", module: "local_embed.server" }
  ]
},
memdb: {
  embedding: {
    base_url: "http://127.0.0.1:8000/v1",  // stable port from local_embed.json5
    api_key: "local",
  }
}

The plugin binds the stable port from local_embed.json5 (default 8000), so slife's base_url is fixed whether local-embed runs as the plugin or standalone. When the service is unreachable, slife degrades gracefully to keyword search.

CLI

local-embed --help
  • --host / --port — bind address (default 127.0.0.1:8000)
  • --backend gguf|transformer — model backend (default gguf)
  • --model — model name/id (for metadata and dim guessing)
  • --gguf-path — path to the GGUF file (required for backend=gguf)
  • --device cpu|cuda — transformer device (default auto)
  • --log-level — logging level (default INFO)

License

MIT — see the repository root LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

local_embed-0.1.0.tar.gz (28.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

local_embed-0.1.0-py3-none-any.whl (26.1 kB view details)

Uploaded Python 3

File details

Details for the file local_embed-0.1.0.tar.gz.

File metadata

  • Download URL: local_embed-0.1.0.tar.gz
  • Upload date:
  • Size: 28.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.15

File hashes

Hashes for local_embed-0.1.0.tar.gz
Algorithm Hash digest
SHA256 fb4cf301db8bbf377a811b0a448482bf33ec71aa7d343626a86e490fd6ade1e4
MD5 90a0262ea2efea6804d38ff8b1085bc6
BLAKE2b-256 c602ba72bc5356ad1b7bfcf499143113cb5df9703677d7941655dae1a5cae50e

See more details on using hashes here.

File details

Details for the file local_embed-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: local_embed-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 26.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.15

File hashes

Hashes for local_embed-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1d69f8957b2d047cdb6b1e3f6f017f731d9f47b51d82c365485ebb5d1d4ab068
MD5 e91198ce5a409d4d7e97d37fa6ea6774
BLAKE2b-256 6a3876486af1a948c789a0f1e899d8015516437547351480b2baee0376a79369

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page