---
title: "Use BM25 for exact words"
lesson_id: "03"
---

# Use BM25 for exact words

- **Lesson ID:** 03
- **Goal:** Implement a small on-device BM25 scorer in Swift, build stats on a Mac in Python, and state that Apple does not ship one.
- **Human lesson:** [03-bm25-for-words.html](03-bm25-for-words.html)

## Prerequisites

- Lesson 02 chunks.

## Inputs, outputs, and artifacts

- **Inputs:** Chunks and a query string.
- **Outputs:** Top lexical hits with scores.

## Agent build steps

1. Say there is no Apple BM25 type.
2. Tokenize to lowercase letters and numbers in Swift for query time.
3. Store document frequency and score on query in Swift.
4. Build df and lengths on a Mac in Python and write bm25-index.json.
5. Unit test a unique invoice id.
6. Do not call Spotlight BM25.

## Constraints

Owned scorer only. No em dashes. Swift for on-device query. Python for Mac index stats.

## Key concepts

- There is no Apple `BM25Index` type. You write a compact scorer.
- BM25 wins for names, invoice numbers, and error codes.
- Build document frequency on a Mac if you want. Query-time scoring stays Swift on the phone.
- Spotlight's lexical engine is not BM25. Do not rename it.

## Takeaways

- Unit test that a unique invoice id ranks first.
- Fix tokenize before you add dense search.
- Do not claim Apple shipped BM25.

## Acceptance checks

- Swift BM25 query code is shown.
- Python Mac index build is shown.
- Apple BM25 is denied.
- Agent brief link is present.
