openai function calling 예제 — 실행 흐름과 형식
OpenAI API의 function calling은 모델이 함수를 직접 실행하는 게 아니라 "이 함수를 이 인자로 불러 달라"는 요청을 돌려주는 기능입니다. 흐름은 두 번의 요청입니다. 첫 요청에 tools로 함수 정의를 넘기면 응답에 function_call(이름과 JSON 문자열 인자)이 오고, 내 코드가 그 함수를 실행한 뒤 결과를 function_call_output으로 call_id와 함께 다시 보내면 최종 답이 옵니다. 주의할 점은 Responses API와 Chat Completions의 함수 정의 형식이 다르다는 것입니다. 아래 예제는 openai 파이썬 SDK 3.24.0으로 직접 돌려 확인했습니다(2026년 10월 기준).
1. function calling은 이렇게 돈다 — 5단계
공식 문서는 function calling(도구 호출이라고도 부릅니다)을 내 애플리케이션과 모델이 여러 번 주고받는 대화로 설명합니다.
- 모델에게 호출할 수 있는 도구(
tools)를 붙여 요청한다 - 모델이 도구 호출(
function_call)을 돌려준다 - 내 코드가 그 인자로 함수를 실행한다
- 실행 결과를 붙여 모델에게 두 번째 요청을 보낸다
- 모델이 최종 답을 준다 (또는 도구를 또 부른다)
3번이 핵심입니다. 날씨 조회든 DB 검색이든 실제 실행은 전부 내 쪽에서 합니다. 모델은 어떤 함수를 어떤 인자로 부를지 정할 뿐입니다.
2. openai function calling 예제 — Responses API
도시 이름을 받아 기온을 돌려주는 get_weather 함수를 붙인 예제입니다. 모델 이름은 공식 가이드 예제와 같은 gpt-6-astra를 썼습니다. API 키는 OPENAI_API_KEY 환경 변수에서 읽습니다(키가 아직 없다면 chatgpt api key 발급부터 하고 오세요).
import json
from openai import OpenAI
client = OpenAI()
# 1. 모델에게 알려 줄 함수 정의
tools = [
{
"type": "function",
"name": "get_weather",
"description": "도시의 현재 기온을 섭씨로 돌려준다.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "도시 이름. 예: 서울"}
},
"required": ["city"],
"additionalProperties": False,
},
"strict": True,
}
]
# 실제로 실행되는 내 함수 (예제라 값을 고정해 둠)
def get_weather(city):
return json.dumps({"city": city, "temp_c": 21}, ensure_ascii=False)
input_list = [{"role": "user", "content": "서울이랑 부산 날씨 알려줘"}]
# 2. 첫 요청 — 모델이 function_call 을 돌려준다
response = client.responses.create(model="gpt-6-astra", tools=tools, input=input_list)
# 모델이 보낸 function_call 항목을 그대로 대화에 남긴다
input_list += response.output
# 3. 호출을 하나씩 실행하고 결과를 call_id 와 함께 붙인다
for item in response.output:
if item.type == "function_call" and item.name == "get_weather":
args = json.loads(item.arguments) # arguments 는 문자열이다
input_list.append({
"type": "function_call_output",
"call_id": item.call_id,
"output": get_weather(**args),
})
# 4~5. 결과를 붙여 다시 요청하면 최종 답이 온다
response = client.responses.create(model="gpt-6-astra", tools=tools, input=input_list)
print(response.output_text)
모델이 서울과 부산을 한 번에 물어서 호출을 두 개 돌려줬다고 하면, 두 번째 요청의 input에는 실제로 아래 다섯 항목이 이 순서로 실려 갑니다. SDK가 보내는 요청 본문을 가로채서 확인한 결과입니다.
{"role": "user", "content": "서울이랑 부산 날씨 알려줘"}
{"type": "function_call", "call_id": "call_abc", "name": "get_weather", "arguments": "{\"city\":\"서울\"}", ...}
{"type": "function_call", "call_id": "call_def", "name": "get_weather", "arguments": "{\"city\":\"부산\"}", ...}
{"type": "function_call_output", "call_id": "call_abc", "output": "{\"city\": \"서울\", \"temp_c\": 21}"}
{"type": "function_call_output", "call_id": "call_def", "output": "{\"city\": \"부산\", \"temp_c\": 21}"}
어떤 결과가 어떤 호출의 답인지는 call_id로만 이어집니다. 그래서 공식 문서도 "호출은 0개일 수도, 1개일 수도, 여러 개일 수도 있으니 여러 개라고 가정하고 처리하라"고 권합니다. 위처럼 for로 도는 이유입니다.
output)는 보통 문자열이면 되고 형식은 자유입니다(JSON, 에러 코드, 평문). 메일 보내기처럼 돌려줄 값이 없는 함수라면 공식 문서 예시대로 "success" 같은 문자열을 돌려주면 됩니다. 한글이 든 JSON을 만들 때 json.dumps에 ensure_ascii=False를 주지 않으면 "서울"이 "\uc11c\uc6b8"로 바뀌어 실립니다.3. openai function calling format — 함수 정의 필드
함수 하나는 다섯 필드로 정의합니다.
| 필드 | 뜻 |
|---|---|
type |
항상 "function" |
name |
함수 이름 (예: get_weather) |
description |
언제·어떻게 쓰는 함수인지. 모델은 이걸 보고 고른다 |
parameters |
인자를 정의하는 JSON Schema |
strict |
스키마를 엄격하게 지키게 할지 여부 |
parameters가 JSON Schema라서 타입·enum·설명·중첩 객체를 그대로 쓸 수 있습니다. 공식 문서의 작성 요령 중 실무에서 효과가 큰 것만 추리면 이렇습니다.
- 함수와 인자의 목적·형식을 설명에 분명히 적는다. 사람 신입이 설명만 보고 쓸 수 있는지("intern test")가 기준입니다.
- 잘못된 조합이 안 나오게
enum을 쓴다.toggle_light(on, off)처럼 둘 다 참일 수 있는 설계는 피합니다. - 이미 아는 값은 모델에게 채우게 하지 않는다. 주문 번호를 이미 갖고 있다면 인자로 받지 말고 코드에서 넣습니다.
- 처음부터 쓸 수 있는 함수는 20개 미만으로 두라고 권합니다(문서도 "느슨한 기준"이라고 적습니다).
함수 정의는 시스템 메시지에 들어가므로 컨텍스트 한도에 포함되고 입력 토큰으로 과금됩니다. 설명을 길게 쓸수록 매 요청 비용이 늘어난다는 뜻이니, 단가 계산은 chatgpt api 요금 글을 같이 보세요.
4. strict 모드 — 켜는 조건 두 가지
"strict": true를 주면 모델이 인자를 스키마대로 반드시 맞춰 보냅니다(끄면 "최선을 다하는" 수준). 공식 문서는 항상 켜기를 권합니다. 대신 조건이 둘 있습니다.
parameters안의 모든 객체에"additionalProperties": falseproperties의 모든 필드를required에 넣기
"선택 인자"는 어떻게 하느냐 — 필드를 빼지 말고 타입에 null을 더합니다.
"properties": {
"city": {"type": "string"},
"units": {"type": ["string", "null"], "enum": ["celsius", "fahrenheit"]}
},
"required": ["city", "units"],
"additionalProperties": false
strict를 안 적었을 때의 기본값이 API마다 다릅니다. Responses API는 스키마를 strict로 맞춰 보려 하고, 안 되면 비strict로 돌아가면서 응답의 도구 정보에 strict: false로 표시합니다. Chat Completions는 기본이 비strict입니다. 그리고 strict: true를 적었는데 위 두 조건을 안 지키면 요청 자체가 거부됩니다.이 규칙은 응답을 JSON으로 받는 구조화 출력(Structured Outputs)과 같은 기반이라, 스키마 제약도 같습니다. 지원 안 되는 키워드 목록은 chatgpt api json 글에 정리해 두었습니다.
5. Chat Completions에서는 형식이 다르다
검색해서 나오는 예제 상당수가 Chat Completions 기준입니다. 같은 함수를 정의해도 모양이 다르니 두 API를 섞어 쓰면 안 됩니다. 공식 이전 가이드의 표현으로는 Chat Completions가 바깥에 태그를 다는(externally tagged) 방식, Responses가 안에 태그를 다는(internally tagged) 방식입니다.
| 구분 | Responses API | Chat Completions |
|---|---|---|
| 함수 정의 | {"type":"function", "name":..., "parameters":...} |
{"type":"function", "function":{"name":..., "parameters":...}} |
| 모델의 호출 | output 안의 function_call 항목 |
message.tool_calls (finish_reason은 "tool_calls") |
| 결과 돌려주기 | {"type":"function_call_output", "call_id":..., "output":...} |
{"role":"tool", "tool_call_id":..., "content":...} |
strict 생략 시 |
strict 시도 후 안 되면 비strict | 비strict |
Chat Completions로 같은 일을 하면 이렇습니다. 결과 메시지를 붙이기 전에 모델이 보낸 assistant 메시지(tool_calls가 든 것)를 먼저 대화에 남겨야 합니다.
chat_tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "도시의 현재 기온을 섭씨로 돌려준다.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": False,
},
"strict": True,
},
}]
messages = [{"role": "user", "content": "서울 날씨 알려줘"}]
completion = client.chat.completions.create(
model="gpt-5.6-terra", reasoning_effort="none",
messages=messages, tools=chat_tools,
)
msg = completion.choices[0].message
messages.append(msg) # tool_calls 가 든 assistant 메시지
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
messages.append({"role": "tool", "tool_call_id": tc.id, "content": get_weather(**args)})
completion = client.chat.completions.create(
model="gpt-5.6-terra", reasoning_effort="none",
messages=messages, tools=chat_tools,
)
print(completion.choices[0].message.content)
reasoning_effort="none"이 붙은 이유가 있습니다. 공식 이전 가이드에 따르면 GPT-5.4부터 Chat Completions는 reasoning_effort가 none이 아니면 도구 호출을 지원하지 않습니다. 또 function calling 가이드는 GPT-6 Astra와 GPT-6.1 Sol은 도구 호출에 Responses API가 필요하다고 적고 있습니다. 새로 만든다면 Responses API로 시작하는 편이 낫습니다.6. 자주 막히는 곳
① arguments가 딕셔너리가 아니다. 두 API 모두 인자는 '{"city":"서울"}' 같은 JSON 문자열로 옵니다. 바로 args["city"]를 하면 안 되고 json.loads를 먼저 거쳐야 합니다.
② pydantic_function_tool()은 Chat Completions 형식을 만든다. 파이썬 SDK의 openai.pydantic_function_tool(모델클래스)는 편하지만, 실제로 찍어 보면 {"type":"function","function":{...}}로 중첩된 모양이 나옵니다. Responses API에 쓰려면 안쪽을 펴서 넘기면 됩니다. 이렇게 넘기면 평평한 형식으로 전송되는 것을 확인했습니다.
import openai
from pydantic import BaseModel
class GetWeather(BaseModel):
"""도시의 현재 기온을 섭씨로 돌려준다."""
city: str
chat_tool = openai.pydantic_function_tool(GetWeather) # Chat Completions 형식
responses_tool = {"type": "function", **chat_tool["function"]} # Responses 형식으로 펴기
client.responses.create(model="gpt-6-astra", input="서울 날씨", tools=[responses_tool])
③ 모델이 함수를 안 부르거나, 꼭 부르게 하고 싶다. tool_choice로 정합니다.
| 값 | 동작 |
|---|---|
"auto" (기본) |
0개·1개·여러 개를 모델이 알아서 |
"required" |
하나 이상은 반드시 부른다 |
{"type":"function","name":"get_weather"} |
그 함수 하나를 정확히 부른다 |
{"type":"allowed_tools", "mode":"auto", "tools":[...]} |
tools 목록은 그대로 두고 쓸 수 있는 함수만 좁힌다 |
"none" |
함수를 안 넘긴 것처럼 동작 |
④ 한 번에 여러 함수를 불러서 곤란하다. parallel_tool_calls: false를 주면 한 번에 0개 또는 1개만 부릅니다.
자주 묻는 질문 (FAQ)
Q. function calling이랑 tool calling은 다른 건가요?
같은 기능입니다. 공식 문서 첫 줄이 "Function calling (also known as tool calling)"입니다. 다만 넓게 보면 도구는 웹 검색 같은 내장 도구까지 포함하고, 함수는 그중 JSON Schema로 내가 정의한 도구를 가리킵니다.
Q. 응답을 JSON으로 받고 싶은 것뿐인데 function calling을 써야 하나요?
아닙니다. 모델을 내 시스템의 기능에 연결할 때가 function calling이고, 모델의 답 형태만 고정하려면 구조화 출력이 맞습니다. 방법은 chatgpt api json 글에 있습니다.
Q. 함수가 실패하면 뭘 돌려줘야 하나요?
결과 형식은 자유라서, 실패 이유를 담은 문자열이나 에러 코드를 그대로 output에 넣으면 됩니다. 모델이 그 문자열을 읽고 다음 행동을 정합니다.
Q. 두 번째 요청에도 tools를 다시 넣어야 하나요?
공식 예제는 두 번째 요청에도 같은 tools를 넣습니다. 5단계에 적힌 대로 모델이 결과를 보고 도구를 또 부를 수도 있기 때문입니다.
마무리
정리하면 function calling은 정의 → 모델의 호출 → 내 코드 실행 → 결과 전달 → 최종 답의 왕복입니다. Responses API에서는 함수 정의가 평평하고(name이 바로 밑), 결과는 function_call_output에 call_id를 맞춰 붙입니다. Chat Completions는 정의가 function 안에 한 겹 더 들어가고 결과는 role: "tool" 메시지입니다. 인자는 언제나 JSON 문자열이니 json.loads를 잊지 말고, strict는 켜 두는 쪽이 안전합니다.
📚 참고 출처 (2026년 10월 4일 확인 · openai 파이썬 SDK 3.24.0)
· Function calling — OpenAI API
· Migrate to the Responses API — OpenAI API
COMMENTS