update
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Root Type="DevExpress.CodeRush.Foundation.CodePlaces.Options.FavoritesListContainer">
|
||||
<Options Language="Neutral">
|
||||
<Groups />
|
||||
</Options>
|
||||
</Root>
|
||||
@@ -37,10 +37,18 @@
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="API-Key:" VerticalAlignment="Center" Margin="10,0,0,0"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="3" Grid.ColumnSpan="2" x:Name="TxtApiKey" Margin="0,2" VerticalContentAlignment="Center" Text="mu9ZPuUXJ4aVGLU1WL2RfPCeSMRmJREwyjVJBP7bHfSz8quZRqN7e7UwnwCDi8GZ"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="Modell:" VerticalAlignment="Center"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="1" x:Name="CmbModel" Margin="0,2" IsEditable="True" VerticalContentAlignment="Center" Text="gemma3_12b_max_context:latest"/>
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="Modelle:" VerticalAlignment="Top"/>
|
||||
<ListBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" x:Name="LstModels" Margin="0,2" MaxHeight="100"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ToolTip="Für jedes angehakte Modell wird eine eigene Analyse durchgeführt (eigener Ergebnis-Tab).">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<Button Grid.Row="1" Grid.Column="4" x:Name="BtnLoadModels" Content="Modelle laden" Width="120" Margin="10,2,0,2" Click="BtnLoadModels_Click"/>
|
||||
<Button Grid.Row="1" Grid.Column="4" x:Name="BtnLoadModels" Content="Modelle laden" Width="120" Margin="10,2,0,2" VerticalAlignment="Top" Click="BtnLoadModels_Click"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
@@ -74,7 +82,7 @@
|
||||
ToolTip="Optional: hält die Antwort kurz. Zählt mit wenigen Tokens in prompt_eval_count mit."/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="2" Content="CustomerId:" VerticalAlignment="Center" Margin="10,0,0,0"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="3" x:Name="TxtCustomerId" Margin="0,2" VerticalContentAlignment="Center"
|
||||
<TextBox Grid.Row="1" Grid.Column="3" x:Name="TxtCustomerId" Margin="0,2" VerticalContentAlignment="Center" Text="token-analyzer"
|
||||
ToolTip="Optional: wird als 'customerid' mitgesendet."/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
@@ -94,7 +102,15 @@
|
||||
</StackPanel>
|
||||
|
||||
<GroupBox Grid.Row="4" Header="Ergebnis" Padding="5">
|
||||
<DataGrid x:Name="GridResults" AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False"
|
||||
<TabControl x:Name="TabResults">
|
||||
<TabControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ModelName}"/>
|
||||
</DataTemplate>
|
||||
</TabControl.ItemTemplate>
|
||||
<TabControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False"
|
||||
HeadersVisibility="Column" GridLinesVisibility="All">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Zielgröße" Binding="{Binding TargetChars, StringFormat=N0}" Width="Auto"/>
|
||||
@@ -102,11 +118,15 @@
|
||||
<DataGridTextColumn Header="Zeichen / 3" Binding="{Binding EstDiv3, StringFormat=N0}" Width="Auto"/>
|
||||
<DataGridTextColumn Header="Zeichen / 3,5" Binding="{Binding EstDiv3_5, StringFormat=N0}" Width="Auto"/>
|
||||
<DataGridTextColumn Header="cl100k" Binding="{Binding Cl100k, StringFormat=N0}" Width="Auto"/>
|
||||
<DataGridTextColumn Header="cl100k - 10%" Binding="{Binding Cl100kMinus10, StringFormat=N0}" Width="Auto"/>
|
||||
<DataGridTextColumn Header="prompt_eval_count" Binding="{Binding PromptEvalCount, StringFormat=N0}" Width="Auto"/>
|
||||
<DataGridTextColumn Header="Dauer (ms)" Binding="{Binding Duration, StringFormat=N0}" Width="Auto"/>
|
||||
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="*"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DataTemplate>
|
||||
</TabControl.ContentTemplate>
|
||||
</TabControl>
|
||||
</GroupBox>
|
||||
|
||||
<StatusBar Grid.Row="5" Margin="0,5,0,0">
|
||||
|
||||
@@ -3,12 +3,15 @@ using SharpToken;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace AiTokenAnalyzer
|
||||
{
|
||||
@@ -17,7 +20,8 @@ namespace AiTokenAnalyzer
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public ObservableCollection<TokenAnalyzeRow> Rows { get; } = new ObservableCollection<TokenAnalyzeRow>();
|
||||
public ObservableCollection<ModelSelectionItem> Models { get; } = new ObservableCollection<ModelSelectionItem>();
|
||||
public ObservableCollection<ModelAnalyzeResult> ModelResults { get; } = new ObservableCollection<ModelAnalyzeResult>();
|
||||
|
||||
private CancellationTokenSource _Cts;
|
||||
|
||||
@@ -25,7 +29,95 @@ namespace AiTokenAnalyzer
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
GridResults.ItemsSource = Rows;
|
||||
LstModels.ItemsSource = Models;
|
||||
TabResults.ItemsSource = ModelResults;
|
||||
|
||||
loadSettings();
|
||||
|
||||
if (Models.Count == 0)
|
||||
Models.Add(new ModelSelectionItem { Name = "gemma3_12b_max_context:latest", IsSelected = true });
|
||||
}
|
||||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
saveSettings();
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
private static string getSettingsPath()
|
||||
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AiTokenAnalyzer", "settings.xml");
|
||||
|
||||
private void loadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = getSettingsPath();
|
||||
|
||||
if (!File.Exists(path))
|
||||
return;
|
||||
|
||||
UserSettings settings;
|
||||
|
||||
using (var stream = File.OpenRead(path))
|
||||
settings = (UserSettings)new XmlSerializer(typeof(UserSettings)).Deserialize(stream);
|
||||
|
||||
if (settings == null)
|
||||
return;
|
||||
|
||||
TxtUrl.Text = settings.Url ?? TxtUrl.Text;
|
||||
TxtApiKey.Text = settings.ApiKey ?? TxtApiKey.Text;
|
||||
TxtSizes.Text = settings.Sizes ?? TxtSizes.Text;
|
||||
TxtNumCtx.Text = settings.NumCtx ?? TxtNumCtx.Text;
|
||||
ChkRepeat.IsChecked = settings.Repeat;
|
||||
TxtSystemPrompt.Text = settings.SystemPrompt ?? TxtSystemPrompt.Text;
|
||||
TxtCustomerId.Text = settings.CustomerId ?? TxtCustomerId.Text;
|
||||
TxtInput.Text = settings.InputText ?? TxtInput.Text;
|
||||
|
||||
if (settings.Models is object && settings.Models.Count > 0)
|
||||
{
|
||||
var selected = new HashSet<string>(settings.SelectedModels ?? new List<string>());
|
||||
|
||||
Models.Clear();
|
||||
|
||||
foreach (var name in settings.Models)
|
||||
Models.Add(new ModelSelectionItem { Name = name, IsSelected = selected.Contains(name) });
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Defekte Settings-Datei ignorieren, Defaults verwenden
|
||||
}
|
||||
}
|
||||
|
||||
private void saveSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = new UserSettings
|
||||
{
|
||||
Url = TxtUrl.Text,
|
||||
ApiKey = TxtApiKey.Text,
|
||||
Sizes = TxtSizes.Text,
|
||||
NumCtx = TxtNumCtx.Text,
|
||||
Repeat = ChkRepeat.IsChecked == true,
|
||||
SystemPrompt = TxtSystemPrompt.Text,
|
||||
CustomerId = TxtCustomerId.Text,
|
||||
InputText = TxtInput.Text,
|
||||
Models = Models.Select(m => m.Name).ToList(),
|
||||
SelectedModels = Models.Where(m => m.IsSelected).Select(m => m.Name).ToList()
|
||||
};
|
||||
|
||||
var path = getSettingsPath();
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
using (var stream = File.Create(path))
|
||||
new XmlSerializer(typeof(UserSettings)).Serialize(stream, settings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Speichern der Settings darf das Schließen nicht verhindern
|
||||
}
|
||||
}
|
||||
|
||||
private OllamaApiClient createClient()
|
||||
@@ -55,10 +147,14 @@ namespace AiTokenAnalyzer
|
||||
}
|
||||
|
||||
var model_names = response.Data.Select(m => m.ModelName).OrderBy(x => x).ToList();
|
||||
var current = CmbModel.Text;
|
||||
|
||||
CmbModel.ItemsSource = model_names;
|
||||
CmbModel.Text = current;
|
||||
// Bisherige Auswahl beibehalten
|
||||
var selected = new HashSet<string>(Models.Where(m => m.IsSelected).Select(m => m.Name));
|
||||
|
||||
Models.Clear();
|
||||
|
||||
foreach (var name in model_names)
|
||||
Models.Add(new ModelSelectionItem { Name = name, IsSelected = selected.Contains(name) });
|
||||
|
||||
TxtStatus.Text = $"{model_names.Count} Modelle geladen.";
|
||||
}
|
||||
@@ -82,11 +178,11 @@ namespace AiTokenAnalyzer
|
||||
return;
|
||||
}
|
||||
|
||||
var model = CmbModel.Text?.Trim();
|
||||
var models = Models.Where(m => m.IsSelected).Select(m => m.Name).ToList();
|
||||
|
||||
if (string.IsNullOrEmpty(model))
|
||||
if (models.Count == 0)
|
||||
{
|
||||
MessageBox.Show("Bitte ein Modell angeben.", Title, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
MessageBox.Show("Bitte mindestens ein Modell auswählen.", Title, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -132,7 +228,7 @@ namespace AiTokenAnalyzer
|
||||
|
||||
try
|
||||
{
|
||||
await runAnalysisAsync(text, sizes, model, repeat, system_prompt, customer_id, num_ctx, _Cts.Token);
|
||||
await runAnalysisAsync(text, sizes, models, repeat, system_prompt, customer_id, num_ctx, _Cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -149,43 +245,57 @@ namespace AiTokenAnalyzer
|
||||
TxtStatus.Text = "Abbruch angefordert (laufender Request wird noch abgewartet)...";
|
||||
}
|
||||
|
||||
private async Task runAnalysisAsync(string text, List<int> sizes, string model, bool repeat, string system_prompt, string customer_id, int? num_ctx, CancellationToken token)
|
||||
private async Task runAnalysisAsync(string text, List<int> sizes, List<string> models, bool repeat, string system_prompt, string customer_id, int? num_ctx, CancellationToken token)
|
||||
{
|
||||
Rows.Clear();
|
||||
ModelResults.Clear();
|
||||
|
||||
// Zeilen + Text-Chunks vorbereiten
|
||||
var chunks = new List<KeyValuePair<TokenAnalyzeRow, string>>();
|
||||
// Text-Chunks vorbereiten (für alle Modelle identisch)
|
||||
var chunks = new List<KeyValuePair<int, string>>();
|
||||
|
||||
foreach (var size in sizes.Distinct().OrderBy(x => x))
|
||||
{
|
||||
var chunk = buildChunk(text, size, repeat);
|
||||
chunks.Add(new KeyValuePair<int, string>(size, buildChunk(text, size, repeat)));
|
||||
|
||||
var row = new TokenAnalyzeRow
|
||||
{
|
||||
TargetChars = size,
|
||||
ActualChars = chunk.Length,
|
||||
Status = "Wartet"
|
||||
};
|
||||
|
||||
Rows.Add(row);
|
||||
chunks.Add(new KeyValuePair<TokenAnalyzeRow, string>(row, chunk));
|
||||
}
|
||||
|
||||
// Lokale cl100k-Abschätzung
|
||||
// Lokale cl100k-Abschätzung (einmalig, modellunabhängig)
|
||||
TxtStatus.Text = "Berechne cl100k-Abschätzung...";
|
||||
|
||||
var encoding = await Task.Run(() => GptEncoding.GetEncoding("cl100k_base"));
|
||||
var cl100k_counts = new List<int>();
|
||||
|
||||
foreach (var pair in chunks)
|
||||
pair.Key.Cl100k = await Task.Run(() => encoding.Encode(pair.Value).Count);
|
||||
cl100k_counts.Add(await Task.Run(() => encoding.Encode(pair.Value).Count));
|
||||
|
||||
// LLM-Requests (sequenziell)
|
||||
// Pro Modell einen Tab mit allen Größen anlegen
|
||||
foreach (var model in models)
|
||||
{
|
||||
var result = new ModelAnalyzeResult { ModelName = model };
|
||||
|
||||
for (var i = 0; i < chunks.Count; i++)
|
||||
{
|
||||
result.Rows.Add(new TokenAnalyzeRow
|
||||
{
|
||||
TargetChars = chunks[i].Key,
|
||||
ActualChars = chunks[i].Value.Length,
|
||||
Cl100k = cl100k_counts[i],
|
||||
Status = "Wartet"
|
||||
});
|
||||
}
|
||||
|
||||
ModelResults.Add(result);
|
||||
}
|
||||
|
||||
// LLM-Requests (sequenziell, Modell für Modell)
|
||||
var client = createClient();
|
||||
var total = models.Count * chunks.Count;
|
||||
var index = 0;
|
||||
|
||||
foreach (var pair in chunks)
|
||||
foreach (var result in ModelResults)
|
||||
{
|
||||
var row = pair.Key;
|
||||
if (!token.IsCancellationRequested)
|
||||
TabResults.SelectedItem = result;
|
||||
|
||||
for (var i = 0; i < chunks.Count; i++)
|
||||
{
|
||||
var row = result.Rows[i];
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
@@ -194,27 +304,27 @@ namespace AiTokenAnalyzer
|
||||
}
|
||||
|
||||
index++;
|
||||
TxtStatus.Text = $"Sende Request {index}/{chunks.Count} ({row.ActualChars:N0} Zeichen) an '{model}'...";
|
||||
TxtStatus.Text = $"Request {index}/{total}: '{result.ModelName}', {row.ActualChars:N0} Zeichen...";
|
||||
row.Status = "Läuft...";
|
||||
|
||||
var payload = buildPayload(model, system_prompt, pair.Value, customer_id, num_ctx);
|
||||
var payload = buildPayload(result.ModelName, system_prompt, chunks[i].Value, customer_id, num_ctx);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await Task.Run(() =>
|
||||
var request_result = await Task.Run(() =>
|
||||
{
|
||||
var response = client.SendAiMessageRequest(payload, out string message, out string response_model, out int? duration, out long? prompt_eval_count, out long? eval_count);
|
||||
|
||||
return new { response, duration, prompt_eval_count };
|
||||
});
|
||||
|
||||
row.PromptEvalCount = result.prompt_eval_count;
|
||||
row.Duration = result.duration;
|
||||
row.PromptEvalCount = request_result.prompt_eval_count;
|
||||
row.Duration = request_result.duration;
|
||||
|
||||
if (result.prompt_eval_count is long)
|
||||
if (request_result.prompt_eval_count is long)
|
||||
row.Status = "OK";
|
||||
else if (result.response is object && !result.response.Success)
|
||||
row.Status = "Fehler: " + result.response.ToErrorString();
|
||||
else if (request_result.response is object && !request_result.response.Success)
|
||||
row.Status = "Fehler: " + request_result.response.ToErrorString();
|
||||
else
|
||||
row.Status = "Fehler: keine prompt_eval_count in der Antwort";
|
||||
}
|
||||
@@ -223,6 +333,7 @@ namespace AiTokenAnalyzer
|
||||
row.Status = "Fehler: " + ex.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TxtStatus.Text = token.IsCancellationRequested ? "Analyse abgebrochen." : "Analyse abgeschlossen.";
|
||||
}
|
||||
|
||||
14
Tools/AiTokenAnalyzer/AiTokenAnalyzer/ModelAnalyzeResult.cs
Normal file
14
Tools/AiTokenAnalyzer/AiTokenAnalyzer/ModelAnalyzeResult.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace AiTokenAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyse-Ergebnis eines Modells (ein Tab in der Ergebnisansicht).
|
||||
/// </summary>
|
||||
public class ModelAnalyzeResult
|
||||
{
|
||||
public string ModelName { get; set; }
|
||||
|
||||
public ObservableCollection<TokenAnalyzeRow> Rows { get; } = new ObservableCollection<TokenAnalyzeRow>();
|
||||
}
|
||||
}
|
||||
22
Tools/AiTokenAnalyzer/AiTokenAnalyzer/ModelSelectionItem.cs
Normal file
22
Tools/AiTokenAnalyzer/AiTokenAnalyzer/ModelSelectionItem.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace AiTokenAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Ein auswählbares Modell in der Modell-Liste.
|
||||
/// </summary>
|
||||
public class ModelSelectionItem : INotifyPropertyChanged
|
||||
{
|
||||
private bool _IsSelected;
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _IsSelected;
|
||||
set { _IsSelected = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected))); }
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,12 @@ namespace AiTokenAnalyzer
|
||||
public int? Cl100k
|
||||
{
|
||||
get => _Cl100k;
|
||||
set { _Cl100k = value; onPropertyChanged(nameof(Cl100k)); }
|
||||
set { _Cl100k = value; onPropertyChanged(nameof(Cl100k)); onPropertyChanged(nameof(Cl100kMinus10)); }
|
||||
}
|
||||
|
||||
public int? Cl100kMinus10
|
||||
=> _Cl100k is int c ? (int?)(int)Math.Round(c * 0.9) : null;
|
||||
|
||||
public long? PromptEvalCount
|
||||
{
|
||||
get => _PromptEvalCount;
|
||||
|
||||
22
Tools/AiTokenAnalyzer/AiTokenAnalyzer/UserSettings.cs
Normal file
22
Tools/AiTokenAnalyzer/AiTokenAnalyzer/UserSettings.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AiTokenAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Beim Schließen gespeicherte / beim Öffnen geladene Eingabewerte.
|
||||
/// </summary>
|
||||
public class UserSettings
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string ApiKey { get; set; }
|
||||
public string Sizes { get; set; }
|
||||
public string NumCtx { get; set; }
|
||||
public bool Repeat { get; set; } = true;
|
||||
public string SystemPrompt { get; set; }
|
||||
public string CustomerId { get; set; }
|
||||
public string InputText { get; set; }
|
||||
|
||||
public List<string> Models { get; set; } = new List<string>();
|
||||
public List<string> SelectedModels { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user