본문 바로가기
GenAI

[OpenAI API] Function Calling

by 룰루셩 2025. 1. 11.

2023년 하반기쯤 AI팩토리 김태영 대표님의 Function Calling을 사용한 Switch bot 제어하는 수업을 들었다.

이때 들은 수업을 바탕으로 강의 내용을 정리하다보니, OpenAI 코드가 몇가지 바뀐 부분이 있어서 다시 정리해보려고 한다.

 

그리고 이 포스팅에서는 Chat Completions에 대한 내용을 적지 않을 것이다.

이전 게시글에 작성해두었으니 참고하면 좋겠다.

 

2025.01.11 - [GenAI] - [OpenAI API] Chat Completions 사용해보기

 

1) Function Calling이란?

  • LLM을 사용할 때 외부 함수를 사용할 수 있는 기능이다. 
  • LLM(GPT)에 함수를 미리 알려주면 필요할 때 함수를 LLM이 불러서 사용한다.
  • 예를 들면 날씨를 Function calling 사용하지 않은채 물어본다면 왼쪽과 같이 답변을 못한다.
  • Function calling을 사용하면 오른쪽과 같이 답변을 해준다.

 

2) Function Calling 작동 원리

  • GPT에게 함수를 미리 알려주면 필요할 때 함수를 GPT가 불러서 사용한다.
  • OpenAI API를 사용할 때 사용자가 가지고 있는 함수를 활용할 수 있게 해준다.

여기서 중요한 포인트는!!!

- GPT가 함수를 실행하지 않는다.
- GPT는 함수를 실행하지 않고 함수를 실행할 때 필요한 인자를 알려준다.
- 실제 실행은 사용자가 직접하고 결과를 GPT에게 다시 알려준다.
- GPT는 결과를 활용한 응답을 다시 사용자에게 알려준다.

 

이 블로그에서 정말 쉽게 예시를 들어주어서 가져왔다.

사용자: GPT야, ‘get_fruit_price’라는 함수를 알려줄게.
            이 함수는 과일 가격을 알려주는 함수야.
            이 함수가 필요하면 과일 이름을 이야기 하도록 해.
GPT: 네 알겠습니다.
사용자: GPT야, 배 8개를 사려면 얼마야?
GPT: 함수 ‘get_fruit_price’를 실행해주세요. 과일 이름은 “배” 입니다.
사용자: 함수 실행결과, “배는 800원”
GPT: 배는 1개에 800원 이니까 8개 사면 6400원 입니다.

 

 

 

어떤 함수를 실행하면 좋을지 사용자에게 알려주면, 사용자는 그 함수를 실행해서 실행결과를 알려준다.

실행 결과를 활용해서 GPT는 다시 질문에 대한 답변을 해준다.

 

OpenAI 블로그에서는 다음과 같이 도식화해서 보여준다.

 

3) 코드

1️⃣ 함수를 정의한다.

  • 과일 이름을 인풋으로 넣으면 과일의 가격을 알려주는 함수
def get_fruit_price(fruit_name):
    fruit_prices = {
    '사과': '1000',
    '바나나': '500',
    '오렌지': '750',
    '배': '800'
    }
    if fruit_name in fruit_prices:
        return fruit_prices[fruit_name]

 

2️⃣ 함수에 대한 설명을 형식에 맞춰 작성한다.

  • 다음 형식에 맞춰서 함수에 대한 설명을 작성한다. (이 형식에 맞춰서 작성하는건 매우 중요하다. 저번에 이거 없이 적은 학생의 오류를 고치는데 시간을 많이썼는데 이 형식을 안맞춘게 문제였다ㅠㅠ)
  • 함수의 이름 (위에서 정의한 함수)
  • 함수에 대한 설명: 이 함수가 어떤 함순지 설명한다.
  • parameter에는 이 함수에 들어가는 인자를 정의해주어야한다. 여기서는 과일 이름을 넣으면 과일의 가격을 알려주는 함수니까, 과일 이름이 parameter로 꼭 들어가야하니 required에도 parameter인 fruit_name을 넣어준다.
use_functions = [
   {
       "type": "function",
       "function":{
           "name": "get_fruit_price", # 함수 이름
           "description": "Returns the current price of the specified fruit.", # 함수에 대한 설명
           "parameters": {
               "type": "object",
               "properties": {
		    # 함수에서 인자 설명
                   "fruit_name": {
                       "type": "string",
                       "description": "The name of the fruit to retrieve the price for (e.g., '사과', '바나나')."
                   }
               },
               "required": ["fruit_name"] 
           }
       }
   }
]

 

 

3️⃣ GPT에게 질문을 해서 어떤 함수를 불러야할지 알아낸다.

  • chat completions를 사용해서 할건데, 이전 포스팅과 달라진건 tools라는 인자를 사용했다는 것이다.
  • tools = use_functions에서 use_functions는 위에서 정의한 것이다.
messages = [
{"role": "system", "content": "You are a helpful assistant. Use the supplied tools to retrieve fruit prices for the user."},
{"role": "user", "content": "Hi, can you tell me the price of 3 apples?"}
]

client = OpenAI()

response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=use_functions
            )

response_message = response.choices[0].message
tool_calls = response_message.tool_calls

 

위의 함수를 실행하면, response_message에는 아래와 같은 내용이 담겨있다.

  • 어떤 함수를 어떤 인자로 사용해서 부를지 알려준다. 
  • response_message 중 tool_calls를 보면 된다. 
ChatCompletionMessage(content=None, refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_nYd7gDnXH8lwM79SIYFomTVH', function=Function(arguments='{"fruit_name":"사과"}', name='get_fruit_price'), type='function')])  

 

4️⃣ 함수를 실행한다.

 tool_calls에 위에 처럼 어떤게 들어있으면 코드가 실행된다.

 그리고 위에서 정의한 함수를 available_functions와 같이 dictionary로 묶어준다.

 그 다음은 for문을 사용하였는데, 여러 개의 함수가 tool_calls에 담길 수 있어서 그런 것이다. (이건 뒤에서 다시 설명하겠다.)

 tool_call에 담긴 것들 가져오기

  • function_name을 불러온다. step 3에서 이야기했듯이 tool_calls를 불러왔는데, 그 중에 function에는 arguments와 name이 있다. 이걸 불러오는게 tool_call.function.name이다.
  • function_to_call은 실행할 함수를 지정해주는 건데, 위에서는 함수 이름이 string으로 저장되어 있었고, 이걸 available_functions에 있는 데이터로 인덱싱해서 실제 함수를 매칭시킨다.
  • function_args도 name과 같이 tool_call.function.args로 불러온다.

 함수를 실행한다.

  • 그럼 함수 실행 결과가 function_response에 담긴다.
  • 아래에 써있는 것과 같이 function_response는 '1000'으로 나온다.
  • 함수 실행 결과는 string으로 나오도록 함수를 짜는 것이 오류가 안난다.

⑥ 함수 결과와 관련된 내용을 메시지에 저장을 해주어야한다.

  • messages에 response_message도 저장해서 어떤 함수를 실행해야할지 저장하였고, 함수 실행 결과 값이 무엇인지도 저장을 하였다.

 

↓↓↓ 코드 복사 ↓↓↓

더보기

(설명을 위해서 코드는 여기에 붙여놓고 캡쳐된 코드를 여기에 붙여넣겠다.)

proc_messages = []

if tool_calls:
    available_functions = {
        "get_fruit_price": get_fruit_price
        }
    for tool_call in tool_calls:
        messages.append(response_message) 

        function_name = tool_call.function.name
        function_to_call = available_functions[function_name]
        function_args = json.loads(tool_call.function.arguments)

        function_response = function_to_call(**function_args)  # 함수 실행

        proc_messages.append(
                            {
                            "tool_call_id": tool_call.id,
                            "role": "tool",
                            "name": function_name,
                            "content": function_response,
                            }
                            )
        messages += proc_messages

5️⃣ 함수를 실행시키고 얻은 결과를 포함해서 다시 GPT에 질문을 한다.

  • 다시 새로운 chat_completions를 만들어준다.
  • 이때 메시지에는 위에서 실행한 내용들이 들어간다.
second_response = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=messages,
    )

 

Messages에는 처음에 한 질문, GPT가 대답한 함수 호출 정보, 함수 실행 결과가 포함된다.

 

⭐️ second_response의 결과는?

second_response.choices[0].message.content

  >> The price of 3 apples is 3,000 won, as each apple costs 1,000 won.

 

 

↓↓ gradio를 사용해서 챗봇 UI 만드는 전체 코드를 아래에 붙여놓았다.

더보기
def get_fruit_price(fruit_name):
    fruit_prices = {
        '사과': '1000',
        '바나나': '500',
        '오렌지': '750',
        '배': '800'
    }
    if fruit_name in fruit_prices:
        return fruit_prices[fruit_name]
        
use_functions = [
    {
        "type": "function",
        "function": {
            "name": "get_fruit_price",
            "description": "Returns the current price of the specified fruit.",
            "parameters": {
                "type": "object",
                "properties": {
                    "fruit_name": {
                        "type": "string",
                        "description": "The name of the fruit to retrieve the price for (e.g., '사과', '바나나')."
                    }
                },
                "required": ["fruit_name"]
            }
        }
    }
]

def ask_openai(llm_model, messages, user_message, functions = ''):
    client = OpenAI()
    proc_messages = messages

    if user_message != '':
        proc_messages.append({"role": "user", "content": user_message})

    if functions == '':
        response = client.chat.completions.create(model=llm_model, messages=proc_messages, temperature = 1.0)
    else:
        response = client.chat.completions.create(model=llm_model, messages=proc_messages, tools=functions, tool_choice="auto") # 이전 코드와 바뀐 부분

    response_message = response.choices[0].message
    tool_calls = response_message.tool_calls

    if tool_calls:
        # Step 3: call the function
        # Note: the JSON response may not always be valid; be sure to handle errors

        available_functions = {
            "get_fruit_price": get_fruit_price
        }

        messages.append(response_message)  # extend conversation with assistant's reply

        # Step 4: send the info for each function call and function response to GPT
        for tool_call in tool_calls:
            function_name = tool_call.function.name
            function_to_call = available_functions[function_name]
            function_args = json.loads(tool_call.function.arguments)


            print(function_args)

            if 'user_prompt' in function_args:
                function_response = function_to_call(function_args.get('user_prompt'))
            else:
                function_response = function_to_call(**function_args)

            proc_messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": function_response,
                }
            )  # extend conversation with function response
        second_response = client.chat.completions.create(
            model=llm_model,
            messages=messages,
        )  # get a new response from GPT where it can see the function response

        assistant_message = second_response.choices[0].message.content
    else:
        assistant_message = response_message.content

    text = assistant_message.replace('\n', ' ').replace(' .', '.').strip()


    proc_messages.append({"role": "assistant", "content": assistant_message})

    return proc_messages, text

 

4) Playground에서 함수 작동 확인해보기 

  • https://platform.openai.com/playground/chat?models=gpt-4o
  • OpenAI에서는 Playground라는 UI 환경에서 openai 패키지로 서비스하는 것들을 테스트해볼 수 있다. 
  • api에 돈이 채워져 있어야 한다. 
  • Function calling이 어떻게 작동하는지도 확인할 수 있다. 
  • 다만, 함수를 정의하는 건 아니고 이 함수를 실행하라는 것까지만 나오고 우리가 함수를 실행했을 때의 값을 입력해주어야 한다.

 

실제 화면을 통해 보겠다.

 

  • Chat completions 설정을 코드에서 했던 것처럼 오른쪽 탭에서 모델에 대한 설정을 할 수 있다.
  • Functions에 내가 사용할 함수에 대해서도 설명을 추가해준다. 

 

  • +Add 버튼을 누르면 다음과 같이 팝업이 뜨는데, 함수 정의를 해준다. 
  • Examples를 누르면 예시로도 몇개 있다.
  • Generate라는 버튼을 누르면, 말로 어떤 함수라고 프롬프트로 작성하면 저 포맷에 맞춰서 함수의 설명을 작성해주니 사용해도 좋을 것 같다.

 

 

※ 참고로, playground에는 함수 정의에 대해선 적지 않는다. (함수를 불러오고, 함수 불러오는 것이 잘 작동하는지만 확인 가능)

def get_weather(): 이거 정의하는 부분이 없다는 의미

 

  • 날씨를 알려주는 함수에 대한 설명을 작성한 후, 날씨를 물어보면 다음과 같이 대전을 인풋으로 넣어서 함수를 실행하도록 나온다.
  • 하지만 우리가 날씨 알려주는 함수를 정의하지는 않았으므로 response에 우리가 직접 값을 넣어주어야 한다. ⇒ 함수를 사용자가 실행해서 user의 message로 다시 전달해주는 원리
  • 위에서 코드로 설명한걸 눈으로 다시 확인할 수 있다.

이렇게 직접 타이핑 하도록 나온다.

 

처음 Function Calling을 배웠을 땐 정말 신기했다.

잘 사용한다면 너무나 유용한 기능이 될 것 같다.

 

'GenAI' 카테고리의 다른 글

[OpenAI API] api key 설정해주기  (0) 2025.01.11
[OpenAI API] Chat Completions 사용해보기  (0) 2025.01.11

댓글