Keyword search
Keyword search, also called "BM25 (Best match 25)" or "sparse vector" search, returns objects that have the highest BM25F scores.
Basic BM25 search
Section titled “Basic BM25 search”To use BM25 keyword search, define a search string.
jeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="food", limit=3)for o in response.objects: print(o.properties)const jeopardy = client.collections.use('JeopardyQuestion');ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("food")
limit := int(3)
result, err := client.GraphQL().Get().
WithClassName(className).
WithFields(
graphql.Field{Name: "question"},
graphql.Field{Name: "answer"},
).
WithBM25(query).
WithLimit(limit).
Do(ctx)CollectionHandle<Map<String, Object>> jeopardy = client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25( "food", q -> q.limit(3));for (var o : response.objects()) { System.out.println(o.properties());}var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25( "food", limit: 3);foreach (var o in response.Objects){ Console.WriteLine(JsonSerializer.Serialize(o.Properties));}{ Get { JeopardyQuestion( limit: 3 bm25: { query: "food" } ) { question answer } }}Example response
The response is like this:
{
"data": {
"Get": {
"JeopardyQuestion": [
{
"answer": "food stores (supermarkets)",
"question": "This type of retail store sells more shampoo & makeup than any other"
},
{
"answer": "cake",
"question": "Devil's food & angel food are types of this dessert"
},
{
"answer": "a closer grocer",
"question": "A nearer food merchant"
}
]
}
}
}Search operators
Section titled “Search operators”Search operators define how many of the query tokens must match, and whether they must all match within a single searched property. The options are or (default), and, and and_cross.
With the or operator, the search returns objects that contain at least minimumOrTokensMatch of the tokens in the search string.
from weaviate.classes.query import BM25Operatorjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="African desert wind", operator=BM25Operator.or_(minimum_match=1), limit=3,)for o in response.objects: print(o.properties)var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25( query: "Australian mammal cute", searchOperator: new BM25Operator.Or(MinimumMatch: 1), limit: 3);foreach (var o in response.Objects){ Console.WriteLine(JsonSerializer.Serialize(o.Properties));}{ Get { JeopardyQuestion( limit: 3 bm25: { query: "Australian mammal cute" searchOperator: { operator: Or, minimumOrTokensMatch: 2 } } ) { question answer } }}With the and operator, the search returns objects where all tokens in the search string appear together within a single searched property.
from weaviate.classes.query import BM25Operatorjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="African desert wind", operator=BM25Operator.and_(), # Each result must include all tokens (e.g. "african", "desert", "wind") limit=3,)for o in response.objects: print(o.properties)var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25( query: "Australian mammal cute", searchOperator: new BM25Operator.And(), // Each result must include all tokens (e.g. "australian", "mammal", "cute") limit: 3);foreach (var o in response.Objects){ Console.WriteLine(JsonSerializer.Serialize(o.Properties));}{ Get { JeopardyQuestion( limit: 3 bm25: { query: "Australian mammal cute" searchOperator: { operator: And, } } ) { question answer } }}and_cross
Section titled “and_cross”With the and_cross operator, every token in the search string must be matched by at least one of the searched properties, but the tokens do not all have to occur in the same property. An object whose title matches one token and whose body matches the rest is a match for and_cross, and is not a match for and.
Because it relaxes the single-property requirement, and_cross returns every object that and returns, and usually more.
The examples below use Python and GraphQL: and_cross is available in the Python client and, from version 1.2.0, the C# client, but not yet in the TypeScript, Go, or Java clients. Both examples restrict the search to question and answer, because the JeopardyQuestion collection mixes tokenizations across its properties.
from weaviate.classes.query import BM25Operatorjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="African desert wind", # and_cross errors unless every searched property shares tokenization settings query_properties=["question", "answer"], # Each token must be matched by at least one searched property, # but not necessarily all by the same property operator=BM25Operator.and_cross(), limit=3,)for o in response.objects: print(o.properties){ Get { JeopardyQuestion( limit: 3 bm25: { query: "Australian mammal cute" properties: ["question", "answer"] searchOperator: { operator: AndCross, } } ) { question answer } }}Retrieve BM25F scores
Section titled “Retrieve BM25F scores”You can retrieve the BM25F score values for each returned object.
from weaviate.classes.query import MetadataQueryjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="food", return_metadata=MetadataQuery(score=True), limit=3)for o in response.objects: print(o.properties) print(o.metadata.score)const jeopardy = client.collections.use('JeopardyQuestion');ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("food")
limit := int(3)
result, err := client.GraphQL().Get().
WithClassName(className).
WithFields(
graphql.Field{Name: "question"},
graphql.Field{Name: "answer"},
graphql.Field{
Name: "_additional",
Fields: []graphql.Field{
{Name: "score"},
},
},
).
WithBM25(query).
WithLimit(limit).
Do(ctx)CollectionHandle<Map<String, Object>> jeopardy = client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("food", q -> q.returnMetadata(Metadata.SCORE).limit(3));for (var o : response.objects()) { System.out.println(o.properties()); System.out.println(o.queryMetadata().score());}var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25( "food", returnMetadata: MetadataOptions.Score, limit: 3);foreach (var o in response.Objects){ Console.WriteLine(JsonSerializer.Serialize(o.Properties)); Console.WriteLine(o.Metadata.Score);}{ Get { JeopardyQuestion( limit: 3 bm25: { query: "food" } ) { question answer _additional { score } } }}Example response
The response is like this:
{
"data": {
"Get": {
"JeopardyQuestion": [
{
"_additional": {
"score": "3.0140665"
},
"answer": "food stores (supermarkets)",
"question": "This type of retail store sells more shampoo & makeup than any other"
},
{
"_additional": {
"score": "2.8725255"
},
"answer": "cake",
"question": "Devil's food & angel food are types of this dessert"
},
{
"_additional": {
"score": "2.7672548"
},
"answer": "a closer grocer",
"question": "A nearer food merchant"
}
]
}
}
}Search on selected properties only
Section titled “Search on selected properties only”A keyword search can be directed to only search a subset of object properties. In this example, the BM25 search only uses the question property to produce the BM25F score.
from weaviate.classes.query import MetadataQueryjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="safety", query_properties=["question"], return_metadata=MetadataQuery(score=True), limit=3)for o in response.objects: print(o.properties) print(o.metadata.score)const jeopardy = client.collections.use('JeopardyQuestion');ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("safety").WithProperties("question")
limit := int(3)
result, err := client.GraphQL().Get().
WithClassName(className).
WithFields(
graphql.Field{Name: "question"},
graphql.Field{
Name: "_additional",
Fields: []graphql.Field{
{Name: "score"},
},
},
).
WithBM25(query).
WithLimit(limit).
Do(ctx)CollectionHandle<Map<String, Object>> jeopardy = client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("safety", q -> q .queryProperties("question") .returnMetadata(Metadata.SCORE) .limit(3));for (var o : response.objects()) { System.out.println(o.properties()); System.out.println(o.queryMetadata().score());}var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25( "safety", searchFields: ["question"], returnMetadata: MetadataOptions.Score, limit: 3);foreach (var o in response.Objects){ Console.WriteLine(JsonSerializer.Serialize(o.Properties)); Console.WriteLine(o.Metadata.Score);}{ Get { JeopardyQuestion( limit: 3 bm25: { query: "food" properties: ["question"] } ) { question answer _additional { score } } }}Example response
The response is like this:
{
"data": {
"Get": {
"JeopardyQuestion": [
{
"_additional": {
"score": "3.7079012"
},
"answer": "cake",
"question": "Devil's food & angel food are types of this dessert"
},
{
"_additional": {
"score": "3.4311616"
},
"answer": "a closer grocer",
"question": "A nearer food merchant"
},
{
"_additional": {
"score": "2.8312314"
},
"answer": "honey",
"question": "The primary source of this food is the Apis mellifera"
}
]
}
}
}Use weights to boost properties
Section titled “Use weights to boost properties”You can weight how much each property affects the overall BM25F score. This example boosts the question property by a factor of 2 while the answer property remains static.
jeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="food", query_properties=["question^2", "answer"], limit=3)for o in response.objects: print(o.properties)const jeopardy = client.collections.use('JeopardyQuestion');ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("food").WithProperties("question^2", "answer")
limit := int(3)
result, err := client.GraphQL().Get().
WithClassName(className).
WithFields(
graphql.Field{Name: "question"},
graphql.Field{Name: "answer"},
).
WithBM25(query).
WithLimit(limit).
Do(ctx)CollectionHandle<Map<String, Object>> jeopardy = client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("food", q -> q .queryProperties("question^2", "answer") .limit(3));for (var o : response.objects()) { System.out.println(o.properties());}var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25( "food", searchFields: ["question^2", "answer"], limit: 3);foreach (var o in response.Objects){ Console.WriteLine(JsonSerializer.Serialize(o.Properties));}{ Get { JeopardyQuestion( limit: 3 bm25: { query: "food" properties: ["question^2", "answer"] } ) { question answer _additional { score } } }}Example response
The response is like this:
{
"data": {
"Get": {
"JeopardyQuestion": [
{
"_additional": {
"score": "4.0038033"
},
"answer": "cake",
"question": "Devil's food & angel food are types of this dessert"
},
{
"_additional": {
"score": "3.8706005"
},
"answer": "a closer grocer",
"question": "A nearer food merchant"
},
{
"_additional": {
"score": "3.2457707"
},
"answer": "food stores (supermarkets)",
"question": "This type of retail store sells more shampoo & makeup than any other"
}
]
}
}
}Set tokenization
Section titled “Set tokenization”The BM25 query string is tokenized before it is used to search for objects using the inverted index.
You must specify the tokenization method in the collection definition for each property.
from weaviate.classes.config import Configure, Property, DataType, Tokenizationclient.collections.create( "Article", vector_config=Configure.Vectors.text2vec_cohere(), properties=[ Property( name="title", data_type=DataType.TEXT, vectorize_property_name=True, # Use "title" as part of the value to vectorize tokenization=Tokenization.LOWERCASE, # Use "lowercase" tokenization description="The title of the article.", # Optional description ), Property( name="body", data_type=DataType.TEXT, skip_vectorization=True, # Don't vectorize this property tokenization=Tokenization.WHITESPACE, # Use "whitespace" tokenization ), ],)import { vectors, dataType, tokenization } from 'weaviate-client';client.collections.create("Article",
col -> col.properties(
Property.text("title",
p -> p.description("The title of the article.")
.tokenization(Tokenization.LOWERCASE)
.vectorizePropertyName(false)),
Property.text("body", p -> p.skipVectorization(true)
.tokenization(Tokenization.WHITESPACE))));Accent folding
Section titled “Accent folding”Text properties can enable accent folding via textAnalyzer.asciiFold to normalize accented characters to their ASCII equivalents during both indexing and querying. For example, "Café Crème" becomes searchable as "cafe creme" and vice versa. This improves BM25 recall for multilingual content without requiring users to type exact accented characters.
See Inverted index: Accent folding for configuration details.
Stopwords
Section titled “Stopwords”By default, Weaviate filters out common English stopwords (like "a", "the", "is") from BM25 scoring. You can customize this behavior:
- Custom presets: Define named stopword lists per collection via
invertedIndexConfig.stopwordPresets, useful for non-English languages or domain-specific terms. - Per-property overrides: Assign different stopword presets to individual properties via
textAnalyzer.stopwordPreset, useful for multilingual collections where each property contains text in a different language.
Stopwords are still indexed and only filtered at query time, so changing the configuration does not require reindexing.
See Inverted index: Custom stopword presets and the stopwords configuration reference for details.
limit & offset
Section titled “limit & offset”Use limit to set a fixed maximum number of objects to return.
Optionally, use offset to paginate the results.
jeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="safety", limit=3, offset=1)for o in response.objects: print(o.properties)const jeopardy = client.collections.use('JeopardyQuestion');ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("safety")
limit := int(3)
offset := int(1)
result, err := client.GraphQL().Get().
WithClassName(className).
WithFields(
graphql.Field{Name: "question"},
graphql.Field{Name: "answer"},
).
WithBM25(query).
WithLimit(limit).
WithOffset(offset).
Do(ctx)CollectionHandle<Map<String, Object>> jeopardy = client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("safety", q -> q .limit(3) .offset(1));for (var o : response.objects()) { System.out.println(o.properties());}var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25( "safety", limit: 3, offset: 1);foreach (var o in response.Objects){ Console.WriteLine(JsonSerializer.Serialize(o.Properties));}{ Get { JeopardyQuestion( bm25: { query: "safety" } limit: 3 ) { question answer _additional { score } } }}Limit result groups
Section titled “Limit result groups”To limit results to groups of similar distances to the query, use the autocut filter to set the number of groups to return.
jeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="safety", auto_limit=1)for o in response.objects: print(o.properties)const jeopardy = client.collections.use('JeopardyQuestion');ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("safety")
autoLimit := int(1)
result, err := client.GraphQL().Get().
WithClassName(className).
WithFields(
graphql.Field{Name: "question"},
graphql.Field{Name: "answer"},
).
WithBM25(query).
WithAutocut(autoLimit).
Do(ctx)var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25( "safety", autoLimit: 1);foreach (var o in response.Objects){ Console.WriteLine(JsonSerializer.Serialize(o.Properties));}{ Get { JeopardyQuestion( bm25: { query: "safety" } autocut: 1 ) { question answer _additional { score } } }}Example response
The response is like this:
{
"data": {
"Get": {
"JeopardyQuestion": [
{
"_additional": {
"score": "2.6768136"
},
"answer": "OSHA (Occupational Safety and Health Administration)",
"question": "The government admin. was created in 1971 to ensure occupational health & safety standards"
}
]
}
}
}Group results
Section titled “Group results”Define criteria to group search results.
from weaviate.classes.query import GroupBy
jeopardy = client.collections.use("JeopardyQuestion")
# Grouping parameters
group_by = GroupBy(
prop="round", # group by this property
objects_per_group=3, # maximum objects per group
number_of_groups=2, # maximum number of groups
)
# Query
response = jeopardy.query.bm25(
query="California",
group_by=group_by
)
for grp_name, grp_content in response.groups.items():
print(grp_name, grp_content.objects)const jeopardy = client.collections.use('JeopardyQuestion');CollectionHandle<Map<String, Object>> jeopardy =
client.collections.use("JeopardyQuestion");
var response = jeopardy.query.bm25("California", q -> q, // No query options needed for this example
GroupBy.property("round", // group by this property
2, // maximum number of groups
3 // maximum objects per group
));
response.groups().forEach((groupName, group) -> {
System.out.println(group.name() + " " + group.objects());
});var jeopardy = client.Collections.Use("JeopardyQuestion");
var response = await jeopardy.Query.BM25(
"California",
groupBy: new GroupByRequest("round") // group by this property
{
NumberOfGroups = 2, // maximum number of groups
ObjectsPerGroup = 3, // maximum objects per group
}
);
foreach (var group in response.Groups.Values)
{
Console.WriteLine($"{group.Name} {JsonSerializer.Serialize(group.Objects)}");
}Example response
The response is like this:
'Jeopardy!'
'Double Jeopardy!'Filter results
Section titled “Filter results”For more specific results, use a filter to narrow your search.
from weaviate.classes.query import Filterjeopardy = client.collections.use("JeopardyQuestion")response = jeopardy.query.bm25( query="food", filters=Filter.by_property("round").equal("Double Jeopardy!"), return_properties=["answer", "question", "round"], # return these properties limit=3)for o in response.objects: print(o.properties)const jeopardy = client.collections.use('JeopardyQuestion');ctx := context.Background()
className := "JeopardyQuestion"
query := (&graphql.BM25ArgumentBuilder{}).WithQuery("food")
limit := int(3)
filter := filters.Where().
WithPath([]string{"round"}).
WithOperator(filters.Equal).
WithValueString("Double Jeopardy!")
result, err := client.GraphQL().Get().
WithClassName(className).
WithFields(
graphql.Field{Name: "answer"},
graphql.Field{Name: "question"},
graphql.Field{Name: "round"},
).
WithBM25(query).
WithWhere(filter).
WithLimit(limit).
Do(ctx)CollectionHandle<Map<String, Object>> jeopardy = client.collections.use("JeopardyQuestion");var response = jeopardy.query.bm25("food", q -> q .filters(Filter.property("round").eq("Double Jeopardy!")) .returnProperties("answer", "question", "round") // return these properties .limit(3));for (var o : response.objects()) { System.out.println(o.properties());}var jeopardy = client.Collections.Use("JeopardyQuestion");var response = await jeopardy.Query.BM25( "food", filters: Filter.Property("round").IsEqual("Double Jeopardy!"), returnProperties: ["answer", "question", "round"], // return these properties limit: 3);foreach (var o in response.Objects){ Console.WriteLine(JsonSerializer.Serialize(o.Properties));}{ Get { JeopardyQuestion( limit: 3 bm25: { query: "food" } where: { path: ["round"] operator: Equal valueText: "Double Jeopardy!" } ) { question answer _additional { score } } }}Example response
The response is like this:
{
"data": {
"Get": {
"JeopardyQuestion": [
{
"_additional": {
"score": "3.0140665"
},
"answer": "food stores (supermarkets)",
"question": "This type of retail store sells more shampoo & makeup than any other",
"round": "Double Jeopardy!"
},
{
"_additional": {
"score": "1.9633813"
},
"answer": "honey",
"question": "The primary source of this food is the Apis mellifera",
"round": "Double Jeopardy!"
},
{
"_additional": {
"score": "1.6719631"
},
"answer": "pseudopods",
"question": "Amoebas use temporary extensions called these to move or to surround & engulf food",
"round": "Double Jeopardy!"
}
]
}
}
}Tokenization
Section titled “Tokenization”Weaviate converts filter terms into tokens. The default tokenization is word. The word tokenizer keeps alphanumeric characters, lowercase them and splits on whitespace. It converts a string like "Test_domain_weaviate" into "test", "domain", and "weaviate".
For details and additional tokenization methods, see Tokenization.
Fuzzy matching
Section titled “Fuzzy matching”You can enable fuzzy matching and typo tolerance in BM25 searches by using trigram tokenization. This technique breaks text into overlapping 3-character sequences, allowing BM25 to find matches even when there are spelling errors or variations.
This enables matching between similar but not identical strings because they share many trigrams:
"Morgn"and"Morgan"share trigrams like"org", "rga", "gan"
Set the tokenization method to trigram at the property level when creating your collection:
from weaviate.classes.config import Configure, Property, DataType, Tokenizationclient.collections.create( "Article", vector_config=Configure.Vectors.text2vec_cohere(), properties=[ Property( name="title", data_type=DataType.TEXT, tokenization=Tokenization.TRIGRAM, # Use "trigram" tokenization ), ],)import { vectors, dataType, tokenization } from 'weaviate-client';client.collections.create("Article", col -> col
.vectorConfig(VectorConfig.text2vecTransformers())
.properties(
Property.text("title", p -> p.tokenization(Tokenization.TRIGRAM))));Soft-rank with Boost
Section titled “Soft-rank with Boost”Keyword (BM25) queries accept an optional boost argument that promotes or demotes matching documents without removing them. This is useful for biasing results by recency, popularity, a soft filter, or another property. Matching documents move up. Everything else stays in the results but ranks lower.
See Boost for the supported condition types (filter, property value, time decay, numeric decay), curve choices, blending semantics, and depth tuning.
Further resources
Section titled “Further resources”Questions and feedback
Section titled “Questions and feedback”Have a question or feedback? Here's how to reach us.