LLM 출력 제어를 위한 핵심 옵션

대규모 언어 모델을 애플리케이션에 통합하는 Spring AI는 강력한 도구입니다.
하지만 단순히 프롬프트만 입력해서는 원하는 결과를 얻기 어려울 수 있습니다.
LLM의 출력은 확률적이며, 이 랜덤성을 제어하여 모델의 응답 스타일을 조정하는 것이 바로 생성 파라미터의 역할입니다.

이번 포스팅에서는 Spring AI를 기반으로 LLM의 랜덤한 응답을 우리가 원하는 방향으로 유도하기 위해 설정할 수 있는 핵심 옵션들인 Temperature, TopK, TopP, 그리고 MaxTokens에 대해 자세히 알아보고, 실제 Spring AI 코드 예제와 함께 적용 방법을 안내합니다.


1. LLM 출력의 '지능'과 '랜덤성'

우리가 사용하는 LLM은 단순히 입력에 대해 항상 똑같은 출력을 내는 함수가 아닙니다.
만약 그렇다면 '지능'이라기보다는 고정된 '계산식'에 가깝겠죠.

지능의 본질은 다양성랜덤성을 내포하는 데 있습니다.
LLM은 다음 단어(토큰)를 예측할 때, 자신이 알고 있는 모든 토큰에 대해 나올 확률을 계산합니다.
이 확률표에서 가장 높은 확률의 토큰만 항상 선택한다면 결과는 늘 똑같을 것입니다.

우리의 목표는 이 확률표를 기반으로 하되, 다양한 응답이 나올 수 있도록 랜덤성을 적용하고, 그 정도를 제어하는 것입니다.


2. 핵심 생성 파라미터 상세 설명

Spring AI에서 ChatOptions를 통해 설정하는 주요 파라미터들은 LLM이 다음 토큰을 선택하는 확률 분포에 간섭하여 응답의 창의성일관성을 조절합니다.

2.1. 온도 🌡️

Temperature는 LLM 출력의 창의성 또는 랜덤성을 조절하는 가장 중요한 파라미터입니다.

  • 원리: 이는 다음 토큰의 확률 분포에 소프트맥스 함수를 적용하는 정도를 제어합니다.
    소프트맥스는 확률값을 극단적으로 몰아주는 역할을 합니다.
    즉, 높은 확률은 더 높게, 낮은 확률은 더 낮게 만들어서 애매모호한 중간값을 없앱니다.

  • 설정값에 따른 영향:
    * textTemperatureapprox0.1\\text{Temperature} \\approx 0.1 (낮음): 확률 분포가 날카로워져 (극도로 샤프) 가장 높은 확률의 토큰만 압도적으로 선택될 가능성이 높아집니다.
    응답이 매우 결정적이고 함수적이며, 정직하고 일관성 있게 나옵니다.
    (예: JSON 포맷팅, 데이터 추출 등)

  • textTemperatureapprox1.0\\text{Temperature} \\approx 1.0 (보통): 원래의 확률 분포를 유지하거나 약간의 변동성을 허용하여 적절히 지능적인 응답이 나옵니다.
    (가장 일반적인 설정값은 0.7 ~ 0.9입니다.)

  • textTemperatureapprox2.0\\text{Temperature} \\approx 2.0 (높음): 확률 분포가 흐릿해져 (블러 처리) 확률이 낮은 토큰도 선택될 가능성이 높아집니다.
    응답이 매우 창의적이고 예측 불가능하며, 때로는 엉뚱하거나 헛소리처럼 들릴 수 있습니다.
    (예: 소설 쓰기, 브레인스토밍 등)

2.2. Top K 🥇

Top K는 다음 토큰을 선택할 후보 토큰의 개수를 제한하는 방식입니다.

  • 원리: 전체 로짓(확률) 분포에서 가장 높은 확률을 가진 상위 K개의 토큰만을 다음 토큰 선택의 후보로 지정합니다.

  • 설정값에 따른 영향:
    K=1K=1: 항상 가장 높은 확률의 토큰만 선택됩니다.
    K=50K=50: 상위 50개의 토큰 내에서만 확률적 선택이 이루어집니다.
    * 효과: 모델이 너무 엉뚱한 토큰을 선택하는 것을 방지하고, 후보의 범위를 제어하여 다양성을 조절합니다.
    KK가 낮을수록 응답은 예측 가능하고 일관성이 높아지며, KK가 높을수록 다양성은 커집니다.

2.3. Top P (누적 확률) 📈

Top P (Nucleus Sampling)는 누적 확률을 기준으로 후보 토큰을 제한하는 방식입니다.

  • 원리: 확률이 높은 토큰부터 더해나가서, 그 누적 확률이 PP 값에 도달할 때까지의 토큰들만 후보로 선택합니다.

  • 설정값에 따른 영향:

    • textTopP=1.0\\text{Top } P = 1.0: 모든 토큰이 후보에 포함됩니다.
    • textTopP=0.6\\text{Top } P = 0.6: 누적 확률이 60%가 될 때까지의 상위 토큰들만 후보로 선택합니다.
    • 효과: 확률값이 매우 낮지만 어쩌다 한 번씩 튀어나오는 토큰들(Long-Tail)을 배제합니다.
      textTopK\\text{Top } K와 함께 사용되어 후보 토큰의 범위를 동적으로 조절할 수 있습니다. 예를 들어, 확률 분포가 평탄할 때는 더 많은 토큰을, 날카로울 때는 더 적은 토큰을 포함하게 됩니다.

💡 참고: 일반적으로 textTopK\\text{Top } KtextTopP\\text{Top } P는 둘 중 하나만 설정하거나, 두 값을 모두 적절히 조절하여 원하는 응답 스타일을 얻습니다.

2.4. Max Tokens (최대 토큰) 📏

Max Tokens는 모델이 출력할 수 있는 최대 토큰의 수를 설정합니다.

  • 주의 사항: textMaxTokens\\text{Max Tokens}를 줄인다고 해서 모델이 답변을 요약해서 짧게 만들어주는 것이 아닙니다.
    단순히 설정된 토큰 수에 도달하면 답변을 강제로 잘라버립니다.

  • 목적: API 사용 비용을 절감하거나, 응답 시간을 제한할 때 사용됩니다.
    짧은 요약 응답을 원한다면, 프롬프트에 "짧게 요약해서 답변해줘"와 같이 명시적으로 요청해야 합니다.


3. Spring AI 코드 ChatOptions 적용

Spring AI에서 이러한 생성 파라미터는 ChatOptions 인터페이스를 통해 모델에 전달됩니다.

아래는 OpenAiChatOptions를 사용하여 위에서 설명한 파라미터들을 설정하는 코드입니다.

ChatOptions` 설정

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.stereotype.Service;

@Service
public class AiGenerationService {

    private final ChatClient chatClient;

    // ChatClient를 주입받아 사용
    public AiGenerationService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    /**
     * LLM 생성 파라미터를 설정하여 응답을 요청하는 메서드
     * @param prompt 사용자 입력 프롬프트
     * @param isCreative 창의적인 응답을 원하는지 여부
     * @return LLM의 응답 내용
     */
    public String generateResponseWithOptions(String prompt, boolean isCreative) {

        // 1. ChatOptions 빌더를 통해 파라미터 설정
        OpenAiChatOptions options = OpenAiChatOptions.builder()
                .withMaxTokens(isCreative ? 200 : 100) // 창의적이면 긴 답변 허용
                .withTopK(isCreative ? 50 : 10)         // 창의적이면 후보군 확장
                .withTopP(isCreative ? 0.9 : 0.7)         // 창의적이면 누적 확률 범위 확장
                .withTemperature(isCreative ? 0.85f : 0.5f) // 창의적이면 Temperature 높임
                .build();

        // 2. ChatClient를 사용하여 요청에 Options 적용
        String response = chatClient.prompt()
                .user(prompt)
                .options(options) // 설정한 옵션을 요청에 적용
                .call()
                .content();

        return response;
    }

    /**
     * 특정 목적에 맞게 Options을 극단적으로 설정하는 예시 (예: JSON 형식의 기계적 응답)
     */
    public String generateJsonOutput(String inputData) {
        // 함수적, 기계적 응답을 위해 Temperature, TopK, TopP를 최소화
        OpenAiChatOptions jsonOptions = OpenAiChatOptions.builder()
                .withTemperature(0.1f) // 거의 함수적으로 작동
                .withTopK(5)           // 후보군을 5개로 극단적으로 제한
                .withTopP(0.5f)          // 누적 확률 50% 이내로 제한
                .withMaxTokens(500)
                .build();

        String prompt = "다음 정보를 기반으로 JSON 객체를 생성해줘: " + inputData;

        return chatClient.prompt()
                .user(prompt)
                .options(jsonOptions)
                .call()
                .content();
    }
}

적용 요약

응답 스타일TemperatureTop KTop PMax Tokens
창의적/소설0.80.8 ~ 1.21.2높게 (ge50\\ge 50)높게 (ge0.85\\ge 0.85)길게
균형잡힌 지능0.70.7 ~ 0.80.8중간중간적절히
함수적/정확0.10.1 ~ 0.50.5낮게 (le10\\le 10)낮게 (le0.7\\le 0.7)짧게

4. LLM 제어

textTemperaturetext{Temperature}textTopKtext{Top } K, textTopPtext{Top } P는 LLM의 '지능'을 우리가 원하는 목적에 맞게 조각하는 도구입니다.
복잡한 추론이나 창의적인 글쓰기에는 textTemperaturetext{Temperature}를 높여 다양성을 확보하고,
데이터 파싱이나 분류처럼 정확하고 일관된 응답이 필요할 때는 textTemperaturetext{Temperature}를 낮추고 textTopKtext{Top } K, textTopPtext{Top } P를 조여 함수적인 반응을 유도할 수 있습니다.

프롬프트 엔지니어링과 더불어 이러한 생성 파라미터를 이해하고 조절하는 것은 Spring AI 기반의 LLM 애플리케이션의 성능과 톤을 결정한다고 생각합니다.

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글