documentation cleanse, sprints work and planning. remaining non EF DAL migration to EF
This commit is contained in:
@@ -21,6 +21,8 @@ using StellaOps.AdvisoryAI.WebService.Security;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
@@ -166,6 +168,17 @@ public static class ChatEndpoints
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
if (!result.GuardrailBlocked && !result.QuotaBlocked && !result.ToolAccessDenied)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Chat gateway runtime fallback activated for tenant {TenantId}, user {UserId}. Reason: {Reason}",
|
||||
tenantId,
|
||||
userId,
|
||||
result.Error ?? "processing_failed");
|
||||
|
||||
return Results.Ok(CreateDeterministicFallbackQueryResponse(request, result));
|
||||
}
|
||||
|
||||
var statusCode = result.GuardrailBlocked
|
||||
? StatusCodes.Status400BadRequest
|
||||
: result.QuotaBlocked
|
||||
@@ -858,6 +871,60 @@ public static class ChatEndpoints
|
||||
} : null
|
||||
};
|
||||
}
|
||||
|
||||
private static AdvisoryChatQueryResponse CreateDeterministicFallbackQueryResponse(
|
||||
AdvisoryChatQueryRequest request,
|
||||
AdvisoryChatServiceResult failedResult)
|
||||
{
|
||||
var diagnostics = failedResult.Diagnostics is null
|
||||
? null
|
||||
: new DiagnosticsResponse
|
||||
{
|
||||
IntentRoutingMs = failedResult.Diagnostics.IntentRoutingMs,
|
||||
EvidenceAssemblyMs = failedResult.Diagnostics.EvidenceAssemblyMs,
|
||||
InferenceMs = failedResult.Diagnostics.InferenceMs,
|
||||
TotalMs = failedResult.Diagnostics.TotalMs,
|
||||
PromptTokens = failedResult.Diagnostics.PromptTokens,
|
||||
CompletionTokens = failedResult.Diagnostics.CompletionTokens,
|
||||
};
|
||||
|
||||
var reason = string.IsNullOrWhiteSpace(failedResult.Error)
|
||||
? "chat runtime unavailable"
|
||||
: failedResult.Error.Trim();
|
||||
var normalizedQuery = request.Query?.Trim() ?? string.Empty;
|
||||
var fallbackId = BuildFallbackResponseId(normalizedQuery, reason);
|
||||
|
||||
return new AdvisoryChatQueryResponse
|
||||
{
|
||||
ResponseId = fallbackId,
|
||||
BundleId = null,
|
||||
Intent = (failedResult.Intent ?? AdvisoryChatIntent.General).ToString(),
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
Summary =
|
||||
$"Chat runtime is temporarily unavailable. For \"{normalizedQuery}\", start with unified search evidence, verify VEX status, and confirm active policy gates before acting.",
|
||||
Confidence = new ConfidenceResponse { Level = ConfidenceLevel.Low.ToString(), Score = 0.2d },
|
||||
EvidenceLinks = [],
|
||||
Mitigations = [],
|
||||
ProposedActions = [],
|
||||
FollowUp = new FollowUpResponse
|
||||
{
|
||||
SuggestedQueries = [normalizedQuery],
|
||||
NextSteps =
|
||||
[
|
||||
"Retry this chat query after runtime recovery.",
|
||||
"Use global search to review findings, VEX, and policy context now."
|
||||
]
|
||||
},
|
||||
Diagnostics = diagnostics,
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildFallbackResponseId(string query, string reason)
|
||||
{
|
||||
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes($"{query}|{reason}"));
|
||||
var token = Convert.ToHexString(bytes.AsSpan(0, 8)).ToLowerInvariant();
|
||||
return $"fallback-{token}";
|
||||
}
|
||||
}
|
||||
|
||||
#region Request/Response DTOs
|
||||
|
||||
@@ -121,6 +121,24 @@ public static class SearchAnalyticsEndpoints
|
||||
|
||||
if (events.Count > 0)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
foreach (var evt in events)
|
||||
{
|
||||
if (!ShouldPersistHistory(evt))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
await analyticsService.RecordHistoryAsync(
|
||||
tenant,
|
||||
userId,
|
||||
evt.Query,
|
||||
evt.ResultCount ?? 0,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Fire-and-forget: do not await in the request pipeline to keep latency low.
|
||||
// The analytics service already swallows exceptions internally.
|
||||
_ = analyticsService.RecordEventsAsync(events, CancellationToken.None);
|
||||
@@ -129,6 +147,22 @@ public static class SearchAnalyticsEndpoints
|
||||
return Results.NoContent();
|
||||
}
|
||||
|
||||
private static bool ShouldPersistHistory(SearchAnalyticsEvent evt)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(evt.Query))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (evt.Query.StartsWith("__", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(evt.EventType, "query", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(evt.EventType, "zero_result", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetHistoryAsync(
|
||||
HttpContext httpContext,
|
||||
SearchAnalyticsService analyticsService,
|
||||
|
||||
Reference in New Issue
Block a user