返回文章列表

大語言模型工具使用

本文探討大語言模型(LLM)的工具使用策略,解決LLM固有的資訊即時性、數學運算和真實世界互動等限制。文章詳細介紹瞭如何使用OpenAI

機器學習 Web 開發

大語言模型的工具使用機制,讓模型能取得外部資訊和執行特定功能,彌補了其本身的不足。OpenAI 提供的 API 允許開發者定義工具,並讓模型在需要時呼叫這些工具。這篇文章詳細介紹瞭如何使用 OpenAI API 建立和使用工具,包含定義工具的 JSON 格式、建立查詢字典以及處理訊息的流程。同時,文章也探討了多輪對話中工具呼叫的連續性,以及如何將工具的執行結果整合回對話流程中,讓模型根據實際情況做出更精確的判斷和回應。更進一步地,文章也分析了工具呼叫的內部機制,以及如何使用 TypeScript 函式定義來清晰地描述工具的功能和引數,並提供了一些設計工具的最佳實務,讓開發者可以更有效地利用工具來提升大語言模型的能力。

工具使用與大語言模型的進化

大語言模型(LLM)如ChatGPT在多個領域展現出驚人的能力,但它們仍有許多侷限性。這些模型無法取得訓練資料以外的「隱藏」知識,例如公司內部的檔案、聊天記錄和程式碼等。此外,它們也無法處理需要即時資訊的任務,例如查詢最新的航班資訊或執行複雜的數學運算。

大語言模型的侷限性

  1. 缺乏即時資訊:LLM的訓練資料是有時間限制的,因此它們可能不知道最新的API變更或新聞事件。
  2. 數學運算能力不足:雖然LLM可以正確回答簡單的算術問題,但對於較複雜的運算,它們的表現就會變差。
  3. 無法與真實世界互動: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"],
            },
        },
    }
]

內容解密:

  1. get_room_temp函式:此函式模擬取得當前房間溫度的功能,回傳一個60至80之間的隨機整數,代表華氏溫度。
  2. set_room_temp函式:此函式模擬設定房間溫度的功能,目前僅傳回一個固定的字串「DONE」。
  3. tools列表:這是一個包含兩個工具定義的JSON陣列,每個工具都對應一個函式及其相關描述。
  4. 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,
            })

內容解密:

  1. process_messages函式:處理傳入的訊息列表,並與模型進行互動。
  2. 步驟1:將訊息和工具定義傳送給指定的模型(此處為"gpt-4o"),並取得模型的回應。
  3. 步驟2:將模型的回應新增到對話歷史中。
  4. 步驟3:檢查模型的回應是否包含工具呼叫請求。
  5. 步驟4:如果包含工具呼叫請求,則提取相關資訊並呼叫相應的函式,將結果儲存在function_response中。
  6. 步驟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)

內容解密:

  1. messages 列表初始化:首先,我們建立了一個包含系統訊息和使用者請求的 messages 列表。系統訊息定義了助理的角色和行為準則,而使用者請求則是具體的操作需求。
  2. process_messages 函式的呼叫:透過呼叫 process_messages 函式,我們讓模型根據目前的訊息列表生成回應。
  3. 模型的回應:模型首先生成了一個呼叫 get_room_temp 工具的請求,這表明模型需要取得目前的房間溫度。
  4. 工具呼叫與回應:應用程式接收到模型的工具呼叫請求後,實際呼叫 get_room_temp 函式,並將結果(例如房間溫度為 74ºF)注入到新的訊息中。

多輪對話與工具呼叫的連續性

在第一次呼叫 process_messages 之後,我們觀察到模型生成了兩個新的訊息:一個是對 get_room_temp 的呼叫,另一個是該工具的回應。接著,我們再次呼叫 process_messages,模型進一步生成了對 set_room_temp 的呼叫,以調整房間溫度。

# 第二次呼叫 process_messages
process_messages(client, messages)

內容解密:

  1. 第二次模型回應:模型根據目前的房間溫度(74ºF),決定將溫度調高 2 度,因此生成了對 set_room_temp 的呼叫,引數為 {"temp": 76}
  2. set_room_temp 的執行結果:應用程式執行 set_room_temp 後,將結果(DONE)追加到訊息列表中。
  3. 最終的使用者通知:最後一次呼叫 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|>

內容解密:

  1. 工具定義的位置:工具的定義被插入到系統訊息之後,這些定義被格式化為 ChatML 的一部分。
  2. 函式定義的重要性:正確描述工具函式有助於模型理解其功能和使用方法,同時也佔用了 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函式定義的優勢

  1. 豐富的型別定義詞彙:TypeScript允許更豐富的型別定義,這有助於確保模型按照正確的型別格式化引數。
  2. 檔案整合:函式定義中可以輕易地整合檔案說明,不僅函式本身有檔案,每個引數也有相應的說明。
  3. 一致的函式呼叫:函式定義的方式要求使用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物件的形式指定引數。

呼叫流程解析

  1. 誰應該發言? OpenAI API在完成文字的開頭插入<|im_start|>assistant,條件模型生成後續文字作為助手的回應。
  2. 是否應該呼叫工具? 模型生成to=functions.,指示將呼叫某個工具。
  3. 應該呼叫哪個工具? 模型生成函式名稱,例如set_room_temp
  4. 應該指定哪個引數? 模型推斷應該指定的引數,例如{"temp":
  5. 引數的值是多少? 模型預測當前引數將採用的值,例如77
  6. 是否完成? 一旦所有引數都已指定,模型預測應該結束,生成}<|im_end|>

這種機制展現了大語言模型的高度靈活性,在短短的10到20個token內,實作了多種高度專門化的推理演算法。

工具定義與使用

在開發與對話代理相關的工具時,需遵循特定的設計原則與描述方法。本章節將提供相關的指導方針,主要依據兩個直覺原則:

  1. 對人類來說易於理解的事物,對大語言模型(LLM)也同樣容易理解。
  2. 最佳結果源自於將提示(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
}

內容解密:

  1. 介面定義SearchCode介面定義了搜尋程式碼所需的引數,包括repoquery
  2. 引數說明repo代表GitHub倉函式庫名稱,query代表搜尋查詢字串。
  3. 函式實作searchCode函式接受SearchCode型別的引數,並傳回一個字串陣列,具體實作細節省略。
  4. 設計考量:此設計簡化了程式碼搜尋工具的定義,保持與GitHub API檔案一致的命名和格式,降低了模型的理解難度。