ChatGPT Prompt Engineering for Developers

박요셉·2023년 5월 8일
0
post-thumbnail

이번에 들은 강의는 DeepLearning.AI(Beta)에서 무료로 공개한 "ChatGPT Prompt Engineering for Developers"이다.

주소 : https://learn.deeplearning.ai/chatgpt-prompt-eng/lesson/1/introduction

이름에서 알 수 있듯 ChatGPT Prompt를 효과적으로 활용하는 방법에 대한 것이다.

Setup

먼저 Setup을 해야 한다.

import openai
import os

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

openai.api_key  = os.getenv('OPENAI_API_KEY')

openai, os를 import하고 dotenv도 import해준다.

dotenv는 .env 파일을 만들고 API 키를 환경 변수로 추가하여 저장해둔다.

추후 실제로 쓸 때는 'OPENAI_API_KEY' 부분만 바꾸면 된다.

helper function도 만들어준다. 이는 gpt-3.5-turbo를 chat 형식으로 쉽게 쓸 수 있게 만들어준다.

def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0, # this is the degree of randomness of the model's output
    )
    return response.choices[0].message["content"]

Prompting Principles

  • Principle 1 : 명확하게 쓰고 specific instruction을 준다
  • Principle 2 : model에게 "생각" 할 시간을 준다

Tactics

1. Input을 구별하기 위해 delimiter를 써서 명확히 지시한다

여기서 delimiter는 어떤 것도 상관없다. 이 강의에서는 ```, """, < >, , : 를 추천한다.

실제 사용법을 보자.

text = f"""
You should express what you want a model to do by \ 
providing instructions that are as clear and \ 
specific as you can possibly make them. \ 
This will guide the model towards the desired output, \ 
and reduce the chances of receiving irrelevant \ 
or incorrect responses. Don't confuse writing a \ 
clear prompt with writing a short prompt. \ 
In many cases, longer prompts provide more clarity \ 
and context for the model, which can lead to \ 
more detailed and relevant outputs.
"""
prompt = f"""
Summarize the text delimited by triple backticks \ 
into a single sentence.
```{text}```
"""
response = get_completion(prompt)
print(response)

text는 f포맷으로 처리했다. 보기좋게 줄바꿈도 해줬다(결과에는 상관없는듯?).
위의 긴 text를 요약하라는 지시이다.

핵심은 아래이다.
Prompt 부분에서 delimiter 내부의 부분만 처리하라 한다.

결과는 훌륭하다. 여기서 왜 이렇게 처리하라는지 예시를 보여주겠다.

만약

이렇게 주어진다면 어떻게 해야 하는가?


당연히 delimiter 내부의 것도 그냥 "내용"으로 판별해 요약해야 할 것이다.
그렇지만 delimiter가 없었다면 prompt 오류가 생겨날 수 있다.

2. Structured Output을 요구한다

Structured output에는 뭐가 있을까?
대표적으로 JSON, HTML 같은 것이 있을 것이다.

JSON으로 출력을 요구하면 다음과 같다.

prompt = f"""
Generate a list of three made-up book titles along \ 
with their authors and genres. 
Provide them in JSON format with the following keys: 
book_id, title, author, genre.
"""
response = get_completion(prompt)
print(response)


JSON 파일처럼 나왔다! 상당히 알아보기 쉽다.
그래서 structured output을 요구하는 것이다.

3. Condition이 만족되었는지 model에게 물어본다

text_1 = f"""
Making a cup of tea is easy! First, you need to get some \ 
water boiling. While that's happening, \ 
grab a cup and put a tea bag in it. Once the water is \ 
hot enough, just pour it over the tea bag. \ 
Let it sit for a bit so the tea can steep. After a \ 
few minutes, take out the tea bag. If you \ 
like, you can add some sugar or milk to taste. \ 
And that's it! You've got yourself a delicious \ 
cup of tea to enjoy.
"""
prompt = f"""
You will be provided with text delimited by triple quotes. 
If it contains a sequence of instructions, \ 
re-write those instructions in the following format:

Step 1 - ...
Step 2 - …
…
Step N - …

If the text does not contain a sequence of instructions, \ 
then simply write \"No steps provided.\"

\"\"\"{text_1}\"\"\"
"""
response = get_completion(prompt)
print("Completion for Text 1:")
print(response)

여기서 text는 차를 끓이는 방법이다.
Prompt에서 조건을 준다.
1) 만약 단계가 있다면 Step 1, 2,... 형식으로 출력
2) 단계가 없다면 "No steps provided."를 출력

Text 1에 대한 출력은 다음과 같다.

훌륭하다.

이제 단계가 없는 text를 입력으로 넣어보자.

text_2 = f"""
The sun is shining brightly today, and the birds are \
singing. It's a beautiful day to go for a \ 
walk in the park. The flowers are blooming, and the \ 
trees are swaying gently in the breeze. People \ 
are out and about, enjoying the lovely weather. \ 
Some are having picnics, while others are playing \ 
games or simply relaxing on the grass. It's a \ 
perfect day to spend time outdoors and appreciate the \ 
beauty of nature.
"""
prompt = f"""
You will be provided with text delimited by triple quotes. 
If it contains a sequence of instructions, \ 
re-write those instructions in the following format:

Step 1 - ...
Step 2 - …
…
Step N - …

If the text does not contain a sequence of instructions, \ 
then simply write \"No steps provided.\"

\"\"\"{text_2}\"\"\"
"""
response = get_completion(prompt)
print("Completion for Text 2:")
print(response)

풍경을 묘사한 text를 입력으로 넣었다.

우리가 원한대로 나올까?

Excellent. 너무나도 똑똑하다.

이처럼 조건을 주고 판별하게 해주면 더 정확하게 얻을 수 있다.

(사실 마지막에 If the text does not contain a sequence of instructions, \
then simply write \"No steps provided.\" 는 지우고 해도 잘 돌아간다.

알아서 잘 출력했다.
)

4. "Few-shot" prompting

prompt = f"""
Your task is to answer in a consistent style.

<child>: Teach me about patience.

<grandparent>: The river that carves the deepest \ 
valley flows from a modest spring; the \ 
grandest symphony originates from a single note; \ 
the most intricate tapestry begins with a solitary thread.

<child>: Teach me about resilience.
"""
response = get_completion(prompt)
print(response)

이번에는 "few-shot" 기술을 써보자.

성공적인 task example을 주고 이걸 토대로 질문하는 것이다.

Your task is to answer in a consistent style.

즉 아래 답변과 일관되게 대답하라는 것이다.

잘 대답한다. 이는 대답 어조를 바꿀 때도 유용해보인다(ex. 해리 포터처럼 말해줘).

Principle 2 : Give the model time to “think”

즉, 생각할 시간을 주라는 말이다.
대체 어떤 시간?
바로 차례차례, 단계를 짚어가며 설명할 시간을 준다는 것이다.

수학 문제도 한 번에 다 풀려하면 어렵지만, 단계를 쪼개며 풀면 쉽다.
Prompt도 마찬가지다. 단계를 쪼개서 풀어달라고 요청하면 더 잘 나온다.

Tactic 1 : task를 완수하는 step을 명시하라

text = f"""
In a charming village, siblings Jack and Jill set out on \ 
a quest to fetch water from a hilltop \ 
well. As they climbed, singing joyfully, misfortune \ 
struck—Jack tripped on a stone and tumbled \ 
down the hill, with Jill following suit. \ 
Though slightly battered, the pair returned home to \ 
comforting embraces. Despite the mishap, \ 
their adventurous spirits remained undimmed, and they \ 
continued exploring with delight.
"""
# example 1
prompt_1 = f"""
Perform the following actions: 
1 - Summarize the following text delimited by triple \
backticks with 1 sentence.
2 - Translate the summary into French.
3 - List each name in the French summary.
4 - Output a json object that contains the following \
keys: french_summary, num_names.

Separate your answers with line breaks.

Text:
```{text}```
"""
response = get_completion(prompt_1)
print("Completion for prompt 1:")
print(response)

여기서 짧은 이야기를 주고 번역 업무를 맡겼다.

Following actions를 따라 대답하라고 prompt에 입력한다.

그러면 대답이 정확하게 나온다.

특정 포맷으로 답변을 하라고 말할 수도 있다.

prompt_2 = f"""
Your task is to perform the following actions: 
1 - Summarize the following text delimited by 
  <> with 1 sentence.
2 - Translate the summary into French.
3 - List each name in the French summary.
4 - Output a json object that contains the 
  following keys: french_summary, num_names.

Use the following format:
Text: <text to summarize>
Summary: <summary>
Translation: <summary translation>
Names: <list of names in Italian summary>
Output JSON: <json with summary and num_names>

Text: <{text}>
"""
response = get_completion(prompt_2)
print("\nCompletion for prompt 2:")
print(response)

여기서는 following actions를 따라 진행하되, 포맷을 지정한다.
포맷은 다음과 같다
Text : <요약한 텍스트를 출력>
Summary : <요약본을 출력>
Translation : <요약본 번역을 출력>
Names : <Italian 요약본에서 이름을 출력>
Output JSON : <요약과 이름 수를 json 형식으로 출력>

아 참고로 Format 지정에서 굳이 <> 를 쓸 필요는 없더라..

이렇게 마구잡이(줄바꿈, <> 제거)로 지정해도 잘 알아듣는다.
물론 명확하게 알려주면 더 좋다.

Tactic 2 : model이 결론에 도달하기 전에 먼저 스스로의 solution을 내도록 하라

이 말이 무슨 말이냐, 이제 예를 들어 학생의 수학 문제를 채점하는 프로그램이라 하자.
바로 '정답이 맞는지 확인하라'라고 하면 오류가 생길 수 있다.
이를 해결하기 위해 '먼저 답을 내고 학생의 답과 비교해 정답을 확인하라'라고 지시하는 것이다.

아래의 예를 보자.

prompt = f"""
Determine if the student's solution is correct or not.

Question:
I'm building a solar power installation and I need \
 help working out the financials. 
- Land costs $100 / square foot
- I can buy solar panels for $250 / square foot
- I negotiated a contract for maintenance that will cost \ 
me a flat $100k per year, and an additional $10 / square \
foot
What is the total cost for the first year of operations 
as a function of the number of square feet.

Student's Solution:
Let x be the size of the installation in square feet.
Costs:
1. Land cost: 100x
2. Solar panel cost: 250x
3. Maintenance cost: 100,000 + 100x
Total cost: 100x + 250x + 100,000 + 100x = 450x + 100,000
"""
response = get_completion(prompt)
print(response)

질문을 주고, 학생의 답변을 줬다.
여기서 보면 Land cost : 100x / Solar panel cost : 250x까지는 맞다.
그러나 Maintenance cost : 100,000 + 10x인데 100x로 오타를 냈다.
즉 오답이어야 한다(정답은 360x+100,000).

그러나 ChatGPT의 답은 뭘까.

이런 망할!
Correct라고 판별해버린다. 아무래도 조금 혼낼 필요가 있다.

이제 이걸 제대로 지시해보자.

먼저 답을 스스로 도출하게 하고, 그 다음 비교하도록 지시한다.



마크다운 오류가 나서 캡쳐로 대체한다.
자, 여기서 보자.
'following format'을 따라 진행하라 한다.
(참고로 저 '''는 지워도 잘 작동한다.
다만 구분을 위해 붙여주는 것이 나은 듯 하다.)
차근차근, 먼저 student의 정답이 뭔지 기억시킨다.
그 다음 인공지능이 풀도록 시킨다.
풀었다면 그 답을 비교해 점수를 낸다.

일반적으로 학생을 가르치듯 말해주면 된다.

결과는 잘 나온다.

Model Limitations(한계) : Hallucinations(그럴듯한 거짓말)

번역은 내가 그럴싸하게 붙여봤다. 원뜻은 '환각'이다.
즉 없는 내용을 아주 그럴싸하게 말하는 것이다.

Boie라는 회사는 있지만 저런 제품은 없다.
GPT-4부터는 수정된걸로 아는데, GPT-3.5인 ChatGPT는 이걸 감지하는 능력이 없다.

따라서 이런 '그럴듯한 거짓말'을 피하기 위해서는 이중 검증이나 사전 지식이 꼭 필요하다.

+) Hallucination(한국어로는 할루시네이션이라 한다) 밈이 상당히 많다.

코드를 직접 짜달라해서 실제로 돌려보면 오류도 자주 난다.
역시 아직은 인간이 이겼다.. 로봇.

Other Lectures

사실 이 뒤에 더 강의가 있다.
그렇지만 듣다보니 이렇게까지 열심히 정리할 필요는 없는 것 같아 요점만 정리할 것이다.
이 강의는 기초적 개념 설명이 주요한 포인트고, 실무적인걸 원한다면 "GPT Prompt examples best 10" 같이 검색해보면 될 것 이다.

Iteratives


Iterative Prompt Development의 단계는 다음과 같다.
1) Idea
2) Implementation prompt
3) Experimental result
4) Error analysis
즉 아이디어 적용 -> 직접 시행 -> 결과 -> 분석 이다.

여기서 사용된 예제는 다음과 같다.
어떤 의자에 대한 정보를 주고, Marketing Product Description을 써달라 했다.

자 이제 이걸 고쳐보자.

1. 너무 길다

줄여달라 한다. words(단어) 대신에 characters(글자), paragraphs(문단) 등으로 조절 가능하다.
(해보니 문단은 영... 마음에 안 들더라..)

2. 포커싱 맞추기

포커싱을 기술적 부분에 맞춰달라고 한다. 해결 완료!

3. 표 만들기

잘 보이게 표로 만들게 한다.

해결 완료!

Summarizing

이제 ChatGPT로 요약을 해보자.
요약/번역이 가장 자주 쓰이는 기능이 아닐까.. 생각해본다.
당장 논문 요약만 해도 능률이 엄청나게 올라간다.

시작해보자.

1. 글자수 제한해서 요약
짧은 글 요약에 유용하다.

2. 포커싱 맞춰서 요약
원하는 내용에 포커싱을 맞출 수 있다.

3. 요약 대신 정보 "추출"해보기
전체 내용 대신, 원하는 내용만 뽑아보자.

4. 다양한 내용 한 번에 요약
다양한 내용을 뽑아 한 번에 요약해보자.

여담이지만.. ChatGPT는 Token(대충 단어수라 생각하면 된다)에 비례해 요금을 부과한다.
논문 여러 편을 넣으면 단어수가 어마어마해지니...
Local LLM이 하루빨리 최적화되길 기도해야겠다.

Inferring

추론이다. 기존 GPT들은 추론에 약했는데 ChatGPT는 어느정도 우수한 능력을 보여준다.
추론 문제에 ChatGPT를 써먹어보자.

1. 감정 추출
글을 주고 이 글이 내는 감정을 알 수 있다.

2. 감정 종류
이 글에서 나오는 감정들을 보여달라 할 수 있다. 여러 개의 감정이 동시에 표현된다.

3. 감정 확률
이 감정을 가지고 있을 확률을 나타내준다.

4. Topic 정리
글에서 가진 Topic을 정리해준다.

5. Topic 알람
내가 원하는 topic의 article이 등장하면 알려준다.

사실 4, 5는 Summarizing의 영역이라 봐도 될 것 같다. 딱 잘라 나누기 힘든 분야라 그냥 넣은듯..

Transforming

text의 형태를 바꾸는 작업이다.
작게 보면 번역, 크게 보면 어투, 어조, 형식을 바꾸는 것이다.

1. 번역
원하는 글 번역이 가능하다. 번역 시 말투도 설정할 수 있다.
Formal : 공식적인
Informal : 비공식적인

번역 응용 ver.

여러 개로 동시 번역 가능하다.

2. Tone 조절
글의 어투를 조절할 수 있다.
여기서는 informal -> formal로 바꿨다.
양식도 "business letter"와 같이 지정 가능하다.

3. 데이터 형식 변환
데이터 형식도 바꿀 수 있다.
예시는 JSON -> HTML table이다.

4. Spell/Grammar Check
문법, 단어 수정이다.

이런 식으로 RedLine 라이브러리로 잘 보여줄 수도 있다.
다만 대체 왜 마지막 daughter. 가 틀린지는 모르겠다.

핵심은 proofread and correct이다.
혹은 여기다가 rewrite corrected version을 붙여 수정본도 써달라 할 수 있다.

Expanding

Expanding은 short piece of text로 text를 generate하는 과정이다.

예시는 AI assistant로 고객 메일에 답장하는 prompt 작성이다.

만일 AI assistant로만 쓸 LLM이라면, 처음에 설정값을 이걸로 줘도 된다.

이 대답을 좀 더 디테일하게 해보자.
Input으로 감정값을 주면 된다.

감정은 앞서 "Inferring"에 나온대로 추출해도 좋다(여기서는 그냥 줌).

Temperature을 조절해 다양한 답변을 만들 수 있다.
(0.0 이면 딱 정해진 확률의 최댓값만, 1.0에 가까울수록 다양성 높임)

ChatBot

이제 배운 내용을 토대로 주문을 받는 ChatBot을 만들어보자.

먼저 Chat을 받는 형식부터 바꿔야 한다.

기존 방식

def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0, # this is the degree of randomness of the model's output
    )
    return response.choices[0].message["content"]

즉, 답변을 하나만 받는다.

이걸 여러 대화를 처음부터 받게 하려면

새로운 방식

def get_completion_from_messages(messages, model="gpt-3.5-turbo", temperature=0):
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=temperature, # this is the degree of randomness of the model's output
    )
#     print(str(response.choices[0].message))
    return response.choices[0].message["content"]

messages에서 prompt를 받게 한다.

활용은 다음과 같다.


이런 대화를 할 때, chatGPT의 반응은 어떨 지 알 수 있다.
role : system 으로 역할을 부여한다.


이름을 맥락에서 주지 않으면 ChatBot은 모른다.
따라서 이용하고 싶은 정보가 있다면 기존 input으로 넣어줘야 한다.

이제 ChatBot을 만들어보자.

def collect_messages(_):
    prompt = inp.value_input
    inp.value = ''
    context.append({'role':'user', 'content':f"{prompt}"})
    response = get_completion_from_messages(context) 
    context.append({'role':'assistant', 'content':f"{response}"})
    panels.append(
        pn.Row('User:', pn.pane.Markdown(prompt, width=600)))
    panels.append(
        pn.Row('Assistant:', pn.pane.Markdown(response, width=600, \
        		style={'background-color': '#F6F6F6'})))
 
    return pn.Column(*panels)

먼저 메세지를 받는 함수이다.
Input을 받고, Response를 출력한다.
진짜 ChatBot처럼 보이게 하기 위해 panel 라이브러리를 썼다.

import panel as pn  # GUI
pn.extension()

panels = [] # collect display 

context = [ {'role':'system', 'content':"""
You are OrderBot, an automated service to collect orders for a pizza restaurant. \
You first greet the customer, then collects the order, \
and then asks if it's a pickup or delivery. \
You wait to collect the entire order, then summarize it and check for a final \
time if the customer wants to add anything else. \
If it's a delivery, you ask for an address. \
Finally you collect the payment.\
Make sure to clarify all options, extras and sizes to uniquely \
identify the item from the menu.\
You respond in a short, very conversational friendly style. \
The menu includes \
pepperoni pizza  12.95, 10.00, 7.00 \
cheese pizza   10.95, 9.25, 6.50 \
eggplant pizza   11.95, 9.75, 6.75 \
fries 4.50, 3.50 \
greek salad 7.25 \
Toppings: \
extra cheese 2.00, \
mushrooms 1.50 \
sausage 3.00 \
canadian bacon 3.50 \
AI sauce 1.50 \
peppers 1.00 \
Drinks: \
coke 3.00, 2.00, 1.00 \
sprite 3.00, 2.00, 1.00 \
bottled water 5.00 \
"""} ]  # accumulate messages


inp = pn.widgets.TextInput(value="Hi", placeholder='Enter text here…')
button_conversation = pn.widgets.Button(name="Chat!")

interactive_conversation = pn.bind(collect_messages, button_conversation)

dashboard = pn.Column(
    inp,
    pn.Row(button_conversation),
    pn.panel(interactive_conversation, loading_indicator=True, height=300),
)

dashboard

물론 panel 만드는 것도 유심히 봐야하지만, 우리가 볼 것은 prompt input이다.
Context를 system의 입장에서 준다.
일 처리 방식을 알려주고, 메뉴판을 주는 것이다.

참.. 예전에 ChatBot 만들기에 비해 난이도가 훨~~씬 쉬워진 것 같다.
Hardcoding 시절에는 일일이 답변을 다 만들어야했지만..


대답 잘하는 것을 알 수 있다.

강의 총평

1시간 내로 볼 수 있는 가벼운 강의이다.
그렇지만 내용은 상당히 유용하다.
아마 chatGPT를 처음 쓰면 "~~를 짜줘" 같이 단순히 물어보는 경우가 많다.
하지만 그렇게 하면 능력을 50%도 못 쓰는 것이다.
100%, 120% 쓸려면 이렇게 정확한 prompt 작성법을 알아야 한다.

ChatGPT Prompt에 관심이 있다면 추천한다.

profile
개발 폐관수련중, ML, DL 무림 초보

0개의 댓글