Skip to content

Commit

Permalink
Merge pull request #38 from OkGoDoIt/metjuperry/master
Browse files Browse the repository at this point in the history
Merging of @metjuperry: Added embedding endpoint and tests
  • Loading branch information
OkGoDoIt authored Feb 3, 2023
2 parents f3c2b75 + 0788505 commit 8f1bac8
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 2 deletions.
59 changes: 59 additions & 0 deletions OpenAI_API/Embedding/EmbeddingEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Threading.Tasks;

namespace OpenAI_API.Embedding
{
/// <summary>
/// OpenAI’s text embeddings measure the relatedness of text strings by generating an embedding, which is 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.
/// </summary>
public class EmbeddingEndpoint : EndpointBase
{
/// <summary>
/// This allows you to send request to the recommended model without needing to specify. Every request uses the <see cref="Model.AdaTextEmbedding"/> model
/// </summary>
public EmbeddingRequest DefaultEmbeddingRequestArgs { get; set; } = new EmbeddingRequest() { Model = Model.AdaTextEmbedding };

/// <summary>
/// The name of the endpoint, which is the final path segment in the API URL. For example, "embeddings".
/// </summary>
protected override string Endpoint { get { return "embeddings"; } }

/// <summary>
/// Constructor of the api endpoint. Rather than instantiating this yourself, access it through an instance of <see cref="OpenAIAPI"/> as <see cref="OpenAIAPI.Embeddings"/>.
/// </summary>
/// <param name="api"></param>
internal EmbeddingEndpoint(OpenAIAPI api) : base(api) { }

/// <summary>
/// Ask the API to embedd text using the default embedding model <see cref="Model.AdaTextEmbedding"/>
/// </summary>
/// <param name="input">Text to be embedded</param>
/// <returns>Asynchronously returns the embedding result. Look in its <see cref="Data.Embedding"/> property of <see cref="EmbeddingResult.Data"/> to find the vector of floating point numbers</returns>
public async Task<EmbeddingResult> CreateEmbeddingAsync(string input)
{
EmbeddingRequest req = new EmbeddingRequest(DefaultEmbeddingRequestArgs.Model, input);
return await CreateEmbeddingAsync(req);
}

/// <summary>
/// Ask the API to embedd text using a custom request
/// </summary>
/// <param name="request">Request to be send</param>
/// <returns>Asynchronously returns the embedding result. Look in its <see cref="Data.Embedding"/> property of <see cref="EmbeddingResult.Data"/> to find the vector of floating point numbers</returns>
public async Task<EmbeddingResult> CreateEmbeddingAsync(EmbeddingRequest request)
{
return await HttpPost<EmbeddingResult>(postData: request);
}

/// <summary>
/// Ask the API to embedd text using the default embedding model <see cref="Model.AdaTextEmbedding"/>
/// </summary>
/// <param name="input">Text to be embedded</param>
/// <returns>Asynchronously returns the first embedding result as an array of floats.</returns>
public async Task<float[]> GetEmbeddingsAsync(string input)
{
EmbeddingRequest req = new EmbeddingRequest(DefaultEmbeddingRequestArgs.Model, input);
var embeddingResult = await CreateEmbeddingAsync(req);
return embeddingResult?.Data?[0]?.Embedding;
}
}
}
51 changes: 51 additions & 0 deletions OpenAI_API/Embedding/EmbeddingRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Newtonsoft.Json;

namespace OpenAI_API.Embedding
{
/// <summary>
/// Represents a request to the Completions API. Matches with the docs at <see href="https://platform.openai.com/docs/api-reference/embeddings">the OpenAI docs</see>
/// </summary>
public class EmbeddingRequest
{
/// <summary>
/// ID of the model to use. You can use <see cref="ModelsEndpoint.GetModelsAsync()"/> to see all of your available models, or use a standard model like <see cref="Model.AdaTextEmbedding"/>.
/// </summary>
[JsonProperty("model")]
public string Model { get; set; }

/// <summary>
/// Main text to be embedded
/// </summary>
[JsonProperty("input")]
public string Input { get; set; }

/// <summary>
/// Cretes a new, empty <see cref="EmbeddingRequest"/>
/// </summary>
public EmbeddingRequest()
{

}

/// <summary>
/// Creates a new <see cref="EmbeddingRequest"/> with the specified parameters
/// </summary>
/// <param name="model">The model to use. You can use <see cref="ModelsEndpoint.GetModelsAsync()"/> to see all of your available models, or use a standard model like <see cref="Model.AdaTextEmbedding"/>.</param>
/// <param name="input">The prompt to transform</param>
public EmbeddingRequest(Model model, string input)
{
Model = model;
this.Input = input;
}

/// <summary>
/// Creates a new <see cref="EmbeddingRequest"/> with the specified input and the <see cref="Model.AdaTextEmbedding"/> model.
/// </summary>
/// <param name="input">The prompt to transform</param>
public EmbeddingRequest(string input)
{
Model = OpenAI_API.Model.AdaTextEmbedding;
this.Input = input;
}
}
}
79 changes: 79 additions & 0 deletions OpenAI_API/Embedding/EmbeddingResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;

namespace OpenAI_API.Embedding
{
/// <summary>
/// Represents an embedding result returned by the Embedding API.
/// </summary>
public class EmbeddingResult : ApiResultBase
{
/// <summary>
/// List of results of the embedding
/// </summary>
[JsonProperty("data")]
public List<Data> Data { get; set; }

/// <summary>
/// Usage statistics of how many tokens have been used for this request
/// </summary>
[JsonProperty("usage")]
public Usage Usage { get; set; }

/// <summary>
/// Allows an EmbeddingResult to be implicitly cast to the array of floats repsresenting the first ebmedding result
/// </summary>
/// <param name="embeddingResult">The <see cref="EmbeddingResult"/> to cast to an array of floats.</param>
public static implicit operator float[](EmbeddingResult embeddingResult)
{
return embeddingResult.Data.FirstOrDefault()?.Embedding;
}
}

/// <summary>
/// Data returned from the Embedding API.
/// </summary>
public class Data
{
/// <summary>
/// Type of the response. In case of Data, this will be "embedding"
/// </summary>
[JsonProperty("object")]

public string Object { get; set; }

/// <summary>
/// The input text represented as a vector (list) of floating point numbers
/// </summary>
[JsonProperty("embedding")]
public float[] Embedding { get; set; }

/// <summary>
/// Index
/// </summary>
[JsonProperty("index")]
public int Index { get; set; }

}

/// <summary>
/// Usage statistics of how many tokens have been used for this request.
/// </summary>
public class Usage
{
/// <summary>
/// How many tokens did the prompt consist of
/// </summary>
[JsonProperty("prompt_tokens")]
public int PromptTokens { get; set; }

/// <summary>
/// How many tokens did the request consume total
/// </summary>
[JsonProperty("total_tokens")]
public int TotalTokens { get; set; }

}

}
11 changes: 9 additions & 2 deletions OpenAI_API/OpenAIAPI.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using OpenAI_API.Files;
using Newtonsoft.Json;
using OpenAI_API.Embedding;
using System;

namespace OpenAI_API
Expand All @@ -12,7 +14,7 @@ public class OpenAIAPI
/// Base url for OpenAI
/// </summary>
public string ApiUrlBase = "https://api.openai.com/v1/";

/// <summary>
/// The API authentication information to use for API calls
/// </summary>
Expand All @@ -28,14 +30,19 @@ public OpenAIAPI(APIAuthentication apiKeys = null)
Completions = new CompletionEndpoint(this);
Models = new ModelsEndpoint(this);
//Search = new SearchEndpoint(this);
Files = new FilesEndpoint(this);
Embeddings = new EmbeddingEndpoint(this);
}

/// <summary>
/// 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).
/// </summary>
public CompletionEndpoint Completions { get; }

/// <summary>
/// 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.
/// </summary>
public EmbeddingEndpoint Embeddings { get; }

/// <summary>
/// The API endpoint for querying available Engines/models
/// </summary>
Expand Down
43 changes: 43 additions & 0 deletions OpenAI_Tests/EmbeddingEndpointTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using NUnit.Framework;
using OpenAI_API;
using OpenAI_API.Embedding;
using System;
using System.Linq;

namespace OpenAI_Tests
{
public class EmbeddingEndpointTests
{
[SetUp]
public void Setup()
{
OpenAI_API.APIAuthentication.Default = new OpenAI_API.APIAuthentication(Environment.GetEnvironmentVariable("TEST_OPENAI_SECRET_KEY"));
}

[Test]
public void GetBasicEmbedding()
{
var api = new OpenAI_API.OpenAIAPI();

Assert.IsNotNull(api.Embeddings);

var results = api.Embeddings.CreateEmbeddingAsync(new EmbeddingRequest(Model.AdaTextEmbedding, "A test text for embedding")).Result;
Assert.IsNotNull(results);
Assert.NotNull(results.Object);
Assert.NotZero(results.Data.Count);
Assert.That(results.Data.First().Embedding.Length == 1536);
}

[Test]
public void GetSimpleEmbedding()
{
var api = new OpenAI_API.OpenAIAPI();

Assert.IsNotNull(api.Embeddings);

var results = api.Embeddings.GetEmbeddingsAsync("A test text for embedding").Result;
Assert.IsNotNull(results);
Assert.That(results.Length == 1536);
}
}
}

0 comments on commit 8f1bac8

Please sign in to comment.