RAG + AI 에이전트 개발 by LangChain, LangGraph
OpenAI의 채팅 API의 기본적인 사용법을 설명합니다. 먼저 OpenAI의 채팅 모델 개요부터 설명합니다.
이 글의 작성 시점(2024년 10월)에 ChatGPT의 유료 플랜 'ChatGPT Plus'에 가입하면 GPT-4o(대부분의 업무에 탁월함), GPT-4o with canvas(글쓰기 및 코딩협업), o1-preview(논리적 설명 가능), o1-mini(더 빠른 논리적 설명, GPT-4o mini(일상적인 작업을 더 빠르게 처리), GPT-4(레거시 모델)의 여섯 가지 '모델'을 선택할 수 있습니다. 무료 플랜에서는 GPT-4o mini라는 모델이 사용됩니다.
이 모델들은 ChatGPT의 UI를 통해 사용할 수도 있고, API를 통해 사용할 수도 있습니다. 애플리케이션에 내장하여 사용할 때는 ChatGPT의 UI가 아닌 API를 사용하게 됩니다. 이 강좌에서는 GPT-4o와 GPT-4o mini를 사용하여 설명하려고 합니다.
GPT-4o 또는 GPT-4o-mini라는 명칭은 실제로는 모델 패밀리를 의미합니다. 실제로 API를 사용할 때는 gpt-4o나 gpt-4o-mini와 같은 이름으로 모델을 지정합니다.
GPT-4o는 OpenAI가 제공하는 가장 진보된 모델로 다양한 작업에서 뛰어난 성능을 발휘합니다. GPT-4o의 'o'는 'omni'의 약자로, 텍스트 입출력뿐만 아니라 이미지, 동영상, 음성 등 멀티모달 입출력을 포함하며, 텍스트 입출력에 국한되지 않고 이미지, 동영상, 음성 등 멀티모달 입출력을 포함합니다. 단, 필자가 9월말까지 체크한 시점에 GPT-4o는 텍스트 입출력 및 이미지 입력 기능만 공개되어 있습니다. 위 표에 나와 있듯이 모델마다 최대 입력 토큰 수(입력 텍스트의 최대 길이)와 최대 출력 토큰 수(출력 텍스트의 최대 길이)가 다릅니다. 토큰 수에 대해서는 나중에 설명하겠습니다. 우선은 단어 수나 문자 수에 가까운 수치라고 생각하시면 됩니다. 단, 모델마다 다른 요금이 책정되어 있습니다.
gpt-4o, gpt-4o-mini와 같은 모델은 공개된 시점에 그대로 유지되는 것이 아니라 지속적으로 업그레이드되고 있습니다. 모델의 특정 버전은 gpt-4o-2024-05-13, gpt-4o-2024-08-06과 같이 날짜가 포함된 스냅샷으로 제공되며, API 사용 시 gpt-4o와 같이 지정하는 경우, 2024년 8월에 gpt-4o의 새로운 스냅샷으로 gpt-4o-2024-08-06이 출시되었습니다. 그리고 o1-preview도 출시되면서 다양해졌습니다.
OpenAI의 텍스트 생성 API에는 'Completions API'와 'Chat Completions API' 두 가지가 있는데, Completions API는 이미 레거시(Legacy)로 분류되어 보통 Chat Completions API를 사용합니다. 이 강좌에서는 Completions API는 나중에 조금 다룰 정도로만 다루고, Chat Completions API를 중심으로 설명합니다.
Chat Completions API의 자세한 사용법은 나중에 설명하겠지만, 여기서는 간략하게 설명하겠습니다. 아주 간단하게 말하면, ChatGPT의 UI를 사용할 때와 마찬가지로 '입력 텍스트를 주고 응답 텍스트를 얻는' 방식입니다. 예를 들어, Chat Completions API에 대한 요청의 예는 다음과 같습니다.
Chat Completions API에서는 messages라는 배열의 각 요소에 역할별 콘텐츠를 넣는 형식으로 되어 있습니다. 예를 들어 위 예시의 경우, '“role”: “system”'으로 LLM의 동작에 대한 지시를 주고, 추가로 '“role”: “user”'로 대화를 위한 입력 메시지를 주고 있습니다. 또한, “role”: “assistant”를 사용하여 다음과 같이 user와 assistant(LLM)의 대화 이력을 포함한 요청을 보내기도 합니다.
사실 Chat Completions API 자체는 브라우저에서 사용할 수 있는 ChatGPT와 달리 무정형이며, 과거 요청의 대화 내역을 기반으로 응답하는 기능을 가지고 있지 않습니다. 대화 히스토리를 기반으로 응답을 원한다면, 이렇게 과거의 모든 대화 내용을 요청에 포함시켜야 합니다. 예를 들어, 위의 요청에 대해 다음과 같은 응답을 얻을 수 있습니다.
이 응답 예시에서는 CHOICES라는 배열 요소의 message 내용인 “예, 당신의 이름은 홍길동입니다. 특별히 말씀하실 것이 있으신가요?"라는 메시지의 내용이 LLM이 생성한 텍스트가 됩니다. 응답의 마지막에 있는 usage 부분에는 completion_tokens, 즉 출력 토큰 수, prompt_tokens, 즉 입력 토큰 수, total_tokens, 즉 총 토큰 수가 포함되어 있습니다. 이 입력과 출력 토큰 수에 따라 수수료가 발생합니다.
이 글을 작성하는 시점(2024년 10월말) 기준, Chat Completions API요금은 아래와 같습니다.
GPT-4o mini(gpt-4o-mini-2024-07-18)는 입력 1M 토큰당 0.15달러, 출력 1M 토큰당 0.6달러입니다. 그리고 GPT-4o(gpt-4o-2024-08-06)는 입력 1M 토큰당 2.5달러, 출력 1M 토큰당 10달러이다. 이 모델들 사이에는 15배 이상의 가격 차이가 있습니다.
실제 발생한 요금은 OpenAI 웹사이트에 로그인하여 [Dashboard]의 [Usage] 화면에 접속하여 확인할 수 있습니다.
관련URL: https://platform.openai.com/usage
또한, [Settings]의 [Limits] 화면에서 이후 요청이 거부되는 하드 리미트 및 알림 메일이 발송되는 소프트 리미트를 설정할 수 있습니다. 필요에 따라 설정하시기 바랍니다.
[참고] Batch API
GPT-4o와 GPT-4o mini를 사용하기 위해 Chat Completions API 외에 Batch API를 사용할 수도 있는데, Batch API는 Chat Completions API와 달리 비동기적으로 GPT-4o와 GPT-4o mini에 의한 출력이 생성됩니다. Batch API는 즉각적인 응답을 얻을 수 없는 대신 Chat Completions API의 절반의 요금으로 이용할 수 있으며, Batch API에 대한 자세한 내용은 공식 문서 다음 페이지를 참고하면 됩니다.
관련 URL: https://platform.openai.com/docs/guides/batch
GPT-4o나 GPT-4o mini, o1와 같은 모델은 텍스트를 '토큰'이라는 단위로 나누어 처리합니다. 토큰이 반드시 단어와 일치하는 것은 아닙니다. 후술할 tiktoken에서 확인해보면 GPT-4o에서는 'ChatGPT'라는 텍스트가 'Chat'과 'GPT'라는 두 개의 토큰으로 나뉘며, OpenAI 공식 문서에서는 대략적인 기준으로 영어 텍스트의 경우 1 토큰은 4글자에서 0.75단어 정도라고 합니다.
Chat Completions API의 응답을 보면 입력과 출력의 토큰 수가 실제로 몇 개였는지 확인할 수 있습니다. 하지만 Chat Completions API를 호출하지 않고도 토큰 수를 파악하고 싶을 때가 많습니다. 이럴 때 사용할 수 있는 것이 OpenAI의 Tokenizer와 tiktoken입니다. OpenAI가 웹사이트에서 제공하는 Tokenizer를 사용하면 입력한 텍스트가 토큰으로 어떻게 나뉘고 토큰으로 어떻게 나뉘고, 토큰 개수는 몇 개인지 확인할 수 있습니다. 단, 본서 작성 시점에 이 Tokenizer가 지원하는 것은 GPT-4o&GPT-4o mini, GPT-3.4&GPT-4를 지원합니다.
관련 URL: https://platform.openai.com/tokenizer
OpenAI가 공개하고 있는 Python 패키지 tiktoken을 사용하면 Python 프로그램으로 토큰 수를 확인할 수 있습니다. 앞서 소개한 Tokenizer와 달리 tiktoken에서는 GPT-4o나 GPT-4o mini의 경우에도 토큰 수를 확인할 수 있으며, tiktoken 패키지를 설치하고 다음과 같은 코드를 작성하면 토큰 수를 확인할 수 있습니다.
관련 URL: https://huyenchip.com/2023/04/11/llm-engineering.html
앞서 언급했듯이 영어 텍스트의 경우, 경험상 1 토큰은 4글자에서 0.75단어 정도라고 합니다. 즉, 단어 하나당 1~수 개의 토큰을 사용할 수 있다는 뜻입니다. 반면, 한국어의 경우 같은 내용의 텍스트도 토큰 수가 많아지기 쉽다고 합니다. 예를 들어, “LLM을 사용하여 멋진 것을 만드는 것은 쉽지만, 프로덕션에서 사용할 수 있는 것을 만드는 것은 매우 어렵다.” 라는 텍스트의 GPT-4 토큰 수를 한국어와 영어로 비교해 봅시다.
GPT-3.4 & GPT-4
이 예시에서는 한국어 텍스트는 61글자로 47개의 토큰으로, 1글자당 1개의 토큰 정도입니다. 이처럼 한국어는 영어보다 토큰 수가 많아지기 쉽습니다. GPT-4o에서는 이전 모델에 비해 한국어에서도 토큰 수를 줄일 수 있도록 개선되었으며, GPT-4o에서 이전 예제와 동일한 한국어와 영어 텍스트의 토큰 수는 아래 표와 같습니다.
GPT-4o & GPT-4o mini
GPT-4에서는 47토큰이었던 한국어 텍스트가 GPT-4o에서는 28토큰으로 줄었습니다.
Chat Completions API의 개요와 요금에 대해 이해했으니, 이제부터 Chat Completions API를 실제로 사용해 보겠습니다. 먼저 API를 사용해 볼 수 있는 Colab 환경을 준비합니다.
Chat Completions API를 사용하기 위해서는 OpenAI 웹사이트를 통해 등록하고 OpenAI의 API 키를 발급받아야 합니다. 먼저 OpenAI 웹사이트에 접속하여 화면 상단의 [Products]의 [API login]에서 계정을 생성하거나 로그인합니다.
화면 오른쪽 상단의 [Settings]을 클릭하고 [Billing]을 엽니다.
현재 OpenAI의 API는 선불로 크레딧을 구매하는 방식으로 운영되고 있습니다. 신용카드를 등록하고 크레딧을 구매해야 합니다. 앞으로 진행될 강의용을 진행할 목적으로 크레딧을 구매할 경우, 10달러 정도의 크레딧으로 충분할 가능성이 높습니다.
크레딧을 구매했다면 API 키를 생성합니다. 화면 우측 상단의 [Dashboard]를 클릭하고, 왼쪽 메뉴에서 [API keys]를 선택하면 API 키 목록 화면으로 이동합니다.
이 화면에서 [Create new secret key]버튼을 클릭하면 OpenAI의 API 키를 생성할 수 있습니다.
적절한 이름을 넣고 API키를 생성합니다. 여기서는 "agent-test"로 정했습니다.
API 키가 생성되면 복사하여 Google Colab을 열고, Google Colab 화면 왼쪽에 있는 '보안비밀'을 클릭하면 '보안비밀'을 저장할 수 있습니다. 'OPENAI_API_KEY'라는 이름으로 방금 복사한 API 키를 저장합니다.
OpenAI의 라이브러리와 따로 설명하는 LangChain은 OpenAI의 API 키로 OPENAI_API_KEY라는 환경 변수를 사용하도록 되어 있습니다. 따라서 Google Colab의 시크릿에 저장한 API 키를 OPENAI_API_KEY라는 환경 변수로 설정하는 코드를 작성합니다.
이 코드를 실행하면 API키 준비가 완료됩니다.
Chat Completions API를 사용하기 위해서는 대부분 OpenAI의 라이브러리를 사용하게 되는데, OpenAI 공식에서 Python과 TypeScript/JavaScript, .NET 라이브러리를 제공하고 있으며, 그 외에도 커뮤니티에서 다양한 언어의 라이브러리를 제공하고 있습니다. 다양한 언어의 라이브러리가 제공되고 있습니다. 이번에는 OpenAI 공식 Python 라이브러리를 사용하며, Google Colab에서 다음 명령을 실행하여 OpenAI의 라이브러리를 설치할 수 있습니다.
우선 아주 간단한 예로 gpt-4o-mini에서 응답을 얻기 위해 Google Colab에 다음과 같은 코드를 작성해 보겠습니다.
OpenAI의 라이브러리는 환경 변수 OPENAI_API_KEY에서 가져온 API 키를 사용하여 요청을 보냅니다. 요청에는 최소 model과 messages를 포함하게 되는데, model에는 gpt-4o, gpt-4o-mini와 같은 모델 이름을 지정하고, messages라는 목록의 각 요소에는 역할별 내용(텍스트)을 넣습니다. 예를 들어 위 예제의 경우, “”role“: ‘system’”으로 LLM의 동작에 대한 지시를 주고, 그 위에 “”role“: ‘user’”로 상호작용을 위한 입력 텍스트를 주고 있습니다.
위의 코드를 실행하면 다음과 같은 응답을 얻을 수 있습니다(응답 내용은 실행할 때마다 다를 수 있습니다).
응답 중 choices라는 배열 요소의 message 내용을 참조하면 LLM이 생성한 텍스트 “안녕하세요, 홍길동님!. 만나서 반갑습니다. 어떻게 도와드릴까요?"라는 텍스트가 포함되어 있습니다. 이처럼 모델을 지정하여 입력 텍스트에 대한 응답 텍스트를 얻는다는 점에서는 ChatGPT와 동일합니다.
앞서 언급했듯이 Chat Completions API는 스테이트리스(stateless)이며, 과거 요청의 대화 기록을 기반으로 응답하는 기능을 가지고 있지 않습니다. 대화 히스토리를 기반으로 응답하기를 원한다면, 과거 대화 내용을 요청에 포함시켜야 한다. 예를 들어, 인간 입력은 “role”: “user”, AI 입력은 “role”: “ assistant ”로 다음과 같은 요청을 보내면 됩니다.
"안녕하세요! 저는 홍길동이라고 합니다."라고 자기소개를 한 후, 다시 한번 "제 이름을 아시나요?"라고 물어보면 순서로 진행됩니다. 이 내용으로 실행해봅시다.
그러자 응답 문자는 “네, 홍길동님이라고 말씀해 주셨어요. 혹시 다른 질문이나 이야기가 있으신가요?"라는 문자가 왔고, 대화 내역을 바탕으로 답변이 왔습니다.
ChatGPT에서는 GPT-4o 또는 GPT-4o mini의 응답이 점진적으로 표시됩니다. 마찬가지로 Chat Completions API에서도 스트리밍으로 응답을 받을 수 있습니다. 스트리밍으로 응답을 받을 때는 요청에 stream= True라는 파라미터를 추가하면 됩니다. 샘플 코드는 다음과 같습니다.
다음은 Chat Completions API에서 model, messages, stream 외에 지정할 수 있는 몇 가지 파라미터를 소개합니다.
관련 URL: https://platform.openai.com/docs/api-reference/chat/create
LLM을 애플리케이션에 통합하여 사용할 때, JSON 형식으로 출력하고 싶은 경우가 종종 있는데, Chat Completions API의 'JSON 모드'를 사용하면 확실히 JSON 형식의 문자열을 출력할 수 있습니다. JSON이라는 문자열을 포함하고 response_format 파라미터에 {“type”: “json_object”}라는 값을 지정하면 됩니다. 샘플코드는 다음과 같습니다.
GPT-4o와 GPT-4o mini는 이미지 입력도 지원합니다. Chat Completions API 요청에 이미지 URL 또는 Base64로 인코딩된 이미지를 포함하면 이미지의 내용을 기반으로 한 응답을 얻을 수 있습니다.
관련 URL: https://platform.openai.com/docs/guides/vision
이미지 입력은 이미지 크기와 요청의 detail이라는 매개변수 설정에 따라 요금이 부과됩니다. 이 문서에서는 이미지 입력은 거의 사용하지 않으므로 자세한 내용은 생략하지만, 필요한 경우 위 공식 문서를 참고하시기 바랍니다.
Function calling은 2023년 6월 Chat Completions API에 추가된 기능입니다. 쉽게 말해, 사용 가능한 함수를 LLM에 알려주고, LLM이 '함수를 사용하고 싶다'는 판단을 내리게 하는 기능입니다(LLM이 함수를 실행하는 것이 아니라, LLM이 '함수를 사용하고 싶다'는 응답을 반환하는 것입니다), LLM에 JSON 등의 형태로 출력하게 하고, 그 내용을 바탕으로 프로그램 내 함수를 실행하는 등의 처리를 구현하고 싶을 때가 많습니다. 이런 경우에 LLM이 잘 응답하도록 API 구현과 모델을 미세하게 튜닝한 것이 Function calling입니다.참고로 LangChain에서는 Tool calling, Anthropic의 API에서는 Tool use라고 불립니다.
Function calling을 사용하여 함수 실행을 사이에 두고 LLM과의 상호작용을 아래 그림과 같이 표현할 수 있습니다.
처리 흐름은 먼저 사용 가능한 함수 목록과 함께 질문 등의 텍스트를 전송합니다. 이에 대해 LLM이 '함수를 사용하고 싶다'는 응답을 보내면, 파이썬 등의 프로그램으로 해당 함수를 실행합니다. 그 실행 결과를 포함한 요청을 다시 LLM에 보내면 최종 답변을 얻을 수 있습니다. 여기서 주의해야 할 점은 LLM은 어떤 함수를 어떻게 사용하고 싶은지 알려줄 뿐, 함수 실행은 Python 등을 이용해 Chat Completions API의 사용자 측에서 실행해야 한다는 점입니다.
Function calling에 대해서는 OpenAI 공식 문서에 샘플 코드가 있습니다. 여기서는 OpenAI 공식 문서에 있는 샘플 코드를 바탕으로 일부 수정한 코드를 조금씩 실행해 보겠습니다. 먼저 get_current_weather라는 지역을 지정하여 날씨를 얻을 수 있는 Python 함수를 정의합니다.
관련 URL: https://platform.openai.com/docs/guides/function-calling
다음으로 LLM이 사용할 수 있는 함수 목록을 정의합니다. 예를 들어, get_current_weather라는 함수에 대한 설명과 매개변수를 정의합니다.
이어서 “서울의 날씨는 어때요?” 라는 질문으로 Chat Completions API를 호출합니다. 이때 사용할 수 있는 함수 목록을 tools라는 인수로 전달합니다.
지금까지의 실행 예시에서는 LLM이 생성한 텍스트가 choices 요소의 message content에 포함되어 있었지만, 해당 부분이 null로 되어 있습니다. 대신 tool_calls라는 요소가 있는데, 'get_current_weather를 이런 인수로 실행하고 싶다'는 내용이 적혀 있습니다. 주어진 함수 목록과 입력 텍스트를 통해 LLM은 “이 질문에 답하기 위해서는 get_current_weather를 ‘{”location“:”Seoul“}’라는 인수로 실행해야 한다”고 판단한 것입니다. 이 응답을 얻은 것을 대화 히스토리로 messages에 추가해 둡니다.
그런데 LLM은 파이썬과 같은 함수를 실행할 수 있는 능력이 없습니다. 따라서 LLM이 사용하고 싶다고 응답한 get_current_weather 함수는 직접 실행해 주어야 하는데, LLM이 지정한 인수를 분석하여 해당 함수를 호출해 주어야 합니다.
이 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다.
{"location": "Seoul", "temperature": "10", "unit": null}
서울의 기온은 10도입니다. 다시 말하지만, 이것은 단순히 파이썬으로 함수를 실행한 것일 뿐, LLM은 함수를 실행할 수 없기 때문에 LLM이 사용하고 싶다고 판단한 함수를 LLM의 사용자 측에서 파이썬으로 실행해 준 것입니다. 위 코드에서는 for 루프의 마지막 처리로 함수 실행 결과를 “”role“:”tool“”로 대화 이력을 보관하는 messages에 추가했습니다. 이 시점의 messages를 표시해 봅시다.
이 코드를 실행하면 messages의 값이 다음과 같이 표시됩니다.
이 messages를 사용하여 Chat Completions API에 다시 한 번 요청을 보냅니다.
이처럼 Function calling을 사용하면 LLM이 필요에 따라 '함수를 사용하고 싶다'고 판단하고, 그 인수까지 생각해주는 것입니다. 그 내용을 바탕으로 여기서 함수를 실행하고 실행 결과를 포함해서 다시 LLM을 호출하면 LLM이 최종 답변을 돌려주는 것입니다.
함수 호출과 관련하여 Chat Completions API의 요청에는 'tool_choice'라는 매개변수도 있는데, tool_choice라는 매개변수에 “none”을 지정하면 LLM은 함수를 호출하는 것과 같은 응답을 하지 않고, 일반적인 tool_choice를 “auto”로 설정하면, LLM은 입력에 따라 지정된 함수를 사용해야 한다고 판단되면 함수 이름과 인수를 반환하게 됩니다. tool_choice 파라미터의 기본 동작은 tools를 제공하지 않으면 “none”, tools를 제공하면 “auto”로 설정되어 있습니다. 또한, 'tool_choice' 파라미터에는 '{“type”: “function”, “function”: {“name”: “<함수명>”}}'이라는 값을 지정할 수 있습니다. 이렇게 함수 이름을 지정하면 LLM이 지정한 함수를 호출하도록 강제할 수 있습니다.
© 2024 ZeR0, Hand-crafted & made with Damon JW Kim.
Profile: https://gaebal.site
개발문의: https://naver.me/GalVgGKH
블로그: https://blog.naver.com/beyond-zero