8ac593d36f
Backend CI / build-and-test (push) Failing after 14m19s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Failing after 12m5s
Frontend CI / build-and-check (push) Failing after 17m58s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 10m11s
🚀 Create and publish a Docker image / Build & publish backend image (push) Failing after 11m3s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Failing after 14m58s
87 lines
3.0 KiB
C#
87 lines
3.0 KiB
C#
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
using UniVerse.Application.Interfaces;
|
|
using UniVerse.Application.Prompts;
|
|
|
|
namespace UniVerse.Infrastructure.ExternalServices;
|
|
|
|
public class LlmClient : ILlmClient
|
|
{
|
|
private readonly HttpClient _http;
|
|
private readonly IConfiguration _config;
|
|
private readonly IReviewPromptService _reviewPrompts;
|
|
private readonly ILogger<LlmClient> _logger;
|
|
|
|
public LlmClient(
|
|
HttpClient http,
|
|
IConfiguration config,
|
|
IReviewPromptService reviewPrompts,
|
|
ILogger<LlmClient> logger)
|
|
{
|
|
_http = http;
|
|
_config = config;
|
|
_reviewPrompts = reviewPrompts;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<LlmReviewAnalysis> AnalyzeReviewAsync(string reviewText, string lectureContext)
|
|
{
|
|
var promptSetting = await _reviewPrompts.GetAsync();
|
|
var prompt = ReviewPromptTemplate.Render(promptSetting.Prompt, reviewText, lectureContext);
|
|
|
|
var request = new
|
|
{
|
|
model = _config["Llm:Model"] ?? "gpt-4o-mini",
|
|
messages = new[] { new { role = "user", content = prompt } },
|
|
temperature = 0.3,
|
|
response_format = new { type = "json_object" }
|
|
};
|
|
|
|
var apiKey = _config["Llm:ApiKey"] ?? "";
|
|
_http.DefaultRequestHeaders.Clear();
|
|
if (!string.IsNullOrEmpty(apiKey))
|
|
_http.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
|
|
|
|
var response = await _http.PostAsJsonAsync("chat/completions", request);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
|
var content = json.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString()!;
|
|
var analysisJson = NormalizeJsonContent(content);
|
|
var analysis = JsonSerializer.Deserialize<LlmRawResponse>(analysisJson,
|
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;
|
|
|
|
return new LlmReviewAnalysis(
|
|
Math.Clamp(analysis.QualityScore, 0, 1),
|
|
analysis.Sentiment ?? "",
|
|
analysis.Tags ?? [],
|
|
analysis.IsInformative,
|
|
content);
|
|
}
|
|
|
|
private static string NormalizeJsonContent(string content)
|
|
{
|
|
var trimmed = content.Trim();
|
|
if (!trimmed.StartsWith("```", StringComparison.Ordinal))
|
|
return trimmed;
|
|
|
|
var firstNewLine = trimmed.IndexOf('\n');
|
|
if (firstNewLine < 0)
|
|
return trimmed;
|
|
|
|
var lastFence = trimmed.LastIndexOf("```", StringComparison.Ordinal);
|
|
return lastFence > firstNewLine
|
|
? trimmed[(firstNewLine + 1)..lastFence].Trim()
|
|
: trimmed[(firstNewLine + 1)..].Trim();
|
|
}
|
|
|
|
private record LlmRawResponse(
|
|
[property: JsonPropertyName("quality_score")] double QualityScore,
|
|
string? Sentiment,
|
|
string[]? Tags,
|
|
[property: JsonPropertyName("is_informative")] bool IsInformative);
|
|
}
|