구조화 출력(Structured Outputs)

구조화 출력(Structured Outputs)

모델의 응답을 항상 같은 형태로 받고 싶을 때가 있어요. 예를 들어 구조화된 데이터를 추출하거나, 이미지를 설명하거나, 매번 일정한 형식으로 답변을 유지해야 하는 경우죠. 구조화 출력은 모델 응답에 JSON 스키마를 강제해서 이런 일을 가능하게 해 줍니다.

출처: 공식문서

참고: Ollama Cloud는 현재 구조화 출력을 지원하지 않습니다.

구조화된 JSON 생성하기

format"json"을 넘기면 모델이 JSON 형태로 응답하도록 지시합니다.

cURL:

curl -X POST http://localhost:11434/api/chat -H "Content-Type: application/json" -d '{
  "model": "gpt-oss",
  "messages": [{"role": "user", "content": "Tell me about Canada in one line"}],
  "stream": false,
  "format": "json"
}'

Python:

from ollama import chat

response = chat(
  model='gpt-oss',
  messages=[{'role': 'user', 'content': 'Tell me about Canada.'}],
  format='json'
)
print(response.message.content)

JavaScript:

import ollama from 'ollama'

const response = await ollama.chat({
  model: 'gpt-oss',
  messages: [{ role: 'user', content: 'Tell me about Canada.' }],
  format: 'json'
})
console.log(response.message.content)

JSON 스키마로 구조화 출력하기

format 필드에 JSON 스키마를 직접 제공하면 더 정밀하게 제어할 수 있어요.

스키마를 문자열로 프롬프트에도 함께 넣어 주면 모델 응답을 더 잘 고정(grounding)할 수 있어요.

cURL:

curl -X POST http://localhost:11434/api/chat -H "Content-Type: application/json" -d '{
  "model": "gpt-oss",
  "messages": [{"role": "user", "content": "Tell me about Canada."}],
  "stream": false,
  "format": {
    "type": "object",
    "properties": {
      "name": {"type": "string"},
      "capital": {"type": "string"},
      "languages": {
        "type": "array",
        "items": {"type": "string"}
      }
    },
    "required": ["name", "capital", "languages"]
  }
}'

Python에서는 Pydantic 모델을 정의하고 model_json_schema()format에 넘긴 뒤, 응답을 검증할 수 있어요.

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': 'Tell me about Canada.'}],
  format=Country.model_json_schema(),
)

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

JavaScript에서는 Zod 스키마를 z.toJSONSchema()로 직렬화해 넘기고, 구조화된 응답을 파싱합니다.

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: 'Tell me about Canada.' }],
  format: z.toJSONSchema(Country),
})

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

예시: 구조화된 데이터 추출

반환받고 싶은 객체를 정의하고 모델이 필드를 채우게 하면 됩니다.

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(
  model='gpt-oss',
  messages=[{'role': 'user', 'content': 'I have two cats named Luna and Loki...'}],
  format=PetList.model_json_schema(),
)

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

예시: 구조화 출력과 비전

비전 모델도 동일한 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]
  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': 'Describe this photo and list the objects you detect.',
    'images': ['path/to/image.jpg'],
  }],
  format=ImageDescription.model_json_schema(),
  options={'temperature': 0},
)

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

안정적인 구조화 출력을 위한 팁

  • 스키마를 Python의 Pydantic 또는 JavaScript의 Zod로 정의하면 재사용과 검증이 쉬워져요.
  • 더 결정적인 결과를 원하면 temperature를 낮추세요(예: 0).
  • 구조화 출력은 OpenAI 호환 API에서 response_format으로도 동작합니다.

더 알아보기 (Learn more)