중급 과정으로 돌아가기
Chapter 3: RAG를 위한 프롬프트 엔지니어링
검색 증강 생성을 최적화하는 프롬프트 전략
3.1 검색을 위한 Chain of Thought
단계별 사고를 통한 검색 품질 향상
기본 CoT 검색 프롬프트
❌ 단순한 접근
사용자 질문: {query}
검색된 문서: {documents}
위 정보를 바탕으로 답변하세요.✅ Chain of Thought 접근
당신은 검색 증강 AI 어시스턴트입니다. 다음 단계를 따라 사용자 질문에 답변하세요:
사용자 질문: {query}
검색된 문서:
{documents}
답변 프로세스:
1. 먼저 사용자의 핵심 의도를 파악하세요
2. 검색된 각 문서의 관련성을 평가하세요
3. 가장 관련성 높은 정보를 추출하세요
4. 정보 간의 모순이나 차이점이 있는지 확인하세요
5. 종합적인 답변을 구성하세요
단계별 분석:
<thinking>
1. 사용자 의도: [여기에 분석]
2. 문서별 관련성:
- 문서1: [관련도 및 핵심 정보]
- 문서2: [관련도 및 핵심 정보]
3. 정보 종합: [추출한 핵심 정보들]
4. 모순 확인: [있다면 설명, 없다면 "없음"]
</thinking>
최종 답변:
[종합적이고 명확한 답변]고급 CoT 기법: Self-Ask
class SelfAskRAGPrompt:
def __init__(self):
self.template = """
사용자 질문을 분석하고 필요한 하위 질문들을 생성한 후,
검색된 정보를 활용해 답변하세요.
원본 질문: {original_query}
<self_ask>
이 질문에 답하려면 어떤 정보가 필요한가?
1. [하위 질문 1]
2. [하위 질문 2]
3. [하위 질문 3]
</self_ask>
각 하위 질문에 대한 검색 결과:
{sub_query_results}
<integration>
하위 답변들을 어떻게 통합할 것인가?
- 정보의 신뢰도 평가
- 시간적 순서나 인과관계 고려
- 모순되는 정보 처리 방법
</integration>
최종 답변:
[통합된 종합 답변]
"""
def generate_sub_queries(self, original_query):
# LLM을 사용해 하위 질문 생성
prompt = f"""
다음 질문을 답하기 위해 필요한 3-5개의 구체적인 하위 질문을 생성하세요:
"{original_query}"
하위 질문들:
"""
return self.llm.generate(prompt)
def search_and_answer(self, query):
# 1. 하위 질문 생성
sub_queries = self.generate_sub_queries(query)
# 2. 각 하위 질문에 대해 검색
sub_results = []
for sub_q in sub_queries:
docs = self.retriever.search(sub_q, k=3)
sub_results.append({
"question": sub_q,
"documents": docs
})
# 3. 통합 답변 생성
final_prompt = self.template.format(
original_query=query,
sub_query_results=sub_results
)
return self.llm.generate(final_prompt)3.2 컨텍스트를 활용한 Few-shot 프롬프팅
예시를 통한 RAG 응답 품질 향상
효과적인 Few-shot 예시 구성
시스템: 당신은 검색된 문서를 바탕으로 정확하고 도움이 되는 답변을 제공하는 AI입니다.
다음 예시들을 참고하여 답변 스타일을 학습하세요.
예시 1:
질문: "Python에서 리스트와 튜플의 차이점은?"
검색 문서:
- 문서1: "리스트는 가변(mutable) 객체로 생성 후 수정 가능..."
- 문서2: "튜플은 불변(immutable) 객체로 한번 생성되면..."
- 문서3: "메모리 효율성 면에서 튜플이 리스트보다..."
답변: 검색된 문서를 바탕으로 Python의 리스트와 튜플의 주요 차이점을 설명하겠습니다:
1. **가변성 (Mutability)**
- 리스트: 가변 객체로 요소 추가, 삭제, 수정 가능
- 튜플: 불변 객체로 생성 후 변경 불가
2. **메모리 효율성**
- 튜플이 리스트보다 메모리를 적게 사용
- 고정된 데이터에는 튜플이 효율적
[출처: 문서1, 문서2, 문서3]
예시 2:
질문: "RESTful API의 주요 원칙은?"
검색 문서:
- 문서1: "REST는 6가지 제약 조건을 따르는..."
- 문서2: "Stateless 특성으로 서버는 클라이언트 상태를..."
- 문서3: "Uniform Interface는 리소스 식별과..."
답변: 검색 결과를 종합하여 RESTful API의 핵심 원칙을 정리하면:
1. **무상태성 (Stateless)**
- 각 요청은 독립적이며 서버는 클라이언트 상태를 저장하지 않음
2. **균일한 인터페이스 (Uniform Interface)**
- 리소스는 URI로 식별
- 표준 HTTP 메서드 사용 (GET, POST, PUT, DELETE)
3. **클라이언트-서버 분리**
- 관심사의 분리로 확장성 향상
[출처: 문서1, 문서2, 문서3]
---
이제 실제 질문에 답변하세요:
질문: {user_query}
검색 문서:
{retrieved_documents}
답변:도메인별 Few-shot 템플릿
🏥 의료 도메인
특징: 정확성 강조, 주의사항 포함, 전문 용어 설명
답변 형식: 1. 의학적 정의 2. 증상/원인 3. 치료 방법 4. ⚠️ 주의사항 [참고 문헌 명시]
⚖️ 법률 도메인
특징: 조항 인용, 판례 참조, 면책 조항
답변 형식: 1. 관련 법령 2. 핵심 내용 3. 판례/해석 4. 💡 실무 팁 [법령/판례 출처]
3.3 시스템 프롬프트 최적화
RAG 시스템의 기본 동작 정의
RAG 최적화된 시스템 프롬프트
SYSTEM_PROMPT = """
당신은 검색 증강 생성(RAG) 시스템을 갖춘 AI 어시스턴트입니다.
핵심 원칙:
1. **정확성**: 검색된 문서에 있는 정보만을 사용하여 답변
2. **투명성**: 정보의 출처를 명확히 표시
3. **완전성**: 관련된 모든 정보를 종합적으로 제공
4. **신뢰성**: 불확실한 정보는 그 불확실성을 명시
답변 가이드라인:
## 정보 처리
- 검색된 문서를 신중히 분석하여 관련 정보 추출
- 여러 문서 간 정보가 상충할 경우 모든 관점 제시
- 시간에 민감한 정보는 날짜/버전 명시
## 답변 구조
1. 핵심 답변 (1-2문장 요약)
2. 상세 설명 (구조화된 정보)
3. 추가 고려사항 (있을 경우)
4. 출처 표시
## 특별 지침
- 검색 결과가 불충분한 경우: "제공된 문서에는 충분한 정보가 없습니다"
- 전문 용어 사용 시: 간단한 설명 추가
- 코드/명령어 포함 시: 명확한 포맷팅과 주석
## 금지 사항
- 검색되지 않은 정보를 추측하거나 생성하지 않기
- 개인적 의견이나 추천을 하지 않기 (문서 기반이 아닌 경우)
- 확실하지 않은 정보를 확실한 것처럼 표현하지 않기
"""
class RAGSystemPromptOptimizer:
def __init__(self, domain=None):
self.base_prompt = SYSTEM_PROMPT
self.domain = domain
def get_optimized_prompt(self, query_type=None):
prompt = self.base_prompt
# 도메인별 추가 지침
if self.domain:
prompt += f"\n\n도메인: {self.domain}\n"
prompt += self.get_domain_specific_rules()
# 쿼리 타입별 조정
if query_type == "factual":
prompt += "\n정확한 사실 확인에 중점을 두세요."
elif query_type == "analytical":
prompt += "\n여러 관점을 분석하고 비교하세요."
elif query_type == "procedural":
prompt += "\n단계별로 명확하게 설명하세요."
return prompt
def get_domain_specific_rules(self):
rules = {
"medical": "의학적 조언은 제공하지 않으며, 전문의 상담을 권고하세요.",
"legal": "법률 자문이 아님을 명시하고, 전문가 상담을 권고하세요.",
"financial": "투자 조언이 아님을 명시하고, 리스크를 설명하세요.",
"technical": "코드 예제는 테스트를 거쳐야 함을 명시하세요."
}
return rules.get(self.domain, "")성능 측정 지표
85%
정확도
92%
출처 명시율
3.2초
평균 응답시간
4.6/5
사용자 만족도
3.4 에러 처리 프롬프트
우아한 실패와 사용자 가이드
상황별 에러 처리 템플릿
📭 검색 결과 없음
죄송합니다. "{query}"에 대한 관련 정보를 찾을 수 없습니다.
다음과 같이 시도해보세요:
• 다른 키워드나 동의어를 사용해보세요
• 더 구체적이거나 일반적인 용어로 검색해보세요
• 철자와 띄어쓰기를 확인해보세요
예시: "{suggested_query}"🔀 모순된 정보
검색된 문서들에서 상충하는 정보가 발견되었습니다:
관점 1: [출처: 문서A]
"{contradicting_info_1}"
관점 2: [출처: 문서B]
"{contradicting_info_2}"
💡 이러한 차이는 다음과 같은 이유일 수 있습니다:
• 정보의 업데이트 시점 차이
• 서로 다른 맥락이나 조건
• 출처의 관점 차이
최신 정보나 공식 출처를 확인하시기 바랍니다.⚠️ 부분적 정보
요청하신 정보의 일부만 찾을 수 있었습니다:
✅ 찾은 정보:
{found_information}
❌ 찾지 못한 정보:
{missing_information}
추가 정보가 필요하시면:
1. 더 구체적인 질문을 해주세요
2. 다른 측면에서 접근해보세요
3. 관련 전문가나 공식 문서를 참조하세요3.5 다중 턴 대화 관리
컨텍스트를 유지하는 연속 대화
대화 컨텍스트 관리 시스템
class MultiTurnRAGManager:
def __init__(self, max_history=5):
self.conversation_history = []
self.retrieved_context = {}
self.max_history = max_history
def process_turn(self, user_query, turn_number):
# 대화 기록 기반 쿼리 개선
enhanced_query = self.enhance_query_with_context(user_query)
# 이전 검색 결과 재사용 여부 결정
should_reuse = self.check_context_relevance(user_query)
if should_reuse:
# 기존 컨텍스트 활용
documents = self.retrieved_context.get('documents', [])
prompt = self.build_contextual_prompt(
user_query,
documents,
self.conversation_history
)
else:
# 새로운 검색 수행
documents = self.retriever.search(enhanced_query)
self.retrieved_context = {
'query': enhanced_query,
'documents': documents,
'turn': turn_number
}
prompt = self.build_fresh_prompt(user_query, documents)
return prompt
def build_contextual_prompt(self, query, documents, history):
return f"""
이전 대화 내용:
{self.format_history(history[-3:])} # 최근 3개 턴만
현재 질문: {query}
관련 컨텍스트 (이전에 검색된 정보):
{self.format_documents(documents)}
지침:
1. 이전 대화의 맥락을 고려하여 답변하세요
2. 이미 언급된 내용은 간략히 참조만 하세요
3. 새로운 관점이나 추가 정보에 집중하세요
4. 대화의 흐름을 자연스럽게 이어가세요
답변:
"""
def enhance_query_with_context(self, current_query):
if not self.conversation_history:
return current_query
# 대명사 해결 및 컨텍스트 추가
context_keywords = self.extract_keywords(self.conversation_history[-2:])
# 쿼리에 컨텍스트가 부족한 경우 보강
if self.is_query_ambiguous(current_query):
enhanced = f"{context_keywords} {current_query}"
return enhanced
return current_query
def check_context_relevance(self, current_query):
"""이전 검색 결과의 재사용 가능성 판단"""
if not self.retrieved_context:
return False
# 의미적 유사도 계산
prev_query = self.retrieved_context.get('query', '')
similarity = self.calculate_similarity(prev_query, current_query)
# 임계값 이상이면 재사용
return similarity > 0.7
# 사용 예시
manager = MultiTurnRAGManager()
# Turn 1
response1 = manager.process_turn("파이썬의 장점은 무엇인가요?", 1)
# Turn 2 - 컨텍스트 활용
response2 = manager.process_turn("그럼 단점은요?", 2) # "그럼"이 파이썬을 지칭
# Turn 3 - 새로운 검색 필요
response3 = manager.process_turn("자바와 비교하면 어떤가요?", 3)실습 과제
RAG 프롬프트 엔지니어링 실습
📋 과제 1: 도메인별 프롬프트 템플릿 구축
- 1. 3개 이상의 도메인 선택 (예: 의료, 법률, 기술)
- 2. 각 도메인별 시스템 프롬프트 작성
- 3. Few-shot 예시 3개씩 준비
- 4. 에러 처리 시나리오 정의
- 5. 실제 질문으로 테스트 및 평가
🎯 과제 2: Chain of Thought 최적화
- • Self-Ask 방식으로 복잡한 질문 분해
- • 각 단계별 추론 과정 명시화
- • 검색 효율성과 답변 품질 측정
- • A/B 테스트로 개선 효과 검증
💡 과제 3: 다중 턴 대화 시뮬레이션
고객 지원 챗봇 시나리오로 다음을 구현:
- • 5턴 이상의 연속 대화 처리
- • 컨텍스트 유지 및 참조 해결
- • 대화 기록 기반 개인화
- • 만족도 평가 시스템 구축
📊 평가 지표
정량적 지표:
- • 응답 정확도
- • 처리 시간
- • 토큰 사용량
정성적 지표:
- • 답변의 자연스러움
- • 컨텍스트 이해도
- • 에러 처리 품질