每天的專案會同步到 GitLab 上,可以前往 GitLab 查看。
有興趣的朋友歡迎留言 or 來信討論,我的信箱是 nickchen1998@gmail.com。
今天我們要來聊聊 Function Call 這個東西。所謂 Function Call,顧名思義就是呼叫一個函式,一般來說,每種語言模型都會有自己所擅長的範圍,
例如最初的 GPT3.5 擅長進行文章摘要以及問答,微軟開發的 Copilot 則是有特別混入程式碼進行訓練,因此針對程式碼的編寫會更加的流暢且準確,
從這兩個範例當中我們可以了解到,每種模型在進行訓練時,依據準備的資料不同,在各個領域當中會有各自所擅長的功能,而 Function Call 的出現,
恰恰補足了這個問題。
首先我們先來看一個簡單的範例,理論上 LLM 如果沒有及時拜訪網址的功能的話,是沒有辦法取得最新資訊的,例如: 取得最新的 PTT 八卦版文章。
from env_settings import EnvSettings
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
env_settings = EnvSettings()
llm = ChatOpenAI(
api_key=env_settings.OPENAI_API_KEY,
model_name="gpt-4o",
verbose=True
)
messages = [HumanMessage(content="可以告訴我 PTT 八卦版最新的文章嗎?"),]
ai_message = llm.invoke(
input=messages,
)
print(ai_message.content)
print(ai_message.tool_calls)
可以看到執行的結果如下,GPT回應我們無法取得最新資訊,並且 tool_calls 為空:
接著我們要來自己撰寫一個 Tool,讓 LLM 可以在接獲類似的提問的時候,自己去辨別是否要使用這個 Tool 來取得最新的資訊。
import requests
from typing import Any
from bs4 import BeautifulSoup
from langchain_core.tools import BaseTool
class PttGossipingTool(BaseTool):
name = "ptt_gossiping_tool"
description = "擷取 PTT 八卦版最新文章的小工具"
def _run(self, *args: Any, **kwargs: Any) -> Any:
url = 'https://www.ptt.cc/'
web = requests.get('https://www.ptt.cc/bbs/Gossiping/index.html', cookies={'over18': '1'})
web.encoding = 'utf-8'
soup = BeautifulSoup(web.text, "html.parser")
titles = soup.find_all('div', class_='title')
output = ''
for i in titles:
if i.find('a') is not None:
output = output + i.find('a').get_text() + '\n' + url + i.find('a')['href'] + '\n\n'
return f"八卦版最新的文章如下:\n{output}"
上方的程式碼當中可以看到,如果今天要自己客製化一個 Tool 的話,只需要繼承 BaseTool 這個類別,並且實作 _run 方法即可,不過還是有幾點要注意的:
接著我們要來看一下如何串接客製化的 Function Call (Tool):
from env_settings import EnvSettings
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from tool import PttGossipingTool
env_settings = EnvSettings()
llm = ChatOpenAI(
api_key=env_settings.OPENAI_API_KEY,
model_name="gpt-4o",
verbose=True
)
messages = [HumanMessage(content="可以告訴我 PTT 八卦版最新的文章嗎?"),]
ai_message = llm.invoke(
input=messages,
)
print(ai_message.content)
print(ai_message.tool_calls)
print("==================================")
tools = [PttGossipingTool()]
llm = llm.bind_tools(tools)
ai_message = llm.invoke(
input=messages,
)
print(ai_message.content)
print(ai_message.tool_calls)
下圖中我們可以看到,紅色方框裡面是第二次執行的結果,這次印出 content 時得到一個空字串,而 tool_calls 裡面則是有我們剛剛所寫的 Tool 的資訊,表示 GPT 有成功辨別到我們有這個 tool 並且告訴我們需要調用他:
知道我們要使用哪個 tool 之後,我們就可以透過調用 tool 來取得我們想要的資訊,眼尖的朋友應該會發現 tool_calls 的型態是一個 list,
代表我們可以同時塞很多個 tool 讓 LLM 自己去告訴我們要使用哪個 tool,在取得列表後,也只需要按照 name 取得對應的 tool 回來做使用即可。
我們把之前的程式碼稍作整理,並重構成下面的程式碼,同時進行 tool 的調用:
from env_settings import EnvSettings
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, ToolMessage
from tool import PttGossipingTool
from pprint import pprint
env_settings = EnvSettings()
llm = ChatOpenAI(
api_key=env_settings.OPENAI_API_KEY,
model_name="gpt-4o",
verbose=True
)
tools = [PttGossipingTool()]
llm = llm.bind_tools(tools)
messages = [HumanMessage(content="可以告訴我 PTT 八卦版最新的文章嗎?"),]
ai_message = llm.invoke(
input=messages,
)
tool_messages = []
for tool_call in ai_message.tool_calls:
for tool in tools:
if tool_call.get("name") == tool.name:
tool_content = tool.run(tool_call.get("args"))
tool_messages.append(ToolMessage(
content=tool_content,
tool_call_id=tool_call.get("id"),
))
break
messages.append(ai_message)
messages.extend(tool_messages)
pprint(messages)
下圖可以看到,我們成功的取得了最新的 PTT 八卦版文章,並同時將之前的問題以及回答都包裝成了一個 list,這樣我們就可以將這些資訊傳遞給模型進行處理:
最後我們可以再次進行調用,將所有的資訊傳遞給模型進行處理:
ai_message = llm.invoke(input=messages)
print(ai_message.content)
可以看到 GPT 成功的回答出了我們想要的答案:
今天我們學習了如何使用 Function Call 來進行客製化的功能,透過這個功能,我們可以讓 LLM 在遇到特定的問題時,自己去辨別是否要使用額外的工具來進行輔助,
另外 LangChain 也提供了一些內建的 Tool 可以參考 這個網址,
如果已經有人寫好的話,我們就直接拿來使用即可,可以大幅降低我們的開發時間!
明天我們要來介紹如何使用 LangChain 來操作資料庫,會使用到上面所提到的內建的 Tool,讓我們可以更加方便的進行資料的操作,敬請期待!