初步體驗通過 Semantic Kernel 與自己部署的通義千問開源大模型進行對話

春節之前被 Semantic Kernel 所吸引,開始瞭解它,學習它。

在寫這篇博文之前讀了一些英文博文,順便在這裏分享一下:

爲了方便學習與體驗以及寫代碼實踐 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 中的 schemehost,也就是將 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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章