Auto-rebuild AdvisoryAI knowledge corpus on startup
This commit is contained in:
@@ -15,6 +15,7 @@ using StellaOps.AdvisoryAI.Evidence;
|
||||
using StellaOps.AdvisoryAI.Explanation;
|
||||
using StellaOps.AdvisoryAI.Hosting;
|
||||
using StellaOps.AdvisoryAI.Inference.LlmProviders;
|
||||
using StellaOps.AdvisoryAI.KnowledgeSearch;
|
||||
using StellaOps.AdvisoryAI.Metrics;
|
||||
using StellaOps.AdvisoryAI.Orchestration;
|
||||
using StellaOps.AdvisoryAI.Outputs;
|
||||
@@ -53,6 +54,7 @@ builder.Configuration
|
||||
|
||||
builder.Services.AddAdvisoryAiCore(builder.Configuration);
|
||||
builder.Services.AddUnifiedSearch(builder.Configuration);
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, KnowledgeSearchStartupRebuildService>());
|
||||
|
||||
var llmAdapterEnabled = builder.Configuration.GetValue<bool?>("AdvisoryAI:Adapters:Llm:Enabled") ?? false;
|
||||
if (llmAdapterEnabled)
|
||||
|
||||
@@ -54,6 +54,8 @@ public sealed class KnowledgeSearchOptions
|
||||
|
||||
public List<string> OpenApiRoots { get; set; } = ["src", "devops/compose"];
|
||||
|
||||
public bool KnowledgeAutoIndexOnStartup { get; set; } = true;
|
||||
|
||||
public string UnifiedFindingsSnapshotPath { get; set; } =
|
||||
"src/AdvisoryAI/StellaOps.AdvisoryAI/UnifiedSearch/Snapshots/findings.snapshot.json";
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.KnowledgeSearch;
|
||||
|
||||
internal sealed class KnowledgeSearchStartupRebuildService : IHostedService
|
||||
{
|
||||
private readonly KnowledgeSearchOptions _options;
|
||||
private readonly IKnowledgeIndexer _indexer;
|
||||
private readonly ILogger<KnowledgeSearchStartupRebuildService> _logger;
|
||||
|
||||
public KnowledgeSearchStartupRebuildService(
|
||||
IOptions<KnowledgeSearchOptions> options,
|
||||
IKnowledgeIndexer indexer,
|
||||
ILogger<KnowledgeSearchStartupRebuildService> logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
_options = options.Value ?? new KnowledgeSearchOptions();
|
||||
_indexer = indexer ?? throw new ArgumentNullException(nameof(indexer));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_options.Enabled)
|
||||
{
|
||||
_logger.LogDebug("AdvisoryAI knowledge search is disabled; skipping startup rebuild.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_options.KnowledgeAutoIndexOnStartup)
|
||||
{
|
||||
_logger.LogDebug("AdvisoryAI knowledge startup rebuild is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var summary = await _indexer.RebuildAsync(cancellationToken).ConfigureAwait(false);
|
||||
_logger.LogInformation(
|
||||
"AdvisoryAI knowledge startup rebuild completed: documents={DocumentCount}, chunks={ChunkCount}, api_specs={ApiSpecCount}, api_operations={ApiOperationCount}, doctor_projections={DoctorProjectionCount}, duration_ms={DurationMs}",
|
||||
summary.DocumentCount,
|
||||
summary.ChunkCount,
|
||||
summary.ApiSpecCount,
|
||||
summary.ApiOperationCount,
|
||||
summary.DoctorProjectionCount,
|
||||
summary.DurationMs);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning(ex, "AdvisoryAI knowledge startup rebuild failed.");
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.AdvisoryAI.KnowledgeSearch;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Tests.KnowledgeSearch;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class KnowledgeSearchStartupRebuildServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task StartAsync_rebuilds_knowledge_index_when_enabled()
|
||||
{
|
||||
var indexer = new RecordingKnowledgeIndexer();
|
||||
var service = new KnowledgeSearchStartupRebuildService(
|
||||
Options.Create(new KnowledgeSearchOptions
|
||||
{
|
||||
Enabled = true,
|
||||
KnowledgeAutoIndexOnStartup = true,
|
||||
}),
|
||||
indexer,
|
||||
NullLogger<KnowledgeSearchStartupRebuildService>.Instance);
|
||||
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(1, indexer.RebuildCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_skips_rebuild_when_startup_bootstrap_is_disabled()
|
||||
{
|
||||
var indexer = new RecordingKnowledgeIndexer();
|
||||
var service = new KnowledgeSearchStartupRebuildService(
|
||||
Options.Create(new KnowledgeSearchOptions
|
||||
{
|
||||
Enabled = true,
|
||||
KnowledgeAutoIndexOnStartup = false,
|
||||
}),
|
||||
indexer,
|
||||
NullLogger<KnowledgeSearchStartupRebuildService>.Instance);
|
||||
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, indexer.RebuildCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_skips_rebuild_when_knowledge_search_is_disabled()
|
||||
{
|
||||
var indexer = new RecordingKnowledgeIndexer();
|
||||
var service = new KnowledgeSearchStartupRebuildService(
|
||||
Options.Create(new KnowledgeSearchOptions
|
||||
{
|
||||
Enabled = false,
|
||||
KnowledgeAutoIndexOnStartup = true,
|
||||
}),
|
||||
indexer,
|
||||
NullLogger<KnowledgeSearchStartupRebuildService>.Instance);
|
||||
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, indexer.RebuildCallCount);
|
||||
}
|
||||
|
||||
private sealed class RecordingKnowledgeIndexer : IKnowledgeIndexer
|
||||
{
|
||||
public int RebuildCallCount { get; private set; }
|
||||
|
||||
public Task<KnowledgeRebuildSummary> RebuildAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
RebuildCallCount++;
|
||||
return Task.FromResult(new KnowledgeRebuildSummary(
|
||||
DocumentCount: 470,
|
||||
ChunkCount: 9050,
|
||||
ApiSpecCount: 1,
|
||||
ApiOperationCount: 2190,
|
||||
DoctorProjectionCount: 8,
|
||||
DurationMs: 42));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user