Function Calling 이해하기

박병현·2025년 2월 11일

🔎 Function Calling 이해하기: AI가 필요할 때만 Tool을 호출하는 방식


🚀 Function Calling이란?

일반적인 AI 모델은 기본적인 일반 지식을 갖고 있지만, LLM의 경우 실시간 정보(예: 날짜, 주식 가격, 날씨 등)는 알지 못합니다.
이를 해결하기 위해, Function Calling을 사용하면 AI가 필요할 때만 특정 Tool(함수)을 호출하여 최신 정보를 가져올 수 있습니다.


🔑 사전 준비: OPENAI API Key 설정

LangChain에서 OpenAI API를 사용하려면, 환경 변수에 API Key를 설정해야 합니다.
아래 코드를 실행하면, API Key를 입력하고 자동으로 환경 변수에 저장할 수 있습니다.

import getpass
import os

def _set_env(key: str):
    if key not in os.environ:
        os.environ[key] = getpass.getpass(f"{key}:")

_set_env("OPENAI_API_KEY")

📌 설명

  • os.environ을 사용하여 환경 변수에 OPENAI_API_KEY를 설정합니다.
  • 실행하면 OPENAI_API_KEY: 프롬프트가 나타나며, 사용자의 API Key를 입력할 수 있습니다.
  • API Key가 이미 설정되어 있다면, 재입력하지 않고 기존 값을 유지합니다.

1️⃣ Function Calling이 왜 필요한가?

📌 AI에게 날짜를 물어보는 코드

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model='gpt-4o-mini')
response = llm.invoke('오늘 몇년도 몇월 몇일이야?').content

print(response)

📌 실행 결과

오늘은 2023년 10월 2일입니다.

📌 문제점:

  • 실행한 날짜가 2025년 2월 11일이라면? → AI가 틀린 정보를 반환함!
  • AI는 현재 날짜를 알지 못하고, 학습된 데이터에서 일반적인 날짜 패턴을 추론하여 대답함.
  • 실시간 정보를 제공해야 하는 서비스에서는 이런 방식이 적절하지 않음.

💡 해결 방법:
AI가 직접 날짜를 알지 못하더라도, Function Calling을 사용하면 필요할 때만 특정 Tool을 호출하여 정확한 정보를 가져올 수 있습니다.


2️⃣ Function Calling을 적용한 코드

📌 Step 1: 라이브러리 불러오기

from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, BaseMessage, ToolMessage
from typing import Literal
import yfinance as yf

📌 사용된 주요 라이브러리

  • ChatOpenAI → OpenAI 기반의 LLM을 사용
  • tool → Function Calling을 적용하기 위한 데코레이터
  • create_react_agent → AI가 Tool을 적절히 호출할 수 있도록 설정
  • AIMessage, HumanMessage, ToolMessage → AI와 Tool 간의 메시지 교환을 추적

📌 Step 2: Tool 정의

AI가 필요할 때 호출할 수 있도록 여러 가지 Tool을 정의합니다.

@tool
def get_today():
    """현재 날짜를 YYYY-MM-DD 형식으로 반환"""
    from datetime import datetime
    return datetime.now().strftime("%Y-%m-%d")

@tool
def get_exchange_rate(currency: str):
    """yfinance 라이브러리를 활용하여 USD 대비 특정 통화의 환율을 반환"""
    import yfinance as yf
    exchange_data = yf.Ticker(f"USD{currency}=X")
    exchange_rate = exchange_data.history(period="1d")['Close'].iloc[-1]
    return f"USD -> {currency} 환율: {exchange_rate:.2f}"

@tool
def get_stock_price(stock_symbol: str):
    """yfinance 라이브러리를 활용하여 특정 주식 종목의 현재 가격을 조회"""
    import yfinance as yf
    stock = yf.Ticker(stock_symbol)
    stock_price = stock.history(period="1d")['Close'].iloc[-1]
    return f"{stock_symbol} 현재 주가: {stock_price:.2f} USD"

@tool
def add(a: int, b: int) -> int:
    """두 숫자의 합을 반환"""
    return a + b

📌 각 Tool의 역할

  • get_today → (datetime 활용) 현재 날짜 반환
  • get_exchange_rate → (yfinance를 활용) 환율 정보 반환
  • get_stock_price → (yfinance를 활용) 특정 주식의 현재 가격 반환
  • add → 두 숫자의 합을 반환

📌 Step 3: AI 메시지 역할 분석 함수

각 메시지가 AI, 사용자, 시스템, Tool 중 어디서 온 것인지 분석할 수 있도록 합니다.

def parse_role_from_message(message: BaseMessage) -> Literal['assistant', 'human', 'system', 'tool', 'unknown']:
    """Extract role from a message"""
    if isinstance(message, AIMessage):
        return 'assistant'
    elif isinstance(message, HumanMessage):
        return 'human'
    elif isinstance(message, SystemMessage):
        return 'system'
    elif isinstance(message, ToolMessage):
        return 'tool'
    else:
        return 'unknown'

📌 이 함수가 필요한 이유

  • AI와 Tool이 주고받는 메시지를 명확하게 구분
  • 어떤 Tool이 호출되었고, 어떤 메시지가 Tool에서 반환되었는지 분석 가능

📌 Step 4: LangChain Agent 생성

llm = ChatOpenAI(model='gpt-4o-mini')

# 앞서 정의한 tool들을 기반으로 LLM Agent생성
agent = create_react_agent(
    llm,
    tools=[get_today,
    	   get_exchange_rate,
           get_stock_price, 
           add
    ]
)

📌 설명

  • OpenAI 모델(gpt-4o-mini)을 불러옵니다.
  • create_react_agent를 이용해 필요할 때만 Tool을 호출하도록 설정합니다.

📌 Step 5: AI에게 질문하고 Tool 호출 분석

def invoke_ai(question: str):
    print(f"\n✅ 질문: '{question}' 실행 중...\n")

    response = agent.invoke({"messages": question})
    
    # 🔹 전체 response 출력
    print("\n📌 Raw Response 출력:")
    print(response)
    
    # 사용된 Tool 추출
    used_tools = set()
    print("\n📌 메시지 분석:")
    for message in response['messages']:
        role = parse_role_from_message(message)

        if role == 'assistant' and hasattr(message, "tool_calls"):
            for tool_call in message.tool_calls:
                tool_name = tool_call['name']
                used_tools.add(tool_name)
                print(f"🔹 사용된 Tool: {tool_name}")

        elif role == 'tool':
            print(f"🔹 ToolMessage - {message.name}: {message.content}")

    # ✅ 사용되지 않은 Tool 출력
    all_tools = {"get_today", "get_exchange_rate", "get_stock_price", "add"}
    unused_tools = all_tools - used_tools

    if unused_tools:
        print(f"⚠️ 사용되지 않은 Tool: {', '.join(unused_tools)}")

    print(f"\n🎯 AI 응답: {response['messages'][-1].content}\n")

📌 이 함수의 역할
1. AI에게 question을 전달하고 agent.invoke()를 실행
2. AI가 실행한 모든 메시지를 분석
3. AI가 호출한 Tool을 출력
4. 사용되지 않은 Tool도 표시


3️⃣ 실행 예제 및 결과 분석

📌 AI에게 Function Calling을 활용해 정보 요청

invoke_ai("오늘 애플 주가 얼마야? 달러 및 현재환율 고려해서 원화로도 얼마인지 알려줘")

📌 실행 결과 분석

✅ 질문: '오늘 애플 주가 얼마야? 달러 및 현재환율 고려해서 원화로도 얼마인지 알려줘' 실행 중...

📌 Raw Response 출력:
{
    'messages': [
        HumanMessage(content='오늘 애플 주가 얼마야? 달러 및 현재환율 고려해서 원화로도 얼마인지 알려줘.'),

        AIMessage(content='', tool_calls=[
            {'name': 'get_stock_price', 'args': {'stock_symbol': 'AAPL'}},
            {'name': 'get_exchange_rate', 'args': {'currency': 'KRW'}}
        ]),

        ToolMessage(content='AAPL 현재 주가: 227.65 USD', name='get_stock_price'),
        ToolMessage(content='USD -> KRW 환율: 1453.99', name='get_exchange_rate'),

        AIMessage(content='현재 애플(AAPL)의 주가는 227.65 달러입니다. 현재 환율을 기준으로(1 USD = 1453.99 KRW) 애플의 주가는 약 331,927 원입니다.')
    ]
}

📌 response 상세 분석

1️⃣ 사용자의 입력

HumanMessage(content='오늘 애플 주가 얼마야? 달러 및 현재환율 고려해서 원화로도 얼마인지 알려줘.')

📌 설명:

  • 사용자는 AI에게 애플(AAPL) 주가현재 환율(KRW/USD)을 활용한 원화 환산 가격을 요청함.

2️⃣ AI의 초기 응답 (tool_calls 발생)

AIMessage(content='', tool_calls=[
    {'name': 'get_stock_price', 'args': {'stock_symbol': 'AAPL'}},
    {'name': 'get_exchange_rate', 'args': {'currency': 'KRW'}}
])

📌 설명:

  • AI는 자신이 주가와 환율 정보를 모른다는 것을 인식하고,
  • 이를 해결하기 위해 Function Calling을 활용하여 Tool을 호출함.
  • 호출된 Tool:
    • get_stock_price("AAPL") → 애플 주가를 가져옴
    • get_exchange_rate("KRW") → USD 대비 원화 환율을 가져옴

3️⃣ Tool의 응답 (실제 데이터 반환)

ToolMessage(content='AAPL 현재 주가: 227.65 USD', name='get_stock_price'),
ToolMessage(content='USD -> KRW 환율: 1453.99', name='get_exchange_rate')

📌 설명:

  • get_stock_price("AAPL") 실행 결과 → "AAPL 현재 주가: 227.65 USD"
  • get_exchange_rate("KRW") 실행 결과 → "USD -> KRW 환율: 1453.99"

4️⃣ AI가 최종 응답 생성

AIMessage(content='현재 애플(AAPL)의 주가는 227.65 달러입니다. 현재 환율을 기준으로(1 USD = 1453.99 KRW) 애플의 주가는 약 331,927 원입니다.')

📌 설명:

  • AI는 Tool에서 가져온 데이터를 조합하여 최종 응답을 생성함.
  • 주가(USD) × 환율(KRW/USD) = 227.65 × 1453.99 = 331,927 KRW

📌 response 최종 분석

사용된 Tool 및 응답 정리

Tool Name사용 여부응답 내용
get_stock_price✅ 사용AAPL 현재 주가: 227.65 USD
get_exchange_rate✅ 사용USD -> KRW 환율: 1453.99
get_today❌ 미사용(오늘 날짜는 필요하지 않았음)
add❌ 미사용(덧셈 기능은 필요하지 않았음)

🎯 AI 최종 응답:

현재 애플(AAPL)의 주가는 227.65 달러입니다. 
현재 환율을 기준으로(1 USD = 1453.99 KRW) 애플의 주가는 약 331,927 원입니다.

🔹 다른 tool을 활용하기 위한 예시 케이스

자동으로 적절한 Tool을 호출하는것을 확인하기 위한, 다른 질문을 수행

예를 들어, 오늘 날짜와 연도/월/일의 합을 계산하는 경우:

invoke_ai("오늘 몇일이지? 오늘의 연도/월/일의 합계 값은?")

📌 실행 결과 분석

✅ 질문: '오늘 몇일이지? 오늘의 연도/월/일의 합계 값은?' 실행 중...

📌 Raw Response 출력:
{
    'messages': [
        HumanMessage(content='오늘 몇일이지? 오늘의 연도/월/일의 합계 값은?'),

        AIMessage(content='', tool_calls=[
            {'name': 'get_today'},
        ]),

        ToolMessage(content='2025-02-11', name='get_today'),

        AIMessage(content='', tool_calls=[
            {'name': 'add', 'args': {'a': 2025, 'b': 2}},
        ]),

        ToolMessage(content='2027', name='add'),

        AIMessage(content='', tool_calls=[
            {'name': 'add', 'args': {'a': 2027, 'b': 11}},
        ]),

        ToolMessage(content='2038', name='add'),

        AIMessage(content='오늘은 2025년 2월 11일입니다. 오늘의 연도, 월, 일의 합계 값은 2038입니다.')
    ]
}

🎯 AI 최종 응답:

오늘은 2025년 2월 11일입니다. 오늘의 연도, 월, 일의 합계 값은 2038입니다.

해당 질문 예시에서는, get_today, add Tool만 활용된것을 확인할 수 있다.

즉, 다수의 Tool을 정의하더라도, Agent는 필요할 때만 특정 Tool을 호출하는 특성을 갖는다.



결론: Function Calling의 강점

✔️ 필요할 때만 특정 Tool을 호출 → 불필요한 호출 방지
✔️ 실시간 정보 제공 가능 → AI가 최신 데이터를 직접 조회하여 응답
✔️ 연산이 필요한 경우에도 적절한 Tool 호출 → 데이터 조회뿐만 아니라 계산까지 수행 가능
✔️ 확장성 우수 → 새로운 Tool을 쉽게 추가하고 활용 가능
✔️ 비효율적인 호출 제거 → 성능 최적화

🚀 Function Calling을 활용하면 AI의 신뢰도를 높이고 더욱 정교한 시스템을 구축할 수 있다!

profile
AI Application Engineer

0개의 댓글