NEWVectors or files. Pick a path.Start →
    Search & Discovery
    10 min read
    Updated 2026-09-15

    Why Can't My Search Find Exact Part Numbers, Codes or Names?

    Semantic search compares meanings, and a part number, an error code or an unusual surname carries almost none, so the index returns records that look alike instead of the one that matches. Run a keyword (BM25) search beside the vector search, merge the two lists by rank, and use an exact filter when the identifier lives in its own field.

    Hybrid Search
    BM25
    Keyword Search
    Exact Match
    Search Quality
    Vector Search

    Why can't my search find exact part numbers, codes or names?



    Because vector search ranks by meaning, and an identifier has very little of it. An embedding model turns "AB-4471-X" into the same kind of vector it makes for a sentence, built from the fragments a tokenizer cut the string into, so "AB-4417-X" and "AB-4471-Y" land almost on top of it. The record you wanted is usually in the index, ranked below its look-alikes. The fix is a keyword search that scores literal tokens and rewards rare ones, run beside the vector search and merged with it by rank. When the identifier sits in a structured field, an exact filter on that field is simpler still.

    How do I know this is the problem?



    Run one test before you change anything. Copy an identifier out of a record you know is indexed (a SKU, an order number, an error code, a customer surname) and search for exactly that string. If near-miss records fill the first page and the one you copied from is missing, this is your failure. If the record never shows up at any depth, check ingestion first, since no ranking change can surface a document that was never indexed.

    Other signs that point the same way:

  1. Descriptive queries such as "waterproof trail shoe" work well, and short literal queries such as "AB-4471-X" do not.
  2. The results for a code are other codes that share most of its characters.
  3. A search for a person's name returns people whose names look similar.
  4. An error string like E1034 returns general troubleshooting pages instead of the one page that names it.
  5. Pasting an exact sentence from a document does not bring that document to the top.


  6. Why does vector search miss exact matches?



    The model reads a code as fragments



    Embedding models never see whole identifiers. A tokenizer first splits the input into subword pieces, and the model builds one vector from those pieces. Two codes that differ by two digits share most of their pieces, so their vectors sit close together. The digits that make it a different part barely move the result, and nothing in training taught the model to treat them as decisive.

    Names and new terms have little meaning to learn from



    A model learns what a word means from the contexts it saw during training. A rare surname, an internal project name or a product launched last month appeared rarely or never. Its vector falls back on spelling, so the nearest neighbors are strings that look alike.

    The identifier is one token in a long chunk



    A chunk of a few hundred words gets one vector, and that vector mostly represents what the chunk is about. A part number mentioned once contributes a small share of it. A query made only of that part number matches chunks on the same topic, which may or may not include the chunk that contains it.

    The query and the record are formatted differently



    Records store "AB-4471-X" while people type "ab4471x" or "AB 4471 X". This one hurts keyword search as much as vector search, which is why normalization is part of the fix below.

    How do I fix it?



    Add a keyword search next to the vector search



    BM25, the ranking function behind most keyword search, scores a document by the query terms it contains and weights each term by how rare it is across the collection. Rarity is what matters here. A part number appears in one or two records, so a match on it scores high, while a common word scores low. An embedding treats the same string the other way around. How BM25 and the inverted index work walks through the formula.

    Run both searches over the same content. The vector search keeps handling descriptions and paraphrases, and the keyword search catches literal tokens.

    Merge the two result lists by rank



    The two searches score on different scales. Cosine similarity sits in a narrow band, and BM25 scores are unbounded and shift from query to query, so adding them lets one side drown the other. Reciprocal rank fusion (RRF) ignores the scores and uses positions: every document earns 1 / (k + rank) from each list it appears in, and the totals decide the final order. The original paper by Cormack, Clarke and Buettcher used k = 60. Each list gets an equal say, so an exact keyword hit can no longer be buried by the vector ordering, and documents both searches rank well rise to the top. Hybrid search fusion compares RRF with weighted score fusion in depth.

    Use an exact filter when the identifier has its own field



    If part numbers, order IDs or employee IDs are stored in a structured field, finding one needs no ranking at all. Filter that field for equality and return what matches. Many teams route on the shape of the query: a string that matches the pattern of an ID goes to the exact filter first, and everything else goes to hybrid search.

    Normalize identifiers the same way on both sides



    Strip spaces, hyphens and case from identifiers when you index them, keep the normalized copy in its own field, and run the same function on identifier-shaped queries. "AB-4471-X", "ab4471x" and "AB 4471 X" then become one value for the filter and for the keyword index.

    Which approach finds which query?



    QueryVector search onlyKeyword (BM25) onlyExact filter on a fieldVector and keyword, merged by rank
    AB-4471-X (part number)Returns look-alike codesFinds itFinds itFinds it
    ab4471x (same part, typed differently)Returns look-alike codesFinds it only if identifiers are normalizedFinds it only if identifiers are normalizedFinds it only if identifiers are normalized
    E1034 timeout (error string)Returns general troubleshooting pagesFinds the page that names itOnly if the code is stored as a fieldFinds it, with related pages below
    A customer surnameReturns similar-looking namesFinds exact spellingsFinds it if names are a fieldFinds it
    shoe for wet trails (description)Finds relevant productsNeeds words the products shareDoes not applyFinds relevant products
    recieving dock schedule (typo)Usually tolerates the typoMisses the misspelled wordDoes not applyThe vector side still finds it

    How do I check which fix I need?



    1. Collect twenty or thirty real queries that failed, from search logs or support tickets. 2. Label each one as an identifier, a name, an error or quoted text, a description, or a misspelling. 3. For the identifiers, check whether the value is stored in a field of its own. Where it is, an exact filter covers those queries. 4. Run the remaining failures against a keyword index on its own. The queries it fixes are the ones hybrid search will fix. 5. Keep the descriptive queries in the test set and confirm they still return what they did before the keyword side was added.

    How does this work in Mixpeek?



    Keyword and vector search run inside one retriever stage. A feature_search stage takes a list of searches, and a search with lexical: true runs BM25 against the namespace's full-text index instead of querying vectors. List the same feature twice, once as a vector search and once as a lexical search, and fuse them with rrf:
    {
      "stage_name": "hybrid_search",
      "stage_type": "filter",
      "config": {
        "stage_id": "feature_search",
        "parameters": {
          "fusion": "rrf",
          "final_top_k": 25,
          "searches": [
            {
              "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
              "query": { "input_mode": "text", "value": "{{INPUT.query}}" },
              "top_k": 100
            },
            {
              "feature_uri": "mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1",
              "query": { "input_mode": "text", "value": "{{INPUT.query}}" },
              "lexical": true,
              "top_k": 100
            }
          ]
        }
      }
    }
    The keyword side reads a full-text index. Declare text payload indexes on the fields that hold your identifiers and names, then register them. Registering backfills existing documents without re-extracting anything:
    import os
    import requests
    
    NAMESPACE = "product-search"
    HEADERS = {"Authorization": f"Bearer {os.environ['MIXPEEK_API_KEY']}"}
    
    requests.patch(
        f"https://api.mixpeek.com/v1/namespaces/{NAMESPACE}",
        headers=HEADERS,
        json={"payload_indexes": [
            {"field_name": "title", "type": "text"},
            {"field_name": "description", "type": "text"},
        ]},
    )
    requests.post(
        f"https://api.mixpeek.com/v1/namespaces/{NAMESPACE}/ensure-indexes",
        headers=HEADERS,
    )
    When the identifier is its own field, an attribute_filter stage with the eq operator matches it exactly, for example metadata.part_number equal to AB-4471-X.

    Two limits are worth knowing before you rely on this. BM25 matches across every text-indexed field at once and cannot be scoped to a single field; for one field on its own, use an attribute_filter with contains (a match, without relevance ranking) or give that field its own text embedding. RRF also ignores how strong each match was, so a weak keyword hit and a perfect one at the same rank count the same. Keep each search's top_k deep enough that the fusion has real candidates to choose from.

    The full-text index lives in the same namespace as the vectors in Mixpeek Vector Store, so the keyword side needs no separate search engine. Pricing covers what search and storage cost, and the feature search reference lists every search option.

    Frequently asked questions



    Will a bigger embedding model find exact part numbers?



    Rarely. A larger model represents meaning better, and an identifier still has little meaning to represent. It still builds its vector from subword fragments, so codes that share most of their characters stay close together at any model size. A keyword search beside the vector search fixes the problem whatever model you use.

    Should I replace vector search with keyword search?



    No. Keyword search misses paraphrases, synonyms and misspellings, and those are the queries vector search handles well. Dropping the vector side swaps one set of failures for another. Run both and merge the results by rank.

    What does reciprocal rank fusion do, in plain terms?



    It merges ranked lists by position. Each document collects points for where it appears in each list, more for a higher position, and the totals set the final order. It never compares raw scores, so the difference in scale between keyword and vector scores stops mattering.

    Why does keyword search fail on typos when vector search does not?



    Keyword search matches whole tokens, and a misspelled word is a different token that appears in no document. The subword fragments of a misspelling overlap heavily with those of the correct spelling, so the two vectors stay close and the vector side still finds the content.

    Can I filter on the part number instead of adding keyword search?



    Yes, when the part number is stored in its own field and the query is exactly that value. A filter is the fastest and most precise option for that case. It cannot find a part number mentioned inside a description, a manual or a transcript, and that is the case keyword search covers.

    Further reading



  7. What Is Hybrid Search? BM25, Vector Retrieval, and How to Fuse Their Rankings -- the technique behind the fix, end to end
  8. Hybrid Search Fusion: How to Combine Dense and Lexical Retrieval Without Breaking Ranking -- rank fusion against weighted scores
  9. BM25 and the Inverted Index -- how the keyword side scores a document
  10. Learned Sparse Retrieval and Dense-Sparse Hybrid -- when a learned model replaces plain BM25
  11. Why Do My Search Results Get Worse as I Add More Data? -- another reason content you know is indexed stops surfacing
  12. Why Does My Search Find the Right Document but the Wrong Part of It? -- when the document matches and the passage does not
  13. Best Hybrid Search Engines -- products that run keyword and vector search together
  14. Glossary: BM25 and hybrid search
  15. Mixpeek docs: text indexes for BM25 and the attribute filter stage
  16. Managed Mixpeek

    Put multimodal search to work

    Connect a bucket and Mixpeek runs the whole multimodal search pipeline for you: extraction, indexing, and search over your own objects. No models to wire up, nothing to host.

    Start with Managed
    MVS · bring your own

    Already have vectors?

    Keep your embeddings on your own cloud and run dense, sparse, and BM25 search directly on object storage. From $25/mo.

    Start with MVS

    Run this on your own data

    Point Mixpeek at the storage you already have and search your video, images, audio, and documents the way this guide describes. Build starts at $25/mo for up to 1M vectors.

    Search your own archiveRead Docs

    Related guides

    Search & Discovery

    Why Do My Search Results Get Worse as I Add More Data?

    Search that worked on ten thousand items and fails on ten million usually has one of five causes: a candidate budget that is a count rather than a fraction, near-duplicates filling the top ten, a coarse index trained on data that no longer looks like yours, a score floor calibrated on a smaller corpus, or a filter starving the candidate pool. This explains each mechanism, the single test that identifies it, and the order to run the tests in.

    Read guide →
    Search & Discovery

    How Do AI Agents Search Big Datasets by Navigating Clusters? (Hierarchical Cluster Search)

    Flat vector search returns top-k against one query vector, which breaks down when an agent does not know the right query, the corpus is huge and diverse, or the task is exploratory. Agentic hierarchical cluster search gives the agent a map instead: a cluster hierarchy (themes -> sub-clusters -> records) it navigates coarse-to-fine, scoring its goal against a few dozen centroids and drilling into the matching branch before running a precise retrieval at the leaf. When it beats flat ANN, the navigation loop, the cost math, the honest limits, and how to build it from clustering + composite clustering + a cluster-scoped retriever.

    Read guide →
    Search & Discovery

    How Do I Filter Vector Search Results by Location? (Radius, Bounding Box, Polygon)

    Filter vector-search and retriever results by geographic location with three operators in an attribute_filter stage: geo_radius (within N meters), geo_bounding_box, and geo_polygon. Exact request shapes, the lat/lon-vs-GeoJSON lon-first gotcha, when to use each operator, combining geo with semantic search and metadata for location-aware RAG, and honest scope (a precise scan over your retrieved set, not a planet-scale geo index).

    Read guide →