구조화된 출력
- 2025-07-27
LLM의 구조화된 출력 모드.
한 연구에 의하면 구조화된 출력을 하면 모델 성능이 떨어질 수 있다고.1
메모
2024년 8월 6일에 OpenAI에서 JSON 스키마에 따른 출력 을 보장해주는 API를 공개했다.2
확률적으로 작동하는 LLM에서 “100% 보장’이라는 게 어떻게 가능한지 궁금해서 자세히 읽어봤더니, 사용자가 제공한 JSON schema로부터 CFG를 만들고 LLM의 토큰 샘플링 단계에서 부적절한 토큰이 선택되지 못하게 막는 방식이다. CFG 생성에 시간이 걸리기 때문에(왜지? CFG 생성 자체가 오래 걸리는 건 아닐테고 토큰 샘플링 단계에서 빠르게 동작할 수 있는 구조를 만드는라 시간이 걸리는 걸까?), 첫 요청에 대해서는 지연이 조금 있다(스키마 크기에 따라 1~5초 정도 느려지더라).
조금 더 찾아보니 이미 유사한 방식들이 있었다.34 예를 들어 jsonformer는 임의의 허깅페이스 모델에 적용해서 쓸 수 있는 모양이다.
아무튼, OpenAI의 새 API를 지금 개발 중인 챗플레잉에 적용해봤는데 잘 작동하는 것 같다. 물론 실제 서비스에 적용하려면 추가로 해야할 일들이 좀 있다.
제일 시간을 많이 쓴 건 스트리밍 처리.
예시의 openai.beta.chat.completions.parse()
대신 openai.beta.chat.completions.stream()
를 쓰고 {stream: true}
를 하면 일단 스트리밍은 된다.
그치만 스트리밍되는 JSON을 UI에 보여주려면 일부만 전송된(따라서 ill-formed & invalid) JSON을 고쳐줘야 한다. 형태를 바로잡는 건 jsonrepair를 써서 해결했고, 유효성 문제(스키마에 부합하도록 아직 전송되지 않은 필드를 채워주기)는 zod schema 정의를 잘해서 해결했다. default()를 쓰지 못하기 때문에(OAI API에서 지원하지 않음), catch()를 쓴다거나 하는 이런저런 꼼수가 필요했다. 스키마를 이렇게 느슨하게 정의하면 별도의 YAML 파일에 담긴 출력 예시(few-shot examples)가 올바른지 단위 테스트에서 검사하기가 어려운데, YAML 파싱만 한 결과랑 스키마 유효성 검사까지 거친 결과가 정확히 일치하는지 보면 된다. (예시에 빠진 필드가 있거나 오타가 있다면 결과가 일치하지 않을테니까.)
필드 길이를 명시할 수 없다거나 하는 제약들이 더 있지만 스키마를 지원한다는 사실만으로도 삶(?)이 한결 편해졌다.