AI 에이전트를 만들다 보면 이런 상황이 생깁니다.
"LLM한테 날씨 알려달라고 했는데, 학습 데이터에 없는 오늘 날씨를 어떻게 알려주지?"
LLM은 학습 데이터 기반으로만 답하기 때문에 실시간 정보나 외부 시스템과 연동이 안 돼요. 이걸 해결하는 게 Function Calling입니다. 이번 글에서는 Function Calling이 뭔지, 어떻게 동작하는지, 실제로 어떻게 구현하는지 정리해 드릴게요.
Function Calling이란?
Function Calling은 LLM이 응답을 생성할 때 "이 질문은 내가 직접 답하는 게 아니라 이 함수를 호출해야 한다" 고 판단해서 함수 호출 정보를 반환하는 기능이에요.
중요한 건 LLM이 함수를 직접 실행하는 게 아니라는 점이에요. LLM은 "어떤 함수를 어떤 인자로 호출하면 된다"는 정보만 반환하고, 실제 실행은 개발자 코드에서 합니다.
사용자: "서울 날씨 알려줘"
LLM: "get_weather 함수를 city='서울'로 호출하세요" (함수 실행 X, 정보만 반환)
개발자 코드: get_weather(city='서울') 실행
결과를 다시 LLM에 전달
LLM: "서울 현재 날씨는 맑음, 기온 15도입니다"
동작 원리
Function Calling의 흐름은 4단계예요.
1단계: 함수 정의를 LLM에 전달
어떤 함수가 있는지, 각 함수가 어떤 인자를 받는지 JSON Schema 형태로 LLM에게 알려줍니다.
2단계: LLM이 함수 호출 여부 판단
사용자 질문을 보고 LLM이 직접 답할 수 있으면 그냥 답하고, 함수가 필요하면 어떤 함수를 어떤 인자로 호출할지 반환합니다.
3단계: 개발자 코드에서 함수 실행
LLM이 반환한 함수 이름과 인자를 보고 실제 함수를 실행합니다.
4단계: 결과를 LLM에 다시 전달
함수 실행 결과를 LLM에게 넘기면 LLM이 최종 응답을 생성합니다.
실제 구현
OpenAI API 기준
함수 정의
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "특정 도시의 현재 날씨를 조회합니다",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "날씨를 조회할 도시명"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "온도 단위"
}
},
"required": ["city"]
}
}
}
]
1차 LLM 호출
from openai import OpenAI
import json
client = OpenAI()
messages = [{"role": "user", "content": "서울 날씨 알려줘"}]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto" # auto: LLM이 알아서 판단 / required: 무조건 함수 호출
)
message = response.choices[0].message
함수 호출 여부 확인 및 실행
# 실제 함수 구현
def get_weather(city: str, unit: str = "celsius") -> dict:
# 실제로는 날씨 API 호출
return {
"city": city,
"temperature": 15,
"condition": "맑음",
"unit": unit
}
# LLM이 함수 호출을 요청했는지 확인
if message.tool_calls:
tool_call = message.tool_calls[0]
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
# 함수 실행
if function_name == "get_weather":
result = get_weather(**function_args)
# 결과를 messages에 추가
messages.append(message) # LLM의 tool_call 메시지
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
2차 LLM 호출 — 최종 응답 생성
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
print(final_response.choices[0].message.content)
# "서울의 현재 날씨는 맑음이며 기온은 15도입니다."
여러 함수 동시 등록
실제 에이전트에서는 함수를 여러 개 등록해요. LLM이 질문에 맞는 함수를 골라서 호출합니다.
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "특정 도시의 현재 날씨 조회",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "search_web",
"description": "웹에서 정보를 검색합니다",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "검색어"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "수학 계산을 수행합니다",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "계산할 수식"}
},
"required": ["expression"]
}
}
}
]
함수 실행 부분은 딕셔너리로 매핑하면 깔끔해요.
function_map = {
"get_weather": get_weather,
"search_web": search_web,
"calculate": calculate
}
if message.tool_calls:
for tool_call in message.tool_calls:
func = function_map.get(tool_call.function.name)
args = json.loads(tool_call.function.arguments)
result = func(**args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False)
})
Parallel Function Calling
하나의 질문에 여러 함수를 동시에 호출할 수도 있어요.
"서울이랑 부산 날씨 둘 다 알려줘"
→ get_weather(city="서울")
→ get_weather(city="부산")
두 개 동시에 호출
LLM이 tool_calls 배열에 여러 개를 담아서 반환하기 때문에 위 코드처럼 for tool_call in message.tool_calls로 처리하면 자동으로 대응돼요.
LangChain에서 Function Calling
LangChain에서는 @tool 데코레이터로 더 간단하게 쓸 수 있어요.
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
@tool
def get_weather(city: str) -> str:
"""특정 도시의 현재 날씨를 조회합니다."""
return f"{city}의 현재 날씨는 맑음, 15도입니다."
@tool
def search_web(query: str) -> str:
"""웹에서 정보를 검색합니다."""
return f"'{query}' 검색 결과: ..."
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools([get_weather, search_web])
response = llm_with_tools.invoke("서울 날씨 알려줘")
함수의 docstring이 LLM에게 전달되는 description이 되기 때문에 docstring을 명확하게 써야 LLM이 올바르게 함수를 선택해요.
tool_choice 옵션
함수 호출 여부를 제어하는 옵션이에요.
# auto: LLM이 알아서 판단 (기본값)
tool_choice="auto"
# required: 반드시 함수 호출 (어떤 함수든)
tool_choice="required"
# none: 함수 호출 금지
tool_choice="none"
# 특정 함수 강제 호출
tool_choice={"type": "function", "function": {"name": "get_weather"}}
Function Calling vs 프롬프트로 JSON 뽑기
Function Calling 이전에는 프롬프트에 "JSON으로 답해줘"라고 써서 함수 호출 여부를 판단했어요. 차이는 이렇습니다.
구분 프롬프트 JSON Function Calling
| 안정성 | 파싱 실패 가능 | 구조화된 출력 보장 |
| 함수 선택 | LLM이 텍스트로 판단 | 스키마 기반 정확한 선택 |
| 인자 추출 | 부정확할 수 있음 | 타입까지 검증됨 |
| 복잡도 | 간단 | 약간 복잡 |
프로덕션 에이전트에서는 Function Calling을 쓰는 게 훨씬 안정적이에요.
마무리
Function Calling의 핵심은 세 가지입니다.
첫째, LLM은 함수를 실행하지 않고 "어떤 함수를 어떤 인자로 호출할지"만 반환합니다. 둘째, 실제 실행과 결과를 다시 LLM에 전달하는 건 개발자 코드의 몫이에요. 셋째, 함수 description을 명확하게 써야 LLM이 올바른 함수를 선택합니다.
AI 에이전트에서 외부 데이터나 시스템과 연동이 필요하다면 Function Calling이 가장 안정적인 방법이에요. 😄
'LLM' 카테고리의 다른 글
| 구글의 딥시크: 터보퀀트(TurboQuant) 완전 분석 — 메모리 6배 절감이 반도체 주가를 흔든 이유 (0) | 2026.03.27 |
|---|---|
| [기초] LLM이 더 똑똑하게 생각하게 만드는 법 — CoT, ToT, Self-Consistency 완전 비교 (0) | 2026.03.26 |
| sglang vs vLLM — 오픈소스 LLM 서빙 프레임워크 실전 비교 (0) | 2026.03.24 |
| LLM 성능 평가는 어떻게 할까? MT-Bench부터 HELM까지 (0) | 2026.03.24 |
| AI 모델의 실력을 정확히 평가하는 방법: LLM 수동 평가 완벽 가이드 (1) | 2026.03.24 |