结构化输出:用 JSON Schema 约束模型响应






结构化输出:用 JSON Schema 约束模型响应


结构化输出:用 JSON Schema 约束模型响应

Ollama 的结构化输出功能允许调用方把 JSON Schema 传给模型,约束响应的字段、类型与必填项。它可用于文档抽取、图像描述和统一下游接口格式。模式约束能减少格式不一致,却不能证明模型给出的内容真实;应用仍需做类型、业务和来源核验。

应用定义 JSON Schema,Ollama 将约束传给模型,应用解析并进行类型和业务校验后再使用结果。
图 1:结构化输出的请求、解析和应用校验流程。编辑部自绘。

准备客户端并定义 Schema

原文发表于 2024 年 12 月 6 日,介绍当时 Ollama 的 Python 与 JavaScript 客户端支持。文中建议先将 Ollama 更新到较新版本,并升级相应客户端库。升级会改变当前环境中的依赖;实际操作前应查看当前兼容要求,并优先在隔离环境和锁文件约束下进行。以下安装命令只作原文示例,本稿未运行。

python -m pip install -U ollama
npm install ollama

请求中的 format 字段可以直接接收 JSON Schema,也可以由 Pydantic 或 Zod 类型生成 Schema,再在客户端解析响应。下面的对象要求模型返回国家名称、首都和语言列表:

{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "capital": { "type": "string" },
    "languages": {
      "type": "array",
      "items": { "type": "string" }
    }
  },
  "required": ["name", "capital", "languages"]
}

通过 HTTP API 传入 Schema

HTTP 示例把模型、用户消息、非流式响应设置和 Schema 放进同一个请求。服务默认监听本机 11434 端口;Schema 描述的是返回格式,不是请求者身份或访问权限。

curl -X POST http://localhost:11434/api/chat \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "llama3.1",
    "messages": [
      {"role": "user", "content": "介绍加拿大。"}
    ],
    "stream": false,
    "format": {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "capital": {"type": "string"},
        "languages": {
          "type": "array",
          "items": {"type": "string"}
        }
      },
      "required": ["name", "capital", "languages"]
    }
  }'

原文展示的响应包含加拿大名称、渥太华首都,以及英语和法语两项语言。字段顺序可以不同,只要数据满足 Schema。

{
  "capital": "Ottawa",
  "languages": [
    "English",
    "French"
  ],
  "name": "Canada"
}

localhost 只表示示例目标在本机;不要把未经鉴权的推理服务端口暴露给不可信网络。若服务需要网络访问,应另行设计访问控制、防火墙和传输保护。

Python:用 Pydantic 描述并验证

Python 客户端可把字典形式的 Schema 传给 format,也可用 Pydantic 定义数据类型并调用 model_json_schema()。收到响应后,再用同一类型解析 JSON:

from ollama import chat
from pydantic import BaseModel

class Country(BaseModel):
    name: str
    capital: str
    languages: list[str]

response = chat(
    messages=[
        {
            "role": "user",
            "content": "介绍加拿大。"
        }
    ],
    model="llama3.1",
    format=Country.model_json_schema(),
)

country = Country.model_validate_json(response.message.content)
print(country)

原文所示输出是已解析的类型化对象;这是原文中的演示输出,不是本稿执行所得:

name='Canada' capital='Ottawa' languages=['English', 'French']

从文本抽取记录时,也可以建模嵌套列表。下例要求模型从用户描述中整理两只猫的信息:

from ollama import chat
from pydantic import BaseModel

class Pet(BaseModel):
    name: str
    animal: str
    age: int
    color: str | None
    favorite_toy: str | None

class PetList(BaseModel):
    pets: list[Pet]

response = chat(
    messages=[
        {
            "role": "user",
            "content": """
                I have two pets.
                A cat named Luna who is 5 years old and loves playing with yarn. She has grey fur.
                I also have a 2 year old black cat named Loki who loves tennis balls.
            """
        }
    ],
    model="llama3.1",
    format=PetList.model_json_schema(),
)

pets = PetList.model_validate_json(response.message.content)
print(pets)

原文给出的预期输出如下。实际结果仍应与输入文本逐项比对;缺失字段、空值与可选字段的业务含义需要在数据模型和应用规则中明确。

pets=[
  Pet(name='Luna', animal='cat', age=5, color='grey', favorite_toy='yarn'),
  Pet(name='Loki', animal='cat', age=2, color='black', favorite_toy='tennis balls')
]

JavaScript:用 Zod 生成 Schema

原文使用 Ollama JavaScript 库、Zod 和 zod-to-json-schema。先定义对象结构,再把它转换为 JSON Schema;对返回的字符串执行 JSON 解析和 Zod 验证:

import ollama from 'ollama';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const Country = z.object({
  name: z.string(),
  capital: z.string(),
  languages: z.array(z.string()),
});

const response = await ollama.chat({
  model: 'llama3.1',
  messages: [{ role: 'user', content: '介绍加拿大。' }],
  format: zodToJsonSchema(Country),
});

const country = Country.parse(JSON.parse(response.message.content));
console.log(country);

原文展示的对象具有同样三项字段:

{
  name: "Canada",
  capital: "Ottawa",
  languages: [ "English", "French" ],
}

这段客户端写法属于原文 2024 年示例。截至 2026 年 10 月 8 日,Ollama 当前官方文档改用 z.toJSONSchema() 的示例;采用哪种转换函数取决于安装的库版本,使用前应核对当前文档与类型定义。

图像描述:限定视觉结果字段

结构化输出也可配合视觉模型。原文示例把图像描述拆成摘要、对象列表、场景、颜色、时段、室内外设置及可选文本;时段与场景使用枚举值,对象记录含名称、置信度和属性。path/to/image.jpg 是占位路径,不是可直接读取的图片:

from ollama import chat
from pydantic import BaseModel
from typing import List, Optional, Literal

class Object(BaseModel):
    name: str
    confidence: float
    attributes: str

class ImageDescription(BaseModel):
    summary: str
    objects: List[Object]
    scene: str
    colors: List[str]
    time_of_day: Literal['Morning', 'Afternoon', 'Evening', 'Night']
    setting: Literal['Indoor', 'Outdoor', 'Unknown']
    text_content: Optional[str] = None

path = 'path/to/image.jpg'

response = chat(
    model='llama3.2-vision',
    format=ImageDescription.model_json_schema(),
    messages=[
        {
            'role': 'user',
            'content': 'Analyze this image and describe what you see, including any objects, the scene, colors and any text you can detect.',
            'images': [path],
        },
    ],
    options={'temperature': 0},
)

image_description = ImageDescription.model_validate_json(response.message.content)
print(image_description)

原文示例输出描述了一张海滩照片。下方仅转录其字段结果,没有附上原文图片,也没有声称重新识别或验证照片内容:

summary='A palm tree on a sandy beach with blue water and sky.'
objects=[
  Object(name='tree', confidence=0.9, attributes='palm tree'),
  Object(name='beach', confidence=1.0, attributes='sand')
],
scene='beach',
colors=['blue', 'green', 'white'],
time_of_day='Afternoon'
setting='Outdoor'
text_content=None

模型返回的置信度是模型输出字段,不应当作经校准的概率。真实应用还应限制进程可读取的路径和文件类型,并评估图像上传中个人信息的处理边界。格式完整并不代表图像识别正确。

通过 OpenAI 兼容接口调用

原文还演示用 OpenAI Python SDK 的解析接口接入 Ollama 兼容端点,以 Pydantic 类型作为响应格式,并分别处理正常解析、拒答和长度限制错误:

from openai import OpenAI
import openai
from pydantic import BaseModel

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",
)

class Pet(BaseModel):
    name: str
    animal: str
    age: int
    color: str | None
    favorite_toy: str | None

class PetList(BaseModel):
    pets: list[Pet]

try:
    completion = client.beta.chat.completions.parse(
        temperature=0,
        model="llama3.1:8b",
        messages=[
            {
                "role": "user",
                "content": """
                    I have two pets.
                    A cat named Luna who is 5 years old and loves playing with yarn. She has grey fur.
                    I also have a 2 year old black cat named Loki who loves tennis balls.
                """
            }
        ],
        response_format=PetList,
    )

    pet_response = completion.choices[0].message
    if pet_response.parsed:
        print(pet_response.parsed)
    elif pet_response.refusal:
        print(pet_response.refusal)
except Exception as e:
    if type(e) == openai.LengthFinishReasonError:
        print("Too many tokens: ", e)
        pass
    else:
        print(e)
        pass

原文的 api_key="ollama" 是供本机兼容客户端填写的占位参数,不是可复用密钥,也不能证明端点已经鉴权。服务一旦可从网络访问,就应设计并核实实际的访问控制与传输保护。SDK 的兼容接口和模型名称会演进;此处示例未测试。

提高结构化输出的可靠性

  • 用 Pydantic(Python)或 Zod(JavaScript)定义和复用响应 Schema,并在客户端解析校验。
  • 在提示中补充“以 JSON 返回”,帮助模型理解预期格式。
  • 把温度设为 0 可以让输出更确定一些,但不保证事实准确,也不保证不同运行逐字相同。
  • 解析成功后仍执行领域规则、权限检查和来源核对;不要将模型生成的字段直接作为可信决策输入。

原文把结构化输出描述为比 JSON mode 更可靠、更加一致的格式约束;这属于原文的产品说明,不是此稿独立测得的比较结论。原文还列出控制 logits、提升性能与准确性、GPU 采样加速以及扩展 JSON Schema 以外格式等后续方向。它们是 2024 年文章发布时的展望,不代表当前路线图。

版本边界:本文中的代码和模型名保留自 2024 年 12 月的原文,未对当前版本作适配或执行。截至 2026 年 10 月 8 日,Ollama 当前官方文档称 Ollama Cloud 当前不支持结构化输出,并使用不同的模型和客户端示例。请按实际部署方式、模型和库版本核对功能支持,尤其不要由本地示例推断云端能力。

原文标题:Structured outputs。来源:Ollama Blog,发表于 2024 年 12 月 6 日;页面未列个人作者,页面版权声明为 © 2026 Ollama。页面没有声明适用于全文的开放内容许可证。本文为中文编译与编辑整理,原作权利仍归 Ollama;图 1 为未完纪编辑部自绘示意图。原文中的安装、HTTP、Python、JavaScript 与 OpenAI 兼容示例均未在本稿中执行。


© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容