6.2.5 중첩 구조(Nested Structures)와 재귀적 스키마 정의
실무의 소프트웨어 파이프라인에서 요구하는 데이터 전송 객체(DTO, Data Transfer Object)는 { "name": "Alice", "age": 25 } 처럼 얄팍한 평면(Flat) 구조로 끝나는 경우가 드물다.
영수증의 품목 리스트, 문서의 체계적 목차, 혹은 객체 지향 프로그래밍의 클래스 구조 등을 거대 언어 모델(LLM)로부터 추출해 내려면 스키마 안에 또 다른 스키마가 들어가는 **중첩 구조(Nested Structures)**를 유창하게 다룰 수 있어야 한다.
1. 객체 내의 객체, 그리고 배열(Array) 내의 객체
가장 흔한 형태의 중첩은 필드의 타입으로 다시 object나 array를 지정하는 방식이다. 쇼핑몰 리뷰 분석 파이프라인을 예로 들어 보자.
{
"properties": {
"review_summary": { "type": "string" },
"defects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"part_name": { "type": "string" },
"issue_type": { "type": "string", "enum": ["SCRATCH", "BROKEN", "MISSING"] }
},
"required": ["part_name", "issue_type"],
"additionalProperties": false
}
}
}
}
이 구조에서 defects 필드는 단순한 문자열 리스트가 아니라, 다시 한번 자신들만의 엄격한 규칙(required, additionalProperties, enum 등)을 갖는 하위 객체들의 배열로 선언되었다. LLM은 디코딩 과정에서 상위 객체의 규칙과 하위 객체의 서브 규칙 사이를 문맥 전환(Context Switching)하며, 단 하나의 예외도 없이 완벽한 계층적 JSON 트리를 조립해 낸다.
2. 모듈화의 마법: $defs 와 $ref
중첩이 깊어지면 스키마 텍스트 자체가 거대해지고, “고객 정보“나 “주소 정보” 같이 반복해서 등장하는 하위 구조체로 인해 코드가 지저분해진다. JSON Schema는 프로그래밍 언어의 함수나 클래스 선언처럼 스키마를 모듈화하여 재사용할 수 있는 기능을 제공한다.
바로 $defs (정의)와 $ref (참조) 메커니즘이다.
{
"$defs": {
"Address": {
"type": "object",
"properties": { "city": { "type": "string" }, "zip_code": { "type": "string" } },
"required": ["city", "zip_code"],
"additionalProperties": false
}
},
"type": "object",
"properties": {
"home_address": { "$ref": "#/$defs/Address" },
"office_address": { "$ref": "#/$defs/Address" }
}
}
이렇게 모듈화를 거치면 스키마의 가독성이 폭발적으로 상승할 뿐만 아니라, LLM이 문맥을 이해하는 데에도 명백한 도움을 주어 추론의 정확도(Accuracy)가 상승하는 효과를 부른다.
3. 재귀적 스키마(Recursive Schema): 무한 계층의 통제
소프트웨어 공학에서 가장 까다로운 데이터 구조는 깊이(Depth)를 예측할 수 없는 트리(Tree) 구조다. 대표적으로 본 서적과 같은 다단계 ‘목차(Table of Contents)’ 생성이나, 회사의 ‘조직도’, 스레드 형식의 ’댓글 대댓글 구조’가 여기에 속한다.
JSON Schema의 $ref는 외부 정의뿐만 아니라 **자기 자신을 참조(Self-Referencing)**할 수도 있다.
{
"$defs": {
"ContentNode": {
"type": "object",
"properties": {
"title": { "type": "string" },
"children_nodes": {
"type": "array",
"items": { "$ref": "#/$defs/ContentNode" }
}
},
"required": ["title", "children_nodes"],
"additionalProperties": false
}
},
"$ref": "#/$defs/ContentNode"
}
items 속성 안에서 다시 자기 자신인 ContentNode를 참조함으로써, 무한히 중첩 가능한 재귀적(Recursive) 스키마가 탄생했다. (단, LLM이 영원히 토큰을 뱉어내는 것을 막기 위해 API 호출 시점의 max_tokens 제한을 반드시 함께 걸어두어야 한다.)
결정론적 오라클 시스템 설계자는 이처럼 재귀적이고 복잡한 스키마를 능수능란하게 직조할 줄 알아야 한다. 스키마가 복잡해지면 복잡해질수록 자연어 파서의 붕괴 확률은 기하급수적으로 낮아지고, 백엔드 데이터 모델(Entity)과의 동질성(Isomorphism)은 극대화되기 때문이다. 모델의 상상력은 철저하게 우리가 직조해 둔 3차원 철창(Schema) 안에서만 허용되어야 한다.