Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

- Add FilesEndpoint (endpoint needed for FineTunning) #22

Merged
merged 1 commit into from
Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions OpenAI_API.sln
Original file line number Diff line number Diff line change
@@ -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
Expand Down
41 changes: 41 additions & 0 deletions OpenAI_API/BaseEndpoint.cs
Original file line number Diff line number Diff line change
@@ -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}";
}


}
}
69 changes: 69 additions & 0 deletions OpenAI_API/Files/File.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// unique id for this file, so that it can be referenced in other operations
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }

/// <summary>
/// the name of the file
/// </summary>
[JsonProperty("filename")]
public string Name { get; set; }


/// <summary>
/// What is the purpose of this file, fine-tune, search, etc
/// </summary>
[JsonProperty("Purpose")]
public string Purpose { get; set; }

/// <summary>
/// object type, ie: file, image, etc
/// </summary>
[JsonProperty("object")]
public string Object { get; set; }

/// <summary>
/// The size of the file in bytes
/// </summary>
[JsonProperty("bytes")]
public long Bytes { get; set; }

/// <summary>
/// Timestamp for the creation time of this file
/// </summary>
[JsonProperty("created_at")]
public long CreatedAt { get; set; }

/// <summary>
/// The object was deleted, this attribute is used in the Delete file operation
/// </summary>
[JsonProperty("deleted")]
public bool Deleted { get; set; }


/// <summary>
/// The status of the File (ie when an upload operation was done: "uploaded")
/// </summary>
[JsonProperty("status")]
public string Status { get; set; }

/// <summary>
/// The status details, it could be null
/// </summary>
[JsonProperty("status_details")]
public string StatusDetails { get; set; }

}
}
128 changes: 128 additions & 0 deletions OpenAI_API/Files/FilesEndpoint.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// The API endpoint for operations List, Upload, Delete, Retrieve files
/// </summary>
public class FilesEndpoint : BaseEndpoint
{
public FilesEndpoint(OpenAIAPI api) : base(api) {}

protected override string GetEndpoint() { return "files"; }

/// <summary>
/// Get the list of all files
/// </summary>
/// <returns></returns>
/// <exception cref="HttpRequestException"></exception>
public async Task<List<File>> GetFilesAsync()
{
var response = await GetClient().GetAsync(GetUrl());
string resultAsString = await response.Content.ReadAsStringAsync();

if (response.IsSuccessStatusCode)
{
var files = JsonConvert.DeserializeObject<FilesData>(resultAsString).Data;
return files;
}
throw new HttpRequestException(GetErrorMessage(resultAsString, response, "List files", "Get the list of all files"));
}

/// <summary>
/// Returns information about a specific file
/// </summary>
/// <param name="fileId">The ID of the file to use for this request</param>
/// <returns></returns>
public async Task<File> GetFileAsync(string fileId)
{
var response = await GetClient().GetAsync($"{GetUrl()}/{fileId}");
string resultAsString = await response.Content.ReadAsStringAsync();

if (response.IsSuccessStatusCode)
{
var file = JsonConvert.DeserializeObject<File>(resultAsString);
return file;
}
throw new HttpRequestException(GetErrorMessage(resultAsString, response, "Retrieve file"));
}


/// <summary>
/// Returns the contents of the specific file as string
/// </summary>
/// <param name="fileId">The ID of the file to use for this request</param>
/// <returns></returns>
public async Task<string> 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"));
}

/// <summary>
/// Delete a file
/// </summary>
/// <param name="fileId">The ID of the file to use for this request</param>
/// <returns></returns>
public async Task<File> DeleteFileAsync(string fileId)
{
var response = await GetClient().DeleteAsync($"{GetUrl()}/{fileId}");
string resultAsString = await response.Content.ReadAsStringAsync();

if (response.IsSuccessStatusCode)
{
var file = JsonConvert.DeserializeObject<File>(resultAsString);
return file;
}
throw new HttpRequestException(GetErrorMessage(resultAsString, response, "Delete file"));
}

/// <summary>
/// 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
/// </summary>
/// <param name="file">The name of the file to use for this request</param>
/// <param name="purpose">The intendend purpose of the uploaded documents. Use "fine-tune" for Fine-tuning. This allows us to validate the format of the uploaded file.</param>
public async Task<File> 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<File>(resultAsString);
}
throw new HttpRequestException(GetErrorMessage(resultAsString, response, "Upload file", "Upload a file that contains document(s) to be used across various endpoints/features."));
}
}
/// <summary>
/// A helper class to deserialize the JSON API responses. This should not be used directly.
/// </summary>
class FilesData
{
[JsonProperty("data")]
public List<File> Data { get; set; }
[JsonProperty("object")]
public string Obj { get; set; }
}
}
12 changes: 11 additions & 1 deletion OpenAI_API/OpenAIAPI.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using OpenAI_API.Files;
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -14,6 +15,11 @@ namespace OpenAI_API
/// </summary>
public class OpenAIAPI
{
/// <summary>
/// Base url for OpenAI
/// </summary>
public const string API_URL = "https://api.openai.com/v1/";

/// <summary>
/// The API authentication information to use for API calls
/// </summary>
Expand All @@ -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);
}

/// <summary>
Expand All @@ -53,7 +60,10 @@ public OpenAIAPI(APIAuthentication apiKeys = null, Engine engine = null)
/// </summary>
public SearchEndpoint Search { get; }


/// <summary>
/// The API lets you do operations with files. You can upload, delete or retrieve files. Files can be used for fine-tuning, search, etc.
/// </summary>
public FilesEndpoint Files { get; }



Expand Down
91 changes: 91 additions & 0 deletions OpenAI_Tests/FilesEndpointTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}

}
}
6 changes: 6 additions & 0 deletions OpenAI_Tests/OpenAI_Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@
<ProjectReference Include="..\OpenAI_API\OpenAI_API.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="fine-tuning-data.jsonl">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Loading