Two LLMs argue. A third one judges. Live-streamed, voiced, scored.
Two minds — One topic — One verdict.
Debate·AI runs a multi-turn structured debate between two large language models on any topic you pick — one playing affirmative, the other negative. A third LLM watches the whole transcript, scores both sides, and delivers a written verdict.
The frontend streams every turn over Server-Sent Events as it's generated, voices each argument with neural TTS, and reveals the text word-by-word in lockstep with the audio so the on-screen reading matches what's actually being spoken.
Built as an exploration of multi-LLM orchestration, real-time streaming, and synchronized multimodal output in a single full-stack project.
1. Configure the debate — pick a topic, set turns (2–12), customize each debater's persona and stance.
2. Watch it happen live — turns stream in over SSE, the active speaker glows, audio plays while text reveals in sync.
3. Watch the rebuttals — each debater rebuts the previous point before adding their own, in a strict alternating rotation.
4. The judge delivers a verdict — structured scoring (1–10 each), a winner, and 2–4 sentences of reasoning.
The two debaters run on different model families on purpose so they sound genuinely different rather than two clones with different prompts:
| Role | Provider | Model |
|---|---|---|
| Debater A | Mistral AI | mistral-small-2506 |
| Debater B | Groq | llama-3.1-8b-instant |
| Judge | Groq | llama-3.1-8b-instant |
Every event — turn start, speaker change, full turn text, final verdict — is pushed to the frontend over Server-Sent Events as the generator yields it. No polling, no waiting for the whole debate to complete.
The trickiest piece. When a turn arrives, the frontend:
- Fetches the audio blob from
/api/tts(Microsoft Edge neural voices viaedge-tts). - Reads the audio's true duration after
loadedmetadata. - Reveals the transcript word-by-word over exactly that duration using
requestAnimationFrame, so the on-screen text matches the voice you're hearing. - Queues the next turn so debaters never talk over each other.
It also degrades gracefully — if TTS fails or the user hits mute, the reveal still runs at a natural pace (~3 words/sec) without audio.
The judge uses LangChain's with_structured_output(Verdict) so the verdict is never free-form text — it's a typed object with winner, debater_a_score, debater_b_score, and reasoning, validated by Pydantic before it leaves the backend.
Each turn has a target length ([40, 70, 100, 130, 150, 150] by default) so debates ramp up — short opening jabs, longer rebuttals as the argument deepens.
flowchart LR
User((User)) -->|configure| UI[React + Vite UI]
UI -->|POST /api/debate/stream| API[FastAPI]
API --> Orchestrator[stream_debate generator]
Orchestrator -->|alternates turns| ChainA[Debater A chain<br/>Mistral]
Orchestrator --> ChainB[Debater B chain<br/>Groq Llama]
Orchestrator -->|final transcript| Judge[Judge chain<br/>Groq + structured output]
Orchestrator -.->|SSE: start, turn, verdict, done| UI
UI -->|POST /api/tts per turn| TTS[edge-tts<br/>Aria + Guy voices]
TTS -.->|audio/mpeg| UI
UI -->|synced reveal| User
Backend — Python 3.10+, FastAPI, LangChain, langchain-mistralai, langchain-groq, Pydantic v2, Uvicorn (SSE streaming), edge-tts (neural TTS).
Frontend — React 18, TypeScript, Vite, Tailwind CSS, Framer Motion (animations), native fetch + ReadableStream for SSE parsing.
Testing — pytest + httpx (49 backend tests), Vitest + Testing Library + jsdom (42 frontend tests). Chains, edge-tts, and fetch are stubbed so the suite runs offline with zero API spend.
python -m venv venv
venv\Scripts\activate # Windows (source venv/bin/activate on Unix)
pip install -r requirements.txt
cp .env.example .env # add MISTRAL_API_KEY + GROQ_API_KEY
python server.py # runs on 127.0.0.1:8001 with hot reloadPort 8001 is the default. On Windows, port 8000 is often reserved by Hyper-V/WSL dynamic port ranges and produces a misleading
WinError 10048. If 8001 is also blocked, change the port inserver.pyand the proxy target infrontend/vite.config.ts.
cd frontend
npm install
npm run dev # http://localhost:5173python main.py # random topic
python main.py -t "Should AI be regulated" # custom topic
python main.py --list-topicsA full SQA suite covers both ends. No real LLM or TTS calls happen in CI — chains and edge_tts.Communicate are patched with deterministic fakes.
python -m pytest tests -vCovers: config validation, Pydantic models, transcript helpers, stream_debate event ordering and error paths, run_debate CLI flow, FastAPI routes (/api/health, /api/defaults, /api/debate/stream happy + validation paths, /api/tts voice mapping + validation + 502 fallback), CORS preflight.
cd frontend
npm testCovers: SSE parser (chunked reads, malformed JSON, comment lines), useTTS hook (mute persistence, empty-text fast path, cancellation), every component (SetupForm, SpeechBubble, Debater, DebateArena, VerdictReveal), and the full App phase machine setup → debating → done.
src/debateai/
config.py # topics, default budgets, personas, model IDs
models.py # Verdict pydantic + transcript types
events.py # streaming event TypedDicts
prompts.py # debater + judge prompt templates
chains.py # LLM init + chain builders
debate.py # stream_debate generator + run_debate CLI wrapper
cli.py # argparse entry
api.py # FastAPI app, SSE endpoint, TTS, CORS
frontend/src/
App.tsx # phase machine: setup → debating → done
types.ts
lib/
api.ts # SSE stream parser
tts.ts # synced audio + word-by-word reveal
components/
SetupForm.tsx # topic + personas + turns config
DebateArena.tsx # two-column layout, live transcript
Debater.tsx # animated Baymax avatar
SpeechBubble.tsx # framer-motion bubble
VerdictReveal.tsx # final verdict card
tests/ # pytest suite (49 tests)
frontend/src/**/*.test.{ts,tsx} # vitest suite (42 tests)
- Topic — pick from built-in list or type your own
- Number of turns — 2 to 12, slider
- Each debater — display name, persona description, stance
- Voice — Aria (US female) for A, Guy (US male) for B; mute toggle persists in localStorage
- Shareable debate links (
/debate/:id) - Persistent debate history
- Streaming token-by-token output (currently per-turn)
- Multi-model judge ensemble
- User-selectable voices
Built with FastAPI, React, LangChain, Mistral, Groq, and edge-tts.



