목차
- 오늘 강의 개요
- 환경 설정 & 라이브러리 import
- 출력 형식 제어 (JSON / Markdown / 직접 지정)
- 구조화된 출력 - 회의록 자동 변환
- ConstrainedPrompt 클래스 - 제약 조건 빌더
- 실습: 광고 카피 & 상세 제품 설명 생성
- 컨텍스트 기반 응답 제어 (Context-Grounded QA)
- answer_with_context 함수
- Zero-Shot vs Few-Shot 프롬프팅
- Few-Shot 감정 분석
- Few-Shot 비즈니스 문체 변환
- 핵심 개념 총정리
- 자주 나오는 실수/주의사항
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. 자주 나오는 실수/주의사항
주의사항
- JSON 파싱 시 코드 블록 확인
- LLM이 응답에
json ... 블록을 붙일 수 있음 - 직접 json.loads()하면 오류 발생
- “다른 텍스트를 포함하지 마세요” 명시하거나 파싱 전 전처리 필요
- Few-Shot 예시의 일관성
- 예시들의 형식이 제각각이면 LLM이 혼란스러워함
- 모든 예시는 동일한 입력-출력 형식을 유지할 것
- 컨텍스트 기반 응답의 제약 조건
- “추측하거나 외부 지식을 사용하지 마세요” 명시 필수
- 없는 내용에 대한 응답 형식도 지정 (“해당 정보가 없습니다”)
- 부정 제약(“거짓말하지 마세요”) 대신 긍정 제약(“문서 내용만 답하세요”) 사용
- ConstrainedPrompt execute() 사용 시
- temperature=0.3 등 낮은 값 → 더 일관된 출력
- max_tokens 지정 시 응답이 잘릴 수 있음 (충분히 설정할 것)
- Few-Shot 토큰 비용
- 예시가 많을수록 매 요청마다 토큰 소모 증가
- 3-5개 예시가 실용적인 균형점
- 예시는 간결하고 대표적인 케이스로 선별
- 카테고리 분류 시 오분류 방지
- categories 리스트를 SystemMessage에 명확히 나열
- “반드시 카테고리 이름만 출력하세요” 명시
- 카테고리 이름은 모호하지 않게 정의할 것
끝