BBC Ingester
End-to-end pipeline to ingest BBC-style JSON articles, deduplicate and chunk text, generate embeddings, persist to Parquet and LanceDB (local or S3-backed), and query via a small FastAPI service.
Key Components
combine_s3_json.py: Lists and combines multiple JSON files from an S3 prefix into one JSON (writes locally and can upload back to S3).embedder.py: Loads/combines JSON, normalizes/deduplicates, chunks text, computes embeddings, writes Parquet, uploads to S3, and writes to LanceDB.upload_parquet_to_lancedb.py: Reads a Parquet file from S3 and writes/appends it to a LanceDB table (local path or S3-backed DB). Falls back to local./lancedbif no cloud URI is set.lance_query.py: Minimal FastAPI app to query the LanceDB table with vector-only or hybrid search.crawler.py, helpers: Project-specific ingestion utilities.
Prerequisites
- Python 3.9+ in a virtual environment.
- AWS credentials with
s3:GetObject,s3:ListBucket, and (for uploads)s3:PutObjecton your bucket. - LanceDB storage:
- Local filesystem directory (no creds), or
- LanceDB Cloud (
db://URI + API key), or - S3-backed LanceDB (uses your AWS creds).
- Hugging Face (only if you use gated models). Open models are recommended.
Environment (.env)
Create .env in the repo root (dotenv format: KEY=VALUE, no export):
-
AWS
AWS_REGION=eu-west-2S3_BUCKET=ansleysawsbucketS3_PREFIX=bbc/news/S3_OUTPUT_KEY=bbc/news/combined.json(optional; auto-derived if omitted)
-
LanceDB (pick one)
- Cloud:
LANCEDB_URI=db://your-db-id,LANCEDB_API_KEY=sk_...,LANCEDB_REGION=us-east-1 - Local: no env needed; scripts fall back to
./lancedb - S3-backed: pass
--lancedb-uri s3://your-bucket/lancedb/mydbto writer script
- Cloud:
-
Embedding/runtime (optional)
EMBED_MODEL=sentence-transformers/all-MiniLM-L6-v2EMBED_DEVICE=cpu(ormpson Apple once tested)EMBED_BATCH=64MAX_CHUNKS=0(0 = all)MIN_CHARS=20
Install
- macOS-friendly base (avoids xformers/CUDA wheels):
pip install --no-cache-dir "torch==2.3.1" "transformers<4.46" "sentence-transformers==2.7.0"pip install --no-cache-dir boto3 python-dotenv lancedb pyarrow pandas tqdm ftfy simhash tiktoken fastapi uvicorn
- Or use the provided
requirements.txtif you’ve tailored it for your environment.
Typical Workflow
-
Combine JSON files from S3
python combine_s3_json.py --bucket ansleysawsbucket --prefix bbc/news/ --output combined.json --output-s3-key bbc/news/combined.json
-
Generate embeddings and write outputs
python embedder.py- Outputs:
- Local Parquet:
embeddings/embeddings-<timestamp>.parquet - S3 (if
S3_BUCKETset):s3://<bucket>/<prefix>/embeddings/run=<timestamp>/embeddings.parquet - LanceDB table: creates/overwrites
embeddings_tableat configured DB/URI
- Local Parquet:
-
Import Parquet from S3 into LanceDB (optional)
- Local LanceDB:
python upload_parquet_to_lancedb.py --bucket ansleysawsbucket --key bbc/news/embeddings/run=<ts>/embeddings.parquet --table embeddings_table --lancedb-uri ./lancedb
- S3-backed LanceDB:
python upload_parquet_to_lancedb.py --bucket ansleysawsbucket --key bbc/news/embeddings/run=<ts>/embeddings.parquet --table embeddings_table --lancedb-uri s3://ansleysawsbucket/lancedb/mydb
- Local LanceDB:
-
Start the query API (FastAPI)
uvicorn lance_query:app --reload- Open docs: http://127.0.0.1:8000/docs
- Example body for
POST /query:{"query":"News about Anglian Water", "limit":5, "hybrid": false}
Notes on Embeddings
- Use open, CPU/MPS-friendly models to avoid
xformersrequirements:- Recommended default:
sentence-transformers/all-MiniLM-L6-v2(384-d)
- Recommended default:
embedder.py:- Deduplicates (SimHash), chunks text, encodes with
encode(..., convert_to_numpy=True, normalize_embeddings=True) - Stores vectors as float32 lists; guards against NaNs/zeros and drops zero vectors
- Writes LanceDB and attempts index creation; gracefully skips if API signature differs
- Deduplicates (SimHash), chunks text, encodes with
FastAPI Query Service
lance_query.pyprovides:GET /health checkPOST /querywith JSON body{query, limit, hybrid}GET /table-infoto inspect columns and a small sample
- Always use the same embedding model as in
embedder.pyand specifyvector_column="embedding"when searching.
S3 + LanceDB Paths
- S3 examples:
- Combined:
s3://<bucket>/<prefix>/combined.json - Embeddings:
s3://<bucket>/<prefix>/embeddings/run=<timestamp>/embeddings.parquet
- Combined:
- LanceDB (S3-backed):
- DB URI:
s3://<bucket>/lancedb/mydb - Table directory typically ends with
.lance, e.g.,.../embeddings_table.lance
- DB URI:
Troubleshooting
- xformers build error on macOS:
- Cause: Some models require xformers; Apple clang lacks OpenMP by default
- Fix: Use
all-MiniLM-L6-v2(no xformers), or install LLVM+OpenMP and build (not recommended)
- urllib3 NotOpenSSLWarning:
- Harmless warning about LibreSSL on macOS; for a proper fix, use a Python built against OpenSSL (Homebrew/pyenv)
- Gated HF models:
huggingface-cli loginand request access, or switch to open models
- Dataset/path not found:
- Point to the table directory (e.g.,
.../embeddings_table.lance), not a single internal data file
- Point to the table directory (e.g.,
- Vector column/index issues:
- Search with
vector_column="embedding"; re-create index with the correct metric if needed
- Search with