< Summary

Information
Class: Orchestrator.Infrastructure.Factories.OpenAiServiceFactory
Assembly: Orchestrator
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Infrastructure/Factories/OpenAiServiceFactory.cs
Line coverage
100%
Covered lines: 44
Uncovered lines: 0
Coverable lines: 44
Total lines: 110
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
CreatePredictionService(...)100%11100%
GetTokenUsageTracker()100%44100%
GetApiKeyFromEnvironment()100%22100%
GetOrCreateCostCalculationService()100%44100%
GetOrCreateInstructionsTemplateProvider()100%44100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Infrastructure/Factories/OpenAiServiceFactory.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using EHonda.KicktippAi.Core;
 3using Microsoft.Extensions.Logging;
 4using OpenAI.Chat;
 5using OpenAiIntegration;
 6
 7namespace Orchestrator.Infrastructure.Factories;
 8
 9/// <summary>
 10/// Default implementation of <see cref="IOpenAiServiceFactory"/>.
 11/// </summary>
 12/// <remarks>
 13/// Reads the API key from OPENAI_API_KEY environment variable.
 14/// Caches services by model to avoid recreating them for repeated requests.
 15/// The <see cref="ITokenUsageTracker"/> is shared across all prediction services.
 16/// </remarks>
 17public sealed class OpenAiServiceFactory : IOpenAiServiceFactory
 18{
 19    private readonly ILoggerFactory _loggerFactory;
 20    private readonly Lazy<string> _apiKey;
 121    private readonly ConcurrentDictionary<string, IPredictionService> _predictionServiceCache = new();
 22    private ITokenUsageTracker? _tokenUsageTracker;
 23    private ICostCalculationService? _costCalculationService;
 24    private IInstructionsTemplateProvider? _instructionsTemplateProvider;
 125    private readonly object _lock = new();
 26
 127    public OpenAiServiceFactory(ILoggerFactory loggerFactory)
 28    {
 129        _loggerFactory = loggerFactory;
 130        _apiKey = new Lazy<string>(GetApiKeyFromEnvironment);
 131    }
 32
 33    /// <inheritdoc />
 34    public IPredictionService CreatePredictionService(string model)
 35    {
 136        ArgumentException.ThrowIfNullOrWhiteSpace(model);
 37
 138        var apiKey = _apiKey.Value;
 39
 40        // Cache key includes model to handle different configurations
 141        return _predictionServiceCache.GetOrAdd(model, _ =>
 142        {
 143            var logger = _loggerFactory.CreateLogger<PredictionService>();
 144            var chatClient = new ChatClient(model, apiKey);
 145
 146            return new PredictionService(
 147                chatClient,
 148                logger,
 149                GetOrCreateCostCalculationService(),
 150                GetTokenUsageTracker(),
 151                GetOrCreateInstructionsTemplateProvider(),
 152                model);
 153        });
 54    }
 55
 56    /// <inheritdoc />
 57    public ITokenUsageTracker GetTokenUsageTracker()
 58    {
 159        if (_tokenUsageTracker == null)
 60        {
 161            lock (_lock)
 62            {
 163                _tokenUsageTracker ??= new TokenUsageTracker(
 164                    _loggerFactory.CreateLogger<TokenUsageTracker>(),
 165                    GetOrCreateCostCalculationService());
 166            }
 67        }
 68
 169        return _tokenUsageTracker;
 70    }
 71
 72    private static string GetApiKeyFromEnvironment()
 73    {
 174        var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
 175        if (string.IsNullOrWhiteSpace(apiKey))
 76        {
 177            throw new InvalidOperationException("OPENAI_API_KEY environment variable is required");
 78        }
 79
 180        return apiKey;
 81    }
 82
 83    private ICostCalculationService GetOrCreateCostCalculationService()
 84    {
 185        if (_costCalculationService == null)
 86        {
 187            lock (_lock)
 88            {
 189                _costCalculationService ??= new CostCalculationService(
 190                    _loggerFactory.CreateLogger<CostCalculationService>());
 191            }
 92        }
 93
 194        return _costCalculationService;
 95    }
 96
 97    private IInstructionsTemplateProvider GetOrCreateInstructionsTemplateProvider()
 98    {
 199        if (_instructionsTemplateProvider == null)
 100        {
 1101            lock (_lock)
 102            {
 1103                _instructionsTemplateProvider ??= new InstructionsTemplateProvider(
 1104                    PromptsFileProvider.Create());
 1105            }
 106        }
 107
 1108        return _instructionsTemplateProvider;
 109    }
 110}