Real-time sermon verse retrieval system. Listens to live preaching, detects biblical themes using AI, and displays relevant Bible verses to the congregation.
Browser Microphone (Web Speech API)
↓
HTTP API (transcript)
↓
TranscriptBuffer (60s context)
↓
FetchRelevantVerse (DSPy pipeline)
├── ContainsRelevantVerses (fast filter)
├── IdentifyRelevantVerses (extract refs/queries)
└── ChromaDB search (RRF multi-query)
↓
VerseQueue (voting + aging)
↓
Display Worker (re-ranks with RankVerses)
↓
WebSocket → React Frontend
Queue-Based Processing:
- Detection runs every 10 seconds, adds candidates to queue
- Display worker polls every 1 second, respects cooldown
- Queue supports voting (same verse detected multiple times = higher priority)
- Verses age out if not displayed
DSPy Pipeline (2-step + ranking):
ContainsRelevantVerses- Fast yes/no: does this segment warrant a verse?IdentifyRelevantVerses- Extract direct references OR semantic search queriesRankVerses- At display time, re-rank queue candidates against current context
| Component | Technology |
|---|---|
| Speech-to-Text | Browser Web Speech API |
| LLM | Gemini 2.0 Flash via DSPy |
| Embeddings | SentenceTransformer all-mpnet-base-v2 |
| Vector Search | ChromaDB (local) |
| Backend | Python AsyncIO + aiohttp |
| Frontend | React |
| Real-time | WebSocket |
- Python 3.12+
- Node.js 18+
- API Keys:
- Deepgram - Speech-to-text (optional, for direct audio)
- Google AI Studio - Gemini API
./scripts/setup.shThis will:
- Create virtual environment
- Install Python and Node dependencies
- Check for
.envand prompt for API keys - Populate Bible verses with enrichment (~30 min first run)
- Test all connections
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
cd frontend && npm install && cd ..
# Configure environment
cp .env.example .env
# Edit .env with your API keys
# Populate Bible verses
python scripts/populate_verses.py
# Test connections
python scripts/test_connection.py# Required
DEEPGRAM_API_KEY=your_key # For direct audio transcription
GEMINI_API_KEY=your_key # For LLM processing
# Optional (defaults shown)
COOLDOWN_SECONDS=30 # Min time between displayed verses
RANKING_CONFIDENCE_THRESHOLD=75 # Min score to display verse (0-100)
ACTIVE_WINDOW_SECONDS=10 # Detection interval
CONTEXT_WINDOW_SECONDS=60 # Transcript context windowQuick start (both backend and frontend):
./scripts/run.shOr manually:
Terminal 1 - Backend:
source venv/bin/activate
python main.pyTerminal 2 - Frontend:
cd frontend
npm start- Frontend: http://localhost:3000
- HTTP API: http://localhost:8080
- WebSocket: ws://localhost:8765
- Open http://localhost:3000
- Click "Connect Microphone" in the Audio Control panel
- Allow microphone access when prompted
- Speak - verses will appear automatically
Edit config.yaml for detailed settings:
verse_retrieval:
top_k_candidates: 5 # Candidates per search
min_relevance_score: 75 # Display threshold
transcript:
context_window_seconds: 60
active_window_seconds: 10
theme_detection:
min_words: 15 # Minimum words before detectionpreacher_verse/
├── backend/
│ ├── dspy/
│ │ ├── programs/
│ │ │ └── fetch_verse.py # Main DSPy pipeline
│ │ └── signatures/ # DSPy signatures
│ │ ├── contains_relevant_verses.py
│ │ ├── identify_relevant_verses.py
│ │ └── rank_verses.py
│ ├── models/
│ │ ├── transcript_buffer.py # Rolling window transcript
│ │ ├── verse_queue.py # Queue with voting
│ │ └── verse_display_event.py
│ ├── processors/
│ │ └── sermon_processor.py # Main orchestration
│ ├── api/
│ │ └── http_server.py # HTTP API for transcripts
│ ├── services/
│ │ ├── websocket_server.py
│ │ └── verse_enricher.py
│ └── utils/
│ ├── config.py
│ └── logger.py
├── frontend/
│ └── src/
│ ├── App.js
│ └── components/
│ ├── VerseDisplay.js
│ ├── VersesPanel.js
│ ├── TranscriptPanel.js
│ └── AudioConfigPanel.js
├── data/
│ ├── chromadb/ # Vector embeddings (31k verses)
│ └── bible-kjv/ # Source JSON files
├── scripts/
│ ├── setup.sh # Full setup
│ ├── run.sh # Start system
│ ├── populate_verses.py # Load Bible into ChromaDB
│ └── test_connection.py # Test all services
├── main.py # Entry point
├── config.yaml
└── requirements.txt
- Transcript arrives from browser (Web Speech API)
- Buffer accumulates 60 seconds of context with timestamps
- Every 10 seconds, detection runs:
ContainsRelevantVerses: Should we look for a verse? (fast filter)IdentifyRelevantVerses: Extract references OR search queries- ChromaDB search with RRF (Reciprocal Rank Fusion) across multiple queries
- Top 3 candidates added to queue with voting scores
- Display worker (separate loop):
- Waits for cooldown (30s default)
- Re-ranks queue with
RankVersesagainst current context - Displays highest-scoring verse above threshold
- Removes from queue, marks as recently shown
- Gemini: Free tier (1,500 requests/day) or ~$0.075/1M tokens
- ChromaDB: Free (local)
- Browser Speech API: Free
Monthly estimate: ~$0-5 for typical usage
# Full setup (venv, deps, populate, test)
./scripts/setup.sh
# Start both backend and frontend
./scripts/run.sh
# Populate verses (enrichment enabled by default)
./scripts/populate.sh
# Populate without enrichment (faster)
./scripts/populate.sh --no-enrich
# Test connections
python scripts/test_connection.pyNo verses appearing:
- Check
RANKING_CONFIDENCE_THRESHOLD- lower it (e.g., 60) for more verses - Check
COOLDOWN_SECONDS- verses won't show faster than this interval - Verify ChromaDB has data:
python scripts/test_connection.py
Microphone not working:
- Ensure you're using Chrome or Edge (Web Speech API support)
- Check browser permissions for microphone access
- Try refreshing the page
High latency:
- Detection targets <2s inference time
- If slow, check Gemini API response times
- Consider reducing
top_k_candidates
MIT