python
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
from langchain.tools import tool
from pydantic import BaseModel, Field
from typing import Literal
load_dotenv(override=True)
class Ticket(BaseModel):
category: Literal["退款", "物流", "其他"] = Field(description="问题类别")
# @tool
# def get_weather(city: str) -> str:
# """获取指定城市的天气信息"""
# return f"{city}今天大部分晴朗!"
model = init_chat_model(
"deepseek:deepseek-v4-flash",
temperature=0,
extra_body={"thinking": {"type": "disabled"}},
)
# model_with_tools = model.bind_tools([get_weather])
model_structured_output = model.with_structured_output(Ticket)
result = model_structured_output.invoke("我的快递三题没更新,我很着急")
print(result)
这段代码的作用
这是用 Pydantic 定义一个结构化数据模型 Ticket,用来描述"工单"这类数据。逐部分拆解:
python
class Ticket(BaseModel): # ① 继承 Pydantic 的 BaseModel
category: Literal["退款", "物流", "其他"] = Field(description="问题类别")
| 部分 | 作用 |
|---|---|
BaseModel |
Pydantic 的数据模型基类,自动提供类型校验 、解析 、model_dump() 序列化等能力 |
Literal["退款", "物流", "其他"] |
限定该字段只能取这三个值 ,传别的值会抛 ValidationError(相当于枚举约束) |
Field(description="问题类别") |
给字段加元数据说明,不影响校验;主要给 LLM / API 文档 / JSON Schema 看 |
没有 default= |
所以 category 是必填字段 (注意:Field(...) 不带 default 不等于有默认值) |
典型使用场景
1. LLM 结构化输出(最常见)
LangChain / OpenAI function calling 里,把这个模型当作 schema,让大模型按固定格式返回:
python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
structured = llm.with_structured_output(Ticket)
result = structured.invoke("我要退款,订单号 12345 没收到货")
# Ticket(category='退款')
这里的 Literal 特别关键------它把可选值约束为中文枚举,防止模型自由发挥 (否则可能返回 "Refund"、"售后" 等变体)。description 会写进 JSON Schema,帮助模型理解字段含义。
2. 数据校验 / 清洗
python
Ticket(category="退款") # ✅ OK
Ticket(category="退货") # ❌ ValidationError: Input should be '退款', '物流' or '其他'
Ticket() # ❌ ValidationError: category Field required
3. FastAPI 请求体
python
@app.post("/tickets")
def create(ticket: Ticket): # 自动校验 + 自动生成 OpenAPI 文档
...
两个容易踩的坑
① 想设默认值要显式写 default:
python
category: Literal["退款", "物流", "其他"] = Field(default="其他", description="问题类别")
② 输出仍是字符串,不是枚举对象 :Literal 只是类型层面的约束,result.category 是 str,不能直接 .value。
③ Pydantic v1 vs v2 :v2 里 description 正常用;v1 里字段说明也支持,但整体 API(dict() vs model_dump())不同,混用会报错。