大語言模型的工具使用機制,讓模型能取得外部資訊和執行特定功能,彌補了其本身的不足。OpenAI 提供的 API 允許開發者定義工具,並讓模型在需要時呼叫這些工具。這篇文章詳細介紹瞭如何使用 OpenAI API 建立和使用工具,包含定義工具的 JSON 格式、建立查詢字典以及處理訊息的流程。同時,文章也探討了多輪對話中工具呼叫的連續性,以及如何將工具的執行結果整合回對話流程中,讓模型根據實際情況做出更精確的判斷和回應。更進一步地,文章也分析了工具呼叫的內部機制,以及如何使用 TypeScript 函式定義來清晰地描述工具的功能和引數,並提供了一些設計工具的最佳實務,讓開發者可以更有效地利用工具來提升大語言模型的能力。
工具使用與大語言模型的進化
大語言模型(LLM)如ChatGPT在多個領域展現出驚人的能力,但它們仍有許多侷限性。這些模型無法取得訓練資料以外的「隱藏」知識,例如公司內部的檔案、聊天記錄和程式碼等。此外,它們也無法處理需要即時資訊的任務,例如查詢最新的航班資訊或執行複雜的數學運算。
大語言模型的侷限性
- 缺乏即時資訊:LLM的訓練資料是有時間限制的,因此它們可能不知道最新的API變更或新聞事件。
- 數學運算能力不足:雖然LLM可以正確回答簡單的算術問題,但對於較複雜的運算,它們的表現就會變差。
- 無法與真實世界互動:LLM本身無法執行任何操作,它們只能透過請求使用者執行某些操作來間接影響真實世界。
工具使用的引入
為瞭解決這些問題,LLM社群開始採用工具使用的策略,讓語言模型能夠取得即時資訊、執行非語言任務,並與周圍的世界互動。其基本思路是告訴模型有哪些工具可用,以及何時和如何使用這些工具。
訓練用於工具使用的LLM
2023年6月,OpenAI推出了一種新的模型,該模型經過微調以支援工具呼叫。其他競爭對手的LLM也紛紛跟進。讓我們來看看OpenAI對工具使用的實作。
定義和使用工具
首先,我們需要設定實際的功能,讓它們能夠與真實世界互動,收集資訊並對環境進行更改。以下是一個範例,展示瞭如何模擬溫度控制的功能:
import random
def get_room_temp():
return str(random.randint(60, 80))
def set_room_temp(temp):
return "DONE"
接下來,我們將這些功能表示為JSON模式,以便OpenAI能夠在提示中表示它們:
tools = [
{
"type": "function",
"function": {
"name": "get_room_temp",
"description": "Get the ambient room temperature in Fahrenheit",
},
},
{
"type": "function",
"function": {
"name": "set_room_temp",
"description": "Set the ambient room temperature in Fahrenheit",
"parameters": {
"type": "object",
"properties": {
"temp": {
"type": "integer",
"description": "The desired room temperature in ºF",
},
},
"required": ["temp"],
},
},
}
]
內容解密:
get_room_temp函式:此函式模擬取得當前房間溫度的功能,回傳一個60至80之間的隨機整數,代表華氏溫度。set_room_temp函式:此函式模擬設定房間溫度的功能,目前僅傳回一個固定的字串「DONE」。tools列表:這是一個包含兩個工具定義的JSON陣列,每個工具都對應一個函式及其相關描述。function欄位:定義了函式的名稱、描述以及引數(如果有的話),讓模型知道如何呼叫這些函式。
建立查詢字典和處理訊息
我們還需要建立一個查詢字典,以便根據名稱檢索可用的工具:
available_functions = {
"get_room_temp": get_room_temp,
"set_room_temp": set_room_temp,
}
內容解密:
available_functions字典:將函式名稱對映到實際的函式實作,使得模型能夠根據名稱呼叫相應的功能。
訊息處理功能的實作如下(範例8-1):
import json
def process_messages(client, messages):
# 步驟1:將訊息和工具定義傳送給模型
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
response_message = response.choices[0].message
# 步驟2:將模型的回應追加到對話中
messages.append(response_message)
# 步驟3:檢查模型是否想要使用工具
if response_message.tool_calls:
# 步驟4:提取工具呼叫並進行評估
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(**function_args)
# 步驟5:將函式回應追加到對話中
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response,
})
內容解密:
process_messages函式:處理傳入的訊息列表,並與模型進行互動。- 步驟1:將訊息和工具定義傳送給指定的模型(此處為"gpt-4o"),並取得模型的回應。
- 步驟2:將模型的回應新增到對話歷史中。
- 步驟3:檢查模型的回應是否包含工具呼叫請求。
- 步驟4:如果包含工具呼叫請求,則提取相關資訊並呼叫相應的函式,將結果儲存在
function_response中。 - 步驟5:將函式執行的結果追加到對話歷史中,以便模型在後續的對話中參考。
工具呼叫的實作與內部機制探討
在前面的章節中,我們探討瞭如何使用 OpenAI 的 API 來實作工具呼叫(tool calling),並觀察了其背後的運作機制。現在,讓我們深入瞭解 process_messages 函式的運作,以及工具呼叫在內部是如何被模型處理的。
process_messages 函式的運作流程
當我們提供一個使用者請求給 process_messages 函式時,它會根據模型的回應生成新的訊息,並將這些訊息追加到訊息列表的末尾。以下是一個具體的範例:
from openai import OpenAI
# 初始化訊息列表
messages = [
{
"role": "system",
"content": "You are HomeBoy, a happy, helpful home assistant.",
},
{
"role": "user",
"content": "Can you make it a couple of degrees warmer in here?",
}
]
# 建立 OpenAI 使用者端
client = OpenAI()
# 呼叫 process_messages 函式
process_messages(client, messages)
內容解密:
messages列表初始化:首先,我們建立了一個包含系統訊息和使用者請求的messages列表。系統訊息定義了助理的角色和行為準則,而使用者請求則是具體的操作需求。process_messages函式的呼叫:透過呼叫process_messages函式,我們讓模型根據目前的訊息列表生成回應。- 模型的回應:模型首先生成了一個呼叫
get_room_temp工具的請求,這表明模型需要取得目前的房間溫度。 - 工具呼叫與回應:應用程式接收到模型的工具呼叫請求後,實際呼叫
get_room_temp函式,並將結果(例如房間溫度為 74ºF)注入到新的訊息中。
多輪對話與工具呼叫的連續性
在第一次呼叫 process_messages 之後,我們觀察到模型生成了兩個新的訊息:一個是對 get_room_temp 的呼叫,另一個是該工具的回應。接著,我們再次呼叫 process_messages,模型進一步生成了對 set_room_temp 的呼叫,以調整房間溫度。
# 第二次呼叫 process_messages
process_messages(client, messages)
內容解密:
- 第二次模型回應:模型根據目前的房間溫度(74ºF),決定將溫度調高 2 度,因此生成了對
set_room_temp的呼叫,引數為{"temp": 76}。 set_room_temp的執行結果:應用程式執行set_room_temp後,將結果(DONE)追加到訊息列表中。- 最終的使用者通知:最後一次呼叫
process_messages,模型生成了一條訊息,通知使用者房間溫度已從 74ºF 調整到 76ºF。
工具呼叫的內部表示與提示格式
工具呼叫在內部被表示為特定的檔案格式(ChatML)。例如,set_room_temp 函式在內部提示中被表示如下:
<|im_start|>system
You are HomeBoy, a happy, helpful home assistant.
# Tools
## functions
namespace functions {
// Set the ambient room temperature in Fahrenheit
type set_room_temp = (_: {
// The desired room temperature in ºF
temp: number,
}) => any;
} // namespace functions
<|im_end|>
內容解密:
- 工具定義的位置:工具的定義被插入到系統訊息之後,這些定義被格式化為 ChatML 的一部分。
- 函式定義的重要性:正確描述工具函式有助於模型理解其功能和使用方法,同時也佔用了 token 的預算。
Plantuml 圖表展示對話流程
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title 大語言模型工具使用
package "LLM 工具使用機制" {
package "LLM 侷限性" {
component [缺乏即時資訊] as realtime
component [數學運算不足] as math
component [無法真實互動] as interact
}
package "工具定義" {
component [JSON 格式定義] as json
component [TypeScript 函式] as typescript
component [OpenAI API] as api
}
package "執行流程" {
component [工具呼叫] as call
component [結果整合] as integrate
component [多輪對話] as dialog
}
}
realtime --> json : 工具解決
math --> json : 外部計算
interact --> json : 環境互動
json --> typescript : 參數定義
typescript --> api : API 整合
api --> call : 呼叫觸發
call --> integrate : 結果回傳
integrate --> dialog : 對話延續
note right of realtime
工具彌補侷限:
- 即時資訊查詢
- 複雜數學運算
- 真實世界操作
end note
note right of call
工具呼叫流程:
- 定義工具 JSON
- 建立查詢字典
- 處理回應訊息
end note
@enduml
此圖示展示了使用者請求、模型回應、工具呼叫及其結果之間的流程關係。模型的回應可能觸發進一步的工具呼叫,直到最終生成一條使用者通知訊息。
圖表說明:
- 節點 A:代表使用者的初始請求。
- 節點 B:表示模型的回應,可能包含工具呼叫或最終的使用者通知。
- 節點 C:實際執行工具函式的步驟,例如取得或設定房間溫度。
- 節點 D:代表工具函式的執行結果,這些結果會被送回給模型。
- 節點 E:最終由模型生成的使用者通知訊息,總結了執行的結果。
透過這個流程圖,我們可以更直觀地理解整個對話代理的工作機制,以及工具呼叫在其中扮演的重要角色。
深入理解工具呼叫與評估機制
在探討大語言模型(LLM)的工具呼叫機制時,我們可以觀察到模型如何巧妙地利用TypeScript函式定義來表示工具。這種方法具有多重優勢:
TypeScript函式定義的優勢
- 豐富的型別定義詞彙:TypeScript允許更豐富的型別定義,這有助於確保模型按照正確的型別格式化引數。
- 檔案整合:函式定義中可以輕易地整合檔案說明,不僅函式本身有檔案,每個引數也有相應的說明。
- 一致的函式呼叫:函式定義的方式要求使用JSON物件呼叫函式,並列出引數名稱,這確保了函式呼叫的一致性,使其更容易解析。同時,由於需要指定每個引數的名稱,模型在呼叫函式時更加「謹慎」,減少錯誤的可能性。
程式碼範例
function setRoomTemp(temp: number): string {
// 實作設定房間溫度的邏輯
return "DONE";
}
內容解密:
此TypeScript函式setRoomTemp接受一個名為temp的引數,型別為number,並傳回一個string型別的值。函式內部實作了設定房間溫度的邏輯,並在成功時傳回"DONE"。這種定義方式確保了引數型別的正確性,並透過檔案說明提供了清晰的使用。
工具呼叫與評估流程
當我們瞭解了工具定義的表示方式後,讓我們來看看它們是如何被呼叫和評估的。以下是一個內部的呼叫示例:
<|im_start|>user
I'm a bit cold. Can you make it a couple of degrees warmer in here?<|im_end|>
<|im_start|>assistant to=functions.get_room_temp
{}<|im_end|>
<|im_start|>tool
74<|im_end|>
<|im_start|>assistant to=functions.set_room_temp
{"temp": 76}<|im_end|>
<|im_start|>tool
DONE<|im_end|>
<|im_start|>assistant
The room temperature was 74ºF and has been increased to 76°F.<|im_end|>
在這個例子中,助手使用特殊的語法來呼叫函式,透過OpenAI訊息中的name欄位指定函式名稱,並使用content欄位以JSON物件的形式指定引數。
呼叫流程解析
- 誰應該發言? OpenAI API在完成文字的開頭插入
<|im_start|>assistant,條件模型生成後續文字作為助手的回應。 - 是否應該呼叫工具? 模型生成
to=functions.,指示將呼叫某個工具。 - 應該呼叫哪個工具? 模型生成函式名稱,例如
set_room_temp。 - 應該指定哪個引數? 模型推斷應該指定的引數,例如
{"temp":。 - 引數的值是多少? 模型預測當前引數將採用的值,例如
77。 - 是否完成? 一旦所有引數都已指定,模型預測應該結束,生成
}<|im_end|>。
這種機制展現了大語言模型的高度靈活性,在短短的10到20個token內,實作了多種高度專門化的推理演算法。
工具定義與使用
在開發與對話代理相關的工具時,需遵循特定的設計原則與描述方法。本章節將提供相關的指導方針,主要依據兩個直覺原則:
- 對人類來說易於理解的事物,對大語言模型(LLM)也同樣容易理解。
- 最佳結果源自於將提示(prompts)設計成類別似訓練資料的模式(即小紅帽原則)。
選擇適當的工具
- 限制模型可使用的工具數量:模型可使用的工具越多,混淆的可能性越大。理想情況下,工具應劃分領域活動,即盡可能涵蓋整個領域,但避免執行類別似操作的工具。
- 選擇簡單的工具:避免直接將網頁API複製到提示中,因為網頁API通常具有大量引數和複雜的回應,這不僅佔用大量空間,也降低了模型成功呼叫該工具的可能性。
命名工具和引數
- 使用有意義且自檔案化的名稱:如同人類閱讀API規範一樣,模型會根據名稱建立對工具和引數用途的預期。
- 遵循命名慣例:對於OpenAI,工具是以TypeScript呈現的,因此建議遵循駝峰式命名(camel case)慣例。避免使用小寫連線詞(如retrieveemail),因為這些名稱更難被解析。
定義工具
- 簡化定義:盡可能簡化定義,同時捕捉足夠的細節,以便模型(或人類)理解如何使用該工具。如果定義聽起來像法律術語,那麼可能引入了太多的概念,讓模型的注意力機制難以處理。
- 參考公共API:如果使用的是模型熟悉的公共API,那麼建立一個簡化版本的API,保持原始API的命名、概念和風格,可以幫助模型更好地理解。
處理引數
- 保持引數簡單少數:盡可能保持引數少且簡單。OpenAI模型能夠很好地處理JSON schema型別,如字串、數字、整數和布林值。
程式碼示例與解析
interface SearchCode {
/**
* GitHub repository name
*/
repo: string;
/**
* Search query
*/
query: string;
}
function searchCode(args: SearchCode): string[] {
// Implementation details
}
內容解密:
- 介面定義:
SearchCode介面定義了搜尋程式碼所需的引數,包括repo和query。 - 引數說明:
repo代表GitHub倉函式庫名稱,query代表搜尋查詢字串。 - 函式實作:
searchCode函式接受SearchCode型別的引數,並傳回一個字串陣列,具體實作細節省略。 - 設計考量:此設計簡化了程式碼搜尋工具的定義,保持與GitHub API檔案一致的命名和格式,降低了模型的理解難度。