Tools
- OpenAI: https://ai.google.dev/gemini-api/docs/function-calling?hl=ko&example=meeting
Function calling (also known as tool calling) provides a powerful and flexible way for OpenAI models to interface with external systems and access data outside their training data.
- Google: https://platform.openai.com/docs/guides/function-calling?api-mode=chat
Function calling lets you connect models to external tools and APIs.
- AWS: https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use.html
OpenAI, Google 의 문서를 읽어보면 Function Calling(Tool calling)은 결국 LLM 모델들이 외부 시스템과 상호작용(Interface), 연결(Connect) 하고 외부 데이터에 접근하기 위한 방법임을 알 수 있다.
1. Tools 이해하기
간단히 핵심만 보면 LLM 모델에 API를 호출할 때 tools(function) 를 정의하고 우리가 어떤 tools 가지고 있는지 전달 하는 것이 핵심이다. OpenAI의 Chat Completions API 를 살펴보면 Request Body 에 tools 를 전달할 수 있다.
이 코드에서 tools 를 전달하는 것을 볼 수 있다. response = openai.chat.completions.create(model=”gpt-oss:20b-cloud”, messages=messages, tools=tools). 그리고 LLM 은 tool 호출이 필요하다고 판단되는 경우 finish_reason 에 tool_calls 를 전달한다.
그럼 적당히 tool_calls 인 경우 어떤 tool 을 호출하도록 처리해주면 되는데 바로 아래의 코드가 그렇다.
def chat(message, history):
messages = [{"role": "system", "content": system_prompt}] + history + [{"role": "user", "content": message}]
done = False
while not done:
# This is the call to the LLM - see that we pass in the tools json
response = openai.chat.completions.create(model="gpt-oss:20b-cloud", messages=messages, tools=tools)
finish_reason = response.choices[0].finish_reason
print(f"response.choices: {response.choices}")
# If the LLM wants to call a tool, we do that!
if finish_reason=="tool_calls":
message = response.choices[0].message
print(f"message: {message}")
tool_calls = message.tool_calls
results = handle_tool_calls(tool_calls)
messages.append(message)
messages.extend(results)
else:
done = True
return response.choices[0].message.content
tools 는 문서[3]에 따라 다음과 같은 형식을 가진다.
{
"tools": [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "특정 위치의 현재 날씨 정보를 가져옵니다",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "도시 이름 (예: Seoul, Tokyo)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "온도 단위"
}
},
"required": ["location"]
}
}
},
{
"type": "function",
"function": {
"name": "record_current_weather",
"description": "특정 위치의 현재 날씨 정보를 기록하고 알람을 보내줍니다.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "도시 이름 (예: Seoul, Tokyo)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "온도 단위"
}
},
"required": ["location"]
}
}
}
]
}
2. 날씨 정보를 받아오는 tool을 구현한 테스트
다음 코드를 위해서 아래의 내용이 필요하다.
- LLM - Ollama 로컬 환경
- Pushover API (무료)
- Openweather API (무료)
아래 코드에서 tool(function)에 해당 하는 건 get_current_weather, record_current_weather 함수다.
LLM에 이런 tool을 tools 로 정의하여 전달하며, system_prompt 에 만약 특정 지역의 현재 날씨를 모른다면 get_current_weather tool 을 이용하고, 사용자가 알람을 받기 원한다면 record_current_weather 함수를 이용하라고 가이드하고 있다.
# imports
from dotenv import load_dotenv
from openai import OpenAI
import json
import os
import requests
from pypdf import PdfReader
import gradio as gr
import random
# The usual start
load_dotenv(override=True)
openai = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama"
)
model_name="gpt-oss:20b-cloud"
# For pushover (알람을 받기 위해)
# Pushover user found and starts with u
# Pushover token found and starts with a
pushover_user = os.getenv("PUSHOVER_USER")
pushover_token = os.getenv("PUSHOVER_TOKEN")
pushover_url = "https://api.pushover.net/1/messages.json"
if pushover_user:
print(f"Pushover user found and starts with {pushover_user[0]}")
else:
print("Pushover user not found")
if pushover_token:
print(f"Pushover token found and starts with {pushover_token[0]}")
else:
print("Pushover token not found")
def push(message):
print(f"Push: {message}")
payload = {"user": pushover_user, "token": pushover_token, "message": message}
requests.post(pushover_url, data=payload)
def get_current_weather(location: str, unit: str = "celsius") -> str:
"""
OpenWeatherMap API를 사용하여 특정 위치의 현재 날씨 정보를 가져옵니다.
Args:
location: 도시 이름 (예: Seoul, Tokyo)
unit: 온도 단위 (celsius 또는 fahrenheit)
Returns:
JSON 형식의 날씨 정보 문자열
"""
# OpenWeatherMap API 키 (환경변수에서 가져오기)
api_key = os.getenv("OPENWEATHER_API_KEY")
if not api_key:
return json.dumps({"error": "OPENWEATHER_API_KEY not found"})
# unit 값 정규화 (metric, celsius 모두 celsius로 처리)
if unit.lower() in ["metric", "celsius"]:
unit = "celsius"
elif unit.lower() in ["imperial", "fahrenheit"]:
unit = "fahrenheit"
# 1. Geocoding API로 도시 이름을 위도/경도로 변환
geo_url = f"http://api.openweathermap.org/geo/1.0/direct?q={location}&limit=1&appid={api_key}"
try:
geo_response = requests.get(geo_url, timeout=5)
geo_response.raise_for_status()
geo_data = geo_response.json()
if not geo_data:
return json.dumps({"error": f"도시를 찾을 수 없습니다: {location}"})
lat = geo_data[0]['lat']
lon = geo_data[0]['lon']
# 2. Current Weather API 호출
weather_url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}&units=metric"
weather_response = requests.get(weather_url, timeout=5)
print(f"weather_response: {weather_response}")
weather_response.raise_for_status()
data = weather_response.json()
# 온도 변환
temp_c = data['main']['temp']
temp_f = (temp_c * 9/5) + 32
temperature = temp_c if unit == "celsius" else temp_f
weather_data = {
"location": data['name'],
"temperature": round(temperature, 1),
"unit": unit,
"condition": data['weather'][0]['description'],
"humidity": data['main']['humidity'],
"wind_speed": data['wind']['speed']
}
return json.dumps(weather_data)
except requests.exceptions.RequestException as e:
return json.dumps({"error": f"API 호출 실패: {str(e)}"})
def record_current_weather(location: str, unit: str = "celsius"):
weather_json = get_current_weather(location, unit)
weather_data = json.loads(weather_json)
if "error" in weather_data:
push(f"Error: {weather_data['error']}")
return {"recorded": "error", "message": weather_data['error']}
push(f"Location: {weather_data['location']}\nTemperature: {weather_data['temperature']}°{weather_data['unit'][0].upper()}\nCondition: {weather_data['condition']}")
return {"recorded": "ok"}
get_current_weather_json = {
"name": "get_current_weather",
"description": "특정 위치의 현재 날씨 정보를 가져옵니다",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "도시 이름 (예: Seoul, Tokyo)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit", "metric", "imperial"],
"description": "온도 단위 (celsius/metric 또는 fahrenheit/imperial)"
}
},
"required": ["location"]
}
}
record_current_weather_json = {
"name": "record_current_weather",
"description": "특정 위치의 현재 날씨 정보를 기록하고 알람을 보내줍니다.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "도시 이름 (예: Seoul, Tokyo)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "온도 단위"
}
},
"required": ["location"]
}
}
tools = [{"type": "function", "function": get_current_weather_json},
{"type": "function", "function": record_current_weather_json}]
def handle_tool_calls(tool_calls):
results = []
for tool_call in tool_calls:
tool_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f"Tool called: {tool_name}", flush=True)
tool = globals().get(tool_name)
result = tool(**arguments) if tool else {}
results.append({"role": "tool","content": json.dumps(result),"tool_call_id": tool_call.id})
return results
# 여기서 도구의 prompt 를 정의해줄 수 있다.
system_prompt = f"You are acting as weather caster \
If you don't know the weather in a specific city, you need to check the current weather in that city and answer. Since you don't know the current weather, you can check it using the get_current_weather tool.\
Also, if the user wants you to record the weather information and send an alert, you can use the record_current_weather tool to do so. When you decide to call record_current_weather function, then don't need to call get_current_weather tool.\
If the person is asking for the weather for a large area (for example, tell me the current weather in the United States), be sure to ask for the city again before calling the tool."
def chat(message, history):
messages = [{"role": "system", "content": system_prompt}] + history + [{"role": "user", "content": message}]
done = False
while not done:
# This is the call to the LLM - see that we pass in the tools json
response = openai.chat.completions.create(model="gpt-oss:20b-cloud", messages=messages, tools=tools)
finish_reason = response.choices[0].finish_reason
print(f"response.choices: {response.choices}")
# If the LLM wants to call a tool, we do that!
if finish_reason=="tool_calls":
message = response.choices[0].message
print(f"message: {message}")
tool_calls = message.tool_calls
results = handle_tool_calls(tool_calls)
messages.append(message)
messages.extend(results)
else:
done = True
return response.choices[0].message.content
gr.ChatInterface(chat, type="messages").launch()
3. 테스트 결과
위 코드를 실행하면 Gradio 인터페이스를 통해 LLM 모델과 채팅을 시작할 수 있고 첫 번째 날씨 요청에 대한 테스트 결과는 다음과 같다.
Test1

서울 날씨를 물어보니 get_current_weather 함수를 통해 날씨를 받아 온것을 알 수 있다. response.choices 를 살펴보면 첫 번째 날씨 요청을 받으면 finish_reason 이 tool_calls 인 것을 알 수 있다. 이 때 handle_tool_calls 를 통해 함수가 호출된것을 알 수 있다.
Test2

부산 날씨에 대한 알람을 요청하니 record_current_weather tool 이 호출된 것을 알 수 있으며, record_current_weather 함수의 push 함수 호출을 통해

실제 Pushover 앱을 통해 핸드폰으로 알람을 받게 될 수 있다.
4. Tool 활용의 의미
다시 처음으로 돌아와서. OpenAI 와 Google 에서는 Tool calling(Function calling)에 대해서 아래와 같이 정의 했다.
Function calling (also known as tool calling) provides a powerful and flexible way for OpenAI models to interface with external systems and access data outside their training data.
Function calling lets you connect models to external tools and APIs.
즉, LLM 모델 자체가 현재 날씨를 알 수는 없지만 외부 도구(get_current_weather, record_current_weather)를 통해 외부 시스템(openweathermap.org)과 인터페이스 및 연동되어 사용자에게 현재 날씨 정보를 제공할 수 있게 되었다. 모든 외부의 호출 가능한 API 는 LLM과 연동되어 사용할 수 있음을 알 수 있으며, 다양한 tool 을 정의하고 LLM 모델과 연동함으로서 LLM 모델을 더 강력하고 유연하게 사용할 수 있을 것으로 예상할 수 있었다.
📚 References
[1] OpenAI Function Calling
- https://platform.openai.com/docs/guides/function-calling
[2] Google Function Calling
- https://platform.openai.com/docs/guides/function-calling?api-mode=chat
[3] OpenAI Chat Completions API
- https://platform.openai.com/docs/api-reference/chat/create
[4] Udemy - AI Engineer Agentic Track
- https://www.udemy.com/course/the-complete-agentic-ai-engineering-course