🔥알림🔥
① 테디노트 유튜브 - 구경하러 가기!
② LangChain 한국어 튜토리얼 바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs) 바로가기 🙌

9 분 소요

이번 튜토리얼 부터는 시리즈 형식으로 OpenAI 에서 지난 2023년 11월에 1.x.x 로 판올림하여 공개한 API 의 사용법에 대해서 알아보겠습니다.

튜토리얼 진행시 참고사항

🌱 환경설정

# 토큰 정보로드를 위한 라이브러리
# 설치: pip install python-dotenv
from dotenv import load_dotenv

# 토큰 정보로드
load_dotenv()
True

① Client 생성

  • client 는 OpenAI 모듈로 생성된 인스턴스 입니다.

[주의] 아래의 코드에서 오류가 난다면 API 키의 오류일 가능성이 높습니다.

from openai import OpenAI

client = OpenAI()
client
<openai.OpenAI at 0x10580d270>

② ChatCompletions

채팅 모델은 메시지 목록을 입력으로 받고 모델이 생성한 메시지를 출력으로 반환합니다. 채팅 형식은 여러 차례에 걸친 대화를 쉽게 할 수 있도록 설계되었지만, 대화가 없는 단일 턴 작업에도 유용합니다.

  • API Reference: https://platform.openai.com/docs/api-reference/chat

  • model 파라미터에 지정할 수 있는 모델의 정보는 다음의 링크를 참고하세요. https://platform.openai.com/docs/models

OpenAI의 API는 사용자가 대화 생성 모델을 더 세밀하게 제어할 수 있도록 다양한 파라미터를 제공합니다. 이러한 파라미터들을 통해 모델의 응답 스타일, 다양성 및 창의성을 조절할 수 있습니다. 주요 파라미터들과 그 의미는 다음과 같습니다.

주요 파라미터

  • messages: 지금까지의 대화를 구성하는 메시지 목록입니다.

  • frequency_penalty: -2.0에서 2.0 사이의 숫자. 양수 값은 지금까지 텍스트에 나타난 기존 빈도에 따라 새로운 토큰에 불이익을 주어 모델이 같은 줄을 그대로 반복할 가능성을 낮춥니다.

  • max_tokens: 생성할 수 있는 최대 토큰 수입니다. 입력 토큰과 생성된 토큰의 총 길이는 모델의 컨텍스트 길이에 의해 제한됩니다.

  • n: 각 입력 메시지에 대해 생성할 선택지(choices) 수입니다. [주의] 모든 선택 항목에서 생성된 토큰 수에 따라 요금이 부과된다는 점에 유의하세요. 비용을 최소화하려면 n을 1로 유지하세요.

  • presence_penalty: -2.0에서 2.0 사이의 숫자. 값이 양수이면 지금까지 텍스트에 등장한 토큰에 따라 새로운 토큰에 불이익을 주므로 모델이 새로운 주제에 대해 이야기할 가능성이 높아집니다.

  • response_format: 모델이 출력해야 하는 형식을 지정하는 객체입니다. gpt-4-1106-previewgpt-3.5-turbo-1106 과 호환됩니다. { "type": "json_object" } 로 설정하면 JSON 모드가 활성화되어 모델이 생성하는 메시지가 유효한 JSON임을 보장합니다.

  • seed: 이 기능을 지정하면 시스템이 결정론적으로 샘플링하여 동일한 시드와 매개변수를 사용한 반복 요청이 동일한 결과를 반환하도록 최선을 다할 것입니다. 결정론은 보장되지 않으며, system_fingerprint 응답 매개변수를 참조하여 백엔드의 변경 사항을 모니터링해야 합니다.

  • temperature: 0에서 2 사이에서 사용할 샘플링 온도입니다. 0.8과 같이 값이 높으면 출력이 더 무작위적이고, 0.2와 같이 값이 낮으면 더 집중적이고 결정론적인 출력이 됩니다. 일반적으로 이 값이나 top_p 중 하나만 변경하는 것이 좋지만 둘 다 변경하지는 않는 것이 좋습니다.

  • top_p: temperature 를 이용한 샘플링의 대안으로, 핵 샘플링이라고 하며, 모델이 top_p 확률을 가진 토큰의 결과를 고려하는 방식입니다. 따라서 0.1은 상위 10% 확률을 구성하는 토큰만 고려한다는 의미입니다. 일반적으로 이 값이나 temperature 중 하나를 변경하는 것이 좋지만 둘 다 변경하는 것은 권장하지 않습니다.

completion = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[
        {
            "role": "system",
            "content": "당신은 파이썬 프로그래머입니다.",
        },
        {
            "role": "user",
            "content": "피보나치 수열을 생성하는 파이썬 프로그램을 작성해주세요.",
        },
    ],
)
print(completion.choices[0].message.content)
당신이 말하고 계신 피보나치 수열은 아마도 이전 두 항의 합으로 이루어진 수열을 말하는 것 같습니다. 

다음은 피보나치 수열을 생성하는 파이썬 프로그램입니다:

```python
def fibonacci(n):
    # 피보나치 수열을 저장할 리스트
    fib = [0, 1]

    # n이 2 이하일 경우 초기값을 반환
    if n <= 2:
        return fib[:n]

    # 피보나치 수열을 생성
    while len(fib) < n:
        next_num = fib[-1] + fib[-2]
        fib.append(next_num)

    return fib

# 사용자로부터 입력값을 받아 피보나치 수열을 생성
n = int(input("피보나치 수열을 생성할 길이를 입력하세요: "))
result = fibonacci(n)

print(result)
```

위의 코드를 실행하면 사용자로부터 피보나치 수열의 길이를 입력받은 후, 해당 길이의 피보나치 수열이 결과로 출력됩니다. 예를 들어, 사용자가 10을 입력하면 `[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]`가 출력됩니다.

③ 스트리밍(Streaming)

스트리밍은 실시간으로 데이터를 전송하고 수신하는 프로세스입니다. 이 기능을 사용하면, 대화형 모델이 토큰 단위로 응답을 생성하고, 사용자는 모델이 응답을 생성하는 과정을 실시간으로 볼 수 있습니다. 이는 특히 긴 답변을 생성하는 경우 유용하며, 사용자에게 대화가 더 자연스럽고 동적으로 느껴지게 만듭니다.


Jupyter Notebook에서 스트리밍 형식으로 실시간 답변을 출력하는 것은 다음과 같은 방법으로 수행할 수 있습니다.


이때, create() 함수내에 stream=True 옵션을 지정하면 됩니다.

또한, 토큰 단위로 실시간 출력을 위해서는 completion 을 순회하면서, choices.delta.content 를 출력해야 합니다.

completion = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[
        {
            "role": "system",
            "content": "당신은 파이썬 프로그래머입니다.",
        },
        {
            "role": "user",
            "content": "피보나치 수열을 생성하는 파이썬 프로그램을 작성해주세요.",
        },
    ],
    stream=True,  # 스트림 모드 활성화
)

final_answer = []

# 스트림 모드에서는 completion.choices 를 반복문으로 순회
for chunk in completion:
    # chunk 를 저장
    chunk_content = chunk.choices[0].delta.content
    # chunk 가 문자열이면 final_answer 에 추가
    if isinstance(chunk_content, str):
        final_answer.append(chunk_content)
        # 토큰 단위로 실시간 답변 출력
        print(chunk_content, end="")
피보나치 수열은 이전 두 숫자의 합으로 이루어진 수열입니다. 처음 두 숫자는 보통 0과 1입니다. 이를 파이썬 프로그램으로 구현해보겠습니다.

```python
def fibonacci(n):
    fib_sequence = [0, 1] # 처음 두 숫자 0과 1로 시작
    for i in range(2, n):
        fib_sequence.append(fib_sequence[i-1] + fib_sequence[i-2])
    return fib_sequence

n = int(input("몇 개의 피보나치 수열을 출력하시겠습니까? "))
sequence = fibonacci(n)
print(sequence)
```

이 프로그램은 사용자로부터 출력할 피보나치 수열의 개수를 입력받아 해당 개수만큼의 피보나치 수열을 생성합니다. 이를 출력해주는 부분은 `print(sequence)`입니다.
# 전체 답변인 final_answer 를 문자열로 변환하여 출력
final_answer = "".join(final_answer)
print(final_answer)
피보나치 수열은 이전 두 숫자의 합으로 이루어진 수열입니다. 처음 두 숫자는 보통 0과 1입니다. 이를 파이썬 프로그램으로 구현해보겠습니다.

```python
def fibonacci(n):
    fib_sequence = [0, 1] # 처음 두 숫자 0과 1로 시작
    for i in range(2, n):
        fib_sequence.append(fib_sequence[i-1] + fib_sequence[i-2])
    return fib_sequence

n = int(input("몇 개의 피보나치 수열을 출력하시겠습니까? "))
sequence = fibonacci(n)
print(sequence)
```

이 프로그램은 사용자로부터 출력할 피보나치 수열의 개수를 입력받아 해당 개수만큼의 피보나치 수열을 생성합니다. 이를 출력해주는 부분은 `print(sequence)`입니다.

④ 이전 대화내용 기억(대화의 연속성)

client.chat.completions.create() 함수는 자체적으로 대화를 저장하는 기능이 없습니다.

따라서, 이전 문맥(context)을 기억하면서 대화를 이어나가기 위해서는(일반적인 ChatBot을 생각하시면 됩니다) 다음과 같이 messages 옵션에 메시지를 추가해야 합니다.


messages는 대화의 각 부분을 구성하는 데 사용되며, 각 메시지는 “role”과 “content”라는 두 가지 주요 요소를 포함하는 딕셔너리 형태로 되어 있습니다. 이 구조를 사용하면 복잡한 대화 흐름을 더 명확하게 관리하고 조작할 수 있습니다.


각 “role”의 의미는 다음과 같습니다:

  • "system": 시스템 전역 설정에 대한 지시문을 포함합니다. 예를 들어, 모델에 특정한 페르소나(persona)를 부여하거나, 대화의 맥락을 설정하는 데 사용됩니다.

  • "user": 사용자의 입력을 나타냅니다. 이는 대화에서 사용자가 질문하거나 요청한 내용을 담고 있습니다.

  • "assistant": AI 모델(예: ChatGPT)의 응답을 나타냅니다. 이는 모델이 생성한 답변이나 정보를 포함합니다.

completion = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[
        {
            "role": "system",
            "content": "You are a helpful assistant. You must answer in Korean.",
        },
        {
            "role": "user",
            "content": "대한민국의 수도는 어디인가요?",
        },
    ],
)

print(completion.choices[0].message.content)
대한민국의 수도는 서울입니다.
def ask(question):
    completion = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {
                "role": "system",
                "content": "You are a helpful assistant. You must answer in Korean.",
            },
            {
                "role": "user",
                "content": question,  # 사용자의 질문을 입력
            },
        ],
    )
    # 답변을 반환
    return completion.choices[0].message.content
# 첫 번째 질문
ask("대한민국의 수도는 어디인가요?")
'대한민국의 수도는 서울입니다.'
# 두 번째 질문
ask("영어로 답변해 주세요")
'네, 어떤 도움이 필요하신가요?'

위의 답변에서 보듯이, GPT가 다음 질문에 대한 답변을 잘못했습니다. 이는 이전 대화내용에 대한 저장을 하지 않았기 때문입니다.


이를 해결하기 위해, 대화의 연속성을 유지하는 로직을 추가해야 합니다. 대화의 각 부분(사용자의 질문과 AI의 응답)을 messages 리스트에 순차적으로 추가함으로써, 챗봇은 이전의 대화 내용을 참조하여 적절한 답변을 생성할 수 있습니다.

completion = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[
        {
            "role": "system",
            "content": "You are a helpful assistant. You must answer in Korean.",
        },
        {
            "role": "user",
            "content": "대한민국의 수도는 어디인가요?",  # 첫 번째 질문
        },
        {
            "role": "assistant",
            "content": "대한민국의 수도는 서울입니다.",  # 첫 번째 답변
        },
        {
            "role": "user",
            "content": "이전의 답변을 영어로 번역해 주세요.",  # 두 번째 질문
        },
    ],
)

# 두 번째 답변을 출력
print(completion.choices[0].message.content)
'The capital of South Korea is Seoul.'

좀 더 깔끔하게 함수화하여 구현해 보겠습니다.

def ask(question, message_history=[], model="gpt-3.5-turbo"):
    if len(message_history) == 0:
        # 최초 질문
        message_history.append(
            {
                "role": "system",
                "content": "You are a helpful assistant. You must answer in Korean.",
            }
        )

    # 사용자 질문 추가
    message_history.append(
        {
            "role": "user",
            "content": question,
        },
    )

    # GPT에 질문을 전달하여 답변을 생성
    completion = client.chat.completions.create(
        model=model,
        messages=message_history,
    )

    # 사용자 질문에 대한 답변을 추가
    message_history.append(
        {"role": "assistant", "content": completion.choices[0].message.content}
    )

    return message_history
# 최초 질문
message_history = ask("대한민국의 수도는 어디인가요?", message_history=[])
# 최초 답변
print(message_history[-1])
{'role': 'assistant', 'content': '대한민국의 수도는 서울입니다.'}
# 두 번째 질문
message_history = ask("이전의 내용을 영어로 답변해 주세요", message_history=message_history)
# 두 번째 답변
print(message_history[-1])
{'role': 'assistant', 'content': 'The capital of South Korea is Seoul.'}

이번에는 이전 대화내용을 message_history 에 저장하여 전달하였습니다. 이전 대화내용을 저장하면서 message_history 를 통해 대화를 이어나갈 수 있습니다.

⑤ json_object 답변형식

이번에는 GPT 의 출력 형식을 지정하는 방법을 살펴보겠습니다. 다음의 예시는 프롬프트를 활용하여 답변을 JSON 형식으로 받는 예제 입니다.

completion = client.chat.completions.create(
    model="gpt-3.5-turbo-1106",
    messages=[
        {
            "role": "system",
            # 답변 형식을 JSON 으로 받기 위해 프롬프트에 JSON 형식을 지정
            "content": "You are a helpful assistant designed to output JSON. You must answer in Korean.",
        },
        {
            "role": "user",
            "content": "대한민국의 수도는 어디인가요?",
        },
    ],
    response_format={"type": "json_object"},  # 답변 형식을 JSON 으로 지정
)

print(completion.choices[0].message.content)
{"대답": "서울"}
response = client.chat.completions.create(
    model="gpt-3.5-turbo-1106",
    response_format={"type": "json_object"},
    messages=[
        {
            "role": "system",
            "content": "You are a helpful assistant designed to output JSON.",
        },
        {"role": "user", "content": "통계를 주제로 4지선다형 객관식 문제를 만들어주세요."},
    ],
    temperature=0.5,
    max_tokens=300,
)
print(response.choices[0].message.content)
{
  "question": "다음 중 통계의 기본 개념으로 옳지 않은 것은 무엇인가요?",
  "options": [
    "평균은 주어진 데이터의 중간값을 의미합니다.",
    "표준편차는 데이터의 산포도를 나타내는 지표입니다.",
    "상관관계는 두 변수 간의 관련성을 나타내는 지표입니다.",
    "표본오차는 표본 조사 결과의 신뢰도를 나타내는 지표입니다."
  ]
}

response_format={"type": "json_object"} 으로 출력시 json 형태로 출력되는 것을 확인할 수 있습니다.


json 형식으로 출력 값을 받으면 데이터베이스에 저장하거나 파일형태로 저장하는데 용이합니다.

response = client.chat.completions.create(
    model="gpt-3.5-turbo-1106",
    response_format={"type": "json_object"},
    messages=[
        {
            "role": "system",
            "content": "You are a helpful assistant designed to output JSON.",
        },
        {
            "role": "user",
            "content": "통계를 주제로 4지선다형 객관식 문제를 만들어주세요. 정답은 index 번호로 알려주세요. "
            "난이도는 [상, 중, 하] 중 하나로 표기해 주세요.",
        },
    ],
    temperature=0.5,
    max_tokens=300,
    n=5,
)

5개의 결과 값을 출력합니다.

for res in response.choices:
    print(res.message.content)
{
  "question": "다음 중 통계의 기본 용어로 옳지 않은 것은?",
  "options": ["평균", "분산", "중앙값", "표준편차"],
  "answer_index": 1,
  "difficulty": "중"
}
{
  "question": "다음 중 통계학에서 가장 많이 사용되는 중심 경향성을 나타내는 지표는 무엇인가요?",
  "options": ["평균", "중앙값", "최빈값", "표준편차"],
  "answer_index": 0,
  "difficulty": "중"
}
{
  "question": "다음 중 통계학에서 사용되는 중심 경향성을 나타내는 지표는 무엇인가요?",
  "options": ["표준편차", "분산", "평균", "중앙값"],
  "answer_index": 2,
  "difficulty": "중"
}
{
  "question": "다음 중 통계학에서 사용되는 중심 경향성을 나타내는 지표는 무엇인가요?",
  "options": ["표준편차", "평균", "분산", "중앙값"],
  "answer_index": 1,
  "difficulty": "하"
}
{
  "question": "다음 중 통계학에서 사용되는 중심 경향성을 나타내는 지표는 무엇인가요?",
  "options": ["표준편차", "평균", "분산", "중앙값"],
  "answer_index": 1,
  "difficulty": "하"
}

아래의 코드는 json 라이브러리를 사용하여 JSON 형식의 답변을 파이썬 객체로 변환하는 코드입니다.

import json

# JSON 형식의 답변을 파이썬 객체로 변환
json_obj = json.loads(res.message.content)
json_obj
{'question': '다음 중 가장 큰 음의 정수는?',
 'options': ['-1', '-5', '-3', '-2'],
 'answer_index': 1,
 'difficulty': '하'}
# type 함수로 json_obj 의 타입을 확인: dict 타입
type(json_obj)
dict
# 모든 json 형식의 답변을 파이썬 객체로 변환
json_result = [json.loads(res.message.content) for res in response.choices]
json_result
[{'question': '다음 중 통계학에서 사용되는 가장 기본적인 요약 통계량은 무엇인가요?',
  'options': ['평균', '중앙값', '최빈값', '표준편차'],
  'answer_index': 0,
  'difficulty': '하'},
 {'question': '다음 중 통계학에서 사용되는 기술로 옳지 않은 것은?',
  'options': ['평균', '중앙값', '분산', '면적'],
  'answer_index': 3,
  'difficulty': '하'},
 {'question': '다음 중 통계학에서 사용되는 가설검정 방법이 아닌 것은?',
  'options': ['t-검정', 'ANOVA', '카이제곱 검정', '상관분석'],
  'answer_index': 3,
  'difficulty': '중'},
 {'question': "다음 중 통계 용어로 '평균'을 영어로 표현한 것은?",
  'options': ['Average', 'Median', 'Mode', 'Range'],
  'answer_index': 0,
  'difficulty': '하'},
 {'question': '다음 중 가장 큰 음의 정수는?',
  'options': ['-1', '-5', '-3', '-2'],
  'answer_index': 1,
  'difficulty': '하'}]

아래는 Pandas 라이브러리를 사용하여 Python dictionary를 데이터프레임으로 변환하는 코드입니다.

import pandas as pd

# 데이터프레임으로 변환
df = pd.DataFrame(json_result)
df.head()
# 데이터프레임을 csv 파일로 저장
df.to_csv("stats_quiz.csv", index=False)
# 데이터프레임을 엑셀 파일로 저장
df.to_excel("stats_quiz.xlsx", index=False)

댓글남기기