feat(ai): enforce local-first routing
Keep external providers behind server consent, task, and prompt-cost gates while persisting actual provider provenance.
This commit is contained in:
@@ -14,6 +14,8 @@ public sealed class AiWorkspaceTests
|
||||
public string? Next = "## Result\nGenerated suggestion.";
|
||||
public int Calls;
|
||||
public string? LastInstruction;
|
||||
public string? ActualProvider;
|
||||
public string? FallbackReason;
|
||||
// The source text the module assembled — what the prompt actually saw.
|
||||
public string? LastText;
|
||||
public Task<string?> SummarizeSectionAsync(string instruction, string text, int maxLength = 180, int minLength = 40)
|
||||
@@ -24,6 +26,17 @@ public sealed class AiWorkspaceTests
|
||||
return Task.FromResult(Next);
|
||||
}
|
||||
public Task<string?> SummarizeAsync(string text, int maxLength = 150, int minLength = 30) => Task.FromResult(Next);
|
||||
public async Task<AiGenerationResult?> GenerateSectionWithMetadataAsync(
|
||||
string instruction,
|
||||
string text,
|
||||
int maxLength = 180,
|
||||
int minLength = 40,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var generated = await SummarizeSectionAsync(instruction, text, maxLength, minLength);
|
||||
return generated is null ? null : new AiGenerationResult(generated, ActualProvider, "test-model", FallbackReason,
|
||||
FallbackReason is null ? "local_primary" : "external_fallback");
|
||||
}
|
||||
public Task<AiTextExtractionResult?> ExtractTextAsync(Stream stream, string fileName, string? contentType = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
public Task RunProbeAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public Task<AiServiceMetrics> GetMetricsAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException();
|
||||
@@ -72,6 +85,22 @@ public sealed class AiWorkspaceTests
|
||||
Assert.Single(await db.AiInteractions.IgnoreQueryFilters().Where(x => x.JobApplicationId == jobId).ToListAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Generate_records_the_actual_provider_and_fallback_metadata()
|
||||
{
|
||||
var (db, svc, ai) = New("user-1");
|
||||
await using var _ = db;
|
||||
ai.ActualProvider = "groq";
|
||||
ai.FallbackReason = "local_circuit_open";
|
||||
var jobId = await SeedJobAsync(db, "user-1");
|
||||
|
||||
var result = await svc.GenerateAsync("user-1", jobId, "cv", "Ada", Req("job-analysis"), "configured-label", default);
|
||||
|
||||
Assert.Equal("groq", result!.Provider);
|
||||
Assert.Contains("local_circuit_open", result.ResultJson);
|
||||
Assert.Contains("test-model", result.ResultJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Cover_letter_normalizes_an_unknown_mode_and_labels_the_title()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user