Autocomplete
Low-RAM autocomplete and query suggestion search
Autocomplete Engine
AutocompleteEngine is brinicle's high-level engine for autocomplete and suggestion search.
Use it for:
- query suggestions
- title suggestions
- product-name suggestions
- category suggestions
- curated search phrases
AutocompleteEngine uses the same disk-first HNSW infrastructure as the other brinicle engines, but it encodes suggestion text internally before indexing and searching.
It supports:
- build
- insert
- upsert
- delete
- single-query search
- batch search
- search with distances
- local sharding for large indexes
- compact rebuild
- graph optimization
Constructor
ac = brinicle.AutocompleteEngine(
index_path,
dim=48,
tokenizer_path=None,
text_prep=None,
delta_ratio=0.10,
M=16,
ef_construction=200,
ef_search=64,
build_n_threads=1,
seed=0,
autocomplete_config=None,
n_shards=1,
)Parameters
| Parameter | Meaning |
|---|---|
index_path | Base path for the index files |
dim | Dimension used for encoded autocomplete tokens |
tokenizer_path | Optional custom tokenizer path |
text_prep | Optional text preprocessing |
delta_ratio | Maintenance threshold for delta and deleted records |
M | HNSW graph connectivity |
ef_construction | Build-time search width |
ef_search | Default query-time search width |
build_n_threads | Number of build threads |
seed | Random seed for graph construction |
autocomplete_config | Optional autocomplete scoring configuration |
n_shards | Number of local shards used when building the index |
Example:
ac = brinicle.AutocompleteEngine(
"autocomplete_index",
dim=48,
M=32,
ef_construction=512,
ef_search=128,
)Local Sharding
n_shards controls how many local shards brinicle creates for the autocomplete index during build.
The current sharding implementation is local sharding. It is not distributed sharding across multiple machines.
During insertion, brinicle uses hash-based routing to decide which shard receives each suggestion. During search, brinicle searches all shards, then merges the partial results into a final ranked suggestion list.
For multi-shard indexes, search methods accept n_jobs, which controls how many shards are searched in parallel.
Higher n_jobs can improve search speed, but it also increases CPU and I/O usage.
Example:
ac = brinicle.AutocompleteEngine(
"large_autocomplete_index",
dim=48,
n_shards=50,
)
results = ac.search(
"iphone",
k=5,
n_jobs=8,
)As a practical starting point, keep n_shards=1 for smaller autocomplete indexes. Sharding becomes more useful when the index contains more than about 2 million suggestions. For example, for an index with around 100 million suggestions, n_shards=50 can be a reasonable starting point.
Benchmark n_shards and n_jobs with your real data and hardware, because the best values depend on suggestion count, encoded dimension, storage speed, and available CPU cores.
Suggestion Format
Each suggestion has an external_id and a text value.
ac.ingest(
"iphone 15 pro max",
"iphone 15 pro max",
)The text value is encoded and indexed.
The external_id is what search returns. It can be the suggestion text itself, an item ID, a query ID, or any caller-defined identifier.
Example with separate IDs:
ac.ingest(
"suggestion_001",
"iphone 15 pro max",
)How Autocomplete Scoring Works
Autocomplete search is prefix-oriented.
brinicle compares query tokens with suggestion tokens from the beginning of the text. Earlier token matches matter more than later token matches.
Example:
query: "iphone 15"
good: "iphone 15 pro max"
weaker: "case for iphone 15"Both suggestions contain related terms, but the first one is prefix-aligned with the query.
Smaller distance means a better match.
Query Length and Thresholds
AutocompleteEngine works cautiously. It usually gives clearer results when the query contains at least one complete term.
Very short partial inputs such as:
"iph"may not produce strong or obvious matches.
Queries such as:
"iphone"
"iphone 15"
"samsung"usually provide clearer signals.
Use threshold to filter weak or irrelevant suggestions:
results = ac.search(
"iphone",
k=5,
threshold=0.8,
)Lower thresholds make autocomplete stricter. Higher thresholds allow more distant suggestions.
AutocompleteConfig
Use AutocompleteConfig when you want direct control over autocomplete scoring.
cfg = brinicle.AutocompleteConfig()
cfg.search_position_decay = 0.5
cfg.search_length_penalty = 0.2
ac = brinicle.AutocompleteEngine(
"autocomplete_index",
dim=48,
autocomplete_config=cfg,
)AutocompleteConfig has separate build-time and search-time parameters.
Build-time parameters affect graph construction.
Search-time parameters affect query ranking.
AutocompleteConfig Parameters
| Parameter | Effect |
|---|---|
build_position_decay | Controls how much later token positions matter during graph construction |
search_position_decay | Controls how much later token positions matter during search |
build_length_penalty | Length penalty used during graph construction |
search_length_penalty | Penalizes suggestions that are much longer than the query during search |
position_decay controls how quickly later token positions lose importance.
Lower values make early tokens much more important.
Higher values make later tokens matter more.
length_penalty controls how strongly longer suggestions are penalized.
Higher values make autocomplete stricter toward long suggestions.
Lower values allow longer completions more easily.
Building an Autocomplete Index
import brinicle
ac = brinicle.AutocompleteEngine(
"autocomplete_index",
dim=48,
)
ac.init(mode="build")
ac.ingest("iphone 15 pro max", "iphone 15 pro max")
ac.ingest("iphone 15 case", "iphone 15 case")
ac.ingest("samsung s24 ultra", "samsung s24 ultra")
ac.finalize()Search
Use search(...) to return suggestion IDs.
results = ac.search("iphone", k=5)
print(results)Example output:
["iphone 15 pro max", "iphone 15 case"]Search Parameters
ac.search(
query,
k=10,
efs=None,
threshold=float("inf"),
normalize=True,
n_jobs=1,
)| Parameter | Meaning |
|---|---|
query | Text query |
k | Maximum number of suggestions |
efs | Query-time search width |
threshold | Maximum accepted distance |
normalize | Whether to normalize encoded query values |
n_jobs | Number of shards to search in parallel on multi-shard indexes |
Increasing efs usually improves recall, but increases query latency.
n_jobs controls how many shards are searched in parallel. Higher values can reduce latency on multi-shard indexes, but they also consume more CPU and I/O. For n_shards=1, you can ignore this parameter.
Search with Distance
Use search_with_distance(...) to return suggestion IDs and distances.
On multi-shard indexes, n_jobs controls how many shards are searched in parallel.
results = ac.search_with_distance(
"iphone",
k=5,
)
print(results)Example output:
[("iphone 15 pro max", 0.0), ("iphone 15 case", 0.12)]The result format is:
[(external_id, distance), ...]Batch Search
Use search_batch(...) to search multiple autocomplete queries.
results = ac.search_batch(
["iphone", "samsung", "laptop"],
k=5,
n_jobs=4,
)The return value contains one suggestion list per query:
[
["iphone 15 pro max", "iphone 15 case"],
["samsung s24 ultra"],
["laptop stand", "laptop sleeve"],
]For single-shard indexes, n_jobs controls parallel query execution when parallel execution is available. For multi-shard indexes, it is also used by sharded search execution, so higher values can reduce latency but increase CPU and I/O usage.
Insert
Use insert mode to add new suggestions to an existing index.
ac.init(mode="insert")
ac.ingest("iphone 16 pro", "iphone 16 pro")
ac.ingest("iphone 16 pro case", "iphone 16 pro case")
ac.finalize()Inserted suggestions are added through the delta index.
Upsert
Use upsert mode to replace existing suggestions or insert new ones.
ac.init(mode="upsert")
ac.ingest("iphone 15 pro max", "iphone 15 pro max 256gb")
ac.finalize()If the external ID already exists, brinicle marks the old record as deleted and inserts the new version.
If the external ID does not exist, the suggestion is inserted as a new record.
Delete
Use delete_items(...) to delete suggestions by external ID.
deleted_count, not_found = ac.delete_items(
["iphone 15 case", "missing"],
return_not_found=True,
)
print(deleted_count)
print(not_found)If return_not_found=False, the second returned value is None.
Deletes are logical until compact rebuild.
Rebuild and Optimize
Autocomplete indexes use the same maintenance model as VectorEngine.
ac.needs_rebuild()Returns whether the index has enough update or delete drift to justify rebuilding.
ac.rebuild_compact()Rebuilds the index from alive records, removes deleted records physically, and clears the delta index.
ac.optimize_graph()Runs conditional maintenance. If the index crosses the configured maintenance threshold, brinicle rebuilds the graph.
Close and Destroy
Close loaded index resources:
ac.close()Destroy the index files:
ac.destroy()destroy() removes the index from disk.
Complete API Reference
init
ac.init(mode="build")Starts a write session.
Supported modes:
build
insert
upsertingest
ac.ingest(
external_id,
text,
normalize=True,
)Adds one suggestion to the current write session.
finalize
ac.finalize(
optimize=False,
M=0,
ef_construction=0,
ef_search=0,
build_n_threads=0,
seed=0,
)Completes the pending write session.
search
ac.search(
query,
k=10,
efs=None,
threshold=float("inf"),
normalize=True,
n_jobs=1,
)Returns suggestion IDs.
search_with_distance
ac.search_with_distance(
query,
k=10,
efs=None,
threshold=float("inf"),
normalize=True,
n_jobs=1,
)Returns (external_id, distance) pairs.
search_batch
ac.search_batch(
queries,
k=10,
efs=None,
threshold=float("inf"),
normalize=True,
n_jobs=1,
)Runs batch autocomplete search.
delete_items
ac.delete_items(
external_ids,
return_not_found=False,
)Deletes suggestions by external ID.
needs_rebuild
ac.needs_rebuild()Returns whether the index has crossed its maintenance threshold.
rebuild_compact
ac.rebuild_compact(
M=16,
ef_construction=200,
ef_search=64,
build_n_threads=1,
seed=0,
)Rebuilds the index from alive records.
optimize_graph
ac.optimize_graph()Runs conditional graph maintenance.
close
ac.close()Closes loaded index resources.
destroy
ac.destroy()Removes index files from disk.