前幾天已把第一輪討論、需求變更、PM 草案與 Review 分開完成。不過實際操作時,仍要依序輸入 /start、/change、/draft 和 /review。
這適合逐段測試,卻不方便驗收整場會議。今天新增 /meeting,把功能接成單一流程,也補上模型逾時、JSON 格式錯誤與 Discord Followup 發送失敗的處理。
目前一場會議會依照以下順序執行:
/meeting
↓
第一輪:PM、Research、Creative、Finance 發言
↓
加入一次需求變更
↓
第二輪:四位 Agent 針對變更補充或修正
↓
PM 整合兩輪內容並產生草案
↓
Review Agent 審查
↓
需要修改時,指定一位 Agent 修改一次
↓
PM 產生最終方案
Discord 指令需要帶入專案編號與需求變更編號:
/meeting project_id:PRJ-001 change_id:CHG-001
輸入指令後,Bot 先檢查三件事:指令是否在伺服器內使用、專案是否存在,以及需求變更是否屬於該專案。
由於本地模型不一定能在 Discord 規定的回應時間內完成,程式會先執行:
await interaction.response.defer(thinking=True)
這代表 Bot 已收到指令,後面的進度與結果再透過 Followup 傳送。
run_full_meeting() 協調各階段完整流程集中在 MeetingManager.run_full_meeting()。它沒有重寫前幾天的功能,而是依序呼叫第一輪、第二輪、PM 草案與 Review 流程。
主要結構可以簡化成:
record = await self.start_first_round(...)
record = await self.start_second_round(...)
proposal_draft = await self.create_proposal_draft(...)
workflow_result = await self.review_and_finalize(...)
真正的程式還會先讀取目前的會議紀錄,再決定要從哪裡開始。這就是這次加入單一入口時最需要處理的部分。
一次完整會議會呼叫多位 Agent。如果在 PM 整合草案時發生錯誤,重新執行後又讓前面的 Agent 全部重講,不只浪費時間與 Token,內容也可能和第一次不同。
因此每完成一個階段,結果就會寫入會議紀錄。重新執行相同的 /meeting 時,程式會檢查已有資料:
resume() 接續未完成步驟。如果目前會議屬於另一個專案,或已經套用不同的需求變更,程式會停止,避免把兩個專案的內容混在一起。
同一個 Guild 也不能同時啟動兩場完整會議。_full_workflow_guilds 會記錄正在執行的 Guild,流程結束或發生錯誤後,再於 finally 移除。這一層是避免使用者連續送出相同指令,造成兩個背景任務同時修改同一筆紀錄。
Review Agent 會回傳「通過」或「需要修改」。兩條路徑的處理方式不同:
| Review 結果 | 後續處理 |
|---|---|
| 通過 | 直接將 PM 草案保存為最終方案 |
| 需要修改 | 選出一項問題,交給指定 Agent 修改一次,再由 PM 整合 |
修改次數只能從 0 變成 1。指定 Agent 成功完成修改後,程式才會增加次數。即使再次執行 /meeting,也不會產生第二次修改。
最終會議紀錄會保留:
proposal_draft:PM 第一次整理的草案review_result:Review 的審查結果revision_output:指定 Agent 的修改內容final_proposal:最後要顯示與保存的方案這些資料可供流程續跑,也能回頭查看最終方案的產生過程。
本地模型偶爾會處理太久,或回傳不是合法 JSON 的內容。若完全不重試,一次短暫錯誤就會中斷整場會議;但所有錯誤都重試,又可能讓真正的程式問題一直重複發生。
目前只有以下錯誤會進入重試:
| 錯誤類型 | 代表情況 |
|---|---|
AgentTimeoutError |
等待模型回覆超過設定時間 |
AgentServiceError |
Ollama 服務暫時無法完成請求 |
AgentInvalidJSONError |
模型回覆無法解析成 JSON |
AgentSchemaValidationError |
JSON 欄位或資料型別不符合格式 |
它們都繼承 RetryableAgentError,共用同一套有限重試機制:
for attempt in range(1, self.agent_max_attempts + 1):
try:
return await operation()
except RetryableAgentError:
if attempt == self.agent_max_attempts:
raise
預設每個模型步驟最多嘗試兩次,兩次之間等待一秒。參數可以放在 .env:
MEETING_AGENT_MAX_ATTEMPTS=2
MEETING_RETRY_DELAY_SECONDS=1.0
參數錯誤與其他未分類例外不會一直重跑。日誌也只記錄會議編號、階段、Agent、嘗試次數與錯誤類型,不寫入完整 Prompt 或模型原始回覆。
模型成功產生內容,不代表 Discord 訊息一定能送出去。若網路暫時不穩,Followup 可能失敗,但後端其實已經完成該階段。
safe_followup_send() 會先嘗試傳送 Followup,失敗後再試一次。公開訊息仍然失敗時,才改用目前頻道的 channel.send()。
私密訊息不會改成公開傳送。否則原本只有使用者看得到的錯誤或資料,可能意外出現在公開頻道。
若 Followup 與頻道傳送都失敗,函式會回傳 False 並留下日誌,不會中止後端流程。這樣訊息發送與資料保存就不會綁在一起。
會議開始後,Bot 會依序顯示每位 Agent 的進度與回覆。前幾天加入的統計也會保留,包括:
流程結束後,Discord 會先顯示 Review 結果,再顯示 PM 的最終方案。如果中途失敗,Bot 會提示重新執行同一個 /meeting,讓程式從保存的階段接續。










今天把原本分開測試的功能接進 /meeting。程式端已經能從第一輪討論走到最終方案,也知道中途失敗時該從哪裡繼續。