mirror of
https://github.com/lancedb/lancedb.git
synced 2026-10-22 11:42:27 +00:00
BREAKING CHANGE: `SentenceTransformerEmbeddings.trust_remote_code` now
defaults to `False` (was `True`). When an embedding function is restored
from table metadata, `trust_remote_code=True` and custom endpoints
(OpenAI `base_url`, Ollama `host`/proxy, watsonx `url`) are only honored
if the process approved them; otherwise computing embeddings raises
`UntrustedEmbeddingConfigError`. Functions created in the same process
are approved automatically. To reopen such a table in a new process,
call `get_registry().allow_remote_code(model, revision=...)` or
`get_registry().allow_endpoint(url)` first.
## Summary
Embedding functions are rebuilt from table metadata, which anyone who
can write the table controls. Since sentence-transformers defaulted
`trust_remote_code` to `True`, opening and querying a crafted table ran
arbitrary code from a model repository (GHSA-x8v2-vj73-gfwm). Metadata
could likewise point `base_url` at another server, sending it queries
and the `OPENAI_API_KEY` from the environment.
The table can no longer grant itself trust:
```python
registry = get_registry()
registry.allow_remote_code("nomic-ai/nomic-embed-text-v1.5", revision="<commit>")
registry.allow_endpoint("http://my-ollama-host:11434")
```
Trust is keyed by model name (optionally pinned to a revision), so
approving one model doesn't let a table swap in another. Restored
functions that aren't approved are replaced by a placeholder that raises
on use without constructing the real function (`huggingface` loads its
model in `__init__`), so non-embedding operations on the table keep
working. `sentence-transformers` and `huggingface` gain a `revision`
parameter. Custom embedding functions can opt in by overriding the new
`remote_code_model()` / `endpoints()` hooks.
Not covered: TypeScript (its built-ins have neither remote code nor
configurable endpoints), Bedrock `assumed_role`, and third-party
functions that don't implement the hooks.
## Evidence
`test_embeddings_trust.py` covers restore with and without approval,
revision pinning, swapped model names, endpoint gating, and that the
table stays usable. Opening a table with crafted metadata and searching
it now gives:
```
UntrustedEmbeddingConfigError: Embedding function 'sentence-transformers' was restored from table metadata with settings this process has not approved:
- runs code from model 'attacker/model' at revision None. If you trust it, call get_registry().allow_remote_code('attacker/model', revision=None)
```
## Merge Risk
**Door:** One-way: changes a default and makes previously working
reopen-and-search flows require explicit approval.
**Blast Radius:** public-api (Python embeddings); users of remote-code
models or custom endpoints who reopen tables in a new process.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
274 lines
12 KiB
Plaintext
274 lines
12 KiB
Plaintext
---
|
|
title: "Managing Embeddings"
|
|
sidebarTitle: "Managing Embeddings"
|
|
description: "Use the embedding API in LanceDB -- registry, functions, schemas, and multi-language SDK support."
|
|
icon: "bars"
|
|
availability:
|
|
oss: available
|
|
enterprise: available
|
|
summary: >-
|
|
Embedding functions run in your process, whether the table is local or on a
|
|
deployment, which stores their configuration but never runs them. Python
|
|
and TypeScript also embed query text; in Rust you embed queries yourself.
|
|
---
|
|
|
|
import {
|
|
PyOpenaiEmbeddings,
|
|
PyCreateEmbeddingFunction,
|
|
PyManualQuerySearch,
|
|
PyEmbeddingFunction,
|
|
PyRegisterDevice,
|
|
PyRegisterSecret,
|
|
TsOpenaiEmbeddings,
|
|
TsCreateEmbeddingFunction,
|
|
TsManualQuerySearch,
|
|
TsEmbeddingFunction,
|
|
TsRegisterModelFallback,
|
|
TsRegisterSecret,
|
|
RsOpenaiEmbeddings,
|
|
RsCreateEmbeddingFunction,
|
|
RsManualQuerySearch,
|
|
RsEmbeddingFunction,
|
|
} from '/snippets/embedding.mdx';
|
|
|
|
Modern machine learning models can be trained to convert raw data into embeddings, which are vectors
|
|
of floating point numbers. The position of an embedding in vector space captures the semantics of
|
|
the data, so vectors that are close to each other are considered similar.
|
|
|
|
LanceDB provides an embedding function registry in OSS as well as its Enterprise versions
|
|
([see below](#embeddings-in-lancedb-enterprise))
|
|
that automatically generates vector embeddings during data ingestion. Query text is embedded
|
|
automatically too, in the client, for local and Enterprise tables alike, with SDK-specific query
|
|
ergonomics. The API abstracts
|
|
embedding generation, allowing you to focus on your application logic.
|
|
|
|
## Embedding Registry {#embedding-registry}
|
|
|
|
You can get a supported embedding function from the registry, and then use it in your table schema.
|
|
Once configured, the embedding function will automatically generate embeddings when you insert data
|
|
into the table. Query-time behavior depends on SDK: Python/TypeScript can query with text directly,
|
|
while Rust examples typically compute query embeddings explicitly before vector search.
|
|
|
|
In the Rust examples, replace `your-openai-api-key` with your OpenAI API key. In
|
|
application code, load credentials from environment variables or a secrets manager.
|
|
|
|
<CodeGroup>
|
|
<CodeBlock filename="Python" language="python" icon="python">
|
|
{PyOpenaiEmbeddings}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="TypeScript" language="typescript" icon="square-js">
|
|
{TsOpenaiEmbeddings}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="Rust" language="rust" icon="rust">
|
|
{RsOpenaiEmbeddings}
|
|
</CodeBlock>
|
|
</CodeGroup>
|
|
|
|
### Using an embedding function {#using-an-embedding-function}
|
|
|
|
Create an embedding function before you attach it to table or schema metadata. Python and TypeScript fetch
|
|
provider implementations from the embedding registry, while Rust constructs the provider embedding function
|
|
directly and registers it on the connection before using it in an `EmbeddingDefinition`.
|
|
|
|
<CodeGroup>
|
|
<CodeBlock filename="Python" language="python" icon="python">
|
|
{PyCreateEmbeddingFunction}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="TypeScript" language="typescript" icon="square-js">
|
|
{TsCreateEmbeddingFunction}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="Rust" language="rust" icon="rust">
|
|
{RsCreateEmbeddingFunction}
|
|
</CodeBlock>
|
|
</CodeGroup>
|
|
|
|
Provider configuration is SDK-specific, so copy the option names from the provider page for the SDK you use.
|
|
For example, the OpenAI model is selected with `name` in Python, `model` in TypeScript, and the model argument
|
|
to `OpenAIEmbeddingFunction::new_with_model` in Rust.
|
|
|
|
| Concept | Python | TypeScript | Rust |
|
|
| --- | --- | --- | --- |
|
|
| Model | `name="text-embedding-3-small"` | `{ model: "text-embedding-3-small" }` | `new_with_model(api_key, "text-embedding-3-small")` |
|
|
| Retry count | `max_retries=7` | Provider/client-specific | Provider/client-specific |
|
|
| API key | `api_key="..."`, environment variables, or `$var:` | `apiKey: "..."`, environment variables, or `$var:` | Constructor argument or environment variable |
|
|
| Device | Provider-specific, for example `device="cuda"` | Provider-specific | Provider-specific |
|
|
|
|
When ingesting data with an embedding definition, LanceDB only computes the vector column if that
|
|
column is missing from the incoming batch or present but entirely null. If you provide any non-null
|
|
values in the vector column, LanceDB treats the column as user-supplied and does not backfill the
|
|
remaining rows in that batch.
|
|
|
|
For reusable runtime configuration, the registry also supports `$var:` placeholders in embedding-function config.
|
|
This is useful for provider secrets and environment-specific settings in Python and TypeScript.
|
|
|
|
- Python uses `registry.set_var(...)`.
|
|
- TypeScript uses `registry.setVar(...)`.
|
|
- You can provide a fallback with `$var:name:default`.
|
|
- Sensitive values such as API keys should be passed through registry variables instead of hardcoding them in config.
|
|
|
|
<CodeGroup>
|
|
<CodeBlock filename="Python" language="python" icon="python">
|
|
{PyRegisterSecret}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="TypeScript" language="typescript" icon="square-js">
|
|
{TsRegisterSecret}
|
|
</CodeBlock>
|
|
</CodeGroup>
|
|
|
|
For non-sensitive settings such as inference device selection, you can also use a default fallback:
|
|
|
|
<CodeGroup>
|
|
<CodeBlock filename="Python" language="python" icon="python">
|
|
{PyRegisterDevice}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="TypeScript" language="typescript" icon="square-js">
|
|
{TsRegisterModelFallback}
|
|
</CodeBlock>
|
|
</CodeGroup>
|
|
|
|
Find the full list of arguments for each provider in the [integrations](/integrations/embedding) section.
|
|
|
|
### Trusting remote code and custom endpoints {#trusting-remote-code-and-custom-endpoints}
|
|
|
|
In Python, the embedding definition is stored in the table, so anyone who can write to the table can change it.
|
|
To keep a table from running code or sending data where you didn't intend, two kinds of settings are only honored
|
|
when this process has approved them:
|
|
|
|
- `trust_remote_code=True`, which runs code from the model's Hugging Face repository.
|
|
- Custom endpoints such as OpenAI `base_url`, Ollama `host`, or watsonx `url`, which receive your data and API keys.
|
|
|
|
Embedding functions you create in the current process are approved automatically. When you open a table in a new
|
|
process, approve these settings explicitly before computing embeddings:
|
|
|
|
```python
|
|
from lancedb.embeddings import get_registry
|
|
|
|
registry = get_registry()
|
|
# Pinning a commit hash is recommended, since the code at a branch can change.
|
|
registry.allow_remote_code("nomic-ai/nomic-embed-text-v1.5", revision="<commit-hash>")
|
|
registry.allow_endpoint("http://my-ollama-host:11434")
|
|
```
|
|
|
|
Until then, computing embeddings raises `UntrustedEmbeddingConfigError`. Other operations on the table still work.
|
|
|
|
## Multiple embedding columns {#multiple-embedding-columns}
|
|
|
|
A single table can include more than one embedding definition when you want to store multiple semantic views
|
|
of the same data, or generate embeddings from different source columns. In practice, each embedding definition
|
|
maps one source column to one vector column, and the table schema can contain multiple such pairs.
|
|
|
|
The exact setup differs by SDK, but the underlying pattern is the same: define a distinct source/vector pair
|
|
for each embedding function you want applied during ingest.
|
|
|
|
In TypeScript, automatic query embedding currently uses the first embedding function stored in the
|
|
table metadata. If a table has multiple embedding definitions and you need to query a specific vector
|
|
column, compute the query embedding explicitly and pass the vector to the search builder.
|
|
|
|
## Embedding model providers {#embedding-model-providers}
|
|
|
|
LanceDB supports most popular embedding providers.
|
|
|
|
### Text embeddings {#text-embeddings}
|
|
|
|
| Provider | Model ID | Default Model |
|
|
|----------|----------|---------------|
|
|
| OpenAI | `openai` | `text-embedding-ada-002` |
|
|
| Sentence Transformers | `sentence-transformers` | `all-MiniLM-L6-v2` |
|
|
| Hugging Face | `huggingface` | `colbert-ir/colbertv2.0` |
|
|
| Cohere | `cohere` | `embed-english-v3.0` |
|
|
| ... | ... | ... |
|
|
|
|
### Multimodal embedding {#multimodal-embedding}
|
|
|
|
| Provider | Model ID | Supported Inputs |
|
|
|----------|----------|------------------|
|
|
| OpenCLIP | `open-clip` | Text, Images |
|
|
| ImageBind | `imagebind` | Text, Images, Audio, Video |
|
|
| ... | ... | ... |
|
|
|
|
You can find all supported embedding models in the [integrations](/integrations/embedding) section.
|
|
|
|
## Embeddings in LanceDB Enterprise {#embeddings-in-lancedb-enterprise}
|
|
<Badge color="red">Enterprise</Badge>
|
|
|
|
In LanceDB Enterprise, embedding generation during data ingestion is client-side and the resulting vectors are
|
|
stored on the remote table.
|
|
|
|
<Info>
|
|
The Enterprise server does not currently generate embeddings from query text on its own. Any automatic
|
|
query-time embedding happens on the client side.
|
|
</Info>
|
|
|
|
### How string queries are interpreted {#how-string-queries-are-interpreted}
|
|
|
|
For the Python remote client, `table.search("hello")` can take two different paths:
|
|
|
|
- If the selected vector column has embedding metadata
|
|
(i.e., the table schema stores the source-column, vector-column, and
|
|
embedding-function mapping created from fields like `SourceField()` and
|
|
`VectorField()` during table creation), then the embeddings are computed in the Python client process.
|
|
The client uses the same local LanceDB embedding registry used by OSS tables to
|
|
reconstruct the embedding function from schema metadata, compute the query vector in
|
|
the client process, and send that vector to Enterprise for search.
|
|
```python
|
|
result = table.search("hello").limit(1).to_list()
|
|
# The Python client computes the query embedding locally, then sends a vector search.
|
|
```
|
|
- If the table does not have embedding metadata for that search, `table.search("hello")` in `auto` mode is
|
|
treated as an FTS query instead.
|
|
```python
|
|
result = table.search("hello").limit(5).to_list()
|
|
# In auto mode this is treated as an FTS query, not a vector query.
|
|
```
|
|
|
|
If you want explicit vector or hybrid behavior and the client cannot resolve an embedding function from the
|
|
table metadata, generate the query embedding yourself and pass the vector directly.
|
|
|
|
TypeScript has a similar string-query distinction in `auto` mode: if no embedding providers have been
|
|
registered in the process, `search("text")` falls back to FTS. Once an embedding provider has been
|
|
imported and registered, the client expects table embedding metadata for automatic vector search and
|
|
raises an error if the table has none.
|
|
|
|
The manual query-embedding flow below works across Enterprise SDKs and is an explicit path you can use when you
|
|
want full control over query-time behavior.
|
|
|
|
<CodeGroup>
|
|
<CodeBlock filename="Python" language="python" icon="python">
|
|
{PyManualQuerySearch}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="TypeScript" language="typescript" icon="square-js">
|
|
{TsManualQuerySearch}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="Rust" language="rust" icon="rust">
|
|
{RsManualQuerySearch}
|
|
</CodeBlock>
|
|
</CodeGroup>
|
|
|
|
## Custom Embedding Functions {#custom-embedding-functions}
|
|
|
|
You can always implement your own embedding function:
|
|
- Python/TypeScript: subclass `TextEmbeddingFunction` (text) or `EmbeddingFunction` (multimodal).
|
|
- Rust: implement the `EmbeddingFunction` trait.
|
|
|
|
<CodeGroup>
|
|
<CodeBlock filename="Python" language="python" icon="python">
|
|
{PyEmbeddingFunction}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="TypeScript" language="typescript" icon="square-js">
|
|
{TsEmbeddingFunction}
|
|
</CodeBlock>
|
|
|
|
<CodeBlock filename="Rust" language="rust" icon="rust">
|
|
{RsEmbeddingFunction}
|
|
</CodeBlock>
|
|
</CodeGroup>
|