콘텐츠로 이동

JSON Schema 규약과 해석

10/27(화)

한 줄 요약: 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은 우리 도구를 몰라도 스키마를 읽을 수 있다.

세 층의 약속

  1. 문법: 최상위는 {"type": "object", "properties": {...}}. 도구 이름은 영문·숫자·_·-만.
  2. 공급자 지원 범위: 회사마다 받아 주는 키워드가 다르다. 안 받는 문법이 있으면 모델이 답하기 전에 API가 요청을 거절한다(보통 400 오류).
  3. 모델 준수: 받아 줘도 모델이 꼭 지키지는 않는다.
공급자·도구 일반적 경향
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 문법으로 만든다.

🔭 더 깊이 보기

📚 참고 자료

  • 함수 호출(Function calling) (Gemini API 문서 한국어) — Gemini가 받아 주는 스키마 범위 · 링크
  • Understanding JSON Schema (json-schema.org, 영문) — 키워드별 설명과 예시 · 링크
  • Structured Outputs (OpenAI 공식 문서, 영문) — 엄격 모드의 제약 조건 · 링크

🗓️ 이력

  • 2026-10-27: 아무 스키마나 통하는지, 커스텀 스키마를 vLLM이 어떻게 읽는지 2번 물었다 → 일지

🕸️ 연결된 주제