이전 포스팅에서 다룬 Assistants API의 기본 사용법과 Retrieval 도구 사용에 이어, 이번 글에서는 ‘함수 호출(Function calling)’ 도구의 활용법을 알아보고, 이를 통해 간단한 ‘해외직구 최저가 확인 챗봇’을 구현하는 과정을 소개합니다.
해외직구 최저가 확인 챗봇
OpenAI의 Assistants API는 다양한 애플리케이션 개발에 유용한 도구를 제공하며, 특히 ‘함수 호출’ 기능은 챗봇이 사용자 정의 함수를 활용할 수 있게 해주는 강력한 기능입니다. 본 글에서는 사용자가 어떤 물품을 구입하려고 할 때 해외 쇼핑몰의 최저가와 환율을 확인한 후에 한화로 최저가를 제공하는 ‘최저가 확인 챗봇’ 기능을 구현하려고 합니다. 이 예시는 실제 상품 검색이나 환율 조회 기능을 포함하지 않고, ‘함수 호출’ 기능의 사용법을 이해하는 데 중점을 둡니다.
Retrieval 도구를 사용한 챗봇 구현은 OpenAI Assistants API와 Retrieval로 챗봇 만들기: 5단계 완전 가이드 에 정리되어 있으니 확인해 보시면 좋을 것 같습니다.
함수호출 (Function calling)
‘함수 호출’ 기능은 Assistant가 사용자가 사전에 정의한 함수를 사용할 수 있도록 합니다. 이 기능의 핵심은 Assistant가 직접 함수를 호출하는 것이 아니라, 필요한 정보를 얻기 위해 사용자에게 함수 호출을 요청하고, 사용자가 제공한 결과를 바탕으로 응답을 생성한다는 점입니다.
참고로 OpenAI 사이트에서 확인할 수 있는 Function calling에 대한 기본적인 설명은 다음과 같습니다.
platform.openai.com/docs/assistants/tools/function-calling
개발과정 (Assistants API, Function calling)
개발을 위한 기본 설정 및 준비는 “OpenAI의 Assistants API: 인공지능 기반 ChatGPT 챗봇 개발의 새로운 지평“의 “Python으로 Assistants API 실행” 부분을 참조하세요.
JSON 파일 출력을 위한 함수
코드 실행 시 출력값이 줄바꿈이 되어 있지 않아 가독성을 높이기 위해 작성하였습니다.
def print_json(obj): json_str = obj.model_dump_json() parsed_json = json.loads(json_str) print(json.dumps(parsed_json, indent=4)) # JSON 객체를 보기 좋게 출력합니다.
Assistant 생성
OpenAI의 gpt-4-1106-preview
모델을 사용하여, ‘함수 호출’ 기능을 포함하는 Assistant를 생성합니다. 이 Assistant는 사용자로부터 최저가와 환율 정보를 받아 최종 가격을 계산하여 제공합니다. 최저가와 환율을 확인하는 2개의 함수를 구현하였습니다.
from openai import OpenAI import json import time # YOUR_OPENAI_API_KEY에는 자신의 OpenAI API 키를 입력 client = OpenAI(api_key="YOUR_OPENAI_API_KEY") assistant = client.beta.assistants.create( name="해외직구 최저가 확인 Python", # 귀하는 특정 품목에 대해 USD로 최저가를 확인하고 원화로 가격을 알려주는 정중하고 전문적인 챗봇입니다. 제공된 기능을 사용하여 질문에 답변해 주세요. instructions="You are a polite, professional chatbot that checks for the lowest price in USD for a specific item and tells you the price in KRW. Please use the functions provided to answer these questions.", model="gpt-4-1106-preview", tools=[ { "type": "function", "function": { "name": "getLowestPrice", "description": "Check the lowest price of product", "parameters": { "type": "object", "properties": { "product_name": {"type": "string", "description": "product name (e.g. iPhone 12 Pro Max 256GB)"}, "us_price": {"type": "number", "description": "us price (e.g. 777)"} }, # 함수를 실행하는데 필수인 property들을 나열합니다. "required": ["product_name", "us_price"] } } }, { "type": "function", "function": { "name": "getExchangeRate", # 현재 원/달러 환율 정보를 알려줍니다. "description": "It will inform you of the krw/usd exchange rate information.", "parameters": { "type": "object", "properties": { "exchange_rate" : {"type": "number", "description": "Won/dollar exchange rate (e.g. 1350)"} }, "required": ["exchange_rate"] } } } ] ) print_json(assistant)
tools
에 function
이 2개 포함되어 있는 것을 확인할 수 있습니다.
{ "id": "asst_cGIqhtO9s4aji3W6Qu0FshiB", "created_at": 1705975990, "description": null, "file_ids": [], "instructions": "You are a polite, professional chatbot that checks for the lowest price in USD for a specific item and tells you the price in KRW. Please use the functions provided to answer these questions.", "metadata": {}, "model": "gpt-4-1106-preview", "name": "\ud574\uc678\uc9c1\uad6c \ucd5c\uc800\uac00 \ud655\uc778 Python", "object": "assistant", "tools": [ { "function": { "name": "getLowestPrice", "description": "Check the lowest price of product", "parameters": { "type": "object", "properties": { "product_name": { "type": "string", "description": "product name (e.g. iPhone 12 Pro Max 256GB)" }, "us_price": { "type": "number", "description": "us price (e.g. 777)" } }, "required": [ "product_name", "us_price" ] } }, "type": "function" }, { "function": { "name": "getExchangeRate", "description": "It will inform you of the krw/usd exchange rate information.", "parameters": { "type": "object", "properties": { "exchange_rate": { "type": "number", "description": "Won/dollar exchange rate (e.g. 1350)" } }, "required": [ "exchange_rate" ] } }, "type": "function" } ] }
Message를 포함한 Thread 생성
아이폰을 해외 직구로 구매할 때 가격을 확인하는 메세지가 포함된 Thread 입니다.
thread = client.beta.threads.create( messages=[ { "role": "user", "content": "I want to buy 'iPhone 13 Pro Max 512GB' as a direct overseas purchase, please let me know the price(KRW)." } ] ) print_json(thread)
정상적으로 Thread가 생성되었습니다.
{ "id": "thread_JH7S0M4KkhVeM9d6rahyKAT4", "created_at": 1705986070, "metadata": {}, "object": "thread" }
Run 생성
메세지가 포함된 Thread를 실행해 보겠습니다.
run = client.beta.threads.runs.create( thread_id="thread_JH7S0M4KkhVeM9d6rahyKAT4", assistant_id="asst_cGIqhtO9s4aji3W6Qu0FshiB", ) print_json(run)
Run의 상태를 확인하는 코드
처음 Run을 생성하면 status
가 queued
상태입니다. 이후 진행 중일 때에는 status
가 in_progress
가 되고 답변이 완료되면 completed
상태가 됩니다. 함수호출 시에는 requires_action
상태가 추가되는데 요청을 수행하기 위해 함수호출이 필요한 상태임을 나타냅니다.
그러므로 queued
혹은 in_progress
인 경우 time.sleep(2)
으로 대기, required_action
시에는 사용자가 함수호출을 수행, completed
시에는 요청이 완료된 상태이므로 메세지의 결과를 확인합니다.
while True: run = client.beta.threads.runs.retrieve( thread_id="thread_JH7S0M4KkhVeM9d6rahyKAT4", run_id="run_tmDz2LrVyKETnXMMQqdpNtgD" ) if run.status in ['queued', 'in_progress']: time.sleep(2) else: print_json(run) break;
Run을 생성하고 작업이 진행되면 추가적인 함수 호출이 필요한 상태(required_action
)가 되어 다음과 같은 결과를 출력합니다. required_action
부분에서 어떤 함수 호출이 필요한지 확인할 수 있습니다.
{ "id": "run_tmDz2LrVyKETnXMMQqdpNtgD", "assistant_id": "asst_cGIqhtO9s4aji3W6Qu0FshiB", "cancelled_at": null, "completed_at": null, "created_at": 1705986120, "expires_at": 1705986720, "failed_at": null, "file_ids": [], "instructions": "You are a polite, professional chatbot that checks for the lowest price in USD for a specific item and tells you the price in KRW. Please use the functions provided to answer these questions.", "last_error": null, "metadata": {}, "model": "gpt-4-1106-preview", "object": "thread.run", "required_action": { "submit_tool_outputs": { "tool_calls": [ { "id": "call_me32SQDGzEHUp5J50BLwpKod", "function": { "arguments": "{\"product_name\":\"iPhone 13 Pro Max 512GB\",\"us_price\":0}", "name": "getLowestPrice" }, "type": "function" } ] }, "type": "submit_tool_outputs" }, "started_at": 1705986120, "status": "requires_action", "thread_id": "thread_JH7S0M4KkhVeM9d6rahyKAT4", "tools": [ { "function": { "name": "getLowestPrice", "description": "Check the lowest price of product", "parameters": { "type": "object", "properties": { "product_name": { "type": "string", "description": "product name (e.g. iPhone 12 Pro Max 256GB)" }, "us_price": { "type": "number", "description": "us price (e.g. 777)" } }, "required": [ "product_name", "us_price" ] } }, "type": "function" }, { "function": { "name": "getExchangeRate", "description": "It will inform you of the krw/usd exchange rate information.", "parameters": { "type": "object", "properties": { "exchange_rate": { "type": "number", "description": "Won/dollar exchange rate (e.g. 1350)" } }, "required": [ "exchange_rate" ] } }, "type": "function" } ], "usage": null }
Run 실행에 필요한 Function 확인
호출이 필요한 함수는 다음과 같이 확인 할 수 있습니다.
run = client.beta.threads.runs.retrieve( thread_id="thread_JH7S0M4KkhVeM9d6rahyKAT4", run_id="run_tmDz2LrVyKETnXMMQqdpNtgD" ) print(run.required_action.submit_tool_outputs.tool_calls)
결과는 다음과 같습니다. RequiredActionFunctionToolCall
의 id
는 함수를 호출하기 위해 필요한 값이 됩니다.
[RequiredActionFunctionToolCall(id='call_me32SQDGzEHUp5J50BLwpKod', function=Function(arguments='{"product_name":"iPhone 13 Pro Max 512GB","us_price":0}', name='getLowestPrice'), type='function')]
또한 필요한 함수를 확인하면서 Assistant 가 예측한 파라메터를 확인할 수 있는데 이번에는 product_name
과 us_price
값 중 us_price
값만 수동으로 설정합니다.
# arguments 값을 추출하고 JSON으로 파싱합니다. arguments = json.loads(run.required_action.submit_tool_outputs.tool_calls[0].function.arguments) # us_price 값을 수정합니다. arguments['us_price'] = 899 # 원하는 값으로 변경하세요. # 수정된 arguments 값을 출력합니다. print(json.dumps(arguments))
product_name
과 us_price
값이 적용된 다음과 같은 값을 얻을 수 있습니다.
{"product_name": "iPhone 13 Pro Max 512GB", "us_price": 899}
Function calling
output
에 product_name
과 us_price
를 넣고 앞서 확인한 함수의 id
로 함수를 호출합니다.
실제 상용 서비스를 개발하는 경우 최저가를 검색하는 기능은 자체 서비스 구현 혹은 크롤링을 하거나 지원하는 API가 있다면 이를 사용하고 환율은 다양한 환율 정보를 확인할 수 있는 API나 사이트가 존재하므로 이를 활용하여 값을 적용하면 됩니다.
run = client.beta.threads.runs.submit_tool_outputs( thread_id="thread_JH7S0M4KkhVeM9d6rahyKAT4", run_id="run_tmDz2LrVyKETnXMMQqdpNtgD", tool_outputs=[ { "tool_call_id": "call_me32SQDGzEHUp5J50BLwpKod", "output": json.dumps({"product_name": "iPhone 13 Pro Max 512GB", "us_price": 899}) } ] ) print_json(run)
Run의 status
를 확인해 보면 완료(completed
)가 아닌 다시 required_action
이 되는 것을 확인할 수 있습니다.
{ "id": "run_tmDz2LrVyKETnXMMQqdpNtgD", "assistant_id": "asst_cGIqhtO9s4aji3W6Qu0FshiB", "cancelled_at": null, "completed_at": null, "created_at": 1705986120, "expires_at": 1705986720, "failed_at": null, "file_ids": [], "instructions": "You are a polite, professional chatbot that checks for the lowest price in USD for a specific item and tells you the price in KRW. Please use the functions provided to answer these questions.", "last_error": null, "metadata": {}, "model": "gpt-4-1106-preview", "object": "thread.run", "required_action": { "submit_tool_outputs": { "tool_calls": [ { "id": "call_jRMxncU9D3ko0eJRDvj9Zvqx", "function": { "arguments": "{\"exchange_rate\":1350}", "name": "getExchangeRate" }, "type": "function" } ] }, "type": "submit_tool_outputs" }, "started_at": 1705986387, "status": "requires_action", "thread_id": "thread_JH7S0M4KkhVeM9d6rahyKAT4", "tools": [ { "function": { "name": "getLowestPrice", "description": "Check the lowest price of product", "parameters": { "type": "object", "properties": { "product_name": { "type": "string", "description": "product name (e.g. iPhone 12 Pro Max 256GB)" }, "us_price": { "type": "number", "description": "us price (e.g. 777)" } }, "required": [ "product_name", "us_price" ] } }, "type": "function" }, { "function": { "name": "getExchangeRate", "description": "It will inform you of the krw/usd exchange rate information.", "parameters": { "type": "object", "properties": { "exchange_rate": { "type": "number", "description": "Won/dollar exchange rate (e.g. 1350)" } }, "required": [ "exchange_rate" ] } }, "type": "function" } ], "usage": null }
이번에는 환율을 설정하는 함수를 호출합니다.
run = client.beta.threads.runs.submit_tool_outputs( thread_id="thread_JH7S0M4KkhVeM9d6rahyKAT4", run_id="run_tmDz2LrVyKETnXMMQqdpNtgD", tool_outputs=[ { "tool_call_id": "call_jRMxncU9D3ko0eJRDvj9Zvqx", "output": json.dumps({"exchange_rate": 1340}) } ] ) print_json(run)
Message 리스트 출력
환율을 설정하는 함수호출까지 완료하고 작업을 완료하고 Run의 status
가 completed
가 되면 메세지를 출력합니다.
Assistant는 사용자로부터 받은 최저가와 환율 정보를 기반으로 상품의 최저가를 계산하고, 이를 사용자에게 알려줍니다.
messages = client.beta.threads.messages.list( thread_id="thread_JH7S0M4KkhVeM9d6rahyKAT4", ) for i, message in enumerate(reversed(messages.data), start=1): print(f"\n# Message {i}:") for content in message.content: print(content.text.value)
결과는 다음과 같습니다. 아이폰의 가격이 899이고 환율이 1340일 때 가격은 1,205,260원이 됨을 알 수 있습니다.
# Message 1: I want to buy 'iPhone 13 Pro Max 512GB' as a direct overseas purchase, please let me know the price(KRW). # Message 2: The lowest price for an iPhone 13 Pro Max 512GB in USD is $899. Given the current exchange rate of 1 USD to 1340 KRW, the price in Korean Won would be: 899 (price in USD) x 1340 (exchange rate) = 1,205,260 KRW. Therefore, the price for the iPhone 13 Pro Max 512GB in Korean Won is approximately 1,205,260 KRW.
Thread 삭제
response = client.beta.threads.delete("thread_JH7S0M4KkhVeM9d6rahyKAT4") print_json(response)
Playground에서 Function calling 사용한 해외직구 최저가 확인 챗봇
생성된 Assistant들은 playform.openai.com의 assistants탭에서 확인할 수 있으며 신규 Assistant를 생성할 수도 있습니다.
Playground에서도 Assistant를 생성할 때 function을 포함시킬 수 있으며 메세지를 추가하고 Run을 실행시키면 getLowestPrice
와 getExchangeRate
를 호출하도록 요청합니다. 필요한 값을 기입한 후 submit 을 선택하면 최저가와 환율이 적용된 답변을 얻을 수 있습니다.
정리
지금까지 우리는 Assistants API의 ‘함수 호출’ 도구를 사용하여 간단한 기능을 구현하는 방법을 알아보았습니다. 이 과정을 통해, 우리는 어떻게 이 도구를 활용하여 사용자 정의 기능을 챗봇에 통합할 수 있는지에 대해 확인해 볼 수 있었습니다. 특히, 다른 서비스나 API들과 연동함으로써, 사용자에게 더욱 풍부하고 다양한 기능을 제공할 수 있는 챗봇을 만들 수 있을 것으로 보입니다.
그러나 이 과정에서 한글을 사용할 때 버그가 발생하거나 대답이 일관되지 않는 문제가 있었는데, 이는 Assistants API 자체의 문제인 것으로 보입니다. 이러한 문제들은 사용자 경험을 저해할 수 있으므로, OpenAI 측에서의 지속적인 개선이 요구됩니다.
이러한 문제에도 불구하고, Assistants API의 발전 가능성은 매우 크다고 할 수 있습니다. 현재는 beta 단계에 있지만, 앞으로 이러한 문제들이 수정되고 더 많은 기능이 추가된다면, 더욱 강력하고 다양한 애플리케이션 개발이 가능할 것으로 기대됩니다.
본 글은 GPTers에 게시되고 있습니다.
https://www.gpters.org/c/llm/assistants-api-function-calling