Retrieve structured facts without embeddings¶
When I see a question about a price, limit, date, or plan name, I first check whether the answer already lives in a table.
The example pricing table is a CSV file called data/plans.csv. Its Pro row contains included_build_minutes with the value 5000.
For the Query How many build minutes come with Pro?, I use an exact row lookup. There is no reason to search by approximate meaning when the table can return the exact field directly.
A direct row lookup¶
The complete lookup needs only Python's CSV reader:
import csv
from pathlib import Path
def load_plans(path: Path) -> list[dict[str, str]]:
with path.open(newline="", encoding="utf-8") as handle:
return list(csv.DictReader(handle))
def lookup_plan(path: Path, plan: str) -> dict[str, str] | None:
wanted = plan.casefold()
return next(
(row for row in load_plans(path) if row["plan"].casefold() == wanted),
None,
)
plan = lookup_plan(Path("data/plans.csv"), "Pro")
build_minutes = plan["included_build_minutes"] if plan else None
The function loads the CSV, compares the plan name without treating uppercase and lowercase as different, and returns the matching record. There is no model call and no ranking threshold to tune.
The lookup itself is simple. The design choice comes next: how much of the table should the rest of the application be allowed to query?
Keep the tool narrow¶
A production system might move this data into PostgreSQL, but the interface can stay specific:
def structured_lookup(
path: Path,
name: str,
filters: dict[str, str],
) -> dict[str, str] | None:
if name != "plan" or set(filters) != {"plan"}:
raise ValueError("unsupported structured lookup")
return lookup_plan(path, filters["plan"])
The function accepts one known lookup and one known filter. Do not hand a user string to unrestricted SQL, and do not ask the model to invent a query against tables it does not understand.
Keep a record of where the returned value came from. Store the Source ID for data/plans.csv, the row identity, and the fields used in the Answer. When generation says "Pro includes 5,000 build minutes," the Citation should lead back to that record.
We now have enough retrieval methods to make a real choice. Instead of asking which method is most advanced, ask what shape the information has.
Choose methods by the shape of the question¶
You now have three retrieval methods:
- glob narrows Sources by path;
- lexical search finds and ranks words or identifiers;
- structured lookup returns known fields and records.
None is a fallback for a failed vector search. Each one fits a different information need.
Next, we will add embeddings for the case these methods still miss: a Query and a relevant passage that mean the same thing but use different words.