< Summary

Information
Class: Orchestrator.Commands.Observability.Experiments.RunExperimentSettingsBase
Assembly: Orchestrator
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Observability/Experiments/RunSliceSettings.cs
Line coverage
81%
Covered lines: 65
Uncovered lines: 15
Coverable lines: 80
Total lines: 260
Line coverage: 81.2%
Branch coverage
62%
Covered branches: 31
Total branches: 50
Branch coverage: 62%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
ValidateCommon()63.64%1264465.12%
CreateRunOptions(...)50%66100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Observability/Experiments/RunSliceSettings.cs

#LineLine coverage
 1using System.ComponentModel;
 2using Spectre.Console;
 3using Spectre.Console.Cli;
 4
 5namespace Orchestrator.Commands.Observability.Experiments;
 6
 7public abstract class RunExperimentSettingsBase : CommandSettings
 8{
 19    private static readonly HashSet<string> AllowedReasoningEfforts = new(StringComparer.Ordinal)
 110    {
 111        "none",
 112        "minimal",
 113        "low",
 114        "medium",
 115        "high",
 116        "xhigh"
 117    };
 18
 19    [CommandArgument(0, "<MODEL>")]
 20    [Description("The model to execute for the experiment run")]
 121    public string Model { get; set; } = string.Empty;
 22
 23    [CommandOption("--manifest")]
 24    [Description("Path to the prepared experiment manifest JSON file")]
 125    public string ManifestPath { get; set; } = string.Empty;
 26
 27    [CommandOption("--run-name")]
 28    [Description("Langfuse dataset run name")]
 129    public string RunName { get; set; } = string.Empty;
 30
 31    [CommandOption("--run-description")]
 32    [Description("Optional Langfuse dataset run description")]
 33    public string? RunDescription { get; set; }
 34
 35    [CommandOption("--run-metadata-file")]
 36    [Description("Optional path to an experiment run metadata JSON file. When omitted, metadata is built from the manife
 37    public string? RunMetadataFile { get; set; }
 38
 39    [CommandOption("--prompt-key")]
 40    [Description("Prompt variant identifier used in run metadata and trace tags")]
 41    [DefaultValue("prompt-v1")]
 142    public string PromptKey { get; set; } = "prompt-v1";
 43
 44    [CommandOption("--prompt-source")]
 45    [Description("Prompt source for experiment predictions: local or langfuse")]
 46    [DefaultValue("local")]
 147    public string PromptSource { get; set; } = "local";
 48
 49    [CommandOption("--langfuse-prompt-name")]
 50    [Description("Langfuse hosted prompt name when --prompt-source langfuse is used")]
 51    public string? LangfusePromptName { get; set; }
 52
 53    [CommandOption("--langfuse-prompt-label")]
 54    [Description("Langfuse hosted prompt label when --prompt-source langfuse is used")]
 55    [DefaultValue("production")]
 156    public string? LangfusePromptLabel { get; set; } = "production";
 57
 58    [CommandOption("--langfuse-prompt-version")]
 59    [Description("Optional Langfuse hosted prompt version when --prompt-source langfuse is used")]
 60    public int? LangfusePromptVersion { get; set; }
 61
 62    [CommandOption("--reasoning-effort")]
 63    [Description("Optional OpenAI reasoning effort for experiment predictions: none, minimal, low, medium, high, or xhig
 64    public string? ReasoningEffort { get; set; }
 65
 66    [CommandOption("--max-output-tokens")]
 67    [Description("Maximum OpenAI output tokens per prediction. Defaults to 10000")]
 68    public int? MaxOutputTokenCount { get; set; }
 69
 70    [CommandOption("--include-justification")]
 71    [Description("Use the justification prompt variant when reconstructing historical prompts")]
 72    [DefaultValue(false)]
 73    public bool IncludeJustification { get; set; }
 74
 75    [CommandOption("--evaluation-time")]
 76    [Description("Optional exact evaluation time in NodaTime invariant ZonedDateTime 'G' format, for example '2026-03-15
 77    public string? EvaluationTime { get; set; }
 78
 79    [CommandOption("--evaluation-policy-kind")]
 80    [Description("Optional evaluation policy kind. Defaults to 'relative' when no run metadata file or exact evaluation 
 81    public string? EvaluationPolicyKind { get; set; }
 82
 83    [CommandOption("--evaluation-policy-offset")]
 84    [Description("Optional evaluation policy offset. Defaults to '-12:00:00' when no run metadata file or exact evaluati
 85    public string? EvaluationPolicyOffset { get; set; }
 86
 87    [CommandOption("--dataset-name")]
 88    [Description("Optional hosted dataset name override")]
 89    public string? DatasetName { get; set; }
 90
 91    [CommandOption("--replace-run")]
 92    [Description("Delete an existing dataset run with the same name before starting")]
 93    [DefaultValue(false)]
 94    public bool ReplaceRun { get; set; }
 95
 96    protected ValidationResult ValidateCommon()
 97    {
 198        if (string.IsNullOrWhiteSpace(Model))
 99        {
 0100            return ValidationResult.Error("Model is required");
 101        }
 102
 1103        if (string.IsNullOrWhiteSpace(ManifestPath))
 104        {
 0105            return ValidationResult.Error("--manifest is required");
 106        }
 107
 1108        if (string.IsNullOrWhiteSpace(RunName))
 109        {
 0110            return ValidationResult.Error("--run-name is required");
 111        }
 112
 1113        if (string.IsNullOrWhiteSpace(PromptKey))
 114        {
 0115            return ValidationResult.Error("--prompt-key must be a non-empty string");
 116        }
 117
 1118        if (!string.IsNullOrWhiteSpace(ReasoningEffort))
 119        {
 1120            var normalizedReasoningEffort = ReasoningEffort.Trim().ToLowerInvariant();
 1121            if (!AllowedReasoningEfforts.Contains(normalizedReasoningEffort))
 122            {
 1123                return ValidationResult.Error("--reasoning-effort must be one of: none, minimal, low, medium, high, xhig
 124            }
 125
 1126            ReasoningEffort = normalizedReasoningEffort;
 127        }
 128
 1129        if (MaxOutputTokenCount is < 1)
 130        {
 1131            return ValidationResult.Error("--max-output-tokens must be at least 1 when provided");
 132        }
 133
 1134        var normalizedPromptSource = PromptSource.Trim().ToLowerInvariant();
 1135        if (normalizedPromptSource is not ("local" or "langfuse"))
 136        {
 0137            return ValidationResult.Error("--prompt-source must be either 'local' or 'langfuse'");
 138        }
 139
 1140        if (normalizedPromptSource == "langfuse")
 141        {
 1142            if (IncludeJustification)
 143            {
 1144                return ValidationResult.Error("--prompt-source langfuse does not support --include-justification in this
 145            }
 146
 1147            if (string.IsNullOrWhiteSpace(LangfusePromptName))
 148            {
 1149                return ValidationResult.Error("--langfuse-prompt-name is required when --prompt-source langfuse is used"
 150            }
 151
 0152            if (LangfusePromptVersion is < 1)
 153            {
 0154                return ValidationResult.Error("--langfuse-prompt-version must be at least 1 when provided");
 155            }
 156        }
 1157        else if (!string.IsNullOrWhiteSpace(LangfusePromptName) || LangfusePromptVersion is not null)
 158        {
 0159            return ValidationResult.Error("Langfuse prompt options require --prompt-source langfuse");
 160        }
 161
 1162        var hasEvaluationPolicyKind = !string.IsNullOrWhiteSpace(EvaluationPolicyKind);
 1163        var hasEvaluationPolicyOffset = !string.IsNullOrWhiteSpace(EvaluationPolicyOffset);
 164
 1165        if (hasEvaluationPolicyKind != hasEvaluationPolicyOffset)
 166        {
 0167            return ValidationResult.Error("--evaluation-policy-kind and --evaluation-policy-offset must be provided toge
 168        }
 169
 1170        if (!string.IsNullOrWhiteSpace(EvaluationTime) && hasEvaluationPolicyKind)
 171        {
 0172            return ValidationResult.Error("--evaluation-time cannot be combined with --evaluation-policy-kind/--evaluati
 173        }
 174
 1175        if (!string.IsNullOrWhiteSpace(EvaluationTime))
 176        {
 177            try
 178            {
 1179                _ = Commands.Observability.EvaluationTimeParser.Parse(EvaluationTime);
 1180            }
 181            catch (ArgumentException ex)
 182            {
 0183                return ValidationResult.Error(ex.Message);
 184            }
 185        }
 186
 1187        if (hasEvaluationPolicyKind)
 188        {
 189            try
 190            {
 0191                _ = Commands.Observability.EvaluationTimestampPolicyParser.Parse(EvaluationPolicyKind, EvaluationPolicyO
 0192            }
 193            catch (ArgumentException ex)
 194            {
 0195                return ValidationResult.Error(ex.Message);
 196            }
 197        }
 198
 1199        return ValidationResult.Success();
 0200    }
 201
 202    private protected PreparedExperimentRunOptions CreateRunOptions(
 203        string batchStrategy,
 204        int? batchSize = null,
 205        int? batchCount = null,
 206        int? parallelism = null)
 207    {
 1208        var normalizedPromptSource = PromptSource.Trim().ToLowerInvariant();
 1209        var langfusePromptName = normalizedPromptSource == "langfuse" ? LangfusePromptName : null;
 1210        var langfusePromptLabel = normalizedPromptSource == "langfuse" ? LangfusePromptLabel : null;
 1211        var langfusePromptVersion = normalizedPromptSource == "langfuse" ? LangfusePromptVersion : null;
 212
 1213        return new PreparedExperimentRunOptions(
 1214            Model,
 1215            PromptKey,
 1216            IncludeJustification,
 1217            EvaluationTime,
 1218            EvaluationPolicyKind,
 1219            EvaluationPolicyOffset,
 1220            DatasetName,
 1221            normalizedPromptSource,
 1222            langfusePromptName,
 1223            langfusePromptLabel,
 1224            langfusePromptVersion,
 1225            batchStrategy,
 1226            batchSize,
 1227            batchCount,
 1228            ReasoningEffort,
 1229            MaxOutputTokenCount,
 1230            parallelism);
 231    }
 232}
 233
 234public sealed class RunSliceSettings : RunExperimentSettingsBase
 235{
 236    [CommandOption("--batch-size")]
 237    [Description("Optional batch size override")]
 238    public int? BatchSize { get; set; }
 239
 240    public override ValidationResult Validate()
 241    {
 242        var commonValidation = ValidateCommon();
 243        if (!commonValidation.Successful)
 244        {
 245            return commonValidation;
 246        }
 247
 248        if (BatchSize is < 1)
 249        {
 250            return ValidationResult.Error("--batch-size must be at least 1 when provided");
 251        }
 252
 253        return ValidationResult.Success();
 254    }
 255
 256    internal PreparedExperimentRunOptions ToRunOptions()
 257    {
 258        return CreateRunOptions("simple-batched", BatchSize);
 259    }
 260}