diff --git a/OpenAI_API/APIAuthentication.cs b/OpenAI_API/APIAuthentication.cs index 4346281..7a739c1 100644 --- a/OpenAI_API/APIAuthentication.cs +++ b/OpenAI_API/APIAuthentication.cs @@ -157,34 +157,6 @@ public static APIAuthentication LoadFromPath(string directory = null, string fil return new APIAuthentication(key, org); } - - - /// - /// Tests the api key against the OpenAI API, to ensure it is valid. This hits the models endpoint so should not be charged for usage. - /// - /// if the api key is valid, or if empty or not accepted by the OpenAI API. - public async Task ValidateAPIKey() - { - if (string.IsNullOrEmpty(ApiKey)) - return false; - - var api = new OpenAIAPI(this); - - List results; - - try - { - results = await api.Models.GetModelsAsync(); - } - catch (Exception ex) - { - Debug.WriteLine(ex.ToString()); - return false; - } - - return (results.Count > 0); - } - } internal static class AuthHelpers diff --git a/OpenAI_API/EndpointBase.cs b/OpenAI_API/EndpointBase.cs index 5d74cfa..e096223 100644 --- a/OpenAI_API/EndpointBase.cs +++ b/OpenAI_API/EndpointBase.cs @@ -48,22 +48,19 @@ protected string Url } } - /// - /// Gets an HTTPClient with the appropriate authorization and other headers set - /// + /// Configures an HttpClient with the appropriate authorization and other headers set /// The fully initialized HttpClient /// Thrown if there is no valid authentication. Please refer to for details. - protected HttpClient GetClient() + internal static HttpClient ConfigureClient(HttpClient client, APIAuthentication auth) { - if (_Api.Auth?.ApiKey is null) + if (auth?.ApiKey is null) { throw new AuthenticationException("You must provide API authentication. Please refer to https://github.com/OkGoDoIt/OpenAI-API-dotnet#authentication for details."); } - HttpClient client = new HttpClient(); - client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _Api.Auth.ApiKey); + client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", auth.ApiKey); client.DefaultRequestHeaders.Add("User-Agent", Value); - if (!string.IsNullOrEmpty(_Api.Auth.OpenAIOrganization)) client.DefaultRequestHeaders.Add("OpenAI-Organization", _Api.Auth.OpenAIOrganization); + if (!string.IsNullOrEmpty(auth.OpenAIOrganization)) client.DefaultRequestHeaders.Add("OpenAI-Organization", auth.OpenAIOrganization); return client; } @@ -99,11 +96,8 @@ private async Task HttpRequestRaw(string url = null, HttpMe if (verb == null) verb = HttpMethod.Get; - var client = GetClient(); - - HttpResponseMessage response = null; - string resultAsString = null; - HttpRequestMessage req = new HttpRequestMessage(verb, url); + + using var req = new HttpRequestMessage(verb, url); if (postData != null) { @@ -118,15 +112,16 @@ private async Task HttpRequestRaw(string url = null, HttpMe req.Content = stringContent; } } - response = await client.SendAsync(req, streaming ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead); + var response = await this._Api.Client.SendAsync(req, streaming ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead); if (response.IsSuccessStatusCode) { return response; } else { - try + string resultAsString = null; + try { resultAsString = await response.Content.ReadAsStringAsync(); } diff --git a/OpenAI_API/Files/FilesEndpoint.cs b/OpenAI_API/Files/FilesEndpoint.cs index 6721a2e..b58eb50 100644 --- a/OpenAI_API/Files/FilesEndpoint.cs +++ b/OpenAI_API/Files/FilesEndpoint.cs @@ -71,7 +71,6 @@ public async Task DeleteFileAsync(string fileId) /// The intendend purpose of the uploaded documents. Use "fine-tune" for Fine-tuning. This allows us to validate the format of the uploaded file. public async Task UploadFileAsync(string filePath, string purpose = "fine-tune") { - HttpClient client = GetClient(); var content = new MultipartFormDataContent { { new StringContent(purpose), "purpose" }, diff --git a/OpenAI_API/IOpenAI.cs b/OpenAI_API/IOpenAI.cs new file mode 100644 index 0000000..37d027f --- /dev/null +++ b/OpenAI_API/IOpenAI.cs @@ -0,0 +1,23 @@ +#nullable enable +namespace OpenAI_API; +using OpenAI_API.Completions; +using OpenAI_API.Embedding; +using OpenAI_API.Files; +using OpenAI_API.Models; + +/// Entry point to the OpenAPI API, handling auth and allowing access to the various API endpoints +public interface IOpenAI +{ + /// Text generation is the core function of the API. You give the API a prompt, and it generates a completion. The way you “program” the API to do a task is by simply describing the task in plain english or providing a few written examples. This simple approach works for a wide range of use cases, including summarization, translation, grammar correction, question answering, chatbots, composing emails, and much more (see the prompt library for inspiration). + public CompletionEndpoint Completions { get; } + + /// The API lets you transform text into a vector (list) of floating point numbers. The distance between two vectors measures their relatedness. Small distances suggest high relatedness and large distances suggest low relatedness. + public EmbeddingEndpoint Embeddings { get; } + + /// The API endpoint for querying available Engines/models + public ModelsEndpoint Models { get; } + + /// The API lets you do operations with files. You can upload, delete or retrieve files. Files can be used for fine-tuning, search, etc. + public FilesEndpoint Files { get; } +} + diff --git a/OpenAI_API/Model/ModelsEndpoint.cs b/OpenAI_API/Model/ModelsEndpoint.cs index a197e68..16cc147 100644 --- a/OpenAI_API/Model/ModelsEndpoint.cs +++ b/OpenAI_API/Model/ModelsEndpoint.cs @@ -25,10 +25,12 @@ internal ModelsEndpoint(OpenAIAPI api) : base(api) { } /// /// The id/name of the model to get more details about /// Asynchronously returns the with all available properties - public Task RetrieveModelDetailsAsync(string id) + public async Task RetrieveModelDetailsAsync(string id) { - return RetrieveModelDetailsAsync(id, _Api?.Auth); - } + string resultAsString = await HttpGetContent($"{Url}/{id}"); + var model = JsonConvert.DeserializeObject(resultAsString); + return model; + } /// /// List all models via the API @@ -43,14 +45,11 @@ public async Task> GetModelsAsync() /// Get details about a particular Model from the API, specifically properties such as and permissions. /// /// The id/name of the model to get more details about - /// API authentication in order to call the API endpoint. If not specified, attempts to use a default. + /// Obsolete: IGNORED /// Asynchronously returns the with all available properties - public async Task RetrieveModelDetailsAsync(string id, APIAuthentication auth = null) - { - string resultAsString = await HttpGetContent($"{Url}/{id}"); - var model = JsonConvert.DeserializeObject(resultAsString); - return model; - } + [Obsolete("Use the overload without the APIAuthentication parameter instead, as it is ignored.", false)] // See #42 + public Task RetrieveModelDetailsAsync(string id, APIAuthentication auth) => + this.RetrieveModelDetailsAsync(id); /// /// A helper class to deserialize the JSON API responses. This should not be used directly. diff --git a/OpenAI_API/OpenAIAPI.cs b/OpenAI_API/OpenAIAPI.cs index 727b261..6bf9555 100644 --- a/OpenAI_API/OpenAIAPI.cs +++ b/OpenAI_API/OpenAIAPI.cs @@ -1,42 +1,68 @@ -using OpenAI_API.Completions; +using Microsoft.Extensions.Logging; +using OpenAI_API.Completions; using OpenAI_API.Embedding; using OpenAI_API.Files; using OpenAI_API.Models; +using System.Net.Http; namespace OpenAI_API { /// /// Entry point to the OpenAPI API, handling auth and allowing access to the various API endpoints /// - public class OpenAIAPI - { + public class OpenAIAPI: IOpenAI + { /// /// Base url for OpenAI /// public string ApiUrlBase = "https://api.openai.com/v1/"; - - /// - /// The API authentication information to use for API calls - /// - public APIAuthentication Auth { get; set; } - /// - /// Creates a new entry point to the OpenAPI API, handling auth and allowing access to the various API endpoints - /// - /// The API authentication information to use for API calls, or to attempt to use the , potentially loading from environment vars or from a config file. - public OpenAIAPI(APIAuthentication apiKeys = null) - { - this.Auth = apiKeys.ThisOrDefault(); - Completions = new CompletionEndpoint(this); - Models = new ModelsEndpoint(this); - Files = new FilesEndpoint(this); - Embeddings = new EmbeddingEndpoint(this); - } + /// The HTTP client configured with the authentication headers. + internal HttpClient Client { get; } - /// - /// Text generation is the core function of the API. You give the API a prompt, and it generates a completion. The way you “program” the API to do a task is by simply describing the task in plain english or providing a few written examples. This simple approach works for a wide range of use cases, including summarization, translation, grammar correction, question answering, chatbots, composing emails, and much more (see the prompt library for inspiration). - /// - public CompletionEndpoint Completions { get; } + private OpenAIAPI() + { + this.Completions = new CompletionEndpoint(this); + this.Models = new ModelsEndpoint(this); + this.Files = new FilesEndpoint(this); + this.Embeddings = new EmbeddingEndpoint(this); + } + + /// + /// Creates a new entry point to the OpenAPI API, handling auth and allowing access to the various API endpoints + /// + /// The HTTP client configured with the authentication headers. + public OpenAIAPI(HttpClient httpClient) : this() => + this.Client = httpClient; + + /// + /// Creates a new entry point to the OpenAPI API, handling auth and allowing access to the various API endpoints + /// + /// The authentication details for the API + [Obsolete(""" + This constructor will generate a new HTTP client every time it is called. + Do not use this in scenarios where multiple instances of the API are required. + This is provided for backwards compatibility, use .NET Dependency Injection instead. + """, false)] + public OpenAIAPI(APIAuthentication auth) : + this(EndpointBase.ConfigureClient(new HttpClient(), auth)) { } + + /// + /// Creates a new entry point to the OpenAPI API, handling auth and allowing access to the various API endpoints + /// + /// The authentication key for the API + [Obsolete(""" + This constructor will generate a new HTTP client every time it is called. + Do not use this in scenarios where multiple instances of the API are required. + This is provided for backwards compatibility, use .NET Dependency Injection instead. + """, false)] + public OpenAIAPI(string key) : + this(EndpointBase.ConfigureClient(new HttpClient(), new APIAuthentication(key))) { } + + /// + /// Text generation is the core function of the API. You give the API a prompt, and it generates a completion. The way you “program” the API to do a task is by simply describing the task in plain english or providing a few written examples. This simple approach works for a wide range of use cases, including summarization, translation, grammar correction, question answering, chatbots, composing emails, and much more (see the prompt library for inspiration). + /// + public CompletionEndpoint Completions { get; } /// /// The API lets you transform text into a vector (list) of floating point numbers. The distance between two vectors measures their relatedness. Small distances suggest high relatedness and large distances suggest low relatedness. @@ -52,8 +78,5 @@ public OpenAIAPI(APIAuthentication apiKeys = null) /// The API lets you do operations with files. You can upload, delete or retrieve files. Files can be used for fine-tuning, search, etc. /// public FilesEndpoint Files { get; } - - - } } diff --git a/OpenAI_API/OpenAIApiExtensions.cs b/OpenAI_API/OpenAIApiExtensions.cs new file mode 100644 index 0000000..4374345 --- /dev/null +++ b/OpenAI_API/OpenAIApiExtensions.cs @@ -0,0 +1,31 @@ +#nullable enable +namespace Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using OpenAI_API; + +public static class OpenAIApiExtensions +{ + /// Register for DI services. Read configuration from appsettings "openAI": { "key": "", "org": "" } + /// + /// + /// + public static IServiceCollection AddOpenAIService(this IServiceCollection services, IConfiguration configuration) + { + var section = configuration.GetSection("openAI"); + if (!section.Exists()) return services; + + string? key = section["key"]; + if (key is null) return services; + + string? organisation = section["org"]; + return services.AddOpenAIService(new APIAuthentication(key, organisation)); + } + + public static IServiceCollection AddOpenAIService(this IServiceCollection services, APIAuthentication auth) + { + services.AddHttpClient(client => + EndpointBase.ConfigureClient(client, auth)); + + return services; + } +} \ No newline at end of file diff --git a/OpenAI_API/OpenAI_API.csproj b/OpenAI_API/OpenAI_API.csproj index b1f8fec..c9c18da 100644 --- a/OpenAI_API/OpenAI_API.csproj +++ b/OpenAI_API/OpenAI_API.csproj @@ -1,8 +1,7 @@  - netstandard2.0 - 8.0 + net7.0 true OkGoDoIt (Roger Pincombe) OpenAI API @@ -30,7 +29,8 @@ snupkg true - + enable + latest @@ -41,7 +41,10 @@ - + + + + diff --git a/OpenAI_Tests/AuthTests.cs b/OpenAI_Tests/AuthTests.cs index e791c81..293c33b 100644 --- a/OpenAI_Tests/AuthTests.cs +++ b/OpenAI_Tests/AuthTests.cs @@ -1,4 +1,7 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using OpenAI_API; using System; using System.IO; using System.Threading.Tasks; @@ -59,26 +62,24 @@ public void GetDefault() } - - - [Test] - public void testHelper() - { - OpenAI_API.APIAuthentication defaultAuth = OpenAI_API.APIAuthentication.Default; - OpenAI_API.APIAuthentication manualAuth = new OpenAI_API.APIAuthentication("pk-testAA"); - OpenAI_API.OpenAIAPI api = new OpenAI_API.OpenAIAPI(); - OpenAI_API.APIAuthentication shouldBeDefaultAuth = api.Auth; - Assert.IsNotNull(shouldBeDefaultAuth); - Assert.IsNotNull(shouldBeDefaultAuth.ApiKey); - Assert.AreEqual(defaultAuth.ApiKey, shouldBeDefaultAuth.ApiKey); - - OpenAI_API.APIAuthentication.Default = new OpenAI_API.APIAuthentication("pk-testAA"); - api = new OpenAI_API.OpenAIAPI(); - OpenAI_API.APIAuthentication shouldBeManualAuth = api.Auth; - Assert.IsNotNull(shouldBeManualAuth); - Assert.IsNotNull(shouldBeManualAuth.ApiKey); - Assert.AreEqual(manualAuth.ApiKey, shouldBeManualAuth.ApiKey); - } + // [Test] + // public void testHelper() + // { + // OpenAI_API.APIAuthentication defaultAuth = OpenAI_API.APIAuthentication.Default; + // OpenAI_API.APIAuthentication manualAuth = new OpenAI_API.APIAuthentication("pk-testAA"); + // OpenAI_API.OpenAIAPI api = AuthTests.InitService(); + // OpenAI_API.APIAuthentication shouldBeDefaultAuth = api.Auth; + // Assert.IsNotNull(shouldBeDefaultAuth); + // Assert.IsNotNull(shouldBeDefaultAuth.ApiKey); + // Assert.AreEqual(defaultAuth.ApiKey, shouldBeDefaultAuth.ApiKey); + // + // OpenAI_API.APIAuthentication.Default = new OpenAI_API.APIAuthentication("pk-testAA"); + // api = AuthTests.InitService(); + // OpenAI_API.APIAuthentication shouldBeManualAuth = api.Auth; + // Assert.IsNotNull(shouldBeManualAuth); + // Assert.IsNotNull(shouldBeManualAuth.ApiKey); + // Assert.AreEqual(manualAuth.ApiKey, shouldBeManualAuth.ApiKey); + // } [Test] public void GetKey() @@ -106,22 +107,35 @@ public void ParseKey() Assert.AreEqual("orgTest", auth.OpenAIOrganization); } - [Test] - public async Task TestBadKey() - { - var auth = new OpenAI_API.APIAuthentication("pk-testAA"); - Assert.IsFalse(await auth.ValidateAPIKey()); - - auth = new OpenAI_API.APIAuthentication(null); - Assert.IsFalse(await auth.ValidateAPIKey()); - } - - [Test] - public async Task TestValidateGoodKey() - { - var auth = new OpenAI_API.APIAuthentication(Environment.GetEnvironmentVariable("TEST_OPENAI_SECRET_KEY")); - Assert.IsTrue(await auth.ValidateAPIKey()); - } + // [Test] + // public async Task TestBadKey() + // { + // var auth = new OpenAI_API.APIAuthentication("pk-testAA"); + // Assert.IsFalse(await auth.ValidateAPIKey()); + // + // auth = new OpenAI_API.APIAuthentication(null); + // Assert.IsFalse(await auth.ValidateAPIKey()); + // } + // + // [Test] + // public async Task TestValidateGoodKey() + // { + // var auth = new OpenAI_API.APIAuthentication(Environment.GetEnvironmentVariable("TEST_OPENAI_SECRET_KEY")); + // Assert.IsTrue(await auth.ValidateAPIKey()); + // } + + internal static IOpenAI InitService() { + IConfiguration config = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", false, true) + .Build(); + + var services = new ServiceCollection(); + services.AddOpenAIService(config); + + var serviceProvider = services.BuildServiceProvider(); + + return serviceProvider.GetService(); + } } } \ No newline at end of file diff --git a/OpenAI_Tests/CompletionEndpointTests.cs b/OpenAI_Tests/CompletionEndpointTests.cs index 936cecb..40d4266 100644 --- a/OpenAI_Tests/CompletionEndpointTests.cs +++ b/OpenAI_Tests/CompletionEndpointTests.cs @@ -21,7 +21,7 @@ public void Setup() [Test] public void GetBasicCompletion() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); Assert.IsNotNull(api.Completions); @@ -41,9 +41,9 @@ public void GetBasicCompletion() [Test] public void GetSimpleCompletion() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); - Assert.IsNotNull(api.Completions); + Assert.IsNotNull(api.Completions); var results = api.Completions.CreateCompletionAsync("One Two Three Four Five Six Seven Eight Nine One Two Three Four Five Six Seven Eight", temperature: 0.1, max_tokens: 5).Result; Assert.IsNotNull(results); @@ -56,9 +56,9 @@ public void GetSimpleCompletion() [Test] public async Task CreateCompletionAsync_MultiplePrompts_ShouldReturnResult() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); - var completionReq = new CompletionRequest + var completionReq = new CompletionRequest { MultiplePrompts = new[] { @@ -80,9 +80,9 @@ public async Task CreateCompletionAsync_MultiplePrompts_ShouldReturnResult() [TestCase(3)] public void CreateCompletionAsync_ShouldNotAllowTemperatureOutside01(double temperature) { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); - var completionReq = new CompletionRequest + var completionReq = new CompletionRequest { Prompt = "three four five", Temperature = temperature, @@ -100,9 +100,9 @@ public void CreateCompletionAsync_ShouldNotAllowTemperatureOutside01(double temp [TestCase(2.0)] public async Task ShouldBeMoreCreativeWithHighTemperature(double temperature) { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); - var completionReq = new CompletionRequest + var completionReq = new CompletionRequest { Prompt = "three four five", Temperature = temperature, @@ -119,9 +119,9 @@ public async Task ShouldBeMoreCreativeWithHighTemperature(double temperature) [TestCase(0.1)] public async Task CreateCompletionAsync_ShouldGetSomeResultsWithVariousTopPValues(double topP) { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); - var completionReq = new CompletionRequest + var completionReq = new CompletionRequest { Prompt = "three four five", Temperature = 0, @@ -140,7 +140,7 @@ public async Task CreateCompletionAsync_ShouldGetSomeResultsWithVariousTopPValue [TestCase(1.0)] public async Task CreateCompletionAsync_ShouldReturnSomeResultsForPresencePenalty(double presencePenalty) { - var api = new OpenAI_API.OpenAIAPI(); + var api =AuthTests.InitService(); var completionReq = new CompletionRequest { @@ -161,7 +161,7 @@ public async Task CreateCompletionAsync_ShouldReturnSomeResultsForPresencePenalt [TestCase(1.0)] public async Task CreateCompletionAsync_ShouldReturnSomeResultsForFrequencyPenalty(double frequencyPenalty) { - var api = new OpenAI_API.OpenAIAPI(); + var api =AuthTests.InitService(); var completionReq = new CompletionRequest { @@ -179,7 +179,7 @@ public async Task CreateCompletionAsync_ShouldReturnSomeResultsForFrequencyPenal [Test] public async Task CreateCompletionAsync_ShouldWorkForBiggerNumberOfCompletions() { - var api = new OpenAI_API.OpenAIAPI(); + var api =AuthTests.InitService(); var completionReq = new CompletionRequest { @@ -199,7 +199,7 @@ public async Task CreateCompletionAsync_ShouldWorkForBiggerNumberOfCompletions() [TestCase(5)] public async Task CreateCompletionAsync_ShouldAlsoReturnLogProps(int logProps) { - var api = new OpenAI_API.OpenAIAPI(); + var api =AuthTests.InitService(); var completionReq = new CompletionRequest { @@ -221,7 +221,7 @@ public async Task CreateCompletionAsync_ShouldAlsoReturnLogProps(int logProps) [Test] public async Task CreateCompletionAsync_Echo_ShouldReturnTheInput() { - var api = new OpenAI_API.OpenAIAPI(); + var api =AuthTests.InitService(); var completionReq = new CompletionRequest { @@ -240,7 +240,7 @@ public async Task CreateCompletionAsync_Echo_ShouldReturnTheInput() [TestCase("Friday")] public async Task CreateCompletionAsync_ShouldStopOnStopSequence(string stopSeq) { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var completionReq = new CompletionRequest { @@ -260,7 +260,7 @@ public async Task CreateCompletionAsync_ShouldStopOnStopSequence(string stopSeq) [Test] public async Task CreateCompletionAsync_MultipleParamShouldReturnTheSameDataAsSingleParamVersion() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var r = new CompletionRequest { @@ -296,7 +296,7 @@ public async Task CreateCompletionAsync_MultipleParamShouldReturnTheSameDataAsSi [TestCase(7, 2)] public async Task StreamCompletionAsync_ShouldStreamIndexAndData(int maxTokens, int numOutputs) { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var completionRequest = new CompletionRequest { @@ -328,7 +328,7 @@ await api.Completions.StreamCompletionAsync(completionRequest, (index, result) = [TestCase(7, 2)] public async Task StreamCompletionAsync_ShouldStreamData(int maxTokens, int numOutputs) { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var completionRequest = new CompletionRequest { @@ -357,7 +357,7 @@ await api.Completions.StreamCompletionAsync(completionRequest, result => [TestCase(7, 2)] public async Task StreamCompletionEnumerableAsync_ShouldStreamData(int maxTokens, int numOutputs) { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var completionRequest = new CompletionRequest { @@ -385,7 +385,7 @@ public async Task StreamCompletionEnumerableAsync_ShouldStreamData(int maxTokens [Test] public async Task StreamCompletionEnumerableAsync_MultipleParamShouldReturnTheSameDataAsSingleParamVersion() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var r = new CompletionRequest { diff --git a/OpenAI_Tests/EmbeddingEndpointTests.cs b/OpenAI_Tests/EmbeddingEndpointTests.cs index ee58a57..cd221d6 100644 --- a/OpenAI_Tests/EmbeddingEndpointTests.cs +++ b/OpenAI_Tests/EmbeddingEndpointTests.cs @@ -17,7 +17,7 @@ public void Setup() [Test] public void GetBasicEmbedding() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); Assert.IsNotNull(api.Embeddings); @@ -41,7 +41,7 @@ public void GetBasicEmbedding() [Test] public void GetSimpleEmbedding() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); Assert.IsNotNull(api.Embeddings); diff --git a/OpenAI_Tests/FilesEndpointTests.cs b/OpenAI_Tests/FilesEndpointTests.cs index 6cf3df8..00fd1b8 100644 --- a/OpenAI_Tests/FilesEndpointTests.cs +++ b/OpenAI_Tests/FilesEndpointTests.cs @@ -17,7 +17,7 @@ public void Setup() [Order(1)] public async Task UploadFile() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var response = await api.Files.UploadFileAsync("fine-tuning-data.jsonl"); Assert.IsNotNull(response); Assert.IsTrue(response.Id.Length > 0); @@ -33,7 +33,7 @@ public async Task UploadFile() [Order(2)] public async Task ListFiles() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var response = await api.Files.GetFilesAsync(); foreach (var file in response) @@ -48,7 +48,7 @@ public async Task ListFiles() [Order(3)] public async Task GetFile() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var response = await api.Files.GetFilesAsync(); foreach (var file in response) { @@ -68,7 +68,7 @@ public async Task GetFile() [Order(4)] public async Task DeleteFiles() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var response = await api.Files.GetFilesAsync(); foreach (var file in response) { diff --git a/OpenAI_Tests/ModelEndpointTests.cs b/OpenAI_Tests/ModelEndpointTests.cs index 3ccfef6..78dad3f 100644 --- a/OpenAI_Tests/ModelEndpointTests.cs +++ b/OpenAI_Tests/ModelEndpointTests.cs @@ -20,7 +20,7 @@ public void Setup() [Test] public void GetAllModels() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); Assert.IsNotNull(api.Models); @@ -33,7 +33,7 @@ public void GetAllModels() [Test] public void GetModelDetails() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); Assert.IsNotNull(api.Models); @@ -55,20 +55,20 @@ public void GetModelDetails() [Test] public async Task GetEnginesAsync_ShouldReturnTheEngineList() { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var models = await api.Models.GetModelsAsync(); models.Count.Should().BeGreaterOrEqualTo(5, "most engines should be returned"); } - [Test] - public void GetEnginesAsync_ShouldFailIfInvalidAuthIsProvided() - { - var api = new OpenAIAPI(new APIAuthentication(Guid.NewGuid().ToString())); - Func act = () => api.Models.GetModelsAsync(); - act.Should() - .ThrowAsync() - .Where(exc => exc.Message.Contains("Incorrect API key provided")); - } + // [Test] + // public void GetEnginesAsync_ShouldFailIfInvalidAuthIsProvided() + // { + // var api = new OpenAIAPI(new APIAuthentication(Guid.NewGuid().ToString())); + // Func act = () => api.Models.GetModelsAsync(); + // act.Should() + // .ThrowAsync() + // .Where(exc => exc.Message.Contains("Incorrect API key provided")); + // } [TestCase("ada")] [TestCase("babbage")] @@ -76,7 +76,7 @@ public void GetEnginesAsync_ShouldFailIfInvalidAuthIsProvided() [TestCase("davinci")] public async Task RetrieveEngineDetailsAsync_ShouldRetrieveEngineDetails(string modelId) { - var api = new OpenAI_API.OpenAIAPI(); + var api = AuthTests.InitService(); var modelData = await api.Models.RetrieveModelDetailsAsync(modelId); modelData?.ModelID?.Should()?.Be(modelId); modelData.Created.Should().BeAfter(new DateTime(2018, 1, 1), "the model has a created date no earlier than 2018"); diff --git a/OpenAI_Tests/OpenAI_Tests.csproj b/OpenAI_Tests/OpenAI_Tests.csproj index 805cc44..57ca2cd 100644 --- a/OpenAI_Tests/OpenAI_Tests.csproj +++ b/OpenAI_Tests/OpenAI_Tests.csproj @@ -1,13 +1,15 @@ - netcoreapp3.1 + net7.0 false + + diff --git a/README.md b/README.md index b490204..8b73a01 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,36 @@ Install package [`OpenAI` from Nuget](https://www.nuget.org/packages/OpenAI/). Install-Package OpenAI ``` -### Authentication +### Register for Dependency Injection +If you are using Dependency Injection, you can register the API using `appsettings.json` configuration: + +```json +"openAI": { + "key": "YOUR_API_KEY", + "org": "OPENAI_ORGANIZATION" +} +``` + +Then in your `Program.cs` or `Startup.cs` add: + +```c# +builder.Services.AddOpenAIService(builder.Configuration); +``` + +This registers `IOpenAI` for dependency injection using: +- `[FromServices] IOpenAI? openAI` in your controllers. +- `IOpenAI? openAI` in your constructor. + +#### Legacy constructors +Previous versions of this package allowed direct instantiation of the service, but this causes connection pool exhaustion when multiple instances exist. + +It is still supported for backwards compatibility, but not recommended. + There are 3 ways to provide your API keys, in order of precedence: 1. Pass keys directly to `APIAuthentication(string key)` constructor 2. Set environment var for OPENAI_API_KEY (or OPENAI_KEY for backwards compatibility) 3. Include a config file in the local directory or in your user directory named `.openai` and containing the line: + ```shell OPENAI_API_KEY=sk-aaaabbbbbccccddddd ```