Implement MongoDB-based storage for Pack Run approval, artifact, log, and state management
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added MongoPackRunApprovalStore for managing approval states with MongoDB. - Introduced MongoPackRunArtifactUploader for uploading and storing artifacts. - Created MongoPackRunLogStore to handle logging of pack run events. - Developed MongoPackRunStateStore for persisting and retrieving pack run states. - Implemented unit tests for MongoDB stores to ensure correct functionality. - Added MongoTaskRunnerTestContext for setting up MongoDB test environment. - Enhanced PackRunStateFactory to correctly initialize state with gate reasons.
This commit is contained in:
@@ -1,27 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using StellaOps.AdvisoryAI.Guardrails;
|
||||
using StellaOps.AdvisoryAI.Orchestration;
|
||||
using StellaOps.AdvisoryAI.Outputs;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.WebService.Contracts;
|
||||
|
||||
public sealed record AdvisoryOutputResponse(
|
||||
internal sealed record AdvisoryOutputResponse(
|
||||
string CacheKey,
|
||||
AdvisoryTaskType TaskType,
|
||||
string TaskType,
|
||||
string Profile,
|
||||
string OutputHash,
|
||||
bool GuardrailBlocked,
|
||||
IReadOnlyCollection<AdvisoryGuardrailViolationResponse> GuardrailViolations,
|
||||
IReadOnlyDictionary<string, string> GuardrailMetadata,
|
||||
string Prompt,
|
||||
IReadOnlyCollection<AdvisoryCitationResponse> Citations,
|
||||
IReadOnlyList<AdvisoryOutputCitation> Citations,
|
||||
IReadOnlyDictionary<string, string> Metadata,
|
||||
AdvisoryOutputGuardrail Guardrail,
|
||||
AdvisoryOutputProvenance Provenance,
|
||||
DateTimeOffset GeneratedAtUtc,
|
||||
bool PlanFromCache);
|
||||
|
||||
public sealed record AdvisoryGuardrailViolationResponse(string Code, string Message)
|
||||
bool PlanFromCache)
|
||||
{
|
||||
public static AdvisoryGuardrailViolationResponse From(AdvisoryGuardrailViolation violation)
|
||||
=> new(violation.Code, violation.Message);
|
||||
public static AdvisoryOutputResponse FromDomain(AdvisoryPipelineOutput output)
|
||||
=> new(
|
||||
output.CacheKey,
|
||||
output.TaskType.ToString(),
|
||||
output.Profile,
|
||||
output.Prompt,
|
||||
output.Citations
|
||||
.Select(citation => new AdvisoryOutputCitation(citation.Index, citation.DocumentId, citation.ChunkId))
|
||||
.ToList(),
|
||||
output.Metadata.ToDictionary(static pair => pair.Key, static pair => pair.Value, StringComparer.Ordinal),
|
||||
AdvisoryOutputGuardrail.FromDomain(output.Guardrail),
|
||||
AdvisoryOutputProvenance.FromDomain(output.Provenance),
|
||||
output.GeneratedAtUtc,
|
||||
output.PlanFromCache);
|
||||
}
|
||||
|
||||
public sealed record AdvisoryCitationResponse(int Index, string DocumentId, string ChunkId);
|
||||
internal sealed record AdvisoryOutputCitation(int Index, string DocumentId, string ChunkId);
|
||||
|
||||
internal sealed record AdvisoryOutputGuardrail(
|
||||
bool Blocked,
|
||||
string SanitizedPrompt,
|
||||
IReadOnlyList<AdvisoryOutputGuardrailViolation> Violations,
|
||||
IReadOnlyDictionary<string, string> Metadata)
|
||||
{
|
||||
public static AdvisoryOutputGuardrail FromDomain(AdvisoryGuardrailResult result)
|
||||
=> new(
|
||||
result.Blocked,
|
||||
result.SanitizedPrompt,
|
||||
result.Violations
|
||||
.Select(violation => new AdvisoryOutputGuardrailViolation(violation.Code, violation.Message))
|
||||
.ToList(),
|
||||
result.Metadata.ToDictionary(static pair => pair.Key, static pair => pair.Value, StringComparer.Ordinal));
|
||||
}
|
||||
|
||||
internal sealed record AdvisoryOutputGuardrailViolation(string Code, string Message);
|
||||
|
||||
internal sealed record AdvisoryOutputProvenance(
|
||||
string InputDigest,
|
||||
string OutputHash,
|
||||
IReadOnlyList<string> Signatures)
|
||||
{
|
||||
public static AdvisoryOutputProvenance FromDomain(AdvisoryDsseProvenance provenance)
|
||||
=> new(
|
||||
provenance.InputDigest,
|
||||
provenance.OutputHash,
|
||||
provenance.Signatures.ToArray());
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.RateLimiting;
|
||||
@@ -9,10 +10,13 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.AdvisoryAI.Caching;
|
||||
using StellaOps.AdvisoryAI.Diagnostics;
|
||||
using StellaOps.AdvisoryAI.Hosting;
|
||||
using StellaOps.AdvisoryAI.Metrics;
|
||||
using StellaOps.AdvisoryAI.Outputs;
|
||||
using StellaOps.AdvisoryAI.Orchestration;
|
||||
using StellaOps.AdvisoryAI.Queue;
|
||||
using StellaOps.AdvisoryAI.WebService.Contracts;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -72,6 +76,9 @@ app.MapPost("/v1/advisory-ai/pipeline/{taskType}", HandleSinglePlan)
|
||||
app.MapPost("/v1/advisory-ai/pipeline:batch", HandleBatchPlans)
|
||||
.RequireRateLimiting("advisory-ai");
|
||||
|
||||
app.MapGet("/v1/advisory-ai/outputs/{cacheKey}", HandleGetOutput)
|
||||
.RequireRateLimiting("advisory-ai");
|
||||
|
||||
app.Run();
|
||||
|
||||
static async Task<IResult> HandleSinglePlan(
|
||||
@@ -85,6 +92,10 @@ static async Task<IResult> HandleSinglePlan(
|
||||
AdvisoryPipelineMetrics pipelineMetrics,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using var activity = AdvisoryAiActivitySource.Instance.StartActivity("advisory_ai.plan_request", ActivityKind.Server);
|
||||
activity?.SetTag("advisory.task_type", taskType);
|
||||
activity?.SetTag("advisory.advisory_key", request.AdvisoryKey);
|
||||
|
||||
if (!Enum.TryParse<AdvisoryTaskType>(taskType, ignoreCase: true, out var parsedType))
|
||||
{
|
||||
return Results.BadRequest(new { error = $"Unknown task type '{taskType}'." });
|
||||
@@ -103,6 +114,7 @@ static async Task<IResult> HandleSinglePlan(
|
||||
var normalizedRequest = request with { TaskType = parsedType };
|
||||
var taskRequest = normalizedRequest.ToTaskRequest();
|
||||
var plan = await orchestrator.CreatePlanAsync(taskRequest, cancellationToken).ConfigureAwait(false);
|
||||
activity?.SetTag("advisory.plan_cache_key", plan.CacheKey);
|
||||
|
||||
await planCache.SetAsync(plan.CacheKey, plan, cancellationToken).ConfigureAwait(false);
|
||||
await taskQueue.EnqueueAsync(new AdvisoryTaskQueueMessage(plan.CacheKey, plan.Request), cancellationToken).ConfigureAwait(false);
|
||||
@@ -125,6 +137,9 @@ static async Task<IResult> HandleBatchPlans(
|
||||
AdvisoryPipelineMetrics pipelineMetrics,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using var activity = AdvisoryAiActivitySource.Instance.StartActivity("advisory_ai.plan_batch", ActivityKind.Server);
|
||||
activity?.SetTag("advisory.batch_size", batchRequest.Requests.Count);
|
||||
|
||||
if (batchRequest.Requests.Count == 0)
|
||||
{
|
||||
return Results.BadRequest(new { error = "At least one request must be supplied." });
|
||||
@@ -153,6 +168,12 @@ static async Task<IResult> HandleBatchPlans(
|
||||
var normalizedRequest = item with { TaskType = parsedType };
|
||||
var taskRequest = normalizedRequest.ToTaskRequest();
|
||||
var plan = await orchestrator.CreatePlanAsync(taskRequest, cancellationToken).ConfigureAwait(false);
|
||||
activity?.AddEvent(new ActivityEvent("advisory.plan.created", tags: new ActivityTagsCollection
|
||||
{
|
||||
{ "advisory.task_type", plan.Request.TaskType.ToString() },
|
||||
{ "advisory.advisory_key", plan.Request.AdvisoryKey },
|
||||
{ "advisory.plan_cache_key", plan.CacheKey }
|
||||
}));
|
||||
|
||||
await planCache.SetAsync(plan.CacheKey, plan, cancellationToken).ConfigureAwait(false);
|
||||
await taskQueue.EnqueueAsync(new AdvisoryTaskQueueMessage(plan.CacheKey, plan.Request), cancellationToken).ConfigureAwait(false);
|
||||
@@ -167,6 +188,37 @@ static async Task<IResult> HandleBatchPlans(
|
||||
return Results.Ok(results);
|
||||
}
|
||||
|
||||
static async Task<IResult> HandleGetOutput(
|
||||
HttpContext httpContext,
|
||||
string cacheKey,
|
||||
string taskType,
|
||||
string? profile,
|
||||
IAdvisoryOutputStore outputStore,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(outputStore);
|
||||
if (!Enum.TryParse<AdvisoryTaskType>(taskType, ignoreCase: true, out var parsedTaskType))
|
||||
{
|
||||
return Results.BadRequest(new { error = $"Unknown task type '{taskType}'." });
|
||||
}
|
||||
|
||||
if (!EnsureAuthorized(httpContext, parsedTaskType))
|
||||
{
|
||||
return Results.StatusCode(StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
var resolvedProfile = string.IsNullOrWhiteSpace(profile) ? "default" : profile!.Trim();
|
||||
var output = await outputStore.TryGetAsync(cacheKey, parsedTaskType, resolvedProfile, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (output is null)
|
||||
{
|
||||
return Results.NotFound(new { error = "Output not found." });
|
||||
}
|
||||
|
||||
return Results.Ok(AdvisoryOutputResponse.FromDomain(output));
|
||||
}
|
||||
|
||||
static bool EnsureAuthorized(HttpContext context, AdvisoryTaskType taskType)
|
||||
{
|
||||
if (!context.Request.Headers.TryGetValue("X-StellaOps-Scopes", out var scopes))
|
||||
|
||||
Reference in New Issue
Block a user