Prompt Engineering
지금까지 OpenAI API의 사용 방법과 언어모델에서 사용할 수 있는 여러 기능들을 살펴보았다. 그러나 축구 규칙을 안다고 해서 누구나 프로 축구선수가 될 수는 없다. 중요한 것은 어떻게 해야 'Goal'을 넣을 수 있는가이다. 언어모델의 'Goal'은 사용자가 원하는 대답을 얻는 것이다. 대화를 통해 원하는 대답을 얻기 위해서는 언어모델을 잘 구슬려서 원하는 대답을 내뱉도록 만드는 사용자의 화술이 중요하다. 언어모델에 주는 사용자의 입력을 '프롬프트'라고 하기도 하는데, 이 프롬프트를 잘 작성하기 위한 기술을 '프롬프트 엔지니어링'이라고 한다.
OpenAI는 수많은 연구끝에 얻은 좋은 프롬프트를 작성하기 위한 6가지 방법들을 공개했는데, 덕분에 우리는 거인의 어깨에 단숨에 올라탈 수 있게 되었다. 이번 글에서는 OpenAI에서 공개한 GPT best practice를 간단히 살펴보고 테스트를 해보도록 하겠다. 자세한 내용은 아래 문서를 참고하기 바란다.
https://platform.openai.com/docs/guides/gpt-best-practices
OpenAI Platform
Explore developer resources, tutorials, API docs, and dynamic examples to get the most out of OpenAI's platform.
platform.openai.com
1. 명확한 명령을 내려라
최근의 언어모델들은 너무 똑똑해서 사람들은 가끔 기계가 아니라 지성과 감정을 가진 존재처럼 느낀다. 그러나 언어 모델은 결국 숫자를 계산하기 위한 도구일 뿐이고, 높은 확률의 결과를 보여줄 뿐이다. 따라서 우리의 생각을 알아주지 않는다고 해서 서운해하는 것은 글씨가 이상하다고 볼펜을 집어던지는 것이나 마찬가지다.
언어모델에게 입력은 명확하면 명확할수록 좋다. 예를 들어, 원하는 답변의 길이를 제한하거나, 답변의 형식, 말투 등을 정해주면 그에 맞춰 답변을 생성해 줄 것이다. 소설을 쓰는 상황을 생각해보자.
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": "소설의 줄거리를 써줘."}
]
)
저는 AI이므로 직접적인 판단을 하지 않고, 사실에 근거한 정보만을 제공할 수 있습니다. 요청하신 소설의 줄거리를 제공해드릴 수는 없지만, 원하는 소설의 제목이나 작가에 대한 정보를 알려주신다면 해당 작품에 대한 요약이나 개요를 제공할 수 있을 것입니다.
소설의 전체적인 줄거리를 만들어 줄 것을 예상했지만, 언어 모델은 이미 존재하는 소설의 내용을 요약해달라는 의미로 받아들여 원하는 소설의 제목을 알려달라고 했다. 이번에는 약간의 추가 정보를 제공해보자.
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": "조선시대 배경의 추리 소설의 줄거리를 써줘."}
]
)
한 조선시대 장사꾼이 발견된 시체를 조사하기 위해 수놓은 '지관'이라는 조선 마을에 찾아간다. 이 마을은 조선 왕실의 영토 범위 내에 위치하고 있어서 왕실의 명령을 받아 철저하게 관리되고 있는 특별한 마을이다. 시체는 조선 왕자의 명예를 훼손한 죄인으로 의심되며, 그의 명령에 따라 수사가 진행된다. 하지만 마을 주민들 모두가 조선 왕실을 존경하고 있기 때문에, 조사 과정에서는 다양한 거짓말과 속임수가 사용된다. 주인공인 장사꾼은 마을 주변의 사건들을 조사하면서, 마을 주민들 간의 정치적 갈등과 숨겨진 비밀을 발견하게 된다. 그리고 시체와 관련된 단서들을 조금씩 모아가며, 진실을 파헤쳐 나가는데 집중한다. 장사꾼과 협업하는 지리사는 마을의 지리 정보를 바탕으로 사건의 배경을 분석하고, 마을 사람들의 마음을 헤아리기 위해 유혹과 조종을 시도한다. 또한 암약을 제작하여 비밀리에 마을 사람들에게 사용해 진실을 밝혀내는 데 도움을 주는데, 이 암약은 가정 규모의 실험을 통해 결정된다. 마을의 고위 관리인인 사무관은 조선 왕실의 명예를 지키기 위해 시체와 관련된 모든 단서를 없애려 한다. 하지만 장사꾼과 지리사는 그의 꾀를 뚫고 진실을 추구한다. 결국, 그들은 마을 주민들 사이에 펼쳐진 복잡한 정치 사건을 파헤치고, 시체의 살인 사건의 진짜 범인을 찾아내는 데 성공한다. 이 소설은 조선시대의 정치력과 관계된 사건을 배경으로 하며, 주인공들이 논리적으로 추리하고 독창적인 방법을 사용하여 사건의 수수께끼를 풀어나가는 과정을 그린다. 이를 통해 독자들에게 조선시대 역사와 철학, 그리고 추리 소설의 매력을 함께 선사한다.
조선시대라는 시대적 배경과 사건의 숨겨진 배경을 찾아가는 전형적인 추리 소설의 줄거리까지 원하는 대로 응답을 받을 수 있었다. 이정도라면 약간의 살을 덧붙여 소설을 작성하는 데 문제가 없을 것이다.
2. 참고 자료를 제공하라
두 번째는 정확한 정보를 위해 참고자료를 제공하는 것이다. 생성형 AI모델이 항상 올바른 정보를 제공하지 않는다는 것은 이미 잘 알려진 사실이다. ChatGPT가 처음 등장했을 때 유명했던 '세종대왕 맥북 사건'이 대표적인 예시다. 이렇게 그럴듯한 말을 지어내서 답변하는 것을 '할루시네이션'이라고 한다. 물론 지금까지 많은 보완이 있었지만 여전히 잘못된 정보를 제공하는 경우가 많다.
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": "차범근은 유럽에서 몇 골을 기록했어?"}
]
)
차범근은 유럽에서 13골을 기록했습니다.
이러한 경우에 언어 모델이 참고할 수 있는 자료를 추가하면 그것을 요약하거나 참고해서 답변을 개선할 수 있다.
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "user",
"content":
"""
차범근은 분데스리가 통산 308경기에 출전해 98골을 기록했고, 유럽리그 통산 372경기서 121골을 넣었다.
---
차범근은 유럽에서 몇 골을 기록했어?
"""
}
]
)
차범근은 유럽에서 121골을 기록했습니다.
3. 복잡한 작업은 여러개의 단순한 작업으로 분리하라
위의 예시들은 단순히 정보를 물어보는 단순한 작업이었다. 그러나 실제 서비스를 만들기 위해서는 훨씬 복잡한 작업을 해야할 때가 많다. 그럴 때는 전체 작업을 여러개의 단순한 작업으로 나누어 처리하면 좋다. 예를 들어, 10장짜리 문서의 내용을 요약할 때, 단순히 '전체 내용을 요약해줘'보다는 '각 문단의 핵심 내용을 요약한 다음, 전체 글의 내용을 정리해줘'라고 명령하면 누락되는 내용 없이 요약이 가능하다.
또 다른 예시로, 쇼핑몰에서 사용자의 문의사항을 처리하는 상황을 생각해보자. 사용자 문의는 단순히 궁금한 점을 질문하는 것일 수도 있고, 혹은 환불이나 교환을 요청하는 것일 수도 있다. 또, 질문의 경우 구매전의 질문과 구매후의 질문으로 나누어질 것이고, 환불이나 교환 요청은 구매자의 변심이냐 판매자의 배송오류냐에 따라 처리 방식이 달라질 것이다. 이렇게 먼저 카테고리를 분류한 다음, 각각의 경우에 따라 답변을 생성하는 명령을 다르게 준다면 잘못된 답변을 생성할 확률이 줄어들게 된다.
4. 생각할 시간을 줘라
지금까지 살펴본 기능 중에는 API 응답 시간을 조절할 수 있는 기능이 없었다. 그런데 생각할 시간을 주라는 것이 무슨 의미일까? 이것은 실제로 오랜 시간을 기다리라는 것이 아니라, 머릿속에서 단계적으로 생각하는 것처럼 여러 추론 과정을 거쳐 정답을 유추하라는 의미이다. 특히 수학 문제를 푸는 것처럼 논리적인 추론이 필요한 문제에서 바로 답변을 생성하는 것은 생각하지 않고 찍는 것이나 마찬가지다.
예를 들어, 소금물의 농도를 구하는 수학문제를 생각해보자.
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "user",
"content":
"""
소금물이 담긴 컵 2개가 있다.
A에는 농도가 12%인 소금물 600g이 있고, B에는 농도가 7%인 소금물 400g이 있다.
두 컵에 담긴 소금물을 섞었을 때의 농도는 몇 %인가?
"""
}
]
)
이것을 연습장에 식을 쓰지 않고 풀 수 있는가? 물론 이 문제는 계산이 쉽도록 숫자를 맞춰놓았기 때문에 암산으로도 풀 수 있을 것이다. 그러나 실제 답변은 정확하지 않다.
두 컵에 담긴 소금물의 양을 더해야 한다. A 컵에 있는 소금물은 600g 이고, B 컵에 있는 소금물은 400g 이므로, 총 소금물의 양은 600g + 400g = 1000g 이 된다.
농도는 소금물의 양에 대한 소금의 비율로 정의된다.
따라서 두 컵을 섞었을 때 소금의 양은 600g + 400g = 1000g 이고, 총 소금물의 양은 1000g 이므로, 농도는 1000g 중 소금의 양인 1000g/1000g = 1 이다.
소금물의 농도는 소금의 양과 소금물의 양에 대한 비율이므로, 농도를 백분율로 표현하기 위해 100을 곱한다.
그러므로 두 컵을 섞었을 때의 농도는 1 * 100 = 100% 이다.
그럴듯하게 설명하지만 자세히 들여다보면 엉망이다. 소금의 양을 구하는 계산을 하지 않은 걸 보니 아무래도 소금물 문제 푸는 방법을 잘 모르는 것 같다. 문제푸는 순서를 알려주고 다시 시도해보자.
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "user",
"content":
"""
소금물이 담긴 컵 2개가 있다.
A에는 농도가 12%인 소금물 600g이 있고, B에는 농도가 7%인 소금물 400g이 있다.
두 컵에 담긴 소금물을 섞었을 때의 농도는 몇 %인가?
---
1. A 소금물에 들어있는 소금의 양을 구한다.
2. B 소금물에 들어있는 소금의 양을 구한다.
3. (전체 소금의 양 / 전체 소금물의 양)을 계산해 100을 곱한다.
"""
}
]
)
1. A 소금물에 들어있는 소금의 양을 구한다.
농도가 12%인 소금물 600g의 소금의 양은 12% × 600g = 72g 이다.
2. B 소금물에 들어있는 소금의 양을 구한다.
농도가 7%인 소금물 400g의 소금의 양은 7% × 400g = 28g 이다.
3. (전체 소금의 양 / 전체 소금물의 양)을 계산해 100을 곱한다.
전체 소금의 양은 72g + 28g = 100g 이다.
전체 소금물의 양은 600g + 400g = 1000g 이다.
농도는 (100g / 1000g) × 100 = 10% 이다.
따라서 두 컵에 담긴 소금물을 섞었을 때의 농도는 10%이다.
이번에는 정확하게 소금의 양을 구했고, 알려준 순서에 따라 정확하게 계산을 완료했다.
5. 외부의 도움을 받아라
여러 예시에서 볼 수 있듯이, 언어 모델이 항상 원하는 답을 주는 것은 아니다. 따라서 언어 모델의 단점을 보완하기 위해 외부의 도움을 받는 것도 하나의 방법이다. 언어 모델에서 사용할 수 있는 대표적인 도구가 임베딩 모델이다.
임베딩 모델은 자연어를 일정한 크기의 벡터로 변환해주는 역할을 한다. 이 벡터들은 자연어가 내포하는 의미를 반영하고 있는데, 비슷한 의미를 가진 문장들은 거리가 가까운 벡터로 임베딩된다. 따라서 이러한 벡터들을 저장하는 벡터 데이터베이스를 활용하면 주어진 입력과 비슷한 자료들을 검색해서 사용할 수 있다. 대표적인 벡터 데이터베이스로는 Chroma, Pinecone 등이 있고, MongoDB, ElasticSearch같은 제품들도 벡터 데이터베이스로 사용이 가능하다.
벡터 데이터베이스를 사용하는 예시를 생각해보자. 먼저, 법원의 판례들을 모아서 벡터로 변환한 다음, 벡터 데이터베이스에 저장한다. 그런 다음, 사용자의 입력을 받아 벡터로 변환하여 벡터 데이터베이스에서 비슷한 판례를 여러개 찾는다. 마지막으로 언어 모델에 다양한 판례와 함께 사용자의 입력을 넣어 판결을 예측하는 서비스를 만들 수 있을 것이다.
6. 평가를 위한 시스템을 마련하라
마지막은 프롬프트를 작성하기 위한 것이 아니라, 작성된 프롬프트의 성능을 평가하는 방법에 대한 것이다. 프롬프트를 수정하거나, 혹은 시스템의 구조가 변경되었을 때 언어 모델 역시 영향을 받을 수 있다. 이런 경우에 성능이 변화하는 것을 탐지할 수 있도록 자동화된 평가 시스템을 마련하는 것이 중요하다. 만약 변화에 의해 모델의 성능이 급격하게 떨어진다면 최대한 빠르게 롤백을 해야하기 때문이다.
AI 모델에서는 모델의 성능을 평가하기 위한 지표가 중요하다. 언어 모델 이전의 AI 모델들은 특정한 목적을 위해 만들어진 것들이 대부분이었기 때문에 평가 지표를 만드는 것이 어렵지 않았다. 예를 들어, 추천 시스템의 추천 결과를 평가하기 위해서는 사용자들이 남긴 별점을 사용할 수도 있고, 클릭율과 같은 간접적인 지표로 평가를 할 수도 있다.
그러나 언어 모델의 경우는 조금 다르다. ChatGPT를 평가할 때 수학 문제들만 잔뜩 가져와서 평가를 하면 올바른 평가일까? 아마 그랬다면 ChatGPT가 지금처럼 세계적으로 유명해지지 않았을 것이다. 따라서 언어 모델을 평가하기 위해서는 다양한 카테고리의 테스트 데이터를 가지고 평가를 해야한다.
3줄 요약
- 언어 모델은 훌륭한 도구지만, 모든 것의 신은 아니다
- 사용자가 어떻게 명령을 내리냐에 따라 좋은 결과를 얻을수도, 완전히 틀린 답을 얻을 수도 있다
- 최대한 자세히, 명확하게 명령을 내리는 것이 중요하다
'AI > LLM' 카테고리의 다른 글
[LLM] OpenAI API Rate Limit 관리하기 (0) | 2023.10.21 |
---|---|
[LLM] OpenAI API 응답 속도 최적화 (0) | 2023.10.08 |
[LLM] OpenAI API ChatCompletion 파라미터 (0) | 2023.09.23 |
[LLM] OpenAI API role 이해하기 (0) | 2023.09.17 |
[LLM] OpenAI API 사용 방법 (0) | 2023.09.09 |