结构化输出:JSON Mode 与 Function Calling 重塑接口设计
在 LLM 刚出现时,开发者最头疼的问题是如何从模型那一大段“废话”中提取出关键信息。 我们不得不写复杂的 Regex 来匹配 Answer: (.*),或者在 Prompt 里苦苦哀求:“Please output ONLY JSON, no other text.” 即便如此,模型还是经常输出:Sure! Here is the JSON: { ... },导致 JSON 解析失败。
结构化输出(Structured Output) 的出现,彻底改变了这一现状。 现在,我们可以像调用普通函数一样调用 LLM,得到的不再是 String,而是强类型的 Object。 这不仅是便利性的提升,更是AI 应用架构的基石。
1. JSON Mode:最基础的约束
1.1 什么是 JSON Mode?
OpenAI 等模型提供了一个 response_format={"type": "json_object"} 参数。 开启后,模型会保证输出合法的 JSON 字符串。
- 注意:你必须在 Prompt 里明确提到 "JSON" 这个词,否则模型可能会报错或生成空白。
- 局限:它只保证是 JSON,不保证符合你的 Schema(比如字段名可能是错的)。
1.2 架构模式:TypeChat
微软推出的 TypeChat 库,核心思想是利用 TypeScript 类型定义作为 Prompt。
// 定义 Schema
interface SentimentResponse {
sentiment: "positive" | "negative" | "neutral";
score: number; // 0 to 1
}LLM 会根据这个 Interface 生成符合结构的数据。如果验证失败,TypeChat 会自动把错误信息(如 "score missing")喂回给 LLM 重试。
2. Function Calling (Tool Use):真正的革命
2.1 什么是 Function Calling?
这不仅仅是输出 JSON,而是让 LLM “以为” 自己在调用一个函数。 你给 LLM 一个函数签名(Name, Description, Parameters),LLM 决定是否调用它,以及用什么参数调用。
2.2 流程详解
- User: "查一下北京明天的天气。"
- App: 发送 Prompt +
get_weather(city: str, date: str)定义给 LLM。 - LLM: 思考后,返回一个结构化对象:json(注意:此时 LLM 并没有真的去查天气,它只是生成了参数)
{ "tool_calls": [{ "name": "get_weather", "arguments": "{\"city\": \"Beijing\", \"date\": \"tomorrow\"}" }] } - App: 拿到参数,执行真正的 API:
weather_api.query("Beijing", "tomorrow")-> 得到 "Sunny, 25°C"。 - App: 把 "Sunny, 25°C" 作为
tool_output发回给 LLM。 - LLM: 生成最终自然语言回复:“北京明天天气晴朗,气温 25度。”
2.3 架构意义:LLM 作为路由器
在 Function Calling 模式下,LLM 不再只是生成器,而是智能路由器 (Intelligent Router)。 它连接了自然语言意图和确定性的代码逻辑。
3. Pydantic Is All You Need
在 Python 生态中,Pydantic 成为了定义 LLM 接口的事实标准。 不管是 LangChain, LlamaIndex 还是 OpenAI SDK,都原生支持 Pydantic 模型。
3.1 定义数据模型
from pydantic import BaseModel, Field
from typing import List
class Step(BaseModel):
explanation: str = Field(description="Step explanation")
output: str = Field(description="Result of this step")
class MathReasoning(BaseModel):
steps: List[Step]
final_answer: float3.2 Instructor 库:最优雅的封装
instructor 库(基于 Pydantic)让 LLM 调用变得极其 Pythonic。
import instructor
from openai import OpenAI
client = instructor.patch(OpenAI())
resp = client.chat.completions.create(
model="gpt-4-turbo",
response_model=MathReasoning, # 直接传 Class
messages=[{"role": "user", "content": "Solve 2x + 5 = 15"}]
)
print(resp.final_answer) # 5.0 (Type: float)
print(resp.steps[0].explanation) # "Subtract 5 from both sides"这就是未来的后端接口开发体验。没有 Regex,没有 String Parsing,只有强类型的对象。
4. 最佳实践与坑
4.1 描述 (Description) 至关重要
函数名和参数的 description 是给 LLM 看的“文档”。
- Bad:
date: str - Good:
date: str, format YYYY-MM-DD, default to today if not specified.写好 Description 等于写好了 Prompt。
4.2 容错与重试
即使有 Function Calling,LLM 偶尔也会生成不符合 Schema 的参数(比如 Enum 越界)。
- Validation: 使用 Pydantic 的
validator装饰器进行校验。 - Retry: 捕获
ValidationError,将错误信息作为 System Message 发回 LLM,让其修正(Self-Correction)。
4.3 性能优化
定义过于复杂的 Schema 会消耗大量 Token,且增加延迟。
- Tip: 尽量把大 Schema 拆分成多个小 Function。
- Tip: 对于简单任务,用 JSON Mode 可能比 Function Calling 更快(少了一次往返)。
结构化输出将 LLM 从“聊天机器人”升级为“数据处理引擎”。 掌握 JSON Mode 和 Function Calling,是构建 Agent、RAG 和复杂 AI 工作流的前提。 未来的 API,输入是 Prompt,输出是 Object。中间的转换,交给 LLM。