> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aihubmix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 구조화된 출력 Structured Outputs

> JSON Schema를 통해 모델 출력 형식을 제어하는 Structured Outputs 기능으로, OpenAI, Anthropic Claude, Gemini 등 모델의 response_format 및 output_config.format을 지원하며, 자동 디그레이드 및 크로스 프로토콜 변환을 포함합니다.

## 기능 개요

구조화된 출력(Structured Outputs)은 모델의 응답이 사용자가 정의한 JSON Schema를 엄격하게 준수하도록 하여, 반환값을 정규식이나 후처리 없이 프로그램에서 직접 파싱할 수 있도록 합니다.

프롬프트에서 모델에게 "JSON으로 반환해 주세요"라고 요청하는 것과 달리, 구조화된 출력은 \*\*제약 디코딩(Constrained Decoding)\*\*에 기반합니다. 업스트림에서 JSON Schema를 문법 규칙으로 컴파일하여 추론 과정에서 토큰 단위로 생성을 제약하므로, 모델이 Schema를 위반하는 출력을 생성하는 것은 **불가능**합니다.

대표적인 활용 시나리오:

* 비정형 텍스트에서 엔티티 및 필드 추출
* 분류 / 레이블링 / 감성 분석
* 다단계 추론 중간 결과의 표준화된 전달
* Agent 도구 호출 파라미터의 강타입 제약

## 각 프로토콜 파라미터 비교

<Note>
  세 가지 프로토콜의 파라미터 이름은 다르지만 기본 메커니즘은 동일합니다. 모델 출력이 제공된 JSON Schema와 엄격하게 일치합니다.
</Note>

| 프로토콜                               | 파라미터                                       | 지원 모델                                              |
| ---------------------------------- | ------------------------------------------ | -------------------------------------------------- |
| OpenAI Chat `/v1/chat/completions` | `response_format.type: "json_schema"`      | Claude 4.5+, GPT-4o / GPT-5 시리즈, Gemini 시리즈        |
| Anthropic Messages `/v1/messages`  | `output_config.format.type: "json_schema"` | Claude 4.5+ (직접 연결 / Vertex); Bedrock은 4.5-4.6만 지원 |
| OpenAI Responses `/v1/responses`   | `text.format.type: "json_schema"`          | 업스트림 모델 능력에 따름                                     |

### AWS Bedrock 제한 사항

AWS Bedrock에서 Claude 4.7 이상 버전은 Mantle 추론 경로를 사용하며, 해당 경로는 현재 `output_config.format`을 지원하지 않습니다. 게이트웨이는 이러한 모델에 대해 자동으로 `format` 필드를 제거하고 응답 헤더에 디그레이드를 표시합니다(아래 [디그레이드 메커니즘](#자동-디그레이드-메커니즘) 참조). 이로 인해 요청이 오류를 반환하지는 않습니다.

***

## 빠른 시작

### OpenAI 프로토콜 (권장)

구조화된 출력을 지원하는 모든 모델에 적용되며, 벤더 간 공통으로 사용할 수 있습니다.

<CodeGroup>
  ```python Python theme={null}
  from openai import OpenAI

  client = OpenAI(
      api_key="<AIHUBMIX_API_KEY>",
      base_url="https://aihubmix.com/v1",
  )

  response = client.chat.completions.create(
      model="claude-sonnet-5",
      messages=[
          {"role": "user", "content": "다음 정보를 추출하세요: 김철수, 28세, 서울특별시 강남구"}
      ],
      response_format={
          "type": "json_schema",
          "json_schema": {
              "name": "person_info",
              "strict": True,
              "schema": {
                  "type": "object",
                  "properties": {
                      "name": {"type": "string"},
                      "age": {"type": "integer"},
                      "city": {"type": "string"}
                  },
                  "required": ["name", "age", "city"],
                  "additionalProperties": False
              }
          }
      }
  )

  import json
  data = json.loads(response.choices[0].message.content)
  print(data)
  # {"name": "김철수", "age": 28, "city": "서울특별시 강남구"}
  ```

  ```typescript Node.js theme={null}
  import OpenAI from "openai";

  const client = new OpenAI({
    apiKey: "<AIHUBMIX_API_KEY>",
    baseURL: "https://aihubmix.com/v1",
  });

  const response = await client.chat.completions.create({
    model: "claude-sonnet-5",
    messages: [
      { role: "user", content: "다음 정보를 추출하세요: 김철수, 28세, 서울특별시 강남구" },
    ],
    response_format: {
      type: "json_schema",
      json_schema: {
        name: "person_info",
        strict: true,
        schema: {
          type: "object",
          properties: {
            name: { type: "string" },
            age: { type: "integer" },
            city: { type: "string" },
          },
          required: ["name", "age", "city"],
          additionalProperties: false,
        },
      },
    },
  });

  const data = JSON.parse(response.choices[0].message.content);
  console.log(data);
  ```

  ```bash cURL theme={null}
  curl https://aihubmix.com/v1/chat/completions \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer <AIHUBMIX_API_KEY>" \
    -d '{
      "model": "claude-sonnet-5",
      "messages": [
        {"role": "user", "content": "다음 정보를 추출하세요: 김철수, 28세, 서울특별시 강남구"}
      ],
      "response_format": {
        "type": "json_schema",
        "json_schema": {
          "name": "person_info",
          "strict": true,
          "schema": {
            "type": "object",
            "properties": {
              "name": {"type": "string"},
              "age": {"type": "integer"},
              "city": {"type": "string"}
            },
            "required": ["name", "age", "city"],
            "additionalProperties": false
          }
        }
      }
    }'
  ```
</CodeGroup>

### 다른 모델 사용 (GLM-5.2 예시)

동일한 OpenAI 프로토콜 파라미터가 구조화된 출력을 지원하는 모든 모델에 적용되며, `model`만 변경하면 됩니다.

```python Python theme={null}
from openai import OpenAI

client = OpenAI(
    api_key="<AIHUBMIX_API_KEY>",
    base_url="https://aihubmix.com/v1",
)

response = client.chat.completions.create(
    model="glm-5.2",
    messages=[
        {"role": "user", "content": "다음 정보를 추출하세요: 김철수, 28세, 서울특별시 강남구"}
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "person_info",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "age": {"type": "integer"},
                    "city": {"type": "string"}
                },
                "required": ["name", "age", "city"],
                "additionalProperties": False
            }
        }
    }
)

import json
data = json.loads(response.choices[0].message.content)
print(data)
# {"name": "김철수", "age": 28, "city": "서울특별시 강남구"}
```

### Claude 네이티브 프로토콜

Anthropic SDK를 사용하여 직접 호출하며, 파라미터는 `output_config.format`입니다.

<CodeGroup>
  ```python Python theme={null}
  import anthropic

  client = anthropic.Anthropic(
      api_key="<AIHUBMIX_API_KEY>",
      base_url="https://aihubmix.com",
  )

  response = client.messages.create(
      model="claude-sonnet-5",
      max_tokens=1024,
      messages=[
          {"role": "user", "content": "다음 정보를 추출하세요: 김철수, 28세, 서울특별시 강남구"}
      ],
      output_config={
          "format": {
              "type": "json_schema",
              "schema": {
                  "type": "object",
                  "properties": {
                      "name": {"type": "string"},
                      "age": {"type": "integer"},
                      "city": {"type": "string"}
                  },
                  "required": ["name", "age", "city"],
                  "additionalProperties": False
              }
          }
      }
  )

  import json
  data = json.loads(response.content[0].text)
  print(data)
  ```

  ```bash cURL theme={null}
  curl https://aihubmix.com/v1/messages \
    -H "Content-Type: application/json" \
    -H "x-api-key: <AIHUBMIX_API_KEY>" \
    -H "anthropic-version: 2023-06-01" \
    -d '{
      "model": "claude-sonnet-5",
      "max_tokens": 1024,
      "messages": [
        {"role": "user", "content": "다음 정보를 추출하세요: 김철수, 28세, 서울특별시 강남구"}
      ],
      "output_config": {
        "format": {
          "type": "json_schema",
          "schema": {
            "type": "object",
            "properties": {
              "name": {"type": "string"},
              "age": {"type": "integer"},
              "city": {"type": "string"}
            },
            "required": ["name", "age", "city"],
            "additionalProperties": false
          }
        }
      }
    }'
  ```
</CodeGroup>

***

## Schema 작성 요점

### 필수 필드

모든 `object` 타입은 `additionalProperties: false`를 명시적으로 선언해야 합니다. 그렇지 않으면 일부 업스트림에서 요청을 거부합니다.

```json theme={null}
{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "score": { "type": "number" }
  },
  "required": ["name", "score"],
  "additionalProperties": false
}
```

### 중첩 객체

중첩된 `object`에도 마찬가지로 `additionalProperties: false`가 필요합니다:

```json theme={null}
{
  "type": "object",
  "properties": {
    "user": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "email": { "type": "string" }
      },
      "required": ["name", "email"],
      "additionalProperties": false
    }
  },
  "required": ["user"],
  "additionalProperties": false
}
```

### 크로스 프로토콜 Schema 차이

| 특성                                | OpenAI 프로토콜      | Anthropic 프로토콜                   |
| --------------------------------- | ---------------- | -------------------------------- |
| `name` 필드                         | 필수               | 미지원 (게이트웨이가 크로스 프로토콜 호출 시 자동 처리) |
| `strict` 필드                       | 선택 사항, `true` 권장 | 미지원                              |
| 숫자 제약 (`minimum`, `maximum` 등)    | 지원               | 미지원 (게이트웨이가 자동 정리, 요청에 영향 없음)    |
| 문자열 제약 (`minLength`, `maxLength`) | 지원               | 미지원 (게이트웨이가 자동 정리)               |

<Tip>
  OpenAI 프로토콜로 Claude 모델을 호출할 때, 게이트웨이가 Schema 형식을 자동 변환하고 호환되지 않는 키워드를 정리하므로 수동 조정이 필요하지 않습니다.
</Tip>

***

## 자동 디그레이드 메커니즘

게이트웨이는 기본적으로 모든 요청에 대해 구조화된 출력의 자동 디그레이드 보호를 활성화합니다. 모델이나 플랫폼에서 지원하지 않을 경우, 게이트웨이는 **오류를 반환하지 않고** Schema 제약을 자동으로 제거한 후 응답 헤더에 디그레이드 사유를 표시합니다. 요청은 여전히 정상적인 모델 응답을 받으며, 다만 출력이 Schema에 의해 강제 제약되지 않을 뿐입니다.

이는 클라이언트에서 각 모델별 호환성 판단 없이 안심하고 구조화된 출력을 일괄 활성화할 수 있음을 의미합니다:

* **멀티 모델 전환 걱정 없음**: 동일한 코드로 Claude, GPT, Gemini, GLM 간에 모델을 전환할 때, 대상 모델이 구조화된 출력을 지원하지 않더라도 요청이 오류를 발생시키지 않습니다
* **Fallback 라우팅 투명성**: 기본 채널이 사용 불가하여 Fallback 라우팅이 백업 채널로 트리거될 때, 백업 채널의 모델 버전이 구조화된 출력을 지원하지 않더라도 요청이 정상적으로 완료됩니다
* **클라이언트 로직 간소화**: "어떤 모델이 구조화된 출력을 지원하는지" 목록을 유지할 필요가 없으며, 게이트웨이가 자동으로 처리합니다. 클라이언트는 응답 헤더만 확인하여 추가 파싱이 필요한지 결정하면 됩니다

### 응답 헤더

```
X-Structured-Output-Degraded: <reason>
```

| reason                                 | 의미                                                     |
| -------------------------------------- | ------------------------------------------------------ |
| `model_unsupported`                    | 해당 모델(또는 현재 플랫폼에서의 해당 모델)이 구조화된 출력을 지원하지 않습니다          |
| `json_object_unsupported_on_anthropic` | `json_object` 모드를 Anthropic 형식으로 변환할 수 없습니다            |
| `json_schema_missing_schema`           | `json_schema` 타입을 지정했지만 `schema` 필드가 누락되었습니다           |
| `schema_keywords_stripped`             | Schema의 일부 제약 키워드가 정리되었습니다 (예: `minimum`, `maxLength`) |

### 감지 예시

```python Python theme={null}
import httpx

response = httpx.post(
    "https://aihubmix.com/v1/chat/completions",
    headers={
        "Authorization": "Bearer <AIHUBMIX_API_KEY>",
        "Content-Type": "application/json",
    },
    json={
        "model": "claude-sonnet-5",
        "messages": [{"role": "user", "content": "정보를 추출하세요: 김철수, 28세"}],
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "person",
                "strict": True,
                "schema": {
                    "type": "object",
                    "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
                    "required": ["name", "age"],
                    "additionalProperties": False,
                }
            }
        }
    }
)

# 디그레이드 발생 여부 확인 (예: 요청이 미지원 Bedrock 채널로 라우팅된 경우)
degraded = response.headers.get("X-Structured-Output-Degraded")
if degraded:
    print(f"구조화된 출력이 디그레이드되었습니다: {degraded}")
    # 이 경우 모델 응답은 정상이지만 출력이 Schema에 의해 강제 제약되지 않습니다
else:
    import json
    data = json.loads(response.json()["choices"][0]["message"]["content"])
    print(data)  # {"name": "김철수", "age": 28}
```

***

## `json_object` 모드와의 차이

|           | `json_schema` (구조화된 출력)        | `json_object`   |
| --------- | ------------------------------ | --------------- |
| 출력 보장     | 지정된 Schema와 엄격하게 일치            | 유효한 JSON임만 보장   |
| 필드 제어     | 필드명, 타입, 필수 여부 모두 제약           | 제약 없음           |
| 적용 프로토콜   | OpenAI / Anthropic / Responses | OpenAI 호환 프로토콜만 |
| Claude 지원 | `output_config.format`을 통해 지원  | 미지원             |

<Warning>
  `json_object` 모드는 Claude 네이티브 프로토콜로의 변환을 지원하지 않습니다. OpenAI 프로토콜을 통해 Claude에 `response_format: {"type": "json_object"}`를 전송하면, 응답 헤더에 `json_object_unsupported_on_anthropic` 디그레이드가 표시됩니다. `json_schema` 타입을 직접 사용하는 것을 권장합니다.
</Warning>

***

## 자주 묻는 질문

<AccordionGroup>
  <Accordion title="어떤 모델이 Structured Outputs를 지원하나요?">
    **Claude 시리즈** (Anthropic API의 `output_config.format`을 통해):

    * Opus / Sonnet / Haiku 4.5 이상 버전
    * Fable / Mythos 5 이상 버전
    * Bedrock 플랫폼은 4.5-4.6만 지원; Vertex AI는 직접 연결과 동일

    **OpenAI 시리즈** (`response_format`을 통해):

    * GPT-4o 이상, GPT-5 시리즈

    **Gemini 시리즈** (`responseSchema`를 통해):

    * Gemini 2.5 이상

    [모델 목록 페이지](https://aihubmix.com/models)에서 각 모델의 기능 태그를 확인할 수 있습니다.
  </Accordion>

  <Accordion title="왜 Bedrock에서 일부 Claude 모델이 구조화된 출력을 지원하지 않나요?">
    AWS Bedrock에서 Claude 4.7+는 새로운 Mantle 추론 경로를 사용하며, 해당 경로는 현재 `output_config.format` 파라미터를 허용하지 않습니다. 게이트웨이가 자동으로 처리합니다: format을 제거하고 정상적으로 응답을 반환하며, 동시에 디그레이드 헤더 `X-Structured-Output-Degraded: model_unsupported`를 표시합니다. Claude 4.5-4.6은 Bedrock에서 완전히 지원됩니다.
  </Accordion>

  <Accordion title="크로스 프로토콜 호출 시 JSON Schema가 수정되나요?">
    네. OpenAI 프로토콜로 Claude 모델을 호출할 때, 게이트웨이가 자동으로:

    1. `response_format`을 `output_config.format`으로 변환합니다
    2. Anthropic에서 지원하지 않는 Schema 키워드(`minimum`, `maxLength` 등)를 제거합니다
    3. 키워드가 정리된 경우 응답 헤더에 `schema_keywords_stripped`를 표시합니다

    역방향(Claude 프로토콜로 OpenAI 모델 호출)도 마찬가지로 자동 변환됩니다.
  </Accordion>

  <Accordion title="Structured Outputs와 Extended Thinking을 동시에 사용할 수 있나요?">
    네. `output_config`의 `format`(구조화된 출력)과 `reasoning`의 `effort`(사고 강도)는 독립적인 파라미터이므로 동시에 설정할 수 있습니다:

    ```json theme={null}
    {
      "output_config": {
        "format": {
          "type": "json_schema",
          "schema": { ... }
        }
      },
      "reasoning": {
        "effort": "high"
      }
    }
    ```
  </Accordion>

  <Accordion title="OpenRouter 등 통합 플랫폼과 비교했을 때, 디그레이드 메커니즘은 어떻게 다른가요?">
    대부분의 API 통합 플랫폼은 모델이 구조화된 출력을 지원하지 않을 때 오류를 직접 반환합니다. AIHubMix는 **그레이스풀 디그레이드** 전략을 채택합니다: 호환되지 않는 파라미터를 자동으로 제거하고, 정상적으로 모델 응답을 반환하며, `X-Structured-Output-Degraded` 응답 헤더를 통해 클라이언트에 디그레이드 사유를 알립니다. 이로 인해 애플리케이션이 중단되지 않습니다.
  </Accordion>
</AccordionGroup>
