
최근 챗 컴플리션 모델을 서비스에 도입할 때, 단순히 질문을 던지는 것을 넘어 비용 최적화와 응답 품질 제어가 핵심 과제로 떠오르고 있습니다.
오늘은 모델이 메시지를 인식하는 방식과 비용을 아끼는 기술적 장치인 '프리픽스 캐시', 그리고 효과적인 프롬프트 배치 전략을 정리해 보겠습니다.
LLM은 단순히 텍스트를 받는 것이 아니라, 지시자를 포함한 메시지 리스트를 입력받습니다.
system, user, assistant라는 지시자를 통해 각각의 역할을 구분하도록 학습되었습니다.긴 대화를 이어갈 때 가장 큰 부담은 토큰 비용입니다.
이를 해결하기 위해 많은 유료 모델이 프리픽스 캐시를 지원합니다.
동작 원리: 두 번째 질문을 보낼 때, 첫 번째 질문과 앞부분이 일치하면 모델은 이전에 계산했던 가중치를 재사용합니다.
작동 조건:
모델의 성격과 규칙을 정하는 system 프롬프트는 어디에 위치해야 할까요?
최상단 배치: 대다수 모델은 학습 시 시스템 프롬프트를 맨 앞에 두고 학습했습니다. 따라서 지시를 가장 정확하게 인식하게 하려면 메시지 리스트의 첫 번째에 두는 것이 국룰입니다.
어텐션 희석 문제: 대화가 길어지면 모델의 주의력이 뒤쪽 토큰에 쏠리면서, 맨 앞의 시스템 지시를 망각하기 시작합니다.
해결책
리마인드 시스템 메시지: 대화 중간이나 마지막에 "너는 JSON으로 대답해야 한다는 걸 기억해"라는 식의 요약된 시스템 메시지를 다시 삽입합니다.
유저 프롬프트 강화: 유저의 질문 마지막에 지시 사항을 살짝 덧붙여 어텐션을 다시 끌어올립니다.
프롬프트에 예시를 주는 기법은 양날의 검입니다.
다음은 위 원칙들을 적용하여 시스템 메시지를 최상단에 고정하고, 캐시 효율을 위해 순서를 유지하며, 필요 시 지시 사항을 강화하는 백엔드 서비스 코드입니다.
import org.springframework.stereotype.Service
/**
* LLM 메시지 역할 정의
*/
enum class ChatRole {
SYSTEM, USER, ASSISTANT
}
data class ChatMessage(
val role: ChatRole,
val content: String
)
@Service
class LlmChatService {
/**
* 실무형 메시지 리스트 생성 로직
* 1. 시스템 프롬프트를 최상단에 배치 (캐시 유지 및 지시 강화)
* 2. 유저와 어시스턴트의 대화 순서 엄격 준수
* 3. 대화가 길어질 경우 마지막 유저 메시지에 지시 사항 리마인드 추가
*/
fun buildMessages(
systemInstruction: String,
history: List<ChatMessage>,
userQuestion: String,
isJsonMode: Boolean = true
): List<ChatMessage> {
val messages = mutableListOf<ChatMessage>()
// [Rule 1] 시스템 프롬프트는 무조건 인덱스 0번
messages.add(ChatMessage(ChatRole.SYSTEM, systemInstruction))
// [Rule 2] 히스토리 추가 (순서가 바뀌면 프리픽스 캐시가 깨짐 주의)
messages.addAll(history)
// [Rule 3] 어텐션 희석 방지를 위한 전략
val finalUserContent = if (isJsonMode && history.size > 5) {
// 대화가 길어지면 유저 질문 뒤에 지시사항을 리마인드하여 어텐션 강화
"$userQuestion\n\n(Reminder: 응답은 반드시 JSON 형식을 유지해 주세요.)"
} else {
userQuestion
}
messages.add(ChatMessage(ChatRole.USER, finalUserContent))
return messages
}
}
["LLM은 마법이 아니라 인터페이스다"]
수많은 API를 연동하고 비즈니스 로직을 짜왔지만, LLM은 확실히 결이 다르다는것을 배운
시간이였습니다.
예전에는 'Input A를 넣으면 Output B가 나온다'는 결정론적 사고가 지배적이었다면, 이제는 확률론적 사고를 백엔드에 녹여내야 한다 생각합니다.
실무에서 가장 무서운 것은 '불확실성'과 '비용'인데
이번에 정리한 프리픽스 캐싱은 단순히 돈을 아끼는 기술을 넘어, 인프라 비용을 예측 가능하게 만드는 중요한 전략입니다.
또한, 시스템 프롬프트의 위치를 고민하는 과정은 과거 디자인 패턴을 고민하던 시간과 비슷하는것을배웠습니다.
"프롬프트 엔지니어링은 기획자의 영역이다?" 아니라. 시스템 메시지를 어디에 배치하고, 컨텍스트를 어떻게 관리하여 캐시 효율을 높일지 결정하는 것은 철저히 백엔드 엔지니어의 엔지니어링 영역 이라는것을 느꼈습니다.