인공지능/RAG

LangChain (4) Prompt Template

BangPro 2024. 3. 23. 20:19
728x90

Prompt란

Prompt란 모델에 대한 입력. 이 입력이 하드 코딩되는 경우는 거의 없지만 여러 구성요소로 구성되는 경우가 많다.
-> 그냥 프롬프트는 챗지피티 사이트에서 적는 말
PromptTemplate은 이 입력의 구성을 담당한다. LangChain은 Prompt를 쉽게 구성하고 작업할 수 있게 여러 클래스와 함수를 제공한다.
특정 기능을 사용할때 사용자들이 반복적으로 입력해야하는 문장이 있는데 해당 문장을 중복 작성할 필요 없게 구조화하는 방법이다.
예를 들어, 운동 루틴을 짜는 경우 하고싶은 동작, 시간, 부위를 전달하면 루틴을 짜줄텐데 매번 귀찮게 목적을 설명하는 과정없이 동작, 시간, 부위만 입력하면 운동 루틴 짜주게 하는 것이다.

(1) 실습을 위한 라이브러리 설치

오늘도 어김없이 라이브러리를 설치한다.

!pip install -q openai langchain langchain-openai
!pip install -q load_dotenv
!pip show langchain

라이브러리를 import 해준다

import langchain
import load_dotenv
import os,dotenv
dotenv.load_dotenv()

langchain version 확인

langchain.__version__
API_KEY = os.getenv("OPENAI_API_KEY")

(2) Prompt Template

📌프롬프트 템플릿은 크게 2가지가 존재한다.
    1. PromptTemplate
    2. ChatPromptTemplate
이 둘은 사용하는 모델이 일반적인 모델인지, chat 모델인지에 따라 다르다.
1번 PromptTemplate은 일반적인 프롬프트를 생성할때 활용한다.
2번 ChatPromptTemplate은 채팅 LLM에 프롬프트를 전달하는데 활용할 수 있는 특화 프롬프트 템플릿이다.

PromptTemplate 맛보기

템플릿 없이 출력을 만든다면 아래와 같다.

from langchain_openai import OpenAI
llm = OpenAI()
result = llm.invoke("왜 파이썬이 가장 인기있는 프로그래밍 언어야?")
print(result)
!pip install icecream

아래와 같이 코드를 짜면 사용할 수 있다.

from icecream import ic
from langchain.prompts import PromptTemplate,ChatPromptTemplate
#프롬프트 템플릿을 통해 매개변수 삽입 가능한 문자열로 변경
string_prompt = PromptTemplate.from_template("tell me a joke about {subject}") # 중괄호 내의 내용만 가변적

#매개변수 삽입한 결과를 string_prompt_value에 할당
string_prompt_value = string_prompt.format_prompt(subject="soccer")

ic(string_prompt_value)
ic(string_prompt_value.to_string());

PromptTemplate.from_template을 통해 템플릿을 만들고 템플릿
안에 변수로 들어갈 값은 .format_prompt(subject="")를 통해 설정할 수 있다.

ChatPromptTemplate

1번

chat_prompt = ChatPromptTemplate.from_template("tell me a joke about {subject}")
chat_prompt_value = chat_prompt.format_prompt(subject = "soccer")
ic(chat_prompt_value)
ic(chat_prompt_value.to_string());

2번

from langchain.schema import AIMessage, HumanMessage, SystemMessage
prompt = SystemMessage(content="You are a nice pirate")
new_prompt= (
    prompt + HumanMessage(content="hi")+AIMessage(content="what")+"{input}"
    )
new_prompt.format_messages(input="i said hi")

아래와 같이 chain을 활용해서 답변을 받을수도 있다.

from langchain.chains import LLMChain
from langchain_openai import ChatOpenAI
model = ChatOpenAI()
chain = LLMChain(llm = model,prompt = new_prompt)
chain.run("i said hi")#invoke 

PromptTemplate 활용해보기

반복적인 프롬프트를 삽입하는 경우, PromptTemplate으로 간편하게 LLM을 활용할 수 있다.

from langchain.prompts import PromptTemplate

template = """
너는 요리사야. 내가 가진 재료들을 갖고 만들 수 있는 요리를 추천하고, 그 요리의 레시피를 제시해줘.
내가 가진 재료는 아래와 같아.

<재료>
{재료}

"""

prompt_template = PromptTemplate(
    input_variables = ['재료'],
    template=template
)

prompt_template

위와 같이 template을 구성할 수 있다. input_variables에 재료를 리스트 형태로 잡아주는데 이렇게 하면 재료에 여러 값들이 들어갈 수 있다.

print(prompt_template.format(재료="양파,계란,사과,빵"))

format을 통해 input_variables에 들어가는 값을 지정할 수 있다.
위 코드의 실행 결과를 보면 template 내에 {재료} 부분에 내가 추가한 재료들이 들어가있다.

model = ChatOpenAI(model_name = "gpt-3.5-turbo",max_tokens=512)
chain = LLMChain(llm=model,prompt=prompt_template)
answer=chain.run(재료='양파,계란,사과,빵')

print(answer)

LLMChain을 통해 답변을 받으면 우리가 원하는 답변을 출력하는 것을 확인할 수 있다.

(3) Few-shot 예제를 통한 프롬프트 템플릿

Few-shot이란, 딥러닝 모델이 결과물을 출력할 때 예시 결과물을 제시함으로써 원하는 결과물로 유도하는 방법론입니다.

LLM 역시, Few-shot 예제를 제공하면 예제와 유사한 형태의 결과물을 출력합니다.

내가 원하는 결과물의 형태가 특수하거나, 구조화된 답변을 원할 경우, 결과물의 예시를 수 개 제시함으로써 결과물의 품질을 향상시킬 수 있습니다.

Few Shot Prompt Template 예제

from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts.prompt import PromptTemplate

examples = [
    {
        "question" : "아이유로 삼행시 만들어줘",
        "answer":
"""
아 : 아이유는
이 : 이런 강의를 들을 이
유 : 유가 없다.
"""
    },

    {"question": "김민수로 삼행시 만들어줘",
     "answer":
"""
김 : 김치는 맛있다
민 : 민달팽이도 좋아하는 김치!
수 : 수억을 줘도 김치는 내꺼!
"""
     }
]

다음과 같이 몇가지 예제를 줘서 GPT가 원래 하지 못했던 대답을 하게 하거나 원하는 답변을 할 수 있도록 할 수 있다.
examples 리스트에 "question","answer"를 딕셔너리 타입으로 저장할 수 있다. 이를 통해 여러 개의 예제를 전달할 수 있다.

example_prompt = PromptTemplate(input_variables = ["question","answer"],template="Question: {question}\n{answer}")

print(example_prompt.format(**examples[0]))

위 코드로 examples를 살펴볼 수 있다.

Few Shot Template 선언

prompt = FewShotPromptTemplate(
    examples = examples,
    example_prompt=example_prompt,
    suffix = "Question: {input}",
    input_variables=["input"]
)

print(prompt.format(input="호날두로 삼행시 만들어줘"))
  • suffix는 prompt의 뒤에 "Question: {input}"을 고정해줌
  • 앞에서 넣어준 examples도 포함된 프롬프트를 전달한다.
prompt.format(input="호날두로 삼행시 만들어줘")

Zero Shot

print(model.invoke("호날두로 삼행시 만들어줘"))

실행 결과를 살펴보면 few shot을 제공하지 않은 경우 답변을 제대로 못한다.

Few Shot

print(model.invoke(prompt.format(input="호날두로 삼행시 만들어줘")))

우리가 원하는대로 삼행시를 만들어주는 것을 확인할 수 있다.

(4) Example Selector를 이용한 동적 Few-shot 러닝

Few-shot 예제를 동적으로 입력하고 싶은 경우, Example Selector를 활용할 수 있습니다.
LLM이 여러 작업을 수행하도록 만들되 내가 원하는 범위의 대답을 출력하도록 하려면 사용자의 입력에 동적으로 반응해야합니다.
이와 동시에, 예제를 모두 학습시키는 것이 아니라 적절한 예시만 포함하도록 함으로써 입력 프롬프트의 길이를 제한하고,
이를 통해 오류가 발생하지 않도록 조절할 수 있습니다.

패키지 설치

!pip install chromadb
!pip install tiktoken

Example Selector

from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain.vectorstores import Chroma 
from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import FewShotPromptTemplate, PromptTemplate

example_prompt = PromptTemplate(
    input_variables=["input","output"],
    template="input: {input}\nOutput: {output}",
)

#These are a lot of example of task of creating antonyms
examples = [
    {"input": "행복", "output": "슬픔"},
    {"input": "흥미", "output": "지루"},
    {"input": "불안", "output": "안정"},
    {"input": "긴 기차", "output": "짧은 기차"},
    {"input": "큰 공", "output": "작은 공"},
]
  • example_selector는 사용자의 질문과 유사한 예시를 가져온다.
  • Chroma는 vectorstore를 통해서 임베딩된 값을 비교하게 해준다.
  • Langchain 임베딩/ openai 임베딩을 통해 문자를 벡터화 하고
  • Langchain prompt를 통해서 fewshotPromptTemplate, PromptTemplate을 만들게 된다.
example_selector = SemanticSimilarityExampleSelector.from_examples(
    #This is the list of examples available to select from.
    examples,
    #This is the Embedding class used to produce embeddings which are used to measure semantic similarity.
    OpenAIEmbeddings(),
    #This is the VectorStore class that is used to store the embeddings and do a similarity search over.
    Chroma,
    #This is the number of examples to produce.
    k=1
)
similar_prompt = FewShotPromptTemplate(
    #We provide an ExampleSelector instead of examples.
    example_selector=example_selector,
    example_prompt = example_prompt,
    prefix="주어진 입력에 대해 반대의 의미를 가진 단어를 출력해줘",
    suffix="Input: {단어}\nOutput:",
    input_variables=["단어"],
)
  • example_selector를 통해 가장 유사성이 높은 예제를 가져오고 example_prompt는 위에서 설정한 것처럼 input, output 들어간 부분을 넣어준다.
  • pefix는 맨 앞에서 고정할 부분을 추가하고
  • suffix는 뒤에 고정할 부분이다.
  • similar_prompt는 FewShotPromptTemplate을 통해서 prefix와 suffix로 고정된 부분중 input_variable에 해당하는 부분과 가장 유사한 example을 example_selector를 통해서 가져오고 example_prompt를 통해서 하나로 묶어준다.

감정 비교

# Input is a feeling, so should select the happy/sad example
print(similar_prompt.format(단어="무서운"))

대소 비교

# Input is a feeling, so should select the happy/sad example
print(similar_prompt.format(단어="큰 비행기"))
query = "큰 비행기"
model = OpenAI(model_name = "gpt-3.5-turbo-instruct",max_tokens=512)
print(model(similar_prompt.format(단어=query)))

무서운의 경우 감정적으로 반대에 해당하는 단어를 출력해야하고 큰 비행기의 경우에는 대소비교를 통해서 단어를 출력해야한다.
이런식으로 단어를 유도해야할때 참고해야하는 예제가 다를 수 있다. 따라서, 이런 목적을 달성하기 위해서 존재하는게 example_selector이다.
동적으로 few-shot learning을 수행한다.
내가 원하는 답변을 출력하도록 하려면 사용자가 어떤 질문을 했느냐에 따라서 다른 example을 넣어줘야한다. 그리고 예제를 모두 학습시키는게 아니라 적절한 예제만 학습하게 해서 입력되는 프롬프트가 너무 길지 않고 오류가 발생하지 않도록 조절해야한다.

(5) Partial Prompt Template

from datetime import datetime
def _get_datetime():
    now = datetime.now()
    return now.strftime("%Y/%d/%m")
prompt = PromptTemplate(
    template="Tell me a {adjective} joke about the day {date}",
    input_variables=["adjective","date"],
)
partial_prompt = prompt.partial(date=_get_datetime)
print(partial_prompt.format(adjective="funny"))
prompt = PromptTemplate(
    template="Tell me a {adjective} joke about the day {date}",
    input_variables=["adjective"],
    partial_variables={"date":_get_datetime},
)
print(prompt.format(adjective="funny"))

(6) Output Parser를 활용한 출력값 조정

LLM의 답변을 내가 원하는 형태로 고정하고 싶다면 Outputparser 함수를 활용할 수 있다. 리스트, JSON 형태 등 다양한 형식의 답변을 고정하여 출력할 수 있습니다.

from langchain.output_parsers import CommaSeparatedListOutputParser
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain_openai import OpenAI, ChatOpenAI

CommaSeparatedListOutputParser를 통해서 콤마로 구분된 리스트형태로 출력을 받을 수 있다.

output_parser = CommaSeparatedListOutputParser()
format_instructions = output_parser.get_format_instructions()
format_instructions
prompt = PromptTemplate(
    template = "{주제} 5개를 추천해줘.\n{format_instructions}",
    input_variables=["주제"],
    partial_variables={"format_instructions":format_instructions}
)
  • input_variables는 사용자가 입력하는 부분, 사용자가 수정 가능한 부분
  • partial_variables : 위에서 선언한 format_instruction을 넣어준다.
  • model = OpenAI(temperature=0) _input = prompt.format(주제="영화") output = model(_input) output
output_parser.parse(output)

위에서 설정한 output parser를 기준으로 parse를 하면 리스트로 감싸진 형태로 얻을 수 있다.

Reference

모두의 AI 유튜브 채널[https://www.youtube.com/@AI-km1yn]