CrewAI 結構化輸出:用 Pydantic 讓 Agent 不再亂回答

6 min read

如果你有遇過「同一個 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 只有 titlesummary,下游任務可能沒有足夠的資訊繼續工作。欄位要包含後面步驟真的需要的資料。

欄位太多太細,模型容易漏:另一個極端是把模型當資料庫用,要求它填 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 入門