IModerationService

This commit is contained in:
Paul Schneider 2026-05-30 18:20:17 +01:00
commit 1aafb7cc7d
10 changed files with 162 additions and 10 deletions

View file

@ -4,6 +4,14 @@ POSTGRES_PORT=5432
POSTGRES_DB=yavsc
POSTGRES_USER=yavsc
POSTGRES_PASSWORD=lame-YAVSC_CONNECTION_PASSWORD
DESTDIR=/srv/www/yavsc
ASPNETCORE_ConnectionStrings__YavscConnection=Server=$POSTGRES_HOST;Port=$POSTGRES_PORT;Database=$POSTGRES_DB;Username=$POSTGRES_USER;Password=$POSTGRES_PASSWORD;
ANTHROPIC_API_KEY=<votre-clé-api-anthropic>
MODERATION_AUTO_REJECT_THRESHOLD=0.9
MODERATION_AUTO_APPROVE_THRESHOLD=0.7
# Limite par appel
ANTHROPIC_MAX_TOKENS=256 # la modération n'a pas besoin de plus
# Choisis le bon modèle selon l'usage
# - Modération : claude-haiku-4-5 (rapide, pas cher)
# - Aide rédaction devis : claude-sonnet-4-6 (meilleur équilibre)

View file

@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Anthropic.SDK" Version="5.10.0" />
<PackageVersion Include="AsciiDocSharp" Version="0.2.0" />
<PackageVersion Include="AsciiDocSharp.Converters.Html" Version="0.2.0" />
<PackageVersion Include="Avalonia" Version="12.0.4" />
@ -63,10 +64,10 @@
<PackageVersion Include="Selenium.WebDriver" Version="4.44.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="4.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.2.0" />
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="10.0.8" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="Yavsc.Abstract" Version="1.0.6-rc18" />
</ItemGroup>
</Project>
</Project>

View file

@ -0,0 +1,8 @@
using Yavsc.Moderation;
namespace Yavsc.Abstract.Interfaces;
public interface IModerationService
{
Task<ModerationResult> ModerateAsync(string content, string context);
}

View file

@ -0,0 +1,3 @@
namespace Yavsc.Moderation;
public enum ModerationAction { Approved, Rejected, NeedsReview }

View file

@ -0,0 +1,7 @@
namespace Yavsc.Moderation;
public record ModerationResult(
ModerationAction Action,
string Reason,
float ConfidenceScore
);

View file

@ -1,5 +1,7 @@
using Anthropic.SDK;
using Microsoft.AspNetCore;
using Yavsc.Abstract.Interfaces;
using Yavsc.Extensions;
namespace Yavsc
@ -9,12 +11,29 @@ namespace Yavsc
public static async Task Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Anthropic client
builder.Services.AddSingleton<AnthropicClient>(_ =>
new AnthropicClient(
new APIAuthentication(
builder.Configuration["ANTHROPIC_API_KEY"]
?? throw new InvalidOperationException("ANTHROPIC_API_KEY manquante")
)
)
);
// Service de modération
if (builder.Environment.IsDevelopment())
builder.Services.AddScoped<IModerationService, MockModerationService>();
else
builder.Services.AddScoped<IModerationService, ClaudeModerationService>();
builder.Configuration
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables()
.Build();
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables()
.Build();
var app = await builder.ConfigureWebAppServices().ConfigurePipeline();
app.Run();
}

View file

@ -77,5 +77,9 @@
},
"Kyc": {
"HmacSecret": "*** via dotnet user-secrets ou variable d'environnement ***"
},
"Moderation": {
"AutoApproveThreshold": 0.7,
"AutoRejectThreshold": 0.9
}
}

View file

@ -0,0 +1,90 @@
using Yavsc.Moderation;
using Yavsc.Abstract.Interfaces;
using Anthropic.SDK;
using Anthropic.SDK.Messaging;
using Anthropic.SDK.Constants;
using Anthropic.SDK.Models;
using Newtonsoft.Json;
using Microsoft.Extensions.Configuration;
using System.Text.Json;
public class ClaudeModerationService : IModerationService
{
private readonly AnthropicClient _client;
private readonly float _autoApproveThreshold;
private readonly float _autoRejectThreshold;
public ClaudeModerationService(
AnthropicClient client,
IConfiguration config)
{
_client = client;
_autoApproveThreshold = config.GetValue<float>(
"Moderation:AutoApproveThreshold", 0.7f);
_autoRejectThreshold = config.GetValue<float>(
"Moderation:AutoRejectThreshold", 0.9f);
}
public async Task<ModerationResult> ModerateAsync(string content, string context)
{
var prompt = $$"""
Tu es un modérateur pour une plateforme de mise en relation prestataires/clients.
Contexte : {{context}}
Contenu à modérer : {{content}}
Réponds UNIQUEMENT en JSON :
{
"action": "approved" | "rejected" | "needs_review",
"reason": "explication courte",
"confidence": 0.0 à 1.0
}
""";
var response = await _client.Messages.GetClaudeMessageAsync(
new MessageParameters
{
Model = AnthropicModels.Claude45Haiku, // Haiku : rapide et économique
MaxTokens = 256,
Messages = [new Message(RoleType.User, prompt)]
});
var text = response.Content
.OfType<TextContent>()
.FirstOrDefault()?.Text ?? string.Empty;
return ParseResult(text);
}
private ModerationResult ParseResult(string json)
{
try
{
var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
var actionStr = root.GetProperty("action").GetString();
var reason = root.GetProperty("reason").GetString() ?? string.Empty;
var confidence = root.GetProperty("confidence").GetSingle();
var action = actionStr switch
{
"approved" => confidence >= _autoApproveThreshold
? ModerationAction.Approved
: ModerationAction.NeedsReview,
"rejected" => confidence >= _autoRejectThreshold
? ModerationAction.Rejected
: ModerationAction.NeedsReview,
"needs_review" => ModerationAction.NeedsReview,
_ => ModerationAction.NeedsReview
};
return new ModerationResult(action, reason, confidence);
}
catch
{
return new ModerationResult(
ModerationAction.NeedsReview,
"Erreur de parsing de la réponse",
0f);
}
}
}

View file

@ -0,0 +1,11 @@
using Yavsc.Abstract.Interfaces;
using Yavsc.Moderation;
public class MockModerationService : IModerationService
{
public Task<ModerationResult> ModerateAsync(string content, string context)
=> Task.FromResult(new ModerationResult(
ModerationAction.Approved,
"Mock modération — API non configurée",
1.0f));
}

View file

@ -8,6 +8,7 @@
<RepositoryUrl>https://github.com/pazof/yavsc</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Anthropic.SDK" />
<PackageReference Include="HigginsSoft.IdentityServer8.EntityFramework" />
<PackageReference Include="Magick.NET-Q8-AnyCPU" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />