DEV Community

Luiza
Luiza

Posted on Edited on AI-assisted

The Culpeper Séance: An Offline RAG Séance with Nicholas Culpeper

Gemma 4 Challenge: Build With Gemma 4 Submission

What I Built

The Culpeper Séance is an offline séance that channels Nicholas Culpeper (1616–1654)—the English herbalist and astrologer—so you can ask him about plants in the voice of a gentle, slightly eerie ghost. Answers are grounded in passages from his Complete Herbal (Project Gutenberg, public domain), not in whatever the model felt like inventing tonight.

When the ghost names a dangerous herb, a Beyond the Veil panel stamps a modern verdict: Folklore, Plausible, Harmful, or Deadly. Those toxicity claims come from a hand-curated verdicts.json file—they are never generated by the model at runtime.

The UI is a candlelit séance circle with streaming ghost text, a browsable grimoire of parsed herbs, and a poison cabinet for the scary ones. It maps almost line-for-line to the challenge prompt’s “offline séance chatbot” idea, except the spirit is a real dead author and his actual book.

Demo

Quick try for judges (local):

git clone https://github.com/myluco/CulpeperSeance.git
cd CulpeperSeance
cp .env.example .env
make setup
make ingest    # one-time: download Gutenberg text, chunk, embed into Chroma
make dev       # Flask API + Vite UI
Enter fullscreen mode Exit fullscreen mode

Open the URL Vite prints (usually http://localhost:5173). Ensure Ollama is running, with ollama pull nomic-embed-text and a chat model such as gemma4:31b-cloud (after ollama signin if you use cloud-tagged models).

No login required. Sample questions:

  • “What herb might quiet a sleepless head?”
  • “Tell me of foxglove”
  • “What rules the stars over rosemary?”

Code

The Culpeper Séance

Summon the ghost of Nicholas Culpeper (1616–1654) and ask him for herbal remedies. He answers in character, grounded in the actual text of The Complete Herbal, and a "Beyond the Veil" panel reports what modern science says (usually: please don't drink that).

Built for the Ghost in the Machine challenge with open pieces only: an open-weight model served by Ollama, LangChain + LangGraph, Chroma, and a public-domain book.

Layout

ui/       React + Vite + TypeScript front end            (own package.json)
server/   Flask API, SSE streaming                       (own pyproject.toml)
agent/    LangGraph agent: guard -> retrieve -> grade -> generate -> verdict
rag/      download, parse, chunk, embed, persist, retrieve (+ one-time ingest CLI)
data/     artifacts only, never code
  raw/        downloaded Gutenberg text (git-ignored)
  processed/  herbs.jsonl (canonical parsed herbs) + parse_report.json
  chroma/     persisted vector DB (git-ignored)
  verdicts.json  hand-curated modern verdicts (toxicity claims are never model-generated)

Dependencies flow one way: server ->…

MIT licensed. Monorepo layout: rag/ (ingest + Chroma), agent/ (LangGraph), server/ (Flask + SSE), ui/ (React + Vite).

How I Built It

Open-weight and open-source AI are the engine, not decoration:

Layer Open stack
Chat Gemma 4 via Ollama (CHAT_MODEL=gemma4:31b-cloud)
Embeddings nomic-embed-text via Ollama (local pull)
Agent LangGraph + LangChain (guard → retrieve → grade → generate → verdict)
Vector store Chroma (persisted under data/chroma/)
Corpus Gutenberg Complete Herbal—parsed into herbs.jsonl, chunked, embedded once

RAG that respects the dead author. The ghost’s system prompt forbids herbs, doses, or facts not present in retrieved passages. A separate grade step asks the model yes/no whether the passages could answer the question; if not, the graph rewrites the query into 17th-century herbal vocabulary or falls back to a polite “the veil is too thick” refusal.

Safety without vibes-only guardrails. Before any retrieval, deterministic regex guardrails refuse self-harm, harm-to-others, and dosage questions—instant, testable, and not negotiable via prompt injection.

Streaming séance. POST /api/ask streams tokens over SSE so the UI can render the ghost word-by-word while the graph finishes and attaches verdict metadata.

Why not a closed API? The book is fixed public-domain text. Embeddings and the vector index live on your machine after make ingest. Swapping CHAT_MODEL or EMBED_MODEL in .env does not require rewriting application code—only re-ingest if you change the embed model.

Why Does Open Innovation Matter?

Culpeper has been dead for centuries; his book is public domain. A closed chat product could still hallucinate foxglove as harmless tea. This project ties the ghost to retrieved passages and curated modern verdicts so the spooky layer does not erase medical reality.

Open pieces also mean:

  • Privacy — After ingest, your herbal index and embeddings stay local; you are not shipping health questions to a proprietary log by default.
  • Longevity — Gutenberg and the repo outlive any single vendor API. If a hosted model tag changes, you point Ollama at another open-weight model and keep the same graph.
  • Offline-capable core — Embeddings run locally; chat can run through Ollama with cloud-tagged Gemma or a fully local model if you prefer air-gapped inference.
  • Inspectable safety — Guardrails and verdicts are plain code and JSON, reviewable in PRs, not hidden policy layers.

The line between past and present really is thinner here: a 1654 voice, a 2026 embedding model, and a UI that looks like you lit a candle with ghosts, witches and black cats flying around.

The Spirit of It

I built this because the Week 4 theme clicked for me all at once: mystery, the past, and that feeling that something old is still in the room with you. Ghost in the Machine is not only jump scares—it is memory, voices from another century, and stories that outlive the people who told them. A séance with Nicholas Culpeper fits that mood without forcing it. He is already a ghost in the historical sense, and his herbal is already a bridge between then and now.

Then there are the plants. I love them—the names, the folklore, the idea that a leaf or root once carried hope and danger in the same breath. Culpeper wrote the stars into every herb; the app lets me sit with that world for a while: candlelight, a question about foxglove or a restless night, and an answer that has to earn its place in the book before the ghost may speak it.

When the demo finally streamed the first reply word by word, it felt less like “AI output” and more like opening a door—uncertain, a little eerie, and rooted in something real that survived four hundred years. That is the moment I wanted. I am proud of this one.

For this Hacktoberfest, 2026, all apps I built for the challenges are garden-related, representing my love for gardening and software development.

Prize category

Best Use of Gemma — Gemma 4 is the model used, relevance grading, query rewrite, and in-character generation, all through Ollama’s open-weight distribution.

Disclosures

  • Parts of this post were drafted with AI assistance
  • AI-disclosure: some_ai

Top comments (0)