JSON Schema 규약과 해석¶
한 줄 요약: JSON Schema는 "이 JSON에는 어떤 칸이 어떤 타입으로 들어간다"를 적는 표준 문법이다. 칸 이름과 구조는 우리가 정하지만, 쓰는 단어(
type,properties,items)는 정해져 있다. LLM 도구 호출에서 스키마는 보통 강한 안내일 뿐 강제가 아니다.
📍 만난 상황¶
- 2026-10-27 —
payload에 스키마 힌트를 넣는 방법을 이해하던 중이었다. 2번 물음 → 그날 일지 - 15:24 직전에 "매니저 모델에 정의된 스키마가 그대로 나간다"는 답을 들었다. 그래서 "어떻게 지정하든 그대로 나가면 다 성공하냐, 정해진 약속이 있냐"고 물었다. → 표준 문법이 있고, 회사마다 받아 주는 범위가 다르고, 지켜도 성공이 보장되진 않는다.
- 16:20 이슈 #3561을 올린 직후 "커스텀으로 만든 json 타입을 vllm이 어떻게 읽냐"고 물었다. → 커스텀은 내용뿐이다. vLLM은 표준 단어만 기계적으로 따라간다.
🧩 쉽게 말하면¶
관공서 서식의 작성 규칙을 떠올리자. 어떤 칸을 둘지(이름, 주소, 인원수)는 부서마다 다르다. 하지만 "칸 옆에 '숫자'라고 쓰면 숫자 칸"이라는 작성 규칙은 모든 부서가 같다.
그래서 처음 보는 서식도 누구나 읽을 수 있다. 다만 서식에 "숫자로 쓰시오"라고 적어도, 사람은 "셋"이라고 쓸 수 있다. 막으려면 숫자만 눌리는 키패드(엄격 모드)가 따로 필요하다.
이 규칙이 없으면 회사마다 양식 문법이 달라진다. 도구 하나를 공급자마다 다시 써야 한다.
📖 제대로 알기¶
기본 단어¶
| 키워드 | 뜻 | 예 |
|---|---|---|
type |
값의 종류 | "string", "integer", "boolean" |
properties |
객체 안 칸 목록 | {"name": {"type": "string"}} |
items |
목록 안 항목 모양 | {"type": "object", ...} |
required |
꼭 있어야 하는 칸 | ["id", "resource_type"] |
enum |
허용 값 목록 | ["dataset", "dashboard"] |
additionalProperties |
적지 않은 칸 허용 여부 | true면 아무 칸이나 |
anyOf |
여러 모양 중 하나 | 숫자 또는 날짜 글자 |
description |
설명 글 | 모델이 읽는 안내 |
type에 쓰는 이름은 일곱 가지다. string, number(실수), integer(정수), boolean(참거짓), object(객체), array(목록), null.
"커스텀"인 부분과 "표준"인 부분¶
- 우리 것:
plan,items,payload,nullable같은 칸 이름과 중첩 구조. - 표준: 그릇(OpenAI 형식의
tools,parameters)과 문법(type,properties,items). - 그래서 vLLM은 우리 도구를 몰라도 스키마를 읽을 수 있다.
세 층의 약속¶
- 문법: 최상위는
{"type": "object", "properties": {...}}. 도구 이름은 영문·숫자·_·-만. - 공급자 지원 범위: 회사마다 받아 주는 키워드가 다르다. 안 받는 문법이 있으면 모델이 답하기 전에 API가 요청을 거절한다(보통 400 오류).
- 모델 준수: 받아 줘도 모델이 꼭 지키지는 않는다.
| 공급자·도구 | 일반적 경향 |
|---|---|
| Anthropic(Claude) | 넓게 받아 준다 |
| OpenAI 호환(vLLM의 Gemma 등) | 일반 모드는 넓게. 엄격 모드는 규칙이 까다롭다 |
| Google Gemini API | 일부만 지원. LangChain이 일부 키를 빼거나 거절될 수 있다 |
| vLLM 타입 복원 | properties·items만 따라감. anyOf 안은 안 들어감 |
세부 제한은 회사·버전마다 바뀐다. 경향으로만 보고, 정확한 값은 공식 문서로 확인한다.
안내와 강제¶
| 모드 | 스키마의 역할 | 오늘 사용 |
|---|---|---|
| 일반 모드 | 모델이 보고 따라 쓰는 안내. 틀릴 수 있음 | 사용 중 |
| 엄격 모드(제약 생성) | 서버가 생성 단계에서 스키마에 맞는 글자만 나오게 강제 | 사용 안 함 |
제약 생성(constrained decoding)은 모델이 다음 토큰을 고를 때, 스키마에 어긋나는 토큰을 아예 후보에서 빼는 방식이다.
⚙️ 어떻게 동작하나¶
vLLM이 스키마로 타입을 되돌리는 과정이다. 인자와 스키마를 칸 이름으로 맞춰 가며 함께 내려간다.
1. 도구 이름으로 요청에 온 tools에서 parameters를 찾는다.
2. 값이 객체면 → 스키마의 properties에서 같은 이름의 칸을 찾아 내려간다.
3. 값이 목록이면 → 스키마의 items로 내려간다.
4. 맨 끝 값이면 → 그 칸의 type을 보고 바꾼다("true" → true).
5. 스키마에 칸이 없거나 type이 없으면 → 그대로 둔다.
flowchart TD
P["plan · properties 있음"] --> I["items · items 있음"]
I --> ID["id · type string"]
I --> PL["payload · type object 만"]
PL --> S["schema.fields 안쪽"]
S --> N["nullable 값 그대로 남음"]
payload까지는 길이 있다. 그 아래는 properties가 없어서 길이 끊긴다. 그래서 nullable이 "true"로 남았다.
🧪 예시¶
-
예시 1 · 일상에 빗대어: 우편 분류기가 주소를 "시 → 구 → 동" 순서로 따라간다. 주소록(스키마)에 "강남구"까지만 있고 동 목록이 없다. 그러면 편지 3통 모두 구 단위까지만 분류되고 나머지는 손대지 못한다. 주소록에 동 목록을 적어 주면 같은 분류기가 끝까지 분류한다.
-
예시 2 · 실제로는: 지금
payload스키마와 힌트를 넣은 모양이다.①// 지금: 안쪽이 비어 있음 "payload": {"type": "object", "additionalProperties": true} // 힌트 추가 후 (일부) "payload": { "type": "object", "additionalProperties": true, "properties": { "schema": {"type": "object", "properties": { "fields": {"type": "array", "items": {"type": "object", "properties": { "nullable": {"type": "boolean"} }}} }} } }additionalProperties: true는 그대로라 다른 칸도 계속 허용된다. ②properties→items→properties순으로 길을 이어 줬다. ③ 맨 끝에type: boolean이 있어서 vLLM이"true"를true로 바꿀 수 있다. 쓴 단어는 모두 가장 기본적인 문법이라 공급자 위험이 낮다.
⚖️ 비교 · 흔한 오해¶
| 오해 | 실제 |
|---|---|
| 우리 스키마는 커스텀 형식이다 | 내용만 우리 것. 문법은 표준 |
| 스키마만 맞으면 요청이 성공한다 | 공급자가 거절할 수도, 모델이 틀릴 수도 있다 |
| 스키마가 있으면 모델이 반드시 지킨다 | 일반 모드에서는 안내일 뿐 |
| 표준 문법은 어디서나 다 지원된다 | Gemini처럼 일부만 받는 곳이 있다 |
왜 그렇게 생각하기 쉬운가
- plan, payload처럼 우리 업무 단어가 가득해서 형식 전체가 우리 것처럼 보인다.
- "스키마"라는 말이 DB 스키마처럼 강제 규칙을 떠올리게 한다. DB는 어긋난 값을 막지만, LLM 일반 모드의 스키마는 막지 않는다.
실제로 본 예: 일반 모드라서 Claude도 7번 중 2번 plan 전체를 글자로 감싸 보냈다. 스키마를 지켜도 모델 실수는 생긴다.
🛠️ 실무에서는¶
- 도구 스키마는
type,properties,items,required,enum,description같은 기본 문법 위주로 쓴다. 공급자 호환성이 가장 좋다. anyOf·oneOf는 꼭 필요할 때만 쓴다. 파서 복원이나 일부 공급자가 따라가지 못한다.- 형식 오류를 원천 차단해야 하면 엄격 모드(구조화 출력)를 검토한다. 대신 모든 칸 필수 같은 제약을 감수해야 한다.
- 스키마를 바꾸면 공급자별로 한 번씩 실제 호출해 본다. 특히 Gemini.
- 문제가 생기면 먼저 볼 것: 400 오류면 공급자가 문법을 거절한 것 → 모델 응답의 인자가 스키마와 다르면 모델 실수 → 타입만 다르면 파서 복원 경로.
❓ 자주 묻는 질문¶
- Q.
additionalProperties: true와properties를 같이 써도 되나? A. 된다. 적은 칸은 그 규칙으로, 안 적은 칸은 아무거나 허용한다는 뜻이다. - Q.
integer와number는 무엇이 다르나? A.integer는 정수만,number는 소수도 포함한다. 소수가 올 칸을integer로 적으면 모든 모델이 틀린 규칙을 따르게 된다. - Q. pydantic이 만든 스키마도 표준인가?
A. 그렇다.
model_json_schema()가 JSON Schema 문법으로 만든다.
🔭 더 깊이 보기¶
$ref·$defs로 스키마를 나눠 쓰는 법 — pydantic이 중첩 모델을 이렇게 만들어서, 공급자 호환에 영향을 준다.- 구조화 출력(Structured Outputs)과 제약 생성 — "안내"를 "강제"로 바꾸는 방법이라서.
- 관련 주제: pydantic 검증과 Field 설정, LLM 도구 호출 파서와 타입 복원, LLM 도구 호출(function calling) 흐름
📚 참고 자료¶
- 함수 호출(Function calling) (Gemini API 문서 한국어) — Gemini가 받아 주는 스키마 범위 · 링크
- Understanding JSON Schema (json-schema.org, 영문) — 키워드별 설명과 예시 · 링크
- Structured Outputs (OpenAI 공식 문서, 영문) — 엄격 모드의 제약 조건 · 링크
🗓️ 이력¶
- 2026-10-27: 아무 스키마나 통하는지, 커스텀 스키마를 vLLM이 어떻게 읽는지 2번 물었다 → 일지