當前位置: 妍妍網 > 碼農

使用 .NET 開發 AI 套用(6): 為 AI 添加「記憶」

2024-06-22碼農

文末附全部原始碼






在上一章中,我們建立了一個 AI 聊天機器人套用。但是在使用過程中,你會發現 AI 模型是沒有「記憶」的。

舉個例子,我們要AI記住我的名字,AI 也回復記住了,但當我們再次詢問時,它卻不記得了:

這是因為 AI 模型 是無狀態的 AI 模型 旨在同時服務於成千上萬的使用者,而為每個使用者維護一個持久的狀態將是一個資源密集型的任務。

無狀態的設計允許模型輕松地擴充套件到多個例項,而不需要同步使用者狀態,從而提高了效率和可靠性。

這種設計使得模型能夠高效地服務於大量使用者,但也限制了對話的連貫性。

然而,透過提示工程,我們可以巧妙地 為 AI 添加「記憶」

提示工程

提示工程是一種技術,它透過精心設計的提示詞,引導模型提供更準確、更相關的資訊,使其更加符合特定的套用場景和研究領域。

提示詞不僅包含指令或問題,還可以包含上下文、輸入或範例等詳細資訊。這些元素共同作用,幫助模型更好地理解請求的意圖,從而生成更加滿意的答案。

例如,透過將對話歷史轉化為提示詞,我們可以使模型在生成響應時考慮之前的互動。

這就像是給模型一個記憶的視窗,讓它能夠在一定程度上「記住」與使用者的對話。

更多提示工程的相關知識,大家可以透過【面向開發者的 ChatGPT 提示詞工程】這門免費課程(https://github.com/GitHubDaily/ChatGPT-Prompt-Engineering-for-Developers-in-Chinese)進行學習。

程式碼實作

我們使用的Gradio.Net已經記錄下了對話歷史,我們只需將其作為上下文傳遞給模型。這樣,每次使用者與AI互動時,模型都會接收到之前的對話,從而生成更加連貫和個人化的回答。

修改後的 GenerateResponse 方法使用提示樣版傳入之前的對話( userPrompt):

static async IAsyncEnumerable<Output> GenerateResponse(Input input, Kernel kernel)
{
var userPrompt = Textbox.Payload(input.Data[0]);
var chatHistory = Chatbot.Payload(input.Data[1]);
var chatMessages = ChatHistoryToPrompt(chatHistory);
var promptTemplate = @"{{ $chatMessages }}
user:{{ $userPrompt }}
assistant:
"
;
chatHistory.Add(new ChatbotMessagePair(new ChatMessage { TextMessage = userPrompt }, new ChatMessage { TextMessage = "" }));
await foreach (var responseMessage in kernel.InvokePromptStreamingAsync<string>(promptTemplate, new() { { "chatMessages", chatMessages }, { "userPrompt", userPrompt } }))
{
if (!string.IsNullOrEmpty(responseMessage))
{
chatHistory.Last().AiMessage.TextMessage += responseMessage;
yield return gr.Output("", chatHistory);
}
await Task.Delay(50);
}
}

現在,我們再執行看下效果:

結論

雖然LLM本身可能不具備記憶功能,但透過提示工程和適當的工具,我們可以克服LLM的無狀態限制,為使用者提供更加豐富和深入的對話體驗。

Gradio.NET(https://github.com/feiyun0112/Gradio.Net/)的目標是成為用於開發 Web 套用的 .NET 開發者的首選框架。它的設計理念是讓開發變得更加簡單,讓每個人都能夠參與到Web套用的創造中來。

添加微信 GradioDotNet ,透過加入技術討論群,開發者們可以分享經驗,解決問題,並共同推動.NET的發展。

完整原始碼:

using Gradio.Net;
using Microsoft.SemanticKernel;
using System.Text;
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_GPT_NAME");
string apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: deploymentName,
endpoint: endpoint,
apiKey: apiKey)
.Build();
App.Launch(await CreateBlocks(kernel));
static async Task<Blocks> CreateBlocks(Kernel kernel)
{
using (var blocks = gr.Blocks())
{
var chatbot = gr.Chatbot();
var txt = gr.Textbox(showLabel: false,
placeholder: "輸入文本並按回車鍵"
);
txt.Submit(streamingFn: (input) => GenerateResponse(input, kernel), inputs: new Gradio.Net.Component[] { txt, chatbot }, outputs: new Gradio.Net.Component[] { txt, chatbot });
return blocks;
}
}
static async IAsyncEnumerable<Output> GenerateResponse(Input input, Kernel kernel)
{
var userPrompt = Textbox.Payload(input.Data[0]);
var chatHistory = Chatbot.Payload(input.Data[1]);
var chatMessages = ChatHistoryToPrompt(chatHistory);
var promptTemplate = @"{{ $chatMessages }}
user:{{ $userPrompt }}
assistant:
"
;
chatHistory.Add(new ChatbotMessagePair(new ChatMessage { TextMessage = userPrompt }, new ChatMessage { TextMessage = "" }));
await foreach (var responseMessage in kernel.InvokePromptStreamingAsync<string>(promptTemplate, new() { { "chatMessages", chatMessages }, { "userPrompt", userPrompt } }))
{
if (!string.IsNullOrEmpty(responseMessage))
{
chatHistory.Last().AiMessage.TextMessage += responseMessage;
yield return gr.Output("", chatHistory);
}
await Task.Delay(50);
}
}
static string ChatHistoryToPrompt(IList<ChatbotMessagePair> chatHistory)
{
var sb = new StringBuilder();
foreach (var chatbotMessagePair in chatHistory)
{
sb.AppendLine($"user:{chatbotMessagePair.HumanMessage.TextMessage}");
sb.AppendLine($"assistant:{chatbotMessagePair.AiMessage.TextMessage}");
}
return sb.ToString();
}