春節之前被 Semantic Kernel 所吸引,開始瞭解它,學習它。
在寫這篇博文之前讀了一些英文博文,順便在這裏分享一下:
- Intro to Semantic Kernel – Part One
- Intro to Semantic Kernel – Part Two
- Build a custom Copilot experience with your private data using and Kernel Memory
- Semantic Kernel: The New Way to Create Artificial Intelligence Applications
- Semantic Kernel: A bridge between large language models and your code
爲了方便學習與體驗以及寫代碼實踐 Semantic Kernel,打算自己部署一個對中文友好的開源大模型,於是選擇了通義千問
根據通義千問開源倉庫中的 README,經過一番折騰,終於部署成功,詳見博文 以容器方式部署通義千問 Qwen
緊接着就是嘗試通過 Semantic Kernel 與自己部署的通義千問進行對話,在昨天晚上睡覺前初步嘗試成功,通過這篇博文記錄一下。
主要面臨的問題是 Semantic Kernel 與通義千問之間互不支持(內置支持),Semantic Kernel 目前只內置支持 OpenAI 與 Azure OpenAI。幸運的是,通義千問實現了一個四兩拔千斤的巧妙能力——提供了兼容 OpenAI api 的 api,於是這個大問題迎刃而解爲一個小問題——如何欺騙 Semantic Kernel 讓它在請求 OpenAI api 時改道請求自己部署的通義千問模型服務?
在 Semantic Kernel github issue 的一個評論中發現了一個移花接木的巧妙方法——通過 DelegatingHandler
修改 HttpClient 請求的 url。
對應到這裏的場景就是修改所請求的 OpenAI api url 中的 scheme
與 host
,也就是將 https://api.openai.com
替換爲 http://localhost:8901
,實現代碼如下
class QwenRedirectingHandler() : DelegatingHandler(new HttpClientHandler())
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.RequestUri = new UriBuilder(request.RequestUri!) { Scheme = "http", Host = "localhost", Port = 8901 }.Uri;
return base.SendAsync(request, cancellationToken);
}
}
移花接木之後本以爲初步體驗小功告成,卻遇到一個小挫折,在用下面的代碼發送 prompt 時報錯
var prompt = @"博客園是什麼網站";
var result = await kernel.InvokePromptAsync(prompt);
Console.WriteLine(result);
錯誤來自通義千問的響應
{"detail":"Invalid request: Expecting at least one user message."}
後來參考公衆號文章利用阿里通義千問和Semantic Kernel,10分鐘搭建知識助手中的代碼解決了
var prompt = @"<message role=""user"">博客園是什麼網站</message>";
var summarize = kernel.CreateFunctionFromPrompt(prompt);
var result = kernel.InvokeStreamingAsync(summarize);
await foreach (var item in result)
{
Console.Write(item.ToString());
}
解決這個問題後,控制檯就能看到來自通義千問慢吞吞的吐字回答:
博客園(CNG.cn)是中國最大的IT社區,也是一個專業的程序員學習交流的平臺。它提供了一個可以讓程序員交流思想、分享經驗的環境,並且有多重功能支持用戶創建個人博客和參與討論。
注:這裏使用的通義千問模型版本是 Qwen-7B-Chat
到此,初步體驗 Semantic Kernel 就小功告成了,下面是完整代碼。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-3.5-turbo", "***");
builder.Services.ConfigureHttpClientDefaults(b =>
b.ConfigurePrimaryHttpMessageHandler(() => new QwenRedirectingHandler()));
var kernel = builder.Build();
var prompt = @"<message role=""user"">博客園是什麼網站</message>";
var summarize = kernel.CreateFunctionFromPrompt(prompt);
var result = kernel.InvokeStreamingAsync(summarize);
await foreach (var item in result)
{
Console.Write(item.ToString());
}
class QwenRedirectingHandler() : DelegatingHandler(new HttpClientHandler())
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.RequestUri = new UriBuilder(request.RequestUri!) { Scheme = "http", Host = "localhost", Port = 8901 }.Uri;
return base.SendAsync(request, cancellationToken);
}
}