已完成 AI LangGraph FastAPI Next.js

DebateAI:讓兩個 AI 為一個題目吵一架

輸入一個題目,看樂觀者與懷疑者兩個 AI 輪流攻防、自己上網查資料,主持人每輪小結、最後寫成總報告。我用這個期末專題測試 LangGraph 多代理人流程:FastAPI 以 SSE 即時串流,Next.js 前端部署在 Cloudflare Pages。

一分鐘看懂 DebateAI 在做什麼。封面與動畫角色由 AI 生成(Codex),旁白是 AI 語音(Gemini TTS),動畫裡的對白是示範。

為什麼做這個#

大三上「進階 Web 程式設計」的期末專題,我想做一個「多個 AI 分工合作」的東西,而不是呼叫一次 LLM 就結束。

當時我對 LangGraph 很好奇:它號稱可以把多個 AI 角色、工具呼叫和輪次狀態畫成一張圖來管理,但我沒有真的用過。與其讀文件,不如做一個一定要用到這些功能的產品。

「辯論」剛好是很好的題材:

  • 天生需要多個角色:正方、反方、裁判,各自有不同的立場和規則。
  • 天生需要輪次:誰先講、講幾輪、什麼時候收尾,正好是狀態機要管的事。
  • 天生需要查資料:光靠模型自己的記憶,辯論很快就會變成空話;讓 AI 自己上網找證據,才看得出工具呼叫有沒有接好。

另一半的動機很單純:我想看看兩個 AI 互相吵架,到底會吵成什麼樣子。

三個角色#

角色立場做什麼
🟢 樂觀者(正方)支持題目先發言,提出論點和資料;每次發言 2~3 句,不准讓步
🔴 懷疑者(反方)質疑題目找對方論點的漏洞,要求證據;需要時先上網查資料再反駁
🔵 主持人(總結者)中立每輪結束做 80~120 字的小結;最後一輪寫 200~300 字的總報告:雙方論點、分歧、評估和建議

兩位辯手都可以自己決定要不要上網搜尋。搜尋結果只取前 3 筆的標題和摘要交給模型,畫面上的狀態列會即時顯示「樂觀者正在搜尋:……」。

實際用起來#

1. 輸入一個題目#

DebateAI 深色首頁:左側是導覽列和最近的辯論,中間的輸入框寫著「AI 會取代大部分人類工作嗎?」

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

2. 看兩個 AI 即時攻防#

辯論進行中:綠色的樂觀者和紅色的懷疑者輪流發言,上方狀態列顯示正在搜尋資料

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

3. 主持人每輪小結#

藍色的主持人訊息,整理這一輪雙方的主要論點與分歧

每輪結束,主持人會整理雙方的主要論點和還沒解決的分歧,再進入下一輪。

4. 最後的總報告#

三輪結束後,主持人會寫出一份總報告:雙方論點、分歧、評估和建議,前端用 react-markdown 渲染標題和清單。

老實說,這張圖我現在截不到:線上版跑到第 2、3 輪時,會撞上 Groq 帳號每分鐘 8,000 個 token 的上限(HTTP 429),而程式遇到 429 不會等一下再重試,辯論就中斷了。

5. 歷史紀錄、設定和其他頁面#

歷史紀錄頁:分頁列出過去的辯論題目與時間

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

設定頁:可以切換淺色、深色、跟隨系統三種主題,以及中文和英文

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

關於頁:介紹 DebateAI 的定位與功能

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

價格方案頁:Free、Pro、Enterprise 三種方案

價格方案頁是產品化的練習,頁尾也註明了價格只是範例,網站沒有收費。

系統架構#

DebateAI 系統架構圖:瀏覽器連到部署在 Cloudflare Pages 的 Next.js,前端呼叫 Cloud Run 上的 FastAPI;FastAPI 內的 LangGraph 管理樂觀者、懷疑者、主持人與 ToolNode,並呼叫 Groq 模型、Tavily/DuckDuckGo 搜尋和 Supabase

整個系統分成三層:

  • 前端:Next.js 與 shadcn/ui,靜態匯出後部署在 Cloudflare Pages;支援中英雙語與深淺色主題。前端用 fetch 加 ReadableStream 接收串流,不需要額外的 WebSocket。
  • 後端:FastAPI,部署在 Google Cloud Run,套件管理用 uv。後端把 LangGraph 的事件轉成 8 種 SSE 事件(status、speaker、token、tool_start、tool_end、speaker_end、complete、error),前端依事件類型更新畫面。
  • AI 流程:LangGraph 的 StateGraph 管理角色、輪次和工具呼叫;模型走 Groq,辯論結束後的紀錄存進 Supabase。

技術細節#

LangGraph:把辯論畫成一張圖#

一場辯論就是一張狀態圖:

  1. 從樂觀者開始。
  2. 樂觀者需要資料時,交給 ToolNode 搜尋,查完回到樂觀者;不需要就輪到懷疑者。
  3. 懷疑者也一樣,可以先搜尋再發言。
  4. 懷疑者講完,交給主持人做小結,回合數加一。
  5. 未滿 3 輪就回到樂觀者;滿了,主持人寫總報告,結束。

整場辯論共用一份狀態:訊息、題目、目前是誰、第幾輪、搜尋了幾次、上一個發言的是誰,以及這場辯論使用的語言。每一步要去哪裡,都由圖上的條件邊決定,程式裡不需要一堆 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 格式。
  • 正式環境去連 localhost:.env.local 的優先權比正式環境變數高,部署後前端還在呼叫本機 API。整理環境變數的載入順序後才修好。
  • 工具事件抓不到、搜尋會無限循環:一開始工具是在節點裡手動呼叫,LangGraph 追蹤不到,前端也收不到「正在搜尋」。改用 ToolNode 和 astream_events v2 之後才抓得到工具事件;另外加上每個角色最多查 3 次的上限。
  • 小模型不會用工具:8B 的模型常常把 function calling 的格式寫錯,換成 gpt-oss-120b 才穩定。
  • 備援模型一直沒被切換到:Groq SDK 的 max_retries 大於 0 時,會在內部對同一個模型一直重試,把限流錯誤吃掉,LangChain 的 with_fallbacks() 根本沒機會接手。我把 max_retries 設成 0,讓錯誤立刻拋出,備援才真正生效。
  • 備援模型拿不到工具:先包 with_fallbacks() 再 bind_tools(),備援模型就沒有工具。順序改成先對每個模型 bind_tools(),再包備援。
  • 兩個人同時用會互相干擾:語言設定原本是全域變數,兩個人用不同語言辯論時會互相覆蓋。改成放進每場辯論的 LangGraph 狀態,各自獨立。
  • 靜態匯出不能做動態路由:歷史紀錄的詳情頁不能用 /history/[id],改用 ?id= 查詢參數加 Suspense。
  • 串流回呼拿到舊狀態:React 的 SSE 回呼閉包裡拿到的是舊 state,改用 ref 保存最新值。

規模#

  • 開發時間:11 月下旬到 1 月初,大約一個半月,主要集中在 12 月。
  • 程式碼:77 個 commit,其中約 93% 是我寫的;隊友協助了介紹頁與部分介面。
  • 測試:後端 59 個 pytest、前端 37 個 vitest,覆蓋率約 77%;CI 會擋覆蓋率低於 50% 的變更。

如果再做一次#

  • 引用來源要能點回去:現在搜尋結果只把標題和摘要交給模型,網址沒有一路傳到畫面上,讀者沒辦法回到原始資料。下一版會把來源連結放進訊息格式。
  • 搜尋次數上限要重新檢查:回頭讀程式時發現,計數器在達到上限時會被歸零,所以「最多查 3 次」實際上可能不會停。要用「一直要求搜尋」的測試去驗證。
  • 遇到限流要會等:現在碰到 429 就整場中斷。應該加上指數退避重試,或是縮短每輪的上下文、把輪數做成可以調整,讓有限的額度也能跑完一場。
  • API 要加保護:後端 API 沒有驗證,任何知道網址的人都能呼叫、消耗模型額度。要變成多人使用的產品,要加登入與限流,並重新設計歷史紀錄的權限。

參考專案#

roy4222
/
DebateAI
DebateAI 是一個基於 LangGraph 與 Groq 的多 Agent 即時辯論平台。 透過「樂觀者」與「懷疑者」兩位 AI 針對主題進行自動化攻防與事實查核。專案採前後端分離架構(FastAPI + Next.js),完美展示了 Agent 狀態管理、聯網搜尋 (Tool Use) 及 即時串流 (Streaming) 等核心 AI 工程技術,旨在呈現極致的推理速度與全端開發能力。
1
0
no-license

DebateAI 的完整原始碼,包含 FastAPI 後端、LangGraph 流程、Next.js 前端與 CI 設定。

DebateAI 線上版
debate.roy422.dev

可以直接輸入題目開始一場辯論。

langchain-ai
/
langgraph
Build resilient agents.
42.2K
7.1K
MIT

用來管理多個 AI 角色、工具呼叫與輪次狀態的框架。