Spring AI 적용 가이드 (3) - 프롬프트 관리 & Structured Output

Spring AI의 메시지 모델, 변수 템플릿링, DB 기반 프롬프트 관리, JSON Schema를 활용한 Structured Output을 다룹니다.

프롬프트가 왜 중요한가

LLM을 서비스에 적용할 때, 코드보다 프롬프트 품질이 결과를 더 크게 좌우한다. 같은 모델이라도 프롬프트를 어떻게 작성하느냐에 따라 응답 품질이 천차만별이다.

문제는 프롬프트 엔지니어링이 반복적인 실험이 필요한 작업이라는 점이다. 프롬프트를 수정할 때마다 코드를 고치고 배포하는 건 비효율적이다. 그래서 우리는 프롬프트를 DB에서 관리하고, 배포 없이 실시간으로 변경할 수 있는 구조를 만들었다.

이번 편에서는 Spring AI의 메시지 모델부터, 프롬프트 관리 시스템과 Structured Output까지 다룬다.

Spring AI의 메시지 모델

LLM API는 대부분 역할(role) 기반 메시지 구조를 사용한다. Spring AI도 이에 맞춰 Message 인터페이스와 구현체를 제공한다.

ChatClient에서 메시지 사용

ChatClient의 fluent API로 간단하게 메시지를 구성할 수 있다.

여러 메시지를 리스트로 전달할 수도 있다:

변수 템플릿링

프롬프트에 동적 데이터를 삽입해야 하는 경우가 많다. 학습자 이름, 수업 내용, 레벨 등을 프롬프트에 넣어야 하는데, 매번 문자열을 직접 조합하면 코드가 지저분해진다.

우리는 {변수명} 패턴으로 프롬프트 템플릿을 작성하고, 런타임에 치환하는 방식을 사용한다.

프롬프트 템플릿 예시

변수 치환 구현

키를 길이 역순으로 정렬하는 이유가 있다. {feedback}와 {feedback_type} 두 변수가 있을 때, {feedback}를 먼저 치환하면 {feedback_type}의 일부가 잘못 치환될 수 있다. 긴 키부터 처리하면 이 문제를 방지할 수 있다.

다양한 변수 포맷 지원

외부에서 넘어오는 변수 키 포맷이 일정하지 않을 수 있다. {var}, {{var}}, 그냥 var 등 다양한 형태를 모두 처리하도록 했다.

키 하나에 대해 여러 포맷의 변형을 모두 등록해두면, 프롬프트 템플릿에서 어떤 포맷을 쓰든 치환이 동작한다. 프롬프트 작성자가 포맷을 신경 쓰지 않아도 되니 편하다.

Collection 값 처리

변수 값이 컬렉션(리스트, 셋 등)인 경우 콤마로 연결한다.

예를 들어 tags = ["grammar", "vocabulary", "pronunciation"]이면 "grammar,vocabulary,pronunciation"으로 치환된다.

DB 기반 프롬프트 관리

프로덕션에서 프롬프트를 코드에 하드코딩하면 수정할 때마다 배포해야 한다. AI 서비스 운영에서 이건 큰 병목이다.

우리는 프롬프트를 DB 테이블에 저장하고, 백오피스에서 관리하는 구조를 만들었다.

프롬프트 엔티티

프롬프트 조회 및 사용

이 구조의 장점: 배포 없이 프롬프트 수정: 백오피스에서 프롬프트 텍스트를 바꾸면 즉시 반영 버전 관리: 문제가 생기면 이전 버전으로 롤백 모델 동적 변경: modelId 값만 바꾸면 해당 프롬프트의 모델 교체 A/B 테스트: 같은 타입의 프롬프트 여러 개를 만들어 성능 비교 다국어/레벨별 분기: langType, contentLevel로 조건별 프롬프트 관리

Structured Output

LLM 응답을 코드에서 처리하려면 정해진 형식이어야 한다. "JSON으로 응답해주세요"라고 프롬프트에 넣는 것만으로는 불안정하다. LLM이 마크다운 코드블록으로 감싸거나, 필드를 누락하거나, 형식을 깨뜨리는 경우가 빈번하다.

JSON Schema 방식