前端转 AI 第 35 天:结构化输出 - 让 LLM 稳定返回 JSON
昨日回顾
Day 34 搞定了 Function Calling,助手能"调用工具"了。
但工具调用的参数是模型生成的------万一模型输出:
json
{"city": "北京" ← 少了右括号
或者:
json
好的,参数是:{"city": "北京"} ← 多了一堆废话
整个流程直接崩。
今天解决这个问题:结构化输出。
Day 35 目标:搞懂 JSON Mode / JSON Schema,让 LLM 100% 稳定返回合法 JSON。
一、为什么需要结构化输出
1. 真实场景:抽取信息
需求: 从用户的一句话里抽取结构化数据。
输入:
arduino
"帮我订明天下午3点从北京到上海的机票,两个人"
期望输出:
json
{
"action": "book_flight",
"from_city": "北京",
"to_city": "上海",
"date": "2026-10-14",
"time": "15:00",
"passengers": 2
}
你写了一版 prompt:
javascript
请从用户输入里提取信息,按以下 JSON 格式返回:
{"action": "...", "from_city": "...", ...}
模型的输出可能是:
情况 1(好):
json
{"action": "book_flight", "from_city": "北京", ...}
情况 2(加了废话):
json
好的,我提取到以下信息:
{"action": "book_flight", ...}
希望对你有帮助!
情况 3(格式错):
javascript
{
"action": "book_flight",
"from_city": "北京",
"to_city": "上海",
"date": "2026-10-14",
"time": "15:00",
"passengers": 2, ← 多了逗号,JSON 非法
}
情况 4(用单引号):
json
{'action': 'book_flight', ...} ← 不是合法 JSON
情况 5(字段名错):
json
{"action": "book_flight", "from": "北京", "to": "上海"}
← 字段名跟你要的不一样
模型有 5% 的概率"发挥创意"------你的下游代码就得写一堆容错。
2. 传统解法:正则 + 重试
Day 9 写过 extract_json------从文本里硬抠 JSON。
优点: 能处理大部分情况 缺点:
- 治标不治本:还是在"猜"模型的输出
- 字段无法校验 :模型给了
from而不是from_city,正则救不了 - 重试成本高:每次重试都是钱
3. 正确的解法:让模型"承诺"按格式输出
OpenAI 和主流厂商都提供了"结构化输出"能力:
- JSON Mode:承诺返回合法 JSON
- JSON Schema :承诺返回符合给定 Schema 的 JSON
- Structured Outputs(严格模式):100% 保证符合 Schema
核心思路:把"约束"前移到模型侧,而不是"事后修补"。
4. 前端视角:就像 TypeScript + Zod
前端怎么做类型安全的 API 响应?
传统方式:
ts
const res = await fetch("/api/user").then(r => r.json());
// 只能祈祷 res 符合预期
if (typeof res.name === "string" && typeof res.age === "number") {
// 手动校验
}
Zod 的方式:
ts
const UserSchema = z.object({
name: z.string(),
age: z.number().min(0),
});
const user = UserSchema.parse(await fetch("/api/user").then(r => r.json()));
// 校验失败直接抛错,不用手写 if
Python 的 Structured Outputs 就是"LLM 版的 Zod":
python
class UserInfo(BaseModel):
name: str
age: int = Field(ge=0)
response = client.beta.chat.completions.parse(
response_format=UserInfo, # ← 关键
...
)
user = response.choices[0].message.parsed # 直接拿到 UserInfo 实例
框架保证 LLM 的输出符合 Pydantic 模型。 你什么都不用校验。
二、三种层次的结构化输出
别急着上手,先搞清楚"结构化输出"的三个层次。
1. 层次一:Prompt 要求(最原始)
只在 prompt 里说"请返回 JSON":
python
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "请以 JSON 格式返回"},
{"role": "user", "content": "..."},
],
)
成功率: 70-90% 适用: 原型验证
问题: 模型会加 markdown、会用单引号、会少括号。
2. 层次二:JSON Mode(较可靠)
用 response_format={"type": "json_object"}:
python
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "以 JSON 返回"},
{"role": "user", "content": "..."},
],
response_format={"type": "json_object"}, # ← 关键
)
成功率: 99% 适用: 大部分场景
保证:
- ✅ 返回的是合法 JSON
- ❌ 不保证字段名、类型、结构符合要求
还是会输出:
json
{"from": "北京", "to": "上海"} ← 合法 JSON,但字段名不对
3. 层次三:JSON Schema(最可靠)
用 JSON Schema 严格约束:
python
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[...],
response_format={
"type": "json_schema",
"json_schema": {
"name": "flight_info",
"schema": {
"type": "object",
"properties": {
"from_city": {"type": "string"},
"to_city": {"type": "string"},
"passengers": {"type": "integer", "minimum": 1},
},
"required": ["from_city", "to_city", "passengers"],
"additionalProperties": False, # ← 关键:禁止额外字段
},
"strict": True, # ← 关键:严格模式
},
},
)
成功率: 100%(严格模式下) 适用: 生产环境
strict: True 是"魔法开关"------开启后模型必须严格按 Schema 输出。
4. 三个层次对比
| 层次 | 保证 | 成功率 | 适用 |
|---|---|---|---|
| Prompt 要求 | 无 | 70-90% | 原型 |
| JSON Mode | 合法 JSON | 99% | 一般场景 |
| JSON Schema | 符合 Schema | 100% | 生产 |
生产环境一律用 JSON Schema + 严格模式。
5. 厂商支持现状
| 厂商 | JSON Mode | JSON Schema | 严格模式 |
|---|---|---|---|
| OpenAI | ✅ | ✅ | ✅(GPT-4o+) |
| DeepSeek | ✅ | ✅ | ✅(V3+) |
| 通义千问 | ✅ | ✅ | ✅ |
| 智谱 GLM | ✅ | ✅ | ✅ |
| Claude | ❌ | ✅(工具调用方式) | ✅ |
注意:
- 严格模式是"新功能"------2024 年 8 月 OpenAI 才首发,各厂商陆续跟上
- 老模型可能不支持------用之前查文档
- 各厂商 API 略有差异------但都兼容 OpenAI 格式
三、JSON Mode:最简单的结构化
先上手最简单的。
1. 基础用法
创建 day35/json_mode.py:
python
"""JSON Mode 基础用法"""
import json
import os
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1",
)
def extract_info(text: str) -> dict:
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{
"role": "system",
"content": (
"从用户输入里提取机票预订信息,"
"返回 JSON,字段:from_city, to_city, date, passengers"
),
},
{"role": "user", "content": text},
],
response_format={"type": "json_object"}, # ← JSON Mode
)
content = response.choices[0].message.content
return json.loads(content)
if __name__ == "__main__":
text = "帮我订明天下午3点从北京到上海的机票,两个人"
result = extract_info(text)
print(json.dumps(result, ensure_ascii=False, indent=2))
输出:
json
{
"from_city": "北京",
"to_city": "上海",
"date": "2026-10-14",
"passengers": 2
}
稳了。 100% 是合法 JSON。
2. JSON Mode 的"坑"
坑 1:某些模型要求 prompt 里出现"json"这个词
OpenAI 有规定:使用 JSON Mode 时,prompt 里必须提"JSON"。
python
# ✅ 可以
system = "以 JSON 格式返回"
system = "返回 JSON"
# ❌ 会报错(OpenAI)
system = "提取以下信息..."
解决:
python
system = "从用户输入提取信息。以 JSON 格式返回。"
# 加一句就行
坑 2:模型可能返回一个空对象
json
{}
JSON Mode 只保证"合法 JSON",不保证"字段存在"。
校验还得自己做。
坑 3:模型可能返回 {"data": {...}} 嵌套
json
{
"result": {
"from_city": "北京",
...
}
}
它会"多包一层"------JSON Mode 管不着。
所以 JSON Mode 通常要配合"prompt 明确说明"或"Pydantic 二次校验"。
3. JSON Mode 适合什么
适合:
- 结构简单:字段少、模型不容易搞错
- 原型验证:先跑通逻辑
- 兼容性要求高:老模型也支持
不适合:
- 复杂嵌套:模型容易跑偏
- 字段强约束:枚举、数字范围等
- 生产环境:还是要上 JSON Schema
四、JSON Schema:生产级方案
这才是重头戏。
1. Pydantic 自动生成 Schema
Day 11 学的 Pydantic 又派上用场。
创建 day35/structured.py:
python
"""结构化输出 - Pydantic 版"""
import json
import os
from typing import Literal
from datetime import date
from pydantic import BaseModel, Field
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1",
)
# ============ 1. 用 Pydantic 定义输出结构 ============
class FlightBooking(BaseModel):
"""机票预订信息"""
from_city: str = Field(description="出发城市")
to_city: str = Field(description="到达城市")
date: str = Field(description="出发日期,格式 YYYY-MM-DD")
time: str = Field(description="出发时间,格式 HH:MM")
passengers: int = Field(ge=1, le=10, description="乘客数量")
class_type: Literal["economy", "business", "first"] = Field(
default="economy",
description="舱位等级",
)
# ============ 2. 调用(严格模式)============
def extract_flight(text: str) -> FlightBooking:
"""从文本提取机票信息,返回 Pydantic 对象"""
# 用 Pydantic 生成 Schema
schema = FlightBooking.model_json_schema()
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "从用户输入提取机票预订信息"},
{"role": "user", "content": text},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "FlightBooking",
"schema": schema,
"strict": True, # ← 关键
},
},
)
content = response.choices[0].message.content
# 自动校验 + 类型转换
return FlightBooking.model_validate_json(content)
if __name__ == "__main__":
text = "帮我订明天下午3点从北京到上海的机票,商务舱,两个人"
result = extract_flight(text)
print(result)
print(f"类型: {type(result)}")
print(f"出发: {result.from_city} → {result.to_city}")
print(f"人数: {result.passengers}")
输出:
lua
from_city='北京' to_city='上海' date='2026-10-14' time='15:00' passengers=2 class_type='business'
类型: <class '__main__.FlightBooking'>
出发: 北京 → 上海
人数: 2
关键收获:
result是FlightBooking实例,不是 dict- 可以直接
result.from_city,IDE 有补全 - Pydantic 已经校验过了,类型安全
2. strict: True 到底做了什么
对比不开启:
python
response_format={
"type": "json_schema",
"json_schema": {
"name": "FlightBooking",
"schema": schema,
# 没有 strict
},
}
效果对比:
| 场景 | 不严格 | 严格模式 |
|---|---|---|
| 多一个字段 | ✅ 会出现 | ❌ 保证没有 |
| 少一个字段 | ⚠️ 可能出现 | ❌ 保证不会 |
| 类型错(int vs str) | ⚠️ 可能出现 | ❌ 保证不会 |
| 枚举外取值 | ⚠️ 可能出现 | ❌ 保证不会 |
严格模式保证:
- 所有字段都在 (
required) - 类型都对
enum内取值- 没有额外字段 (
additionalProperties: false)
踩坑提醒:
严格模式要求所有字段都 required,且 additionalProperties: false。 有些 SDK 会自动加,有些要手动。
Pydantic 的 model_json_schema() 默认不生成 additionalProperties: false------需要手动处理:
python
def to_strict_schema(model_cls):
"""生成符合严格模式的 Schema"""
schema = model_cls.model_json_schema()
# 递归设置 additionalProperties: false
def _process(obj):
if isinstance(obj, dict):
if obj.get("type") == "object":
obj["additionalProperties"] = False
# 所有字段必填(严格模式要求)
if "properties" in obj:
obj["required"] = list(obj["properties"].keys())
for v in obj.values():
_process(v)
elif isinstance(obj, list):
for item in obj:
_process(item)
_process(schema)
return schema
这是很多人在严格模式上踩的坑。
3. 嵌套结构
Pydantic 支持嵌套------严格模式也支持。
python
class Address(BaseModel):
city: str
district: str
street: str
class Order(BaseModel):
order_id: str
customer_name: str
shipping_address: Address # 嵌套
items: list[str] # 列表
total: float = Field(ge=0)
def parse_order(text: str) -> Order:
response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": text}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "Order",
"schema": to_strict_schema(Order),
"strict": True,
},
},
)
return Order.model_validate_json(response.choices[0].message.content)
嵌套、列表、可选字段全支持。
这就是"生产级结构化输出"。
五、OpenAI SDK 的 .parse() 语法糖
OpenAI 官方 SDK 提供了更优雅的接口:
python
from openai import OpenAI
client = OpenAI(...)
# 不用手写 response_format
response = client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "帮我订明天下午 3 点..."}],
response_format=FlightBooking, # ← 直接传 Pydantic 类
)
# 直接拿对象
booking = response.choices[0].message.parsed
print(booking.from_city)
parse() 帮你做了一切:
- Pydantic → JSON Schema
- 设置严格模式
- 解析响应
- 返回 Pydantic 实例
限制:
- 只支持 OpenAI / Azure(其他厂商要看 SDK 是否兼容)
- 需要模型支持严格模式(GPT-4o / 4o-mini)
- 旧 SDK 版本可能没有
DeepSeek 现在也兼容部分接口,可以试试。如果不行,用"手动 JSON Schema"版本。
六、实战:给 Agent 加结构化输出
把结构化输出整合到我们 Day 34 的 Agent 里。
1. 应用场景
做一个"智能笔记助手":
输入: 用户的一段自然语言 输出: 结构化笔记(标题 / 标签 / 待办事项 / 优先级)
2. 定义模型
src/ai_assistant/schemas/note.py:
python
"""笔记的结构化模型"""
from pydantic import BaseModel, Field
from typing import Literal
class TodoItem(BaseModel):
"""待办事项"""
content: str = Field(min_length=1, max_length=200, description="事项内容")
priority: Literal["high", "medium", "low"] = Field(
default="medium",
description="优先级",
)
due_date: str | None = Field(
default=None,
description="截止日期,格式 YYYY-MM-DD,没有就不填",
)
class Note(BaseModel):
"""结构化笔记"""
title: str = Field(min_length=1, max_length=100, description="笔记标题")
summary: str = Field(max_length=500, description="一句话摘要")
tags: list[str] = Field(
default_factory=list,
max_length=5,
description="标签列表,最多 5 个",
)
todos: list[TodoItem] = Field(
default_factory=list,
description="从内容里抽取的待办事项",
)
category: Literal["work", "life", "study", "idea", "other"] = Field(
default="other",
description="笔记分类",
)
3. 实现抽取函数
src/ai_assistant/services/note_extractor.py:
python
"""笔记抽取服务"""
from openai import AsyncOpenAI
from ..schemas.note import Note
from ..observability.logging import get_struct_logger
logger = get_struct_logger(__name__)
SYSTEM_PROMPT = """你是一个智能笔记助手。从用户输入里提取结构化信息。
要求:
- title:简洁的标题
- summary:一句话摘要
- tags:3-5 个相关的标签
- todos:如果用户提到要做的事,提取为待办事项
- category:根据内容分类
如果用户没提到待办事项,todos 返回空数组。"""
def _strict_schema(model_cls) -> dict:
"""生成严格模式 Schema"""
schema = model_cls.model_json_schema()
def _process(obj):
if isinstance(obj, dict):
if obj.get("type") == "object":
obj["additionalProperties"] = False
if "properties" in obj:
obj["required"] = list(obj["properties"].keys())
for v in obj.values():
_process(v)
elif isinstance(obj, list):
for item in obj:
_process(item)
_process(schema)
return schema
class NoteExtractor:
"""笔记抽取器"""
def __init__(self, client: AsyncOpenAI, model: str):
self.client = client
self.model = model
self.schema = _strict_schema(Note)
async def extract(self, text: str) -> Note:
"""从文本抽取结构化笔记"""
response = await self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": text},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "Note",
"schema": self.schema,
"strict": True,
},
},
)
content = response.choices[0].message.content
logger.info("抽取笔记完成", content_length=len(content))
# Pydantic 二次校验
return Note.model_validate_json(content)
4. 加 API 接口
api/routes.py:
python
from ..services.note_extractor import NoteExtractor
from ..schemas.note import Note
@router.post("/notes/extract", response_model=Note)
async def extract_note(
req: NoteRequest,
client = Depends(get_llm_client),
):
"""从文本抽取结构化笔记"""
extractor = NoteExtractor(client, model="deepseek-chat")
try:
note = await extractor.extract(req.text)
return note
except Exception as e:
logger.exception("抽取失败")
raise LLMError(f"抽取失败: {e}")
schemas/note.py 加请求模型:
python
class NoteRequest(BaseModel):
text: str = Field(min_length=1, max_length=5000, description="要抽取的文本")
5. 测试
bash
curl -X POST http://localhost:8000/api/notes/extract \
-H "Content-Type: application/json" \
-H "X-API-Key: test-key" \
-d '{
"text": "下周一前要完成产品设计文档,还要跟张三对一下接口。顺便把会议室订了,最好是下午。"
}'
返回:
json
{
"title": "产品设计文档 + 接口对齐 + 会议室",
"summary": "需在下周一前完成产品设计文档、与张三对齐接口,并预订下午会议室",
"tags": ["工作", "产品", "会议"],
"todos": [
{
"content": "完成产品设计文档",
"priority": "high",
"due_date": "2026-10-19"
},
{
"content": "跟张三对接口",
"priority": "high",
"due_date": null
},
{
"content": "订会议室(最好是下午)",
"priority": "medium",
"due_date": null
}
],
"category": "work"
}
完美符合 Schema。
前端拿到这个结构,直接渲染就行------完全不用写"容错逻辑"。
6. 前端 React 渲染示例
jsx
function NoteCard({ text }) {
const [note, setNote] = useState(null);
const extract = async () => {
const res = await fetch("/api/notes/extract", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
setNote(await res.json());
};
if (!note) return <button onClick={extract}>抽取</button>;
return (
<div className="note-card">
<h2>{note.title}</h2>
<p>{note.summary}</p>
<div>
{note.tags.map(tag => <span key={tag} className="tag">{tag}</span>)}
</div>
<ul>
{note.todos.map((todo, i) => (
<li key={i} data-priority={todo.priority}>
{todo.content}
{todo.due_date && <small>截止 {todo.due_date}</small>}
</li>
))}
</ul>
</div>
);
}
为什么能这么写?
因为 API 契约稳定了。 字段名、类型都保证对。
对比没有结构化输出时:
jsx
// 得写一堆容错
const todos = note.todos || [];
const tags = Array.isArray(note.tags) ? note.tags : [];
if (typeof note.priority === "string" && ["high", "medium", "low"].includes(note.priority)) {
// ...
}
现在一行 note.todos.map(...) 就够了。
这就是结构化输出的价值------把"容错"从代码里赶出去。
七、结构化输出的高级技巧
1. 用 Field 加语义约束
Pydantic 的 Field 能定义:
python
class Review(BaseModel):
rating: int = Field(ge=1, le=5, description="评分 1-5")
comment: str = Field(min_length=10, max_length=500, description="评论,至少 10 字")
sentiment: Literal["positive", "neutral", "negative"]
tags: list[str] = Field(default_factory=list, max_length=3)
is_verified_purchase: bool = Field(description="是否已验证购买")
这些约束会进入 JSON Schema,严格模式下模型会遵守。
2. 处理"可选"字段
问题: 严格模式要求所有字段 required,但业务上有"可选"字段。
解法: 用默认值:
python
class Booking(BaseModel):
from_city: str
to_city: str
notes: str = Field(default="", description="备注,没有就空字符串")
# ↑ 默认值让它 required 但可为空
模型看到默认值,就知道"没内容的话给空值"。
比 Optional[str] 更适合严格模式。
3. 多模型输出(异构)
有时候模型需要"输出 A 或 B",不是同一结构:
python
from typing import Union
from pydantic import BaseModel, Field
class SuccessResult(BaseModel):
status: Literal["success"]
data: dict
execution_time: float
class ErrorResult(BaseModel):
status: Literal["error"]
error_code: str
error_message: str
class Result(BaseModel):
"""用 Union 表达"二选一" """
result: SuccessResult | ErrorResult = Field(discriminator="status")
discriminator="status" 是关键 ------告诉 Pydantic / LLM 用 status 字段区分类型。
严格模式支持 Union,但有坑:
- 所有可能结构都要列出来
- 用
discriminator明确区分字段 - 不要用
Optional[X]表达"二选一"
4. 敏感字段的脱敏
模型输出可能包含"敏感信息"------比如用户原文里的手机号。
Pydantic 有 Field(exclude=True):
python
class User(BaseModel):
name: str
email: str
raw_input: str = Field(exclude=True) # ← 输出 JSON 时不带
但严格模式下不推荐 ------模型不理解 exclude,可能会填 null。
更好的做法: 分两步------先让模型输出,再 Pydantic 处理后过滤。
5. 复杂 JSON:用"中间结构"
太复杂的 Schema 会让模型很吃力。
比如: "电商订单"有商品列表、收货地址、支付信息、优惠券。
拆法:
python
# 别一口气定义巨大模型
class GiantOrder(BaseModel):
...
# 拆成"两步调用"
class OrderBasic(BaseModel):
"""第一步:抽取基本信息"""
user_id: str
total: float
class OrderItems(BaseModel):
"""第二步:抽取商品列表"""
items: list[dict]
# 或者用"工具调用"做多步(Day 34)
经验:单次 JSON Schema 不要超过 20 个字段、不要嵌套超过 3 层。
八、前端视角:结构化输出 vs 前端"组件 Props"
做过前端的你,可以把结构化输出理解成"LLM 版的 Props 校验"。
React 组件:
jsx
function UserCard({ name, age, avatar }) {
PropTypes.checkPropTypes({
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
avatar: PropTypes.string,
});
...
}
结构化输出:
python
class UserCard(BaseModel):
name: str
age: int
avatar: str | None = None
核心一样:给"数据"定义"契约",输入方必须遵守。
对照表:
| 前端 | 后端(Python) |
|---|---|
| PropTypes | Pydantic |
| zod | Pydantic |
TS interface |
Pydantic BaseModel |
required props |
Field() |
| 枚举 props | Literal |
| defaultProps | Field 默认值 |
| 运行时校验 | model_validate |
| 生成文档 | model_json_schema() |
你在前端养成的"类型驱动开发"习惯,直接用到 LLM 上。
这就是"结构化输出"最迷人的地方------
它把"从前端到后端"的契约思维,扩展到了"从前端到 LLM"。
九、今日踩坑
- 严格模式要求
additionalProperties: false→ Pydantic 默认不生成,要手动处理 - 严格模式要求所有字段
required→ 可选字段要加默认值 - JSON Mode 要求 prompt 提"JSON" → OpenAI 的规定,容易忘
model_json_schema()生成的$defs不被所有厂商支持 → 有的要展开引用- 嵌套太深模型会出错 → 控制在 3 层内
- 字段太多会失败 → 控制在 20 个以内
- 枚举值拼错模型也"顺从" → 严格模式才会真正拦住
Field(exclude=True)在严格模式下会填 null → 别指望模型理解- Union 类型没加
discriminator→ 模型分不清哪种 response_format各厂商 API 名字不同 → 通义叫response_format,智谱可能不一样,查文档- 忘了检查
finish_reason→ 如果是length,JSON 可能被截断 strict: True但模型不支持 → 报错或降级成普通模式- Pydantic 校验失败没兜底 → 建议加 try/except 或二次重试
- 流式 + 结构化输出 → 部分厂商不支持,要提前测
- 生产环境一定要
strict=True→ 别信"差不多就行"
十、今日总结
今天核心收获三件事:
- 结构化输出三层次 :Prompt 要求 → JSON Mode → JSON Schema,生产用第三层
- Pydantic 是核心:定义模型、生成 Schema、二次校验,一站式搞定
strict: True是保障:保证字段、类型、枚举都符合,比"祈祷模型听话"稳一万倍
一个感受:
LLM 从"文本生成器"变成"结构化数据生成器",这是一个质变。
有了它,你可以:
- 从文本抽取结构化数据(简历解析、新闻结构化)
- 生成 API 请求参数(自然语言转 API 调用)
- 分类和标注(给内容打标签)
- 生成配置("帮我生成一个 nginx 配置")
LLM 不再只是"聊天玩具",而是"数据处理工具"。
前端开发的本质是"和接口打交道"------接口要结构、要类型、要契约。
结构化输出让 LLM 输出的东西也能"契约化" ------这才是它真正融入工程体系的开始。
Day 35 完成度:✅
明日预告(Day 36)
- Prompt 工程入门 :
- 什么是好的 Prompt
- System / User / Assistant 角色的用法
- 指令的清晰度、具体性
- 输出格式的约束
- 常见的 Prompt 反模式
- 用一套方法论写 Prompt,而不是"感觉对了就行"
最后
如果这篇对你有用,点个赞让我知道有人在看。
评论区互动: 你之前在什么场景下需要"结构化输出"?解析简历?抽取文章元数据?欢迎分享。
有问题评论区见,我们第 36 天继续。
100 天,第 36 天见。
附录:系列目录
第一阶段(已完成 ✅)
| 阶段 | 天数 | 内容 |
|---|---|---|
| Python 基础 | 01-15 | 语法 + 命令行助手 |
| 后端工程化 | 16-30 | 数据库/测试/FastAPI/Docker/上线 |
第二阶段:大模型应用(进行中 🎯)
| 天数 | 内容 |
|---|---|
| 31-34 | LLM 入门 + 客户端 + 流式 + Function Calling ✅ |
| 35 | 结构化输出(JSON Schema) ✅ |
| 36-40 | Prompt 工程 + 多模型 + 成本优化 |
| 41-45 | 本地模型 + 阶段复盘 |
第三阶段:RAG(Day 46-60)
第四阶段:Agent(Day 61-80)
第五阶段:完整项目(Day 81-100)
35 天已过,65 天继续。
一起从切图仔变成 Agent 开发者。