6.5.5. 복합 의도 분류를 위한 다형성(Polymorphism) 스키마 설계 (`OneOf`, `AnyOf`)

6.5.5. 복합 의도 분류를 위한 다형성(Polymorphism) 스키마 설계 (OneOf, AnyOf)

시스템에 인입되는 고객이나 사용자의 거친 자연어 발화(Utterance)는 모델의 테스트셋처럼 언제나 예쁘고 단일한 목적(Single Intent)만을 순수하게 담고 있지 않다. 라이브 프로덕션 환경에서는 “어제 산 모니터 배송 파손 났으니 즉시 환불해주시고요, 같이 주문한 무선 키보드는 대체 언제 배송되나요? 아, 그리고 이번 달 제 VIP 포인트 적립금 잔액도 확인해서 알려주세요.” 와 같이, 단 하나의 텍스트 문장 블록 안에 완전히 성격과 도메인이 다른 여러 개의 비즈니스 로직 요구사항이 끈적하게 혼재되어 있는 ‘복합 의도(Complex Intent)’ 상황이 심심치 않게 발생한다.

이러한 무질서한 상황에서, 단순히 파싱 에러를 피하겠다고 백엔드의 모든 마이크로서비스(Microservices) 파라미터를 한곳에 긁어모아, 수십 개의 Optional 타입 변수와 Nullable 속성 필드성가 덕지덕지 달려 있는 거대하고 뚱뚱한(Fat) 단일 글로벌 JSON 스키마를 LLM에게 던져주는 것은 설계적으로 최악의 안티 패턴(Anti-pattern)이다.
대신, 훌륭한 오라클(Oracle) 스키마 설계자는 객체 지향 프로그래밍(OOP)의 정수이자 JSON Schema 자체 스펙이 완벽하게 지원하는 ‘다형성(Polymorphism)’ 기능, 즉 oneOfanyOf 속성을 적극적으로 활용하여 LLM의 출력 노드가 우아하고 안전하게 분기(Branching)되도록 런타임 스택을 유연하게 설계해야 한다.

1. Union 타입과 다형성(Polymorphism) 객체 배열의 설계

모호한 프롬프트 엔지니어링의 세계에서 주먹구구식으로 다루던 ‘다중 의도 파악(Multi-Intent Extraction)’ 텍스트 분리 작업을, 정적 타입 컴파일 애플리케이션의 언어로 엄밀하게 번역 정규화하면, 결국 이는 **‘서로 다른 서브 속성 형태(Shape)를 가진 데이터 객체들의 교집합 배열(Array of Union Types)’**을 만들어내는 대수학적 타입 시스템(Algebraic Type System) 설계 문제로 귀결된다.

파이썬 백엔드 생태계의 대세인 Pydantic 패키지를 이용해, 서브 트리가 완전히 다른 세 가지 목적(환불, 배송 조회, 일반 잔액 문의)을 가진 스키마를 하나의 다형성 배열(Polymorphic Array)로 타이트하게 묶어내는 고급 아키텍처 패턴 코드는 다음과 같다.

from pydantic import BaseModel, Field
from typing import Literal, Union, List

# 1. [환불 전담] 결제 도메인 전용 격리 스키마
class RefundIntentSchema(BaseModel):
    # 구별자(Discriminator) 필드 강제
    intent_type: Literal["REFUND"] 
    order_id: str = Field(description="결제 DB에서 조회할 정확한 '-' 포함 주문 번호")
    reason: str = Field(description="고객의 불만 및 환불 사유 요약 (10자 이상)")

# 2. [물류 조회] 배송 도메인 전용 격리 스키마
class ShippingIntentSchema(BaseModel):
    intent_type: Literal["SHIPPING_STATUS"]
    item_name: str = Field(description="현재 배송 현황 상태를 추적 조회할 타겟 상품명")

# 3. [일반 문의] CS 도메인 전용 일반 스키마
class GeneralInquiryIntentSchema(BaseModel):
    intent_type: Literal["GENERAL_INQUIRY"]
    question: str = Field(description="마이페이지/포인트 관련 사용자의 질문 핵심 의도 내용")

# Type Hinting: 대수적 타입(Algebraic Data Types) Union을 통한 엄격한 다형성 교집합 적용 
# 이 세 가지 형태 외의 그 어떤 객체 구조도 허용하지 않음
IntentUnionTarget = Union[RefundIntentSchema, ShippingIntentSchema, GeneralInquiryIntentSchema]

# 최종 메인 오라클 출력 스키마 (Main Output DTO)
class MultiIntentExtractionOracle(BaseModel):
    intents: List[IntentUnionTarget] = Field(
        description="사용자의 거친 복합 발화에 포함된 모든 독립된 의도를 분리하여 명시된 배열 형태로 추출해라.",
        discriminator="intent_type" # 다형성 분기를 캐스팅할 기준 구별자 필드명 명시적 선언
    )

2. 구별자(Discriminator) 필드가 스키마에 부여하는 기하학적 제약의 의미

위 Pydantic 오라클 명세 코드에서 수학적으로 가장 의미 심장하고 핵심적인 제약 기법은, 바로 각 서브 하위 클래스 객체에 하드코딩(Hard-coding)된 intent_type: Literal["..."] 필드와, 부모 메인 래퍼(Wrapper) 클래스에서 선언한 discriminator="intent_type" 속성의 강력한 결합이다.

거대 언어 모델(LLM)이 제약된 디코딩(Constrained Decoding) 모드에서 중첩 배열(Nested Array) 구조의 토큰(Token)을 런타임에 한 땀 한 땀 생성(Generation)하기 시작할 때, intent_type이라는 약속된 구별자(Discriminator) 토큰을 가장 먼저 내뱉어 결정하고 나면,
그 즉시 C/Rust 코어로 짜여진 파서 모델의 문법 트리(Context-Free Grammar, CFG) 상태 기계 머신(State Machine)은 확률 트리 구조상에서 **‘선택되지 않은 나머지 다른 모든 서브 객체의 생성 경로 노드’**를 메모리에서 가차 없이 완전히 파기(Pruning)해 버린다.

예를 들어 확률 샘플링 도중 모델이 스스로 "intent_type": "REFUND" 토큰 문자열을 선택하여 출력하는 순간, 그 배열 인덱스의 이어진 텍스트 토큰 생성 트리 경로는 오로지 환불 스키마의 속성인 order_idreason 필드만을 필수적으로 렌더링하고 허용하도록 가장 강력한 수학적 족쇄로 구속(Constrained)된다. 이 상태에서는 모델이 제멋대로 물류 스키마의 item_name 필드를 섞어서 창조하고 싶어도 확률적으로 절대 생성해 낼 수가 없다.
그 단일 객체의 구속된 렌더링이 중괄호 }로 안전하게 끝나면, 모델은 다시 메인 배열(Array)의 다음 콤마(,) 인덱스 요소로 넘어가 또 다른 intent_type 구별자를 독립적으로 선택하고, 그에 맞는 새로운 서브 스키마 가지(Branch) 규칙을 엄격하게 다시 따르게 된다.

3. 소결: 백엔드 MSA 라우터(Backend Router)가 얻는 진정한 컴퓨팅 자유

이 거대하고 복잡한 다형성(Polymorphism) 오라클 스키마 명세를 무사히 통과하여 얻어낸 JSON 페이로드(Payload) 결과물 객체는, 더 이상 백엔드 개발자가 정규표현식으로 위험하고 지저분하게 파싱(Parsing)하며 에러 처리를 고민해야 할 문법적으로 모호한 자연어 문자열 덩어리가 절대 아니다.
엄격한 Pydantic이나 Zod 라이브러리에 의해 메모리 단에서 철저히 유효성 리플렉션(Reflection) 검증을 마친 후 온전히 매핑되어 객체화된 결괏값은, JVM이나 파이썬 런타임 안에서 타겟 클래스 타입(Class Type)이 아주 명확하게 분리된 안전한 인스턴스(Instance)들의 순수한 리스트(List) 덩어리가 된다.

이제 애플리케이션 서버의 백엔드 비즈니스 로직(Backend API Router) 트래픽 제어기는, 단지 if isinstance(intent_instance, RefundIntentSchema): 또는 switch(intent.intent_type) 같은 가장 단순하고 프로그래머틱하게 안전한 런타임 타입 캐스팅(Type Casting) 분기 검사 구문 하나만으로도, 사용자 발화 내의 각각의 쪼개진 목적 페이로드에 따라 사내의 무거운 ‘환불/결제팀 API 서버’ 격리망이나 ‘물류/택배 네트워크팀 API 서버’ 망으로 HTTP 비동기 큐 요청을 아주 치밀하고 깔끔하게 병렬 코루틴(Parallel Coroutine)으로 분산 처리시킬 수 있다.

이처럼 다형성 오라클 스키마 속성(OneOf, AnyOf) 패턴은, 파운데이션 LLM 모델들이 태생적으로 가진 끈끈하게 응집되고 복잡한 자유도 높은 모호한 자연어 해석 통합 능력을, 기계 친화적인 독립된 레고 블록(Lego Blocks)의 컴포넌트 데이터 단위로 극단적으로 잘게 쪼개어, 현대적인 마이크로서비스 아키텍처(MSA)의 거대한 파이프라인 입력값 프로토콜 버퍼로 그 어떤 장애 없이 가장 신뢰성 있고 완벽하게 도킹(Docking)시키는 고수준의 브릿징(Bridging) 아키텍트 스키마 설계 기법이다.