AiTokenAnalyzer: Token-Analyse (Zeichen/3, Zeichen/3,5, cl100k, prompt_eval_count)
- OllamaApiClient: Überladung mit prompt_eval_count/eval_count - Tool auf net48 umgestellt, Referenzen auf AICore/ServerUtils/Shared - UI: Text-Eingabe, Zeichenanzahlen (50k, 100k, ...), Ergebnistabelle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -72,10 +72,17 @@ namespace AICore.Facade.LLM
|
||||
}
|
||||
|
||||
public override ApiResponse<string> SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, FileLogger logger = null)
|
||||
{
|
||||
return SendAiMessageRequest((object)payload, out message, out model, out duration, out long? _, out long? _, logger);
|
||||
}
|
||||
|
||||
public ApiResponse<string> SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, out long? prompt_eval_count, out long? eval_count, FileLogger logger = null)
|
||||
{
|
||||
message = null;
|
||||
duration = null;
|
||||
model = null;
|
||||
prompt_eval_count = null;
|
||||
eval_count = null;
|
||||
|
||||
_PostClientFacade.JsonObject = payload;
|
||||
|
||||
@@ -123,6 +130,9 @@ namespace AICore.Facade.LLM
|
||||
|
||||
var json = response.GetResponseData();
|
||||
|
||||
prompt_eval_count = json.prompt_eval_count;
|
||||
eval_count = json.eval_count;
|
||||
|
||||
if (json.message is object && !string.IsNullOrEmpty(json.message.content))
|
||||
{
|
||||
var total_ns = json.total_duration;
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpToken" Version="2.0.3" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
|
||||
<PackageReference Include="System.Buffers" Version="4.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Server\Components\AICore\AICore.csproj" />
|
||||
<ProjectReference Include="..\..\..\Server\Components\ServerUtils\ServerUtils.csproj" />
|
||||
<ProjectReference Include="..\..\..\Shared\Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,12 +1,116 @@
|
||||
<Window x:Class="AiTokenAnalyzer.MainWindow"
|
||||
<Window x:Class="AiTokenAnalyzer.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:AiTokenAnalyzer"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="450" Width="800">
|
||||
<Grid>
|
||||
xml:lang="de-DE"
|
||||
Title="Ai Token Analyzer" Height="850" Width="1200" WindowStartupLocation="CenterScreen">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="2*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="3*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Grid.Row="0" Header="Verbindung" Padding="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="3*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="URL:" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" x:Name="TxtUrl" Margin="0,2" VerticalContentAlignment="Center" Text="https://ai2.ownsoft.de"/>
|
||||
|
||||
<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"/>
|
||||
|
||||
<Button Grid.Row="1" Grid.Column="4" x:Name="BtnLoadModels" Content="Modelle laden" Width="120" Margin="10,2,0,2" Click="BtnLoadModels_Click"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Grid.Row="1" Header="Analyse-Einstellungen" Padding="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="Zeichenanzahlen:" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" x:Name="TxtSizes" Margin="0,2" VerticalContentAlignment="Center" Text="50k, 100k, 150k"
|
||||
ToolTip="Kommagetrennte Liste, z.B. '50k, 100k, 150k' oder '50000, 100000'"/>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="num_ctx:" VerticalAlignment="Center" Margin="10,0,0,0"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="3" x:Name="TxtNumCtx" Margin="0,2" VerticalContentAlignment="Center"
|
||||
ToolTip="Optional: wird als options.num_ctx mitgesendet. Ohne ausreichend großes Kontextfenster schneidet Ollama den Prompt ab und prompt_eval_count ist gedeckelt."/>
|
||||
|
||||
<CheckBox Grid.Row="0" Grid.Column="4" x:Name="ChkRepeat" Content="Text wiederholen bis Zielgröße" IsChecked="True" VerticalAlignment="Center" Margin="10,0,0,0"
|
||||
ToolTip="Ist der Eingabetext kürzer als die Zielgröße, wird er so oft aneinandergehängt, bis die Zielgröße erreicht ist."/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="System-Prompt:" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" x:Name="TxtSystemPrompt" Margin="0,2" VerticalContentAlignment="Center" Text="Antworte ausschließlich mit 'OK'."
|
||||
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"
|
||||
ToolTip="Optional: wird als 'customerid' mitgesendet."/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Grid.Row="2" Header="Eingabetext" Padding="5">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Bottom" x:Name="TxtInputInfo" Margin="0,3,0,0" Text="Zeichen: 0"/>
|
||||
<TextBox x:Name="TxtInput" AcceptsReturn="True" TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
|
||||
TextChanged="TxtInput_TextChanged"/>
|
||||
</DockPanel>
|
||||
</GroupBox>
|
||||
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,5">
|
||||
<Button x:Name="BtnAnalyze" Content="Analysieren" Width="120" Height="26" Click="BtnAnalyze_Click"/>
|
||||
<Button x:Name="BtnCancel" Content="Abbrechen" Width="120" Height="26" Margin="10,0,0,0" IsEnabled="False" Click="BtnCancel_Click"/>
|
||||
</StackPanel>
|
||||
|
||||
<GroupBox Grid.Row="4" Header="Ergebnis" Padding="5">
|
||||
<DataGrid x:Name="GridResults" AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False"
|
||||
HeadersVisibility="Column" GridLinesVisibility="All">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Zielgröße" Binding="{Binding TargetChars, StringFormat=N0}" Width="Auto"/>
|
||||
<DataGridTextColumn Header="Zeichen (gesendet)" Binding="{Binding ActualChars, StringFormat=N0}" Width="Auto"/>
|
||||
<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="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>
|
||||
</GroupBox>
|
||||
|
||||
<StatusBar Grid.Row="5" Margin="0,5,0,0">
|
||||
<TextBlock x:Name="TxtStatus" Text="Bereit."/>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -1,24 +1,307 @@
|
||||
using System.Text;
|
||||
using AICore.Facade.LLM;
|
||||
using SharpToken;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace AiTokenAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public ObservableCollection<TokenAnalyzeRow> Rows { get; } = new ObservableCollection<TokenAnalyzeRow>();
|
||||
|
||||
private CancellationTokenSource _Cts;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
GridResults.ItemsSource = Rows;
|
||||
}
|
||||
|
||||
private OllamaApiClient createClient()
|
||||
=> new OllamaApiClient(TxtUrl.Text.Trim(), TxtApiKey.Text.Trim());
|
||||
|
||||
private void TxtInput_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (TxtInputInfo is object)
|
||||
TxtInputInfo.Text = $"Zeichen: {TxtInput.Text.Length:N0}";
|
||||
}
|
||||
|
||||
private async void BtnLoadModels_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
BtnLoadModels.IsEnabled = false;
|
||||
TxtStatus.Text = "Lade Modelle...";
|
||||
|
||||
try
|
||||
{
|
||||
var client = createClient();
|
||||
|
||||
var response = await Task.Run(() => client.GetAiModelle());
|
||||
|
||||
if (response?.Success != true)
|
||||
{
|
||||
TxtStatus.Text = "Fehler beim Laden der Modelle: " + response?.ToErrorString();
|
||||
return;
|
||||
}
|
||||
|
||||
var model_names = response.Data.Select(m => m.ModelName).OrderBy(x => x).ToList();
|
||||
var current = CmbModel.Text;
|
||||
|
||||
CmbModel.ItemsSource = model_names;
|
||||
CmbModel.Text = current;
|
||||
|
||||
TxtStatus.Text = $"{model_names.Count} Modelle geladen.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TxtStatus.Text = "Fehler beim Laden der Modelle: " + ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
BtnLoadModels.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async void BtnAnalyze_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var text = TxtInput.Text;
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
MessageBox.Show("Bitte einen Eingabetext angeben.", Title, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var model = CmbModel.Text?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(model))
|
||||
{
|
||||
MessageBox.Show("Bitte ein Modell angeben.", Title, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
List<int> sizes;
|
||||
|
||||
try
|
||||
{
|
||||
sizes = parseSizes(TxtSizes.Text);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message, Title, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sizes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("Bitte mindestens eine Zeichenanzahl angeben (z.B. '50k, 100k, 150k').", Title, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
int? num_ctx = null;
|
||||
var num_ctx_text = TxtNumCtx.Text?.Trim();
|
||||
|
||||
if (!string.IsNullOrEmpty(num_ctx_text))
|
||||
{
|
||||
if (!int.TryParse(num_ctx_text, out var tmp) || tmp <= 0)
|
||||
{
|
||||
MessageBox.Show("Ungültiger Wert für num_ctx.", Title, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
num_ctx = tmp;
|
||||
}
|
||||
|
||||
var repeat = ChkRepeat.IsChecked == true;
|
||||
var system_prompt = TxtSystemPrompt.Text?.Trim();
|
||||
var customer_id = TxtCustomerId.Text?.Trim();
|
||||
|
||||
BtnAnalyze.IsEnabled = false;
|
||||
BtnCancel.IsEnabled = true;
|
||||
_Cts = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
await runAnalysisAsync(text, sizes, model, repeat, system_prompt, customer_id, num_ctx, _Cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_Cts.Dispose();
|
||||
_Cts = null;
|
||||
BtnAnalyze.IsEnabled = true;
|
||||
BtnCancel.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_Cts?.Cancel();
|
||||
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)
|
||||
{
|
||||
Rows.Clear();
|
||||
|
||||
// Zeilen + Text-Chunks vorbereiten
|
||||
var chunks = new List<KeyValuePair<TokenAnalyzeRow, string>>();
|
||||
|
||||
foreach (var size in sizes.Distinct().OrderBy(x => x))
|
||||
{
|
||||
var chunk = 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
|
||||
TxtStatus.Text = "Berechne cl100k-Abschätzung...";
|
||||
|
||||
var encoding = await Task.Run(() => GptEncoding.GetEncoding("cl100k_base"));
|
||||
|
||||
foreach (var pair in chunks)
|
||||
pair.Key.Cl100k = await Task.Run(() => encoding.Encode(pair.Value).Count);
|
||||
|
||||
// LLM-Requests (sequenziell)
|
||||
var client = createClient();
|
||||
var index = 0;
|
||||
|
||||
foreach (var pair in chunks)
|
||||
{
|
||||
var row = pair.Key;
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
row.Status = "Abgebrochen";
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
TxtStatus.Text = $"Sende Request {index}/{chunks.Count} ({row.ActualChars:N0} Zeichen) an '{model}'...";
|
||||
row.Status = "Läuft...";
|
||||
|
||||
var payload = buildPayload(model, system_prompt, pair.Value, customer_id, num_ctx);
|
||||
|
||||
try
|
||||
{
|
||||
var 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;
|
||||
|
||||
if (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
|
||||
row.Status = "Fehler: keine prompt_eval_count in der Antwort";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
row.Status = "Fehler: " + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
TxtStatus.Text = token.IsCancellationRequested ? "Analyse abgebrochen." : "Analyse abgeschlossen.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kürzt den Text auf die Zielgröße. Ist er zu kurz, wird er optional
|
||||
/// so oft wiederholt, bis die Zielgröße erreicht ist.
|
||||
/// </summary>
|
||||
private static string buildChunk(string text, int target_chars, bool repeat)
|
||||
{
|
||||
if (text.Length >= target_chars)
|
||||
return text.Substring(0, target_chars);
|
||||
|
||||
if (!repeat)
|
||||
return text;
|
||||
|
||||
var sb = new StringBuilder(target_chars + text.Length);
|
||||
|
||||
while (sb.Length < target_chars)
|
||||
sb.Append(text);
|
||||
|
||||
return sb.ToString(0, target_chars);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parst z.B. "50k, 100k, 150k" oder "50000; 100000" zu Zeichenanzahlen.
|
||||
/// </summary>
|
||||
private static List<int> parseSizes(string input)
|
||||
{
|
||||
var result = new List<int>();
|
||||
|
||||
foreach (var part in (input ?? "").Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var s = part.Trim().ToLowerInvariant().Replace(".", "").Replace(" ", "");
|
||||
var factor = 1;
|
||||
|
||||
if (s.EndsWith("k"))
|
||||
{
|
||||
factor = 1000;
|
||||
s = s.Substring(0, s.Length - 1);
|
||||
}
|
||||
else if (s.EndsWith("m"))
|
||||
{
|
||||
factor = 1000000;
|
||||
s = s.Substring(0, s.Length - 1);
|
||||
}
|
||||
|
||||
if (!int.TryParse(s, out var value) || value <= 0)
|
||||
throw new FormatException($"Ungültige Zeichenanzahl: '{part.Trim()}'");
|
||||
|
||||
result.Add(value * factor);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object buildPayload(string model, string system_prompt, string user_message, string customer_id, int? num_ctx)
|
||||
{
|
||||
var messages = new List<object>();
|
||||
|
||||
if (!string.IsNullOrEmpty(system_prompt))
|
||||
messages.Add(new { role = "system", content = system_prompt });
|
||||
|
||||
messages.Add(new { role = "user", content = user_message });
|
||||
|
||||
var payload = new Dictionary<string, object>
|
||||
{
|
||||
["model"] = model,
|
||||
["messages"] = messages,
|
||||
["requestid"] = Guid.NewGuid().ToString("N")
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(customer_id))
|
||||
payload["customerid"] = customer_id;
|
||||
|
||||
if (num_ctx is int ctx)
|
||||
payload["options"] = new { num_ctx = ctx };
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Tools/AiTokenAnalyzer/AiTokenAnalyzer/TokenAnalyzeRow.cs
Normal file
51
Tools/AiTokenAnalyzer/AiTokenAnalyzer/TokenAnalyzeRow.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace AiTokenAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Eine Zeile der Ergebnistabelle (eine Textgröße).
|
||||
/// </summary>
|
||||
public class TokenAnalyzeRow : INotifyPropertyChanged
|
||||
{
|
||||
private int? _Cl100k;
|
||||
private long? _PromptEvalCount;
|
||||
private int? _Duration;
|
||||
private string _Status;
|
||||
|
||||
public int TargetChars { get; set; }
|
||||
public int ActualChars { get; set; }
|
||||
|
||||
public int EstDiv3 => (int)Math.Round(ActualChars / 3.0);
|
||||
public int EstDiv3_5 => (int)Math.Round(ActualChars / 3.5);
|
||||
|
||||
public int? Cl100k
|
||||
{
|
||||
get => _Cl100k;
|
||||
set { _Cl100k = value; onPropertyChanged(nameof(Cl100k)); }
|
||||
}
|
||||
|
||||
public long? PromptEvalCount
|
||||
{
|
||||
get => _PromptEvalCount;
|
||||
set { _PromptEvalCount = value; onPropertyChanged(nameof(PromptEvalCount)); }
|
||||
}
|
||||
|
||||
public int? Duration
|
||||
{
|
||||
get => _Duration;
|
||||
set { _Duration = value; onPropertyChanged(nameof(Duration)); }
|
||||
}
|
||||
|
||||
public string Status
|
||||
{
|
||||
get => _Status;
|
||||
set { _Status = value; onPropertyChanged(nameof(Status)); }
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private void onPropertyChanged(string name)
|
||||
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user