OpenAI 在 2023 年 11 月推出了 GPTs,讓使用者可以客制化自己的聊天助理,透過自定義的提示詞來設定 ChatGPT 的用途和功能並且可以串接自己或是第三方的 API 來讓自定義的 GPTs 可以完成更多模型無法直接完成的功能,而在推出 GPTs 的時候也推出了 Assistants API 讓開發者也可以客制畫出自己的聊天助理,透過這個 API 也可以讓我們更快速的來建立聊天助理,而不需要另外處理過往的聊天記錄,在過去要客制化自己的聊天助理,需要另外準備 DB 或是其它方式來儲存過往的聊天歷史對話,現在透過 Assistants API 就可以記錄在 OpenAI 上,透過 API 就可以存取對話的歷史聊天記錄,在 2024 年 2 月微軟也把這個 API 新增到 Azure 上了,後面就來介紹這個 API 並且實做。
說明
運行流程
首先用一張 OpenAI 官方的圖來說明一些後面會提到的物件。
物件 | 說明 |
Assistant 助理 | 使用 OpenAI 模型和可呼叫工具的 AI |
Thread 聊天串 | 助理和使用者的對話內容。儲存聊天訊息並且可以自動處理成適合模型的前後文內容。 |
Message 訊息 | 每個訊息皆為助理或使用者建立的。訊息內容可以包含文字、圖像和文件,會以列表的形式儲存在聊天串上。 |
Run 執行 | 助理在聊天串的呼叫。助理透過呼叫模型和工具,利用其設定和聊天串的訊息來執行任務。作為每一次執行的一部分,助理將訊息附加到聊天串上。 |
Run Step 執行步驟 | 一個助理在每一次執行中所採取的詳細步驟清單。助理可以在其執行期間呼叫工具或建立訊息。檢查執行步驟讓您能夠深入了解助理是如何得到其最終結果的。 |
整個 Assistant API 運行的步驟說明如下:
- 建立助理
首先要先建立一個助理,建立之後會產生一組獨立的 Id,未來可以利用此 Id 來取得設定好的助理來修改相關設定,建立助理的時候可以設定使用的 GPT 模型以及使用的工具,目前支援的工具包含了程式碼解譯器 (Code Interpreter)、知識檢索 (Knowledge Retrieval)和函示呼叫 (Function calling),其中知識檢索在 Azure 上面還不支援,這些工具的說明和使用會在後面詳細說明。助理是不會被自動刪除,且有 API 可以列出所有建立過的助理。 - 建立聊天串和附加訊息
再來建立一個聊天串,可以放入聊天的訊息前後文讓呼叫的模型達到更好的效果,建立的時候也會產生一組獨立的 Id,強烈建議要把這組 Id 儲存起來,因為目前 API 無法取得過往的聊天串,如果沒記錄就會消失在大海中,未來想重複使用或刪除就會無法使用,而閒置的聊天串會在 60 天 後被刪除 (這只在討論區看到,並沒有找告官方文件說明)。 - 呼叫助理並傳入聊天工作階段來執行 (Run) 結果
透過前面建立的助理和聊天串 Id 來產生一次的執行,執行結果的回覆或產生的檔案會被加入到聊天串中,後續就繼續把使用者的回覆新增到聊天串之後進行下一次的執行。
聊天串和助理並沒有絕對的關連,可以把聊天串給不同的助理去產生結果。
透過 Azure OpenAI Studio 來建立和使用助理
後面我們會用 Azure 上面的 Azure OpenAI Assistants API 來實做,但是 OpenAI 跟 Azure OpenAI 大部分都是相容的,所以程式碼基本上只需要調整 Client 就可以通用了。
在 Azure OpenAI Studio 上點選助理來查看看管理已建立的助理,如果目前沒有部署任何 GPT 模型的話會出現底下畫面,可以點選建立新部署來建立 GPT 模型。
如果我們有建立好模型的話就可以來建立我們第一個助理。最簡單的助理設定就是名字和使用的 Gpt 模型,函數和程式碼解譯器就是前面提到的助理工具,目前只支援程式碼解譯器 (Code Interpreter)和函示呼叫 (Function calling),至於檔案的部分則是可以上傳檔案給助理使用,而這檔案是可以多助理共用的,這邊就不多做介紹,後面我們會用程式來實做,透過 Azure OpenAI Studio 遊樂場來測試助理是無法真正發揮他的用處的,像是函示呼叫也不會真的可以運作,這邊僅適合建立跟管理助理和做簡單的聊天測試,要真正發揮助理還是得靠程式實做才行,如果要列出所有已建立的助理可以點選 Open 就可以列出跟切換建立好的助理,這邊就不多太多做說明,還是後面透過程式實做來說明。
基本範例
接下來我們會使用 Azure.AI.OpenAI
來操作 Assistants API,可以透過 NuGet 來使用這個套件。
dotnet add package Azure.AI.OpenAI --prerelease
接下來建立一個 Console 程式。
static async Task Main(string[] args)
{
var azureResourceUrl = "https://{your account}.openai.azure.com/";
var azureApiKey = "{Your Api Key}";
var deploymentName = "{Your Model Name}";
// Assistants is a beta API and subject to change; acknowledge its experimental status by suppressing the matching warning.
#pragma warning disable OPENAI001
AssistantClient client = new AzureOpenAIClient(new Uri(azureResourceUrl), new AzureKeyCredential(azureApiKey)).GetAssistantClient();
// 1. 建立助理
Assistant assistant = await client.CreateAssistantAsync(
model: deploymentName,
new AssistantCreationOptions()
{
Name = "DEMO 助理",
Instructions = "你是 Azure 專家,會回覆關於 Azure 的問題。"
});
// 2. 建立聊天串
AssistantThread thread = await client.CreateThreadAsync();
var messageResponse = await client.CreateMessageAsync(thread,
[
"如何建立一台 VM?"
]
);
// 3. 運行助理回覆問題
var runResponse = await client.CreateRunAsync(thread, assistant);
ThreadRun run = runResponse.Value;
do
{
await Task.Delay(TimeSpan.FromMilliseconds(500));
runResponse = await client.GetRunAsync(thread.Id, runResponse.Value.Id);
}
while (runResponse.Value.Status == RunStatus.Queued || runResponse.Value.Status == RunStatus.InProgress);
// 4. 顯示出助理運行完後的聊天串
var afterRunMessagesResponse = client.GetMessagesAsync(thread);
await foreach (ThreadMessage threadMessage in afterRunMessagesResponse)
{
Console.WriteLine($"{threadMessage.CreatedAt:yyyy-MM-dd HH:mm:ss} - {threadMessage.Role,10}: ");
foreach (MessageContent contentItem in threadMessage.Content)
{
if (!string.IsNullOrEmpty(contentItem.Text))
{
Console.Write(contentItem.Text);
}
else if (!string.IsNullOrEmpty(contentItem.ImageFileId))
{
Console.Write($"<image from ID: {contentItem.ImageFileId}");
}
Console.WriteLine();
}
}
}
我們也可以把 3 和 4 合併執行,可以改成底下程式碼。
RunCreationOptions runOptions = new()
{
//AdditionalInstructions = "Please address the user as Jane Doe. The user has a premium account."
};
await foreach (StreamingUpdate streamingUpdate in client.CreateRunStreamingAsync(thread, assistant, runOptions))
{
if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated)
{
Console.WriteLine($"--- Run started! ---");
}
else if (streamingUpdate is MessageContentUpdate contentUpdate)
{
Console.Write(contentUpdate.Text);
if (contentUpdate.ImageFileId is not null)
{
Console.WriteLine($"[Image content file ID: {contentUpdate.ImageFileId}");
}
}
}
執行程式之後可以得到這樣的結果:
以上就是一個簡單的呼叫範例,如同前面說明按照順序執行就可以跑出結果了,後續就可以把聊天串 Id 記錄下來重複利用就可以完成聊天助理了,完全不需要再自己處理過往的聊天訊息。
費用
Assistants API 的費用會分成兩個部分,一個是呼叫模型,另一個是呼叫工具,而模型就取決於助理設定的 GPT 模型來計費,工具的部分目前僅有程式碼解譯器會產生額外的費用,他會根據工作階段來計費,每個工作階段預設為一小時,目前 Azure OpenAI 和 OpenAI 的價錢是一樣的。
可透過 API 取得每一的執行的使用量資料,但目前 Azure OpenAI 上的 Assistants API 還不支援, OpenAI 的 Assistants API 回傳的結果會包含使用的 Token 數。
結論
之前我們要透過 OpenAI 來實做聊天機器人會需要花很多時間處理和儲存過往的聊天訊息,把它帶入每一次的模型呼叫,現在我們透過 Assistants API 就完全不需要處理這些,API 都會在後面幫我們處理掉,而且還可以把聊天串給不同的助理來回覆,可以讓結果達到最佳的校過,而本文僅簡單實做 Assistants API,後面我們會更進一步說明 API 的內容和使用。