C# Ollama Yapay Zeka Modeli ile Konuşma Uygulaması

  • 16
  • (1)
  • (5)
  • 16 Eyl 2024

Ollama Nasıl Yüklenir

Ollama uygulaması ile kendi bilgisayarınıza Large Language Model (Geniş Dil Modeli) yükleyebilirsiniz. Çeşitli geniş dil modellerini indirebileceğiniz ve kullanabileceğiniz bir uygulamadır.

ollama.com websitesinden uygulamayı kurup indirebilirsiniz. Ollama uygulamasını kurduktan sonra Ollama ile çalıştırabileceğiniz çeşitli Geniş Dil Modellerini websitesindeki Models sekmesinden inceleyebilirsiniz.

ollama.com/library adresinde indirilebilecek modeller mevcuttur. Bu yazı içeriğinde Facebook, Instagram ve Whatsapp gibi uygulamaların sahibi Meta şirketinin geliştirdiği llama3.1 modeli ile, Google'ın geliştirdiği gemma2 modeli kullanılacaktır.

Ollama'yı kurduktan sonra Windows işletim sisteminde cmd ile Komut İstemi'ni açarak siyah ekranda ollama run llama3.1 komutu ile llama modelinin 8b sürümünü indirebilirsiniz. 70b ve 405b sürümlerini indirmeniz durumunda bilgisayarınız yetersiz kalabilir. Çünkü çok yüksek miktarda SSD alanı (çok sayıda terabyte), RAM gerektiren bu modeller için en üst seviye CPU ve GPU kapasiteleri şarttır.

Kurulum işlemi bittikten sonra siyah ekranda tekrar ollama run llama3.1 komutu ile geniş dil modeline giriş yapıp komut satırı üzerinde konuşma sağlayabilirsiniz. Bu dil modelleri bir konuşma bittikten sonra başka bir konuşmada önceki konuşmaları hatırlama özelliğine sahip değildir. Hatta aynı konuşma içerisinde belli sayıda girdiden sonra konuşmanın ilk başlarını hatırlamama durumu da söz konusu olabilmektedir. Bunun sebebi lokal kapasitenin azlığı ve düşük kapasiteli versiyonlarının kullanılabilmesidir.

c# ollama yapay zeka llama

Ardından ollama run gemma2 komutu ile Gemma2 modelini de indirip aynı şekilde deneyebilirsiniz.

Komut satırında ollama list komutu ile bilgisayarınızda yüklü olan Large Language Modelleri listeleyebilirsiniz.

c# ollama yapay zeka llama

C# OllamaSharp Kütüphanesi ile WPF Uygulaması

C# dili ile Ollama uygulamasına bağlanıp interaktif bir biçimde konuşabileceğiniz bir WPF uygulaması geliştirmek için bir WPF projesi oluşturulabilir.

c# wpf project create

Proje türünü WPF Application olarak seçtikten sonra, projeye isim verip projeyi oluşturun.

c# ollama large language model project

Proje oluştuktan sonra MainWindow penceresinin tasarım kısmı karşınıza gelecektir. Bu pencereyi sanki bir chat ekranıymış gibi tasarlayabilirsiniz.

c# ollama wpf llama

C# ile Ollama uygulamasına bağlanıp dil modelleri ile konuşma sağlayabilmek için, OllamaSharp kütüphanesini NuGet Package Manager üzerinden projeye kurmanız gerekiyor. Visual Studio 2022'de Tools menüsü üzerinden Nuget Package Manager içerisinde Manage NuGet Packages for Solution... üzerine tıklayın.

c# ollamasharp nuget package

Proje içerisinde OllamaSharp 3.0.7 sürümü kullanılacaktır. Eğer bu yazıyı daha sonraki sürümünü kurduktan sonra okuyor olursanız kütüphane içerisinde değişiklikler mevcut olabilir. Nitekim önceki sürüm ile geliştirilen başka bir projede mevcut olan bazı metotlar, OllamaSharp'ın 3.0.7 sürümünde mevcut değildir.

C# ile Ollama Geniş Dil Modeline Nasıl Bağlanılır?

Aşağıdaki kod örneği Ollama'daki bir modele bağlanıp girdi gönderilebilen basit bir koddur. Proje içerisinde daha tafsilatlı bir şekilde uygulanacaktır.


var uri = new Uri("http://localhost:11434");
// Ollama'ya bağlanmanızı sağlayan client oluşturur
var apiClient = new OllamaApiClient(uri);

// Client oluşturulunca hangi modele bağlanacağınızı string olarak belirtir 
apiClient.SelectedModel = "llama3.1:8b";

// Sisteminizde var olan modelleri listeler
var models = await apiClient.ListLocalModels();


localhost:11434 adresi sisteminizin Ollama için kullanılan kullandığı porttur.

Bir konsol uygulaması için aşağıdaki basit kodu kullandığınızda siyah ekranda llama modeli ile bir konuşma yapabilirsiniz. Konsol uygulaması C# 8.0 sürüm ile yazılmalıdır.


using OllamaSharp;

Console.WriteLine("llama3.1 ile konuşun!");
var uri = new Uri("http://localhost:11434");
// llama3.1 modeline bağlanacak bir client oluşturulur
var apiClient = new OllamaApiClient(uri)
{
    SelectedModel = "llama3.1"
};
// konuşma içeriğini hafızada tutacak bir chat modeli
var chat = new Chat(apiClient);
// sonsuz döngü
while (true)
{
    // ekrandan yazı okunur
    var message = Console.ReadLine();
    if (message == null) { continue; }
    // verilen cevap kelime kelime foreach döngüsü ile ekrana yazılır
    await foreach (var answerToken in chat.Send(message))
        Console.Write(answerToken);
    // yeni satıra geçilir
    Console.Write('\n');
}

Ancak yazının kalanında bir konsol uygulaması değil, oluşturulan WPF uygulaması ile devam edilecektir.

WPF Arayüzü Tasarlama

Öncelikle WPF penceresi üzerinde Grid oluşturup üç ayrı satır belirleyebiliriz.


<Window x:Class="OllamaConversation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OllamaConversation"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="32" />
            <RowDefinition Height="1*" />
            <RowDefinition Height="64" />
        </Grid.RowDefinitions>
    </Grid>
</Window>

İlk satır 32 piksel yüksekliğinde olacaktır. Buraya kullanıcının hangi modeli (llama veya gemma) seçebileceği bir açılır kutu (ComboBox) eklenebilir. Son satır 64 piksel yüksekliğinde olacaktır. Buraya girdinin yazılacağı bir TextBox ve üzerinde Gönder yazan bir Button eklenebilir. Orta satır ise pencerenin toplam yüksekliğinden 96 piksel daha az yükseklik kadar olacaktır. Burada da bir chat ekranı gibi girdiler ve cevaplar gösterilebilir.


<Window x:Class="OllamaConversation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OllamaConversation"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="32" />
            <RowDefinition Height="1*" />
            <RowDefinition Height="64" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
            <TextBlock Text="Bir Yapay Zeka Modeli Seçin: " VerticalAlignment="Center" Margin="10 0 0 0" />
            <ComboBox Width="140" Height="24" x:Name="cbOllamaModels"></ComboBox>
        </StackPanel>
        <ScrollViewer Grid.Row="1">
        </ScrollViewer>
        <Grid Grid.Row="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*" />
                <ColumnDefinition Width="100" />
            </Grid.ColumnDefinitions>
            <TextBox x:Name="tbPrompt" TextWrapping="Wrap" Grid.Column="0" KeyDown="tbPrompt_KeyDown" />
            <Button Content="Gönder" Grid.Column="1" Click="SendMessage_Clicked" />
        </Grid>
    </Grid>
</Window>

MainWindow tasarımı aşağıdaki şekilde görünecektir.

c# ollamasharp wpf design

Veri Modelleri

WPF uygulamasında Binding özelliği ile arayüze doğrudan yansıtılabilecek veri modelleri tasarlanabilir.

MyConversation.cs sınıf dosyası içerisinde MyConversation ve MyConversationMessage nesneleri aşağıdaki gibi yazılabilir.


using OllamaSharp;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace OllamaConversation
{
    internal class MyConversation : INotifyPropertyChanged
    {
        private string _modelName;
        private bool _isWaitingPrompt;

        // Seçili Ollama modelinin adı
        public string ModelName
        {
            get { return _modelName; }
            set { _modelName = value; NotifyPropertyChanged(); }
        }

        // Yapay zekanın cevap verirken false, verdikten sonra true olur
        // Böylece penceredeki tuşların IsEnabled özelliği Binding ile belirlenir
        public bool IsWaitingPrompt
        {
            get { return _isWaitingPrompt; }
            set { _isWaitingPrompt = value; NotifyPropertyChanged(); }
        }

        // Konuşma içerisindeki bütün mesajların listesi
        public ObservableCollection<MyConversationMessage> Messages { get; set; }

        // OllamaSharp kütüphanesindeki Chat nesnesi
        public Chat? Chat { get; set; }

        public MyConversation()
        {
            _modelName = string.Empty;
            _isWaitingPrompt = true;
            Messages = [];
            Chat = null;
        }
        
        public event PropertyChangedEventHandler? PropertyChanged;

        // Bu sınıfın herhangi bir özelliği değiştiğinde WPF arayüzüne otomatik yansıtacaktır
        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    internal class MyConversationMessage : INotifyPropertyChanged
    {
        private string _message;
        private DateTime _dateTime;
        private bool _isSentByUser;

        // Mesajın içeriği string olacak
        public string Message
        {
            get { return _message; }
            set { _message = value; NotifyPropertyChanged(); }
        }
        // Mesajın zamanı DateTime olacak
        public DateTime DateTime
        {
            get { return _dateTime; }
            set { _dateTime = value; NotifyPropertyChanged(); }
        }
        // Mesaj kullanıcı tarafından gönderildiyse true
        // Yapay zekaya ait ise false olacak
        public bool IsSentByUser
        {
            get { return _isSentByUser; }
            set { _isSentByUser = value; NotifyPropertyChanged(); }
        }
        public event PropertyChangedEventHandler? PropertyChanged;

        public MyConversationMessage()
        {
            _message = string.Empty;
            _dateTime = DateTime.Now;
            _isSentByUser = false;
        }

        // Bu sınıfın herhangi bir özelliği değiştiğinde WPF arayüzüne otomatik yansıtacaktır
        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

C# içerisinde normalde bir sınıfa ait değişkeni public string Message { get; set; } şeklinde tanımlayabiliriz. Ancak get veya set metotları içerisinde okuma veya değer atama dışında başka bir işlem yapılacaksa bu metotlar açıkça yazılmalıdır.

Her iki sınıf da INotifyPropertyChanged interface özelliklerini taşımak zorunda olduğu için değişkenler açıkça yazılmalıdır. INotifyPropertyChanged interface özellikleri WPF arayüzünü değişkenleri Binding ile doğrudan yansıtmaya yarar.

Mesela MyConversationMessage sınıfına ait Message özelliği arayüze yansıyacağı için bu interface özelliklerinden PropertyChanged eventına ait bir metot kullanılmalıdır.


    // Message özelliğinin değeri bu değişkende tutulacaktır
    private string _message;
    // Bu event değişen özellik olduğunda tetiklenecektir
    public event PropertyChangedEventHandler? PropertyChanged;
    
    //
    public string Message
    {
        // okuma yapıldığında _message değişkeni döndürülecektir
        get { return _message; }
        // yazma yapıldığında değer _message değişkenine atanacaktır
        // ardından NotifyPropertyChanged("Message") şeklinde çağrılacaktır
        // ancak NotifyPropertyChanged() şeklinde yazılması yeterlidir
        set { _message = value; NotifyPropertyChanged(); }
    }
    
    // propertyName değişkeni hangi özellikte çağılırsa onun adını alır
    // CallerMemberName isimli attribute özelliğin adını doğrudan propertyName argümanına verir
    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        // Message özelliği set edildiğinde PropertyChanged isimli event tetiklenir
        // propertyName argümanı "Message" olarak gönderilir
        // ve arayüz Message özelliğinin değiştiğini anlar.
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

Daha iyi anlayabilmek için kısa bir örnekle izah edebiliriz. Mesela projenin başka bir yerinde şöyle bir kod olsa


    MyConversationMessage ornekMessage = new MyConversationMessage();
    // Bir aşağıdaki satırda önce Message özelliğin set metoduna girilir
    ornekMessage.Message = "Bu bir deneme mesajıdır";
    // _message = value; çalışır. value burada "Bu bir deneme mesajıdır" olur
    // NotifyPropertyChanged(); metodu çalışır
    // NotifyPropertyChanged metodunun propertyName argümanı "Message" olacaktır
    // PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Message"));
    // PropertyChanged eventi tetiklenir
    // Bu tetiklenme sayesinde INotifyPropertyChanged interface i arayüze o değerin değiştiğini belirtir
    // WPF arayüzüne {Binding Message} şeklinde yazılan her değer de güncellenir

Liste halindeki özellikler için ise ObservableCollection kullanılarak doğrudan { get; set; } şeklinde tanımlama yapılabilir. ObservableCollection, List gibidir ve INotifyPropertyChanged interface özelliklerini kendi içerisinde doğrudan barındırır.

WPF arayüzünde MainWindow penceresinin tamamında kullanılabilecek bir veri modeli şöyle oluşturulur.

MyDataViewModel.cs sınıf dosyası içerisinde MyDataViewModel nesnesi aşağıdaki gibi yazılabilir.


using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace OllamaConversation
{
    internal class MyDataViewModel : INotifyPropertyChanged
    {
        private MyConversation? _selectedConversation;

        public ObservableCollection<MyConversation> Conversations { get; set; }

        public MyConversation? SelectedConversation
        {
            get { return _selectedConversation; }
            set { _selectedConversation = value; NotifyPropertyChanged(); }
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        public MyDataViewModel()
        {
            Conversations = [];
            _selectedConversation = null;
        }


        // Bu sınıfın herhangi bir özelliği değiştiğinde WPF arayüzüne otomatik yansıtacaktır
        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Veri Modellerini Arayüze Bağlama

MainWindow.xaml.cs dosyası içerisi aşağıdaki gibi olmalıdır.


using OllamaSharp;
using System.Windows;
using System.Windows.Input;

namespace OllamaConversation
{
    public partial class MainWindow : Window
    {
        private readonly Uri uri = new("http://localhost:11434");
        private OllamaApiClient apiClient; // Ollamaya bağlanacak client
        // MyDataViewModel türünde bir özellik
        internal MyDataViewModel DataViewModel { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            apiClient = new OllamaApiClient(uri);
            DataViewModel = new MyDataViewModel();
            // MyDataViewModel türünde özellik MainWindow'un DataContext özelliğine atanır
            DataContext = DataViewModel;
            // Bu sayede arayüze doğrudan bu nesneden yansıma yapılacaktır
        }

        private async Task GetOllamaModels()
        {
            // Ollama içerisinde yüklü olan geniş dil modelleri burada okunacak
            // ardından üstteki comboboxa gelecektir
        }

        private async void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Pencere açılınca geniş dil modelleri okunacaktır
            await GetOllamaModels();
        }

        private async Task SendPrompt()
        {
            // Mesaj gönderme kodları buraya yazılacaktır
        }

        private async void SendMessage_Clicked(object sender, RoutedEventArgs e)
        {
            // Gönder tuşuna basılınca mesaj gönderme metodu çağrılacaktır
            await SendPrompt();
        }

        private async void tbPrompt_KeyDown(object sender, KeyEventArgs e)
        {
            // Yazı kutusunda entera basıldığında
            if (e.Key == Key.Enter)
            {
                // Eğer shift tuşlarından birine de basılı tutuluyorsa
                if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
                {
                    // Yeni satır eklenecek
                    tbPrompt.Text += "\n";
                }
                else
                {
                    // Eğer shifte basılmıyorsa mesaj gönderilecektir
                    await SendPrompt();
                }
            }
        }
    }
}

Arayüzün son hali aşağıdaki gibi olmalıdır.


<Window x:Class="OllamaConversation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OllamaConversation"
        mc:Ignorable="d" Loaded="Window_Loaded"
        Height="450" Width="800">
    <Window.Title>
        <MultiBinding StringFormat="Ollama Yapay Zeka [Model: {0}]">
            <Binding Path="SelectedConversation.ModelName" />
        </MultiBinding>
    </Window.Title>
    <Window.Resources>
        <DataTemplate x:Key="PromptTemplate" DataType="{x:Type local:MyConversationMessage}">
            <Grid Margin="0 5">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="1*" />
                    <ColumnDefinition Width="2*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="1*" />
                    <RowDefinition Height="1*" />
                </Grid.RowDefinitions>
                <Border Background="#D2E0FB" CornerRadius="10" Grid.Column="1" Grid.Row="0" Margin="4">
                    <StackPanel Margin="5">
                        <TextBlock TextWrapping="Wrap" Text="{Binding Message}" />
                    </StackPanel>
                </Border>
                <TextBlock Text="{Binding DateTime}" Grid.Column="1" Grid.Row="1" FontSize="10" Height="12" TextAlignment="Right" Margin="20 0" />
            </Grid>
        </DataTemplate>
        <DataTemplate x:Key="AnswerTemplate" DataType="{x:Type local:MyConversationMessage}">
            <Grid Margin="0 5">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="2*" />
                    <ColumnDefinition Width="1*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="1*" />
                    <RowDefinition Height="1*" />
                </Grid.RowDefinitions>
                <Border Background="#F2C18D" CornerRadius="10" Grid.Column="0" Grid.Row="0" Margin="4">
                    <StackPanel Margin="5">
                        <TextBlock TextWrapping="Wrap" Text="{Binding Message}" />
                    </StackPanel>
                </Border>
                <TextBlock Text="{Binding DateTime}" Grid.Column="0" Grid.Row="1" FontSize="10" Height="12" TextAlignment="Left" Margin="20 0" />
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="32" />
            <RowDefinition Height="1*" />
            <RowDefinition Height="64" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
            <TextBlock Text="Bir Yapay Zeka Modeli Seçin: " VerticalAlignment="Center" Margin="10 0 0 0" />
            <ComboBox Width="140" Height="24" x:Name="cbOllamaModels" IsEnabled="{Binding Path=SelectedConversation.IsWaitingPrompt}"
                      ItemsSource="{Binding Conversations}"
                      DisplayMemberPath="ModelName"
                      SelectedValuePath="ModelName"
                      SelectedItem="{Binding SelectedConversation}"
                      IsSynchronizedWithCurrentItem="True">
            </ComboBox>
        </StackPanel>
        <ScrollViewer Grid.Row="1">
            <ItemsControl ItemsSource="{Binding Path=SelectedConversation.Messages}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate DataType="{x:Type local:MyConversationMessage}">
                        <ContentControl Content="{Binding}">
                            <ContentControl.Style>
                                <Style TargetType="{x:Type ContentControl}">
                                    <Setter Property="ContentTemplate" Value="{StaticResource PromptTemplate}" />
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsSentByUser}" Value="False">
                                            <Setter Property="ContentTemplate" Value="{StaticResource AnswerTemplate}" />
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </ContentControl.Style>
                        </ContentControl>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
        <Grid Grid.Row="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*" />
                <ColumnDefinition Width="100" />
            </Grid.ColumnDefinitions>
            <TextBox x:Name="tbPrompt" TextWrapping="Wrap" Grid.Column="0" KeyDown="tbPrompt_KeyDown" IsEnabled="{Binding Path=SelectedConversation.IsWaitingPrompt}" />
            <Button Content="Gönder" Grid.Column="1" Click="SendMessage_Clicked" IsEnabled="{Binding Path=SelectedConversation.IsWaitingPrompt}" />
        </Grid>
    </Grid>
</Window>

Eğer Gönder butonunun Click="SendMessage_Clicked" event yakalayıcı metodu hatalı görünürse, SendMessage_Clicked üzerine tıklayıp imleci getirin ve F12 tuşuna basın. Aynısını Window_Loaded ve diğer event metotları için de yapabilirsiniz.

MainWindow başlığı MultiBinding ile formatlı bir string ile doldurulacaktır.

MainWindow penceresinin DataContext özelliğine atanan DataViewModel içerisindeki veriler bu pencereyi dolduracaktır.

DataViewModel içerisinde SelectedConversation.ModelName özelliği StringFormat="Ollama Yapay Zeka [Model: {0}]" ile başlığa bağlanmıştır.


    <Window.Title>
        <MultiBinding StringFormat="Ollama Yapay Zeka [Model: {0}]">
            <Binding Path="SelectedConversation.ModelName" />
        </MultiBinding>
    </Window.Title>

Window.Resources içerisinde iki adet DataTemplate bulunmaktadır. PromptTemplate girilen mesajın tasarımıdır. AnswerTemplate verilecek cevabın tasarımıdır.

ItemsControl içerisinde listelenecek olan girdiler ve cevapların hangi DataTemplate ile belirleneceği, ContentControl ile sağlanır.


<ContentControl Content="{Binding}">
    <ContentControl.Style>
        <Style TargetType="{x:Type ContentControl}">
            <Setter Property="ContentTemplate" Value="{StaticResource PromptTemplate}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsSentByUser}" Value="False">
                    <Setter Property="ContentTemplate" Value="{StaticResource AnswerTemplate}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ContentControl.Style>
</ContentControl>

MyConversationMessage sınıfına tekrar bakın. IsSentByUser özelliği true ise PromptTemplate, false ise AnswerTemplate kullanılır.

Mesaj gönderme metodu aşağıdaki gibi olacaktır.


private async Task SendPrompt()
{
    if (DataViewModel.SelectedConversation == null) { return; }
    if (DataViewModel.SelectedConversation.Chat == null) { return; }
    if (string.IsNullOrWhiteSpace(tbPrompt.Text)) { return; }
    DataViewModel.SelectedConversation.IsWaitingPrompt = false;
    apiClient.SelectedModel = DataViewModel.SelectedConversation.ModelName;
    string prompt = tbPrompt.Text;
    var message = new MyConversationMessage()
    {
        Message = prompt,
        DateTime = DateTime.Now,
        IsSentByUser = true
    };
    DataViewModel.SelectedConversation.Messages.Add(message);
    tbPrompt.Text = "";
    var request = new OllamaSharp.Models.GenerateRequest()
    {
        Prompt = tbPrompt.Text,
        Context = null
    };
    var answer = new MyConversationMessage()
    {
        DateTime = DateTime.Now,
        IsSentByUser = false
    };
    DataViewModel.SelectedConversation.Messages.Add(answer);
    await foreach (var answerToken in DataViewModel.SelectedConversation.Chat.Send(prompt))
    {
        await Task.Delay(20);
        answer.Message += answerToken; answer.DateTime = DateTime.Now;
    }
    DataViewModel.SelectedConversation.IsWaitingPrompt = true;
}

Eğer seçili bir SelectedConversation değeri yoksa veya gönderilen metin boşsa metot sonlanacaktır.

Her gönderilen mesajda IsSentByUser özelliği true, her alınan cevapta ise false olacaktır.

Cevapların her kelimesi 20 ms arayla yazılacaktır. Uygulamanız kasarsa bu değeri arttırabilirsiniz.

GetOllamaModels metodu içerisinde yüklenecek olan geniş dil modelleri ComboBox içerisinde gösterilecektir.


private async Task GetOllamaModels()
{
    var models = await apiClient.ListLocalModels();
    DataViewModel.Conversations.Clear();
    foreach (var model in models)
    {
        DataViewModel.Conversations.Add(new MyConversation()
        {
            ModelName = model.Name,
            Chat = new Chat(apiClient) { Model = model.Name }
        });
    }
}

Herbir model için sadece bir conversation yani konuşma gibi düşünülürse, DataViewModel.Conversations içerisine modeller eklenecektir. Bu ekleme yapılır yapılmaz ComboBox içerisi otomatik dolar.


    <ComboBox Width="140" Height="24" x:Name="cbOllamaModels" IsEnabled="{Binding Path=SelectedConversation.IsWaitingPrompt}"
              ItemsSource="{Binding Conversations}"
              DisplayMemberPath="ModelName"
              SelectedValuePath="ModelName"
              SelectedItem="{Binding SelectedConversation}"
              IsSynchronizedWithCurrentItem="True">
    </ComboBox>

DataViewModel içerisindeki Conversations özelliği {Binding Conversations} ile ComboBox içerisine bağlanmıştır. İçerisindeki her item buradan beslenecektir. DisplayMemberPath ile gösterilecek string değerin hangi özellik olduğu yazılır.

Yine DataViewModel içerisindeki SelectedConversation özelliği seçili olan MyConversation nesnesini belirtir. Yani bu bağlama olayı tek taraflı değil iki taraflıdır. Backend tarafında olan veriler arayüze yansır, arayüzde biri seçilince seçili olan da backend tarafına yansır.

ItemsSource="{Binding Conversations}" ile DataViewModel.Conversations içerisinde liste halindeki veri ComboBox içini doldurur. SelectedItem="{Binding SelectedConversation}" sayesinde herhangi biri seçildiğinde de, DataViewModel.SelectedConversation belirlenmiş olur.

MainWindow.xaml.cs içerisindeki C# kodları en son haliyle şu şekilde olmalıdır.


using OllamaSharp;
using System.Windows;
using System.Windows.Input;

namespace OllamaConversation
{
    public partial class MainWindow : Window
    {
        private readonly Uri uri = new("http://localhost:11434");
        private OllamaApiClient apiClient;
        internal MyDataViewModel DataViewModel { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            apiClient = new OllamaApiClient(uri);
            DataViewModel = new MyDataViewModel();
            DataContext = DataViewModel;
        }

        private async Task GetOllamaModels()
        {
            var models = await apiClient.ListLocalModels();
            DataViewModel.Conversations.Clear();
            foreach (var model in models)
            {
                DataViewModel.Conversations.Add(new MyConversation()
                {
                    ModelName = model.Name,
                    Chat = new Chat(apiClient) { Model = model.Name }
                });
            }
        }

        private async void Window_Loaded(object sender, RoutedEventArgs e)
        {
            await GetOllamaModels();
        }

        private async Task SendPrompt()
        {
            if (DataViewModel.SelectedConversation == null) { return; }
            if (DataViewModel.SelectedConversation.Chat == null) { return; }
            if (string.IsNullOrWhiteSpace(tbPrompt.Text)) { return; }
            DataViewModel.SelectedConversation.IsWaitingPrompt = false;
            apiClient.SelectedModel = DataViewModel.SelectedConversation.ModelName;
            string prompt = tbPrompt.Text;
            var message = new MyConversationMessage()
            {
                Message = prompt,
                DateTime = DateTime.Now,
                IsSentByUser = true
            };
            DataViewModel.SelectedConversation.Messages.Add(message);
            tbPrompt.Text = "";
            var request = new OllamaSharp.Models.GenerateRequest()
            {
                Prompt = tbPrompt.Text,
                Context = null
            };
            var answer = new MyConversationMessage()
            {
                DateTime = DateTime.Now,
                IsSentByUser = false
            };
            DataViewModel.SelectedConversation.Messages.Add(answer);
            await foreach (var answerToken in DataViewModel.SelectedConversation.Chat.Send(prompt))
            {
                await Task.Delay(20);
                answer.Message += answerToken; answer.DateTime = DateTime.Now;
            }
            DataViewModel.SelectedConversation.IsWaitingPrompt = true;
        }

        private async void SendMessage_Clicked(object sender, RoutedEventArgs e)
        {
            await SendPrompt();
        }

        private async void tbPrompt_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
                {
                    tbPrompt.Text += "\n";
                }
                else
                {
                    await SendPrompt();
                }
            }
        }
    }
}

Programı çalıştırdığınızda istediğiniz yapay zeka dil modelini seçip konuşmaya başlayabilirsiniz.

c# ollamasharp wpf application c# ollamasharp wpf application

Projeyi GitHub üzerinden incelemek veya kendi bilgisayarınıza indirmek için buraya tıklayabilirsiniz.

Paylaşın
Etiket Bulutu