Skip to content

Repository files navigation

Gutenberg Poetry Writer

A little RAG + GPT-2 project that writes original poems, using the Gutenberg Poetry Corpus as inspiration. Give it a topic, it digs up a stylistically similar passage from thousands of chunks of old public-domain verse, and uses that as a style primer for a fine-tuned GPT-2 model to riff on.

It's not going to win any poetry prizes, and it definitely won't always make sense — but on a good run it produces something genuinely poem-shaped, in an old, slightly melancholic, 19th-century voice.

How it works

  1. Retrieval — your prompt gets embedded (TF-IDF → SVD) and compared against a corpus of ~thousands of poetry chunks pulled from Project Gutenberg. The closest match gets pulled out as a style reference.
  2. Generation — a couple of clean lines from that reference get used to "prime" andreipb/gpt2-poetry-model-crpo, followed by your topic. The model then just continues in that style.
  3. Cleanup — this is the part that does most of the actual work. The model isn't instruction-tuned, so it can't just be told not to copy its source or ramble into prose — instead there's a chunk of post-processing that strips leaked training tokens, drops lines that are copied (even partially) from the reference, trims truncated fragments, and gets rid of stray punctuation-only lines.

If you're curious how the sausage is made, it's all in poetry_engine.py.

Setup

uv sync

You'll need the corpus and the retrieval models built once before anything will run:

uv run download_data.py   # pulls + cleans the Gutenberg Poetry Corpus -> data/gutenberg.csv
uv run train.py            # fits TF-IDF + SVD -> models/tfidf.pkl, models/svd.pkl

This can take a little while the first time (it's downloading a decent-sized dataset from Hugging Face).

Running it

Three ways to use it, depending on what you're doing:

Quick CLI test

uv run generate.py

Web UI

uv run streamlit run app.py

http://localhost:8501

API

uv run uvicorn api:app --reload

http://localhost:8000/docs for the interactive Swagger UI, or:

curl -X POST http://localhost:8000/generate \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a lonely moon over the ocean"}'

If you edit poetry_engine.py while the Streamlit app or API is already running, you need to fully restart the process (not just save the file). It's an already-imported module, so a hot reload of app.py won't re-run its top-level code.

Project structure

.
├── data/
│   └── gutenberg.csv        # cleaned poetry corpus (generated)
├── models/
│   ├── tfidf.pkl             # generated by train.py
│   └── svd.pkl                # generated by train.py
├── download_data.py          # pulls + cleans the raw Gutenberg corpus
├── train.py                  # fits the TF-IDF/SVD retrieval models
├── poetry_engine.py          # retrieval + generation + cleanup logic
├── generate.py                # CLI entry point
├── app.py                     # Streamlit web UI
└── api.py                     # FastAPI REST API

Known limitations

  • The base model is a plain fine-tuned GPT-2, not an instruction-following model, so quality is inherently inconsistent — some prompts land, some come back stilted or half-finished.
  • Style skews heavily toward 19th-century / classical diction, since that's mostly what's in the Gutenberg corpus.
  • Retrieval is TF-IDF based, so it matches on vocabulary overlap rather than deeper semantic meaning — an odd or very modern prompt may pull an unrelated reference passage.
  • No persistence — nothing you generate gets saved anywhere unless you add that yourself.

Tech stack

Python, scikit-learn (TF-IDF + SVD), Hugging Face Transformers, Streamlit, FastAPI, pandas.

Credits

About

Original poems generated with a RAG pipeline (TF-IDF/SVD retrieval + fine-tuned GPT-2) trained on the Gutenberg Poetry Corpus. CLI, Streamlit UI, and FastAPI included.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages