Embeddings

June 20, 2026 · View on GitHub

Overview

A single, swappable embeddings backend powers every embeddings surface and the vector-store search path. Each endpoint only translates request/response shapes; they all delegate to the same get_embeddings_backend (ovos_persona_server/embeddings.py). Swap the provider in one place (TEXT_EMBEDDINGS_PLUGIN) and it changes everywhere.

EndpointRouterShape
POST /openai/v1/embeddingschat.pyOpenAI
POST /ollama/api/embedollama.pyOllama batch (input)
POST /ollama/api/embeddingsollama.pyOllama legacy (prompt)
POST /cohere/v1/embedcohere.pyCohere
POST /gemini/v1beta/models/{model}:embedContentgemini.pyGemini (single)
POST /gemini/v1beta/models/{model}:batchEmbedContentsgemini.pyGemini (batch)
POST /tgi/embedhuggingface_tgi.pyHF Text-Embeddings-Inference
POST /bedrock/model/{model}/invokeaws_bedrock.pyBedrock Titan / Cohere embed

Anthropic has no first-party embeddings API, so the Anthropic surface intentionally exposes none.

Backend resolution

get_embeddings_backend resolves, in order:

  1. the configured TEXT_EMBEDDINGS_PLUGIN (default ovos-gguf-embeddings-plugin) — point it at a remote service with EMBEDDINGS_URL / EMBEDDINGS_MODEL / EMBEDDINGS_KEY for OpenAI-compatible plugins;
  2. otherwise, a persona solver exposing get_embeddings(text) -> list[float].

If neither is available the endpoints return HTTP 501 Not Implemented:

{"detail": "No embeddings backend available: configure TEXT_EMBEDDINGS_PLUGIN ..."}

See rag.md for how the same backend drives vector-store search.

Request and Response Formats

OpenAI (POST /openai/v1/embeddings)

Request (OpenAIEmbeddingsRequestovos_persona_server/chat.py:345):

{
  "model": "text-embedding-ada-002",
  "input": "text to embed"
}

input may be a string or list of strings.

Response:

{
  "object": "list",
  "data": [
    {"object": "embedding", "embedding": [0.1, 0.2, ...], "index": 0}
  ],
  "model": "text-embedding-ada-002",
  "usage": {"prompt_tokens": 0, "total_tokens": 0}
}

Ollama (POST /ollama/api/embeddings)

Request (OllamaEmbedRequestovos_persona_server/schemas/ollama.py):

{
  "model": "any-string",
  "input": "text to embed"
}

input may be a string or list of strings. If a list, strings are joined with a space before passing to the solver.

Response (OllamaEmbedResponse):

{
  "embeddings": [[0.1, 0.2, ...]]
}

Cohere (POST /cohere/v1/embed)

Request (CohereEmbedRequestovos_persona_server/cohere.py:45):

{
  "texts": ["text one", "text two"],
  "input_type": "search_document"
}

texts must be a non-empty list.

Response:

{
  "id": "<24 chars>",
  "embeddings": [[0.1, 0.2, ...], [0.3, 0.4, ...]],
  "texts": ["text one", "text two"],
  "meta": {"api_version": {"version": "1"}}
}

Gemini (POST /gemini/v1beta/models/{model}:embedContent)

Request: {"content": {"parts": [{"text": "text to embed"}]}} Response: {"embedding": {"values": [0.1, 0.2, ...]}}

The batch variant :batchEmbedContents takes {"requests": [{"content": {...}}, ...]} and returns {"embeddings": [{"values": [...]}, ...]}.

HuggingFace TGI (POST /tgi/embed)

Text-Embeddings-Inference shape — Request: {"inputs": "text" | ["a", "b"]}, Response: a bare JSON array of vectors [[0.1, ...], ...].

AWS Bedrock (POST /bedrock/model/{model}/invoke)

Embedding model ids (amazon.titan-embed-*, cohere.embed-*) route to the backend:

  • Titan — Request {"inputText": "..."}Response {"embedding": [...], "inputTextTokenCount": N}
  • Cohere — Request {"texts": [...]}Response {"embeddings": [[...]], "texts": [...], ...}

Notes

  • Token counts in usage are always zero — the backend does not report them.
  • encoding_format and dimensions from the OpenAI request schema are accepted but not acted on; the vector format is determined entirely by the backend (encoding_format="base64" returns 500 — not yet implemented).