langchain-typesense is a LangChain VectorStore implementation for Typesense.
Features:
- Sync & Async: Supports both synchronous and asynchronous methods.
- Search & Retrieval: Vector similarity search, hybrid keyword/vector search, MMR (Maximal Marginal Relevance), and relevance scoring.
- Filtering: Metadata filtering via Typesense filter expressions.
- Document Management: Batch writes, ID lookups, and deletions.
Requirements:
- Python 3.10+
langchain-core1.xtypesense2.x (Typesense Python client)- Typesense Server 30.2
- A LangChain
Embeddingsprovider.
pip install -U langchain-typesense langchain-openailangchain-openai is used here as an example embedding provider; replace it with your provider of choice.
import typesense
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
from langchain_typesense import TypesenseVectorStore
client = typesense.Client(
{
"nodes": ["http://localhost:8108"],
"api_key": "xyz",
"connection_timeout_seconds": 2,
}
)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
store = TypesenseVectorStore(
client=client,
embedding=embeddings,
collection_name="my-documents",
)
store.add_documents(
[
Document(page_content="Typesense is fast", metadata={"source": "docs"}),
Document(page_content="LangChain composes applications", metadata={"source": "blog"}),
],
ids=["typesense", "langchain"],
)
matches = store.similarity_search(
"Which search engine is fast?",
k=2,
filter={"source": "docs"},
)
print(matches)
store.close()By default, the first non-empty write creates the collection or validates its schema. Later writes from the same store instance reuse the validation result.
Pass clients directly when your application owns their lifecycle:
store = TypesenseVectorStore(
client=client, # typesense.Client | None
async_client=async_client, # typesense.AsyncClient | None
embedding=embeddings,
collection_name="articles",
text_key="body",
vector_key="embedding",
metadata_key="attributes",
index_metadata=True,
vec_dist="cosine", # "cosine" or "ip"
)At least one client is required. Sync methods require a sync client. Async methods use the async client when supplied and otherwise run the sync method in a worker thread.
| Parameter | Default | What it does |
|---|---|---|
client |
required unless async-only | Supplies the synchronous Typesense connection. Sync store methods require it. |
async_client |
None |
Supplies a native async connection. Without it, async methods run sync methods in a worker. |
embedding |
required | Embeds document text for writes and query text for searches. |
collection_name |
"langchain-typesense" |
Selects the Typesense collection used by this store. |
text_key |
"text" |
Names the field that stores Document.page_content. |
vector_key |
"vec" |
Names the float[] field that stores embeddings. |
metadata_key |
"metadata" |
Names the object field that stores Document.metadata. |
index_metadata |
True |
Makes metadata filterable in collections created by the store. False keeps it return-only. |
vec_dist |
"cosine" |
Chooses cosine distance or inner-product distance ("ip") for the vector field. |
Field names must be non-empty, distinct, and different from id.
from_client_params creates the requested connection pools. api_key falls back to
TYPESENSE_API_KEY when omitted.
store = TypesenseVectorStore.from_client_params(
embedding=embeddings,
typesense_url="https://example.a1.typesense.net:443",
api_key="your-typesense-api-key",
client_mode="both", # "sync", "async", or "both"
connection_timeout_seconds=2.0,
collection_name="articles",
text_key="body",
vector_key="embedding",
metadata_key="attributes",
index_metadata=True,
vec_dist="cosine",
)The URL must be absolute HTTP(S) and cannot contain credentials, a path, query, or fragment. Its connection-specific parameters are:
| Parameter | Default | What it does |
|---|---|---|
typesense_url |
required | Sets the single Typesense node, including its scheme, host, and optional port. |
api_key |
TYPESENSE_API_KEY env |
Authenticates requests. An explicit value takes precedence over the environment. |
client_mode |
"sync" |
Creates a "sync", "async", or "both" client pool. |
connection_timeout_seconds |
2.0 |
Sets the request timeout; it must be finite and greater than zero. |
When the URL omits a port, the URL scheme supplies its standard port: HTTP uses 80
and HTTPS uses 443. Port 8108 is Typesense's common direct-server port, not HTTP's
standard port, so it must be written explicitly: http://example.com:8108. Explicit
ports are preserved. For example, https://example.a1.typesense.net:443 is valid and
targets the same port as https://example.a1.typesense.net.
import typesense
config = {
"nodes": ["http://localhost:8108"],
"api_key": "xyz",
"connection_timeout_seconds": 2,
}
store = TypesenseVectorStore(
client=None,
async_client=typesense.AsyncClient(config),
embedding=embeddings,
collection_name="articles",
)
await store.aadd_texts(["alpha", "beta"], ids=["alpha", "beta"])
matches = await store.asimilarity_search("alpha", k=2)
await store.aclose()close() closes the sync pool. aclose() closes the async pool and then the sync pool,
if present. Do not reuse clients after the store closes them.
ids = store.add_documents(
[
Document(id="doc-1", page_content="First", metadata={"group": "a"}),
Document(page_content="Second", metadata={"group": "b"}),
],
batch_size=100,
)
ids = store.add_texts(
["Third", "Fourth"],
metadatas=[{"group": "a"}, {"group": "b"}],
ids=["doc-3", "doc-4"],
batch_size=100,
)ids and metadatas must match the input length. Missing IDs come from Document.id or
are generated as UUIDs. IDs may contain only URL-unreserved characters. Reusing an ID
upserts the document. For multi-request imports, explicit stable IDs make retries easier
to reconcile.
By default, the first import retrieves or creates the collection and validates its schema. The validated dimension is cached, so later writes through the same store and client path skip that request. The store also recreates a collection deleted between managed writes.
For a caller-managed collection, skip creation and validation—and their first-write API
request—with create_collection_if_not_exists=False:
store.add_documents(
documents,
create_collection_if_not_exists=False,
)In this mode, the write fails normally if the collection is missing or incompatible. The flag is also available on sync and async text helpers and factories.
The async methods are aadd_documents and aadd_texts. One-step factories are also
available:
store = TypesenseVectorStore.from_documents(documents, embeddings, client=client)
store = TypesenseVectorStore.from_texts(texts, embeddings, client=client)
store = await TypesenseVectorStore.afrom_documents(
documents,
embeddings,
client=None,
async_client=async_client,
)
store = await TypesenseVectorStore.afrom_texts(
texts,
embeddings,
client=None,
async_client=async_client,
)Factories accept the same schema parameters as the constructor, plus ids, metadatas,
and batch_size.
documents = store.similarity_search(
"release notes",
k=8,
filter={"source": "docs"},
distance_threshold=0.4,
ef=100,
flat_search_cutoff=20,
search_parameters={
"enable_lazy_filter": True,
"search_cutoff_ms": 500,
"use_cache": True,
"cache_ttl": 60,
},
)
documents_and_distances = store.similarity_search_with_score("release notes", k=4)
query_vector = embeddings.embed_query("release notes")
documents = store.similarity_search_by_vector(query_vector, k=4)| Parameter | Default | What it does |
|---|---|---|
k |
4 |
Caps the number of returned documents. 0 skips embedding and the Typesense request. |
filter |
None |
Keeps only documents matching a metadata dictionary or raw Typesense filter expression. |
search_parameters |
None |
Passes the safe Typesense options represented by TypesenseSearchParameters. |
distance_threshold |
None |
Drops hits above this raw vector distance. Smaller distances are closer. |
ef |
None |
Expands the HNSW search. Larger values can improve recall at the cost of more server work. |
flat_search_cutoff |
None |
Switches to exact search when a filter leaves fewer than this many candidates. |
Async equivalents are asimilarity_search, asimilarity_search_with_score, and
asimilarity_search_by_vector.
Dictionary filters target the configured metadata object and combine entries with &&:
store.similarity_search(
"release notes",
filter={"source": "docs", "year": 2026, "published": True},
)Use a raw string for ranges, joins, or other Typesense syntax:
store.similarity_search(
"release notes",
filter="metadata.year:>=2024 && metadata.source:=docs",
)Raw strings are passed through unchanged. Do not interpolate untrusted values. Dictionary filters require indexed metadata.
The exported TypesenseSearchParameters type shows which Typesense options this adapter
accepts. See the official Typesense Search API reference
for their meanings, accepted values, and server defaults.
The store manages q, query_by, vector_query, per_page, page, filter_by,
include_fields, and exclude_fields; they cannot be overridden. Options that change
pagination, sorting, grouping, or the hit shape are rejected. Curation options may reorder
results.
Hybrid search combines Typesense's keyword rank with the supplied embedding rank. Use
alpha to choose their balance:
documents = store.hybrid_search(
"wireless keyboard",
k=8,
alpha=0.4, # 40% vector, 60% keyword
query_by=["text", "metadata.title"],
filter={"published": True},
distance_threshold=0.5,
ef=100,
flat_search_cutoff=20,
search_parameters={
"query_by_weights": [2, 1],
"drop_tokens_threshold": 0,
"num_typos": 1,
"rerank_hybrid_matches": True,
"enable_lazy_filter": True,
},
)
documents_and_scores = store.hybrid_search_with_score(
"wireless keyboard",
k=4,
alpha=0.3,
)
documents = await store.ahybrid_search("wireless keyboard", k=4)
documents_and_scores = await store.ahybrid_search_with_score("wireless keyboard", k=4)| Parameter | Default | What it does |
|---|---|---|
k |
4 |
Caps the number of returned documents. 0 skips embedding and the Typesense request. |
alpha |
0.3 |
Sets the vector rank weight from 0 (keyword) to 1 (vector). |
query_by |
configured text field | Selects one or more indexed string fields for keyword matching. |
filter |
None |
Keeps only documents matching a metadata dictionary or raw Typesense filter expression. |
search_parameters |
None |
Passes safe keyword, filtering, execution, and fusion options to Typesense. |
distance_threshold |
None |
Drops vector candidates above this raw distance. |
ef |
None |
Expands the HNSW vector search; larger values trade more server work for possible recall. |
flat_search_cutoff |
None |
Uses exact vector search when a filter leaves fewer than this many candidates. |
query_by defaults to text_key; the adapter adds vector_key separately through
vector_query, so query_by must not include the vector field. query_by_weights must
line up with the fields in query_by.
Hybrid search_parameters accepts every safe option from
TypesenseSearchParameters, plus the keyword and fusion options represented by the
exported TypesenseHybridSearchParameters type.
See the official Typesense hybrid-search documentation
for rank-fusion behavior, keyword options, server defaults, reranking tradeoffs, and
Typesense's performance guidance for multiword queries.
hybrid_search_with_score returns Typesense's rank_fusion_score, where larger is
better. It is rank-dependent—not a raw vector distance or normalized relevance score—so
do not compare scores from separate queries.
similarity_search_with_score returns raw Typesense distance, where lower is better.
For cosine vectors, LangChain's inherited method converts distance to relevance:
documents_and_relevance = store.similarity_search_with_relevance_scores(
"release notes",
k=4,
score_threshold=0.75,
)The conversion is clamp(1 - distance / 2, 0, 1). Use score_threshold for normalized
relevance and distance_threshold for raw Typesense distance. Inner-product collections
support raw-distance search but not bounded relevance scores or local MMR.
documents = store.max_marginal_relevance_search(
"release notes",
k=4,
fetch_k=20,
lambda_mult=0.5,
filter={"source": "docs"},
distance_threshold=0.5,
ef=100,
flat_search_cutoff=20,
search_parameters={"enable_lazy_filter": True},
)fetch_k candidates are fetched and up to k are selected locally. lambda_mult=1
favors query similarity; 0 favors diversity. MMR requires vec_dist="cosine".
By-vector and async variants are max_marginal_relevance_search_by_vector,
amax_marginal_relevance_search, and amax_marginal_relevance_search_by_vector. They
accept the same filtering and search-tuning parameters.
documents = store.get_by_ids(["doc-1", "missing", "doc-2"])
store.delete(ids=["doc-1", "doc-2"])
store.create_collection(num_dim=1536)
deleted = store.delete_collection() # False if already absent
store.delete(delete_all_documents=True) # keep schema, remove all documentsget_by_ids de-duplicates IDs, ignores missing documents, and may return fewer results
than requested. delete(ids=[]) is a no-op. Bare delete() is rejected to prevent an
accidental truncate. Async equivalents are aget_by_ids, adelete,
acreate_collection, and adelete_collection.
retriever = store.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"k": 8, "score_threshold": 0.75},
)
documents = retriever.invoke("release notes")Supported LangChain search types are similarity, mmr, and
similarity_score_threshold. Hybrid search is exposed through the explicit methods above,
not through as_retriever(search_type="hybrid"). The inherited search and asearch
methods remain available for LangChain's standard search types.
New collections contain a string text field, a float[] vector field, and an optional
nested metadata object. index_metadata=False stores metadata without indexing it.
Existing collections must match the configured field types, vector dimension, distance,
metadata indexing requirement, and nested-field setting.
from langchain_typesense import (
TypesenseCollectionError,
TypesenseHybridSearchParameters,
TypesenseImportError,
TypesenseSearchParameters,
TypesenseVectorStoreError,
)TypesenseCollectionError: incompatible schema or malformed stored document.TypesenseImportError: one or more bulk-import records failed; inspect.failures.TypesenseVectorStoreError: malformed or unexpected Typesense response.
Bulk import may partially succeed. Successful records are not rolled back. Typesense client, network, authentication, and embedding exceptions retain their original types.
# Before
from langchain_community.vectorstores import Typesense
# After
from langchain_typesense import TypesenseVectorStoreTypesense remains an alias.
- Rename
typesense_clienttoclientandtypesense_collection_nametocollection_name - Use
from_client_paramsfortypesense_urlandapi_key.
This package uses nested metadata, preserves Document.id, validates existing schemas,
rejects unknown options, and reports per-record import failures. Reindex old collections
that do not match the managed schema.
Install uv, then install all dependency groups:
uv sync --all-groupsRun formatting, linting, type checks, tests, and builds:
make format
make lint
make test
make buildRun integration tests against the repository's Typesense server:
# Typesense listens on http://localhost:8108; the development key is xyz.
docker compose up -d
make integration_test
docker compose downUnit tests block network access. Integration tests use unique collection names and clean them up.