iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
AI 自動化

AI 公司模擬器:Discord x Multi-Agent 架構實作系列 第 19 篇

DAY 19|讓 PM 整合兩輪討論並產生草案

  • 分享至 

  • xImage
  •  

DAY 18 完成第二輪需求變更後,PM、Research、Creative 與 Finance 都提出了新的意見。不過,資料仍散在不同 Agent 的回覆裡,還看不出最後採用哪一個方案。

今天加入 PM 的整合流程,讓它讀取兩輪摘要與重要原文,整理成固定格式的提案草案。同時新增 Discord /draft 指令,負責觸發整合並顯示摘要。

今天完成的內容

  • 為 PM 加入 integrate()
  • 使用 Pydantic 限制草案格式與長度
  • 從兩輪會議中挑選摘要及重要原文
  • 將草案保存到 meetings.json
  • 新增 Discord /draft
  • 記錄每個 Agent 的執行時間與文字用量
  • 調整 PM 整合工作的逾時時間

整體流程

第一輪討論
    ↓
需求變更與第二輪討論
    ↓
/draft
    ↓
MeetingManager 整理兩輪資料
    ↓
PMAgent.integrate()
    ↓
Pydantic 驗證輸出
    ↓
保存草案並建立 Discord Embed

PM Agent 多了一項工作

原本的 PMAgent.respond() 用來拆解單次需求。這次新增 integrate(),專門處理兩輪討論:

async def integrate(self, meeting_input: str) -> PMProposalDraft:
    return await self._proposal_agent.respond(meeting_input)

兩個方法共用同一個 LLMService,但使用不同的 System Prompt 與 Schema。

方法 用途
respond() 整理目標、限制、工作項目與分歧
integrate() 整合兩輪討論並記錄取捨

整合用的 Prompt 會要求 PM 處理互相衝突的意見,不能只把各 Agent 的回答重新排列。

固定草案格式

草案使用 PMProposalDraft、PMProposalSections 與 PMDecision 驗證。輸出包含標題、摘要、五個章節,以及重要決策:

{
  "title": "提案標題",
  "summary": "草案摘要",
  "sections": {
    "background_and_goal": "專案背景與目標",
    "integrated_solution": "整合方案",
    "execution_plan": "執行計畫",
    "risks_and_responses": "風險與對策",
    "acceptance_criteria": "驗收標準"
  },
  "decisions": [
    {
      "topic": "決策主題",
      "decision": "折衷",
      "reason": "取捨原因",
      "sources": ["Finance Agent 第 2 輪"]
    }
  ]
}

decision 只能是「採用」、「拒絕」或「折衷」。sources 則保留 Agent 名稱與輪次,之後才能回頭確認這項決策來自哪一段討論。

控制草案長度

Meeting Manager 設定的單次回覆上限是 4,000 字,只在 Prompt 寫「請簡短回答」不夠可靠,所以 Schema 也限制欄位長度:

  • 標題最多 100 字,摘要最多 300 字
  • 每個章節最多 240 字
  • 最多 4 項決策
  • 決策主題最多 60 字,原因最多 120 字
  • 每項決策最多 3 個來源

測試會建立各欄位都填滿的草案,確認轉成 JSON 後仍不超過 4,000 字。

Meeting Manager 傳了哪些資料?

create_proposal_draft() 不會直接把整份 meetings.json 丟給模型。它先檢查會議是否完成、兩輪資料是否齊全,再由 _build_proposal_input() 挑出:

  • 原始需求與本次需求變更
  • 第一輪、第二輪摘要
  • 每位 Agent 在各輪的一項重要原文
  • 固定章節名稱與合法決策類型

每輪摘要最多保留 1,400 字,重要原文則限制在 220 字。這樣 PM 仍看得到意見來源,Prompt 也不會一直膨脹。

如果 proposal_draft 已有資料,程式會直接回傳舊草案,不再呼叫模型。同一場會議因此不會因為重複執行 /draft 而產生另一個版本。

保存到 meetings.json

MeetingRecord 新增一個欄位:

proposal_draft: dict[str, object] | None = None

尚未建立草案時是 null。成功後,標題、摘要、五個章節與決策紀錄都會寫入這裡。舊資料沒有這個欄位時仍可讀取,預設值會是 None。

Discord /draft 指令

/draft 只能在 Discord 伺服器使用。指令先以 defer(thinking=True) 回應,再請 Meeting Manager 建立草案:

proposal = await discussion_manager.create_proposal_draft(
    interaction.guild_id
)

await interaction.followup.send(
    embed=format_proposal_embed(proposal)
)

Embed 只顯示標題、摘要、三種決策的數量與主要決策。完整五個章節留在 meetings.json,避免 Discord 訊息太長。

記錄執行時間與文字用量

前幾次實測時,我常遇到 Agent 思考太久,或輸出 Token 用完,最後只看到錯誤訊息。如果沒有其他紀錄,很難判斷是執行逾時、輸入內容太長,還是模型輸出碰到上限。

因此,這次替每個 Agent 加入思考時間與文字用量統計,並由 MeetingStepRecord 保存:

input_characters: int | None = None
output_characters: int | None = None
prompt_tokens: int | None = None
completion_tokens: int | None = None
max_output_tokens: int | None = None
execution_time_seconds: float | None = None

Meeting Manager 以 len() 計算輸入與輸出字元數,保留 Ollama 的 Token 統計,再用 time.perf_counter() 計時。成功、失敗或取消都會保存耗時。

Discord 的第一輪與第二輪訊息會顯示這些資料:

第 1 輪 1/4 PM 需求拆解(<耗時> 秒)
📊 輸入 <字元數>/6,000|輸出 <字元數>/4,000
Prompt Token <數量>|輸出 Token <數量>/<上限>

使用量達 80% 時會顯示 ⚠️,達到 95% 則顯示 🚨,方便我在真正發生錯誤前先注意異常。

/draft 也會使用 ProposalMetrics 保存統計。草案建立完成後,Embed 底部會顯示輸入/輸出字元、Prompt Token、輸出 Token 與整合耗時。如果建立失敗,錯誤訊息也會附上已等待的時間。

舊的會議紀錄仍可正常載入,不過不會自動補上這些資料,必須重新執行 Agent 才會產生統計。

實測遇到逾時

一開始 PM 整合沿用 Agent 的 60 秒預設逾時,結果在內容還沒產生完之前就被取消。這表示整合兩輪資料需要更長的等待時間。

因此,只有 PM Integration Agent 改成 180 秒:

timeout_seconds=180.0

其他 Agent 仍維持原本設定。這次調整也加入測試,避免之後又意外改回 60 秒。

測試結果

https://ithelp.ithome.com.tw/upload/images/20261003/20183880pacfOVBB9R.png

今天完成到哪裡?

兩輪討論現在有了收斂出口。PM 可以把摘要與重要原文整理成固定章節,並保留採用、拒絕或折衷的原因與來源。

DAY 19 已完成從兩輪討論到 Discord 草案畫面的完整串接。之後若再次執行 /draft,程式會直接讀取已保存的草案,不必重新等待模型推論。


上一篇
DAY 18|加入需求變更,讓 Agent 進行第二輪討論
下一篇
DAY 20|讓 Review Agent 審查草案並修改一次
系列文
AI 公司模擬器:Discord x Multi-Agent 架構實作 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言