DebateAI:讓兩個 AI 為一個題目吵一架
輸入一個題目,看樂觀者與懷疑者兩個 AI 輪流攻防、自己上網查資料,主持人每輪小結、最後寫成總報告。我用這個期末專題測試 LangGraph 多代理人流程:FastAPI 以 SSE 即時串流,Next.js 前端部署在 Cloudflare Pages。
輸入一個題目,看樂觀者與懷疑者兩個 AI 輪流攻防、自己上網查資料,主持人每輪小結、最後寫成總報告。我用這個期末專題測試 LangGraph 多代理人流程:FastAPI 以 SSE 即時串流,Next.js 前端部署在 Cloudflare Pages。
一分鐘看懂 DebateAI 在做什麼。封面與動畫角色由 AI 生成(Codex),旁白是 AI 語音(Gemini TTS),動畫裡的對白是示範。
大三上「進階 Web 程式設計」的期末專題,我想做一個「多個 AI 分工合作」的東西,而不是呼叫一次 LLM 就結束。
當時我對 LangGraph 很好奇:它號稱可以把多個 AI 角色、工具呼叫和輪次狀態畫成一張圖來管理,但我沒有真的用過。與其讀文件,不如做一個一定要用到這些功能的產品。
「辯論」剛好是很好的題材:
另一半的動機很單純:我想看看兩個 AI 互相吵架,到底會吵成什麼樣子。
| 角色 | 立場 | 做什麼 |
|---|---|---|
| 🟢 樂觀者(正方) | 支持題目 | 先發言,提出論點和資料;每次發言 2~3 句,不准讓步 |
| 🔴 懷疑者(反方) | 質疑題目 | 找對方論點的漏洞,要求證據;需要時先上網查資料再反駁 |
| 🔵 主持人(總結者) | 中立 | 每輪結束做 80~120 字的小結;最後一輪寫 200~300 字的總報告:雙方論點、分歧、評估和建議 |
兩位辯手都可以自己決定要不要上網搜尋。搜尋結果只取前 3 筆的標題和摘要交給模型,畫面上的狀態列會即時顯示「樂觀者正在搜尋:……」。

首頁:輸入題目,按下「開始辯論」。側欄可以開新辯論,也列出最近 5 場。

文字透過 SSE 一個字一個字出現;辯手決定查資料時,狀態列會顯示正在搜尋的關鍵字。

每輪結束,主持人會整理雙方的主要論點和還沒解決的分歧,再進入下一輪。
三輪結束後,主持人會寫出一份總報告:雙方論點、分歧、評估和建議,前端用 react-markdown 渲染標題和清單。
老實說,這張圖我現在截不到:線上版跑到第 2、3 輪時,會撞上 Groq 帳號每分鐘 8,000 個 token 的上限(HTTP 429),而程式遇到 429 不會等一下再重試,辯論就中斷了。

歷史紀錄頁每頁列 10 場,點進去可以回看完整內容。辯論要完整跑完才會存檔,所以因為額度中斷之後,這裡目前是空的。

設定頁:主題(淺色/深色/跟隨系統)和語言(中文/英文)。

關於頁。上面列的「論點附來源與信心度」「爭點矩陣」「一鍵匯出」是當時的規劃,程式裡還沒有做。

價格方案頁是產品化的練習,頁尾也註明了價格只是範例,網站沒有收費。
整個系統分成三層:
fetch 加 ReadableStream 接收串流,不需要額外的 WebSocket。status、speaker、token、tool_start、tool_end、speaker_end、complete、error),前端依事件類型更新畫面。一場辯論就是一張狀態圖:
整場辯論共用一份狀態:訊息、題目、目前是誰、第幾輪、搜尋了幾次、上一個發言的是誰,以及這場辯論使用的語言。每一步要去哪裡,都由圖上的條件邊決定,程式裡不需要一堆 if 去追蹤「現在輪到誰」。
web_search_tool 會先用 Tavily 搜尋;Tavily 失敗就改用 DuckDuckGo;兩個都搜不到,就回一段提示,讓辯手用既有知識發言,而不是讓整場辯論卡住。
主模型是 Groq 上的 gpt-oss-120b,失敗時依序切換到 kimi-k2-instruct 和 llama-3.1-8b-instant。這個備援一開始其實沒有生效,下一節會講原因。
| 時間 | 做了什麼 |
|---|---|
| 11 月下旬 | 定題、開 repo,把 FastAPI 和 Next.js 放進同一個 monorepo,確認兩端連得通 |
| 12 月上旬 | 先用假資料做 SSE 串流,確定前端能一個字一個字顯示,再換成真正的 Groq 串流;前後端都部署上線 |
| 12 月中旬 | 辯論流程改成 LangGraph StateGraph,加入搜尋工具與主持人 |
| 12 月下旬 | 歷史紀錄、中英雙語、主題切換、模型備援、自動化測試 |
| 1 月初 | 收尾與驗收 |
\\n\\n,不是真正的換行,瀏覽器不認得事件的邊界。我寫了一個 sse_event() 輔助函數,統一產生 SSE 格式。.env.local 的優先權比正式環境變數高,部署後前端還在呼叫本機 API。整理環境變數的載入順序後才修好。astream_events v2 之後才抓得到工具事件;另外加上每個角色最多查 3 次的上限。gpt-oss-120b 才穩定。max_retries 大於 0 時,會在內部對同一個模型一直重試,把限流錯誤吃掉,LangChain 的 with_fallbacks() 根本沒機會接手。我把 max_retries 設成 0,讓錯誤立刻拋出,備援才真正生效。with_fallbacks() 再 bind_tools(),備援模型就沒有工具。順序改成先對每個模型 bind_tools(),再包備援。/history/[id],改用 ?id= 查詢參數加 Suspense。DebateAI 的完整原始碼,包含 FastAPI 後端、LangGraph 流程、Next.js 前端與 CI 設定。
可以直接輸入題目開始一場辯論。
用來管理多個 AI 角色、工具呼叫與輪次狀態的框架。