Add self-serve search telemetry gap surfacing
This commit is contained in:
@@ -17,7 +17,10 @@ public static class SearchAnalyticsEndpoints
|
||||
"query",
|
||||
"click",
|
||||
"zero_result",
|
||||
"synthesis"
|
||||
"synthesis",
|
||||
"answer_frame",
|
||||
"reformulation",
|
||||
"rescue_action"
|
||||
};
|
||||
|
||||
public static RouteGroupBuilder MapSearchAnalyticsEndpoints(this IEndpointRouteBuilder builder)
|
||||
@@ -30,10 +33,11 @@ public static class SearchAnalyticsEndpoints
|
||||
|
||||
group.MapPost("/analytics", RecordAnalyticsAsync)
|
||||
.WithName("SearchAnalyticsRecord")
|
||||
.WithSummary("Records batch search analytics events (query, click, zero_result, synthesis).")
|
||||
.WithSummary("Records batch search analytics events (query, click, zero_result, synthesis, answer_frame, reformulation, rescue_action).")
|
||||
.WithDescription(
|
||||
"Accepts a batch of search analytics events for tracking query frequency, click-through rates, " +
|
||||
"zero-result queries, and synthesis usage. Queries and user identifiers are pseudonymized before persistence. " +
|
||||
"zero-result queries, self-serve answer states, reformulations, rescue-action usage, and synthesis usage. " +
|
||||
"Queries and user/session identifiers are pseudonymized before persistence. " +
|
||||
"Fire-and-forget from the client; failures do not affect search functionality.")
|
||||
.RequireAuthorization(AdvisoryAIPolicies.OperatePolicy)
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
@@ -113,11 +117,14 @@ public static class SearchAnalyticsEndpoints
|
||||
EventType: apiEvent.EventType.Trim().ToLowerInvariant(),
|
||||
Query: apiEvent.Query.Trim(),
|
||||
UserId: userId,
|
||||
SessionId: string.IsNullOrWhiteSpace(apiEvent.SessionId) ? null : apiEvent.SessionId.Trim(),
|
||||
EntityKey: string.IsNullOrWhiteSpace(apiEvent.EntityKey) ? null : apiEvent.EntityKey.Trim(),
|
||||
Domain: string.IsNullOrWhiteSpace(apiEvent.Domain) ? null : apiEvent.Domain.Trim(),
|
||||
ResultCount: apiEvent.ResultCount,
|
||||
Position: apiEvent.Position,
|
||||
DurationMs: apiEvent.DurationMs));
|
||||
DurationMs: apiEvent.DurationMs,
|
||||
AnswerStatus: string.IsNullOrWhiteSpace(apiEvent.AnswerStatus) ? null : apiEvent.AnswerStatus.Trim(),
|
||||
AnswerCode: string.IsNullOrWhiteSpace(apiEvent.AnswerCode) ? null : apiEvent.AnswerCode.Trim()));
|
||||
}
|
||||
|
||||
if (events.Count > 0)
|
||||
@@ -301,6 +308,8 @@ public sealed record SearchAnalyticsApiEvent
|
||||
|
||||
public string Query { get; init; } = string.Empty;
|
||||
|
||||
public string? SessionId { get; init; }
|
||||
|
||||
public string? EntityKey { get; init; }
|
||||
|
||||
public string? Domain { get; init; }
|
||||
@@ -310,6 +319,10 @@ public sealed record SearchAnalyticsApiEvent
|
||||
public int? Position { get; init; }
|
||||
|
||||
public int? DurationMs { get; init; }
|
||||
|
||||
public string? AnswerStatus { get; init; }
|
||||
|
||||
public string? AnswerCode { get; init; }
|
||||
}
|
||||
|
||||
public sealed record SearchHistoryApiResponse
|
||||
|
||||
@@ -39,11 +39,11 @@ public static class SearchFeedbackEndpoints
|
||||
// G10-002: List quality alerts (admin only)
|
||||
group.MapGet("/quality/alerts", GetAlertsAsync)
|
||||
.WithName("SearchQualityAlertsList")
|
||||
.WithSummary("Lists open search quality alerts (zero-result queries, high negative feedback).")
|
||||
.WithSummary("Lists open search quality alerts (zero-result, high negative feedback, fallback loops, abandoned fallback).")
|
||||
.WithDescription(
|
||||
"Returns search quality alerts ordered by occurrence count. " +
|
||||
"Filterable by status (open, acknowledged, resolved) and alert type " +
|
||||
"(zero_result, low_feedback, high_negative_feedback). Requires admin scope.")
|
||||
"(zero_result, high_negative_feedback, fallback_loop, abandoned_fallback). Requires admin scope.")
|
||||
.RequireAuthorization(AdvisoryAIPolicies.AdminPolicy)
|
||||
.Produces<IReadOnlyList<SearchQualityAlertDto>>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status403Forbidden);
|
||||
@@ -198,6 +198,12 @@ public static class SearchFeedbackEndpoints
|
||||
ZeroResultRate = metrics.ZeroResultRate,
|
||||
AvgResultCount = metrics.AvgResultCount,
|
||||
FeedbackScore = metrics.FeedbackScore,
|
||||
FallbackAnswerRate = metrics.FallbackAnswerRate,
|
||||
ClarifyRate = metrics.ClarifyRate,
|
||||
InsufficientRate = metrics.InsufficientRate,
|
||||
ReformulationCount = metrics.ReformulationCount,
|
||||
RescueActionCount = metrics.RescueActionCount,
|
||||
AbandonedFallbackCount = metrics.AbandonedFallbackCount,
|
||||
Period = metrics.Period,
|
||||
LowQualityResults = metrics.LowQualityResults
|
||||
.Select(row => new SearchLowQualityResultDto
|
||||
@@ -308,6 +314,12 @@ public sealed record SearchQualityMetricsDto
|
||||
public double ZeroResultRate { get; init; }
|
||||
public double AvgResultCount { get; init; }
|
||||
public double FeedbackScore { get; init; }
|
||||
public double FallbackAnswerRate { get; init; }
|
||||
public double ClarifyRate { get; init; }
|
||||
public double InsufficientRate { get; init; }
|
||||
public int ReformulationCount { get; init; }
|
||||
public int RescueActionCount { get; init; }
|
||||
public int AbandonedFallbackCount { get; init; }
|
||||
public string Period { get; init; } = "7d";
|
||||
public IReadOnlyList<SearchLowQualityResultDto> LowQualityResults { get; init; } = [];
|
||||
public IReadOnlyList<SearchTopQueryDto> TopQueries { get; init; } = [];
|
||||
|
||||
Reference in New Issue
Block a user