Spring AI 프롬프트 엔지니어링 패턴

Spring AI 프롬프트 엔지니어링 패턴 (Prompt Engineering Patterns)

Prompt Engineering 기법을 실제 Java 코드로 구현한 실용 가이드예요. 프롬프트 엔지니어링 가이드의 이론과 원리, 패턴을 Spring AI의 플루언트 ChatClient API로 옮겨서 보여 줍니다. 데모 소스 코드는 Prompt Engineering Patterns Examples에서 볼 수 있어요.

1. 구성 (Configuration)

LLM 프로바이더 선택

프롬프트 엔지니어링을 시작하려면 먼저 모델을 골라야 해요. Spring AI는 여러 LLM 프로바이더(OpenAI, Anthropic, Google GenAI, AWS Bedrock, Ollama 등)를 지원해서, 애플리케이션 코드를 바꾸지 않고 설정만 갱신하면 프로바이더를 전환할 수 있습니다.

선택한 스타터 의존성 spring-ai-starter-model-<MODEL-PROVIDER-NAME> 을 추가하면 돼요. 예를 들어 Anthropic Claude API를 활성화하려면:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-anthropic</artifactId>
</dependency>

LLM 모델 이름은 이렇게 지정합니다:

.options(ChatOptions.builder()
        .model("claude-sonnet-4-6")  // Use Anthropic's Claude model
        .build())

LLM 출력 구성

프롬프트 엔지니어링 기법으로 들어가기 전에 LLM의 출력 동작을 어떻게 구성하는지 이해하는 게 중요해요. Spring AI는 ChatOptions 빌더를 통해 생성의 여러 측면을 제어하는 구성 옵션을 제공합니다.

모든 구성은 아래 예시처럼 프로그래밍 방식으로, 또는 시작 시점에 Spring 애플리케이션 속성으로 적용할 수 있습니다.

Temperature

Temperature는 모델 응답의 무작위성 또는 "창의성"을 제어합니다.

  • 낮은 값 (0.0-0.3): 더 결정적이고 집중된 응답. 사실 질문, 분류, 일관성이 중요한 작업에 좋아요.
  • 중간 값 (0.4-0.7): 결정성과 창의성 사이의 균형. 일반 용도에 좋습니다.
  • 높은 값 (0.8-1.0): 더 창의적이고 다양하며 예상 밖일 수 있는 응답. 창작, 브레인스토밍, 다양한 옵션 생성에 좋아요.
.options(ChatOptions.builder()
        .temperature(0.1)  // Very deterministic output
        .build())

출력 길이 (MaxTokens)

maxTokens 파라미터는 모델이 응답에서 생성할 수 있는 토큰(단어 조각) 수를 제한합니다.

  • 낮은 값 (5-25): 단일 단어, 짧은 구, 분류 라벨용.
  • 중간 값 (50-500): 단락 또는 짧은 설명용.
  • 높은 값 (1000+): 장문 콘텐츠, 스토리, 복잡한 설명용.
.options(ChatOptions.builder()
        .maxTokens(250)  // Medium-length response
        .build())

샘플링 제어 (Top-K와 Top-P)

이 파라미터들은 생성 중 토큰 선택 과정을 세밀하게 제어합니다.

  • Top-K: 토큰 선택을 가장 가능성 높은 다음 토큰 K개로 제한. 높은 값(예: 40-50)이 더 다양한 결과를 만듭니다.
  • Top-P (nucleus sampling): 누적 확률이 P를 초과하는 가장 작은 토큰 집합에서 동적으로 선택. 0.8-0.95 같은 값이 흔해요.
.options(ChatOptions.builder()
        .topK(40)      // Consider only the top 40 tokens
        .topP(0.8)     // Sample from tokens that cover 80% of probability mass
        .build())

이 샘플링 제어는 temperature와 함께 작동해 응답 특성을 결정합니다.

구조화 응답 형식

평문 텍스트 응답(.content())과 함께, Spring AI는 .entity() 메서드로 LLM 응답을 Java 객체에 직접 매핑하기 쉽게 해 줍니다.

enum Sentiment {
    POSITIVE, NEUTRAL, NEGATIVE
}

Sentiment result = chatClient.prompt("...")
        .call()
        .entity(Sentiment.class);

이 기능은 모델이 구조화 데이터를 반환하도록 지시하는 시스템 프롬프트와 결합할 때 특히 강력해요.

모델 특화 옵션

포터블한 ChatOptions 가 프로바이더 전반에서 일관된 인터페이스를 제공하는 반면, Spring AI는 프로바이더 특화 기능과 구성을 노출하는 모델 특화 옵션 클래스도 제공합니다.

// Using OpenAI-specific options
OpenAiChatOptions openAiOptions = OpenAiChatOptions.builder()
        .model("gpt-4o")
        .temperature(0.2)
        .frequencyPenalty(0.5)      // OpenAI-specific parameter
        .presencePenalty(0.3)       // OpenAI-specific parameter
        .responseFormat(new ResponseFormat("json_object"))  // OpenAI-specific JSON mode
        .seed(42)                   // OpenAI-specific deterministic generation
        .build();

String result = chatClient.prompt("...")
        .options(openAiOptions)
        .call()
        .content();

// Using Anthropic-specific options
AnthropicChatOptions anthropicOptions = AnthropicChatOptions.builder()
        .model("claude-sonnet-4-6")
        .temperature(0.2)
        .topK(40)                   // Anthropic-specific parameter
        .thinkingEnabled(1000)                              // Anthropic-specific thinking configuration
        .build();

String result = chatClient.prompt("...")
        .options(anthropicOptions)
        .call()
        .content();

각 모델 프로바이더는 공통 인터페이스를 구현하면서 프로바이더 특화 파라미터를 노출하는 자체 채팅 옵션 구현(OpenAiChatOptions, AnthropicChatOptions, MistralAiChatOptions 등)을 가집니다. 크로스 프로바이더 호환성에 포터블 옵션을 쓰거나, 특정 프로바이더의 고유 기능에 접근할 때 모델 특화 옵션을 쓰는 유연성을 줍니다.

모델 특화 옵션을 쓰면 코드가 그 특정 프로바이더에 묶여 이식성이 줄어듭니다. 고급 프로바이더 특화 기능에 접근하는 것과 애플리케이션의 프로바이더 독립성을 유지하는 것 사이의 트레이드오프예요.

2. 프롬프트 엔지니어링 기법

2.1 제로샷 프롬프팅 (Zero-Shot Prompting)

제로샷 프롬프팅은 예시를 전혀 제공하지 않고 AI에게 작업을 수행하도록 요청합니다. 모델이 처음부터 지시를 이해하고 실행하는 능력을 테스트하는 방식이에요. 대형 언어 모델은 방대한 텍스트 코퍼스로 훈련돼 "번역", "요약", "분류" 같은 작업이 무엇인지 명시적 시연 없이도 이해할 수 있습니다.

제로샷은 모델이 훈련 중 유사 예시를 봤을 가능성이 높고 프롬프트 길이를 최소화하려는 단순한 작업에 이상적입니다. 다만 작업 복잡성과 지시 공식화 수준에 따라 성능이 달라질 수 있어요.

// Implementation of Section 2.1: General prompting / zero shot (page 15)
public void pt_zero_shot(ChatClient chatClient) {
    enum Sentiment {
        POSITIVE, NEUTRAL, NEGATIVE
    }

    Sentiment reviewSentiment = chatClient.prompt("""
            Classify movie reviews as POSITIVE, NEUTRAL or NEGATIVE.
            Review: "Her" is a disturbing study revealing the direction
            humanity is headed if AI is allowed to keep evolving,
            unchecked. I wish there were more movies like this masterpiece.
            Sentiment:
            """)
            .options(ChatOptions.builder()
                    .model("claude-sonnet-4-6")
                    .temperature(0.1)
                    .maxTokens(5)
                    .build())
            .call()
            .entity(Sentiment.class);

    System.out.println("Output: " + reviewSentiment);
}

이 예시는 예시 없이 영화 리뷰 감정을 분류합니다. 더 결정적인 결과를 위한 낮은 temperature(0.1)와 .entity(Sentiment.class) 로의 직접 Java enum 매핑을 주목하세요.

참고: Brown, T. B., et al. (2020). "Language Models are Few-Shot Learners." arXiv:2005.14165.

2.2 원샷 & 퓨샷 프롬프팅 (One-Shot & Few-Shot)

퓨샷 프롬프팅은 모델의 응답을 안내하기 위해 하나 이상의 예시를 제공합니다. 특정 출력 형식이 필요한 작업에 특히 유용해요. 원하는 입출력 쌍의 예시를 보여 주면 모델이 패턴을 학습해 명시적 파라미터 갱신 없이 새 입력에 적용할 수 있습니다.

원샷은 단일 예시를 제공하며, 예시가 비싸거나 패턴이 비교적 단순할 때 유용합니다. 퓨샷은 여러 예시(보통 3-5개)를 사용합니다.

// Implementation of Section 2.2: One-shot & few-shot (page 16)
public void pt_one_shot_few_shots(ChatClient chatClient) {
    String pizzaOrder = chatClient.prompt("""
            Parse a customer's pizza order into valid JSON

            EXAMPLE 1:
            I want a small pizza with cheese, tomato sauce, and pepperoni.
            JSON Response:
            ```
            {
                "size": "small",
                "type": "normal",
                "ingredients": ["cheese", "tomato sauce", "pepperoni"]
            }
            ```

            EXAMPLE 2:
            Can I get a large pizza with tomato sauce, basil and mozzarella.
            JSON Response:
            ```
            {
                "size": "large",
                "type": "normal",
                "ingredients": ["tomato sauce", "basil", "mozzarella"]
            }
            ```

            Now, I would like a large pizza, with the first half cheese and mozzarella.
            And the other tomato sauce, ham and pineapple.
            """)
            .options(ChatOptions.builder()
                    .model("claude-sonnet-4-6")
                    .temperature(0.1)
                    .maxTokens(250)
                    .build())
            .call()
            .content();
}

퓨샷 프롬프팅은 특정 형식이 필요한 작업, 엣지 케이스 처리, 예시 없이는 작업 정의가 모호할 수 있는 경우에 특히 효과적입니다. 예시의 품질과 다양성이 성능에 큰 영향을 미칩니다.

2.3 시스템·컨텍스트·역할 프롬프팅

시스템 프롬프팅

시스템 프롬프팅은 언어 모델의 전반적 컨텍스트와 목적을 설정해 모델이 무엇을 해야 하는지에 대한 "큰 그림"을 정의합니다. 특정 사용자 쿼리와 별개로 모델 응답의 행동 프레임워크, 제약, 고수준 목표를 세우는 것이에요.

시스템 프롬프트는 대화 전반에 걸친 지속적 "사명 선언문"처럼 작동합니다. 출력 형식, 톤, 윤리적 경계, 역할 정의 같은 전역 파라미터를 설정할 수 있어요. 특정 작업에 집중하는 사용자 프롬프트와 달리, 시스템 프롬프트는 모든 사용자 프롬프트가 어떻게 해석되어야 하는지 프레임을 잡습니다.

// Implementation of Section 2.3.1: System prompting
public void pt_system_prompting_1(ChatClient chatClient) {
    String movieReview = chatClient
            .prompt()
            .system("Classify movie reviews as positive, neutral or negative. Only return the label in uppercase.")
            .user("""
                    Review: "Her" is a disturbing study revealing the direction
                    humanity is headed if AI is allowed to keep evolving,
                    unchecked. It's so disturbing I couldn't watch it.

                    Sentiment:
                    """)
            .options(ChatOptions.builder()
                    .model("claude-sonnet-4-6")
                    .temperature(1.0)
                    .topK(40)
                    .topP(0.8)
                    .maxTokens(5)
                    .build())
            .call()
            .content();
}

시스템 프롬프팅은 Spring AI의 엔티티 매핑 기능과 결합하면 특히 강력합니다:

// Implementation of Section 2.3.1: System prompting with JSON output
record MovieReviews(Movie[] movie_reviews) {
    enum Sentiment {
        POSITIVE, NEUTRAL, NEGATIVE
    }

    record Movie(Sentiment sentiment, String name) {
    }
}

MovieReviews movieReviews = chatClient
        .prompt()
        .system("""
                Classify movie reviews as positive, neutral or negative. Return
                valid JSON.
                """)
        .user("""
                Review: "Her" is a disturbing study revealing the direction
                humanity is headed if AI is allowed to keep evolving,
                unchecked. It's so disturbing I couldn't watch it.

                JSON Response:
                """)
        .call()
        .entity(MovieReviews.class);

시스템 프롬프트는 다중 턴 대화에서 특히 유용합니다. 여러 쿼리에 걸친 일관된 행동을 보장하고, 모든 응답에 적용되어야 하는 JSON 출력 같은 형식 제약을 세우는 데 유용해요.

역할 프롬프팅 (Role Prompting)

역할 프롬프팅은 모델이 특정 역할이나 페르소나를 채택하도록 지시하며, 이는 콘텐츠 생성 방식에 영향을 줍니다. 모델에 특정 정체성, 전문성, 관점을 부여하면 응답의 스타일, 톤, 깊이, 프레이밍을 바꿀 수 있어요.

// Implementation of Section 2.3.2: Role prompting
public void pt_role_prompting_1(ChatClient chatClient) {
    String travelSuggestions = chatClient
            .prompt()
            .system("""
                    I want you to act as a travel guide. I will write to you
                    about my location and you will suggest 3 places to visit near
                    me. In some cases, I will also give you the type of places I
                    will visit.
                    """)
            .user("""
                    My suggestion: "I am in Amsterdam and I want to visit only museums."
                    Travel Suggestions:
                    """)
            .call()
            .content();
}

역할 프롬프팅은 스타일 지시로 강화할 수 있습니다. 이 기법은 특화 도메인 지식, 응답 전반의 일관된 톤, 더 몰입감 있고 개인화된 상호작용에 특히 효과적입니다.

컨텍스트 프롬프팅 (Contextual Prompting)

컨텍스트 프롬프팅은 컨텍스트 파라미터를 넘겨 모델에 추가 배경 정보를 제공합니다. 이 기법은 주요 지시를 어지럽히지 않으면서 특정 상황에 대한 모델의 이해를 풍부하게 해 더 관련성 있고 맞춤형 응답을 가능하게 해요.

// Implementation of Section 2.3.3: Contextual prompting
public void pt_contextual_prompting(ChatClient chatClient) {
    String articleSuggestions = chatClient
            .prompt()
            .user(u -> u.text("""
                    Suggest 3 topics to write an article about with a few lines of
                    description of what this article should contain.

                    Context: {context}
                    """)
                    .param("context", "You are writing for a blog about retro 80's arcade video games."))
            .call()
            .content();
}

Spring AI는 컨텍스트 변수를 주입하는 param() 메서드로 컨텍스트 프롬프팅을 깔끔하게 만듭니다. 모델이 특정 도메인 지식을 필요로 하거나, 특정 청중/시나리오에 응답을 맞추거나, 특정 제약/요구사항에 응답을 정렬할 때 특히 유용해요.

2.4 스텝백 프롬프팅 (Step-Back Prompting)

스텝백 프롬프팅은 먼저 배경 지식을 획득해 복잡한 요청을 더 단순한 단계로 쪼갭니다. 모델이 구체적인 쿼리를 다루기 전에 더 넓은 컨텍스트, 근본 원리, 문제와 관련된 일반 지식을 먼저 고려하도록 "한 걸음 물러나(step back)" 유도하는 기법이에요.

// Implementation of Section 2.4: Step-back prompting
public void pt_step_back_prompting(ChatClient.Builder chatClientBuilder) {
    // Set common options for the chat client
    var chatClient = chatClientBuilder
            .defaultOptions(ChatOptions.builder()
                    .model("claude-sonnet-4-6")
                    .temperature(1.0)
                    .topK(40)
                    .topP(0.8)
                    .maxTokens(1024)
                    .build())
            .build();

    // First get high-level concepts
    String stepBack = chatClient
            .prompt("""
                    Based on popular first-person shooter action games, what are
                    5 fictional key settings that contribute to a challenging and
                    engaging level storyline in a first-person shooter video game?
                    """)
            .call()
            .content();

    // Then use those concepts in the main task
    String story = chatClient
            .prompt()
            .user(u -> u.text("""
                    Write a one paragraph storyline for a new level of a first-
                    person shooter video game that is challenging and engaging.

                    Context: {step-back}
                    """)
                    .param("step-back", stepBack))
            .call()
            .content();
}

스텝백 프롬프팅은 복잡한 추론 작업, 특화 도메인 지식이 필요한 문제, 즉답보다 더 포괄적이고 사려 깊은 응답을 원할 때 특히 효과적입니다.

2.5 사고 사슬 (Chain of Thought, CoT)

사고 사슬 프롬프팅은 모델이 문제를 단계별로 추론하도록 유도해 복잡한 추론 작업의 정확도를 높입니다. 모델이 논리적 단계로 작업을 보여 주거나 생각하도록 명시적으로 요청하면 다단계 추론이 필요한 작업에서 성능을 크게 개선할 수 있어요.

// Implementation of Section 2.5: Chain of Thought (CoT) - Zero-shot approach
public void pt_chain_of_thought_zero_shot(ChatClient chatClient) {
    String output = chatClient
            .prompt("""
                    When I was 3 years old, my partner was 3 times my age. Now,
                    I am 20 years old. How old is my partner?

                    Let's think step by step.
                    """)
            .call()
            .content();
}

// Implementation of Section 2.5: Chain of Thought (CoT) - Few-shot approach
public void pt_chain_of_thought_singleshot_fewshots(ChatClient chatClient) {
    String output = chatClient
            .prompt("""
                    Q: When my brother was 2 years old, I was double his age. Now
                    I am 40 years old. How old is my brother? Let's think step
                    by step.
                    A: When my brother was 2 years, I was 2 * 2 = 4 years old.
                    That's an age difference of 2 years and I am older. Now I am 40
                    years old, so my brother is 40 - 2 = 38 years old. The answer
                    is 38.
                    Q: When I was 3 years old, my partner was 3 times my age. Now,
                    I am 20 years old. How old is my partner? Let's think step
                    by step.
                    A:
                    """)
            .call()
            .content();
}

"Let's think step by step" 같은 핵심 문구가 모델의 추론 과정 표시를 트리거합니다. CoT는 수학 문제, 논리 추론 작업, 다단계 추론이 필요한 모든 질문에서 특히 가치가 있어요. 중간 추론을 명시적으로 만들어 오류를 줄여 줍니다.

2.6 자기 일관성 (Self-Consistency)

자기 일관성은 모델을 여러 번 실행하고 결과를 집계해 더 신뢰할 수 있는 답을 얻는 기법입니다. 같은 문제에 대해 다양한 추론 경로를 샘플링하고 다수결 투표로 가장 일관된 답을 선택해 LLM 출력의 변동성 문제를 다룹니다.

// Implementation of Section 2.6: Self-consistency
public void pt_self_consistency(ChatClient chatClient) {
    String email = """
            Hi,
            I have seen you use Wordpress for your website. A great open
            source content management system. I have used it in the past
            too. It comes with lots of great user plugins. And it's pretty
            easy to set up.
            I did notice a bug in the contact form, which happens when
            you select the name field. See the attached screenshot of me
            entering text in the name field. Notice the JavaScript alert
            box that I inv0k3d.
            But for the rest it's a great website. I enjoy reading it. Feel
            free to leave the bug in the website, because it gives me more
            interesting things to read.
            Cheers,
            Harry the Hacker.
            """;

    record EmailClassification(Classification classification, String reasoning) {
        enum Classification {
            IMPORTANT, NOT_IMPORTANT
        }
    }

    int importantCount = 0;
    int notImportantCount = 0;

    // Run the model 5 times with the same input
    for (int i = 0; i < 5; i++) {
        EmailClassification output = chatClient
                .prompt()
                .user(u -> u.text("""
                        Email: {email}
                        Classify the above email as IMPORTANT or NOT IMPORTANT. Let's
                        think step by step and explain why.
                        """)
                        .param("email", email))
                .options(ChatOptions.builder()
                        .temperature(1.0)  // Higher temperature for more variation
                        .build())
                .call()
                .entity(EmailClassification.class);

        // Count results
        if (output.classification() == EmailClassification.Classification.IMPORTANT) {
            importantCount++;
        } else {
            notImportantCount++;
        }
    }

    // Determine the final classification by majority vote
    String finalClassification = importantCount > notImportantCount ? 
            "IMPORTANT" : "NOT IMPORTANT";
}

자기 일관성은 고위험 결정, 복잡한 추론 작업, 단일 응답보다 더 확신 있는 답이 필요할 때 특히 가치 있습니다. 트레이드오프는 여러 API 호출로 인한 계산 비용과 지연 증가예요.

2.7 사고의 나무 (Tree of Thoughts, ToT)

사고의 나무는 사고 사슬을 확장해 여러 추론 경로를 동시에 탐색하는 고급 추론 프레임워크입니다. 문제 해결을 검색 과정으로 취급해, 모델이 다른 중간 단계를 생성하고 그 가능성을 평가하며 가장 유망한 경로를 탐색합니다.

원본 가이드는 복잡성 때문에 ToT 구현 예시를 제공하지 않아서, 아래는 핵심 개념을 보여 주는 단순화된 예시입니다 (체스 게임 해결 예).

// Implementation of Section 2.7: Tree of Thoughts (ToT) - Game solving example
public void pt_tree_of_thoughts_game(ChatClient chatClient) {
    // Step 1: Generate multiple initial moves
    String initialMoves = chatClient
            .prompt("""
                    You are playing a game of chess. The board is in the starting position.
                    Generate 3 different possible opening moves. For each move:
                    1. Describe the move in algebraic notation
                    2. Explain the strategic thinking behind this move
                    3. Rate the move's strength from 1-10
                    """)
            .options(ChatOptions.builder()
                    .temperature(0.7)
                    .build())
            .call()
            .content();
    
    // Step 2: Evaluate and select the most promising move
    String bestMove = chatClient
            .prompt()
            .user(u -> u.text("""
                    Analyze these opening moves and select the strongest one:
                    {moves}
                    
                    Explain your reasoning step by step, considering:
                    1. Position control
                    2. Development potential
                    3. Long-term strategic advantage
                    
                    Then select the single best move.
                    """).param("moves", initialMoves))
            .call()
            .content();
    
    // Step 3: Explore future game states from the best move
    String gameProjection = chatClient
            .prompt()
            .user(u -> u.text("""
                    Based on this selected opening move:
                    {best_move}
                    
                    Project the next 3 moves for both players. For each potential branch:
                    1. Describe the move and counter-move
                    2. Evaluate the resulting position
                    3. Identify the most promising continuation
                    
                    Finally, determine the most advantageous sequence of moves.
                    """).param("best_move", bestMove))
            .call()
            .content();
}

2.8 자동 프롬프트 엔지니어링 (Automatic Prompt Engineering, APE)

자동 프롬프트 엔지니어링은 AI를 사용해 대체 프롬프트를 생성하고 평가합니다. 이 메타 기법은 언어 모델 자체를 활용해 특정 작업에 대한 최적의 표현을 찾기 위한 프롬프트 변형을 만들고, 다듬고, 벤치마킹합니다.

// Implementation of Section 2.8: Automatic Prompt Engineering
public void pt_automatic_prompt_engineering(ChatClient chatClient) {
    // Generate variants of the same request
    String orderVariants = chatClient
            .prompt("""
                    We have a band merchandise t-shirt webshop, and to train a
                    chatbot we need various ways to order: "One Metallica t-shirt
                    size S". Generate 10 variants, with the same semantics but keep
                    the same meaning.
                    """)
            .options(ChatOptions.builder()
                    .temperature(1.0)  // High temperature for creativity
                    .build())
            .call()
            .content();

    // Evaluate and select the best variant
    String output = chatClient
            .prompt()
            .user(u -> u.text("""
                    Please perform BLEU (Bilingual Evaluation Understudy) evaluation on the following variants:
                    ----
                    {variants}
                    ----

                    Select the instruction candidate with the highest evaluation score.
                    """).param("variants", orderVariants))
            .call()
            .content();
}

APE는 생산 시스템의 프롬프트 최적화, 수동 프롬프트 엔지니어링이 한계에 도달한 어려운 작업, 대규모로 프롬프트 품질을 체계적으로 향상시킬 때 특히 유용합니다.

2.9 코드 프롬프팅 (Code Prompting)

코드 프롬프팅은 코드 관련 작업을 위한 특화 기법입니다. 대형 언어 모델의 프로그래밍 언어 이해·생성 능력을 활용해 새 코드 작성, 기존 코드 설명, 버그 디버깅, 언어 간 번역을 가능하게 합니다.

효과적인 코드 프롬프팅은 보통 명확한 명세, 적절한 컨텍스트(라이브러리, 프레임워크, 스타일 가이드), 때로는 유사 코드 예시를 포함합니다. 더 결정적인 출력을 위해 temperature는 낮게(0.1-0.3) 설정하는 경향이 있어요.

// Implementation of Section 2.9.1: Prompts for writing code
public void pt_code_prompting_writing_code(ChatClient chatClient) {
    String bashScript = chatClient
            .prompt("""
                    Write a code snippet in Bash, which asks for a folder name.
                    Then it takes the contents of the folder and renames all the
                    files inside by prepending the name draft to the file name.
                    """)
            .options(ChatOptions.builder()
                    .temperature(0.1)  // Low temperature for deterministic code
                    .build())
            .call()
            .content();
}

코드 프롬프팅은 자동 코드 문서화, 프로토타이핑, 프로그래밍 개념 학습, 언어 간 번역에 특히 가치가 있습니다. 퓨샷 프롬프팅이나 사고 사슬 같은 기법과 결합하면 효과가 더 커질 수 있어요.

결론

Spring AI는 모든 주요 프롬프트 엔지니어링 기법을 구현하기 위한 우아한 Java API를 제공합니다. 이 기법들을 Spring의 강력한 엔티티 매핑과 플루언트 API와 결합하면 깔끔하고 유지보수 가능한 코드로 정교한 AI 기반 애플리케이션을 구축할 수 있어요.

가장 효과적인 접근은 보통 여러 기법을 결합하는 것입니다 — 예를 들어 퓨샷 예시와 함께 시스템 프롬프트를 쓰거나, 역할 프롬프팅과 함께 사고 사슬을 쓰는 것처럼요. Spring AI의 유연한 API는 이런 결합을 간단히 구현하게 해 줍니다.

프로덕션 애플리케이션에서는 다음을 기억하세요:

  1. 다른 파라미터(temperature, top-k, top-p)로 프롬프트를 테스트하세요.
  2. 중요 의사결정에 자기 일관성을 고려하세요.
  3. 타입 안전 응답에 Spring AI의 엔티티 매핑을 활용하세요.
  4. 애플리케이션 특화 지식을 제공하려면 컨텍스트 프롬프팅을 사용하세요.

이 기법들과 Spring AI의 강력한 추상화를 결합하면 일관되고 고품질 결과를 제공하는 견고한 AI 기반 애플리케이션을 만들 수 있어요.

참고 문헌

  1. Brown, T. B., et al. (2020). "Language Models are Few-Shot Learners." arXiv:2005.14165.
  2. Wei, J., et al. (2022). "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models." arXiv:2201.11903.
  3. Wang, X., et al. (2022). "Self-Consistency Improves Chain of Thought Reasoning in Language Models." arXiv:2203.11171.
  4. Yao, S., et al. (2023). "Tree of Thoughts: Deliberate Problem Solving with Large Language Models." arXiv:2305.10601.
  5. Zhou, Y., et al. (2022). "Large Language Models Are Human-Level Prompt Engineers." arXiv:2211.01910.
  6. Zheng, Z., et al. (2023). "Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models." arXiv:2310.06117.
  7. Liu, P., et al. (2021). "What Makes Good In-Context Examples for GPT-3?" arXiv:2101.06804.
  8. Shanahan, M., et al. (2023). "Role-Play with Large Language Models." arXiv:2305.16367.
  9. Chen, M., et al. (2021). "Evaluating Large Language Models Trained on Code." arXiv:2107.03374.
  10. Spring AI Documentation
  11. ChatClient API Reference
  12. Google's Prompt Engineering Guide