動手學Avalonia:基於SemanticKernel與矽基流動構建AI聊天與翻譯工具

mingupupup發表於2024-07-03

Avalonia是什麼?

Avalonia是一個跨平臺的UI框架,專為.NET開發打造,提供靈活的樣式系統,支援Windows、macOS、Linux、iOS、Android及WebAssembly等多種平臺。它已成熟並適合生產環境,被Schneider Electric、Unity、JetBrains和GitHub等公司採用。

許多人認為Avalonia是WPF的繼任者,它為XAML開發人員提供了一種熟悉且現代的跨平臺應用開發體驗。儘管與WPF相似,但Avalonia並非完全複製,而包含了許多改進。

image-20240703120741584

SemanticKernel是什麼?

Semantic Kernel是一個SDK,它可以將大型語言模型(如OpenAI、Azure OpenAI和Hugging Face)與常規程式語言(如C#、Python和Java)整合。特殊之處在於,Semantic Kernel透過允許定義和鏈式呼叫外掛,能夠自動排程並組合這些AI模型。其功能是,使用者可以向LLM提出個性化目標,由Semantic Kernel的規劃器生成實現目標的計劃,然後由系統自動執行這份計劃。

image-20240703121053734

矽基流動介紹

矽基流動致力於打造大模型時代的AI基礎設施,透過演算法、系統和硬體的協同創新,跨數量級降低大模型應用成本和開發門檻,加速AGI普惠人類。

SiliconCloud是集合主流開源大模型的一站式雲服務平臺,為開發者提供更快、更便宜、更全面、體驗更絲滑的模型API。

目前,SiliconCloud已上架包括DeepSeek-Coder-V2、Stable Diffusion 3 Medium、Qwen2、GLM-4-9B-Chat、DeepSeek V2、SDXL、InstantID在內的多種開源大語言模型、圖片生成模型,支援使用者自由切換符合不同應用場景的模型。同時,SiliconCloud提供開箱即用的大模型推理加速服務,為生成式AI應用帶來更高效的使用者體驗。

我們知道在國內使用OpenAI不太方便同時成本也比較高。現在已經有很多開源的大模型了,但是對於個人開發者而言,部署它們的一大難點是硬體資源。沒有顯示卡,也能部署一些引數少一些的開源大模型,但是推理速度肯定是很慢的,這裡選擇矽基流動的原因是第一,之前註冊送了42元的額度,該額度不會過期,可以一直使用,第二,試了一下推理速度真的很快,第三(也是最重要的一點)(白嫖),矽基流動宣佈:SiliconCloud平臺的Qwen2(7B)、GLM4(9B)、Yi1.5(9B)等頂尖開源大模型免費使用。

構建什麼樣的工具

最近在學習Avalonia,動手做一個小工具實現自己的需求是一個很好的開始。同時對SemanticKernel也比較感興趣,所以選擇從最基本的製作一個基於大模型的聊天應用開始。個人對大模型的一大需求就是翻譯,在檢視英文網站時,遇到不太理解的地方,總喜歡問大模型,將某某某翻譯為中文。因此選擇構建解決自己這個需求的Avalonia練手小工具。該工具的效果如下所示:

聊天

英譯中

中譯英

開始實踐

在SemanticKernel中使用SiliconCloud提供的API服務

要解決的第一個問題就是如何在SemanticKernel中使用SiliconCloud提供的服務。

SemanticKernel中並沒有告訴我們如何連線其他的大模型,但由於SiliconCloud提供的介面是與OpenAI相容的,因此可以透過在傳送請求時,改變傳送請求的地址來實現。

新增OpenAIHttpClientHandler類:

public class OpenAIHttpClientHandler : HttpClientHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        UriBuilder uriBuilder;
        switch (request.RequestUri?.LocalPath)
        {
            case "/v1/chat/completions":
                uriBuilder = new UriBuilder(request.RequestUri)
                {
                    // 這裡是你要修改的 URL
                    Scheme = "https",
                    Host = "api.siliconflow.cn",
                    Path = "v1/chat/completions",
                };
                request.RequestUri = uriBuilder.Uri;
                break;
        }
    
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
      
        return response;
    }
}

kernel透過這種方式構建:

var handler = new OpenAIHttpClientHandler();
var builder = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
   modelId: "Qwen/Qwen1.5-7B-Chat",
   apiKey: "你的apikey",
   httpClient: new HttpClient(handler));
_kernel = builder.Build();

_kernel為全域性私有變數:

private Kernel _kernel;

構建頁面

axaml如下所示:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="using:AvaloniaChat.ViewModels"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:views="clr-namespace:AvaloniaChat.Views"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="AvaloniaChat.Views.MainWindow"
        Icon="/Assets/avalonia-logo.ico"
        Title="AvaloniaChat">
	<Design.DataContext>
		<!-- This only sets the DataContext for the previewer in an IDE,
         to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
		<vm:MainViewModel />
	</Design.DataContext>
	<StackPanel>
		<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>

    <Grid Grid.Column="0">
		<StackPanel>
			<StackPanel Orientation="Horizontal">
				 <Button Content="問AI" Margin="10"
					     Command="{Binding AskCommand}"></Button>
				 <!--<Button Content="翻譯為:"></Button>-->
				 <Label Content="翻譯為:"
						HorizontalAlignment="Center"
						VerticalAlignment="Center"></Label>
				 <ComboBox ItemsSource="{Binding Languages}"
						   SelectedItem="{Binding SelectedLanguage}"
						   HorizontalAlignment="Center"
						   VerticalAlignment="Center"></ComboBox>
			     <Button Content="翻譯" Margin="10"
					Command="{Binding TranslateCommand}"></Button>
			</StackPanel>	   
	        <TextBox Height="300" Margin="10"
					 Text="{Binding AskText}"
				     TextWrapping="Wrap"
					 AcceptsReturn="True"></TextBox>
		</StackPanel>    
    </Grid>

    <Grid Grid.Column="1">
       <StackPanel>
		    <Button Content="AI回答" Margin="10"></Button>
	        <TextBox Height="300" 					 
					 Margin="10"
					 Text="{Binding ResponseText}"
	                 TextWrapping="Wrap"></TextBox>
		</StackPanel>    
    </Grid>
</Grid>		
	</StackPanel>
</Window>

介面效果如下所示:

image-20240703134726518

構建ViewModel

ViewModel如下所示:

public partial class MainViewModel : ViewModelBase
{  
    private Kernel _kernel;

    [ObservableProperty]
    private string askText;

    [ObservableProperty]
    private string responseText;

    [ObservableProperty]
    private string selectedLanguage;

    public string[] Languages { get; set; }

    public MainViewModel()
    {
        var handler = new OpenAIHttpClientHandler();
        var builder = Kernel.CreateBuilder()
        .AddOpenAIChatCompletion(
           modelId: "Qwen/Qwen1.5-7B-Chat",
           apiKey: "你的apikey",
           httpClient: new HttpClient(handler));
        _kernel = builder.Build();
        AskText = " ";
        ResponseText = " ";
        SelectedLanguage = " ";
        Languages = new string[] { "中文","英文"};
    }

    [RelayCommand]
    private async Task Ask()
    {   
        if(ResponseText != "")
        {
            ResponseText = "";
        }
        await foreach (var update in _kernel.InvokePromptStreamingAsync(AskText))
        {
            ResponseText += update.ToString();         
        }     
    }

    [RelayCommand]
    private async Task Translate()
    {
        string skPrompt =   """
                            {{$input}}

                            將上面的輸入翻譯成{{$language}},無需任何其他內容
                            """;
    
        if (ResponseText != "")
        {
            ResponseText = "";
        }
        await foreach (var update in _kernel.InvokePromptStreamingAsync(skPrompt, new() { ["input"] = AskText,["language"] = SelectedLanguage }))
        {
            ResponseText += update.ToString();
        }
    }
}

使用流式返回

[RelayCommand]
private async Task Ask()
{   
    if(ResponseText != "")
    {
        ResponseText = "";
    }
    await foreach (var update in _kernel.InvokePromptStreamingAsync(AskText))
    {
        ResponseText += update.ToString();         
    }     
}

實現效果如下:

寫提示

當我們需要翻譯功能的時候,只需要翻譯文字,其他的內容都不要,簡易的模板如下:

 string skPrompt =   """
                     {{$input}}

                     將上面的輸入翻譯成{{$language}},無需任何其他內容
                     """;

{{$input}}{{$language}}是模板裡的引數,使用時會被替換,如下所示:

 await foreach (var update in _kernel.InvokePromptStreamingAsync(skPrompt, new() { ["input"] = AskText,["language"] = SelectedLanguage }))
 {
     ResponseText += update.ToString();
 }

透過以上這幾個步驟,我們就使用Avalonia製作完成一個簡易的小工具了。

相關文章