개념
구조화 출력(structured output)은 LLM의 응답을 사람이 읽는 문장이 아니라 프로그램이 그대로 읽을 수 있는 형식으로 받아 내는 기능이다. 보통 JSON 스키마로 필드 이름과 타입, 필수 여부를 미리 정의해 두고, 모델이 그 틀에 맞는 값만 채우게 한다.
이런 기능이 따로 필요한 이유는 모델이 확률적으로 다음 토큰을 고르기 때문이다. 프롬프트로 "JSON으로만 답하라"고 부탁하는 것만으로는 설명 문장이 앞에 붙거나 마지막 쉼표가 어긋나는 일이 생긴다. 수천 건 중 몇 건만 어긋나도 그 뒤 처리 과정 전체가 멈추기 때문에, 부탁이 아니라 보장이 필요해진다.
구현 방식의 핵심은 추론 과정에 제약을 거는 것이다. 매 단계에서 다음에 올 수 있는 토큰을 스키마상 허용되는 것들로 제한하면, 문법적으로 잘못된 출력은 애초에 생성될 수 없다. 형식을 지키는지 검사해서 틀리면 다시 요청하는 방식과 달리, 이쪽은 재시도 없이 형식이 보장된다.
제약이 미치는 범위는 문법까지다. 여는 괄호를 열었으면 닫아야 하고 정의된 필드 이름만 쓸 수 있으며 숫자 자리에 문자열이 올 수 없다는 것까지는 강제되지만, 그 안에 어떤 값이 들어갈지는 여전히 모델의 판단이다. 형식은 보장되고 내용은 보장되지 않는다는 이 경계를 이해하는 것이 구조화 출력을 제대로 쓰는 출발점이다.
쓰임새
에이전트의 도구 호출이 가장 기본적인 무대다. 어떤 도구를 어떤 인자로 부를지를 모델이 정해진 스키마로 내놓아야 시스템이 실제 함수로 연결할 수 있다. 문서에서 항목을 뽑아 표로 만드는 정보 추출, 문의를 유형과 긴급도로 나누는 분류, 여러 단계를 잇는 파이프라인의 중간 결과 전달에도 널리 쓰인다.
실무 설계에서 중요한 것은 필드 이름을 의미가 드러나게 짓고 각 필드에 설명을 붙이는 일이다. 모델은 스키마를 지시문의 일부로도 읽기 때문에, 이름과 설명이 모호하면 형식은 맞는데 내용이 엉뚱한 결과가 나온다. 값이 정해진 몇 가지 중 하나여야 한다면 자유 문자열로 두지 말고 선택지를 열거해 고정하는 편이 훨씬 안정적이다.
빠뜨리기 쉬운 것은 정보가 없을 때의 처리다. 원문에 해당 내용이 없어도 스키마가 필수 필드로 정의해 두면 모델은 무언가를 채워 넣는다. 값을 비워 둘 수 있게 하거나 확인 불가를 나타내는 선택지를 명시적으로 두어야, 없는 정보가 그럴듯한 값으로 둔갑하는 일을 줄일 수 있다.
자주 하는 오해
형식이 보장되면 내용도 정확한 것 아닌가?
보장되는 것은 껍데기뿐이다. 스키마에 맞는 JSON 안에 사실과 다른 값이 들어 있는 환각은 그대로 일어난다. 형식 검증과 값 검증은 별개로 두어야 한다.
프롬프트에 형식을 잘 적어 두면 충분하지 않나?
잘 쓴 프롬프트는 실패율을 낮출 뿐 없애지는 못한다. 자동화 파이프라인처럼 한 번의 실패가 뒤로 번지는 자리에서는 제약 기반 방식을 쓰는 편이 안전하고, 그럴 수 없다면 받은 값을 검증하고 재시도하는 처리를 반드시 붙여야 한다.
스키마는 복잡할수록 좋은 것 아닌가?
중첩이 깊고 필드가 많아질수록 각 필드의 정확도가 떨어지고 응답도 느려진다. 한 번의 호출이 채워야 할 필드를 줄이고 여러 단계로 나누는 편이 결과가 나은 경우가 많으며, 이때 단계를 잇는 방식으로 프롬프트 체이닝이 함께 쓰인다.