search and ai stabilization work, localization stablized.
This commit is contained in:
@@ -33,7 +33,10 @@ internal static class KnowledgeSearchCommandGroup
|
||||
{
|
||||
"docs",
|
||||
"api",
|
||||
"doctor"
|
||||
"doctor",
|
||||
"findings",
|
||||
"vex",
|
||||
"policy"
|
||||
};
|
||||
|
||||
internal static Command BuildSearchCommand(
|
||||
@@ -329,6 +332,32 @@ internal static class KnowledgeSearchCommandGroup
|
||||
};
|
||||
|
||||
var backend = services.GetRequiredService<IBackendOperationsClient>();
|
||||
|
||||
// Try unified search endpoint first (covers all domains)
|
||||
var unifiedResult = await TryUnifiedSearchAsync(
|
||||
backend, normalizedQuery, normalizedTypes, normalizedTags,
|
||||
product, version, service, boundedTopK, verbose,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (unifiedResult is not null)
|
||||
{
|
||||
if (emitJson)
|
||||
{
|
||||
WriteJson(ToUnifiedJsonPayload(unifiedResult));
|
||||
return;
|
||||
}
|
||||
|
||||
if (suggestMode)
|
||||
{
|
||||
RenderUnifiedSuggestionOutput(unifiedResult, verbose);
|
||||
return;
|
||||
}
|
||||
|
||||
RenderUnifiedSearchOutput(unifiedResult, verbose);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to legacy knowledge search
|
||||
AdvisoryKnowledgeSearchResponseModel response;
|
||||
try
|
||||
{
|
||||
@@ -1281,4 +1310,194 @@ internal static class KnowledgeSearchCommandGroup
|
||||
{
|
||||
Console.WriteLine(JsonSerializer.Serialize(payload, JsonOutputOptions));
|
||||
}
|
||||
|
||||
private static async Task<UnifiedSearchResponseModel?> TryUnifiedSearchAsync(
|
||||
IBackendOperationsClient backend,
|
||||
string query,
|
||||
IReadOnlyList<string> types,
|
||||
IReadOnlyList<string> tags,
|
||||
string? product,
|
||||
string? version,
|
||||
string? service,
|
||||
int? topK,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var domains = MapTypesToDomains(types);
|
||||
var request = new UnifiedSearchRequestModel
|
||||
{
|
||||
Q = query,
|
||||
K = topK,
|
||||
Filters = new UnifiedSearchFilterModel
|
||||
{
|
||||
Domains = domains.Count > 0 ? domains : null,
|
||||
Product = product,
|
||||
Version = version,
|
||||
Service = service,
|
||||
Tags = tags.Count > 0 ? tags : null
|
||||
},
|
||||
IncludeSynthesis = true,
|
||||
IncludeDebug = verbose
|
||||
};
|
||||
|
||||
return await backend.SearchUnifiedAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> MapTypesToDomains(IReadOnlyList<string> types)
|
||||
{
|
||||
if (types.Count == 0) return [];
|
||||
var domains = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var type in types)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "docs":
|
||||
case "api":
|
||||
case "doctor":
|
||||
domains.Add("knowledge");
|
||||
break;
|
||||
case "findings":
|
||||
domains.Add("findings");
|
||||
break;
|
||||
case "vex":
|
||||
domains.Add("vex");
|
||||
break;
|
||||
case "policy":
|
||||
domains.Add("policy");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return domains.ToArray();
|
||||
}
|
||||
|
||||
private static void RenderUnifiedSearchOutput(UnifiedSearchResponseModel response, bool verbose)
|
||||
{
|
||||
Console.WriteLine($"Query: {response.Query}");
|
||||
Console.WriteLine($"Results: {response.Cards.Count.ToString(CultureInfo.InvariantCulture)} cards / topK {response.TopK.ToString(CultureInfo.InvariantCulture)}");
|
||||
Console.WriteLine($"Mode: {response.Diagnostics.Mode} (fts={response.Diagnostics.FtsMatches.ToString(CultureInfo.InvariantCulture)}, vector={response.Diagnostics.VectorMatches.ToString(CultureInfo.InvariantCulture)}, duration={response.Diagnostics.DurationMs.ToString(CultureInfo.InvariantCulture)}ms)");
|
||||
Console.WriteLine();
|
||||
|
||||
if (response.Synthesis is not null)
|
||||
{
|
||||
Console.WriteLine($"Summary ({response.Synthesis.Confidence} confidence):");
|
||||
Console.WriteLine($" {response.Synthesis.Summary}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
if (response.Cards.Count == 0)
|
||||
{
|
||||
Console.WriteLine("No results found.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var index = 0; index < response.Cards.Count; index++)
|
||||
{
|
||||
var card = response.Cards[index];
|
||||
var severity = string.IsNullOrWhiteSpace(card.Severity)
|
||||
? string.Empty
|
||||
: $" severity={card.Severity}";
|
||||
Console.WriteLine($"[{(index + 1).ToString(CultureInfo.InvariantCulture)}] {card.Domain.ToUpperInvariant()}/{card.EntityType.ToUpperInvariant()} score={card.Score.ToString("F6", CultureInfo.InvariantCulture)}{severity}");
|
||||
Console.WriteLine($" {card.Title}");
|
||||
var snippet = CollapseWhitespace(card.Snippet);
|
||||
if (!string.IsNullOrWhiteSpace(snippet))
|
||||
{
|
||||
Console.WriteLine($" {snippet}");
|
||||
}
|
||||
|
||||
foreach (var action in card.Actions)
|
||||
{
|
||||
var actionDetail = action.IsPrimary ? " [primary]" : "";
|
||||
if (!string.IsNullOrWhiteSpace(action.Route))
|
||||
{
|
||||
Console.WriteLine($" -> {action.Label}: {action.Route}{actionDetail}");
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(action.Command))
|
||||
{
|
||||
Console.WriteLine($" -> {action.Label}: {action.Command}{actionDetail}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private static void RenderUnifiedSuggestionOutput(UnifiedSearchResponseModel response, bool verbose)
|
||||
{
|
||||
Console.WriteLine($"Symptom: {response.Query}");
|
||||
Console.WriteLine($"Mode: {response.Diagnostics.Mode} (duration={response.Diagnostics.DurationMs.ToString(CultureInfo.InvariantCulture)}ms)");
|
||||
Console.WriteLine();
|
||||
|
||||
if (response.Synthesis is not null)
|
||||
{
|
||||
Console.WriteLine($"Analysis ({response.Synthesis.Confidence}):");
|
||||
Console.WriteLine($" {response.Synthesis.Summary}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
var byDomain = response.Cards
|
||||
.GroupBy(static c => c.Domain, StringComparer.Ordinal)
|
||||
.OrderBy(static g => g.Key, StringComparer.Ordinal);
|
||||
|
||||
foreach (var group in byDomain)
|
||||
{
|
||||
Console.WriteLine($"{group.Key.ToUpperInvariant()} results:");
|
||||
var items = group.ToArray();
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
var card = items[i];
|
||||
Console.WriteLine($" {(i + 1).ToString(CultureInfo.InvariantCulture)}. {card.Title} (score={card.Score.ToString("F6", CultureInfo.InvariantCulture)})");
|
||||
var snippet = CollapseWhitespace(card.Snippet);
|
||||
if (!string.IsNullOrWhiteSpace(snippet))
|
||||
{
|
||||
Console.WriteLine($" {snippet}");
|
||||
}
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private static object ToUnifiedJsonPayload(UnifiedSearchResponseModel response)
|
||||
{
|
||||
return new
|
||||
{
|
||||
query = response.Query,
|
||||
topK = response.TopK,
|
||||
diagnostics = new
|
||||
{
|
||||
ftsMatches = response.Diagnostics.FtsMatches,
|
||||
vectorMatches = response.Diagnostics.VectorMatches,
|
||||
entityCardCount = response.Diagnostics.EntityCardCount,
|
||||
durationMs = response.Diagnostics.DurationMs,
|
||||
usedVector = response.Diagnostics.UsedVector,
|
||||
mode = response.Diagnostics.Mode
|
||||
},
|
||||
synthesis = response.Synthesis is null ? null : new
|
||||
{
|
||||
summary = response.Synthesis.Summary,
|
||||
template = response.Synthesis.Template,
|
||||
confidence = response.Synthesis.Confidence,
|
||||
sourceCount = response.Synthesis.SourceCount,
|
||||
domainsCovered = response.Synthesis.DomainsCovered
|
||||
},
|
||||
cards = response.Cards.Select(static card => new
|
||||
{
|
||||
entityKey = card.EntityKey,
|
||||
entityType = card.EntityType,
|
||||
domain = card.Domain,
|
||||
title = card.Title,
|
||||
snippet = card.Snippet,
|
||||
score = card.Score,
|
||||
severity = card.Severity,
|
||||
actions = card.Actions.Select(static action => new
|
||||
{
|
||||
label = action.Label,
|
||||
actionType = action.ActionType,
|
||||
route = action.Route,
|
||||
command = action.Command,
|
||||
isPrimary = action.IsPrimary
|
||||
}).ToArray(),
|
||||
sources = card.Sources
|
||||
}).ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user