From 591c9b8a648799a182050672160b70985105421f Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sat, 11 Apr 2026 21:55:51 +0200 Subject: [PATCH] Clamp AI summarize lengths for CV rewrite --- JobTrackerApi.Tests/SummarizerServiceTests.cs | 49 +++++++++++++++++++ JobTrackerApi/Services/SummarizerService.cs | 15 +++++- 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 JobTrackerApi.Tests/SummarizerServiceTests.cs diff --git a/JobTrackerApi.Tests/SummarizerServiceTests.cs b/JobTrackerApi.Tests/SummarizerServiceTests.cs new file mode 100644 index 0000000..c7d317a --- /dev/null +++ b/JobTrackerApi.Tests/SummarizerServiceTests.cs @@ -0,0 +1,49 @@ +using System.Net; +using System.Net.Http; +using System.Text; +using Microsoft.Extensions.Caching.Memory; +using Moq; +using Xunit; +using JobTrackerApi.Services; + +namespace JobTrackerApi.Tests; + +public sealed class SummarizerServiceTests +{ + [Fact] + public async Task Summarize_section_clamps_lengths_to_ai_service_limits() + { + var handler = new CapturingHandler(); + var httpClient = new HttpClient(handler) + { + BaseAddress = new Uri("http://localhost:8001") + }; + + var httpFactory = new Mock(); + httpFactory.Setup(x => x.CreateClient("ai-service")).Returns(httpClient); + + using var memoryCache = new MemoryCache(new MemoryCacheOptions()); + var service = new SummarizerService(httpFactory.Object, memoryCache); + + var result = await service.SummarizeSectionAsync("Rewrite this CV", "Professional Summary\nBuilt backend systems.", 1800, 400); + + Assert.Equal("ok", result); + Assert.NotNull(handler.LastBody); + Assert.Contains("\"max_length\":256", handler.LastBody); + Assert.Contains("\"min_length\":180", handler.LastBody); + } + + private sealed class CapturingHandler : HttpMessageHandler + { + public string? LastBody { get; private set; } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + LastBody = request.Content is null ? null : await request.Content.ReadAsStringAsync(cancellationToken); + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"summary\":\"ok\"}", Encoding.UTF8, "application/json") + }; + } + } +} diff --git a/JobTrackerApi/Services/SummarizerService.cs b/JobTrackerApi/Services/SummarizerService.cs index 404a184..8942c1c 100644 --- a/JobTrackerApi/Services/SummarizerService.cs +++ b/JobTrackerApi/Services/SummarizerService.cs @@ -76,6 +76,10 @@ namespace JobTrackerApi.Services public class SummarizerService : ISummarizerService { private const int AiSummarizeMaxInputChars = 20000; + private const int AiServiceMaxSummaryLength = 256; + private const int AiServiceMaxMinLength = 180; + private const int AiServiceMinSummaryLength = 24; + private const int AiServiceMinMinLength = 8; private readonly IHttpClientFactory _httpFactory; private readonly IMemoryCache _cache; private readonly object _metricsLock = new(); @@ -172,7 +176,14 @@ namespace JobTrackerApi.Services private async Task SummarizeCoreAsync(string text, int maxLength, int minLength) { - var key = BuildCacheKey(text, maxLength, minLength); + var normalizedMaxLength = Math.Clamp(maxLength, AiServiceMinSummaryLength, AiServiceMaxSummaryLength); + var normalizedMinLength = Math.Clamp(minLength, AiServiceMinMinLength, AiServiceMaxMinLength); + if (normalizedMinLength >= normalizedMaxLength) + { + normalizedMinLength = Math.Max(AiServiceMinMinLength, normalizedMaxLength - 1); + } + + var key = BuildCacheKey(text, normalizedMaxLength, normalizedMinLength); Interlocked.Increment(ref _requests); if (_cache.TryGetValue(key, out var cached)) @@ -189,7 +200,7 @@ namespace JobTrackerApi.Services Interlocked.Increment(ref _cacheMisses); var client = _httpFactory.CreateClient("ai-service"); - var payload = JsonSerializer.Serialize(new { text, max_length = maxLength, min_length = minLength }); + var payload = JsonSerializer.Serialize(new { text, max_length = normalizedMaxLength, min_length = normalizedMinLength }); using var content = new StringContent(payload, Encoding.UTF8, "application/json"); var sw = Stopwatch.StartNew();