codecamp

Ollama 结构化输出

让模型返回一段人读的文字很容易,让它返回一段程序能直接解析的数据就麻烦了——多一个逗号、少一个引号,json.loads 立刻抛异常。结构化输出(Structured Outputs)解决的就是这件事:把 JSON Schema 交给 Ollama,由它约束模型的生成过程,而不是生成完再祈祷格式对。

它适合三类活儿:从杂乱文本里抽字段、把图片描述成固定结构、让同一条提示词每次都返回一致形状的回复。

⚠️ 一个必须先知道的前提:Ollama 云端目前不支持结构化输出。下面的用法都跑在本地 localhost:11434 上。

版本基线 v0.40.12,官方 docs.ollama.com/capabilities/structured-outputs。

一、最简形态:format: "json"

只要在请求里加 format 字段,模型就会输出合法 JSON,但结构仍由它自己决定:

## format 设为 "json":保证是合法 JSON,但不保证字段名
curl -X POST http://localhost:11434/api/chat \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-oss",
    "messages": [{"role": "user", "content": "用一句话介绍加拿大"}],
    "stream": false,
    "format": "json"
  }'

from ollama import chat

response = chat(
    model='gpt-oss',
    messages=[{'role': 'user', 'content': '用一句话介绍加拿大'}],
    format='json',     # 只要合法 JSON,不约束结构
)
print(response.message.content)

import ollama from 'ollama'

const response = await ollama.chat({
  model: 'gpt-oss',
  messages: [{ role: 'user', content: '用一句话介绍加拿大' }],
  format: 'json',    // 同上
})
console.log(response.message.content)

实际项目里很少只用到这一步——因为下次运行返回的键名可能就变了。要真正稳定,得给 Schema。

二、给 Schema:format 换成对象

把 format 的值从字符串 "json" 换成一个标准的 JSON Schema 对象:

## format 传入完整 JSON Schema:字段名、类型、必填项全被约束
curl -X POST http://localhost:11434/api/chat \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-oss",
    "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 也以字符串形式写进提示词。模型看到 format 里的结构约束,同时在提示词里再读一遍字段含义,两者互相印证,产出质量通常更稳——尤其字段名比较抽象的时候。

手写 Schema 容易出错,Python 和 JavaScript 各有更省事的路子。

Python:Pydantic 一把梭。 用 model_json_schema() 生成 Schema,用 model_validate_json() 校验回包,两行代码省掉手工拼对象:

## pip install ollama pydantic
from ollama import chat
from pydantic import BaseModel

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

response = chat(
    model='gpt-oss',
    messages=[{'role': 'user', 'content': '介绍加拿大'}],
    format=Country.model_json_schema(),   # 由模型类自动生成 Schema
)

country = Country.model_validate_json(response.message.content)  # 顺手校验
print(country.capital)   # 现在是强类型对象,不是裸 dict

JavaScript:Zod。 思路一样,z.toJSONSchema() 生成、parse 校验:

// npm install ollama zod
import ollama from 'ollama'
import * as z from 'zod'

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

const response = await ollama.chat({
  model: 'gpt-oss',
  messages: [{ role: 'user', content: '介绍加拿大' }],
  format: z.toJSONSchema(Country),      // 由 Zod schema 生成 JSON Schema
})

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

三、实战一:从自然语言里抽结构化数据

这是结构化输出最高频的用法。假设用户随手打了一段话:

"我养了两只猫,一只叫 Luna,一只叫 Loki…"

你想拿到一份结构化的宠物清单。定义好模型类,把"字段含义"交给类型注解,剩下的模型会填:

from ollama import chat
from pydantic import BaseModel

## 单只宠物:Optional 字段允许模型在信息缺失时返回 null
class Pet(BaseModel):
    name: str
    animal: str
    age: int
    color: str | None
    favorite_toy: str | None

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

response = chat(
    model='gpt-oss',
    messages=[{'role': 'user', 'content': '我养了两只猫,一只叫 Luna,一只叫 Loki…'}],
    format=PetList.model_json_schema(),
)

pets = PetList.model_validate_json(response.message.content)
for p in pets.pets:
    print(p.name, p.animal, p.age)

注意 str | None 的用处:用户没提到颜色和玩具,模型就能合法返回 null,而不是硬编一个假值。该可选的字段一定要标可选,否则模型会为了满足 Schema 而编造内容,这是抽取任务最常见的脏数据来源。

四、实战二:视觉 + 结构化,输出确定的图像描述

视觉模型接受同一个 format 参数。两者组合起来,图片描述就能变成程序可用的数据,而不是一段散文:

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

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

class ImageDescription(BaseModel):
    summary: str
    objects: list[Object]
    scene: str
    colors: list[str]
    # Literal 把取值锁死成枚举,避免模型自创"清晨""黄昏"
    time_of_day: Literal['Morning', 'Afternoon', 'Evening', 'Night']
    setting: Literal['Indoor', 'Outdoor', 'Unknown']
    text_content: Optional[str] = None

response = chat(
    model='gemma4',
    messages=[{
        'role': 'user',
        'content': '描述这张照片,并列出你检测到的物体。',
        'images': ['path/to/image.jpg'],
    }],
    format=ImageDescription.model_json_schema(),
    options={'temperature': 0},   # 视觉标注建议压到 0,减少随机性
)

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

Literal 这个技巧值得记住:比起在提示词里写"请从早/中/晚/夜中选一个",用类型枚举约束显然更可靠,模型连"傍晚"这种边界词都没机会输出。

五、四条提高成功率的经验

优先级:用模型类而不是手写 Schema。 Pydantic / Zod 生成的 Schema 与校验逻辑同源,改一处两边同步,不会出现"Schema 改了校验没改"。

温度压到 0。 官方明确建议在需要确定性的场景设 temperature: 0。同样的输入反复调用,结果才稳定可比。

可选字段一律显式标可选。 见上面第三节,这是防止模型编造的关键。

OpenAI 兼容接口走 response_format。 如果你的代码是套 OpenAI SDK 的,不用改成 /api/chat,OpenAI 兼容层同样支持结构化输出,只是参数名是 response_format 而非 format。

想按 JSON 之外的逻辑控制生成,可以看 3.4 Ollama 决策(Decision);图片输入的完整用法见 3.5 Ollama 视觉(Vision)。

Ollama 思考能力(Thinking)
Ollama 决策(Decision)
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

Ollama Cloud

Ollama CLI 命令行参考

Ollama Modelfile 参考

Ollama 上下文长度

Ollama 导入自定义模型

Ollama 常见问题

Ollama 硬件支持

Ollama 故障排查

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }