diff --git a/OpenAI_API.sln b/OpenAI_API.sln
index 1f1864f..9412cfe 100644
--- a/OpenAI_API.sln
+++ b/OpenAI_API.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30309.148
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI_API", "OpenAI_API\OpenAI_API.csproj", "{99C80D3E-3F0F-4ACC-900D-7AAE6230A780}"
EndProject
diff --git a/OpenAI_API/BaseEndpoint.cs b/OpenAI_API/BaseEndpoint.cs
new file mode 100644
index 0000000..d4a21a8
--- /dev/null
+++ b/OpenAI_API/BaseEndpoint.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OpenAI_API
+{
+ public abstract class BaseEndpoint
+ {
+ protected readonly OpenAIAPI api;
+
+ public BaseEndpoint(OpenAIAPI api)
+ {
+ this.api = api;
+ }
+
+ protected abstract string GetEndpoint();
+
+ protected string GetUrl()
+ {
+ return $"{OpenAIAPI.API_URL}{GetEndpoint()}";
+ }
+
+ protected HttpClient GetClient()
+ {
+ HttpClient client = new HttpClient();
+ client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", api.Auth?.ThisOrDefault().ApiKey);
+ client.DefaultRequestHeaders.Add("User-Agent", "okgodoit/dotnet_openai_api");
+ return client;
+ }
+
+ protected string GetErrorMessage(string resultAsString, HttpResponseMessage response, string name, string description = "")
+ {
+ return $"Error at {name} ( {description} ) with HTTP status code: {response.StatusCode} . Content: {resultAsString}";
+ }
+
+
+ }
+}
diff --git a/OpenAI_API/Files/File.cs b/OpenAI_API/Files/File.cs
new file mode 100644
index 0000000..e6054e7
--- /dev/null
+++ b/OpenAI_API/Files/File.cs
@@ -0,0 +1,69 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OpenAI_API.Files
+{
+ public class File
+ {
+ ///
+ /// unique id for this file, so that it can be referenced in other operations
+ ///
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ ///
+ /// the name of the file
+ ///
+ [JsonProperty("filename")]
+ public string Name { get; set; }
+
+
+ ///
+ /// What is the purpose of this file, fine-tune, search, etc
+ ///
+ [JsonProperty("Purpose")]
+ public string Purpose { get; set; }
+
+ ///
+ /// object type, ie: file, image, etc
+ ///
+ [JsonProperty("object")]
+ public string Object { get; set; }
+
+ ///
+ /// The size of the file in bytes
+ ///
+ [JsonProperty("bytes")]
+ public long Bytes { get; set; }
+
+ ///
+ /// Timestamp for the creation time of this file
+ ///
+ [JsonProperty("created_at")]
+ public long CreatedAt { get; set; }
+
+ ///
+ /// The object was deleted, this attribute is used in the Delete file operation
+ ///
+ [JsonProperty("deleted")]
+ public bool Deleted { get; set; }
+
+
+ ///
+ /// The status of the File (ie when an upload operation was done: "uploaded")
+ ///
+ [JsonProperty("status")]
+ public string Status { get; set; }
+
+ ///
+ /// The status details, it could be null
+ ///
+ [JsonProperty("status_details")]
+ public string StatusDetails { get; set; }
+
+ }
+}
diff --git a/OpenAI_API/Files/FilesEndpoint.cs b/OpenAI_API/Files/FilesEndpoint.cs
new file mode 100644
index 0000000..5a93a13
--- /dev/null
+++ b/OpenAI_API/Files/FilesEndpoint.cs
@@ -0,0 +1,128 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OpenAI_API.Files
+{
+ ///
+ /// The API endpoint for operations List, Upload, Delete, Retrieve files
+ ///
+ public class FilesEndpoint : BaseEndpoint
+ {
+ public FilesEndpoint(OpenAIAPI api) : base(api) {}
+
+ protected override string GetEndpoint() { return "files"; }
+
+ ///
+ /// Get the list of all files
+ ///
+ ///
+ ///
+ public async Task> GetFilesAsync()
+ {
+ var response = await GetClient().GetAsync(GetUrl());
+ string resultAsString = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode)
+ {
+ var files = JsonConvert.DeserializeObject(resultAsString).Data;
+ return files;
+ }
+ throw new HttpRequestException(GetErrorMessage(resultAsString, response, "List files", "Get the list of all files"));
+ }
+
+ ///
+ /// Returns information about a specific file
+ ///
+ /// The ID of the file to use for this request
+ ///
+ public async Task GetFileAsync(string fileId)
+ {
+ var response = await GetClient().GetAsync($"{GetUrl()}/{fileId}");
+ string resultAsString = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode)
+ {
+ var file = JsonConvert.DeserializeObject(resultAsString);
+ return file;
+ }
+ throw new HttpRequestException(GetErrorMessage(resultAsString, response, "Retrieve file"));
+ }
+
+
+ ///
+ /// Returns the contents of the specific file as string
+ ///
+ /// The ID of the file to use for this request
+ ///
+ public async Task GetFileContentAsStringAsync(string fileId)
+ {
+ var response = await GetClient().GetAsync($"{GetUrl()}/{fileId}/content");
+ string resultAsString = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode)
+ {
+ return resultAsString;
+
+ }
+ throw new HttpRequestException(GetErrorMessage(resultAsString, response, "Retrieve file content"));
+ }
+
+ ///
+ /// Delete a file
+ ///
+ /// The ID of the file to use for this request
+ ///
+ public async Task DeleteFileAsync(string fileId)
+ {
+ var response = await GetClient().DeleteAsync($"{GetUrl()}/{fileId}");
+ string resultAsString = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode)
+ {
+ var file = JsonConvert.DeserializeObject(resultAsString);
+ return file;
+ }
+ throw new HttpRequestException(GetErrorMessage(resultAsString, response, "Delete file"));
+ }
+
+ ///
+ /// Upload a file that contains document(s) to be used across various endpoints/features. Currently, the size of all the files uploaded by one organization can be up to 1 GB. Please contact us if you need to increase the storage limit
+ ///
+ /// The name of the file to use for this request
+ /// 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 file, string purpose = "fine-tune")
+ {
+ HttpClient client = GetClient();
+ var content = new MultipartFormDataContent
+ {
+ { new StringContent(purpose), "purpose" },
+ { new ByteArrayContent(System.IO.File.ReadAllBytes(file)), "file", Path.GetFileName(file) }
+ };
+ var response = await client.PostAsync($"{GetUrl()}", content);
+ string resultAsString = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode)
+ {
+ return JsonConvert.DeserializeObject(resultAsString);
+ }
+ throw new HttpRequestException(GetErrorMessage(resultAsString, response, "Upload file", "Upload a file that contains document(s) to be used across various endpoints/features."));
+ }
+ }
+ ///
+ /// A helper class to deserialize the JSON API responses. This should not be used directly.
+ ///
+ class FilesData
+ {
+ [JsonProperty("data")]
+ public List Data { get; set; }
+ [JsonProperty("object")]
+ public string Obj { get; set; }
+ }
+}
diff --git a/OpenAI_API/OpenAIAPI.cs b/OpenAI_API/OpenAIAPI.cs
index 2baedea..27c419a 100644
--- a/OpenAI_API/OpenAIAPI.cs
+++ b/OpenAI_API/OpenAIAPI.cs
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
+using OpenAI_API.Files;
using System;
using System.Collections.Generic;
using System.IO;
@@ -14,6 +15,11 @@ namespace OpenAI_API
///
public class OpenAIAPI
{
+ ///
+ /// Base url for OpenAI
+ ///
+ public const string API_URL = "https://api.openai.com/v1/";
+
///
/// The API authentication information to use for API calls
///
@@ -36,6 +42,7 @@ public OpenAIAPI(APIAuthentication apiKeys = null, Engine engine = null)
Completions = new CompletionEndpoint(this);
Engines = new EnginesEndpoint(this);
Search = new SearchEndpoint(this);
+ Files = new FilesEndpoint(this);
}
///
@@ -53,7 +60,10 @@ public OpenAIAPI(APIAuthentication apiKeys = null, Engine engine = null)
///
public SearchEndpoint Search { 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_Tests/FilesEndpointTests.cs b/OpenAI_Tests/FilesEndpointTests.cs
new file mode 100644
index 0000000..c824a7b
--- /dev/null
+++ b/OpenAI_Tests/FilesEndpointTests.cs
@@ -0,0 +1,91 @@
+using NUnit.Framework;
+using OpenAI_API;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OpenAI_Tests
+{
+ public class FilesEndpointTests
+ {
+ [SetUp]
+ public void Setup()
+ {
+ OpenAI_API.APIAuthentication.Default = new OpenAI_API.APIAuthentication(Environment.GetEnvironmentVariable("TEST_OPENAI_SECRET_KEY"));
+ }
+
+ [Test]
+ [Order(1)]
+ public async Task UploadFile()
+ {
+ var api = new OpenAI_API.OpenAIAPI(engine: Engine.Davinci);
+ var response = await api.Files.UploadFileAsync("fine-tuning-data.jsonl");
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Id.Length > 0);
+ Assert.IsTrue(response.Object == "file");
+ Assert.IsTrue(response.Bytes > 0);
+ Assert.IsTrue(response.CreatedAt > 0);
+ Assert.IsTrue(response.Status == "uploaded");
+ // The file must be processed before it can be used in other operations, so for testing purposes we just sleep awhile.
+ Thread.Sleep(10000);
+ }
+
+ [Test]
+ [Order(2)]
+ public async Task ListFiles()
+ {
+ var api = new OpenAI_API.OpenAIAPI(engine: Engine.Davinci);
+ var response = await api.Files.GetFilesAsync();
+
+ foreach (var file in response)
+ {
+ Assert.IsNotNull(file);
+ Assert.IsTrue(file.Id.Length > 0);
+ }
+ }
+
+
+ [Test]
+ [Order(3)]
+ public async Task GetFile()
+ {
+ var api = new OpenAI_API.OpenAIAPI(engine: Engine.Davinci);
+ var response = await api.Files.GetFilesAsync();
+ foreach (var file in response)
+ {
+ Assert.IsNotNull(file);
+ Assert.IsTrue(file.Id.Length > 0);
+ string id = file.Id;
+ if (file.Name == "fine-tuning-data.jsonl")
+ {
+ var fileResponse = await api.Files.GetFileAsync(file.Id);
+ Assert.IsNotNull(fileResponse);
+ Assert.IsTrue(fileResponse.Id == id);
+ }
+ }
+ }
+
+ [Test]
+ [Order(4)]
+ public async Task DeleteFiles()
+ {
+ var api = new OpenAI_API.OpenAIAPI(engine: Engine.Davinci);
+ var response = await api.Files.GetFilesAsync();
+ foreach (var file in response)
+ {
+ Assert.IsNotNull(file);
+ Assert.IsTrue(file.Id.Length > 0);
+ if (file.Name == "fine-tuning-data.jsonl")
+ {
+ var deleteResponse = await api.Files.DeleteFileAsync(file.Id);
+ Assert.IsNotNull(deleteResponse);
+ Assert.IsTrue(deleteResponse.Deleted);
+ }
+ }
+ }
+
+ }
+}
diff --git a/OpenAI_Tests/OpenAI_Tests.csproj b/OpenAI_Tests/OpenAI_Tests.csproj
index 56b6dea..9da037d 100644
--- a/OpenAI_Tests/OpenAI_Tests.csproj
+++ b/OpenAI_Tests/OpenAI_Tests.csproj
@@ -16,4 +16,10 @@
+
+
+ PreserveNewest
+
+
+
diff --git a/OpenAI_Tests/fine-tuning-data.jsonl b/OpenAI_Tests/fine-tuning-data.jsonl
new file mode 100644
index 0000000..d903bdb
--- /dev/null
+++ b/OpenAI_Tests/fine-tuning-data.jsonl
@@ -0,0 +1,75 @@
+{ "prompt": "type for FilterRelationType", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for FilterOperation", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for TargetType", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for RuntimeEnvironment", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for MapType", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for LogLevel", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for StorePurchaseState", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for StorePurchasePlatform", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for StoreProductType", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for StorePurchaseStatus", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for MediaMetadataKey", "completion":"VarChar(50).###"}
+{ "prompt": "type for MediaStreamType", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for DeviceAuthenticationPolicy", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for Url", "completion":"VarChar(1000).###"}
+{ "prompt": "type for IMEMode", "completion":"Character(40).###"}
+{ "prompt": "type for Time", "completion":"DateTime.###"}
+{ "prompt": "type for Encoding", "completion":"Character(256).###"}
+{ "prompt": "type for Timezones", "completion":"Character(60).###"}
+{ "prompt": "type for Effect", "completion":"Character(20).###"}
+{ "prompt": "type for CallType", "completion":"Character(20).###"}
+{ "prompt": "type for CryptoEncryptAlgorithm", "completion":"Character(40).###"}
+{ "prompt": "type for CryptoHashAlgorithm", "completion":"Character(40).###"}
+{ "prompt": "type for CryptoSignAlgorithm", "completion":"Character(40).###"}
+{ "prompt": "type for TrnMode", "completion":"Character(3).###"}
+{ "prompt": "type for Address", "completion":"VarChar(1K).###"}
+{ "prompt": "type for Component", "completion":"VarChar(1000).###"}
+{ "prompt": "type for Email", "completion":"VarChar(100).###"}
+{ "prompt": "type for Geolocation", "completion":"Character(50).###"}
+{ "prompt": "type for Html", "completion":"LongVarChar(2M).###"}
+{ "prompt": "type for Phone", "completion":"Character(20).###"}
+{ "prompt": "type for APIAuthorizationStatus", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for MessageTypes", "completion":"Numeric(2.0).###"}
+{ "prompt": "type for ProgressIndicatorType", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for RecentLinksOptions", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for ObjectName", "completion":"VarChar(256).###"}
+{ "prompt": "type for CallTargetSize", "completion":"Character(10).###"}
+{ "prompt": "type for EventExecution", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for PushNotificationPriority", "completion":"Character(20).###"}
+{ "prompt": "type for SmartDeviceType", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for CameraAPIQuality", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for AudioAPISessionType", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for MediaDuration", "completion":"Numeric(12.0).###"}
+{ "prompt": "type for PlaybackState", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for NetworkAPIConnectionType", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for EventAction", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for EventStatus", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for EventData", "completion":"LongVarChar(2M).###"}
+{ "prompt": "type for EventErrors", "completion":"LongVarChar(2M).###"}
+{ "prompt": "type for ApplicationState", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for SynchronizationReceiveResult", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for RegionState", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for BeaconProximity", "completion":"Numeric(1.0).###"}
+{ "prompt": "type for MediaFinishReason", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for HttpMethod", "completion":"Character(7).###"}
+{ "prompt": "type for HttpAuthenticationType", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for CommonCallTarget", "completion":"Character(20).###"}
+{ "prompt": "type for BarcodeType", "completion":"VarChar(40).###"}
+{ "prompt": "type for Name", "completion":"VarChar(100).###"}
+{ "prompt": "type for ContactData", "completion":"VarChar(80).###"}
+{ "prompt": "type for Lang", "completion":"Character(3).###"}
+{ "prompt": "type for Bio", "completion":"LongVarChar(2M).###"}
+{ "prompt": "type for FullName", "completion":"VarChar(150).###"}
+{ "prompt": "type for Status", "completion":"Character(1).###"}
+{ "prompt": "type for Id", "completion":"Numeric(8.0).###"}
+{ "prompt": "type for SessionType", "completion":"Character(1).###"}
+{ "prompt": "type for Title", "completion":"VarChar(160).###"}
+{ "prompt": "type for Abstract", "completion":"VarChar(1000).###"}
+{ "prompt": "type for Position", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for Hashtag", "completion":"VarChar(40).###"}
+{ "prompt": "type for Duration", "completion":"Numeric(3.0).###"}
+{ "prompt": "type for ColorTrack", "completion":"Character(3).###"}
+{ "prompt": "type for Description", "completion":"LongVarChar(2M).###"}
+{ "prompt": "type for SponsorType", "completion":"Character(1).###"}
+{ "prompt": "type for Count", "completion":"Numeric(4.0).###"}
+{ "prompt": "type for ListType", "completion":"Character(1).###"}