Completion¶
complete returns ranked suggestions for what a user has typed so far. Every match kind is collected and
ranked in one request; you do not choose between prefix, infix or fuzzy search.
index.complete(query, limit=10, *, contexts=None)
engine.namespace(name).complete(query, layers, limit=10, *, contexts=None, ignore_missing_layers=False)
engine.complete(query, layers, limit=10, *, contexts=None, ignore_missing_layers=False) # the default namespace
| Method | Returns |
|---|---|
complete |
Direct matches: exact, prefix, abbreviation, infix and fuzzy, as Suggestions. |
complete_aliases |
Documents whose synonyms match, as AliasSuggestions. See Synonyms. |
vector_search, hybrid_search |
Nearest documents by embedding, alone or fused with complete. See Semantic and hybrid. |
An Index searches one index. An Engine, including one from db.engine(), and a Namespace from
engine.namespace(name) take a list of index names to search as layers. Both have the same methods and options. In Rust, the _with variants
take SearchOptions:
use completype::SearchOptions;
let pop = SearchOptions::new(5).contexts(["pop"]);
let hits = index.complete_with("danc", &pop);
let layered = engine.complete_with(&["songs"], "danc", &pop)?;
The examples on this page use this index:
from completype import Index
index = Index.from_documents([
{"id": "bohemian", "text": "Bohemian Rhapsody – Queen", "popularity": 0.95, "synonyms": ["is this the real life"], "contexts": ["rock"]},
{"id": "killer", "text": "Killer Queen – Queen", "popularity": 0.6, "contexts": ["rock"]},
{"id": "dancing", "text": "Dancing Queen – ABBA", "popularity": 0.85, "contexts": ["pop", "disco"]},
{"id": "dark", "text": "Dancing in the Dark – Bruce Springsteen", "popularity": 0.7, "contexts": ["rock"]},
{"id": "ownown", "text": "Dancing On My Own – Robyn", "popularity": 0.5, "contexts": ["pop"]},
{"id": "bridge", "text": "Under the Bridge – Red Hot Chili Peppers", "popularity": 0.75, "abbreviations": ["RHCP"], "contexts": ["rock"]},
{"id": "teen", "text": "Smells Like Teen Spirit – Nirvana", "popularity": 0.8, "abbreviations": ["SLTS"], "contexts": ["rock"]},
{"id": "billie", "text": "Billie Jean – Michael Jackson", "popularity": 0.9, "abbreviations": ["MJ"], "contexts": ["pop"]},
])
Match kinds¶
| Query | Top suggestion | kind |
Highlights |
|---|---|---|---|
killer queen – queen |
Killer Queen – Queen | exact |
Killer Queen – Queen |
danc |
Dancing Queen – ABBA, Dancing in the Dark – Bruce Springsteen | prefix |
Dancing Queen – ABBA |
Dancing Qu |
Dancing Queen – ABBA | prefix |
Dancing Queen – ABBA |
rhcp |
Under the Bridge – Red Hot Chili Peppers | abbreviation |
none |
queen |
Bohemian Rhapsody – Queen, Dancing Queen – ABBA | infix |
Bohemian Rhapsody – Queen |
rapsody |
Bohemian Rhapsody – Queen | fuzzy |
Bohemian Rhapsody – Queen |
bohemain rapsody |
Bohemian Rhapsody – Queen | fuzzy |
Bohemian Rhapsody – Queen |
dancingqueen |
Dancing Queen – ABBA | prefix |
Dancing Queen – ABBA |
is this the real, through complete_aliases |
Bohemian Rhapsody – Queen | synonym |
none |
for query in ["killer queen – queen", "danc", "rhcp", "queen", "bohemain rapsody", "dancingqueen"]:
print(f"{query!r:22}", [(s.text, s.kind) for s in index.complete(query, limit=2)])
'killer queen – queen' [('Killer Queen – Queen', MatchKind.EXACT)]
'danc' [('Dancing Queen – ABBA', MatchKind.PREFIX), ('Dancing in the Dark – Bruce Springsteen', MatchKind.PREFIX)]
'rhcp' [('Under the Bridge – Red Hot Chili Peppers', MatchKind.ABBREVIATION)]
'queen' [('Bohemian Rhapsody – Queen', MatchKind.INFIX), ('Dancing Queen – ABBA', MatchKind.INFIX)]
'bohemain rapsody' [('Bohemian Rhapsody – Queen', MatchKind.FUZZY)]
'dancingqueen' [('Dancing Queen – ABBA', MatchKind.PREFIX)]
Notes on each kind:
- Exact and prefix matching works on the whole text and word by word, so
Dancing Qucompletes Dancing Queen – ABBA. Every query word must start a word of the text. - Abbreviations match exactly and case-insensitively:
rhcpfinds a document with the abbreviationRHCP, butrhcdoes not match it as an abbreviation, so short codes do not flood the results. - Infix matches a word inside the text. Words shorter than
min_word_chars(3) are not indexed for infix matching. - Fuzzy matching corrects up to
max_edit_distance(2) edits per word, SymSpell-style, and verifies candidates with Levenshtein distance. Fuzzy hits always rank below the weakest direct match. - Word decomposition splits run-together input, such as
dancingqueen, into dictionary words. - Synonyms are matched by
complete_aliases; see Synonyms. - Semantic hits come from embeddings; see Semantic and hybrid.
Highlights¶
highlights lists the (start, end) character ranges of text that matched, sorted, ready to render in
bold. Abbreviation and synonym matches have no highlights, since the query does not appear in the text.
Contexts¶
Tag documents with contexts and pass contexts= to any search. A document qualifies when it has any
of the requested contexts. Filtering happens while candidates are collected, so a filtered request still
returns up to limit results.
['Dancing Queen – ABBA', 'Dancing On My Own – Robyn']
['Dancing Queen – ABBA', 'Dancing in the Dark – Bruce Springsteen']
Contexts are the only filter completype has. Use them for genres, tenants or languages, or use separate indexes and layers when the partitions are large.
Synonyms¶
complete_aliases matches synonyms by prefix and returns the documents they belong to, as
AliasSuggestions with id, text (the document's, not the synonym), score and layer. It is a
separate call, so an alternative name never displaces something the user typed:
To show synonyms below the direct matches, append the alias hits that are not already there:
def complete_with_synonyms(index, query, limit=10):
direct = index.complete(query, limit)
seen = {s.id for s in direct}
return direct + [a for a in index.complete_aliases(query, limit) if a.id not in seen][: limit - len(direct)]
print([s.text for s in complete_with_synonyms(index, "is this the real life")])
A collection's complete(..., aliases=True) does this in one call.
Limits¶
limit (10 by default) caps the number of suggestions. Queries of up to short_query_chars characters
(3 by default) are answered from a per-index cache of short_query_limit (100) results, so one- to
three-character prefixes, which match large parts of an index, stay fast. Requests with contexts bypass
this cache.
Ranking¶
Suggestions are ordered by score, then shorter text, then id. The score combines the match kind, the text
length, whole-word bonuses and popularity; Concepts lists the formula.
popularity_weight (0.4 by default) sets how much popularity counts, and max_score the raw score that
normalises to 1.0:
flat = Index.from_documents(
[{"id": "a", "text": "Dancing Queen – ABBA", "popularity": 0.0}, {"id": "b", "text": "Dancing in the Dark – Bruce Springsteen", "popularity": 1.0}],
popularity_weight=0.0,
)
print([s.text for s in flat.complete("danc")]) # shorter text first
An index estimates max_score from its documents unless you pass one. A database stores it per index.
Pin it, with max_score= or Transaction.set_max_score, when scores must stay comparable across
rebuilds. popularity_weight is also an option of db.engine(), db.open_index() and Index(...);
Configuration lists every index option.