포스트

2026-03-25 강의 정리

프롬프트 엔지니어링 - Zero-shot, Few-shot

2026-03-25 강의 정리

목차

  1. 오늘 강의 개요
  2. 환경 설정 & 라이브러리 import
  3. 출력 형식 제어 (JSON / Markdown / 직접 지정)
  4. 구조화된 출력 - 회의록 자동 변환
  5. ConstrainedPrompt 클래스 - 제약 조건 빌더
  6. 실습: 광고 카피 & 상세 제품 설명 생성
  7. 컨텍스트 기반 응답 제어 (Context-Grounded QA)
  8. answer_with_context 함수
  9. Zero-Shot vs Few-Shot 프롬프팅
  10. Few-Shot 감정 분석
  11. Few-Shot 비즈니스 문체 변환
  12. 핵심 개념 총정리
  13. 자주 나오는 실수/주의사항

1. 오늘 강의 개요

오늘 강의는 LLM의 출력을 정밀하게 제어하는 기법과, 예시를 통해 LLM을 유도하는 Few-Shot 프롬프팅을 학습한다.

핵심 키워드: 출력 형식 제어, 제약 조건, Few-Shot

전체 학습 흐름

1
2
3
4
5
6
7
8
9
10
11
출력 형식 제어 (JSON, Markdown, 직접 지정)
        ↓
구조화된 데이터 추출 (회의록 → JSON)
        ↓
ConstrainedPrompt 클래스 (제약 조건 체이닝)
        ↓
컨텍스트 기반 응답 제어 (hallucination 방지)
        ↓
Zero-Shot vs Few-Shot 비교
        ↓
Few-Shot 실전 응용 (감정 분석, 문체 변환)

프롬프트를 잘 짜는 것 = LLM을 코딩 없이 “프로그래밍”하는 것 오늘 배우는 기법들은 실제 프로덕션 LLM 앱에서 가장 많이 쓰이는 패턴들이다


2. 환경 설정 & 라이브러리 import

코드

1
2
3
4
5
6
7
8
9
10
11
12
import os
from dotenv import load_dotenv
import pandas as pd
import json
import time

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage

load_dotenv()

llm = ChatOpenAI(model="gpt-4o-mini")

3. 출력 형식 제어 (JSON / Markdown / 직접 지정)

LLM이 출력하는 형식을 정밀하게 제어하는 방법 3가지를 비교한다.

형식 비교 표

1
2
3
방법 1: Plain 텍스트에 "json만 출력" 지시
방법 2: Markdown 형식의 프롬프트로 작성
방법 3: json 구조를 템플릿으로 직접 제시

방법 1: 일반 텍스트 + 단순 지시

1
2
3
4
5
6
plain_prompt = """다음 제품 리뷰를 분석해줘.
전체 감정(긍정/부정/혼합), 1~5점 점수, 장점 목록, 단점 목록, 핵심 키워드 3개를 알려줘.
리뷰 : "이 노트북 정말 가벼워서 좋아요! 다만 키보드 타건감이 아쉽네요"
유효한 json만 출력하세요"""

print(llm.invoke([HumanMessage(content=plain_prompt)]).content)

방법 1 응답 예시 (코드 블록 포함됨 - 파싱 주의)

1
2
3
4
5
6
7
8
```json
{
  "전체_감정": "혼합",
  "점수": 4,
  "장점": ["가벼운 무게"],
  "단점": ["타건감 아쉬움"],
  "핵심_키워드": ["가벼움", "키보드", "타건감"]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
> 방법 1의 문제점: 응답에 ```json ... ``` 코드 블록이 포함되어 직접 json.loads() 불가
> 파이썬에서 파싱할 때 코드 블록 마커를 제거하는 전처리 필요

### 방법 2: Markdown 형식 프롬프트

```python
markdown_prompt = """# 제품 리뷰 감정 분석

## 입력 리뷰
> "이 노트북 정말 가벼워서 좋아요! 다만 키보드 타건감이 아쉽네요"

## 분석 항목
- **overall_sentiment** : 긍정/부정/혼합 중 하나
- **score** : 1~5점수
- **pros** : 장점 리스트
- **cons** : 단점 리스트
- **keywords** : 핵심 키워드 3개

## 출력 형식
유효한 json만 출력하세요. 다른 텍스트를 포함하지 마세요
"""

print(llm.invoke([HumanMessage(content=markdown_prompt)]).content)

방법 2 응답 예시 (순수 JSON - 바로 파싱 가능)

1
2
3
4
5
6
7
{
  "overall_sentiment": "혼합",
  "score": 3,
  "pros": ["가벼움"],
  "cons": ["타건감 아쉬움"],
  "keywords": ["노트북", "가벼워서", "키보드"]
}

방법 2의 장점: 프롬프트 자체가 Markdown으로 구조화되어 있어 LLM이 형식을 더 잘 따름 “다른 텍스트를 포함하지 마세요” 명시 → 코드 블록 없이 순수 JSON 반환

방법 3: JSON 템플릿 직접 제시

1
2
3
4
5
6
7
8
9
10
11
12
13
json_prompt = """다음 제품 리뷰를 분석해서 json 형식으로 출력하세요

리뷰 : "이 노트북 정말 가벼워서 좋아요! 다만 키보드 타건감이 아쉽네요"

출력 형식
{
    "overall_sentiment" : 긍정/부정/혼합,
    "score" : 1~5점수,
    "pros" : [장점],
    "cons" : [단점],
    "keywords" : [키워드1, 키워드2, 키워드3]
}
"""

방법별 특징 비교

1
2
3
방법 1 (plain)    : 코드 블록 포함 가능성 있음, 파싱 전처리 필요
방법 2 (markdown) : 구조화된 지시로 형식 준수율 높음, 순수 JSON 반환
방법 3 (template) : 빈 칸 채우기 방식, 키 이름 고정 가능

4. 구조화된 출력 - 회의록 자동 변환

비정형 텍스트(회의 내용)를 정형 JSON으로 변환하는 실전 예시.

JSON 구조 정의

1
2
3
4
5
6
7
8
9
json_structure = {
    'date'         : 'YYYY-MM-DD',
    'attendees'    : ['이름1', '이름2'],
    'agenda'       : ['안건1', '안건2'],
    'decisions'    : ['결정1'],
    'action_items' : [
        {'assignee': '담당자', 'task': '작업', 'deadline': '기한'}
    ]
}

generate_meeting_minute 함수

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
text = '3월 15일 마케팅팀 주간 회의. 참석: 김팀장, 이대리, 박사원. '
       '신규 SNS 캠페인 예산 5000만원 확정. '
       '이대리가 3월 22일까지 시안 준비. '
       '박사원은 경쟁사 분석 보고서 3월 20일까지.'

def generate_meeting_minute(raw_text):
    messages = [
        SystemMessage(content='당신은 회의록 전문가입니다. '
                               '인사말이나 부연설명 없이 순수한 json 데이터만 반환하세요'),
        HumanMessage(content=f"""다음 회의 내용을 지정된 json 구조로 변환하세요.

        회의내용 : {raw_text}

        json구조:
        {{
            'date'         : 'YYYY-MM-DD',
            'title'        : '회의 제목',
            'attendees'    : ['이름1', '이름2'],
            'agenda'       : ['안건1', '안건2'],
            'decisions'    : ['결정1'],
            'action_items' : [
                    {{'assignee' : '담당자', 'task' : '작업', 'deadline' : '기한'}}]
        }}
        """)
    ]

    response = llm.invoke(messages).content
    return response

print(generate_meeting_minute(text))

변환 결과 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "date": "2023-03-15",
  "title": "마케팅팀 주간 회의",
  "attendees": ["김팀장", "이대리", "박사원"],
  "agenda": ["신규 SNS 캠페인 예산 확정", "시안 준비", "경쟁사 분석 보고서"],
  "decisions": ["신규 SNS 캠페인 예산 5000만원 확정"],
  "action_items": [
    { "assignee": "이대리", "task": "시안 준비", "deadline": "2023-03-22" },
    {
      "assignee": "박사원",
      "task": "경쟁사 분석 보고서",
      "deadline": "2023-03-20"
    }
  ]
}

전체 변환 흐름

1
2
3
4
5
6
7
8
비정형 텍스트 (자연어 회의 내용)
        ↓
SystemMessage: "json만 반환"
HumanMessage: 내용 + json 구조 템플릿
        ↓
LLM 처리
        ↓
구조화된 JSON (DB 저장, API 연동 가능)

SystemMessage에 “인사말이나 부연설명 없이 순수한 json 데이터만 반환하세요”를 명시하면 코드 블록이나 설명 텍스트 없이 바로 json.loads()가 가능한 형태로 나온다


5. ConstrainedPrompt 클래스 - 제약 조건 빌더

제약 조건을 메서드 체이닝으로 추가하고, 최종 프롬프트를 생성하는 빌더 패턴 클래스.

클래스 구조 도식

1
2
3
4
5
6
7
ConstrainedPrompt(base_instruction)
    ├─ add_length(description)   → [길이] 제약 추가
    ├─ add_content(description)  → [내용] 제약 추가
    ├─ add_format(description)   → [형식] 제약 추가
    ├─ add_style(description)    → [스타일] 제약 추가
    ├─ build()                   → 완성된 프롬프트 문자열 반환
    └─ execute(temperature, max_tokens) → llm 호출 후 결과 반환

코드

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
class ConstrainedPrompt:
    def __init__(self, base_instruction):
        self.instruction = base_instruction
        self.constraints = []

    def add_length(self, description):
        self.constraints.append(f"[길이] {description}")
        return self

    def add_content(self, description):
        self.constraints.append(f"[내용] {description}")
        return self

    def add_format(self, description):
        self.constraints.append(f"[형식] {description}")
        return self

    def add_style(self, description):
        self.constraints.append(f"[스타일] {description}")
        return self

    def build(self):
        parts = [self.instruction, "\n제약조건:"]
        for c in self.constraints:
            parts.append(f" - {c}")
        return '\n'.join(parts)

    def execute(self, temperature=0.7, max_tokens=None):
        prompt = self.build()
        llm_instance = ChatOpenAI(
            model="gpt-4o-mini",
            temperature=temperature,
            **({"max_tokens": max_tokens} if max_tokens else {})
        )
        return llm_instance.invoke([HumanMessage(content=prompt)]).content

return self가 핵심 - 각 add_* 메서드가 자기 자신을 반환하므로 체이닝이 가능하다 이 패턴을 “Builder Pattern” 또는 “Method Chaining”이라고 한다

사용 예시 - 클라우드 컴퓨팅 설명

1
2
3
4
5
6
7
8
9
result = (
    ConstrainedPrompt('클라우드 컴퓨팅의 장점을 설명해주세요')
    .add_length("5개의 불릿포인트")
    .add_content('비용, 확장성, 보안 관점을 반드시 포함')
    .add_format('각 포인트는 한줄로, 이모지로 시작')
    .add_style('IT 비전공 경영진을 대상, 전문 용어에 괄호로 설명 추가')
    .execute(temperature=0.3, max_tokens=200)
)
print(result)

build() 실행 시 생성되는 프롬프트 구조

1
2
3
4
5
6
7
클라우드 컴퓨팅의 장점을 설명해주세요

제약조건:
 - [길이] 5개의 불릿포인트
 - [내용] 비용, 확장성, 보안 관점을 반드시 포함
 - [형식] 각 포인트는 한줄로, 이모지로 시작
 - [스타일] IT 비전공 경영진을 대상, 전문 용어에 괄호로 설명 추가

응답 예시

1
2
3
4
5
💰 비용 효율성: 클라우드 서비스는 초기 투자 비용을 줄이고, 사용한 만큼만 지불하여 재무 부담을 최소화합니다.
📈 확장성: 필요에 따라 신속하게 리소스를 추가하거나 감소시킬 수 있어, 사업 성장에 유연하게 대응합니다.
🔒 보안 강화: 클라우드 제공업체는 높은 수준의 보안을 제공하며, 데이터 보호를 위해 최신 기술을 업데이트합니다.
🚀 신속한 배포: 새로운 서비스를 간편하게 배포할 수 있어, 시장 변화에 빠르게 적응합니다.
🌍 접근성 향상: 인터넷만 있으면 언제 어디서나 데이터에 접근 가능하여 글로벌 비즈니스 운영에 유리합니다.

6. 실습: 광고 카피 & 상세 제품 설명 생성

ConstrainedPrompt로 같은 제품에 대해 서로 다른 톤/목적의 텍스트를 생성하는 실습.

제품 정보 준비

1
2
product  = "에어프로 맥스 무선 헤드폰"
features = ["40시간 배터리", "멀티포인트 연결", "30dB 노이즈캔슬링", "300g 경량 설계"]

광고 카피 생성

1
2
3
4
5
6
7
8
9
10
ad_result = (
    ConstrainedPrompt(f"{product} 제품 광고 카피를 작성해주세요. 특징 : {', '.join(features)}")
    .add_length("한 줄, 30자 이내")
    .add_content('핵심 특징 1가지만 강조')
    .add_format('슬로건 형태, 마침표 없이')
    .add_style('2030 타겟, 감성적이고 트렌디한 톤')
    .execute()
)
print(ad_result)
# → 40시간의 음악 여정, 자유롭게 즐겨요

상세 제품 설명 생성

1
2
3
4
5
6
7
8
9
detail_result = (
    ConstrainedPrompt(f"{product} 상세 제품 설명을 작성해주세요. 특징 : {', '.join(features)}")
    .add_length("200자 내외, 3단락")
    .add_content('모든 특징을 빠짐없이 설명, 사용 시나리오 1개 포함')
    .add_format('단락 구분을 명확히, 마지막에 가격 정보 위치 표시')
    .add_style('공식 제품 페이지 톤, 신뢰감 있는 문체')
    .execute()
)
print(detail_result)

상세 설명 응답 예시

1
2
3
4
5
6
7
8
9
에어프로 맥스 무선 헤드폰은 최대 40시간 지속 재생이 가능한 배터리 성능을 자랑합니다.
한 번의 완충으로 오랜 시간 음악을 감상하거나 회의에 참여할 수 있어, 바쁜 일상에서도 쾌적한 청취 환경을 제공합니다.

멀티포인트 연결 기능으로 두 개의 기기를 동시에 연결하여 편리하게 전환할 수 있으며,
30dB의 강력한 노이즈 캔슬링 기술로 외부 소음을 차단하고 집중력을 극대화합니다.
300g의 경량 설계로 장시간 착용에도 귀의 피로를 최소화합니다.

출퇴근 시 지하철에서도 노이즈 캔슬링으로 완벽한 음악 감상이 가능합니다.
[가격 정보]

두 결과물 비교

1
2
광고 카피    : 짧고 감성적, 특정 세대 타겟, 임팩트 있는 한 줄
상세 설명    : 길고 신뢰감 있는 문체, 모든 특징 나열, 사용 시나리오 포함

같은 데이터(features)에서 제약 조건만 바꿔서 완전히 다른 텍스트를 생성할 수 있다 ConstrainedPrompt는 콘텐츠 마케팅, 제품 설명, 이메일 등 다양한 텍스트 생성에 재사용 가능


7. 컨텍스트 기반 응답 제어 (Context-Grounded QA)

LLM이 외부 지식 대신 제공된 문서만을 근거로 답변하도록 강제하는 기법. RAG 시스템의 핵심 원리와 동일하다. 할루시네이션(Hallucination) 방지에 필수적이다.

할루시네이션 문제

1
2
3
LLM은 학습 데이터 기반으로 그럴듯한 답변을 생성하기 때문에
문서에 없는 내용도 자신 있게 답변하는 문제가 발생한다
→ 이를 "Hallucination(환각)" 이라고 함

컨텍스트 문서 예시

1
2
3
4
5
6
7
8
9
10
company_policy = """
[모두컴퍼니 재택근무 정책 v2.3]
- 주 3일 재택, 2일 출근 (화/목 필수 출근)
- 재택근무 시 오전 9시까지 Slack 상태 '업무중' 설정 필수
- 해외 원격근무는 최대 연속 2주까지 가능 (사전 승인 필요)
- 야간근무(22시 이후) 시 익일 오후 출근 가능
- 재택근무 장비 지원금: 연 100만원 (영수증 제출)
"""

question = '해외에서 한 달 동안 원격 근무할 수 있나요?'

기본 컨텍스트 제어

1
2
3
4
5
6
7
8
9
10
with_context = f"""아래 회사 정책 문서를 참고하여 질문에 답하세요.
문서에 없는 내용은 "해당 정책 문서에 명시되어 있지 않습니다"라고 답하세요.

정책 문서:
\"\"\"{company_policy}\"\"\"

질문 : {question}"""

print(llm.invoke([HumanMessage(content=with_context)]).content)
# → 해당 정책 문서에 명시되어 있지 않습니다.

실제 정책: “최대 연속 2주” → 한 달은 불가 → LLM이 정확히 판단 이처럼 문서를 제공하면 LLM이 외부 지식이 아닌 문서만 참조한다


8. answer_with_context 함수

컨텍스트 기반 응답을 재사용 가능하게 함수화하고, 답변과 근거를 함께 출력하는 형식을 추가한 버전.

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def answer_with_context(context, question):
    clause = """
    중요 : 반드시 제공된 문서 내용만을 근거로 답변하세요.
    문서에 없는 내용은 "제공된 문서에 해당 정보가 없습니다"라고 답하세요.
    추측하거나 외부 지식을 사용하지 마세요."""

    prompt = f"""아래 참고 문서를 기반으로 질문에 답하세요.
    {clause}

    참고 문서:
    \"\"\"{context}\"\"\"

    질문 : {question}

    답변 형식:
    - 답변 : [핵심 답변]
    - 근거 : [문서에서 관련 부분 인용]"""

    return llm.invoke([HumanMessage(content=prompt)]).content

응답 예시 1 - 정보 있는 경우

1
answer_with_context(company_policy, '해외에서 한 달 동안 원격 근무할 수 있나요?')
1
2
- 답변 : 해외에서 한 달 동안 원격 근무할 수 없습니다.
- 근거 : "해외 원격근무는 최대 연속 2주까지 가능 (사전 승인 필요)"

응답 예시 2 - 정보 없는 경우

1
answer_with_context(company_policy, '식대 지원금은 얼마인가요?')
1
2
- 답변 : 제공된 문서에 해당 정보가 없습니다
- 근거 : 제공된 문서에는 식대 지원금에 대한 정보가 포함되어 있지 않습니다.

전체 흐름

1
2
3
4
5
6
7
context (문서) + question (질문)
        ↓
"문서만 근거로", "없으면 없다고" 제약 조건 포함 프롬프트 생성
        ↓
LLM 호출
        ↓
답변 + 근거 (문서 인용) 형식으로 반환

답변 형식에 “근거 : [문서에서 관련 부분 인용]”을 포함하면 LLM이 어떤 정보를 참조했는지 추적할 수 있어 신뢰도가 높아진다


9. Zero-Shot vs Few-Shot 프롬프팅

Zero-Shot

1
2
예시 없이 지시만으로 LLM이 작업을 수행하는 방식
→ 범용적인 작업에는 잘 동작하지만, 특수한 형식/스타일 요구 시 불안정

Few-Shot

1
2
몇 가지 예시(입력-출력 쌍)를 함께 제공해서 LLM이 패턴을 학습하게 하는 방식
→ LLM이 원하는 형식이나 스타일을 더 정확히 따름

비교 도식

1
2
3
4
5
6
7
8
9
10
11
12
Zero-Shot:
[SystemMessage] + [HumanMessage(질문)]
                        ↓
              LLM이 학습 지식만으로 답변

Few-Shot:
[SystemMessage]
+ [HumanMessage(예시1)] + [AIMessage(예시1 답변)]
+ [HumanMessage(예시2)] + [AIMessage(예시2 답변)]
+ [HumanMessage(실제 질문)]
                        ↓
         LLM이 예시 패턴을 보고 동일한 형식으로 답변

Few-Shot의 핵심은 LangChain의 AIMessage를 활용해서 “이미 일어난 대화인 척” 예시를 대화 히스토리에 심어두는 것이다


10. Few-Shot 감정 분석

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
few_shot_messages = [
    SystemMessage(content='주어진 리뷰의 감정을 분석하세요'),

    # 예시 1 (긍정)
    HumanMessage(content="리뷰: '이 제품 정말 최고입니다! 강력 추천해요'"),
    AIMessage(content='{"sentiment" : "긍정", "score" : 0.95, "keywords" : ["최고", "강력 추천"]}'),

    # 예시 2 (부정)
    HumanMessage(content="리뷰: '배송도 느리고 제품 품질도 형편없네요'"),
    AIMessage(content='{"sentiment" : "부정", "score" : 0.15, "keywords" : ["느리고", "형편없네요"]}'),

    # 예시 3 (혼합)
    HumanMessage(content="리뷰: '가격 대비 괜찮지만, 기대했던 것보다는 아쉬워요'"),
    AIMessage(content='{"sentiment" : "혼합", "score" : 0.50, "keywords" : ["괜찮지만", "아쉬워요"]}'),

    # 실제 쿼리
    HumanMessage(content="리뷰: '디자인은 예쁜데, 배터리가 빨리 닳아요. 전체적으로 보통입니다'")
]

print(llm.invoke(few_shot_messages).content)

응답 예시

1
2
3
4
5
{
  "sentiment": "혼합",
  "score": 0.45,
  "keywords": ["예쁜데", "빨리 닳아요", "보통"]
}

Few-Shot을 쓰지 않으면 응답이 자유로운 자연어 형태로 나올 수 있다 Few-Shot으로 json 예시를 3개 보여주면 LLM이 동일한 json 구조로 일관되게 답변한다

Few-Shot 예시 수에 관한 팁

1
2
3
1-2개  : 약한 힌트, 형식이 가끔 무너질 수 있음
3-5개  : 적정 수준, 패턴 인식이 확실해짐
6개 이상: 토큰 낭비 가능, 3-5개와 품질 차이 거의 없음

11. Few-Shot 비즈니스 문체 변환

비공식 메시지를 정중한 비즈니스 이메일 톤으로 변환하는 실습.

예시 데이터 준비

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
examples = [
    {
        "informal": "내일 미팅 좀 미룰 수 있을까? 갑자기 일이 생겼어.",
        "formal"  : "안녕하세요. 내일 예정된 미팅 일정 변경을 요청드립니다. "
                    "긴급한 업무가 발생하여 조율이 필요합니다. "
                    "가능한 대체 일정을 알려주시면 감사하겠습니다."
    },
    {
        "informal": "그 보고서 다 했어? 빨리 보내줘.",
        "formal"  : "안녕하세요. 요청드렸던 보고서 진행 상황을 확인드립니다. "
                    "완료되셨다면 전달 부탁드리며, 추가 시간이 필요하시면 말씀해 주세요."
    },
    {
        "informal": "이번 프로젝트 결과 별로인데 어떻게 할까?",
        "formal"  : "안녕하세요. 이번 프로젝트 결과에 대해 논의가 필요합니다. "
                    "개선 방안을 함께 검토하기 위해 미팅을 잡는 것이 어떨까요?"
    }
]

test_messages = [
    "다음 주 워크숍 참석 못 할 것 같아. 다른 사람 보내도 돼?",
    "예산 좀 더 받을 수 있을까? 지금 부족해."
]

Few-Shot 메시지 구성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
messages = [
    SystemMessage(content='당신은 비즈니스 커뮤니케이션 전문가입니다. '
                           '비공식적인 메시지를 정중한 비즈니스 이메일 톤으로 변환하세요. '
                           '인사말, 존칭, 구체적 표현을 포함하세요')
]

# Few-Shot 예시 추가
for ex in examples:
    messages.append(HumanMessage(content=ex['informal']))
    messages.append(AIMessage(content=ex['formal']))

# 실제 변환할 메시지 추가
messages.append(HumanMessage(content=test_messages[0]))

print(llm.invoke(messages).content)

응답 예시 1

1
2
3
4
입력 : "다음 주 워크숍 참석 못 할 것 같아. 다른 사람 보내도 돼?"

출력 : "안녕하세요. 다음 주 예정된 워크숍에 참석하기 어려운 상황입니다.
         대신 다른 분을 참석시켜도 괜찮으신지 확인 부탁드립니다. 감사합니다."

응답 예시 2

1
2
3
4
5
입력 : "예산 좀 더 받을 수 있을까? 지금 부족해."

출력 : "안녕하세요. 현재 예산에 대한 상황을 말씀드리고자 합니다.
         현재 예산이 부족하여 추가 지원을 요청드릴 수 있는지 검토해 주시면 감사하겠습니다.
         필요한 추가 자료가 있다면 말씀해 주세요."

매번 새로운 쿼리를 테스트할 때 메시지 구성 방법

1
2
3
4
5
6
7
8
9
10
11
def transform_to_formal(informal_text):
    """examples를 활용한 few-shot 문체 변환"""
    messages = [
        SystemMessage(content='당신은 비즈니스 커뮤니케이션 전문가입니다. '
                               '비공식적인 메시지를 정중한 비즈니스 이메일 톤으로 변환하세요.')
    ]
    for ex in examples:
        messages.append(HumanMessage(content=ex['informal']))
        messages.append(AIMessage(content=ex['formal']))
    messages.append(HumanMessage(content=informal_text))
    return llm.invoke(messages).content

Few-Shot은 매번 동일한 예시 메시지를 앞에 붙이는 방식이므로 예시가 많을수록 토큰이 증가한다 3개 예시 정도가 품질 대비 토큰 비용이 적절한 균형점이다


12. 핵심 개념 총정리

오늘 배운 기법 정리

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
1. 출력 형식 제어
   - Markdown 형식 프롬프트: 형식 준수율 높음
   - "다른 텍스트 포함하지 마세요": 코드 블록 방지
   - JSON 템플릿 제시: 키 이름과 구조 고정

2. 구조화된 출력 (회의록 변환)
   - SystemMessage에 "순수한 json만 반환" 명시
   - json 구조 템플릿을 프롬프트에 직접 포함

3. ConstrainedPrompt (빌더 패턴)
   - add_length / add_content / add_format / add_style
   - return self로 메서드 체이닝 가능
   - execute()로 직접 LLM 호출

4. 컨텍스트 기반 응답 (Grounded QA)
   - "문서만 근거로", "없으면 없다고" 제약
   - 답변 + 근거 형식으로 출처 추적 가능
   - Hallucination 방지의 기본 원리

5. Zero-Shot vs Few-Shot
   - Zero-Shot: 예시 없이 지시만
   - Few-Shot: HumanMessage + AIMessage 쌍으로 예시 제공
   - 3-5개 예시가 적정 수준

6. 뉴스 분류 (Zero-Shot)
   - SystemMessage에 카테고리 목록 명시
   - "반드시 카테고리 이름만 출력" 제약

7. Few-Shot 문체 변환
   - examples 리스트로 예시 관리
   - 매번 SystemMessage + examples + 새 쿼리 조합

기법 선택 가이드

1
2
3
4
5
원하는 출력이 JSON인 경우          → 방법 2 (Markdown 구조 프롬프트)
특수한 형식/스타일이 있는 경우     → Few-Shot
환각 방지가 필요한 경우            → 컨텍스트 기반 응답
다양한 제약 조건 조합이 필요한 경우 → ConstrainedPrompt 빌더 패턴
단순 분류/추출인 경우              → Zero-Shot + 출력 형식 명시

13. 자주 나오는 실수/주의사항

주의사항

  1. JSON 파싱 시 코드 블록 확인
    • LLM이 응답에 json ... 블록을 붙일 수 있음
    • 직접 json.loads()하면 오류 발생
    • “다른 텍스트를 포함하지 마세요” 명시하거나 파싱 전 전처리 필요
  2. Few-Shot 예시의 일관성
    • 예시들의 형식이 제각각이면 LLM이 혼란스러워함
    • 모든 예시는 동일한 입력-출력 형식을 유지할 것
  3. 컨텍스트 기반 응답의 제약 조건
    • “추측하거나 외부 지식을 사용하지 마세요” 명시 필수
    • 없는 내용에 대한 응답 형식도 지정 (“해당 정보가 없습니다”)
    • 부정 제약(“거짓말하지 마세요”) 대신 긍정 제약(“문서 내용만 답하세요”) 사용
  4. ConstrainedPrompt execute() 사용 시
    • temperature=0.3 등 낮은 값 → 더 일관된 출력
    • max_tokens 지정 시 응답이 잘릴 수 있음 (충분히 설정할 것)
  5. Few-Shot 토큰 비용
    • 예시가 많을수록 매 요청마다 토큰 소모 증가
    • 3-5개 예시가 실용적인 균형점
    • 예시는 간결하고 대표적인 케이스로 선별
  6. 카테고리 분류 시 오분류 방지
    • categories 리스트를 SystemMessage에 명확히 나열
    • “반드시 카테고리 이름만 출력하세요” 명시
    • 카테고리 이름은 모호하지 않게 정의할 것

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

인기 태그