feat: externalize AI configuration, implement resilience policies, and update extraction prompt formatting

This commit is contained in:
2026-04-26 10:01:47 +02:00
parent d8e6931289
commit 412320980f
6 changed files with 65 additions and 38 deletions
@@ -8,7 +8,9 @@ using NexusReader.Domain.Entities;
using NexusReader.Infrastructure.Helpers;
using NexusReader.Infrastructure.Persistence;
using Polly;
using Polly.Retry;
using Polly.Registry;
using Microsoft.Extensions.Options;
using NexusReader.Infrastructure.Configuration;
namespace NexusReader.Infrastructure.Services;
@@ -16,25 +18,20 @@ public class KnowledgeService : IKnowledgeService
{
private readonly IChatClient _chatClient;
private readonly AppDbContext _dbContext;
private readonly ResiliencePipeline _retryPipeline;
private readonly AiSettings _settings;
private const string PromptVersion = "1.0";
private const string ModelId = "gemini-1.5-flash";
private static readonly ResiliencePipeline _retryPipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<Exception>(ex =>
ex.Message.Contains("429") || ex.Message.Contains("Too Many Requests") || ex.Message.Contains("quota")),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(2)
})
.Build();
public KnowledgeService(IChatClient chatClient, AppDbContext dbContext)
public KnowledgeService(
IChatClient chatClient,
AppDbContext dbContext,
ResiliencePipelineProvider<string> pipelineProvider,
IOptions<AiSettings> settings)
{
_chatClient = chatClient;
_dbContext = dbContext;
_retryPipeline = pipelineProvider.GetPipeline("ai-retry");
_settings = settings.Value;
}
public async Task<Result<KnowledgePacket>> GetKnowledgeAsync(string text, CancellationToken cancellationToken = default)
@@ -48,10 +45,9 @@ public class KnowledgeService : IKnowledgeService
var normalizedText = ContentHasher.Normalize(text);
// Phase 4: Request Pre-processing (Token Saving)
const int MaxInputLength = 15000; // Roughly 3k-4k tokens
if (normalizedText.Length > MaxInputLength)
if (normalizedText.Length > _settings.MaxInputLength)
{
return Result.Fail($"Input text is too long ({normalizedText.Length} characters after normalization). Max allowed is {MaxInputLength}.");
return Result.Fail($"Input text is too long ({normalizedText.Length} characters after normalization). Max allowed is {_settings.MaxInputLength}.");
}
// Simple token estimation (4 chars per token)
@@ -86,8 +82,8 @@ public class KnowledgeService : IKnowledgeService
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json,
Temperature = 0.1f,
MaxOutputTokens = 1000
Temperature = (float)_settings.Temperature,
MaxOutputTokens = _settings.MaxOutputTokens
};
var response = await _retryPipeline.ExecuteAsync(async ct =>
@@ -117,7 +113,7 @@ public class KnowledgeService : IKnowledgeService
{
ContentHash = hash,
JsonData = jsonResponse,
ModelId = ModelId,
ModelId = _settings.Model,
PromptVersion = PromptVersion,
CreatedAt = DateTime.UtcNow
};