C# Ollama Yapay Zeka Modeli ile Konuşma Uygulaması
- 10
- (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.
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# 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.
Proje türünü WPF Application olarak seçtikten sonra, projeye isim verip projeyi oluşturun.
Proje oluştuktan sonra MainWindow
penceresinin tasarım kısmı karşınıza gelecektir. Bu pencereyi sanki bir chat ekranıymış gibi tasarlayabilirsiniz.
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.
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.
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.
Projeyi GitHub üzerinden incelemek veya kendi bilgisayarınıza indirmek için buraya tıklayabilirsiniz.