iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0

昨天完成 BaseAgent 後,專案已經有一套共用的模型呼叫流程。今天加入 PM Agent 與 Research Agent,並要求兩個角色回傳固定 JSON,讓 Python 能穩定取得需要的欄位。

PM 負責整理「接下來要做什麼」,Research 則區分「哪些資訊已確認、哪些只是推論」。為了共用結構化輸出的處理方式,我另外建立 StructuredAgent,使用 Pydantic 驗證模型回覆。

今天完成的內容

  • 建立 StructuredAgent
  • 建立 PM Agent 與 Research Agent
  • 使用 Pydantic 定義輸出格式
  • 將 JSON Schema 傳給 Ollama
  • 使用 Fake LLM 測試正常與錯誤回覆

為什麼先做這兩個角色?

如果使用者只輸入:

我想做一個可以回答公司產品問題的 Discord Bot。

這句話仍缺少使用對象、資料來源、時程與限制,也可能混著未確認的假設。因此先分成兩個角度:

  • PM Agent:整理目標、限制、工作項目與尚未解決的分歧。
  • Research Agent:分類已知資訊、合理推論與待查證項目。

兩個角色收到同一段需求,但輸出用途不同。

安裝 Pydantic

專案固定使用 Pydantic 2.12.5:

python -m pip install "pydantic==2.12.5"

這篇使用 Pydantic 2 的 model_json_schema() 與 model_validate_json()。

定義 PM 與 Research 的輸出

PM 的輸出格式如下:

class PMAnalysis(BaseModel):
    model_config = ConfigDict(extra="forbid")

    goal: NonEmptyText
    constraints: list[NonEmptyText]
    work_items: list[NonEmptyText]
    disagreements: list[NonEmptyText]

goal 是主要目標,constraints 記錄限制,work_items 是可執行工作,disagreements 則保存衝突或分歧。

Research 使用另一份格式:

class ResearchAnalysis(BaseModel):
    model_config = ConfigDict(extra="forbid")

    known_information: list[NonEmptyText]
    reasonable_inferences: list[NonEmptyText]
    items_to_verify: list[NonEmptyText]
  • known_information:使用者已明確提供的資訊
  • reasonable_inferences:可以推論,但還不是事實的內容
  • items_to_verify:需要另外確認的項目

這樣可以避免後面的 Agent 把推測一路傳下去,最後看起來像正式需求。

兩個 Model 都使用 ConfigDict(extra="forbid"),多出未定義欄位就會失敗。NonEmptyText 也會移除前後空白並拒絕空字串。

建立 StructuredAgent

BaseAgent 已經負責組合 Prompt、呼叫 LLM Service 與處理一般錯誤。StructuredAgent 在取得回答後,多做一次 Pydantic 驗證:

class StructuredAgent(Generic[OutputModel]):
    async def respond(self, user_input: str) -> OutputModel:
        response = await self._base_agent.respond(user_input)

        try:
            return self.output_model.model_validate_json(response.content)
        except ValidationError as error:
            raise AgentError(
                f"{self.config.name} 回覆格式不正確,請重新產生。"
            ) from error

PM 傳入 PMAnalysis,Research 則傳入 ResearchAnalysis。共用流程不需要知道每個角色有哪些欄位。

原始需求
  ↓
PM Agent 或 Research Agent
  ↓
StructuredAgent → BaseAgent → LLM Service
  ↓
JSON 文字
  ↓
PMAnalysis 或 ResearchAnalysis

JSON Schema 的兩道檢查

Pydantic Model 能直接產生 Schema:

PMAnalysis.model_json_schema()
ResearchAnalysis.model_json_schema()

Schema 會放進 AgentConfig 並傳給 Ollama;Python 收到 JSON 後,再用 model_validate_json() 驗證欄位、型別與內容。

合法 JSON 不一定是可用資料。少了必要欄位、出現空字串或多出未定義欄位,都會在第二道檢查被擋下。

兩個 Agent 的設定

PM 與 Research 都使用:

temperature = 0.2
seed = 42
max_output_tokens = 500

兩者的差異集中在角色、System Prompt 與 JSON Schema。整理需求與分類資訊需要比較穩定的結果,因此先使用低溫度。

PM 的 Prompt 另外要求沒有分歧時回傳空陣列,後續程式便能直接處理 disagreements,不必先判斷欄位是否存在。

格式錯誤時怎麼處理?

如果回覆無法通過 Pydantic 驗證,StructuredAgent 會把 ValidationError 轉成專案自己的 AgentError:

PM Agent 回覆格式不正確,請重新產生。

上層只需處理 AgentError,錯誤訊息也會保留 Agent 名稱。目前尚未加入自動重試。

目前如何傳入需求?

現階段尚未建立共用 Context,測試直接把同一段需求交給兩個 Agent:

pm_result = await pm_agent.respond(requirement)
research_result = await research_agent.respond(requirement)

使用 Fake LLM 測試

tests/test_project_agents.py 使用 FakeProjectLLMService 回傳事先準備的 JSON,不會啟動 Ollama。測試會確認:

  • PM 回傳 PMAnalysis
  • Research 回傳 ResearchAnalysis
  • 兩個 Agent 收到相同需求並共用 LLM Service
  • PM 缺少必要欄位時會拋出 AgentError

執行測試

python -m unittest discover -s tests -v
Ran 35 tests in 0.097s

OK

DAY 12 新增 4 個 PM 與 Research 測試。這只能證明 Python 端的格式與錯誤處理正確,因為測試沒有真的呼叫 Qwen。

今天完成到哪裡?

DAY 12 完成 PM、Research 與共用的 StructuredAgent。目前兩個角色仍各自分析原始需求,尚未建立共享 Context;下一篇會加入 Creative 與 Finance,讓專案從需求分析往提案和風險評估延伸。


上一篇
DAY 11|建立共用 BaseAgent
下一篇
DAY 13|建立 Creative 與 Finance Agent
系列文
AI 公司模擬器:Discord x Multi-Agent 架構實作 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言