外观
本页尚未翻译
本页内容仍为英文原文,源自官方文档 embedding/index。中文翻译正在分批进行中。
Managing Embeddings
Use the embedding API in LanceDB: registry, functions, schemas, and multi-language SDK support.
Applies to: Embedded ✅, Enterprise ✅
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 LanceDB Embedded as well as LanceDB Enterprise (see below) 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
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.
python
import lancedb
db = lancedb.connect("/tmp/db")
func = get_registry().get("openai").create(name="text-embedding-ada-002")
class Words(LanceModel):
text: str = func.SourceField()
vector: Vector(func.ndims()) = func.VectorField()
table = db.create_table("words", schema=Words, mode="overwrite")
table.add([{"text": "hello world"}, {"text": "goodbye world"}])
query = "greetings"
actual = table.search(query).limit(1).to_pydantic(Words)[0]
print(actual.text)typescript
const db = await lancedb.connect(databaseDir);
const func = getRegistry()
.get("openai")
?.create({ model: "text-embedding-ada-002" }) as EmbeddingFunction;
const wordsSchema = LanceSchema({
text: func.sourceField(new Utf8()),
vector: func.vectorField(),
});
const tbl = await db.createEmptyTable("words", wordsSchema, {
mode: "overwrite",
});
await tbl.add([{ text: "hello world" }, { text: "goodbye world" }]);
const query = "greetings";
const actual = (await tbl.search(query).limit(1).toArray())[0];rust
use std::{iter::once, sync::Arc};
use arrow_array::{record_batch, StringArray};
use arrow_schema::{DataType, Field, Schema};
use futures::StreamExt;
use lancedb::{
connect,
embeddings::{openai::OpenAIEmbeddingFunction, EmbeddingDefinition, EmbeddingFunction},
query::{ExecutableQuery, QueryBase},
Result,
};
#[tokio::main]
async fn main() -> Result<()> {
let db = connect("./mydb").execute().await?;
let api_key = "your-openai-api-key".to_string();
let embedding = Arc::new(OpenAIEmbeddingFunction::new_with_model(
api_key,
"text-embedding-3-large",
)?);
db.embedding_registry().register("openai", embedding.clone())?;
let schema = Arc::new(Schema::new(vec![Field::new("text", DataType::Utf8, false)]));
let table = db
.create_empty_table("mytable", schema)
.add_embedding(EmbeddingDefinition::new("text", "openai", Some("vector")))?
.execute()
.await?;
table
.add(record_batch!(("text", Utf8, ["This is a test.", "Another example."]))?)
.execute()
.await?;
let query = Arc::new(StringArray::from_iter_values(once("test example")));
let query_vector = embedding.compute_query_embeddings(query)?;
let mut results = table.vector_search(query_vector)?.limit(5).execute().await?;
while let Some(batch) = results.next().await {
println!("{:?}", batch?);
}
Ok(())
}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.
python
func = get_registry().get("openai").create(
name="text-embedding-3-small",
max_retries=7,
)typescript
const func = getRegistry().get("openai")!.create({
model: "text-embedding-3-small",
});rust
use std::sync::Arc;
use lancedb::embeddings::openai::OpenAIEmbeddingFunction;
let api_key = "your-openai-api-key".to_string();
let embedding = Arc::new(
OpenAIEmbeddingFunction::new_with_model(api_key, "text-embedding-3-small")
.expect("failed to create OpenAI embedding function"),
);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.
python
registry = get_registry()
registry.set_var("api_key", "sk-...")
func = registry.get("openai").create(api_key="$var:api_key")typescript
const registry = getRegistry();
registry.setVar("api_key", "sk-...");
const func = registry.get("openai")!.create({
apiKey: "$var:api_key",
});For non-sensitive settings such as inference device selection, you can also use a default fallback:
python
import torch
registry = get_registry()
if torch.cuda.is_available():
registry.set_var("device", "cuda")
func = registry.get("huggingface").create(device="$var:device:cpu")typescript
const registry = getRegistry();
registry.setVar("openai_model", "text-embedding-3-large");
const func = registry.get("openai")!.create({
model: "$var:openai_model:text-embedding-3-small",
});Find the full list of arguments for each provider in the integrations section.
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 HuggingFace repository.- Custom endpoints such as OpenAI
base_url, Ollamahost, or watsonxurl, 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
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
LanceDB supports most popular embedding providers.
Text embeddings
| Provider | Model ID | Default Model |
|---|---|---|
| OpenAI | openai | text-embedding-ada-002 |
| Sentence Transformers | sentence-transformers | all-MiniLM-L6-v2 |
| HuggingFace | huggingface | colbert-ir/colbertv2.0 |
| Cohere | cohere | embed-multilingual-v3.0 |
| ... | ... | ... |
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 section.
Embeddings in LanceDB Enterprise
Enterprise
In LanceDB Enterprise, embedding generation during data ingestion is client-side and the resulting vectors are stored in the Enterprise table.
说明
The Enterprise server does not currently generate embeddings from query text on its own. Any automatic query-time embedding happens on the client side.
How string queries are interpreted
When the Python SDK connects to Enterprise, 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()andVectorField()during table creation), then the embeddings are computed in the Python client process. The client uses the same local LanceDB embedding registry used by LanceDB Embedded 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.pythonresult = 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")inautomode is treated as an FTS query instead.pythonresult = 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.
python
# query_vector is assumed to already be generated by your embedding function
actual = table.search(query_vector).limit(1).to_pydantic(Words)[0]
print(actual.text)typescript
// queryVector is assumed to already be generated by your embedding function
const actual = (await tbl.search(queryVector).limit(1).toArray())[0];rust
// query_vector is assumed to already be generated by your embedding function
let mut results = table.vector_search(query_vector)?.limit(5).execute().await?;
while let Some(batch) = results.next().await {
println!("{:?}", batch?);
}Custom Embedding Functions
You can always implement your own embedding function:
- Python/TypeScript: subclass
TextEmbeddingFunction(text) orEmbeddingFunction(multimodal). - Rust: implement the
EmbeddingFunctiontrait.
python
from functools import cached_property
from lancedb.embeddings import TextEmbeddingFunction, register
class MyEmbeddingModel:
def __init__(self, model_name: str):
self.model_name = model_name
def encode(self, texts: list[str]) -> list[list[float]]:
return [[1.0, 2.0, 3.0] for _ in texts]
@register("my-embedder")
class MyTextEmbedder(TextEmbeddingFunction):
model_name: str = "my-model"
def generate_embeddings(self, texts: list[str]) -> list[list[float]]:
# Your embedding logic here
return self._model.encode(texts)
def ndims(self) -> int:
# Return the dimensionality of the embeddings
return len(self.generate_embeddings(["test"])[0])
@cached_property
def _model(self) -> MyEmbeddingModel:
# Initialize your model once
return MyEmbeddingModel(self.model_name)typescript
const db = await lancedb.connect(databaseDir);
@register("my_embedding")
class MyEmbeddingFunction extends EmbeddingFunction<string> {
constructor(optionsRaw = {}) {
super();
const options = this.resolveVariables(optionsRaw);
// Initialize using options
}
ndims() {
return 3;
}
protected getSensitiveKeys(): string[] {
return [];
}
embeddingDataType(): Float {
return new Float32();
}
async computeQueryEmbeddings(_data: string) {
// This is a placeholder for a real embedding function
return [1, 2, 3];
}
async computeSourceEmbeddings(data: string[]) {
// This is a placeholder for a real embedding function
return Array.from({ length: data.length }).fill([
1, 2, 3,
]) as number[][];
}
}
const func = new MyEmbeddingFunction();
const data = [{ text: "pepperoni" }, { text: "pineapple" }];
// Option 1: manually specify the embedding function
const table = await db.createTable("vectors", data, {
embeddingFunction: {
function: func,
sourceColumn: "text",
vectorColumn: "vector",
},
mode: "overwrite",
});
// Option 2: provide the embedding function through a schema
const schema = LanceSchema({
text: func.sourceField(new Utf8()),
vector: func.vectorField(),
});
const table2 = await db.createTable("vectors2", data, {
schema,
mode: "overwrite",
});rust
use std::{borrow::Cow, sync::Arc};
use arrow_array::{Array, FixedSizeListArray, Float32Array};
use arrow_schema::{DataType, Field, Schema};
use lancedb::{
connect,
embeddings::{EmbeddingDefinition, EmbeddingFunction},
Result,
};
#[derive(Debug, Clone)]
struct MyTextEmbedder {
dim: usize,
}
impl EmbeddingFunction for MyTextEmbedder {
fn name(&self) -> &str {
"my-embedder"
}
fn source_type(&self) -> Result<Cow<'_, DataType>> {
Ok(Cow::Owned(DataType::Utf8))
}
fn dest_type(&self) -> Result<Cow<'_, DataType>> {
Ok(Cow::Owned(DataType::new_fixed_size_list(
DataType::Float32,
self.dim as i32,
true,
)))
}
fn compute_source_embeddings(&self, source: Arc<dyn Array>) -> Result<Arc<dyn Array>> {
let values = Arc::new(Float32Array::from(vec![1.0f32; source.len() * self.dim]));
let field = Arc::new(Field::new("item", DataType::Float32, true));
Ok(Arc::new(FixedSizeListArray::new(
field,
self.dim as i32,
values,
None,
)))
}
fn compute_query_embeddings(&self, _input: Arc<dyn Array>) -> Result<Arc<dyn Array>> {
unimplemented!()
}
}
#[tokio::main]
async fn main() -> Result<()> {
let db = connect("./mydb").execute().await?;
db.embedding_registry()
.register("my-embedder", Arc::new(MyTextEmbedder { dim: 3 }))?;
let schema = Arc::new(Schema::new(vec![Field::new("text", DataType::Utf8, false)]));
db.create_empty_table("mytable", schema)
.add_embedding(EmbeddingDefinition::new(
"text",
"my-embedder",
Some("vector"),
))?
.execute()
.await?;
Ok(())
}