프롬프트 기초
- 프롬프트 개념
- 프롬프트 단계는 검색기에서 검색된 문서들을 바탕으로 언어 모델이 사용할 질문이나 명령을 생성하는 과정입니다. 이 단계는 검색된 정보를 바탕으로 최종 사용자의 질문에 가장 잘 대응할 수 있는 응답을 생성하기 위해 필수적인 단계
- 프롬프트 필요성
- 문맥(Context) 설정: 프롬프트는 언어 모델이 특정 문맥에서 작동하도록 설정하는 역할을 합니다. 이를 통해 모델은 제공된 정보를 바탕으로 보다 정확하고 관련성 높은 답변을 생성할 수 있습니다.
- 정보 통합: 여러 문서에서 검색된 정보는 서로 다른 관점이나 내용을 포함할 수 있습니다. 프롬프트 단계에서 이러한 정보를 통합하고, 모델이 이를 효율적으로 활용할 수 있는 형식으로 조정합니다.
- 응답 품질 향상: 질문에 대한 모델의 응답 품질은 프롬프트의 구성에 크게 의존합니다. 잘 구성된 프롬프트는 모델이 보다 정확하고 유용한 정보를 제공하게 돕습니다.
- 프롬프트 구조
- 지시사항(Instruction)
- 질문(사용자 입력 질문)
- 문맥(검색된 정보)
당신은 질문-답변(Question-Answer) Task 를 수행한는 AI 어시스턴트 입니다.
검색된 문맥(context)를 사용하여 질문(question)에 답하세요.
만약, 문맥(context) 으로부터 답을 찾을 수 없다면 '모른다' 고 말하세요.
한국어로 대답하세요.
#Question:
{이곳에 사용자가 입력한 질문이 삽입됩니다}
#Context:
{이곳에 검색된 정보가 삽입됩니다}
- 프롬프트의 중요성
- 사용자의 질문에 대해 최적화된 방식으로 응답을 생성할 수 있다.
- 반대로 자칫 잘못 작성하면 좋지않은 응답을 얻을 수도 있다.
프롬프트 생성 방법
방법 1. from_template() 메소드를 사용하여 PromptTemplate 객체 생성
- 아주 간단하게 프롬트 생성
- 여러 변수도 처리가능
from langchain.prompts import PromptTemplate
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(model="llama3.1", temperature=0, max_length=135)
template = "{country}의 수도는 어디인가요?"
prompt = PromptTemplate(
template=template,
input_variables=["country"],
)
prompt
PromptTemplate(input_variables=['country'], template='{country}의 수도는 어디인가요?')
# format은 템플릿 문자열에서 지정된 변수 대체, 실제 값을 대입하는건 아니고, 확인만 하는용도
# 체인의 과정에서 format은 필요없다. 실제 삽입은 invoke를 통해 한다.
prompt.format(country="대한민국")
'대한민국의 수도는 어디인가요?'
방법 2. PromptTemplate 객체 생성과 동시에 prompt 생성
- PromptTemplate 객체를 생성할 때 input_variables를 명시적으로 지정하는게 첫번째와 다르다.
- 더 나은점은 input_variables를 명시적으로 지정하여 템플릿 문자열과 일치하지 않는 경우 예외를 발생시켜. 코드의 안정성을 높이고, 더 복잡한 템플릿을 다룰 수 있다.
template = "{country1}과 {country2}의 수도는 각각 어디인가요?"
# PromptTemplate 객체를 활용하여 prompt_template 생성
prompt = PromptTemplate(
template=template,
input_variables=["country1"],
# 일부 변수만 선언후 넣어놈
partial_variables={
"country2": "미국" # dictionary 형태로 partial_variables를 전달
},
)
prompt
PromptTemplate(input_variables=['country1'], partial_variables={'country2': '미국'}, template='{country1}과 {country2}의 수도는 각각 어디인가요?')
# 나머지 변수 삽입
prompt.format(country1="대한민국")
'대한민국과 미국의 수도는 각각 어디인가요?'
# 새로운 prompt_partial 만들고 캐나다 일부변수로 삽입
prompt_partial = prompt.partial(country2="캐나다")
prompt_partial
PromptTemplate(input_variables=['country1'], partial_variables={'country2': '캐나다'}, template='{country1}과 {country2}의 수도는 각각 어디인가요?')
# 체인 생성
chain =prompt_partial | llm
# invoke 메소드는 LangChain 라이브러리에서 체인을 실행하는 데 사용.
# 이 메소드는 프롬프트 템플릿에 입력 변수를 전달하고, 이를 기반으로 모델이 응답을 생성하도록 함
chain.invoke("대한민국")
AIMessage(content='대한민국의 수도는 서울입니다. 캐나다의 수도는 오타와입니다.', response_metadata={'model': 'llama3.1', 'created_at': '2024-08-07T05:00:26.6307439Z', 'message': {'role': 'assistant', 'content': ''}, 'done_reason': 'stop', 'done': True, 'total_duration': 8247940100, 'load_duration': 4405396900, 'prompt_eval_count': 24, 'prompt_eval_duration': 1231724000, 'eval_count': 22, 'eval_duration': 2609071000}, id='run-ffb31d58-85da-4643-ae8b-4c685790b7c9-0')
# content로 결과물로부터 내용만 추출
chain.invoke("대한민국").content
'대한민국의 수도는 서울입니다. 캐나다의 수도는 오타와입니다.'
chain.invoke({"country1": "대한민국", "country2": "호주"}).content
'대한민국의 수도는 서울입니다. 호주의 수도는 캔버라입니다.'
template = "{country1}과 {country2}의 수도는 각각 어디인가요?"
prompt = PromptTemplate(
template=template,
input_variables=["country1","country2"] # 이번엔 두 변수다 입력 받게 해봄
)
prompt
PromptTemplate(input_variables=['country1', 'country2'], template='{country1}과 {country2}의 수도는 각각 어디인가요?')
# 입력값을 변수가 아닌 함수로 줘봄
from datetime import datetime
# 날짜를 반환하는 함수 정의
def get_today():
return datetime.now().strftime("%B %d")
prompt = PromptTemplate(
template="오늘의 날짜는 {today} 입니다. 오늘이 생일인 유명인 {n}명을 나열해 주세요. 생년월일을 표기해주세요.",
input_variables=["n"],
partial_variables={
"today": get_today # dictionary 형태로 partial_variables를 전달
},
)
# format을 통해 변수에 n=3을 넣어보고 양식 확인.
prompt.format(n=3)
'오늘의 날짜는 August 07 입니다. 오늘이 생일인 유명인 3명을 나열해 주세요. 생년월일을 표기해주세요.'
# chain 을 생성.
chain = prompt | llm
# chain에 실제로 n=3 을 삽입 후 결과를 확인합니다.
chain.invoke(3).content
'생일이 August 7 인 유명인 3 명은 다음과 같습니다.\n\n1. 제시카 비엘 (Jessica Biel, 1982년 3월 3 일 ~ ) - 미국의 배우\n2. 마이클 J. 폭스 (Michael J. Fox, 1961년 6월 9 일 ~ ) - 캐나다의 배우\n3. 제니퍼 로렌스 (Jennifer Lawrence, 1990년 8월 15 일 ~ ) - 미국의 배우'
# 오늘 날짜가 아닌 다른 날짜도 삽입해봄
chain.invoke({"today": "Jan 02", "n": 3}).content
'1. 제니퍼 로페즈(Jennifer Lopez, 1969년 7월 24일)\n2. 마이클 J. 폭스(Michael J. Fox, 1961년 6월 9일)\n3. 조지 해리슨(George Harrison, 1943년 2월 25일)'
파일로부터 template 읽어오기
- yaml 파일에 template 구조를 저장하고 불러옴
[yaml 파일 내용]
template: "{fruit}의 색깔이 뭐야?"
input_variables:
- fruit
- 왜 YAML 파일을 사용하는가?
- 가독성: YAML은 사람이 읽기 쉽고 이해하기 쉬운 형식으로, 데이터 구조가 복잡해질수록 유리.
- 주석: YAML 파일에 주석을 추가할 수 있어, 데이터를 설명하거나 문서화하는 데 유용.
- 구조 표현: 계층적 데이터를 직관적으로 표현할 수 있으며, 특히 설정 파일이나 구성 파일에서 자주 사용됩니다.
- 잘못된 방법 : load_prompt("prompts/fruit_color.yaml", encoding="cp949")
- LangChain의 load_prompt 함수는 기본적으로 인코딩을 지정할 수 있는 매개변수를 제공하지 않습니다. 따라서, 파일을 읽을 때 인코딩을 직접 지정할 수 있는 사용자 정의 함수 load_prompt_with_encoding를 작성
from langchain_core.prompts import load_prompt
import yaml
from langchain_core.prompts import PromptTemplate
def load_prompt_with_encoding(file_path, encoding='utf-8'):
from pathlib import Path
file_path = Path(file_path)
if file_path.suffix.endswith((".yaml", ".yml")):
with open(file_path, "r", encoding=encoding) as f:
config = yaml.safe_load(f)
else:
raise ValueError(f"Got unsupported file type {file_path.suffix}")
# PromptTemplate 생성
return PromptTemplate(**config)
# 예시 사용
try:
prompt = load_prompt_with_encoding("study.yaml", encoding='utf-8')
except UnicodeDecodeError:
prompt = load_prompt_with_encoding("study.yaml", encoding='cp949')
prompt.format(fruit="사과")
'사과의 색깔이 뭐야?'
try:
prompt2 = load_prompt_with_encoding("study2.yaml", encoding='utf-8')
except UnicodeDecodeError:
prompt2 = load_prompt_with_encoding("study2.yaml", encoding='cp949')
# 변수를 임시로 삽입해보고 실제로 삽인된건지 확인
prompt2.format(country="대한민국")
chain = prompt2 | llm
chain
# 결과를 보면 삽입 안되있는걸 알수 있음. 삽입은 invoke로
PromptTemplate(input_variables=['country'], template='{country}의 수도에 대해서 알려주세요.\n수도의 특징을 다음의 양식에 맞게 정리해 주세요.\n300자 내외로 작성해 주세요.\n한글로 작성해 주세요.\n----\n[양식]\n1. 면적\n2. 인구\n3. 역사적 장소\n4. 특산품\n\n#Answer:\n')
| ChatOllama(model='llama3.1', temperature=0.0)
result = chain.invoke({"country": "대한민국"})
result.content
'대한민국의 수도는 서울입니다.\n\n**서울의 특징**\n\n1. **면적**: 605.25km² (국토의 약 0.6%를 차지)\n2. **인구**: 약 10,219,000명 (2020년 기준)\n3. **역사적 장소**: 고대 한양시(서울)의 역사를 지닌 곳으로, 삼국시대부터 조선시대까지의 역사와 문화가 살아남은 곳\n4. **특산품**: 서울에는 다양한 특산품이 있습니다. 대표적인 것들은 다음과 같습니다.\n * **한강수**: 한강에서 채집한 수로, 한국의 대표적인 생수입니다.\n * **서울식당**: 다양한 종류의 음식을 제공하는 식당으로, 한국의 대표적인 음식문화를 느낄 수 있는 곳입니다.\n * **명동시장**: 역사와 문화가 살아남은 시장으로, 다양한 물건을 판매하는 곳입니다.'
ChatPromptTemplate
- 대화목록을 프롬프트로 주입하고자 할 때 활용
- 메시지는 튜플(tuple) 형식으로 구성하며, (role, message) 로 구성하여 리스트로 생성
- role ["system": 시스템 설정 메시지. 주로 전역설정과 관련된 프롬프트. - "human" : 사용자 입력 메시지. - "ai": AI 의 답변 메시지]
from langchain_core.prompts import ChatPromptTemplate
chat_prompt = ChatPromptTemplate.from_template("{country}의 수도는 어디인가요?")
chat_prompt.format(country="대한민국")
'Human: 대한민국의 수도는 어디인가요?'
from langchain_core.prompts import ChatPromptTemplate
chat_template = ChatPromptTemplate.from_messages(
[
# role, message
("system", "당신은 친절한 AI 어시스턴트입니다. 당신의 이름은 {name} 입니다."),
("human", "반가워요!"),
("ai", "안녕하세요! 무엇을 도와드릴까요?"),
("human", "{user_input}"),
]
)
# 변수를 넣어서 확인해봄
messages = chat_template.format_messages(
name="테디", user_input="당신의 이름은 무엇입니까?"
)
messages
[SystemMessage(content='당신은 친절한 AI 어시스턴트입니다. 당신의 이름은 테디 입니다.'),
HumanMessage(content='반가워요!'),
AIMessage(content='안녕하세요! 무엇을 도와드릴까요?'),
HumanMessage(content='당신의 이름은 무엇입니까?')]
llm.invoke(messages).content
'내 이름은 테디(Teddy)입니다! 반갑게 만나요!'
chain = chat_template | llm
chain.invoke({"name": "Teddy", "user_input": "당신의 이름은 무엇입니까?"}).content
'내 이름은 테디(Teddy)야! 반가워! 어떻게 할 수 있는지 물어보면 좋겠어!'
MessagePlaceholder
- 프롬프트 템플릿에서 변수를 정의하고, 실제 값을 대입하는 데 사용
- 사용처
- 대화형 AI 응답 생성: 대화형 AI 모델이 사용자 입력에 따라 다양한 응답을 생성해야 할 때, MessagePlaceholder를 사용하여 프롬프트 템플릿을 동적으로 생성할 수 있습니다.
- 자동화된 이메일 또는 보고서 생성: 다양한 변수를 포함하는 자동화된 이메일, 보고서, 알림 등을 생성할 때 유용합니다. 예를 들어, 사용자 이름, 날짜, 특정 데이터 포인트 등을 동적으로 대입하여 문서를 생성할 수 있습니다.
- 데이터 처리 및 분석: 데이터 처리 파이프라인에서 변수를 사용하여 다양한 데이터 포인트를 대입하고, 이를 기반으로 분석 결과를 생성할 수 있습니다.
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# from_messages는 LangChain 라이브러리에서 프롬프트 템플릿을 여러 메시지를 결합하여 생성하는 데 사용
chat_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"당신은 요약 전문 AI 어시스턴트입니다. 당신의 임무는 주요 키워드로 대화를 요약하는 것입니다.",
),
# MessagesPlaceholder는 실제 대화 내용을 대입할 자리 표시자 역할
# variable_name으로 conversation을 지정하여, 실제 대화가 이 위치에 삽입
MessagesPlaceholder(variable_name="conversation"),
# 인간 사용자 메시지로, AI에게 대화를 특정 단어 수로 요약하도록 지시
("human", "지금까지의 대화를 {word_count} 단어로 요약합니다."),
]
)
chat_prompt
ChatPromptTemplate(input_variables=['conversation', 'word_count'], input_types={'conversation': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]]}, messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='당신은 요약 전문 AI 어시스턴트입니다. 당신의 임무는 주요 키워드로 대화를 요약하는 것입니다.')), MessagesPlaceholder(variable_name='conversation'), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['word_count'], template='지금까지의 대화를 {word_count} 단어로 요약합니다.'))])
formatted_chat_prompt = chat_prompt.format(
word_count=5,
conversation=[
("human", "안녕하세요! 저는 오늘 새로 입사한 테디 입니다. 만나서 반갑습니다."),
("ai", "반가워요! 앞으로 잘 부탁 드립니다."),
],
)
formatted_chat_prompt
'System: 당신은 요약 전문 AI 어시스턴트입니다. 당신의 임무는 주요 키워드로 대화를 요약하는 것입니다.\nHuman: 안녕하세요! 저는 오늘 새로 입사한 테디 입니다. 만나서 반갑습니다.\nAI: 반가워요! 앞으로 잘 부탁 드립니다.\nHuman: 지금까지의 대화를 5 단어로 요약합니다.'
# chain 생성
chain = chat_prompt | llm
chain
ChatPromptTemplate(input_variables=['conversation', 'word_count'], input_types={'conversation': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]]}, messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='당신은 요약 전문 AI 어시스턴트입니다. 당신의 임무는 주요 키워드로 대화를 요약하는 것입니다.')), MessagesPlaceholder(variable_name='conversation'), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['word_count'], template='지금까지의 대화를 {word_count} 단어로 요약합니다.'))])
| ChatOllama(model='llama3.1', temperature=0.0)
# chain 실행 및 결과확인
chain.invoke(
{
"word_count": 5,
"conversation": [
(
"human",
"안녕하세요! 저는 오늘 새로 입사한 테디 입니다. 만나서 반갑습니다.",
),
("ai", "반가워요! 앞으로 잘 부탁 드립니다."),
],
}
).content
'테디와 인사하기'
# StrOutputParser()을 추가하고 위의 체인과 비교
chain = chat_prompt | llm | StrOutputParser()
# content를 안써도 똑같은 결과가 나옴
chain.invoke(
{
"word_count": 5,
"conversation": [
(
"human",
"안녕하세요! 저는 오늘 새로 입사한 테디 입니다. 만나서 반갑습니다.",
),
("ai", "반가워요! 앞으로 잘 부탁 드립니다."),
],
}
)
'테디와 인사하기'