InterviewPrepKit

Home / Coding / Agent Coding / Evaluation / Exact Match and Token F1

Exact Match and Token F1

medium 00:00
Solving tips
  • Normalize both strings the same way before you compare anything. Exact match and F1 that disagree on casing or punctuation are measuring two different things and neither is the one you meant.
  • Token F1 counts shared tokens with multiplicity, not set overlap. Use a multiset intersection (min of per-token counts) so a gold answer that says 'the the' is not fully matched by a single 'the'.
  • Nail the empty-string cases explicitly. When a predicted or gold answer normalizes to no tokens, F1 is 1.0 only if both are empty and 0.0 otherwise — don't divide by zero into a silent NaN.

Once an agent or a RAG pipeline produces an answer, you need a number that says how good it was. Two metrics do most of the work in open-domain QA: exact match, which is strict and unforgiving, and token-level F1, which gives partial credit for overlapping words. Both live or die on normalization, because “The Eiffel Tower.” and “eiffel tower” are the same answer and a raw string compare would call them different. This exercise implements both from scratch, the same way the SQuAD evaluation script does.

Why normalize first

A predicted answer and a gold answer rarely match byte for byte even when they mean the same thing. Casing differs, trailing punctuation sneaks in, and filler articles (“a”, “an”, “the”) pad the text without adding meaning. Before comparing, both strings are lowercased, stripped of punctuation, cleared of articles, and squeezed to single-spaced tokens. Every metric here runs on that normalized form, so the same helper feeds both exact match and F1.

The two metrics

  • Exact match is a hard 0.0 or 1.0: after normalization, do the two strings match exactly? It is the metric that rewards getting the whole answer right and nothing partial.
  • Token F1 grades on overlap. Split both normalized answers into tokens, count how many tokens they share (with multiplicity — a token appearing twice in each counts twice), and combine precision (shared over predicted length) and recall (shared over gold length) into their harmonic mean. This gives partial credit when the prediction has the right words buried in extra ones, or covers only part of the gold answer.

Task

Complete qa_metrics(pred, gold):

  1. Normalize both strings: lowercase, remove punctuation, drop the articles a/an/the, and collapse whitespace.
  2. Compute exact_match as 1.0 if the normalized strings are identical, else 0.0.
  3. Tokenize each normalized string on whitespace. Compute the multiset intersection count shared (sum of min(pred_count, gold_count) over all tokens).
  4. Compute precision = shared / len(pred_tokens), recall = shared / len(gold_tokens), and f1 as their harmonic mean.
  5. Handle the empty case: if either token list is empty, all three of precision, recall, and f1 are 1.0 when both are empty and 0.0 otherwise.
  6. Return the dict {"exact_match", "precision", "recall", "f1"} with float values.

Example

m = qa_metrics("The Eiffel Tower.", "eiffel tower")
m["exact_match"]        # -> 1.0   (both normalize to "eiffel tower")
round(m["f1"], 3)       # -> 1.0

m = qa_metrics("Paris, France", "Paris")
m["exact_match"]        # -> 0.0   ("paris france" != "paris")
round(m["precision"], 3)  # -> 0.5   (1 shared / 2 predicted tokens)
round(m["recall"], 3)     # -> 1.0   (1 shared / 1 gold token)
round(m["f1"], 3)         # -> 0.667

m = qa_metrics("no", "")
m["exact_match"]        # -> 0.0
m["f1"]                 # -> 0.0   (gold empty, pred not)

Constraints

  • Do not use any external metric library (no sklearn, no evaluate, no NLTK). Standard library only.
  • Punctuation is every character in string.punctuation; removing it must not merge or split words unexpectedly (strip it, leaving whitespace to separate tokens).
  • Article removal is token-level: drop standalone a, an, the, not those letters inside other words.
  • F1 uses multiset (count-aware) overlap, not set overlap.
  • All four returned values are floats.

Write your solution, then hit Run tests to check it — or get a mock grade from the AI coach.

The coach remembers this session — revise your code and ask again, and it grades your progress. It gives hints, not the answer.
Report a bug