A real-time, AI-judged debate app where two players debate a topic on video and an AI (Gemini) scores them with evidence-backed reasoning using a RAG pipeline (OpenSearch + embeddings). The project includes a Next.js frontend, a Spring Boot backend (API + signaling), and Python worker scripts to chunk/embed and index evidence into OpenSearch. BIG THANKS to Kaelyn Cho for her amazing artwork she's provided for this project <:
- Architecture
- Tech Stack
- Local development
- RAG pipeline
- Gemini & prompt notes
- UX & features
- Troubleshooting
- Contributing
- Frontend: Next.js (App Router), React, Tailwind CSS, Framer Motion, react-confetti
- Backend: Spring Boot (Java 21), REST controllers, WebRTC signaling
- RAG worker: Python, FastAPI, HuggingFace SentenceTransformers (all-MiniLM-*), OpenSearch (k-NN)
- LLM: Google Gemini (via REST) with prompt → JSON parsing
Prereqs: Node.js, Java 21 + Maven, Python 3.11+, OpenSearch (or remote), Git
-
Frontend (apps/web)
- cd apps/web
- npm install
- npm run dev
- Open http://localhost:3000
-
Backend API (apps/api)
- cd apps/api
- mvn spring-boot:run
- Config via
src/main/resources/application.properties(or environment vars)
-
Worker / RAG (apps/worker)
- python -m venv .venv
- .venv\Scripts\activate (. ./venv/bin/activate on mac/linux)
- pip install -r requirements.txt
- uvicorn semantic_search_api:app --reload --port 8000
- Use scripts in
apps/worker/scripts/to chunk, embed, and index to OpenSearch
Environment variables (examples):
# Gemini
GEMINI_API_KEY=<primary>
GEMINI_API_KEY2=<fallback>
# OpenSearch (for worker/fastapi)
OPENSEARCH_URL=http://localhost:9200
OPENSEARCH_USER=admin
OPENSEARCH_PASS=adminNotes:
- The app will POST to
/api/debates/:id/resultsto trigger scoring and RAG evidence retrieval. - Use the RAG scripts to populate an OpenSearch index before relying on key-evidence output.
- Chunk documents (PDFs, text) with
chunk_and_embed.pyorindex_chunks.py. - Embed using SentenceTransformers and store k-NN vectors in OpenSearch.
apps/worker/semantic_search_api.pyexposes/searchused by the API to gather evidence chunks for prompts.
If you want to run the full RAG pipeline locally (OpenSearch + worker + Java API → Gemini), follow these steps:
- Start OpenSearch (example using Docker):
# run a single-node OpenSearch for local testing
docker run -d --name opensearch -p 9200:9200 -e "discovery.type=single-node" opensearchproject/opensearch:latest- Prepare the Python worker environment and install deps:
cd apps/worker
python -m venv .venv
.venv\Scripts\activate # Windows
# or: source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt- Index your content (populate the OpenSearch index):
# from the repo root
python apps/worker/scripts/index_chunks.py- Run the worker FastAPI semantic-search service (keeps running):
# from the repo root (uses the module path)
python -m uvicorn apps.worker.scripts.semantic_search_api:app --reload --host 127.0.0.1 --port 8000- Verify the
/searchendpoint returns chunks:
curl -s -X POST "http://localhost:8000/search" -H "Content-Type: application/json" -d '{"query":"climate change","top_k":3}' | jq- Start the Java API and ensure it can reach the worker on
http://localhost:8000(theGeminiServiceposts to/search). Set Gemini keys and any needed OpenSearch env vars:
OPENSEARCH_URL=http://localhost:9200
GEMINI_API_KEY=<your_key_here>
GEMINI_API_KEY2=<optional_fallback>Notes:
- Re-run
index_chunks.pywhenever you add or update source documents to refresh the evidence index. - If your worker binds to
127.0.0.1/localhost, the Java API must run on the same machine or be able to reach that host; bind to0.0.0.0if you need external access. - Keep secrets out of client-side code — the Gemini call should remain server-side (the Java API or worker).
- Gemini is invoked from
apps/api/src/main/java/com/didicook/api/service/GeminiService.java. - Important behaviors implemented:
- Attempts fallback API keys (GEMINI_API_KEY, GEMINI_API_KEY2..GEMINI_API_KEY5).
- Transcript builder maps numeric speakers to real
player1Name/player2Namewhen provided. - Sends an explicit JSON schema instruction (no sample payload embedded in final prod prompt) to avoid sample bias.
Tips:
- If you see parsing errors in the backend response, the returned text may include extra content — we try to extract the first top-level JSON object.
- For debugging, add a temporary log of the final prompt before sending (avoid logging secrets in production).
- In-call judging: users remain on the call while AI scores (results appear as a themed overlay)
- Animated judging state: an "AI is judging..." modal with subtle animations (framer-motion)
- Results page: flippable
CookbookCardstyle cards (front/back), lined-paper background aesthetic - Confetti shows only for the viewer when they are the winner (visitor-specific)
- Tie handling displays "It's a tie!" with no confetti
- Dev conveniences: sample-mode used during development earlier; currently the app fetches live results by default