Home [AI 실전] 프롬프트 엔지니어링: AI에게 함수 호출(Function Call)하듯 명령하기
Post
Cancel

[AI 실전] 프롬프트 엔지니어링: AI에게 함수 호출(Function Call)하듯 명령하기

프롬프트는 문과적 글쓰기가 아니에요, 함수 설계입니다

“ChatGPT야, 코드 짜줘” 했는데 엉뚱한 결과가 나온 적 있으시죠?

많은 개발자들이 프롬프트 엔지니어링을 “AI에게 친절하게 말 걸기” 정도로 생각해요. 하지만 실제로는 함수를 설계하는 것과 똑같은 논리적 과정입니다!

1
2
3
4
5
6
7
8
9
10
// 이건 함수 설계가 아니라 마법 주문이에요
magicFunction(); // 뭘 리턴할까요? 🤷

// 이게 제대로 된 함수 설계죠
Response generate(
    Context context,      // 배경 정보
    Task task,            // 수행할 작업
    Constraints rules,    // 제약 조건
    OutputFormat format   // 출력 형식
);

오늘은 프롬프트 엔지니어링을 함수 호출 관점에서 완전히 정복해보겠습니다!


1. 프롬프트를 함수로 바라보기 🎯

Bad Prompt = 잘못 설계된 함수

1
2
3
4
5
6
7
8
9
10
11
// ❌ Bad: 파라미터 없이 호출
public String generateCode() {
    // 뭘 만들어야 할까요?
    // 어떤 언어로?
    // 어떤 스타일로?
    return "???";
}

// 사용
String code = generateCode(); 
// → 예측 불가능한 결과!
1
2
3
4
5
❌ Bad Prompt:
"코드 짜줘"

AI 응답:
"어떤 코드를 원하시나요? 언어와 구체적인 요구사항을 알려주세요."

Good Prompt = 잘 설계된 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ✅ Good: 명확한 파라미터와 반환 타입
public String generateCode(
    String language,           // 언어 지정
    String task,               // 구체적 작업
    CodeStyle style,           // 코딩 스타일
    List<String> constraints,  // 제약 조건
    OutputFormat format        // 출력 형식
) {
    // 명확한 입력 → 예측 가능한 출력
    return processWithParameters(...);
}

// 사용
String code = generateCode(
    "Java",
    "Spring Security JWT 인증 구현",
    CodeStyle.CLEAN_CODE,
    Arrays.asList("주석 포함", "예외 처리"),
    OutputFormat.MARKDOWN
);
// → 정확한 결과!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
✅ Good Prompt:
"""
[Context] Spring Boot 3.2 프로젝트에서
[Task] JWT 기반 인증 필터를 구현하고 싶어요.
[Constraints]
- Clean Code 원칙 준수
- 주석으로 각 단계 설명
- SecurityConfig와 JwtFilter 분리
[Output Format]
- 코드는 Java 블록으로
- 각 클래스별로 구분해서 제공
"""

AI 응답:
정확히 요구사항에 맞는 코드를 생성!

2. Temperature - AI의 Random Seed 조절 🌡️

Temperature란?

AI의 창의성(무작위성)을 조절하는 파라미터 (0.0 ~ 2.0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Java의 Random과 유사한 개념
public class LLM {
    
    private double temperature; // 0.0 ~ 2.0
    
    public Token selectNextToken(Map<Token, Double> probabilities) {
        if (temperature == 0.0) {
            // 항상 가장 확률 높은 것만 선택 (Deterministic)
            return probabilities.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .get().getKey();
        } else {
            // 확률 분포에서 무작위 샘플링 (Probabilistic)
            return weightedRandomSample(probabilities, temperature);
        }
    }
}

Temperature 값에 따른 동작

1
2
3
4
5
6
7
8
9
10
11
12
13
// Temperature = Random 범위 조절
public class RandomComparison {
    
    // Temperature 0.0 = 고정된 시드
    Random deterministicRandom = new Random(42); // 항상 같은 결과
    
    // Temperature 0.7 = 적당한 무작위성
    Random moderateRandom = new Random(); // 적당히 다양한 결과
    
    // Temperature 1.5 = 극도의 무작위성
    Random wildRandom = new Random();
    // nextInt() 범위를 확장한 것과 유사
}

실제 예시 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
질문: "Spring Boot의 장점을 3가지 말해줘"

┌─────────────────────────────────────────────────┐
│ Temperature = 0.0 (Deterministic)               │
└─────────────────────────────────────────────────┘
첫 번째 실행:
1. 자동 설정 (Auto Configuration)
2. 독립 실행형 애플리케이션
3. 강력한 생태계

두 번째 실행:
1. 자동 설정 (Auto Configuration)  ← 똑같음!
2. 독립 실행형 애플리케이션
3. 강력한 생태계

→ 항상 동일한 답변 (정답이 정해진 질문에 적합)

┌─────────────────────────────────────────────────┐
│ Temperature = 0.7 (Balanced)                    │
└─────────────────────────────────────────────────┘
첫 번째 실행:
1. 빠른 개발 속도
2. 마이크로서비스 친화적
3. 방대한 커뮤니티

두 번째 실행:
1. 설정의 단순화
2. 내장 서버 지원
3. 프로덕션 준비 기능

→ 합리적으로 다양한 답변

┌─────────────────────────────────────────────────┐
│ Temperature = 1.5 (Creative)                    │
└─────────────────────────────────────────────────┘
첫 번째 실행:
1. 개발자 행복도 향상 😊
2. 마법 같은 자동 설정
3. 세상에서 가장 쿨한 프레임워크

두 번째 실행:
1. Spring Boot는 커피처럼 개발자를 깨워줘요
2. XML 지옥에서의 탈출구
3. ...

→ 창의적이지만 부정확할 수 있음

Temperature 선택 가이드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Service
public class AIService {
    
    /**
     * 작업 유형에 따른 Temperature 선택
     */
    public String generateResponse(String task, String prompt) {
        double temperature = selectTemperature(task);
        return llm.generate(prompt, temperature);
    }
    
    private double selectTemperature(String taskType) {
        return switch (taskType) {
            // 정답이 정해진 작업
            case "CODE_GENERATION" -> 0.0;      // 코드 생성
            case "TRANSLATION" -> 0.0;          // 번역
            case "MATH" -> 0.0;                 // 수학 문제
            case "DATA_EXTRACTION" -> 0.1;      // 데이터 추출
            
            // 균형 잡힌 작업
            case "EXPLANATION" -> 0.3;          // 개념 설명
            case "CODE_REVIEW" -> 0.3;          // 코드 리뷰
            case "REFACTORING" -> 0.5;          // 리팩토링 제안
            case "DOCUMENTATION" -> 0.5;        // 문서 작성
            
            // 창의성이 필요한 작업
            case "BRAINSTORMING" -> 0.8;        // 아이디어 회의
            case "NAMING" -> 0.7;               // 네이밍
            case "BLOG_WRITING" -> 0.7;         // 블로그 글쓰기
            case "CREATIVE_WRITING" -> 1.0;     // 창작
            
            default -> 0.7; // 기본값
        };
    }
}
Temperature사용 사례비유
0.0 - 0.2코드 생성, 번역, 팩트 체크Collections.sort() - 정해진 순서
0.3 - 0.5설명, 요약, 코드 리뷰HashMap - 예측 가능하지만 유연함
0.6 - 0.8아이디어, 네이밍, 블로그new Random() - 적당한 무작위성
0.9 - 2.0창작, 소설, 실험적 시도ThreadLocalRandom - 극도의 변동성

실전 코드 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@RestController
@RequestMapping("/api/ai")
public class AIController {
    
    @Autowired
    private OpenAIClient openAI;
    
    /**
     * 코드 생성 - Temperature 0.0
     */
    @PostMapping("/generate-code")
    public String generateCode(@RequestBody CodeRequest request) {
        String prompt = String.format("""
            다음 요구사항에 맞는 Java 코드를 생성해주세요:
            %s
            
            코드만 출력하고, 설명은 주석으로 작성하세요.
            """, request.getRequirements());
        
        return openAI.chat(prompt, temperature = 0.0); // 정확성 최우선
    }
    
    /**
     * 변수명 제안 - Temperature 0.7
     */
    @PostMapping("/suggest-naming")
    public List<String> suggestNaming(@RequestBody NamingRequest request) {
        String prompt = String.format("""
            다음 용도의 변수명을 5가지 제안해주세요:
            용도: %s
            타입: %s
            
            Java 네이밍 컨벤션을 따르되, 창의적으로 제안해주세요.
            """, request.getPurpose(), request.getType());
        
        String response = openAI.chat(prompt, temperature = 0.7); // 창의성
        return parseNamingSuggestions(response);
    }
    
    /**
     * 코드 리뷰 - Temperature 0.3
     */
    @PostMapping("/code-review")
    public CodeReviewResult reviewCode(@RequestBody String code) {
        String prompt = String.format("""
            다음 코드를 리뷰해주세요:
            
            ```java
            %s
            ```
            
            다음 관점에서 분석하세요:
            1. 버그 가능성
            2. 성능 이슈
            3. 가독성 개선점
            4. Best Practice 위반
            """, code);
        
        return openAI.chat(prompt, temperature = 0.3); // 균형
    }
}

3. Persona (역할 부여) - 클래스 상속과 DI 🎭

Persona란?

AI에게 특정 역할과 전문성을 부여하는 기술

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Persona = 클래스 상속
public interface Developer {
    String explainConcept(String topic);
}

// 일반 개발자
public class JuniorDeveloper implements Developer {
    public String explainConcept(String topic) {
        return "간단하게 설명해드릴게요...";
    }
}

// 시니어 개발자 (전문성 추가)
public class SeniorJavaDeveloper extends JuniorDeveloper {
    
    private int experience = 10; // 10년 경력
    private List<String> expertise = Arrays.asList(
        "Spring", "JPA", "Microservices", "Performance Tuning"
    );
    
    @Override
    public String explainConcept(String topic) {
        // 더 깊이 있고 실무 경험 기반의 설명
        return explainWithRealWorldExamples(topic);
    }
}

Persona 적용 전후 비교

1
2
3
4
5
6
7
8
9
10
❌ Persona 없음:

Prompt: "JPA N+1 문제 설명해줘"

AI 응답:
"JPA N+1 문제는 연관관계 조회 시 발생하는 성능 문제입니다.
1개의 쿼리로 N개의 엔티티를 조회할 때, 각 엔티티마다 추가로
1개씩의 쿼리가 발생하여 총 N+1개의 쿼리가 실행됩니다..."

→ 교과서적인 설명
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
✅ Persona 적용:

Prompt:
"""
당신은 10년차 Spring/JPA 전문가입니다.
실무에서 수많은 성능 이슈를 해결한 경험이 있습니다.
초보 개발자가 이해하기 쉽게 실제 코드 예시와 함께 설명해주세요.

질문: JPA N+1 문제 설명해줘
"""

AI 응답:
"실무에서 가장 많이 마주치는 성능 이슈죠!
제가 실제로 겪은 사례를 들어볼게요.

[상황]
회원 목록을 조회하는데 각 회원의 주문 내역도 함께 보여줘야 했어요.

[문제 코드]
@GetMapping("/users")
public List<UserDto> getUsers() {
    List<User> users = userRepository.findAll(); // 1번 쿼리
    
    return users.stream()
        .map(user -> {
            List<Order> orders = user.getOrders(); // N번 쿼리!
            return new UserDto(user, orders);
        })
        .collect(Collectors.toList());
}

100명의 회원이 있으면 총 101번의 쿼리가 실행됩니다!

[해결 방법 3가지]
1. Fetch Join
2. @EntityGraph
3. Batch Size 설정

제가 실무에서는 보통 @EntityGraph를 선호하는데..."

→ 실무 경험 기반의 깊이 있는 설명!

Persona 설계 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Persona = Builder 패턴으로 설계
public class PersonaBuilder {
    
    private String role;
    private int experience;
    private List<String> expertise;
    private String communicationStyle;
    private String targetAudience;
    
    public PersonaBuilder role(String role) {
        this.role = role;
        return this;
    }
    
    public PersonaBuilder experience(int years) {
        this.experience = years;
        return this;
    }
    
    public PersonaBuilder expertise(List<String> skills) {
        this.expertise = skills;
        return this;
    }
    
    public PersonaBuilder style(String style) {
        this.communicationStyle = style;
        return this;
    }
    
    public PersonaBuilder audience(String audience) {
        this.targetAudience = audience;
        return this;
    }
    
    public String build() {
        return String.format("""
            당신은 %d년차 %s입니다.
            전문 분야: %s
            의사소통 스타일: %s
            대상 청중: %s
            """,
            experience,
            role,
            String.join(", ", expertise),
            communicationStyle,
            targetAudience
        );
    }
}

// 사용 예시
String persona = new PersonaBuilder()
    .role("시니어 백엔드 개발자")
    .experience(10)
    .expertise(Arrays.asList("Spring Boot", "JPA", "Kafka", "Redis"))
    .style("실무 경험 기반, 코드 예시 위주")
    .audience("주니어 개발자")
    .build();

실전 Persona 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Component
public class PersonaTemplates {
    
    /**
     * 시니어 Java 개발자 페르소나
     */
    public static final String SENIOR_JAVA_DEVELOPER = """
        당신은 10년 이상 경력의 시니어 Java 백엔드 개발자입니다.
        
        [전문 분야]
        - Spring Framework (Spring Boot, Spring Security, Spring Data JPA)
        - 대용량 트래픽 처리 (분산 시스템, 캐싱, 메시지 큐)
        - 성능 최적화 (JVM 튜닝, 쿼리 최적화)
        - Clean Code & Refactoring
        
        [답변 스타일]
        - 실무 경험 기반의 구체적인 예시 제공
        - "왜"를 중심으로 원리 설명
        - 실제 코드 예시 포함
        - Trade-off와 Best Practice 언급
        - 초보자도 이해하기 쉽게 단계별 설명
        
        [금지 사항]
        - 추상적이거나 이론적인 설명만 하지 마세요
        - "그냥 이렇게 하세요" 같은 근거 없는 조언 금지
        - 복붙용 코드만 던져주지 마세요
        """;
    
    /**
     * 코드 리뷰어 페르소나
     */
    public static final String CODE_REVIEWER = """
        당신은 까다롭지만 친절한 시니어 코드 리뷰어입니다.
        
        [리뷰 관점]
        1. 버그 가능성 (Null 체크, Exception 처리)
        2. 성능 이슈 (N+1 문제, 불필요한 반복)
        3. 가독성 (변수명, 메서드 분리, 주석)
        4. 보안 (SQL Injection, XSS, 민감정보 노출)
        5. Best Practice 준수 (SOLID, DRY, KISS)
        
        [답변 형식]
        👍 잘한 점: ...
        🚨 심각한 문제: ...
        💡 개선 제안: ...
        
        [답변 스타일]
        - 비판보다는 건설적 제안
        - 왜 문제인지 이유 설명
        - 개선된 코드 예시 제공
        """;
    
    /**
     * 기술 면접관 페르소나
     */
    public static final String TECH_INTERVIEWER = """
        당신은 대기업 백엔드 개발자 채용 면접관입니다.
        
        [면접 스타일]
        - 지원자의 이해도를 확인하기 위해 꼬리 질문
        - "그럼 이 경우는 어떻게 되나요?" 같은 심화 질문
        - 실무 경험 유무를 파악하는 질문
        - 단순 암기가 아닌 원리 이해를 확인
        
        [질문 난이도]
        - 기본 개념 → 심화 → 실무 시나리오 순으로 진행
        - 답변에 따라 난이도 조절
        """;
}

4. 프롬프트 4대 요소 (CO-STAR) 📋

함수 시그니처처럼 구조화하기

1
2
3
4
5
6
7
8
9
// 프롬프트 = 함수 호출
Response generate(
    Context context,      // 1. 배경 정보
    Objective objective,  // 2. 목적/임무
    Style style,          // 3. 스타일/제약
    Tone tone,            // 4. 어조
    Audience audience,    // 5. 대상
    Response format       // 6. 출력 형식
);

CO-STAR 프레임워크

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class PromptStructure {
    
    // C: Context (배경)
    private String context;
    
    // O: Objective (목표)
    private String objective;
    
    // S: Style (스타일)
    private String style;
    
    // T: Tone (어조)
    private String tone;
    
    // A: Audience (대상)
    private String audience;
    
    // R: Response Format (응답 형식)
    private String responseFormat;
    
    public String build() {
        return String.format("""
            [Context - 배경]
            %s
            
            [Objective - 목표]
            %s
            
            [Style - 스타일]
            %s
            
            [Tone - 어조]
            %s
            
            [Audience - 대상]
            %s
            
            [Response Format - 출력 형식]
            %s
            """,
            context, objective, style, tone, audience, responseFormat
        );
    }
}

Bad Prompt vs Good Prompt 실전 비교

예시 1: 코드 생성
1
2
3
4
5
6
7
8
// ❌ Bad Prompt
"Spring Security 코드 짜줘"

// AI 응답:
// - 어떤 버전?
// - 어떤 인증 방식?
// - 프로젝트 구조는?
// → 추가 질문만 돌아옴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// ✅ Good Prompt
String prompt = """
    [Context]
    - Spring Boot 3.2, Java 17 프로젝트
    - JWT 기반 인증 구현 중
    - 현재 SecurityConfig만 있고 필터는 없는 상태
    
    [Objective]
    JWT 토큰을 검증하는 필터를 구현하고 싶습니다.
    요청 헤더에서 토큰을 추출하고, 유효성을 검증한 후,
    SecurityContext에 인증 정보를 저장하는 로직이 필요합니다.
    
    [Style]
    - Clean Code 원칙 준수
    - 각 메서드에 JavaDoc 주석
    - 예외 처리 포함
    - 로깅 추가
    
    [Tone]
    실무에서 사용 가능한 프로덕션 레벨 코드
    
    [Audience]
    Spring Security 초보 개발자
    
    [Response Format]
    1. JwtAuthenticationFilter.java 전체 코드
    2. 주요 로직 단계별 설명
    3. SecurityConfig에 필터 등록하는 방법
    
    코드는 ```java 블록으로 구분해주세요.
    """;

// AI 응답:
// 정확히 요구사항에 맞는 완벽한 코드 생성!

예시 2: 개념 설명
1
2
3
4
5
6
// ❌ Bad Prompt
"JPA 트랜잭션 설명해줘"

// AI 응답:
// 일반적인 교과서적 설명
// 실무와 동떨어진 추상적 내용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ✅ Good Prompt
String prompt = """
    [Context]
    저는 3개월차 주니어 백엔드 개발자입니다.
    @Transactional을 사용은 하는데 정확한 동작 원리를 모르겠어요.
    특히 readOnly, propagation, isolation이 실무에서 왜 중요한지 궁금합니다.
    
    [Objective]
    JPA 트랜잭션의 동작 원리와 각 옵션의 실무 활용법을 이해하고 싶습니다.
    
    [Style]
    - 실제 코드 예시 위주
    - 각 옵션별 "이런 경우에 이렇게 쓴다" 식의 실용적 설명
    - 잘못 사용했을 때의 문제점도 함께 설명
    
    [Tone]
    선배 개발자가 후배에게 알려주는 친근한 말투
    
    [Audience]
    Spring 기본은 알지만 트랜잭션 개념이 부족한 주니어
    
    [Response Format]
    1. 기본 개념 (비유 사용)
    2. 주요 옵션별 설명
       - readOnly
       - propagation
       - isolation
    3. 실무 Best Practice
    4. 흔한 실수 사례
    
    각 옵션마다 Good/Bad 코드 예시를 보여주세요.
    """;

// AI 응답:
// 실무 중심의 깊이 있는 설명 + 구체적 예시!

예시 3: 코드 리뷰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ❌ Bad Prompt
"""
이 코드 리뷰해줘:

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User getUser(Long id) {
        return userRepository.findById(id).get();
    }
}
"""

// AI 응답:
// "좋은 코드입니다" 같은 피상적 리뷰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// ✅ Good Prompt
"""
[Context]
이 코드는 실제 서비스에서 사용 중인 회원 조회 로직입니다.
최근 가끔 500 에러가 발생하는데 원인을 모르겠습니다.

[Objective]
다음 관점에서 코드를 리뷰해주세요:
1. 잠재적 버그 가능성
2. 성능 이슈
3. 예외 처리 누락
4. Best Practice 위반

[Style]
- 문제점마다 심각도 표시 (🚨 심각, ⚠️ 경고, 💡 제안)
- 왜 문제인지 이유 설명
- 개선된 코드 예시 제공

[Code]
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User getUser(Long id) {
        return userRepository.findById(id).get();
    }
}

[Response Format]
1. 발견된 문제점 (심각도 순)
2. 각 문제점의 설명
3. 개선된 전체 코드
4. 추가 제안 사항
"""

// AI 응답:
"""
🚨 심각한 문제:

1. NoSuchElementException 위험
   - `findById().get()` 사용 시 데이터가 없으면 예외 발생
   - 이게 500 에러의 원인입니다!

2. 필드 주입 사용
   - @Autowired 필드 주입은 테스트가 어렵고 순환 참조 위험

⚠️ 경고:

3. 트랜잭션 누락
   - readOnly 트랜잭션을 명시하는 게 좋습니다

💡 개선 제안:

[개선된 코드]
@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    
    @Transactional(readOnly = true)
    public User getUser(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(
                "사용자를 찾을 수 없습니다: " + id
            ));
    }
}

[Exception 클래스]
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

[변경 사항]
1. 생성자 주입으로 변경 (테스트 용이)
2. Optional.orElseThrow()로 명시적 예외 처리
3. @Transactional(readOnly = true) 추가
4. 커스텀 예외로 적절한 HTTP 상태 코드 반환

이제 500 에러 대신 404 에러가 발생하여 클라이언트가
적절히 대응할 수 있습니다!
"""

5. 실전 프롬프트 템플릿 모음 📚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
@Component
public class PromptTemplates {
    
    /**
     * 코드 생성 템플릿
     */
    public static String codeGeneration(
        String tech,
        String task,
        String constraints
    ) {
        return String.format("""
            [Role]
            당신은 %s 전문가입니다.
            
            [Task]
            %s
            
            [Constraints]
            %s
            
            [Output Format]
            1. 완전한 동작 코드 (```java 블록)
            2. 주요 로직 단계별 설명
            3. 주의사항 및 Best Practice
            
            실무에서 바로 사용 가능한 프로덕션 레벨 코드를 작성해주세요.
            """, tech, task, constraints);
    }
    
    /**
     * 개념 설명 템플릿
     */
    public static String conceptExplanation(
        String concept,
        String level,
        String focusArea
    ) {
        return String.format("""
            [Persona]
            당신은 10년차 시니어 개발자로, 복잡한 개념을
            쉽게 설명하는 데 능숙합니다.
            
            [Concept]
            %s
            
            [Target Level]
            %s
            
            [Focus]
            %s
            
            [Style]
            - 실생활 또는 프로그래밍 비유 사용
            - 실제 코드 예시 포함
            - "왜 필요한가?"부터 시작
            - 실무 활용법 중심
            
            [Output Format]
            1. 핵심 개념 (비유로 설명)
            2. 실제 코드 예시
            3. 실무 활용 시나리오
            4. 흔한 오해와 주의사항
            
            초보자도 이해할 수 있도록 단계별로 설명해주세요.
            """, concept, level, focusArea);
    }
    
    /**
     * 디버깅 도움 템플릿
     */
    public static String debugging(
        String error,
        String code,
        String context
    ) {
        return String.format("""
            [Situation]
            다음 에러가 발생했습니다:
            ```
            %s
            ```
            
            [Code]
            ```java
            %s
            ```
            
            [Context]
            %s
            
            [Task]
            1. 에러 원인 분석
            2. 근본 원인 설명 (왜 발생했는가?)
            3. 해결 방법 (단계별)
            4. 재발 방지 방법
            
            [Output Format]
            🔍 원인 분석:
            ...
            
            🛠️ 해결 방법:
            1. ...
            2. ...
            
            ✅ 수정된 코드:
            ```java
            ...
            ```
            
            💡 재발 방지:
            ...
            
            실무 경험을 바탕으로 구체적으로 설명해주세요.
            """, error, code, context);
    }
    
    /**
     * 리팩토링 제안 템플릿
     */
    public static String refactoring(String code, String issues) {
        return String.format("""
            [Role]
            당신은 Clean Code 전문가입니다.
            
            [Current Code]
            ```java
            %s
            ```
            
            [Known Issues]
            %s
            
            [Task]
            다음 관점에서 리팩토링 제안:
            1. SOLID 원칙 위반
            2. 코드 중복 (DRY)
            3. 복잡도 (Cyclomatic Complexity)
            4. 네이밍
            5. 테스트 가능성
            
            [Output Format]
            📊 현재 코드 분석:
            - 문제점 1: ...
            - 문제점 2: ...
            
            ✨ 리팩토링된 코드:
            ```java
            ...
            ```
            
            📝 변경 사항 설명:
            1. ...
            2. ...
            
            💡 추가 제안:
            ...
            
            각 변경의 이유를 명확히 설명해주세요.
            """, code, issues);
    }
}

6. 실전 적용: OpenAI API 호출

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
@Service
@RequiredArgsConstructor
public class AIAssistantService {
    
    private final OpenAIClient openAI;
    
    /**
     * 구조화된 프롬프트로 코드 생성
     */
    public String generateCode(CodeGenerationRequest request) {
        // Persona 설정
        String systemPrompt = PersonaTemplates.SENIOR_JAVA_DEVELOPER;
        
        // 구조화된 프롬프트 생성
        String userPrompt = String.format("""
            [Context]
            - 프로젝트: %s
            - 기술 스택: %s
            - 현재 상태: %s
            
            [Objective]
            %s
            
            [Constraints]
            %s
            
            [Output Format]
            1. 완전한 코드 (테스트 가능)
            2. 주요 로직 설명
            3. 사용 예시
            """,
            request.getProjectContext(),
            String.join(", ", request.getTechStack()),
            request.getCurrentState(),
            request.getObjective(),
            String.join("\n", request.getConstraints())
        );
        
        // API 호출 (Temperature 조절)
        return openAI.chat(
            systemPrompt,
            userPrompt,
            temperature = 0.0,  // 코드 생성은 정확성 최우선
            maxTokens = 2000
        );
    }
    
    /**
     * Temperature에 따른 응답 비교
     */
    public Map<String, String> compareTemperatures(String prompt) {
        Map<String, String> results = new HashMap<>();
        
        // 결정적 응답
        results.put("deterministic", 
            openAI.chat(prompt, temperature = 0.0));
        
        // 균형 잡힌 응답
        results.put("balanced", 
            openAI.chat(prompt, temperature = 0.7));
        
        // 창의적 응답
        results.put("creative", 
            openAI.chat(prompt, temperature = 1.2));
        
        return results;
    }
    
    /**
     * Persona 적용 비교
     */
    public Map<String, String> comparePersonas(String question) {
        Map<String, String> results = new HashMap<>();
        
        // Persona 없음
        results.put("no_persona", 
            openAI.chat(null, question, temperature = 0.7));
        
        // 시니어 개발자
        results.put("senior_developer", 
            openAI.chat(
                PersonaTemplates.SENIOR_JAVA_DEVELOPER, 
                question, 
                temperature = 0.7
            ));
        
        // 면접관
        results.put("interviewer", 
            openAI.chat(
                PersonaTemplates.TECH_INTERVIEWER, 
                question, 
                temperature = 0.7
            ));
        
        return results;
    }
}

7. 정리: 프롬프트 = 함수 설계

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * 프롬프트 엔지니어링의 핵심
 */
public class PromptEngineering {
    
    // 1. 프롬프트는 함수다
    Response generate(
        Context ctx,       // 배경 정보
        Task task,         // 수행할 작업
        Constraints rules, // 제약 조건
        OutputFormat fmt   // 출력 형식
    );
    
    // 2. Temperature = Random Seed
    double temperature = 0.7; // 0.0(정확) ~ 2.0(창의)
    
    // 3. Persona = 클래스 상속
    interface AI extends SeniorDeveloper {
        String answer(String question);
    }
    
    // 4. 구조화 = 함수 시그니처 설계
    String prompt = buildStructuredPrompt(
        context, objective, style, tone, audience, format
    );
}

핵심 체크리스트

1
2
3
4
5
6
7
8
9
// ✅ Good Prompt 체크리스트
public boolean isGoodPrompt(String prompt) {
    return hasContext(prompt)           // 배경 정보 있는가?
        && hasObjective(prompt)         // 명확한 목표가 있는가?
        && hasConstraints(prompt)       // 제약 조건이 있는가?
        && hasOutputFormat(prompt)      // 출력 형식이 지정되었는가?
        && hasPersona(prompt)           // 역할이 부여되었는가?
        && hasProperTemperature(task);  // 작업에 맞는 Temperature인가?
}
요소필수 여부예시
Context✅ 필수“Spring Boot 3.2 프로젝트에서”
Objective✅ 필수“JWT 인증 구현”
Constraints⚠️ 권장“Clean Code, 주석 포함”
Output Format⚠️ 권장“코드 + 설명 + 예시”
Persona💡 선택“시니어 개발자”
Temperature💡 선택0.0 (코드) ~ 1.0 (창작)

마치며

프롬프트 엔지니어링은 단순한 글쓰기가 아닙니다. 함수를 설계하는 것처럼 논리적으로 접근해야 해요!

  1. 함수 설계 관점: 명확한 입력 → 예측 가능한 출력
  2. Temperature 조절: 작업 특성에 맞는 무작위성 선택
  3. Persona 부여: 클래스 상속처럼 전문성 범위 좁히기
  4. 구조화 (CO-STAR): 함수 시그니처처럼 명확하게

개발자로서 이 원칙들을 이해하면:

  • AI로부터 원하는 결과를 정확히 얻을 수 있고
  • 프롬프트를 재사용 가능한 템플릿으로 관리할 수 있으며
  • Temperature와 Persona로 응답 품질을 제어할 수 있습니다!

다음 프롬프트를 작성할 때는 “이건 함수 호출이다”라고 생각해보세요. 🚀


참고 자료

  • OpenAI Best Practices: https://platform.openai.com/docs/guides/prompt-engineering
  • Prompt Engineering Guide: https://www.promptingguide.ai/
  • CO-STAR Framework: https://towardsdatascience.com/how-i-won-singapores-gpt-4-prompt-engineering-competition-34c195a93d41
This post is licensed under CC BY 4.0 by the author.

[AI 도구] Cursor AI의 @Codebase 기능 완벽 이해하기 (feat. Spring 개발자 관점)

[AI 실전] AI의 지능을 해킹하자: Few-Shot 기법과 Chain of Thought (CoT)