目錄

Ollama 結構化輸出:讓模型回傳固定格式的 JSON

把模型整合進程式時,我們常常需要的不是一段文字,而是程式可以直接讀取的資料。例如從一封客訴信中取出姓名、訂單編號、問題類型。

Ollama 的 format 參數可以強制模型的輸出符合你指定的 JSON 格式。

最簡單:只要求 JSON

curl http://localhost:11434/api/chat -d '{
  "model": "qwen3:8b",
  "messages": [{ "role": "user", "content": "列出台灣三個城市和人口,用 JSON 回答" }],
  "format": "json",
  "stream": false
}'

輸出保證是合法的 JSON,但欄位名稱和結構由模型自己決定,每次可能不同。

指定 JSON Schema

把 JSON Schema 放進 format,就能精確規定欄位:

curl http://localhost:11434/api/chat -d '{
  "model": "qwen3:8b",
  "messages": [{ "role": "user", "content": "介紹台北市" }],
  "format": {
    "type": "object",
    "properties": {
      "city": { "type": "string" },
      "population": { "type": "integer" },
      "landmarks": { "type": "array", "items": { "type": "string" } }
    },
    "required": ["city", "population", "landmarks"]
  },
  "stream": false
}'

message.content 會是一個符合格式的 JSON 字串。

Python:用 Pydantic 定義格式

from ollama import chat
from pydantic import BaseModel


class Complaint(BaseModel):
    customer_name: str
    order_id: str | None
    category: str          # 例如:物流、品質、退款、其他
    urgency: int           # 1~5
    summary: str


email = """
您好,我是王小美,訂單編號 A20261012-88 的吹風機收到時外盒破損,
插電後完全沒有反應。下週要出國很急,請盡快處理退換貨,謝謝。
"""

response = chat(
    model="qwen3:8b",
    messages=[
        {"role": "system", "content": "從客訴內容擷取資訊。category 只能是:物流、品質、退款、其他。"},
        {"role": "user", "content": email},
    ],
    format=Complaint.model_json_schema(),
    options={"temperature": 0},
)

data = Complaint.model_validate_json(response.message.content)
print(data)
print(data.urgency)
customer_name='王小美' order_id='A20261012-88' category='品質' urgency=4 summary='吹風機外盒破損且無法通電,要求盡快退換貨'
4

model_validate_json 會再驗證一次型別,確保程式拿到的資料一定正確。

限定選項:用 Literal 或 Enum

from typing import Literal


class Complaint(BaseModel):
    category: Literal["物流", "品質", "退款", "其他"]

這樣模型只能從這四個值中選一個,比在提示詞裡說明更可靠。

JavaScript:用 Zod 定義格式

npm install ollama zod
import ollama from "ollama";
import { z } from "zod";

const Product = z.object({
  name: z.string(),
  price: z.number(),
  tags: z.array(z.string()),
});

const response = await ollama.chat({
  model: "qwen3:8b",
  messages: [{ role: "user", content: "這款 iPhone 手機殼售價 590 元,防摔、輕薄、支援 MagSafe" }],
  format: z.toJSONSchema(Product),
});

const product = Product.parse(JSON.parse(response.message.content));
console.log(product);

(Zod 4 內建 z.toJSONSchema;舊版 Zod 3 可使用 zod-to-json-schema 套件。)

範例:批次分類

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


class Review(BaseModel):
    sentiment: Literal["正面", "負面", "中立"]
    topics: list[str]


reviews = [
    "餐點很好吃,但是等了快一個小時。",
    "服務人員態度很好,下次還會再來!",
    "價格普通,味道普通。",
]

for text in reviews:
    r = chat(
        model="qwen3:8b",
        messages=[{"role": "user", "content": f"分析這則評論:{text}"}],
        format=Review.model_json_schema(),
        options={"temperature": 0},
    )
    print(text, "→", Review.model_validate_json(r.message.content))

從圖片擷取資料

搭配視覺模型,可以從發票、名片、截圖中擷取結構化資料:

class BusinessCard(BaseModel):
    name: str
    company: str | None
    title: str | None
    phone: str | None
    email: str | None


r = chat(
    model="gemma3",
    messages=[{"role": "user", "content": "擷取名片上的資訊", "images": ["./card.jpg"]}],
    format=BusinessCard.model_json_schema(),
    options={"temperature": 0},
)
print(BusinessCard.model_validate_json(r.message.content))

提升正確率的技巧

  • temperature 設為 0,讓輸出更穩定。
  • 在提示詞中也說明格式與欄位意義,不要只靠 schema。例如:「urgency 是 1~5 的整數,5 最緊急」。
  • 欄位名稱取得清楚易懂(order_id 比 oid 好)。
  • 可能沒有資料的欄位設為可為空(str | None),避免模型為了填欄位而編造內容。
  • 結構化輸出保證的是格式,不保證內容正確。重要資料仍需要驗證。

更新於 2026年10月12日