目標先講清楚:
結構化輸出(Structured Output)在 Agent 開發裡相關的框架。
當任務被拆成多個節點(或多個子代理)時,每一步都像「微服務」:
→ 沒有結構化,就容易在多步驟工作流裡「前一步講 A、下一步聽成 B」,最後 Debug 非常痛。
面向 | LangChain structured output | Pydantic + LLM(教學/輕量) | langextract |
---|---|---|---|
定位 | 通用 LLM 能力(.with_structured_output() ) |
以 Pydantic 驗證 LLM 輸出 | 長文抽取 + 證據對位 |
最佳用例 | 企業 RFP 解析 → 報價管線;多步驟、多供應商 | 客服抄錄 → JSON;簡單 API/PoC | 合約/行程/報價書批次抽取與稽核 |
Schema 驅動 | Pydantic / TypedDict / JSON Schema;可 union/literal | 以 Pydantic 為核心 | 以抽取 schema 定義欄位與關係 |
失敗回補 | 內建重試/結構約束(視模型支援) | 需自行處理 | 長文可多 pass/分塊 |
長文與證據 | 長文靠分塊/RAG;無內建證據高亮 | 同左 | 可輸出 evidence offset(頁碼/範圍) |
整合能力 | 強(RAG、代理、工具、LangGraph/LCEL) | 輕(自己拼) | 匹配抽取→JSONL→檢閱 |
學習曲線 | 中 | 低 | 中 |
適用規模 | 中~大 | 小~中 | 中~大 |
本篇實作主軸:LangChain structured output + Pydantic。最簡單的 baseline 其實就是「直接在 Prompt 指示格式」,但搭配 Pydantic能顯著提升穩定度與可驗證性。
from pydantic import BaseModel, Field
# 對應「意圖判斷Node」的輸出
class Intent(BaseModel):
"""判斷使用者問題的意圖以及是否與主題相關"""
intent: str = Field(description="使用者的意圖分類")
related: bool = Field(description="問題是否與主要主題相關")
# 對應「圖片識別Node」和「問題回覆Node」的輸出
class Description(BaseModel):
"""針對輸入內容產生的描述"""
desc: str = Field(description="一段描述性文字")
# 對應「是否回答問題Node」的輸出
class Summary(BaseModel):
"""總結所有資訊並判斷是否完成"""
summary: str = Field(description="綜合所有描述後產生的最終總結")
finish: bool = Field(description="是否已經可以結束對話")
from typing import Annotated, TypedDict
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
messages: Annotated[list, add_messages] # 自動管理對話歷史
intent: str
related: bool
image_desc: str
question_desc: str
summary: str
finish: bool
重點是:狀態欄位對齊 Schema,未來排錯能快速對焦「哪個欄位沒被填、哪個節點回傳格式錯」。
# 需使用支援 structured output 的模型(部分模型如 deepseek-r1 就不支援)
structured_llm_intent = llm.with_structured_output(Intent)
structured_llm_desc = llm.with_structured_output(Description)
structured_llm_summary = llm.with_structured_output(Summary)
def intent_node(state: AgentState):
last_message = state['messages'][-1].content
prompt = f"分析以下使用者問題,判斷其意圖以及是否與 'AI Agents' 這個主題相關:'{last_message}'"
result = structured_llm_intent.invoke(prompt)
return {"intent": result.intent, "related": result.related}
def image_recognition_node(state: AgentState):
prompt = "這是一張關於 LangGraph 流程的圖片,請產生一段簡短描述。"
result = structured_llm_desc.invoke(prompt)
return {"image_desc": result.desc}
def question_answering_node(state: AgentState):
last_message = state['messages'][-1].content
prompt = f"針對以下問題提供一個簡短的回覆:'{last_message}'"
result = structured_llm_desc.invoke(prompt)
return {"question_desc": result.desc}
def summarize_node(state: AgentState):
combined = f"圖片描述:{state['image_desc']}\n\n問題回覆:{state['question_desc']}"
prompt = f"請根據以下綜合資訊,產生一段最終總結,並判斷是否已完整回答問題:\n\n{combined}"
result = structured_llm_summary.invoke(prompt)
return {"summary": result.summary, "finish": result.finish}
from langgraph.graph import StateGraph, END
graph = StateGraph(AgentState)
graph.add_node("intent_node", intent_node)
graph.add_node("image_recognition_node", image_recognition_node)
graph.add_node("question_answering_node", question_answering_node)
graph.add_node("summarize_node", summarize_node)
graph.set_entry_point("intent_node")
# 依意圖/主題判斷決定是否繼續與路由
graph.add_conditional_edges(
"intent_node",
should_continue, # 你可以根據 state.intent/related 自訂
{
"continue_nodes": ["image_recognition_node", "question_answering_node"],
"end_node": END,
},
)
graph.add_edge("image_recognition_node", "summarize_node")
graph.add_edge("question_answering_node", "summarize_node")
graph.add_edge("summarize_node", END)
除了定義pydantic model,Prompt也可以進行設計