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 수를 줄일 수 있습니다.

OpenAI API function_call 사용하기”에 대한 2개의 생각

    1. plusha 글의 글쓴이

      감사합니다. 최근 OpenAI API는 개념은 그대로지만 함수 사용법이 약간 달라졌으니 사용하실 때 API 문서를 참고하세요^^

      응답

Sang Y. Yang님에게 덧글 달기 응답 취소