iT邦幫忙

2024 iThome 鐵人賽

DAY 5
0

每天的專案會同步到 GitLab 上,可以前往 GitLab 查看。
有興趣的朋友歡迎留言 or 來信討論,我的信箱是 nickchen1998@gmail.com

什麼是 Function Call?

今天我們要來聊聊 Function Call 這個東西。所謂 Function Call,顧名思義就是呼叫一個函式,一般來說,每種語言模型都會有自己所擅長的範圍,
例如最初的 GPT3.5 擅長進行文章摘要以及問答,微軟開發的 Copilot 則是有特別混入程式碼進行訓練,因此針對程式碼的編寫會更加的流暢且準確,
從這兩個範例當中我們可以了解到,每種模型在進行訓練時,依據準備的資料不同,在各個領域當中會有各自所擅長的功能,而 Function Call 的出現,
恰恰補足了這個問題。

不使用 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 為空:

with out tool

客製化 Function Call (Tool)

接著我們要來自己撰寫一個 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 方法即可,不過還是有幾點要注意的:

  1. name: 這個是 Tool 的名稱,一定要有,且不能重複。
  2. description: 這個是 Tool 的描述,可以讓 LLM 知道這個 Tool 是做什麼的,如同 Prompt 的下法一樣,越簡潔越清楚越好。
  3. _run: 雖然沒有強制一定要回傳 str 類型的資料,不過建議還是回傳 str 類型的資料,這樣才能夠讓 LLM 正確的回應。

串接客製化的 Function Call (Tool)

接著我們要來看一下如何串接客製化的 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 並且告訴我們需要調用他:

with tool

調用 Function Call (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,這樣我們就可以將這些資訊傳遞給模型進行處理:

with tool messages

進行最後的資料彙整

最後我們可以再次進行調用,將所有的資訊傳遞給模型進行處理:

ai_message = llm.invoke(input=messages)
print(ai_message.content)

可以看到 GPT 成功的回答出了我們想要的答案:

result

總結

今天我們學習了如何使用 Function Call 來進行客製化的功能,透過這個功能,我們可以讓 LLM 在遇到特定的問題時,自己去辨別是否要使用額外的工具來進行輔助,
另外 LangChain 也提供了一些內建的 Tool 可以參考 這個網址
如果已經有人寫好的話,我們就直接拿來使用即可,可以大幅降低我們的開發時間!

內容預告

明天我們要來介紹如何使用 LangChain 來操作資料庫,會使用到上面所提到的內建的 Tool,讓我們可以更加方便的進行資料的操作,敬請期待!


上一篇
Day 04 - Prompt Template 以及要點
下一篇
Day 06 - LangChain 與 SQL
系列文
初探 Langchain 與 LLM:打造簡易問診機器人30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言