AuthorBuddy/Services/OllamaService.cs

179 lines
5.7 KiB
C#
Raw Normal View History

2026-06-21 07:52:03 +02:00
using System.Diagnostics;
2026-05-14 12:31:35 +02:00
using AuthorBuddy.Web.Models;
namespace AuthorBuddy.Web.Services;
2026-05-16 16:13:35 +02:00
public class OllamaService : IOllamaService
2026-05-14 12:31:35 +02:00
{
2026-05-31 19:31:27 +02:00
private readonly IEnumerable<ILLMBackend> _backends;
2026-05-17 07:27:18 +02:00
private readonly OllamaSettings _settings;
2026-05-31 19:31:27 +02:00
private readonly OperationState _opState;
2026-06-21 07:52:03 +02:00
private readonly object _startLock = new();
private bool _startAttempted;
2026-05-14 12:31:35 +02:00
2026-05-31 19:31:27 +02:00
public OllamaService(IEnumerable<ILLMBackend> backends, OllamaSettings settings, OperationState opState)
2026-05-14 12:31:35 +02:00
{
2026-05-31 19:31:27 +02:00
_backends = backends;
2026-05-17 07:27:18 +02:00
_settings = settings;
2026-05-31 19:31:27 +02:00
_opState = opState;
2026-05-14 12:31:35 +02:00
}
2026-05-31 19:31:27 +02:00
private ILLMBackend ActiveBackend =>
_backends.FirstOrDefault(b => b.Name == _settings.Backend) ?? _backends.First();
2026-06-21 07:52:03 +02:00
private async Task EnsureRunningAsync()
{
if (ActiveBackend.Name != "Ollama")
return;
var reachable = await ActiveBackend.TestConnectionAsync();
if (reachable)
return;
lock (_startLock)
{
if (_startAttempted)
return;
_startAttempted = true;
}
_opState.SetThought("Ollama läuft nicht starte ollama serve...");
try
{
var psi = new ProcessStartInfo("ollama", "serve")
{
CreateNoWindow = true,
UseShellExecute = false
};
Process.Start(psi);
}
catch (Exception ex)
{
_opState.AppendThought($" Fehler: {ex.Message}");
return;
}
for (int i = 0; i < 30; i++)
{
await Task.Delay(1000);
reachable = await ActiveBackend.TestConnectionAsync();
if (reachable)
{
_opState.AppendThought(" Ollama ist bereit");
return;
}
_opState.AppendThought(".");
}
_opState.AppendThought(" Zeitüberschreitung");
}
2026-05-16 16:13:35 +02:00
public void Reconfigure(string url)
2026-05-14 12:31:35 +02:00
{
2026-05-31 19:31:27 +02:00
_settings.OllamaUrl = url;
2026-05-14 12:31:35 +02:00
}
2026-05-31 19:31:27 +02:00
public async Task<(bool Success, long LatencyMs)> TestConnectionAsync()
2026-05-20 06:12:54 +02:00
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
_opState.SetThought($"Teste Verbindung zu {ActiveBackend.Name}...");
2026-06-21 07:52:03 +02:00
var sw = Stopwatch.StartNew();
2026-05-31 19:31:27 +02:00
var result = await ActiveBackend.TestConnectionAsync();
sw.Stop();
_opState.AppendThought(result ? " OK" : " Fehlgeschlagen");
return (result, result ? sw.ElapsedMilliseconds : 0);
2026-05-20 06:12:54 +02:00
}
2026-05-31 19:31:27 +02:00
public async Task<IEnumerable<LLM_Model>> GetLocalModelsAsync()
2026-05-20 06:12:54 +02:00
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
_opState.SetThought($"Lade Modelle von {ActiveBackend.Name}...");
var models = await ActiveBackend.GetModelsAsync();
_opState.AppendThought($" {models.Count()} gefunden");
return models;
2026-05-20 06:12:54 +02:00
}
2026-05-31 19:31:27 +02:00
public async Task<bool> TestEmbeddingAsync(string model)
2026-05-14 12:31:35 +02:00
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
return ActiveBackend.TestEmbedding(model);
}
2026-05-16 16:13:35 +02:00
2026-05-31 19:31:27 +02:00
public async Task<float[]> GetEmbeddingAsync(string text)
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
return await ActiveBackend.GetEmbeddingAsync(text, _settings.EmbedModel);
2026-05-14 12:31:35 +02:00
}
2026-05-17 07:27:18 +02:00
public async Task<string> QuickSpellCheckAsync(string text)
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
return await Chat(_settings.SpellingModel, text, _settings.SpellingPrompt, _settings.SpellingTemp);
2026-05-17 07:27:18 +02:00
}
public async Task<string> StreamStyleCheckAsync(string text)
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
return await Chat(_settings.StyleModel, text, _settings.LektorSystemPrompt, _settings.LektorTemp);
2026-05-17 07:27:18 +02:00
}
public async Task<string> AnswerBasedOnFactsAsync(string question, string facts)
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-17 07:27:18 +02:00
var prompt = _settings.GetFactCheckPrompt(question, facts);
2026-05-31 19:31:27 +02:00
return await Chat(_settings.EmbedModel, prompt, temperature: _settings.FactCheckTemp);
2026-05-17 07:27:18 +02:00
}
2026-05-31 19:31:27 +02:00
public async Task<bool> ModelExistsAsync(string model)
2026-05-14 12:31:35 +02:00
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
return await ActiveBackend.ModelExistsAsync(model);
2026-05-14 12:31:35 +02:00
}
2026-05-31 19:31:27 +02:00
public async Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model)
2026-05-14 12:31:35 +02:00
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
return await ActiveBackend.GetModelContextWindowAsync(model);
2026-05-14 12:31:35 +02:00
}
2026-05-31 19:31:27 +02:00
public async Task<long> MeasureLatencyAsync(string model)
2026-05-14 12:31:35 +02:00
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
return await ActiveBackend.MeasureLatencyAsync(model);
}
2026-05-16 16:13:35 +02:00
2026-05-31 19:31:27 +02:00
public async Task<string> Chat(string model, string userMessage, string? systemPrompt = null, double? temperature = null)
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
_opState.SetThought($"{ActiveBackend.Name}: Sende Anfrage an {model}...");
var result = await ActiveBackend.ChatAsync(model, userMessage, systemPrompt, temperature);
_opState.AppendThought(" Fertig");
return result;
2026-05-14 12:31:35 +02:00
}
2026-05-31 19:31:27 +02:00
public async Task ChatStream(string model, string userMessage, Func<string, Task> onToken, string? systemPrompt = null, double? temperature = null)
2026-05-14 12:31:35 +02:00
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
_opState.SetThought($"{ActiveBackend.Name}: Verbinde mit Modell {model}...");
2026-05-16 16:13:35 +02:00
2026-05-31 19:31:27 +02:00
int tokenCount = 0;
await ActiveBackend.ChatStreamAsync(model, userMessage, async chunk =>
2026-05-16 16:13:35 +02:00
{
2026-05-31 19:31:27 +02:00
tokenCount++;
_opState.AppendThought(chunk);
await onToken(chunk);
}, systemPrompt, temperature);
_opState.AppendThought($" Fertig ({tokenCount} Token)");
2026-05-14 12:31:35 +02:00
}
2026-05-16 16:13:35 +02:00
public async Task<bool> PullModelAsync(string modelName, IProgress<string> progress)
2026-05-14 12:31:35 +02:00
{
2026-06-21 07:52:03 +02:00
await EnsureRunningAsync();
2026-05-31 19:31:27 +02:00
_opState.SetThought($"Lade Modell {modelName}...");
var result = await ActiveBackend.PullModelAsync(modelName, progress);
_opState.AppendThought(result ? " Erfolgreich" : " Fehlgeschlagen");
return result;
2026-05-14 12:31:35 +02:00
}
}