A LangGraph-based multi-agent system where three LLM agents with distinct cognitive biases β Optimist, Pessimist, and Devil's Advocate β debate any topic through multiple rounds of structured argumentation. Agents detect real semantic divergence, track concessions with attribution, and produce an auditable consensus report with a formula-derived confidence score.
Built as a portfolio project demonstrating: multi-agent LangGraph graphs, semantic divergence detection, Pydantic structured outputs, SQLite persistence, and Streamlit streaming UI.
User: "Is remote work net positive for companies?"
Round 1 (parallel):
π’ Optimist β "Remote work increases productivity by 15-20%..."
π΄ Pessimist β "Collaboration and culture suffer irreparably..."
π Devil's Adv β "The productivity gains are selection bias..."
Divergence score: 0.82 β Round 2 triggered
Round 2 (rebuttal):
π’ Optimist β Concedes: "Culture risks are real for junior employees"
π΄ Pessimist β Maintains position
π Devil's Adv β Shifts: "Hybrid is the actual optimum"
Final Report:
Confidence: 71% | Status: Converged
Consensus: ["Async communication tools are essential", ...]
Disputed: [{"topic": "Culture impact", "optimist": "...", "pessimist": "..."}]
- Python 3.10+
- An LLM API key β pick one backend:
- SiliconFlow (domestic, free credits, recommended for China) β https://cloud.siliconflow.cn/
- Anthropic (direct API key or corporate proxy)
- Groq / OpenAI / Cerebras / Together / SambaNova (see
.env.example)
git clone https://github.com/xyma2003/multi-agent-debate.git
cd multi-agent-debate# Option A: venv (built-in)
python3 -m venv .venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows
# Option B: conda
conda create -n debate-agent python=3.10
conda activate debate-agentpip install -r requirements.txtNote: First run will download the
BAAI/bge-small-en-v1.5embedding model (~130MB) from HuggingFace. This happens automatically on first debate start.
Copy the example env file and fill in your credentials:
cp .env.example .envOption A β SiliconFlow / OpenAI-compatible (domestic, recommended for China):
# .env
LLM_BACKEND=openai
OPENAI_API_KEY=sk-... # SiliconFlow key
OPENAI_API_BASE=https://api.siliconflow.cn/v1
OPENAI_MODEL=Qwen/Qwen3-32B # or deepseek-ai/DeepSeek-V3Get a free SiliconFlow key at https://cloud.siliconflow.cn/ (free credits on signup).
Option B β Direct Anthropic API key (overseas):
# .env
ANTHROPIC_API_KEY=sk-ant-api03-...Option C β Internal proxy (e.g. corporate proxy):
# .env
ANTHROPIC_BASE_URL=https://your-proxy-base-url
ANTHROPIC_AUTH_TOKEN=your-auth-token
ANTHROPIC_CUSTOM_HEADERS=X-Custom-Header: valueThe app auto-loads
.envviapython-dotenv(withoverride=True, so.envwins over stale shell vars). No manualexportneeded β juststreamlit run app.py.
streamlit run app.pyOpen http://localhost:8501 in your browser.
- Enter any topic or question (e.g. "Is AI regulation good for innovation?")
- Set Max Rounds (1β3) β more rounds = more rebuttal cycles
- Click Start Debate β watch agents argue in real time
- Read the final report: confidence score, verdict, consensus/disputed split, reasoning trace
- Past debates appear in the sidebar for instant replay without re-running agents
User topic
β
βΌ
initialize βββΊ [Optimist | Pessimist | Devil's Advocate] (Round 1, parallel)
β
βΌ
collect_round1
β
βΌ
divergence_check_node β semantic similarity on key_claims embeddings
β
βββββββββββ΄ββββββββββ
diverged converged / max_rounds
β β
[rebuttal round] synthesize_stub
β β
(loop back) save_node β SQLite
β
DebateReport
Key design decisions:
| Decision | Rationale |
|---|---|
| Methodology-based personas | "You apply bear-case scenario analysis" beats "be pessimistic" β prevents sycophancy collapse |
Divergence on key_claims (not full text) |
Full argument embeddings cluster by topic; claim-level embeddings preserve disagreement signal |
| Confidence formula in code | (1 - max_divergence) * round_adjustment β never LLM-invented, always auditable |
| Concession attribution | Each concession records triggered_by_agent + triggered_by_claim β full reasoning chain |
| Single flat StateGraph | No subgraph nesting β explicit state control and checkpointing for auditable trace |
multi-agent-debate/
βββ app.py # Streamlit UI β single-file app
βββ conftest.py # Pytest config + backend-aware skip markers
βββ requirements.txt # Pinned dependencies
βββ .env.example # API credential template
βββ debates.db # Auto-created SQLite DB on first run
βββ debate/
β βββ graph.py # StateGraph assembly + compiled graph singleton
β βββ state.py # DebateState TypedDict + all Pydantic models
β βββ store.py # SQLite save / load / list API
β βββ divergence.py # compute_divergence() with sentence-transformers
β βββ classify.py # NLI cross-encoder stance classification
β βββ llm.py # Auth-aware ChatAnthropic factory + retry wrapper
β βββ prompts.py # Methodology-based system prompts (PROHIBITION blocks)
β βββ prompts_adaptive.py # Adaptive PROHIBITION variants (ablation study)
β βββ nodes/
β βββ initialize.py # Sets debate_id, round_num=0
β βββ agents.py # optimist_node, pessimist_node, devil_node
β βββ dispatch.py # dispatch_round1 + route_divergence routing functions
β βββ collect.py # collect_round1 fan-in (reused for all rounds)
β βββ divergence_check.py
β βββ synthesize.py # Synthesizer β DebateReport assembly
β βββ save.py # save_node (SQLite side-effect, returns {})
βββ benchmark/
β βββ questions.json # 30 benchmark questions (business/tech/policy/prediction)
β βββ evaluator.py # PDS / HR / SSS / RTC metric definitions
β βββ baseline.py # Single-LLM runner
β βββ variants.py # 6 ablation variants
β βββ run_experiment.py # CLI entry point
βββ results/
β βββ full_system.json # Multi-agent fixed devil (n=10)
β βββ original_devil.json # Multi-agent old devil (n=10)
β βββ single_llm.json # Single-LLM baseline (n=10)
β βββ nli_detection.json # NLI divergence (n=2)
βββ analysis/
β βββ analysis.ipynb # 7-section analysis notebook
β βββ fig_*.png # Experiment figures
βββ tests/ # 5-phase test suite
β βββ test_phase1.py # Graph foundation + smoke test
β βββ test_phase2.py # Debate loop + divergence detection
β βββ test_phase3.py # Synthesis + confidence formula
β βββ test_phase4.py # SQLite persistence + replay
β βββ test_phase5.py # UI tests
βββ PAPER.md # Research writeup (Adaptive PROHIBITION, arXiv format)
βββ BLOG_EN.md # English blog post
βββ BLOG_ZH.md # Chinese blog post
Ablation study across 4 system variants, 10 questions each (business + technology topics).
| Variant | n | PDS β | HR β | SSS | Rounds |
|---|---|---|---|---|---|
| Multi-agent (fixed devil) | 10 | 0.2242 | 0.0093 | 1.000 | 1.00 |
| Single-LLM baseline | 10 | 0.2160 | 0.0129 | N/A | 1.00 |
| Multi-agent (old devil prompt) | 10 | 0.1707 | 0.0077 | 1.000 | 1.00 |
| Multi-agent + NLI detection | 2 | 0.1439 | 0.0050 | 0.883 | 3.00 |
- PDS (Position Diversity Score): avg pairwise semantic distance between agents' final positions. Higher = more genuinely distinct viewpoints.
- HR (Hedge Ratio): hedge words / total words. Lower = less "on-the-other-hand" hedging.
- SSS (Stance Stability Score): similarity between Round-1 and final position embedding. Only meaningful in multi-round debates.
Finding A β PROHIBITION reduces sycophantic hedging by 28% Multi-agent HR (0.0093) vs single-LLM HR (0.0129). The PROHIBITION constraints successfully prevent agents from retreating to balanced, non-committal language.
Finding B β PDS Paradox: wrong devil prompt inverts diversity Old devil prompt ("challenge the dominant view") caused 2-vs-1 alignment β devil auto-sided with pessimist against optimist, producing lower PDS than single-LLM (0.1707 < 0.2160). Fixed by redefining devil's role as "Assumption Challenger" who targets the shared premise both sides take for granted. Post-fix PDS (0.2242) exceeds single-LLM baseline.
Finding C β Cosine similarity is broken for stance detection 100% of cosine-based debates terminated after Round 1 (divergence scores 0.097β0.258, all below 0.75 threshold). Cosine measures topic overlap, not stance opposition β "VC accelerates growth" and "VC destroys growth" score as similar because they share vocabulary. NLI cross-encoder correctly detects CONTRADICTION regardless of vocabulary overlap, enabling genuine multi-round debate (SSS = 0.883 vs 1.000).
See PAPER.md for the full research writeup (Adaptive PROHIBITION in Multi-Agent Debate, Xinyue Ma, 2026) and analysis/analysis_executed.ipynb for figures.
# Requires VPN if using Groq backend
cd multi-agent-debate
# Run all variants (n=10 each, 2-min delay between questions for rate limits)
python benchmark/run_experiment.py --variants full_system single_llm --limit 10 --delay 5
python benchmark/run_experiment.py --variants nli_detection --limit 10 --delay 120
# View results summary
python -c "
import json, statistics
for v in ['full_system', 'single_llm', 'original_devil', 'nli_detection']:
with open(f'results/{v}.json') as f: d = json.load(f)
pds = [r['pds'] for r in d['results']]
hr = [r['hedge_ratio'] for r in d['results']]
print(f'{v}: n={len(pds)} PDS={statistics.mean(pds):.4f} HR={statistics.mean(hr):.4f}')
"# Fast unit tests only (no API calls, ~5 seconds)
python -m pytest tests/ -m "not integration" -v
# Full suite including live LLM calls (~5 minutes)
python -m pytest tests/ -vThis project supports LangSmith tracing for debugging and monitoring agent execution. When enabled, every node (initialize β 3 parallel agents β divergence check β synthesize β save) and every LLM call is captured as a trace in the LangSmith dashboard, showing:
- Per-node latency (which step is slow)
- Full LLM input/output (prompt sent, response received)
- Token consumption and cost per call
- Error traces when a node fails
- Create a free account at smith.langchain.com
- Add these to
.env:
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=ls-... # from LangSmith dashboard
LANGCHAIN_PROJECT=multi-agent-debate # project name in dashboard- Run as usual β traces appear in real-time on the LangSmith dashboard.
No code changes required. LangGraph nodes and LangChain chat models automatically report to LangSmith when these env vars are set.
| Component | Library | Version |
|---|---|---|
| Agent orchestration | LangGraph | 1.1.9 |
| LLM (default) | SiliconFlow Qwen/Qwen3-32B via LLM_BACKEND=openai (ε½ε
η΄θΏ) |
β |
| LLM (alt) | Groq / Claude / Cerebras / Together / SambaNova via LLM_BACKEND=* |
β |
| Structured outputs | Pydantic | 2.x |
| Divergence (cosine) | sentence-transformers + bge-small-en-v1.5 | 5.4.1 |
| Divergence (NLI) | sentence-transformers + cross-encoder/nli-deberta-v3-small | 5.4.1 |
| Persistence | SQLite (stdlib) | β |
| UI | Streamlit | 1.56.0 |
Built as a portfolio project to demonstrate multi-agent LLM system design.
Resume bullet:
Built a multi-agent debate system where specialized LLM agents with distinct cognitive biases analyze topics independently, then engage in structured argumentation with divergence detection and concession tracking, producing auditable consensus reports with confidence scoring. (LangGraph Β· Claude API Β· Pydantic Β· Streamlit Β· SQLite)