Fast US address parsing in Python, backed by Rust.
Compatible with the usaddress API and its trained model.
import fastaddress
fastaddress.tag("123 N Main St Apt 4B Springfield IL 62704")
# ({'AddressNumber': '123', 'StreetNamePreDirectional': 'N', ...}, 'Street Address')usaddress is widely used, with about 5.2M monthly downloads as of August 2026 and 1,145 dependent GitHub repositories, including government and open-data projects, but its Python CRF runtime is slow on large datasets. On our
benchmark machine it parses about 8,000 addresses per second, which becomes noticeable on county
tax rolls and national property datasets.
fastaddress runs the same trained model in Rust.
usaddress 7,941 addresses/sec
fastaddress 89,653 addresses/sec
fastaddress, 8 threads 360,035 addresses/sec
Single-core throughput is 11.3x higher.
The compatibility suite checks 20,738 real county addresses at the token, feature, serialized attribute, and tagged-output levels. It found zero divergences.
pip install fastaddressPrebuilt wheels are published for Linux, macOS, and Windows (Python 3.10+); no Rust toolchain
needed. To build from source instead, install Rust and run
pip install git+https://github.com/vinvomero/fastaddress.
Verify:
python -c "import fastaddress; print(fastaddress.tag('123 N Main St Springfield IL 62704'))"See AGENTS.md for the agent runbook.
- 89,653 addresses/sec on one core. 360,035/sec on 8 threads.
- Drop-in API compatibility.
parse(),tag(),tag_mapping, andRepeatedLabelError. - Zero output divergences across 20,738 real addresses in the parity suite.
- Native mode.
tag_native()handles cases such asST JAMES PLACEwithout raisingRepeatedLabelError. - Confidence scores. Token, component, and sequence probabilities from the CRF.
- Small wheels. Roughly 0.8 MB with the model included. No model download or external service.
- Reproducible benchmarks. The scripts and source data are in the repo.
The normal API follows usaddress 0.5.16 behavior:
fastaddress.parse(address)
fastaddress.tag(address)Native parsing is also available:
fastaddress.tag_native(address)There are two known compatibility differences:
tag()returns an insertion-ordereddict, notOrderedDict.- Unicode casing and digit classification may differ for non-ASCII inputs.
For typical US property data, both implementations produce the same tagged output.
fastaddress.parse_with_confidence(
"123 N Main St Springfield IL 62704"
)
# [
# ('123', 'AddressNumber', 0.99995),
# ('N', 'StreetNamePreDirectional', 0.99452),
# ...
# ]Tagged output:
tagged, address_type, confidence, sequence_confidence = (
fastaddress.tag_with_confidence(addr)
)For components spanning multiple tokens, confidence is the minimum token probability.
The implementation was compared against pycrfsuite.Tagger.marginal() over 34,028 token
positions:
max absolute difference 1.665e-15
Viterbi disagreements 0
Reproduce it:
python benchmark/compare_marginals.py --rows 5000Confidence is useful for deciding which records to review. It is not a calibrated probability that the full address is correct.
The shipping model is unchanged. The repo also contains an experimental retrained model that targeted known parsing errors. It does not ship because accuracy did not improve.
The retraining passed every internal gate — 74 wins / 0 losses on adjudicated hard cases, +2.400pp on a held-out real-text set, both national scans — and then failed the deciding national exam three times running:
Gold-2 attempt 1 v36 +0.215pp 95% CI [-0.861, +1.291]
Gold-2 attempt 2 v43 +0.789pp 95% CI [-0.287, +1.865]
Gold-2b attempt 1 v50 -0.275pp 95% CI [-0.927, +0.343]
The third is net negative. That prompted building an evaluation set from sources disjoint from every exam and every training corpus, labeled by hand (Gold-2c). Scored on it, every retrained candidate is worse than the original on ordinary streets that carry their suffix — the commonest shape in US mail:
original 38 / 47 suffix-present correct
best retrained candidate 25 / 47
The result is that the retraining traded real-world accuracy for wins on self-selected cases. The default model — DataMade's, redistributed unmodified — is what ships.
The full protocol, training history, failed candidates, and adjudications are here:
The experimental model uses upstream labeled data, public county parcel records, Census data, generated error cases, and 162,879 aligned owner-mailing records from 30 states.
No Gold or clean-set evaluation address appears in a training corpus. The builders enforce this with normalized-identity deduplication.
See training/ for manifests and builders.
python benchmark/run_speed.py
python benchmark/run_parity.py
python benchmark/compare_marginals.py
python benchmark/confidence_error_auc.pyIf a number in this README disagrees with the benchmark output, the benchmark wins.
cargo test --workspace
pip install -r benchmark/requirements.txt
python benchmark/fetch_data.py
python benchmark/dump_oracle.py
cargo build --release
python benchmark/run_parity.pyRepository layout:
crates/core Rust parser and CRF engine
crates/python PyO3 bindings
benchmark/ Benchmarks and parity tests
training/ Experimental model training
eval/ Evaluation data and protocol
model/ Models and provenance
The default model comes from DataMade/usaddress and is redistributed unmodified under the MIT license. See model/PROVENANCE.md.
usaddress-rs and us-addrs also informed the Rust model-loading work and supplied useful test cases.
Issue triage is committed for at least 12 months after launch by @vinvomero.
