iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0

DAY 14 完成 Review Agent 後,五位 Agent 仍是獨立類別。今天建立 MeetingManager,由 Python 控制執行順序、準備下一步輸入,並保存每一步的結果。

這次先用 Fake Agent 測試後端流程,還不會從 Discord 啟動,也沒有真的呼叫 Qwen。

今天完成的內容

  • 定義會議與步驟狀態
  • 使用 MeetingRecord 保存五位 Agent 的執行紀錄
  • 使用 JSON Repository 保存會議
  • 依固定順序呼叫 Agent 並傳遞前文
  • 限制同一個 Guild 同時只能執行一場會議
  • 保存失敗與取消狀態,並支援從失敗處繼續

主要檔案的分工如下:

檔案 用途
models/meeting.py 定義會議狀態與步驟資料
repositories/meeting_repository.py 將會議寫入或讀回 JSON
services/meeting_manager.py 控制順序、資料傳遞、取消與恢復

Agent 只負責回答自己的問題,流程控制與保存時機都交給 MeetingManager。

定義會議狀態

目前還沒有第二輪與使用者決策,因此 MeetingStatus 先保留 PENDING、RUNNING、COMPLETED、FAILED、CANCELLED 五種狀態。

合法轉換如下:

PENDING   → RUNNING、CANCELLED
RUNNING   → COMPLETED、FAILED、CANCELLED
FAILED    → RUNNING、CANCELLED
COMPLETED → 不可再轉換
CANCELLED → 不可再轉換

FAILED 可以回到 RUNNING,讓流程從失敗處繼續;完成與取消則是終止狀態。

保存每位 Agent 的步驟

一場會議除了整體狀態,也要知道執行到哪一位 Agent。MeetingStepRecord 會保存 Agent 名稱、順序、狀態、輸入、輸出與錯誤。

DAY 15 先用固定順序驗證流程:

PM → Research → Creative → Finance → Review

這還不是完整的兩輪會議,PM 整合草案與 Review 退修會在後面加入。

為什麼 Agent 執行前就要保存?

Meeting Manager 會在呼叫 Agent 前保存輸入與 RUNNING 狀態,取得結果後再保存一次:

step.input_text = self._build_input(record, index)
step.status = MeetingStepStatus.RUNNING
self._save(record)

response = await agent.respond(step.input_text)

step.output_data = response.model_dump(mode="json")
step.status = MeetingStepStatus.COMPLETED
self._save(record)

如果程式在推論途中停止,仍能知道這一步收到什麼內容。Agent 回傳的 Pydantic Model 則用 model_dump(mode="json") 轉成可寫入 JSON 的資料。

把前面的結果交給下一位

PM 直接收到原始需求;從 Research 開始,輸入會加入專案 ID、需求與已完成的結果:

context = {
    "project_id": record.project_id,
    "requirement": record.requirement,
    "previous_outputs": previous_outputs,
}

資料會依序累積:

Research → PM 結果
Creative → PM、Research 結果
Finance  → PM、Research、Creative 結果

Review 的 ReviewRequest 格式不同,需要另外組合 project_id、draft 與 revision_count。目前 draft 只是需求與前面輸出的 JSON,還不是 PM 整理後的正式草案。

使用 Repository 保存會議

MeetingManager 不直接操作檔案,而是依賴 MeetingRepository。目前由 JsonMeetingRepository 將 Guild 對應的會議 ID 與完整紀錄寫入 meetings.json。寫入時先建立 .tmp 暫存檔,再取代正式檔案,降低留下不完整 JSON 的機會。

避免同一個 Guild 重複啟動

每個 Guild 都有自己的 asyncio.Lock:

lock = self._guild_locks.setdefault(guild_id, asyncio.Lock())

if lock.locked():
    raise MeetingManagerError("這個伺服器已有會議正在執行。")

相同 Guild 的第二個請求會被拒絕,不同 Guild 仍可同時執行。目前 Bot 是單一 Python 程序,因此先使用這個做法。

失敗後從原位繼續

假設 Finance 執行失敗,紀錄會是:

PM          COMPLETED
Research    COMPLETED
Creative    COMPLETED
Finance     FAILED
Review      PENDING

呼叫 resume() 後,程式會清除 Finance 的舊輸入、輸出與錯誤,再從 Finance 重新執行,前面三位不會重跑。JSON 只保存「Finance Agent 執行失敗」這類安全訊息,不直接留下底層例外內容。

cancel() 則會先保存 CANCELLED 狀態,再取消執行中的 Task。

使用 Fake Agent 測試

單元測試不會啟動 Ollama。FakeAgent 只記錄名稱與輸入,再回傳固定結果,用來檢查呼叫順序、資料保存、Guild Lock、失敗恢復、取消流程及 JSON 讀寫。

測試故意讓 Finance 第一次失敗,恢復後的順序是:

PM → Research → Creative → Finance
                         → Finance → Review

實際測試結果

python -m unittest discover -s tests -v
Ran 50 tests in 0.106s

OK

這 50 項測試確認了狀態、順序、資料保存、取消與恢復邏輯,但沒有真的呼叫 Qwen,也沒有從 Discord 啟動會議。

今天完成到哪裡?

DAY 15 已經把五位 Agent 放進同一個 Python 流程。Meeting Manager 會依序呼叫角色、傳遞前文,並在每一步開始與完成後保存紀錄;失敗時可從原位繼續,同一個 Guild 的重複請求也會被擋下。

下一篇會先把第一輪與 Review 分開,讓 PM、Research、Creative 和 Finance 的執行進度顯示在 Discord。


上一篇
DAY 14|建立 Review Agent
下一篇
DAY 16|完成第一輪討論,讓四位 Agent 依序發言
系列文
AI 公司模擬器:Discord x Multi-Agent 架構實作 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言