Two LLMs from different vendors research the same question independently, debate each other's claims round by round, and a neutral referee distills their agreed claims into a LaTeX-compiled PDF report.
A Python pipeline that answers a research question with two LLM agents instead of one. Gemini (default model gemini-1.5-pro-latest) plays the "Explorer" — broad, higher temperature (0.7) — and Claude (default model claude-sonnet-4-5-20250929) plays the "Judge" — strict, lower temperature (0.5). Both draft answers independently from the same evidence, then compare and revise claim by claim over several rounds. A referee stage (Claude at temperature 0.2) extracts a consensus report, which is rendered to an academic-style PDF through LaTeX and passed through an automated quality-assurance step. The project's working slogan, repeated across the code and the UI, is Truth = A ∩ B.
The pipeline is orchestrated as a LangGraph state machine (src/workflow.py) over a shared Pydantic DebateState (src/schemas.py) and driven from a Streamlit web UI (app.py, Turkish-language interface).
A single model's answer inherits that model's blind spots. This project is an experiment in reducing that risk through cross-model consensus: if two independently prompted models from different vendors, given the same sources, both assert a claim, that claim is kept and anything only one asserts is meant to be dropped. This trades coverage for confidence rather than making the output hallucination-proof. The debate protocol has changed over time — the repo still carries an older adversarial "devil's advocate / cross-examination" implementation alongside the current collaborative-convergence one (see Status & caveats).
The workflow builds a LangGraph StateGraph with these nodes: grounding → parallel_drafting → iterative_debate → intersection_synthesis → latex_generation → pdf_compilation → quality_assurance → approval_decision, plus a conditional pdf_revision edge that loops back to latex_generation.
- Grounding (
src/phases/phase_1_grounding.py) — three modes:offline(model knowledge only),internet, orauto. In auto mode a keyword check (TIME_SENSITIVE_KEYWORDSinsrc/config.py, e.g. "news", "price", "2025", "güncel") picks internet vs offline. Internet mode queries the Tavily search API (advanced depth, up to 5 results, withpinterest.comandquora.comexcluded) and packs the results into a shared context of numbered, URL-tagged sources. Any search error — including a missing Tavily key — is caught and falls back to offline mode. - Parallel drafting (
src/phases/phase_2_parallel_drafting.py) — both agents answer the same question from the same shared context concurrently (asyncio.gather), with no visibility into each other's output. Their system prompts (src/prompts.py) require every claim to carry a[Source: ...]tag. Gemini runs at 0.7, Claude at 0.5. - Iterative debate (
src/phases/phase_3_4_iterative_debate.py) — for up toMAX_ROUNDSrounds (default 3), each agent is shown both agents' previous answers and must return a JSON block — a claim-by-claim comparison table marking each claim agree / partial / conflict, each with a 0–1 confidence and a source per side — followed by a plain-text revised answer. Claims that both agents mark "agree" with confidence at or above a per-round threshold (0.70 in round 1, 0.80 in round 2, 0.85 thereafter) are recorded as "locked"; from the next round on the agents are instructed to respect locked claims and focus on the disputed points. Four metrics are computed each round — a symmetric, intersection-based consensus plus each agent's individual coverage and their average — and the debate converges only when all four reach the convergence threshold (default 0.95). Otherwise the loop runs to max rounds and sets aforced_stopflag. - Intersection synthesis (
src/phases/phase_5_intersection.py) — Claude, acting as a neutral referee at temperature 0.2, is asked to keep only claims both drafts assert, stated without hedging and supported by the sources; anything one-sided, uncertain, or contradicted is meant to be excluded ("when in doubt, exclude"). - LaTeX generation (
src/agents/latex_generator.py) — Claude converts the consensus report into an article-class LaTeX document with Turkishbabelsupport. Astrip_citations()pass then removes all\cite/\citep/\citetcommands, inline[n]reference numbers,natbib, and any bibliography/References section. - PDF compilation (
src/agents/pdf_compiler.py) — writes the LaTeX into a temp directory and runspdflatex -interaction=nonstopmodetwice; the whole compile is retried up toMAX_LATEX_RETRIEStimes (default 3) with a per-run timeout (LATEX_TIMEOUT, default 30s). Only the resulting PDF is copied into./output/pdfs. - Quality assurance and approval (
src/agents/qa_agents.py) — Gemini Vision scores the first rendered page image (layout, typography, tables/figures, professional appearance) and Claude scores the extracted PDF text (question completeness, citation accuracy, structure, academic standards). If the average of the two scores is belowQA_THRESHOLD(default 75) the pipeline regenerates the LaTeX/PDF, up toMAX_PDF_REGENERATIONStimes (default 2). QA is intentionally non-blocking: if a QA model's JSON can't be parsed or the call errors, that stage auto-approves with a default score of 85, so a PDF is essentially always produced.
Supporting modules: src/api_clients.py wraps the Anthropic and google-genai SDKs with tenacity retry/backoff and enables Google Search grounding on the (text) Gemini calls; src/prompts.py holds every agent persona and the debate handshake prompt; src/schemas.py defines DebateState and the DebateRound / ComparisonClaim / LockedClaim records.
Prerequisites:
- Python 3.11 (the Docker image is
python:3.11-slim;SETUP_GUIDE.mdtargets 3.11+) - A LaTeX distribution providing
pdflatex(TeX Live or MiKTeX) — the compiler raises"pdflatex not found. Please install TeX Live or MiKTeX."if it is missing - Poppler utilities (
pdf2imageuses them to rasterize the PDF for visual QA) - API keys:
ANTHROPIC_API_KEYandGOOGLE_API_KEYare always required;TAVILY_API_KEYis additionally required forinternetandautomodes (config.validate_api_keys)
Local run:
pip install -r requirements.txt
cp .env.example .env # fill in ANTHROPIC_API_KEY, GOOGLE_API_KEY, TAVILY_API_KEY
streamlit run app.pyThe UI serves on http://localhost:8501. Compiled PDFs are written to ./output/pdfs and run logs to ./output/logs (the generated LaTeX source is compiled in a temporary directory and is not persisted).
Docker (installs TeX Live packages and Poppler for you):
docker compose up --buildConfiguration is environment-driven; .env.example lists every setting: model IDs (CLAUDE_MODEL, GEMINI_MODEL), MAX_ROUNDS, CONVERGENCE_THRESHOLD, QA_THRESHOLD, DEFAULT_RESEARCH_MODE, LATEX_TIMEOUT, MAX_LATEX_RETRIES, MAX_PDF_REGENERATIONS, and the output directories. Temperatures and token limits are hard-coded in src/config.py, not exposed as env vars.
A personal, experimental prototype, rough in several places. Limitations visible in the code:
- Debate JSON parsing is fragile. Agents must emit strict JSON plus free text;
parse_comparison_responsefalls back to regex when the JSON is malformed, and that fallback pegs a claim's consensus at 0.5. Because convergence needs all four metrics at or above 0.95, a fallback essentially guarantees the round cannot converge, so such runs end at max rounds ("forced stop"). - The debate's output is not what gets published.
intersection_synthesisis given the original Phase-2 drafts (state.gemini_draft/state.claude_draft), not the revised answers or the locked-claim list produced by the debate loop — those currently only drive the convergence check. - The final PDF has no citations, by design. Sources are required in the drafts and debate tables, but
strip_citations()removes all citation markers, reference numbers, and bibliographies from the final LaTeX. - QA can auto-approve. On any QA JSON-parse failure or API error the visual/content scorer returns 85 and passes; the Streamlit sidebar states outright that QA will not block PDF generation.
- Docs and code disagree on the convergence default. The code default is
0.95(src/config.pyand.env.example); the design noteCONSENSUS_SYSTEM.mddiscusses0.85. The code value is what runs. - Language. The UI and most user-facing prompts are Turkish; reports compile with Turkish
babelsupport. Agent system prompts are a mix of English scaffolding and Turkish instructions. - Dead / stray code.
phase_3_cross_examination.pyandphase_4_convergence.py, plus theDEVIL_ADVOCATE_STRICT/DEVIL_ADVOCATE_OPENandPROMPT_TEMPLATE_CRITIQUEprompts, are leftovers from the earlier adversarial protocol — still imported bysrc/phases/__init__.pybut not used by the workflow, which runs the collaborativerun_debate_loop.src/utils/citation_validator.pyis unused (and would error, since it callscall_claude_apiwithout the requiredsystem_prompt).IMPLEMENTATION_SUMMARY.mdstill describes some removed pieces as active. Ad-hoc scripts (test_api.py,test_gemini_*.py,test_simple.py,list_models_new.py) sit at the repo root; there is no automated test suite. - Dependency age. Some pins are old (e.g.
langgraph==0.0.32), and the defaultgemini-1.5-pro-latestmodel id may need updating for a given account.
MIT — Copyright (c) 2026 Kerem Gür. See LICENSE.