블로그 • AI / JSON Schema
AI에 JSON Schema가 필요한 이유: Structured Output, Function Calling, JSON Schema 정리
ChatGPT, Gemini, DeepSeek를 붙이면 문서에Function Calling, Structured Output, JSON Schema가 동시에 나온다. 세 기능처럼 보이지만, 같은 계약 언어의 세 가지 쓰임이다.
JSON Schema는 「이 JSON이 어떤 모양이어야 하는지」를 기술한다. 현재 주류는2020-12. Function Calling은 도구 인자에, Structured Output은 최종 답에 쓴다. AI에 필요한 이유는 JSON이 유행이라서가 아니라, 자연어를 하위 코드가 안정적으로 소비하지 못해서다.
이 글에서는:
- 세 개념이 각각 무엇을 제약하는지
- 자연어 출력만으로는 부족한 이유
- Function Calling이 schema로 도구 인자를 제약하는 방법
- Structured Output이 schema로 최종 답을 제약하는 방법
- 같은 schema를 두 곳에 재사용하고, 선택과 검증까지
이것만 기억하세요: 모델이 JSON을 내는 것과 schema에 맞는 JSON을 내는 것은 다르다. JSON Schema가 정의하는 것은 후자다. Function Calling은 「어떤 도구를, 올바른 인자로」; Structured Output은 「최종 답의 모양」. 아래는 왜 → 무엇 → 도구 인자 → 최종 답 → 선택 → 검증 순으로 나눈다.
세 개념을 한 표로
먼저 세 단어를 한 표에 둔다. JSON Schema라는 언어는 공유하지만 제약 대상이 다르다.
| 개념 | 제약 대상 | 대표 필드 / 표준 | 푸는 문제 |
|---|---|---|---|
| JSON Schema | JSON 필드, 타입, 필수, enum | JSON Schema 2020-12 | 계약 자체: 데이터가 어떤 모양이어야 하는가 |
| Function Calling | tool_calls.arguments |
tools[].parameters |
어떤 도구를 부를지, 인자가 적법한지 |
| Structured Output | 최종 답 JSON | response_format.json_schema / responseSchema |
결과를 바로 저장하거나 파이프라인에 넣을 수 있는지 |
세 단어가 섞이는 이유는 문서가 기능 이름과 계약 언어를 같이 쓰기 때문이다. 층을 기억하자: JSON Schema는 언어, Function Calling과 Structured Output은 두 가지 쓰임.
AI가 자연어만으로는 부족한 이유
프롬프트에 「JSON을 반환하라」만 쓰면 이런 실패가 난다.
| 실패 모드 | 전형 증상 | 근본 원인 |
|---|---|---|
| 구문 오류 | 따옴표 누락, 후행 쉼표, 작은따옴표 | 모델이 산문처럼 생성하고 token 열이 제약되지 않음 |
| 필드 드리프트 | 같은 프롬프트로 필드명이 두 번 다름 | schema 계약이 없어 모델이 자유롭게 씀 |
| 타입 오류 | 숫자가 문자열로, 배열이 객체로 | 프롬프트가 모호하고 type 하드 제약이 없음 |
| 회귀 불가 | 같은 케이스를 fixture로 비교할 수 없음 | 자연어에는 안정적인 필드 집합이 없다 |
데모에서는 재시도나 이차 파싱으로 가릴 수 있다. 운영 Agent, 파이프라인, 자동화에서는 하위 시스템이 바로 죽는다. JSON Schema는 형식 계약: 생성 전에 필드, 타입, 필수를 선언한다.
JSON Schema는 계약이지 주석이 아니다
JSON Schema는 JSON 구조를 기술하는 표준이다. AI 연동에서는 사람용 주석이 아니라 기계가 실행하는 계약이다:
-
1
필드명과 타입을 선언한다
properties의 각 key가 출력 필드이고, type이 string / integer / array 등을 지정한다.
-
2
필수와 enum을 제한한다
required는 필수 필드, enum은 값 범위를 막아 모델의 「창의적」 값을 막는다.
-
3
여분 필드를 막는다
additionalProperties: false는 선언되지 않은 key를 금지한다. Strict Schema는 보통 이를 강제한다.
{
"type": "object",
"properties": {
"ticket_id": {
"type": "string",
"description": "Support ticket ID, e.g. TCK-1042"
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
},
"summary": {
"type": "string",
"description": "One-sentence problem summary"
}
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": false
}
이 schema는 tools에 있든 response_format에 있든 상관없다. 묻는 것은 「이 JSON이 적법한가」뿐이다. 다음 두 절에서 Function Calling과 Structured Output에 연결한다.
Function Calling: schema로 도구 인자 제약
Function Calling은 추측 대신 구조화된 도구 호출을 반환한다. 각 도구의tools[].parameters가 JSON Schema다:
{
"model": "gpt-4o",
"messages": [
{ "role": "user", "content": "Create a high-priority ticket for checkout timeout." }
],
"tools": [
{
"type": "function",
"function": {
"name": "create_ticket",
"description": "Create a support ticket from the user request.",
"parameters": {
"type": "object",
"properties": {
"ticket_id": { "type": "string" },
"priority": { "type": "string", "enum": ["low", "medium", "high"] },
"summary": { "type": "string" }
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": false
}
}
}
]
}
모델이 반환하는tool_calls[0].function.arguments는 JSON 문자열이다. parameters에 「최대한」 맞추지만 매번 유효하진 않다. 누락, 타입 오류, 구문 깨짐이 난다. 파싱과 검증은 비즈니스 계층의 일이다.
Agent 루프, MCP inputSchema, 더 자세한 도구 작성은AI Agent와 JSON Schema 해설을 본다.
Structured Output: schema로 최종 답 제약
Structured Output이 제약하는 것은 도구 인자가 아니라 최종 답이다. OpenAI는response_format, Gemini는responseSchema. 밑바닥은 둘 다 JSON Schema:
{
"model": "gpt-4o-2024-08-06",
"messages": [
{ "role": "user", "content": "Extract a support ticket from this email." }
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": true,
"schema": {
"type": "object",
"properties": {
"ticket_id": { "type": "string" },
"priority": { "type": "string", "enum": ["low", "medium", "high"] },
"summary": { "type": "string" }
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": false
}
}
}
}
strict: true를 켜면 추론 단계에서 schema가 token을 이끈다. 생성한 뒤 기도하는 것이 아니라, 적합한 것만 생성한다.
ChatGPT와 Gemini 설정 차이, JSON Mode와 Strict Schema의 보장 범위는2026 AI Structured Output 가이드를 본다.
같은 schema, 두 연결점
이 글의 요점: 도구 인자와 최종 답에 서로 어긋나는 두 세트 필드를 쓰지 말 것. 티켓은 세 필드 그대로다. 바뀌는 것은 연결점뿐이다.
TICKET_SCHEMA = {
"type": "object",
"properties": {
"ticket_id": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
"summary": {"type": "string"},
},
"required": ["ticket_id", "priority", "summary"],
"additionalProperties": False,
}
# 1) Function Calling: constrain tool arguments
tools = [{
"type": "function",
"function": {
"name": "create_ticket",
"description": "Create a support ticket.",
"parameters": TICKET_SCHEMA,
},
}]
# 2) Structured Output: constrain the final answer
response_format = {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": True,
"schema": TICKET_SCHEMA,
},
}
하나의 계약, 세 착지:
-
1
도구 호출
모델이 create_ticket을 고르고, arguments는 schema에 맞아야 한다.
-
2
최종 답
도구가 필요 없을 때도 최종 JSON은 같은 필드이며 그대로 저장할 수 있다.
-
3
비즈니스 검증
데이터가 tool_calls든 message.content든 같은 parse + validate를 탄다.
네 경로를 고르는 법
2026년 LLM 구조화 데이터는 보통 네 경로다:
| 경로 | API 설정 | 보장하는 것 | 쓰는 장면 |
|---|---|---|---|
| 프롬프트만 | 특수 설정 없음 | 형식을 보장하지 않음 | 프로토타입, 사람이 읽음 |
| JSON Mode | response_format: json_object |
유효한 JSON 객체 | 단순 추출, 느슨한 필드 |
| Function Calling | tools[].parameters |
arguments가 parameters에 최대한 맞춤 | 외부 도구 / DB 기록 / HTTP |
| Structured Output | json_schema + strict: true |
schema에 엄격히 적합 | 최종 결과를 저장하거나 파이프라인에 넣을 때 |
Agent 네 층(LLM, Function Calling, MCP, JSON Schema) 조합은2026 AI Agent 스택을 본다.
운영 작성과 검증
Structured Output과 Function Calling 성공률은 schema 품질에 달렸다. 비즈니스 계층은 그래도 검증한다.
-
1
모든 property에 description을 쓴다
모델은 description을 읽고 값을 정한다. 「Support ticket ID, e.g. TCK-1042」가 맨 필드명보다 정확하다.
-
2
문장으로 암시하지 말고 enum을 쓴다
문장의 「low/medium/high」보다
enum가 확실하다. -
3
additionalProperties: false를 켠다
OpenAI Strict는 보통 이를 강제한다. 루트 객체에
additionalProperties: false를 넣어 미선언 키를 막는다. -
4
중첩을 너무 깊게 하지 않는다
3층 이상이나 많은 oneOf는 실패율을 올린다. 복잡한 형태는 여러 번 호출로 나누는 편이 안정적이다.
import json
from jsonschema import validate, ValidationError
def parse_model_json(raw: str, schema: dict) -> dict:
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON: {e}") from e
try:
validate(instance=data, schema=schema)
except ValidationError as e:
raise ValueError(f"Schema mismatch: {e.message}") from e
return data
ChatGPT, Gemini, DeepSeek 어느 쪽이든 같은 parse → validate → 비즈니스 로직. 개발 중에는 schema와 실제 출력을 JSONNote에 붙이는 편이 print보다 빠르다.
JSONNote로 모델 JSON 디버그
느린 부분은 API를 쓰는 일이 아니라, 돌아온 JSON이 계약 어디를 깨는지 보는 일이다. JSONNote는 브라우저에서 로컬로 동작한다:
-
1
schema와 출력을 검증한다
API JSON과 schema를JSON Schema 페이지에 붙이면 계약에 안 맞는 필드가 바로 보인다.
-
2
API 응답을 포맷한다
JSON 포맷터로 구문과 들여쓰기를 확인한다.
-
3
두 호출을 비교한다
JSON Diff로 두 번의 structured output 또는 tool arguments를 비교해 회귀 검사한다.
자주 묻는 질문
Function Calling과 Structured Output의 차이는?
Function Calling은 tool_calls.arguments(어떤 도구를, 어떤 인자로)를 제약한다. Structured Output은 최종 답 JSON을 제약한다. 둘 다 JSON Schema지만 층이 다르다. Agent에서는 자주 함께 쓴다: tools가 외부 동작, Structured Output이 최종 구조화 결과.
JSON Mode만으로 충분한가?
부족하다. JSON Mode(response_format.type=json_object)는 유효한 JSON 객체만 보장하고 필드와 타입은 보장하지 않는다. 운영의 복잡한 형태에는 Structured Output(json_schema + strict:true 또는 Gemini responseSchema)을 쓴다.
Strict Schema를 켜도 수동 검증이 필요한가?
필요하다. API 계층 제약은 비즈니스 validate를 대체하지 않는다. 빈 content, 잘린 페이로드, 엣지 케이스는 남는다. try/except 파싱 + JSON Schema validate가 운영 기본이다.
JSON Schema는 어떤 버전을 써야 하나?
2026년 주요 LLM API와 MCP 도구 계약은 JSON Schema 2020-12다. type, properties, required, enum, items, additionalProperties를 지원한다.
한 schema를 도구와 최종 출력에 같이 쓸 수 있나?
가능하다. 같은 객체 정의를 tools[].parameters와 response_format.json_schema.schema에 넣을 수 있다. 단일 진실을 유지해 두 계약이 어긋나지 않게 한다.
요약
AI에 JSON Schema가 필요한 이유는 모델 출력을 「데이터처럼 보이는 것」에서 「기계가 검증할 수 있는 계약」으로 바꾸기 때문이다.
JSON Schema는 언어다. Function Calling은 도구 인자에, Structured Output은 최종 답에 쓴다.
schema를 하나 쓰고 두 곳에 연결한 뒤 비즈니스 계층에서 validate한다. JSONNote에서 로컬로 포맷, 검증, Diff. 데모에서 운영으로 가는 가장 짧은 길이다.
다음에 해볼 것