Function Calling 에이전트
!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% 상승했습니다.