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 |
댓글