AI

Function Calling 에이전트

Astero 2025. 4. 9. 16:24
728x90
!pip -q install langchain openai tiktoken yfinance langchain_community langchain_openai
import os

os.environ["OPENAI_API_KEY"] = "본인 open api Key 값"

Function Calling 함수 명세 예시

 

간단 예시

Function Calling을 하기 위해서는 함수의 명세를 아래와 같은 형식으로 작성해야 합니다.

functions = [
   {
       "name": "get_current_weather",
       "description": "현재 날씨 정보 가져오기",
       "parameters": {
           "type": "object",
           "properties": {
               "location": {
                   "type": "string",

                   "description": "도시와 주/도, 예: San Francisco, CA",
               },
               "format": {
                   "type": "string",
                   "enum": ["celsius", "fahrenheit"],
                   "description": "사용할 온도 단위. 사용자의 위치에서 이를 추론합니다.",
               },
           },
           "required": ["location", "format"],
       },
   }
]

야후파이낸스 함수 셋팅

야후파이낸스를 이용한 함수 호출

import yfinance as yf

def get_stock_price(symbol):
    """
    주어진 주식 심볼의 최신 종가를 반환하는 함수

    :param symbol: 주식 심볼 (예: 'AAPL' for Apple Inc.)
    :return: 소수점 둘째 자리까지 반올림된 최신 종가
    """
    ticker = yf.Ticker(symbol)
    todays_data = ticker.history(period='1d')
    return round(todays_data['Close'].iloc[0], 2)
print(get_stock_price('AAPL'))

254.49

 

print(get_stock_price('GOOG'))

192.96

 

Setting up tools

from langchain.tools import BaseTool
from typing import Optional, Type
from langchain.agents import initialize_agent, Tool, AgentType
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

class StockPriceCheckInput(BaseModel):
    """Input for Stock price check."""

    stockticker: str = Field(..., description="Ticker symbol for stock or index")
class StockPriceTool(BaseTool):
    name: str = "get_stock_ticker_price"
    description: str = "주식 가격을 알아야 할 때 유용합니다. yfinance API에서 사용되는 주식 티커(종목 코드)를 입력해야 합니다."

    def _run(self, stockticker: str):
        # print("i'm running")
        price_response = get_stock_price(stockticker)
        return price_response

    def _arun(self, stockticker: str):
        raise NotImplementedError("This tool does not support async")

    args_schema: Optional[Type[BaseModel]] = StockPriceCheckInput

랭체인을 이용한 Function Calling

import json
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, AIMessage, ChatMessage, FunctionMessage
from langchain.tools import MoveFileTool, format_tool_to_openai_function
model = ChatOpenAI(model="gpt-4o")
# StockPriceTool를 랭체인이 사용하는 도구에 등록
tools = [StockPriceTool()]

# 도구를 OpenAI에서 이해할 수 있는 형식으로 서술 등록
functions = [format_tool_to_openai_function(t) for t in tools]
functions
[{'name': 'get_stock_ticker_price',
  'description': '주식 가격을 알아야 할 때 유용합니다. yfinance API에서 사용되는 주식 티커(종목 코드)를 입력해야 합니다.',
  'parameters': {'properties': {'stockticker': {'description': 'Ticker symbol for stock or index',
     'type': 'string'}},
   'required': ['stockticker'],
   'type': 'object'}}]
# GPT-4o와 함수의 연결: Function Calling
# GPT-4o에게 위의 '함수 명세'와 '사용자의 질의'를 입력
ai_message = model.predict_messages([HumanMessage(content="구글 주식의 가격을 알려줘")], functions=functions)

<ipython-input-16-1d4611ee25e9>:3: LangChainDeprecationWarning: The method `BaseChatModel.predict_messages` was deprecated in langchain-core 0.1.7 and will be removed in 1.0. Use :meth:`~invoke` instead. ai_message = model.predict_messages([HumanMessage(content="구글 주식의 가격을 알려줘")], functions=functions)

 

# 결과로 나오는 것은 LLM의 답변이 아니라 어떤 함수를 어떤 파라미터로 호출해야 할 것인지를 알려준다.
ai_message
AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{"stockticker":"GOOGL"}', 'name': 'get_stock_ticker_price'}, 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 90, 'total_tokens': 111, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_d28bcae782', 'finish_reason': 'function_call', 'logprobs': None}, id='run-8ba39b31-adba-4dad-b54b-01ab3b5d1683-0', usage_metadata={'input_tokens': 90, 'output_tokens': 21, 'total_tokens': 111, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})

 

위의 ai_message에서 'function_call'이라고 적힌 부분에 주목한다.
GPT-4o는 현재 주어진 함수 명세와 사용자의 질의를 고려하였을 때, 어떤 함수 어떤 파라미터 값으로 호출해야하는지 제안한다.

ai_message.additional_kwargs['function_call']
{'arguments': '{"stockticker":"GOOGL"}', 'name': 'get_stock_ticker_price'}

 

GPT-4o는 주어진 함수 명세와 사용자의 질의를 고려하였을 때

get_stock_tiker_price라는 함수를 {"stockticker":"GOOGL"}라는 파라미터로 호출해야 한다.

따라서 이를 실제로 호출한다.

# GPT-4o가 제안한 파라미터 값을 문자열로 추출
ai_message.additional_kwargs['function_call'].get('arguments')

{"stockticker":"GOOGL"}

# GPT-4o가 제안한 파라미터 값이 현재 문자열 타입이므로 파이썬의 Dictionary 형태로 읽는다.
args = json.loads(ai_message.additional_kwargs['function_call'].get('arguments'))
print(args)

{'stockticker': 'GOOGL'}

 

구글 주식의 가격을 알려줘의 질문에 대한 답변은 {'stockticker': 'GOOGL'}인 것이다.
이제 함수에 실제로 argument를 전달하여 함수의 결과를 얻어보자.

tools[0]
StockPriceTool()
# 함수 호출
tool_result = tools[0](args)

# 함수 호출 결과를 문자열로 변환
tool_result = str(tool_result)
print(tool_result)

191.41

# 함수 호출 결과를 최종 답변에 활용하기 위해서 랭체인의 형식으로 변환
FunctionMessage(name='get_stock_ticker_price', content=tool_result)
FunctionMessage(content='191.41', additional_kwargs={}, response_metadata={}, name='get_stock_ticker_price')

 

final_message = model.predict_messages([HumanMessage(content='구글 주식의 가격을 알려줘'), # 사용자의 질문
                                        ai_message, # GPT-4o가 결정한 함수와 파라미터의 값
                                        FunctionMessage(name='get_stock_ticker_price',content=tool_result)], # 함수 호출 결과
                                        functions=functions) #함수의 명세
# 각종 값들이 혼재되어져 있다. 실제 답변은 .content로 접근한다.
final_message
AIMessage(content='구글 주식(GOOGL)의 현재 가격은 $191.41입니다.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 124, 'total_tokens': 143, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_d28bcae782', 'finish_reason': 'stop', 'logprobs': None}, id='run-d9cd7ee9-e893-49bd-a806-0db272fdad3f-0', usage_metadata={'input_tokens': 124, 'output_tokens': 19, 'total_tokens': 143, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})
final_message.content

구글 주식(GOOGL)의 현재 가격은 $191.41입니다.

 

Function Calling 에이전트

llm = ChatOpenAI(temperature=0, model="gpt-4o")
open_ai_agent = initialize_agent(tools,
                        llm,
                        agent=AgentType.OPENAI_FUNCTIONS, # Funtion Calling Agent
                        verbose=True)

<ipython-input-37-3bfd662c9ca4>:1: LangChainDeprecationWarning: LangChain agents will continue to be supported, but it is recommended for new use cases to be built with LangGraph. LangGraph offers a more flexible and full-featured framework for building agents, including support for tool-calling, persistence of state, and human-in-the-loop workflows. See LangGraph documentation for more details: https://langchain-ai.github.io/langgraph/. Refer here for its pre-built ReAct agent: https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/ open_ai_agent = initialize_agent(tools,

 

result = open_ai_agent.run("구글 주식의 가격은?")


> Entering new AgentExecutor chain... Invoking: `get_stock_ticker_price` with `{'stockticker': 'GOOGL'}` 191.41현재 구글 주식(GOOGL)의 가격은 $191.41입니다. > Finished chain.

 

print(result)

현재 구글 주식(GOOGL)의 가격은 $191.41입니다.

 

ReACT 에이전트 Vs. Function Calling 에이전트 비교

  • Function Calling은 CoT(생각 과정)이 없는 도구 호출 에이전트이다.
  • 이전에 ReACT 에이전트로 구현되었던 많은 프로젝트가 Function Calling으로 전환되고 있다. 이유는 CoT라는 선 과정이 토큰 소모와 느린 답변 속도에 영향을 미친다는 점때문에 현재는 Function Calling 성능이 좋아지면서 CoT 없이도 도구 호출이 정확하게 가능한 경우가 많아졌기 때문이다.
  • 하지만 반대로 Function Calling이 파인 튜닝 후에도 잘 동작하지 않는다면 성능을 올리기 위해서 다시 CoT를 추가하여 ReACT 에이전트로 변경하는 것을 고려하기도 한다. CoT는 문제를 풀기 전 생각 과정을 추가하여 성능을 올리는 방법이라는 점을 기억한다.

장점

  • ReACT 에이전트 대비 적은 토큰 필요. 답변 시 CoT가 없기 때문이다.
  • ReACT는 기본적으로 CoT를 하기 때문에 답변 속도가 느리다. CoT를 하는 동안에 시간은 계속 흐르고, CoT가 끝나야만 답변이 가능하기 때문이다. Function Calling은 더 빠른 답변을 얻을 수 있다.

단점

  • CoT가 없기 때문에 제대로 작동하지 않을 경우 프롬프트 변경 등을 통한 커스터마이징이 쉽지 않다. Function Calling은 도구 설명 정도밖에 수정을 하지 못한다. 반면, ReACT 에이전트는 CoT라는 도구 호출 전 생각 과정이 있기때문에 해당 생각 부분에 대한 프롬프트 엔지니어링이 가능하기 때문이다.

오픈 모델 파인 튜닝

  • 파인 튜닝이 가능한 상황이라면 특정 시나리오에 대해서 특정 함수를 호출하도록 학습하여 해당 도메인 특화 에이전트를 개발하는 것이 가능하다.
  • 또한, 파인 튜닝이 가능하다면 ReACT 에이전트보다는 CoT가 없는 Function Calling으로 성능을 먼저 보는 것을 권장하며 (토큰 절약, 인퍼런스 속도를 감안하면 Function Calling이 더 낫기 때문.) 이후 성능이 오르지 않을 경우에 CoT를 추가하여 ReACT 형식으로 개조하는 방향으로 진행하는 것을 권한다.

    다수의 도구 시나리오

     

    다수의 도구를 사용하는 Function Calling 에이전트를 만들어보자. 우선 두 개의 함수를 추가로 만든다.

    • calculate_performance: 특정 주식과 기간을 주면 해당 기간 동안의 변동률을 계산한다.
    • get_best_performing: 다수의 주식과 기간을 주면 해당 기간 동안 가장 좋은 성과를 보인 주식을 반환.
import yfinance as yf
from datetime import datetime, timedelta
def calculate_performance(symbol, days_ago):
    """
    주어진 기간 동안 특정 주식의 성과(가격 변동률)를 계산하는 함수.

    :param symbol: 주식 심볼 (예: 'AAPL' for Apple Inc.)
    :param days_ago: 몇 일 전부터의 성과를 계산할지 지정하는 정수
    :return: 주식의 가격 변동률 (백분율)
    """
    # 주어진 심볼에 대한 Ticker 객체 생성
    ticker = yf.Ticker(symbol)

    # 현재 날짜를 종료 날짜로 설정
    end_date = datetime.now()

    # 시작 날짜 계산 (현재로부터 days_ago일 전)
    start_date = end_date - timedelta(days=days_ago)

    # 날짜를 'YYYY-MM-DD' 형식의 문자열로 변환
    start_date = start_date.strftime('%Y-%m-%d')
    end_date = end_date.strftime('%Y-%m-%d')

    # 지정된 기간의 주가 데이터 가져오기
    historical_data = ticker.history(start=start_date, end=end_date)

    # 시작 날짜의 종가와 마지막 날짜의 종가 가져오기
    old_price = historical_data['Close'].iloc[0]
    new_price = historical_data['Close'].iloc[-1]

    # 퍼센트 변화율 계산
    # ((새 가격 - 옛 가격) / 옛 가격) * 100
    percent_change = ((new_price - old_price) / old_price) * 100

    # 결과를 소수점 둘째 자리까지 반올림하여 반환
    return round(percent_change, 2)
def get_best_performing(stocks, days_ago):
    """
    주어진 주식 목록에서 지정된 기간 동안 가장 좋은 성과를 보인 주식을 찾는 함수.

    :param stocks: 주식 심볼 리스트 (예: ['AAPL', 'GOOGL', 'MSFT'])
    :param days_ago: 몇 일 전부터의 성과를 계산할지 지정하는 정수
    :return: 튜플 (최고 성과 주식 심볼, 해당 주식의 성과율)
    """
    best_stock = None  # 최고 성과 주식을 저장할 변수
    best_performance = None  # 최고 성과율을 저장할 변수

    # 주어진 주식 목록을 순회
    for stock in stocks:
        try:
            # 각 주식의 성과 계산
            performance = calculate_performance(stock, days_ago)

            # 현재 주식의 성과가 지금까지의 최고 성과보다 좋으면 업데이트
            if best_performance is None or performance > best_performance:
                best_stock = stock
                best_performance = performance
        except Exception as e:
            # 성과 계산 중 오류 발생 시 오류 메시지 출력
            print(f"Could not calculate performance for {stock}: {e}")

    # 최고 성과 주식과 그 성과율 반환
    return best_stock, best_performance

 

get_best_performing의 함수 호출 예시를 봅시다.

# 분석할 주식 목록 정의
stocks = ['AAPL', 'MSFT', 'GOOG']  # Apple, Microsoft, Google의 주식 심볼

# 분석 기간 설정 (일 단위)
days_ago = 90  # 지난 90일 동안의 성과를 분석

# get_best_performing 함수를 호출하여 최고 성과 주식과 그 성과율 얻기
best_stock, best_performance = get_best_performing(stocks, days_ago)

# 결과 출력
print(f"지난 {days_ago}일 동안 가장 좋은 성과를 보인 주식은 {best_stock}이며, 성과율은 {best_performance}%입니다.")

지난 90일 동안 가장 좋은 성과를 보인 주식은 AAPL이며, 성과율은 -2.81%입니다.

 

도구 추가

from typing import List, Optional, Type
from pydantic import BaseModel, Field
from langchain.tools import BaseTool

class StockChangePercentageCheckInput(BaseModel):
    """주식 가격 변동률 확인을 위한 입력 모델"""

    stockticker: str = Field(..., description="주식 또는 지수의 티커 심볼")
    days_ago: int = Field(..., description="몇 일 전부터 확인할지 지정하는 정수")

class StockPercentageChangeTool(BaseTool):
    """주식 가격 변동률을 계산하는 도구"""

    name: str = "get_price_change_percent"
    description: str = "주식 가치의 백분율 변화를 확인해야 할 때 유용합니다. yfinance API에서 사용되는 주식 티커와 변화를 확인할 일수를 입력해야 합니다."

    def _run(self, stockticker: str, days_ago: int) -> float:
        """
        주어진 주식의 가격 변동률을 계산합니다.
        :param stockticker: 주식 티커 심볼
        :param days_ago: 몇 일 전부터 계산할지 지정하는 정수
        :return: 가격 변동률
        """
        price_change_response = calculate_performance(stockticker, days_ago)
        return price_change_response

    def _arun(self, stockticker: str, days_ago: int):
        """비동기 실행을 지원하지 않음을 나타냅니다."""
        raise NotImplementedError("이 도구는 비동기를 지원하지 않습니다")

    args_schema: Optional[Type[BaseModel]] = StockChangePercentageCheckInput


class StockBestPerformingInput(BaseModel):
    """최고 성과 주식 찾기를 위한 입력 모델"""

    stocktickers: List[str] = Field(..., description="주식 또는 지수의 티커 심볼 리스트")
    days_ago: int = Field(..., description="몇 일 전부터 확인할지 지정하는 정수")

class StockGetBestPerformingTool(BaseTool):
    """여러 주식 중 최고 성과 주식을 찾는 도구"""

    name: str = "get_best_performing"
    description: str = "특정 기간 동안 여러 주식의 성과를 확인해야 할 때 유용합니다. yfinance API에서 사용되는 주식 티커 리스트와 변화를 확인할 일수를 입력해야 합니다."

    def _run(self, stocktickers: List[str], days_ago: int) -> tuple:
        """
        주어진 주식 리스트에서 최고 성과 주식을 찾습니다.
        :param stocktickers: 주식 티커 심볼 리스트
        :param days_ago: 몇 일 전부터 계산할지 지정하는 정수
        :return: 최고 성과 주식과 그 성과율
        """
        price_change_response = get_best_performing(stocktickers, days_ago)
        return price_change_response

    def _arun(self, stockticker: List[str], days_ago: int):
        """비동기 실행을 지원하지 않음을 나타냅니다."""
        raise NotImplementedError("이 도구는 비동기를 지원하지 않습니다")

    args_schema: Optional[Type[BaseModel]] = StockBestPerformingInput

 

도구 3개를 모두 선택지로 제안합니다.

tools = [StockPriceTool(), StockPercentageChangeTool(), StockGetBestPerformingTool()]
tools
[StockPriceTool(), StockPercentageChangeTool(), StockGetBestPerformingTool()]

추가된 도구로부터 에이전트 호출

llm = ChatOpenAI(temperature=0, model="gpt-4o")
open_ai_agent = initialize_agent(tools,
                        llm,
                        agent=AgentType.OPENAI_FUNCTIONS,
                        verbose=True)
open_ai_agent.run("오늘 구글 주식은?")

> Entering new AgentExecutor chain...

Invoking: `get_stock_ticker_price` with `{'stockticker': 'GOOGL'}`


191.41오늘 구글 주식(GOOGL)의 가격은 $191.41입니다.

> Finished chain.
오늘 구글 주식(GOOGL)의 가격은 $191.41입니다.

 

open_ai_agent.run("구글의 주가가 지난 90일 동안 상승했나요? 상승했다면 얼마나 상승했나요?")

> Entering new AgentExecutor chain...

Invoking: `get_price_change_percent` with `{'stockticker': 'GOOGL', 'days_ago': 90}`


18.4구글(GOOGL)의 주가는 지난 90일 동안 18.4% 상승했습니다.

> Finished chain.
구글(GOOGL)의 주가는 지난 90일 동안 18.4% 상승했습니다.
open_ai_agent.run("구글의 주가가 지난 3개월 동안 얼마나 상승했습니까?")
> Entering new AgentExecutor chain...

Invoking: `get_price_change_percent` with `{'stockticker': 'GOOGL', 'days_ago': 90}`


18.4구글(GOOGL)의 주가는 지난 3개월 동안 약 18.4% 상승했습니다.

> Finished chain.
구글(GOOGL)의 주가는 지난 3개월 동안 약 18.4% 상승했습니다.
open_ai_agent.run("구글, 메타, 마이크로소프트 중 어느 주식이 지난 3개월 동안 가장 좋은 성과를 보였습니까?")
> Entering new AgentExecutor chain...

Invoking: `get_best_performing` with `{'stocktickers': ['GOOGL', 'META', 'MSFT'], 'days_ago': 90}`


('GOOGL', 18.4)지난 3개월 동안 구글(GOOGL) 주식이 18.4% 상승하며 메타(META)와 마이크로소프트(MSFT)보다 가장 좋은 성과를 보였습니다.

> Finished chain.
지난 3개월 동안 구글(GOOGL) 주식이 18.4% 상승하며 메타(META)와 마이크로소프트(MSFT)보다 가장 좋은 성과를 보였습니다.

 

open_ai_agent.run("MSFT(마이크로소프트)의 주가가 지난 3개월 동안 얼마나 상승했습니까?")

> Entering new AgentExecutor chain...

Invoking: `get_price_change_percent` with `{'stockticker': 'MSFT', 'days_ago': 90}`


0.91마이크로소프트(MSFT)의 주가는 지난 3개월 동안 약 0.91% 상승했습니다.

> Finished chain.
마이크로소프트(MSFT)의 주가는 지난 3개월 동안 약 0.91% 상승했습니다.
open_ai_agent.run("비트코인이 지난 3개월 동안 얼마나 상승했습니까?")

> Entering new AgentExecutor chain...

Invoking: `get_price_change_percent` with `{'stockticker': 'BTC-USD', 'days_ago': 90}`


53.59비트코인은 지난 3개월 동안 약 53.59% 상승했습니다.

> Finished chain.
비트코인은 지난 3개월 동안 약 53.59% 상승했습니다.

 

open_ai_agent.run("비트코인이 지난 3개월 동안 얼마나 상승했는지랑 구글이 5개월 간 얼마나 상승했는지 알려줘")
> Entering new AgentExecutor chain...

Invoking: `get_price_change_percent` with `{'stockticker': 'BTC-USD', 'days_ago': 90}`


53.59
Invoking: `get_price_change_percent` with `{'stockticker': 'GOOGL', 'days_ago': 150}`


11.15비트코인은 지난 3개월 동안 약 53.59% 상승했습니다. 구글(알파벳)은 지난 5개월 동안 약 11.15% 상승했습니다.

> Finished chain.
비트코인은 지난 3개월 동안 약 53.59% 상승했습니다. 구글(알파벳)은 지난 5개월 동안 약 11.15% 상승했습니다.
728x90