291 lines
9 KiB
C#
291 lines
9 KiB
C#
using AuthorBuddy.Web.Models;
|
|
using System.Text.Json;
|
|
using static System.Net.Mime.MediaTypeNames;
|
|
|
|
namespace AuthorBuddy.Web.Services;
|
|
|
|
public class OllamaService : IOllamaService
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private readonly OllamaSettings _settings;
|
|
private string _baseUrl = "http://localhost:11434";
|
|
|
|
public OllamaService(OllamaSettings settings)
|
|
{
|
|
_settings = settings;
|
|
_baseUrl = settings.OllamaUrl;
|
|
_httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(300) };
|
|
}
|
|
|
|
public void Reconfigure(string url)
|
|
{
|
|
_baseUrl = url;
|
|
}
|
|
|
|
public async Task<bool> TestEmbeddingAsync(string model)
|
|
{
|
|
try
|
|
{
|
|
var request = new { model = model, prompt = "test" };
|
|
var response = await _httpClient.PostAsJsonAsync($"{_baseUrl}/api/embeddings", request);
|
|
if (!response.IsSuccessStatusCode) return false;
|
|
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
using var doc = JsonDocument.Parse(content);
|
|
if (doc.RootElement.TryGetProperty("embedding", out var embedding))
|
|
return embedding.EnumerateArray().Any();
|
|
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<float[]> GetEmbeddingAsync(string text)
|
|
{
|
|
try
|
|
{
|
|
var request = new { model = _settings.EmbedModel, prompt = text };
|
|
var response = await _httpClient.PostAsJsonAsync($"{_baseUrl}/api/embeddings", request);
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
using var doc = JsonDocument.Parse(content);
|
|
if (doc.RootElement.TryGetProperty("embedding", out var embedding))
|
|
{
|
|
var list = new List<float>();
|
|
foreach (var val in embedding.EnumerateArray())
|
|
list.Add(val.GetSingle());
|
|
return list.ToArray();
|
|
}
|
|
}
|
|
return Array.Empty<float>();
|
|
}
|
|
catch
|
|
{
|
|
return Array.Empty<float>();
|
|
}
|
|
}
|
|
|
|
public async Task<IEnumerable<LLM_Model>> GetLocalModelsAsync()
|
|
{
|
|
try
|
|
{
|
|
var response = await _httpClient.GetAsync($"{_baseUrl}/api/tags");
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
using var doc = JsonDocument.Parse(content);
|
|
var root = doc.RootElement;
|
|
|
|
if (root.TryGetProperty("models", out var models))
|
|
{
|
|
var modelList = new List<LLM_Model>();
|
|
foreach (var model in models.EnumerateArray())
|
|
{
|
|
modelList.Add(new LLM_Model(model));
|
|
|
|
|
|
}
|
|
return modelList;
|
|
}
|
|
}
|
|
return new List<LLM_Model>();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return new List<LLM_Model>();
|
|
}
|
|
}
|
|
|
|
public async Task<string> QuickSpellCheckAsync(string text)
|
|
{
|
|
return await Chat(_settings.SpellingModel, text, _settings.SpellingPrompt);
|
|
}
|
|
|
|
public async Task<string> StreamStyleCheckAsync(string text)
|
|
{
|
|
var systemPrompt = _settings.LektorSystemPrompt;
|
|
return await Chat(_settings.StyleModel, text, systemPrompt);
|
|
|
|
}
|
|
|
|
public async Task<string> AnswerBasedOnFactsAsync(string question, string facts)
|
|
{
|
|
var prompt = _settings.GetFactCheckPrompt(question, facts);
|
|
|
|
return await Chat(_settings.EmbedModel, prompt);
|
|
}
|
|
|
|
|
|
|
|
public async Task<string> Chat(string model, string userMessage, string? systemPrompt = null)
|
|
{
|
|
try
|
|
{
|
|
var request = new
|
|
{
|
|
model = model,
|
|
prompt = userMessage,
|
|
system = systemPrompt,
|
|
stream = false
|
|
};
|
|
|
|
var response = await _httpClient.PostAsJsonAsync($"{_baseUrl}/api/generate", request);
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
using var doc = JsonDocument.Parse(content);
|
|
var root = doc.RootElement;
|
|
|
|
if (root.TryGetProperty("response", out var responseText))
|
|
{
|
|
return responseText.GetString() ?? "";
|
|
}
|
|
}
|
|
return "Fehler bei der Verarbeitung.";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return $"Error: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
public async IAsyncEnumerable<string> ChatStream(string model, string userMessage, string? systemPrompt = null)
|
|
{
|
|
var request = new
|
|
{
|
|
model = model,
|
|
prompt = userMessage,
|
|
stream = true
|
|
};
|
|
|
|
HttpResponseMessage? response = null;
|
|
try
|
|
{
|
|
response = await _httpClient.PostAsync(
|
|
$"{_baseUrl}/api/generate",
|
|
new StringContent(JsonSerializer.Serialize(request), System.Text.Encoding.UTF8, "application/json")
|
|
);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
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))
|
|
{
|
|
yield return text;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
response?.Dispose();
|
|
}
|
|
}
|
|
|
|
private string ExtractResponseText(string jsonLine)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(jsonLine))
|
|
return string.Empty;
|
|
|
|
try
|
|
{
|
|
using var doc = JsonDocument.Parse(jsonLine);
|
|
var root = doc.RootElement;
|
|
|
|
if (root.TryGetProperty("response", out var responseText))
|
|
{
|
|
return responseText.GetString() ?? string.Empty;
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
public async Task<bool> DeleteModel(string modelName)
|
|
{
|
|
try
|
|
{
|
|
var request = new HttpRequestMessage(HttpMethod.Delete, $"{_baseUrl}/api/delete");
|
|
request.Content = new StringContent(JsonSerializer.Serialize(new { name = modelName }),
|
|
System.Text.Encoding.UTF8, "application/json");
|
|
|
|
var response = await _httpClient.SendAsync(request);
|
|
return response.IsSuccessStatusCode;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> PullModelAsync(string modelName, IProgress<string> progress)
|
|
{
|
|
try
|
|
{
|
|
var request = new { name = modelName, stream = true };
|
|
var response = await _httpClient.PostAsync(
|
|
$"{_baseUrl}/api/pull",
|
|
new StringContent(JsonSerializer.Serialize(request), System.Text.Encoding.UTF8, "application/json")
|
|
);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
using var stream = await response.Content.ReadAsStreamAsync();
|
|
using var reader = new StreamReader(stream);
|
|
|
|
string? line;
|
|
while ((line = await reader.ReadLineAsync()) != null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(line)) continue;
|
|
|
|
try
|
|
{
|
|
using var doc = JsonDocument.Parse(line);
|
|
var root = doc.RootElement;
|
|
|
|
if (root.TryGetProperty("status", out var status))
|
|
{
|
|
var statusText = status.GetString() ?? "";
|
|
var msg = statusText;
|
|
|
|
if (root.TryGetProperty("completed", out var completed) &&
|
|
root.TryGetProperty("total", out var total))
|
|
{
|
|
var comp = completed.GetInt64();
|
|
var tot = total.GetInt64();
|
|
if (tot > 0)
|
|
{
|
|
var pct = (int)(comp * 100 / tot);
|
|
msg = $"{statusText}: {comp}/{tot} ({pct}%)";
|
|
}
|
|
}
|
|
|
|
progress?.Report(msg);
|
|
|
|
if (statusText == "success")
|
|
return true;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|