태그 보관물: ChatGPT

ChatGPT에서 JSON 출력하기

ChatGPT에서의 프롬프트 엔지니어링

ChatGPT에서 원하는 출력을 얻기 위해서는 시행착오가 필요합니다. 원하는 출력을 얻는 프롬프트를 찾아내는 과정을 프롬프트 엔지니어링이라고 하죠. 문자열을 해석해서 컴퓨터에서 작업하기 좋도록 json 형식으로 만들고 싶습니다. 그래서 ChatGPT에게 다음과 같이 요청했습니다. One-shot learning을 위한 예시도 넣었죠.

문자열에서 정보를 추출해서 json으로 바꿔줘.

## 예시
“수연이는 파란색을 좋아하고, 철호는 빨간색을 좋아한다” => {“수연”: “blue”,”철호”: “red”}

## 입력
“영희는 노란색을 좋아하고, 민수는 초록색을 좋아한다” =>

그랬더니 직접 json을 만들어주는 대신 json으로 변환하는 코드를 만들어 줬습니다.(결과는 물어볼 때마다 달라질 수 있습니다.)

입력된 문자열에서 정보를 추출하고 JSON 형식으로 바꾸려면 Python 프로그래밍 언어를 사용하여 다음과 같이 할 수 있습니다.

import re
import json

# 입력 문자열
input_string = "영희는 노란색을 좋아하고, 민수는 초록색을 좋아한다"

# 이름과 색상 정보 추출
name_color_pattern = r'(\w+)는\s(\w+)색을\s좋아한다'
matches = re.findall(name_color_pattern, input_string)

# 결과를 JSON 형식으로 변환
result = {name: color for name, color in matches}

# JSON 문자열로 변환
json_result = json.dumps(result, ensure_ascii=False)

print(json_result)


위 코드는 입력된 문자열에서 이름과 색상 정보를 추출하고 이를 JSON 형식으로 변환합니다. 코드를 실행하면 원하는 결과가 출력됩니다.

그러나 이 코드는 예시에서 보여준 것처럼 색깔을 영어로 만들지 못하죠. 그래서 “키는 이름, 값은 색깔을 영어로” 만들어 달라고 프롬프트에 추가했는데 앞에서와 비슷한 대답을 했습니다. 이번엔 코드를 작성하지 말고 직접 바꿔달라고 했습니다.

입력 문자열에서 정보를 추출해서 아래와 같이 json으로 바꿔줘. 키는 이름, 값은 색깔을 영어로. 코드를 작성하지 말고 직접 바꿔서 결과를 보여줘.

## 예시
“수연이는 파란색을 좋아하고, 철호는 빨간색을 좋아한다” => {“수연”: “blue”,”철호”: “red”}

## 입력
“영희는 노란색을 좋아하고, 민수는 초록색을 좋아한다” =>

그랬더니 아래와 같이 대답했습니다.

입력 문자열을 파싱하여 아래와 같이 JSON 형식으로 변환합니다.

입력 문자열:
"영희는 노란색을 좋아하고, 민수는 초록색을 좋아한다"

JSON 결과:
{
"영희": "yellow",
"민수": "green"
}

원하는 결과가 마지막에 나오긴 했지만 앞에 불필요한 설명이 들어가 있습니다. 그래서 다시 프롬프트를 수정했습니다.

당신은 json 변환기입니다. 보기처럼 문장을 입력 받으면 json 형식으로 출력하세요. 설명 없이 json만 출력하세요.

## 보기
“수연이는 파란색을 좋아하고, 철호는 빨간색을 좋아한다” => {“수연”: “blue”, “철호”: “red”}

## 문장
“민수는 노란색을 좋아하고, 은정이는 분홍색을 좋아한다”

이제 원하는 답이 나옵니다.

{"민수": "yellow", "은정이": "pink"}

입력 문장을 바꿔가며 실행해봐도 결과가 잘 나옵니다. 몇 번 실행해봤을 때 결과가 잘 나왔으나 항상 잘 나온다는 보장은 없습니다. 응답이 확률적으로 나오므로 언제 부연 설명이 들어가게 될지 알 수 없죠. 앞에 설명이 들어가면 {} 기호를 이용해 json 부분만 추출할 수도 있으나 결과가 항상 json 형식으로 나온다면 더 좋겠죠?

사실 json 출력은 ChatGPT에서 대화할 때 보다는 API를 사용할 때 더 유용합니다. LLM의 응답을 바로 파이썬에서 딕셔너리로 바꿔서 사용할 수 있기 때문이죠. 위의 프롬프트를 가지고 API를 사용할 수도 있지만, 함수 호출 기능을 이용하면 더 확실하게 부연 설명 없는 json 결과물을 얻을 수 있습니다.

API에서 function_call을 이용해 json 출력하기

OpenAI API 호출시 functions 인자를 이용해 함수 사용법을 알려줄 수 있습니다. LLM은 필요하다고 판단될 경우 응답의 function_call을 통해 호출할 함수 이름과 호출할 때 사용할 인자(arguments)를 알려줍니다. 이 때 인자를 json object 형식(파이썬 딕셔너리에 해당)으로 받아서 출력하면 됩니다. 이를 위해 사용할 functions 인자는 다음과 같습니다.

functions = [
    {
        "name": "name_and_color",
        "description": "사람 이름과 좋아하는 색깔의 영어 이름을 매칭시킴",
        "parameters": {
            "type":"object",
            "properties": {
                "match": {
                    "type": "object",
                    "description": "key=name, value=color"
                }
            },
            "required": ["match"]
        }
    }
]

위에서 함수 이름은 중요하지 않습니다. 실제로는 존재하지 않는 함수입니다. 중요한 것은 함수 설명과 함수 인자 설명입니다. 함수 설명에는 우리의 목적에 대한 설명을 넣어서 LLM에서 호출해야 하는 함수로 판단할 수 있도록 합니다. 인자는 object 형식으로 만들고 인자 설명에 key는 사람 이름, value는 색깔을 넣도록 했습니다.

messages = [{"role": "system", "content": "You are a helpful assistant."},
           {"role": "user", "content": "수연이는 파란색을 좋아하고 철호는 빨간색을 좋아한다."}]
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=messages,
    functions=functions
)
print(response["choices"][0]['message']["function_call"]["arguments"])

### 결과
{
  "수연": "blue",
  "철호": "red"
}

원하는대로 json 문자열이 결과로 나왔습니다. 문장을 바꿔도 잘 나옵니다. 이제 부연 설명이 나올지 안 나올지 걱정할 필요가 없죠. json.loads 함수를 이용해 파이썬 딕셔너리로 바꿔서 사용하면 이후 과정을 편리하게 진행할 수 있습니다.

OpenAI API function_call 사용하기

OpenAI API를 이용할 때 ChatCompletion.create 함수에 functions라는 인자(argument)가 있습니다. LLM에게 사용할 수 있는 함수 정보를 알려줄 때 사용합니다. 이를 이용하면 LLM이 질문에 응답하기 위해 함수 호출이 필요할 경우 함수를 호출해달라는 출력(function_call)을 내놓습니다. 실제 호출은 파이썬 프로그램에서 하고 LLM은 어떤 함수를 어떤 인자들을 사용해 호출해야 하는지 알려주는 것이죠. 사용자는 함수 호출 결과를 기존 질문과 함께 다시 LLM에 전달하고 LLM은 결과를 바탕으로 질문에 응답합니다.

예제: Wikipedia 함수

먼저, LLM이 호출할 수 있는 함수를 하나 만들어보겠습니다. 검색 문자열을 받아 Wikipedia를 검색하고 첫 번째로 검색된 페이지의 내용을 반환하는 함수입니다. 페이지 내용은 다시 LLM에 보내 LLM이 답변하는데 참고할 수 있도록 합니다. 이를 위해 파이썬 wikipedia 패키지를 설치하겠습니다.

pip install wikipedia

패키지를 설치했으면 파이썬에서 다음과 같이 사용할 수 있습니다.

import wikipedia
wikipedia.set_lang("en")
r = wikipedia.search("Large language model")
print(r)

## 출력 결과
['Large language model',
 'Language model',
 'BERT (language model)',
 'BLOOM (language model)',
 'LLaMA',
 'Prompt engineering',
 'Modeling language',
 'Generative pre-trained transformer',
 'Transformer (machine learning model)',
 'GPT-3']

한글 검색을 위해서는 언어를 한글로 바꾼 후 검색하면 됩니다.

wikipedia.set_lang("ko")
r = wikipedia.search("광안대교 정식 개통일")
print(r)

## 출력 결과
['광안대교',
 '수영강변대로',
 '남항대교',
 '인천대교',
 '해운대',
 '세토 대교',
 '만덕대로',
 '교량',
 '부산광역시',
 '남구 (부산광역시)']

위의 결과들을 보면, 검색 결과는 검색 관련 내용이 있는 Wikipedia 페이지 제목 10개입니다. 페이지의 내용은 다음과 같이 받아올 수 있습니다.

p = wikipedia.page(r[0])
print(p.content)

위의 내용을 종합해서 다음과 같은 함수를 만들었습니다. 검색 내용과 언어를 입력받으면 Wikipedia를 검색해서 가장 먼저 나오는 페이지의 내용을 반환하는 함수입니다.

def search_wikipedia(query, lang):
    wikipedia.set_lang(lang)
    r = wikipedia.search(query, results=1)
    p = wikipedia.page(r[0])
    return p.content

위 함수가 LLM에서 사용할 함수입니다.

입력: functions

함수 사용법을 LLM에게 알려줄 때는 아래와 같은 형식으로 알려줍니다.

functions = [
    {
        "name": "search_wikipedia",
        "description": "wikipedia 검색",
        "parameters": {
            "type":"object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "검색할 내용"
                },
                "lang": {
                    "type": "string",
                    "enum": ["en","ko"],
                    "description": "wikipedia 언어"
                }
            },
            "required": ["query","lang"]
        }
    }
]

여기서는 하나의 함수, 두 개의 인자를 사용했는데, 함수가 여러 개 있으면 functions 리스트에 같은형식으로 추가하면 됩니다. 함수 정의 부분을 살펴보면, name에 함수 이름이 들어가고, description 부분에 함수 설명이 들어갑니다. parameters – properties에 인자 설명이 들어가는데, 입력 자료형은 type으로 지정해주고, 설명은 description에 들어가게 됩니다. 선택 가능한 경우가 정해져 있으면 enum으로 지정해주면 됩니다. 필수 인자는 required로 지정해줍니다. 위 내용을 추가해서 OpenAI API를 호출해보겠습니다.

import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")

messages = [{"role": "system", "content": "You are a helpful assistant."},
           {"role": "user", "content": "광안대교 정식 개통일이 언제야?"}]

response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=messages,
    functions=functions
)

출력: function_call

광안대교 정식 개통일이 언제인지 질문했죠. 참고로, 제가 ChatGPT에게 같은 질문을 했을 때는 틀린 답을 내놓았습니다. LLM은 질문을 바탕으로 주어진 함수 호출이 필요한지 아닌지 판단합니다. 함수 호출이 필요하다면 response['choices'][0]['message'] 내에 function_call 항목이 생기고, 필요 없다면 이 항목이 생기지 않습니다. 따라서 함수를 실제로 호출할 것인지는 function_call이 존재하는지 여부로 판단할 수 있습니다. 위 코드의 실행 결과로 얻은 response는 다음과 같습니다.

# 생략
  "model": "gpt-3.5-turbo-0613",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "function_call": {
          "name": "search_wikipedia",
          "arguments": "{\n\"query\": \"\uad11\uc548\ub300\uad50 \uac1c\ud1b5\uc77c\",\n\"lang\": \"ko\"\n}"
        }
      },
      "finish_reason": "function_call"
    }
  ],
# 생략

function_call이 존재하고, 호출할 함수의 이름과 인자 정보가 있는 것을 알 수 있습니다. 참고로, query 부분이 알아보기 어렵게 나오는데, json 인코딩 때문에 그렇습니다. \n은 개행, \"는 큰 따옴표로 감싼 문자열 안에 큰 따옴표 문자를 넣기 위한 표시죠. print 해보면 한글이 제대로 나옵니다.

print(response["choices"][0]['message']["function_call"]["arguments"])

## 아래는 출력 결과
{
    "query": "광안대교 개통일",
    "lang": "ko"
}

실제 함수 호출

이 정보를 이용해 함수를 호출하고 그 결과를 다시 LLM에게 전달해주면 됩니다. 함수 호출을 위해서는 아래 함수를 이용하겠습니다.

import json

def execute_function_call(msg):
    function_name = msg["function_call"]["name"]
    args = json.loads(msg['function_call']["arguments"])
    if function_name == "search_wikipedia":
        results = search_wikipedia(**args)
        return results

response["choices"][0]['message']["function_call"]["arguments"]에 있는 인자는 문자열로 되어 있으므로 dictionary로 바꾸기 위해 json.loads를 이용했고, 읽어들인 argssearch_wikipedia함수의 keyword arguments로 넘겼습니다.

함수 호출 결과를 LLM에 전달하기

위 함수를 실행해 얻은 결과는 LLM에 보내는 messages에 추가한 후 OpenAI API를 다시 호출합니다. 이 때 role에는 function이 들어가고, 기존에 없던 name에 호출한 함수 이름, content에 함수의 결과가 들어갑니다.

msg = response["choices"][0]["message"]
content = execute_function_call(msg)
messages.append(
    {
        "role":"function",
        "name":"search_wikipedia",
        "content":content
    }
)

response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=messages,
    functions=functions
)
print(response["choices"][0]["message"]["content"])

## 출력 결과
광안대교의 정식 개통일은 2003년 1월 6일입니다.

이제 맞는 답을 얻었습니다. 참고로, wikipedia 페이지 중에는 길이가 긴 것들도 있으므로 페이지 내용을 전달할 때 content를 모두 전달하면 token 수 제한에 걸릴 수 있습니다. functions에 들어가는 함수 설명은 system 메시지에 들어가 token 수를 차지하므로 너무 많은 함수를 넣으면 역시 token 수 제한에 걸릴 수 있습니다. Retrieval Augmented Generation에서처럼 페이지 내용을 임베딩 벡터로 만들고 벡터 검색을 이용해 질문과 관련된 내용만 추출하면 token 수를 줄일 수 있습니다.

ChatGPT 하이퍼파라미터

OpenAI의 GPT 모델들의 API 호출시 사용 가능한 하이퍼파라미터들이 있습니다. 하이퍼파라미터는 훈련되는 신경망 가중치가 아니라 사용자가 직접 입력하는 매개변수로, 모델의 응답에 영향을 미치게 됩니다. 주요 하이퍼파라미터들은 다음과 같습니다.

하이퍼파라미터기본값범위
temperature10.0~2.0
top-p10.0~1.0
max_tokensinf정수
frequency_penalty0-2.0~2.0
presence_penalty0-2.0~2.0
Hyperparameters

각 하이퍼파라미터를 통해 얻을 수 있는 결과를 살펴봅시다.

temperature

결과의 다양성과 예측 가능성을 조절합니다. 값이 클수록 더 무작위적인 응답(더 창의적인 응답)을 얻을 수 있습니다. 예측 가능하고 일관된 응답을 얻어야 할 경우에는 0에 가까운 값을 사용합니다. 보통 아래 나오는 top_p나 temperature 중 하나를 조정합니다.

top_p

단어 샘플링시 사용하는 확률 분포의 상위 부분을 의미합니다. 예를 들어, top_p=0.9라면 확률 분포 상위 90% 내에서만 단어를 샘플링합니다. 이렇게 되면 이상치를 제거하는 효과를 얻을 수 있겠죠. top_p=0.1이라면 상위 10% 내의 확률 분포 내에서만 단어를 샘플링하므로 좀 더 예측 가능한 결과를 얻을 수 있습니다.

max_tokens

답변의 최대 토큰 수를 제한할 때 사용합니다.

presence_penalty

0보다 큰 값을 사용할 경우 앞에서 나왔던 단어(토큰)가 또 나오는 것을 억제합니다. 즉, 새로운 내용을 이야기할 가능성이 높아집니다.

frequency_penalty

0보다 큰 값을 사용할 경우 특정 단어(토큰)의 출현 빈도가 높아지는 것을 억제합니다. 즉, 같은 말을 문자 그대로 반복하는 것을 막습니다.

예시

OpenAI Playground에서는 직접 API를 호출하지 않고도 위의 하이퍼파라미터들을 바꿔가며 테스트해볼 수 있습니다. ChatGPT에서도 메시지 입력시 아래와 같이 하이퍼파라미터를 입력하여 응답 결과를 조정할 수 있습니다.

예시: temperature=0.1

나는 대학생이야. 다음 괄호에 들어갈만한 장소를 10군데 적어줘. temperature=0.1

나는 내일 ( )에 갈 것이다. 
=====
주어진 문장에서 괄호에 들어갈 수 있는 장소를 10군데 제안해 드립니다:

나는 내일 (도서관)에 갈 것이다.
나는 내일 (학교)에 갈 것이다.
나는 내일 (카페)에 갈 것이다.
나는 내일 (식당)에 갈 것이다.
나는 내일 (공원)에 갈 것이다.
나는 내일 (영화관)에 갈 것이다.
나는 내일 (체육관)에 갈 것이다.
나는 내일 (박물관)에 갈 것이다.
나는 내일 (친구 집)에 갈 것이다.
나는 내일 (도시 쇼핑몰)에 갈 것이다.

예시: temperature=1.0

나는 대학생이야. 다음 괄호에 들어갈만한 장소를 10군데 적어줘. temperature=1.0

나는 내일 ( )에 갈 것이다. 
=====
온도 (temperature)가 높은 경우, 모델은 더 다양한 창조적인 답변을 생성할 수 있습니다. 다음은 온도가 높을 때 가능한 10개의 장소 예시입니다:

나는 내일 (자연휴양림)에 갈 것이다.
나는 내일 (물놀이공원)에 갈 것이다.
나는 내일 (미술갤러리)에 갈 것이다.
나는 내일 (음악 페스티벌)에 갈 것이다.
나는 내일 (카페트매장)에 갈 것이다.
나는 내일 (과학 박물관)에 갈 것이다.
나는 내일 (헬스 클럽)에 갈 것이다.
나는 내일 (해변)에 갈 것이다.
나는 내일 (캠핑장)에 갈 것이다.
나는 내일 (동물원)에 갈 것이다.
이것들은 더 다양한 장소를 나타내는 창조적인 예시입니다.

예시: top_p=0.1

나는 대학생이야. 다음 괄호에 들어갈만한 행동을 10가지 적어줘. top_p=0.1

나는 1시간 후 ( )을/를 할 것이다. 
=====
top_p (penalty) 값이 낮은 경우, 모델은 더 확실한 예상 답변에 가까운 답변을 생성합니다. 따라서 가능한 10가지 행동 예시는 다음과 같습니다:

나는 1시간 후 (공부)를 할 것이다.
나는 1시간 후 (운동)을 할 것이다.
나는 1시간 후 (음악 듣기)를 할 것이다.
나는 1시간 후 (친구와 만남)을 할 것이다.
나는 1시간 후 (요리)를 할 것이다.
나는 1시간 후 (영화 보기)를 할 것이다.
나는 1시간 후 (독서)를 할 것이다.
나는 1시간 후 (산책)을 할 것이다.
나는 1시간 후 (미술 활동)을 할 것이다.
나는 1시간 후 (봉사 활동)을 할 것이다.
이러한 답변은 top_p 값이 낮아 더 일반적이고 확실한 행동을 나타냅니다.

예시: top_p=1.0

나는 대학생이야. 다음 괄호에 들어갈만한 행동을 10가지 적어줘. top_p=1.0

나는 1시간 후 ( )을/를 할 것이다. 
=====
top_p (penalty) 값이 높은 경우, 모델은 더 다양하고 창조적인 답변을 생성할 수 있습니다. 따라서 가능한 10가지 행동 예시는 다음과 같습니다:

나는 1시간 후 (하늘을 바라볼 것이다).
나는 1시간 후 (별을 관찰할 것이다).
나는 1시간 후 (음악을 연주할 것이다).
나는 1시간 후 (그림을 그릴 것이다).
나는 1시간 후 (배낭을 싸고 여행을 떠날 것이다).
나는 1시간 후 (심리적인 독서를 할 것이다).
나는 1시간 후 (새로운 요리를 시도할 것이다).
나는 1시간 후 (사진을 찍으러 나갈 것이다).
나는 1시간 후 (봉사 활동을 할 것이다).
나는 1시간 후 (친구와 대화를 나눌 것이다).
이러한 답변은 top_p 값이 높아 더 창조적이고 다양한 행동을 나타냅니다.

프롬프트 엔지니어링: 좋은 프롬프트 작성하기

OpenAI 사이트에 소개된 챗GPT 프롬프트 엔지니어링 Best practices를 소개해드립니다.

1. 최신 모델 사용하기

현재 챗GPT 최신 모델은 gpt-3.5-turbo와 gpt-4입니다.

2. 앞부분에 Instruction 쓰고 ###, “”” 같은 기호를 이용해 Context와 분리하기

안 좋은 예

Summarize the text below as a bullet point list of the most important points.

{text input here}

좋은 예

Summarize the text below as a bullet point list of the most important points.

Text: """
{text input here}
"""

3. 구체적으로 쓰기, 자세히 설명하기(상황, 원하는 결과, 출력 형식, 길이, 스타일 등)

안 좋은 예

Write a poem about OpenAI.

좋은 예

Write a short inspiring poem about OpenAI, focusing on the recent DALL-E product launch (DALL-E is a text to image ML model) in the style of a {famous poet}

4. 출력 형식 자세히 알려주기

안 좋은 예

Extract the entities mentioned in the text below. Extract the following 4 entity types: company names, people names, specific topics and themes.

Text: {text}

좋은 예

Extract the important entities mentioned in the text below. First extract all company names, then extract all people names, then extract specific topics which fit the content and finally extract general overarching themes

Desired format:
Company names: <comma_separated_list_of_company_names>
People names: -||-
Specific topics: -||-
General themes: -||-

Text: {text}

5. Zero-shot으로 안 되면 few-shot, few-shot으로 안 되면 fine-tune

Zero-shot

Extract keywords from the below text.

Text: {text}

Keywords:

Few-shot

Extract keywords from the corresponding texts below.

Text 1: Stripe provides APIs that web developers can use to integrate payment processing into their websites and mobile applications.
Keywords 1: Stripe, payment processing, APIs, web developers, websites, mobile applications
##
Text 2: OpenAI has trained cutting-edge language models that are very good at understanding and generating text. Our API provides access to these models and can be used to solve virtually any task that involves processing language.
Keywords 2: OpenAI, language models, text processing, API.
##
Text 3: {text}
Keywords 3:

6. 애매모호한 설명 쓰지 않기

안 좋은 예

The description for this product should be fairly short, a few sentences only, and not too much more.

좋은 예

Use a 3 to 5 sentence paragraph to describe this product.

7. 하지 말라는 명령 대신 하라는 명령 사용하기

안 좋은 예

The following is a conversation between an Agent and a Customer. DO NOT ASK USERNAME OR PASSWORD. DO NOT REPEAT.

Customer: I can’t log in to my account.
Agent:

좋은 예

The following is a conversation between an Agent and a Customer. The agent will attempt to diagnose the problem and suggest a solution, whilst refraining from asking any questions related to PII. Instead of asking for PII, such as username or password, refer the user to the help article www.samplewebsite.com/help/faq

Customer: I can’t log in to my account.
Agent:

8. 코드 생성시 코드 시작 구문을 보여줘서 LLM이 해당 구문으로 시작하는 패턴을 따르도록 하기

안 좋은 예

# Write a simple python function that
# 1. Ask me for a number in mile
# 2. It converts miles to kilometers

좋은 예

# Write a simple python function that
# 1. Ask me for a number in mile
# 2. It converts miles to kilometers
 
import

기타

위 내용들 외에도 영어로 명령하기(보통 영어로 명령했을 때 더 좋은 결과가 나옵니다), 역할 부여하기, 자기 목적에 맞는 프롬프트를 여러 가지 실험해보기, 다른 사람의 좋은 프롬프트 보고 배우기 등의 방법이 있습니다.

좀 더 복잡한 문제 해결을 위한 Chain-of-Thought, Tree-of-Thought, Self-Consistency, ReAct 등의 프롬프팅 프레임워크도 계속 발표되고 있습니다.

ChatGPT API 사용하기

ChatGPT vs GPT

이해를 돕기 위해 제목에 ChatGPT라고 썼지만, 엄밀히 말하면 대화형 ChatGPT와 OpenAI API를 통해 사용하는 GPT 모델은 다른 모델입니다. 대화형 ChatGPT는 자연스러운 대화를 위해 GPT 모델을 별도로 훈련시킨 모델입니다. 특히, 이전 대화 내용을 기억하는 기능이 추가되어 있죠. API를 통해 이용하는 GPT 모델들은 대화의 맥락을 기억하는 기능이 없기 때문에 API를 이용해 대화를 이어가기 위해서는 사용자가 이전 대화의 내용을 입력으로 전달해야 합니다.

파이썬으로 API 사용하기

파이썬에서는 openai 패키지를 설치하면 OpenAI API를 쉽게 사용할 수 있습니다. 명령줄에서 다음을 실행합니다.

pip install openai

설치 후에는 openai 패키지를 import하고 API Key를 지정해줍니다. 아래 예제에서는 환경변수로 지정해놓은 API Key를 가지고 왔습니다.

import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")

ChatCompletion

OpenAI 문서에 있는 예제를 보겠습니다. ChatCompletion.create를 이용해 LLM 모델을 사용할 수 있습니다.

response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Who won the world series in 2020?"},
        {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
        {"role": "user", "content": "Where was it played?"}
    ]
)

위 코드에서 model에는 OpenAI에서 제공하는 LLM 모델들 중 원하는 것을 적습니다. messages는 LLM에 전달할 입력입니다. 입력값을 보면 rolecontent로 이루어진 딕셔너리들의 배열임을 알 수 있습니다.

Role

role에는 “system”, “user”, “assistant” 중 하나가 오는데, “system”과 “user”는 사용자가 LLM에 요청하는 내용이고, “assistant”는 LLM의 응답입니다. 앞에서 API를 통해 사용하는 LLM은 이전 대화 내용을 기억하지 못한다고 했죠? 위 코드는 이전 대화를 기억할 수 있도록 전달하는 코드임을 알 수 있습니다. “system” 메시지는 LLM에게 대화 전체에 적용될 역할 등을 지정해주는 메시지로, 보통 대화 처음에 한 번 나오는데, 꼭 필요한 것은 아닙니다. 생략하면 “You are a helpful assistant.”라고 주는 것과 유사합니다. 이후에는 보통 “user”와 “assistant”가 번갈아 나옵니다.

위 예제에서는 modelmessages를 입력했는데, 이 두 항목은 필수로 입력해야 하고, 그 외에도 하이퍼파라미터들과 기타 여러 가지 항목들이 있습니다.

Response

결과는 다음과 같이 확인합니다. 여러분이 실행하면 결과가 약간씩 다르게 나올 수 있습니다. 아래 있는 제 실행 결과도 OpenAI 문서에 나온 결과와 단어 순서에 약간 차이가 있습니다.

print(response['choices'][0]['message']['content'])
# 실행 결과
# The World Series in 2020 was played at Globe Life Field in Arlington, Texas.

결과 확인하기가 좀 복잡하죠? response 전체를 출력해보면 다음과 같이 다른 정보들을 포함하고 있는 것을 알 수 있습니다. 사실 response는 chat completion 객체로, 세부 항목 설명은 여기에 있습니다.

{
  "id": "chatcmpl-7wi85qbDjFeDclPTjepMccfCbiWHa",
  "object": "chat.completion",
  "created": 1694227013,
  "model": "gpt-3.5-turbo-0613",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "The World Series in 2020 was played at Globe Life Field in Arlington, Texas."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 53,
    "completion_tokens": 18,
    "total_tokens": 71
  }
}

API를 이용한 대화

LLM을 한 번만 호출한다면 위와 같이 실행하면 되고, LLM과 대화를 이어가고 싶다면 반복문 내에서 이전 대화 내용을 누적해서 다음 입력으로 전달해주면 됩니다.

대화가 길어지면 입력 토큰 수가 크게 증가하겠죠? 비용도 문제지만, 토큰 수 제한에 걸릴 수도 있습니다.

그럴 경우 이전 대화 내용을 요약해서 전달하면 됩니다. 요약하는 것 역시 LLM을 이용하죠. 현재 대화와는 별도로 API를 호출하면서 이전 대화 내용을 전달하고 요약해달라고 하면 됩니다.

이 때 이전 대화 전체를 전달하며 요약할 수도 있고, 기존 요약된 내용에 새로운 대화를 추가해서 전달하며 요약할 수도 있습니다. 이전 대화 전체를 전달하면 과거 내용을 유지할 수 있지만 토큰이 많이 필요하고, 요약된 내용에 새로운 대화를 추가해서 요약하면 토큰 수를 줄일 수 있지만, 과거 대화 내용이 여러 번 요약되며 사라질 수 있습니다.

OpenAI API (GPT) 사용 준비

OpenAI에서는 다양한 서비스들을 API를 이용해 사용할 수 있도록 REST API를 제공하고 있습니다. 사용할 수 있는 모델들에는 GPT-3.5, GPT-4 외에도 DALL-E (그림 그리기), Whisper (음성 인식), Embeddings (텍스트 임베딩) 등이 있습니다. API를 사용하기 위해서는 OpenAI API 사이트에 회원 가입sk-로 시작하는 API Key를 받아야 합니다. API는 사용료가 있는데, 처음 가입하면 3개월간 쓸 수 있는 $5를 제공하므로, 여러 가지 테스트를 해볼 수 있습니다.

LLM 모델 – 성능, 가격, 토큰 수

LLM 모델은 gpt-3.5-turbo나 gpt-4를 사용하면 됩니다. 물론 gpt-4의 성능이 더 좋지만 비용이 더 비싸므로 gpt-3.5-turbo의 성능으로 충분한 경우에는 해당 모델을 사용하면 됩니다. 아래 표에 이 글을 작성하는 시점의 가격을 적었지만 앞으로 바뀔 수 있으므로 최신 가격은 OpenAI 사이트에서 확인하시기 바랍니다.

LLM 모델에는 입출력 토큰 수 제한이 있습니다. 토큰 수는 단어 수보다 많죠. 입력과 출력 토큰의 합계로 제한하는데, gpt-3.5-turbo 모델의 경우 4K, gpt-4의 경우 8K 토큰을 지원합니다. 더 많은 토큰이 필요한 경우에 사용할 수 있는 gpt-3.5-turbo-16kgpt-4-32k 모델도 있습니다. LLM 모델 정보는 여기에서 확인할 수 있습니다.

ModelMax TokensInput Price
(per 1K tokens)
Output Price
(per 1K tokens)
gpt-3.5-turbo4,097$0.0015$0.002
gpt-3.5-turbo-16k16,385$0.003$0.004
gpt-48,192$0.03$0.06
gpt-4-32k32,768$0.06$0.12
OpenAI LLM Models

참고로, gpt-3.5-turbo 모델의 경우 자신의 데이터를 이용해 Fine Tuning할 수 있습니다. 훈련 가격은 $0.008/1K, 입력 가격은 $0.012/1K, 출력 가격은 $0.016/1K로, 기본 gpt-3.5-turbo 모델보다 8배 비싸네요.

임베딩 모델

텍스트 임베딩이 필요할 경우 text-embedding-ada-002 모델을 사용하면 됩니다. 이 때 임베딩 모델에서 사용하는 토크나이저는 cl100k_base, 최대 입력 토큰수는 8192, 출력 벡터는 1536 차원입니다. 임베딩 모델 정보는 여기에서 확인할 수 있습니다. API 사용 가격은 $0.0001/1K로, 1달러를 쓰기 위해서는 10,000K 토큰이 필요하네요. LLM에 비해 임베딩 가격은 무시할만하다고 할 수 있습니다.

거대 언어 모델(Large Language Model, LLM)과 프롬프트 엔지니어링

프롬프트와 프롬프트 엔지니어링

ChatGPT와 같은 거대 언어 모델(Large Language Model, LLM)을 사용할 때 모델에 전달하는 메시지를 프롬프트(Prompt)라고 하죠. 프롬프트를 잘 만들어서 원하는 결과물을 얻기 위해 연구하는 것을 프롬프트 엔지니어링이라고 합니다.

프롬프트는 한 문장이 될 수도 있고 여러 문단이 될 수도 있죠. 프롬프트를 몇 가지 주요 구성 요소들로 나눠보면 아래와 같이 정리할 수 있습니다.

  • Goal: 달성해야 하는 목표
  • Instruction: 구체적인 지시, 요구 사항 또는 질문
  • Role: 모델에게 조언자나 편집자, 작가 등의 역할을 지정해주는 부분
  • Context: 작업의 배경 지식이나 상황 제공
  • Output Format: 원하는 응답 형식을 지정해주는 부분
  • Systematic Approach: 특정 시스템이나 알고리즘을 따르도록 지시
  • Step-by-step: 복잡한 작업을 단계별로 처리하라고 지시
  • Clarifying Questions: 불확실한 부분은 모델이 사용자에게 질문하도록 권장
  • Examples: 질문과 응답 예제

이러한 분류는 정해져 있는 것은 아니고, 사람에 따라 다르게 분류할 수 있습니다. 또 매번 위의 모든 요소가 다 들어갈 필요도 없죠. 사용 목적에 따라 들어가는 요소들이 달라지게 됩니다. LLM은 예제로부터 학습할 수 있는데, 예제가 없으면 Zero-shot, 하나 있으면 One-shot, 두 개 이상 있으면 Few-shot learning이라고 합니다.

아래 나오는 내용은 주로 대화형 환경 보다는 LLM의 API (Application Programming Interface)를 이용해 사용자에게 드러나지 않게 구현하게 됩니다.

환각 현상의 완화

거대 언어 모델에서 나타나는 환각(hallucination, 거짓 응답) 현상을 줄이는 방법 중 하나가 Context를 제공하고 주어진 정보를 이용해 답하라고 지시하는 방법입니다. 물론 Context에는 사실을 담아야겠죠. 그러려면 Context 정보를 어디선가 가지고 와야 합니다.

Bing Chat, Bard

질문을 가지고 인터넷을 검색해서 Context에 들어갈 내용을 가져오면 Microsoft의 Bing Chat이나 Google의 Bard와 같이 최신 정보를 반영한 모델이 됩니다. 검색한 웹문서를 직접 열어서 필요한 부분을 찾을 필요 없이 질문에 대한 답을 알아서 정리해주죠.

문서에 대한 질의 응답: ChatPDF

Context를 PDF 파일과 같은 문서에서 가져오면 문서의 내용에 대해 질문할 수 있는 챗봇, ChatPDF가 됩니다. 인터넷 검색의 경우 이미 있는 Google이나 Bing과 같은 검색 엔진의 검색 결과에서 가장 먼저 나오는 몇 개의 인터넷 문서에서 정보를 가져오면 됩니다. PDF 파일은 어떻게 할까요?

PDF나 워드 문서의 경우 문서에서 텍스트 정보를 추출하여 텍스트를 컴퓨터에서 처리할 수 있는 숫자들의 벡터로 변환합니다. 이 과정을 벡터 임베딩(Vector Embedding)이라고 하죠. 문서 전체의 텍스트를 일정 길이(Chunk)로 나눠서 벡터로 변환한 후 벡터들을 벡터 데이터베이스(VectorDB)에 저장해 놓습니다. 질문이 들어오면 질문도 벡터로 변환합니다. 벡터들끼리는 거리를 측정할 수 있죠? 질문 벡터와 문서의 벡터들 사이의 거리를 측정하여 거리가 가까운 벡터들을 몇 개 추출하면 그 벡터에 해당하는 원본 텍스트가 Context에 들어가게 됩니다. 그럼 LLM은 질문에 대한 답을 포함할 가능성이 높은 Context의 정보를 이용해서 대답을 해주죠. 정리해보면, 다음과 같습니다.

  1. 답을 찾고자 하는 문서(문서들)에서 텍스트 추출
  2. 텍스트를 일정 길이로 나누기
  3. 나눈 내용을 벡터 임베딩을 이용해 벡터로 변환
  4. 변환한 벡터들을 벡터 데이터베이스에 저장
  5. 사용자 질문 (사용자 질문이 제일 처음에 문서와 함께 주어질 수도 있습니다)
  6. 사용자 질문을 벡터 임베딩을 이용해 벡터로 변환
  7. 질문 벡터와 벡터 데이터베이스에 저장된 벡터들 사이의 거리 계산
  8. 거리가 가장 가까운 벡터들 추출
  9. 추출한 벡터들의 원본 텍스트를 LLM에 질문에 대한 Context로 제공
  10. LLM은 질문에 대한 답을 Context에서 찾아서 사용자에게 응답

일반 검색에서는 검색하는 단어나 문장과 글자가 일치하는 것을 찾지만 벡터 검색은 의미가 유사한 내용을 찾는 의미 검색(semantic search)입니다. 벡터 데이터베이스에 회사의 제품에 관한 여러 가지 문서들을 넣으면 회사의 제품 상담 챗봇이 됩니다.

문서에는 PDF 뿐 아니라 Word, Excel, Power Point, Gmail, Notion 등 다양한 문서들이 있습니다. 이러한 문서들에서 텍스트를 편리하게 추출해주는 라이브러리로 Llama-Index가 있습니다.

인공지능 비서: AutoGPT, BabyAGI

LLM의 API를 호출하는 프로그램에서 Instruction에 특정 함수의 사용법을 알려줄 수 있습니다. 예를 들면 위키피디아 검색을 하고 싶으면 wikipedia(query)라고 출력하라고 알려줄 수 있습니다. 수학 계산을 하고 싶으면 calculate(equation)이라고 출력하라고 알려줄 수도 있죠. 질문과 함께 함수 사용법을 알려주고 함수를 사용할 수 있다고 LLM에 알려주면 LLM은 최종 대답을 하기 전에 필요한 정보를 얻기 위해 함수를 사용하게 됩니다. 직접 함수를 호출하는 것은 아니고 함수 호출에 필요한 명령을 출력하게 되는거죠. 그럼 프로그램에서 해당 함수를 호출하여 결과를 LLM에 전달해줍니다.

  1. 질문과 함께 사용할 수 있는 함수들을 LLM에 제시
  2. LLM에서 필요한 함수를 호출하는 명령 출력
  3. LLM을 호출하는 프로그램에서 함수 실행
  4. 함수 실행 결과를 LLM에 알려주기
  5. LLM에서 최종 응답

예를 들어, 광안대교를 언제 개통했는지 질문하고 위의 함수를 사용할 수 있다고 알려주면, wikipedia(광안대교 개통일)과 같은 출력을 내놓게 됩니다. 그럼 이 함수 호출 내용을 프로그램에서 분석해서 위키피디아 검색을 수행하고 결과 페이지(위키피디아 광안대교 페이지)의 내용을 LLM에 질문과 함께 Context로 전달하게 됩니다. 그럼 LLM은 Context에서 답을 찾아 대답하게 되죠.

LLM에서 Python 코드를 출력하면 코드를 실행해서 결과를 알려주겠다고 지시할 수도 있습니다. LLM에 지시를 내리면 LLM에서 지시를 수행하기 위한 코드를 작성해서 출력하고, 프로그램에서 코드를 실행한 후 실행 결과를 다시 LLM에 전달합니다. 그럼 LLM은 결과를 이용해 이후의 응답을 생성할 수 있죠. ChatGPT Plus의 Advanced Data Analysis (Code Interpreter의 새 이름)의 기본 원리입니다.

이렇게 LLM에 알려주는 함수는 LLM의 Tool 또는 Skill이라고 부릅니다. 많은 함수를 알려줄수록 LLM이 할 수 있는 일도 많아지죠. 복잡한 문제의 경우 문제 풀이 단계를 논리적으로 분석해서 제시하고 하나씩 차례대로 수행하라고 지시할 수도 있습니다. 각 단계를 수행할 때 사용할 수 있는 함수들도 같이 알려주죠. AutoGPTBabyAGI의 기본 원리입니다. 이런 LLM 프로그래밍을 편리하게 만들어주는 라이브러리로 LangChain이 있습니다.

J.A.R.V.I.S.

말로 지시를 내리고 소리로 응답을 듣고 싶다면? 음성 인식(Speech Recognition) 기술을 이용해 말을 글로 바꿔서 지시를 내릴 수 있습니다. LLM의 출력은 글을 읽어주는 음성 합성(Text-to-Speech) 기술을 이용하면 됩니다.

  1. 사용자가 음성으로 명령
  2. 음성 인식: 음성을 글자로 변환
  3. 글자를 LLM (AutoGPT)에 전달
  4. LLM (AutoGPT)에서 응답
  5. 음성 합성: 응답 결과를 읽어주기

간단한 자비스(JARVIS)가 됩니다. 토니 스타크의 자비스에 비하면 아직 한참 멀었지만, 점점 향상되고 있습니다.