CrewAI 結構化輸出:用 Pydantic 讓 Agent 不再亂回答
如果你有遇過「同一個 Task,這次回 markdown 格式、下次回純文字、再下次回了個 JSON 但 key 名又不一樣」——你不是一個人。
這是 LLM 輸出的本質問題:模型產生的是「看起來合理的文字」,不是「保證符合你期待格式的結構化資料」。你在 expected_output 裡寫得再清楚,模型還是有一定機率自己改個格式。
解法很直接:用 Pydantic 把輸出格式鎖死。
為什麼要結構化輸出?
不是為了程式碼好看,是為了讓下游任務有固定的資料可以用。
假設你的研究員 Agent 輸出了一份報告,後面的編輯 Agent 要讀它——如果格式每次都不一樣,你要嘛每次手動確認,要嘛寫一堆防禦性的字串解析邏輯。這兩個方向都在製造麻煩。
用 Pydantic 定義輸出結構之後:
- 下游任務直接存取固定欄位,不用猜格式
- 格式不符就直接報錯,比跑完才發現資料壞了快很多
- 整個流程的資料契約是明確的,好維護
這就像 REST API 有 schema——有規格才有穩定協作。
定義輸出模型
from pydantic import BaseModel, Field
from typing import Optional
class Finding(BaseModel):
title: str = Field(description="Finding title in plain language")
summary: str = Field(description="2-3 sentence explanation of why this matters")
source: Optional[str] = Field(
default=None,
description="Reference URL, if available"
)
class ResearchReport(BaseModel):
topic: str = Field(description="The research topic")
findings: list[Finding] = Field(
description="List of 5-10 key findings",
min_length=5,
max_length=10,
)
conclusion: str = Field(description="1-2 sentence summary and recommendation")Field(description=...) 很重要——模型會讀這些描述來理解每個欄位的用途和期待內容。欄位名本身加上 description,基本上就是你在告訴模型「這個欄位要填什麼」。
在 Task 使用 output_pydantic
from crewai import Task
research_task = Task(
description="""
Research AI observability tools available in 2026.
Focus on tools that help monitor LLM-based applications.
""",
expected_output="Structured research report with findings and conclusion.",
output_pydantic=ResearchReport,
)跑完之後,task.output.pydantic 就是你的 ResearchReport 物件,可以直接用欄位存取:
result = crew.kickoff()
report = research_task.output.pydantic
print(report.topic)
for finding in report.findings:
print(f"- {finding.title}: {finding.summary}")沒有字串解析、沒有 JSON load、沒有猜格式——就是 Python 物件。
expected_output 還要不要寫?
要。
很多人以為有了 output_pydantic 就不用寫 expected_output,但這兩個是不同層次的東西。
output_pydantic 定義的是資料結構——有哪些欄位、型別是什麼。expected_output 定義的是內容品質——你希望每個欄位裡裝的是什麼等級的內容。兩個合在一起,才能讓輸出又有固定格式、又有足夠的內容品質。
新手常見的 3 個誤區
欄位太少,資訊不足:如果你的 Finding 只有 title 和 summary,下游任務可能沒有足夠的資訊繼續工作。欄位要包含後面步驟真的需要的資料。
欄位太多太細,模型容易漏:另一個極端是把模型當資料庫用,要求它填 20 個欄位,結果很多欄位都是空的或者亂填的。從 3-5 個關鍵欄位開始,夠用再擴。
把 Pydantic 欄位設計成你的 UI 欄位:Pydantic 模型是 LLM 的輸出契約,不是前端的資料模型。兩者的關心點不一樣,不要混在一起設計。
常見問題
Q:output_pydantic 和 output_json 有什麼差別?
output_json 回傳的是一個 dict,你還是要自己存取 key。output_pydantic 直接給你 Pydantic 物件,有型別提示、有驗證、有自動補全。能用 output_pydantic 就用它。
Q:如果模型沒有回傳所有欄位,會怎樣?
Pydantic 驗證會失敗,task 會拋出錯誤。這是好事——比讓壞資料悄悄流進下游任務要好很多。在 expected_output 裡寫清楚每個欄位的要求,可以降低這種情況的發生機率。
Q:list 欄位可以設最小和最大長度嗎?
可以,用 Field(min_length=5, max_length=10) 就好。這會讓模型知道你期待多少條資料,不會出現一條都沒有或者輸出 50 條的情況。
下一步
單一 Crew 現在輸出穩定了。下一步來看,當流程變成多個 Crew 組合的時候,怎麼用 Flow 把它們協調起來:
👉 Flow 入門