226 lines
8.2 KiB
C#
226 lines
8.2 KiB
C#
|
|
using System.Diagnostics;
|
||
|
|
using System.Net.Http.Headers;
|
||
|
|
using System.Text.Json;
|
||
|
|
using System.Text.RegularExpressions;
|
||
|
|
using AuthorBuddy.Web.Models;
|
||
|
|
|
||
|
|
namespace AuthorBuddy.Web.Services;
|
||
|
|
|
||
|
|
public class OllamaCloudBackend : ILLMBackend
|
||
|
|
{
|
||
|
|
private readonly IHttpClientFactory _httpFactory;
|
||
|
|
private readonly OllamaSettings _settings;
|
||
|
|
|
||
|
|
public string Name => "Ollama Cloud";
|
||
|
|
public bool SupportsModelManagement => false;
|
||
|
|
|
||
|
|
public OllamaCloudBackend(OllamaSettings settings, IHttpClientFactory httpFactory)
|
||
|
|
{
|
||
|
|
_settings = settings;
|
||
|
|
_httpFactory = httpFactory;
|
||
|
|
}
|
||
|
|
|
||
|
|
private HttpClient CreateClient()
|
||
|
|
{
|
||
|
|
var client = _httpFactory.CreateClient();
|
||
|
|
client.Timeout = TimeSpan.FromSeconds(300);
|
||
|
|
client.BaseAddress = new Uri(_settings.OllamaCloudUrl.TrimEnd('/') + "/");
|
||
|
|
if (!string.IsNullOrEmpty(_settings.OllamaCloudApiKey))
|
||
|
|
{
|
||
|
|
client.DefaultRequestHeaders.Authorization =
|
||
|
|
new AuthenticationHeaderValue("Bearer", _settings.OllamaCloudApiKey);
|
||
|
|
}
|
||
|
|
return client;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<bool> TestConnectionAsync()
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var http = CreateClient();
|
||
|
|
var sw = Stopwatch.StartNew();
|
||
|
|
var response = await http.GetAsync("api/tags");
|
||
|
|
sw.Stop();
|
||
|
|
return response.IsSuccessStatusCode;
|
||
|
|
}
|
||
|
|
catch { return false; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<IEnumerable<LLM_Model>> GetModelsAsync()
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var http = CreateClient();
|
||
|
|
var response = await http.GetAsync("api/tags");
|
||
|
|
if (!response.IsSuccessStatusCode) return Enumerable.Empty<LLM_Model>();
|
||
|
|
var content = await response.Content.ReadAsStringAsync();
|
||
|
|
using var doc = JsonDocument.Parse(content);
|
||
|
|
if (!doc.RootElement.TryGetProperty("models", out var models))
|
||
|
|
return Enumerable.Empty<LLM_Model>();
|
||
|
|
var list = new List<LLM_Model>();
|
||
|
|
foreach (var m in models.EnumerateArray())
|
||
|
|
list.Add(new LLM_Model(m));
|
||
|
|
return list;
|
||
|
|
}
|
||
|
|
catch { return Enumerable.Empty<LLM_Model>(); }
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<string> ChatAsync(string model, string prompt, string? systemPrompt = null, double? temperature = null)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var request = new Dictionary<string, object?>
|
||
|
|
{
|
||
|
|
["model"] = model, ["prompt"] = prompt, ["stream"] = false
|
||
|
|
};
|
||
|
|
if (!string.IsNullOrEmpty(systemPrompt)) request["system"] = systemPrompt;
|
||
|
|
if (temperature.HasValue) request["temperature"] = Math.Clamp(temperature.Value, 0.0, 2.0);
|
||
|
|
|
||
|
|
var http = CreateClient();
|
||
|
|
var response = await http.PostAsJsonAsync("api/generate", request);
|
||
|
|
if (!response.IsSuccessStatusCode) return "Fehler bei der Verarbeitung.";
|
||
|
|
var content = await response.Content.ReadAsStringAsync();
|
||
|
|
using var doc = JsonDocument.Parse(content);
|
||
|
|
if (doc.RootElement.TryGetProperty("response", out var resp))
|
||
|
|
return resp.GetString() ?? "";
|
||
|
|
return "Fehler bei der Verarbeitung.";
|
||
|
|
}
|
||
|
|
catch (Exception ex) { return $"Error: {ex.Message}"; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task ChatStreamAsync(string model, string prompt, Func<string, Task> onToken, string? systemPrompt = null, double? temperature = null)
|
||
|
|
{
|
||
|
|
var request = new Dictionary<string, object?>
|
||
|
|
{
|
||
|
|
["model"] = model, ["prompt"] = prompt, ["stream"] = true
|
||
|
|
};
|
||
|
|
if (!string.IsNullOrEmpty(systemPrompt)) request["system"] = systemPrompt;
|
||
|
|
if (temperature.HasValue) request["temperature"] = Math.Clamp(temperature.Value, 0.0, 2.0);
|
||
|
|
|
||
|
|
var http = CreateClient();
|
||
|
|
using var response = await http.PostAsync(
|
||
|
|
"api/generate",
|
||
|
|
new StringContent(JsonSerializer.Serialize(request), System.Text.Encoding.UTF8, "application/json")
|
||
|
|
);
|
||
|
|
if (!response.IsSuccessStatusCode) return;
|
||
|
|
|
||
|
|
using var stream = await response.Content.ReadAsStreamAsync();
|
||
|
|
using var reader = new StreamReader(stream);
|
||
|
|
string? line;
|
||
|
|
while ((line = await reader.ReadLineAsync()) != null)
|
||
|
|
{
|
||
|
|
var text = ExtractResponseText(line);
|
||
|
|
if (!string.IsNullOrEmpty(text))
|
||
|
|
await onToken(text);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string ExtractResponseText(string jsonLine)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(jsonLine)) return string.Empty;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
using var doc = JsonDocument.Parse(jsonLine);
|
||
|
|
if (doc.RootElement.TryGetProperty("response", out var resp))
|
||
|
|
return resp.GetString() ?? string.Empty;
|
||
|
|
}
|
||
|
|
catch { }
|
||
|
|
return string.Empty;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<float[]> GetEmbeddingAsync(string text, string? model = null)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var m = model ?? _settings.EmbedModel;
|
||
|
|
var http = CreateClient();
|
||
|
|
var response = await http.PostAsJsonAsync("api/embeddings", new { model = m, prompt = text });
|
||
|
|
if (!response.IsSuccessStatusCode) return Array.Empty<float>();
|
||
|
|
var content = await response.Content.ReadAsStringAsync();
|
||
|
|
using var doc = JsonDocument.Parse(content);
|
||
|
|
if (!doc.RootElement.TryGetProperty("embedding", out var emb)) return Array.Empty<float>();
|
||
|
|
var list = new List<float>();
|
||
|
|
foreach (var v in emb.EnumerateArray()) list.Add(v.GetSingle());
|
||
|
|
return list.ToArray();
|
||
|
|
}
|
||
|
|
catch { return Array.Empty<float>(); }
|
||
|
|
}
|
||
|
|
|
||
|
|
public Task<bool> PullModelAsync(string name, IProgress<string>? progress = null)
|
||
|
|
{
|
||
|
|
return Task.FromResult(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
public Task<bool> DeleteModelAsync(string name)
|
||
|
|
{
|
||
|
|
return Task.FromResult(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<int?> GetContextSizeAsync(string model)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var http = CreateClient();
|
||
|
|
var response = await http.PostAsJsonAsync("api/show", new { model });
|
||
|
|
if (!response.IsSuccessStatusCode) return null;
|
||
|
|
var content = await response.Content.ReadAsStringAsync();
|
||
|
|
using var doc = JsonDocument.Parse(content);
|
||
|
|
if (doc.RootElement.TryGetProperty("modelfile", out var mf))
|
||
|
|
{
|
||
|
|
var modelfile = mf.GetString() ?? "";
|
||
|
|
var match = Regex.Match(modelfile, @"num_ctx\s+(\d+)");
|
||
|
|
if (match.Success && int.TryParse(match.Groups[1].Value, out var ctx))
|
||
|
|
return ctx;
|
||
|
|
}
|
||
|
|
return 2048;
|
||
|
|
}
|
||
|
|
catch { return null; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<long> MeasureLatencyAsync(string model)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var http = CreateClient();
|
||
|
|
var sw = Stopwatch.StartNew();
|
||
|
|
var response = await http.PostAsJsonAsync("api/generate", new { model, prompt = "Hallo", stream = false });
|
||
|
|
sw.Stop();
|
||
|
|
return response.IsSuccessStatusCode ? sw.ElapsedMilliseconds : -1;
|
||
|
|
}
|
||
|
|
catch { return -1; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<bool> ModelExistsAsync(string model)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(model)) return false;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var models = await GetModelsAsync();
|
||
|
|
return models.Any(m => m.Name == model || m.Name.StartsWith(model.Split(':')[0]));
|
||
|
|
}
|
||
|
|
catch { return false; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model)
|
||
|
|
{
|
||
|
|
var ctx = await GetContextSizeAsync(model);
|
||
|
|
return (ctx.HasValue, ctx ?? 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool TestEmbedding(string model)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var http = CreateClient();
|
||
|
|
var response = http.PostAsJsonAsync("api/embeddings", new { model, prompt = "test" }).GetAwaiter().GetResult();
|
||
|
|
if (!response.IsSuccessStatusCode) return false;
|
||
|
|
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||
|
|
using var doc = JsonDocument.Parse(content);
|
||
|
|
if (doc.RootElement.TryGetProperty("embedding", out var emb))
|
||
|
|
return emb.EnumerateArray().Any();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
catch { return false; }
|
||
|
|
}
|
||
|
|
}
|