Giter Site home page Giter Site logo

rageagainstthepixel / com.openai.unity Goto Github PK

View Code? Open in Web Editor NEW
390.0 12.0 56.0 4.64 MB

A Non-Official OpenAI Rest Client for Unity (UPM)

Home Page: https://openai.com/

License: MIT License

C# 100.00%
upm upm-package unity unity3d openai open-ai openupm ai unity-package unity-ml

com.openai.unity's Introduction

OpenAI

Discord openupm openupm

Based on OpenAI-DotNet

A OpenAI package for the Unity to use though their RESTful API. Independently developed, this is not an official library and I am not affiliated with OpenAI. An OpenAI API account is required.

All copyrights, trademarks, logos, and assets are the property of their respective owners.

This repository is available to transfer to the OpenAI organization if they so choose to accept it.

Installing

Requires Unity 2021.3 LTS or higher.

The recommended installation method is though the unity package manager and OpenUPM.

Via Unity Package Manager and OpenUPM

  • Open your Unity project settings
  • Add the OpenUPM package registry:
    • Name: OpenUPM
    • URL: https://package.openupm.com
    • Scope(s):
      • com.openai
      • com.utilities

scoped-registries

  • Open the Unity Package Manager window
  • Change the Registry from Unity to My Registries
  • Add the OpenAI package

Via Unity Package Manager and Git url


Documentation

Table of Contents

There are 4 ways to provide your API keys, in order of precedence:

⚠️ We recommended using the environment variables to load the API key instead of having it hard coded in your source. It is not recommended use this method in production, but only for accepting user credentials, local testing and quick start scenarios.

  1. Pass keys directly with constructor ⚠️
  2. Unity Scriptable Object ⚠️
  3. Load key from configuration file
  4. Use System Environment Variables

You use the OpenAIAuthentication when you initialize the API as shown:

Pass keys directly with constructor

⚠️ We recommended using the environment variables to load the API key instead of having it hard coded in your source. It is not recommended use this method in production, but only for accepting user credentials, local testing and quick start scenarios.

var api = new OpenAIClient("sk-apiKey");

Or create a OpenAIAuthentication object manually

var api = new OpenAIClient(new OpenAIAuthentication("sk-apiKey", "org-yourOrganizationId"));

Unity Scriptable Object

You can save the key directly into a scriptable object that is located in the Assets/Resources folder.

You can create a new one by using the context menu of the project pane and creating a new OpenAIConfiguration scriptable object.

⚠️ Beware checking this file into source control, as other people will be able to see your API key. It is recommended to use the OpenAI-DotNet-Proxy and authenticate users with your preferred OAuth provider.

Create new OpenAIConfiguration

Load key from configuration file

Attempts to load api keys from a configuration file, by default .openai in the current directory, optionally traversing up the directory tree or in the user's home directory.

To create a configuration file, create a new text file named .openai and containing the line:

Organization entry is optional.

Json format
{
  "apiKey": "sk-aaaabbbbbccccddddd",
  "organization": "org-yourOrganizationId"
}
Deprecated format
OPENAI_KEY=sk-aaaabbbbbccccddddd
ORGANIZATION=org-yourOrganizationId

You can also load the configuration file directly with known path by calling static methods in OpenAIAuthentication:

  • Loads the default .openai config in the specified directory:
var api = new OpenAIClient(new OpenAIAuthentication().LoadFromDirectory("path/to/your/directory"));
  • Loads the configuration file from a specific path. File does not need to be named .openai as long as it conforms to the json format:
var api = new OpenAIClient(new OpenAIAuthentication().LoadFromPath("path/to/your/file.json"));

Use System Environment Variables

Use your system's environment variables specify an api key and organization to use.

  • Use OPENAI_API_KEY for your api key.
  • Use OPENAI_ORGANIZATION_ID to specify an organization.
var api = new OpenAIClient(new OpenAIAuthentication().LoadFromEnvironment());

You can also choose to use Microsoft's Azure OpenAI deployments as well.

You can find the required information in the Azure Playground by clicking the View Code button and view a URL like this:

https://{your-resource-name}.openai.azure.com/openai/deployments/{deployment-id}/chat/completions?api-version={api-version}
  • your-resource-name The name of your Azure OpenAI Resource.
  • deployment-id The deployment name you chose when you deployed the model.
  • api-version The API version to use for this operation. This follows the YYYY-MM-DD format.

To setup the client to use your deployment, you'll need to pass in OpenAISettings into the client constructor.

var auth = new OpenAIAuthentication("sk-apiKey");
var settings = new OpenAISettings(resourceName: "your-resource-name", deploymentId: "deployment-id", apiVersion: "api-version");
var api = new OpenAIClient(auth, settings);

Authenticate with MSAL as usual and get access token, then use the access token when creating your OpenAIAuthentication. Then be sure to set useAzureActiveDirectory to true when creating your OpenAISettings.

Tutorial: Desktop app that calls web APIs: Acquire a token

// get your access token using any of the MSAL methods
var accessToken = result.AccessToken;
var auth = new OpenAIAuthentication(accessToken);
var settings = new OpenAISettings(resourceName: "your-resource", deploymentId: "deployment-id", apiVersion: "api-version", useActiveDirectoryAuthentication: true);
var api = new OpenAIClient(auth, settings);

NuGet version (OpenAI-DotNet-Proxy)

Using either the OpenAI-DotNet or com.openai.unity packages directly in your front-end app may expose your API keys and other sensitive information. To mitigate this risk, it is recommended to set up an intermediate API that makes requests to OpenAI on behalf of your front-end app. This library can be utilized for both front-end and intermediary host configurations, ensuring secure communication with the OpenAI API.

Front End Example

In the front end example, you will need to securely authenticate your users using your preferred OAuth provider. Once the user is authenticated, exchange your custom auth token with your API key on the backend.

Follow these steps:

  1. Setup a new project using either the OpenAI-DotNet or com.openai.unity packages.
  2. Authenticate users with your OAuth provider.
  3. After successful authentication, create a new OpenAIAuthentication object and pass in the custom token with the prefix sess-.
  4. Create a new OpenAISettings object and specify the domain where your intermediate API is located.
  5. Pass your new auth and settings objects to the OpenAIClient constructor when you create the client instance.

Here's an example of how to set up the front end:

var authToken = await LoginAsync();
var auth = new OpenAIAuthentication($"sess-{authToken}");
var settings = new OpenAISettings(domain: "api.your-custom-domain.com");
var api = new OpenAIClient(auth, settings);

This setup allows your front end application to securely communicate with your backend that will be using the OpenAI-DotNet-Proxy, which then forwards requests to the OpenAI API. This ensures that your OpenAI API keys and other sensitive information remain secure throughout the process.

Back End Example

In this example, we demonstrate how to set up and use OpenAIProxyStartup in a new ASP.NET Core web app. The proxy server will handle authentication and forward requests to the OpenAI API, ensuring that your API keys and other sensitive information remain secure.

  1. Create a new ASP.NET Core minimal web API project.
  2. Add the OpenAI-DotNet nuget package to your project.
    • Powershell install: Install-Package OpenAI-DotNet-Proxy
    • Manually editing .csproj: <PackageReference Include="OpenAI-DotNet-Proxy" />
  3. Create a new class that inherits from AbstractAuthenticationFilter and override the ValidateAuthentication method. This will implement the IAuthenticationFilter that you will use to check user session token against your internal server.
  4. In Program.cs, create a new proxy web application by calling OpenAIProxyStartup.CreateDefaultHost method, passing your custom AuthenticationFilter as a type argument.
  5. Create OpenAIAuthentication and OpenAIClientSettings as you would normally with your API keys, org id, or Azure settings.
public partial class Program
{
    private class AuthenticationFilter : AbstractAuthenticationFilter
    {
        public override void ValidateAuthentication(IHeaderDictionary request)
        {
            // You will need to implement your own class to properly test
            // custom issued tokens you've setup for your end users.
            if (!request.Authorization.ToString().Contains(userToken))
            {
                throw new AuthenticationException("User is not authorized");
            }
        }
    }

    public static void Main(string[] args)
    {
        var auth = OpenAIAuthentication.LoadFromEnv();
        var settings = new OpenAIClientSettings(/* your custom settings if using Azure OpenAI */);
        var openAIClient = new OpenAIClient(auth, settings);
        var proxy = OpenAIProxyStartup.CreateDefaultHost<AuthenticationFilter>(args, openAIClient);
        proxy.Run();
    }
}

Once you have set up your proxy server, your end users can now make authenticated requests to your proxy api instead of directly to the OpenAI API. The proxy server will handle authentication and forward requests to the OpenAI API, ensuring that your API keys and other sensitive information remain secure.

List and describe the various models available in the API. You can refer to the Models documentation to understand what models are available and the differences between them.

Also checkout model endpoint compatibility to understand which models work with which endpoints.

To specify a custom model not pre-defined in this library:

var model = new Model("model-id");

The Models API is accessed via OpenAIClient.ModelsEndpoint

Lists the currently available models, and provides basic information about each one such as the owner and availability.

var api = new OpenAIClient();
var models = await api.ModelsEndpoint.GetModelsAsync();

foreach (var model in models)
{
    Debug.Log(model.ToString());
}

Retrieves a model instance, providing basic information about the model such as the owner and permissions.

var api = new OpenAIClient();
var model = await api.ModelsEndpoint.GetModelDetailsAsync("text-davinci-003");
Debug.Log(model.ToString());

Delete a fine-tuned model. You must have the Owner role in your organization.

var api = new OpenAIClient();
var isDeleted = await api.ModelsEndpoint.DeleteFineTuneModelAsync("your-fine-tuned-model");
Assert.IsTrue(isDeleted);

⚠️ Beta Feature

Build assistants that can call models and use tools to perform tasks.

The Assistants API is accessed via OpenAIClient.AssistantsEndpoint

Returns a list of assistants.

var api = new OpenAIClient();
var assistantsList = await api.AssistantsEndpoint.ListAssistantsAsync();

foreach (var assistant in assistantsList.Items)
{
    Debug.Log($"{assistant} -> {assistant.CreatedAt}");
}

Create an assistant with a model and instructions.

var api = new OpenAIClient();
var request = new CreateAssistantRequest(Model.GPT4_Turbo);
var assistant = await api.AssistantsEndpoint.CreateAssistantAsync(request);

Retrieves an assistant.

var api = new OpenAIClient();
var assistant = await api.AssistantsEndpoint.RetrieveAssistantAsync("assistant-id");
Debug.Log($"{assistant} -> {assistant.CreatedAt}");

Modifies an assistant.

var api = new OpenAIClient();
var createRequest = new CreateAssistantRequest(Model.GPT3_5_Turbo);
var assistant = await api.AssistantsEndpoint.CreateAssistantAsync(createRequest);
var modifyRequest = new CreateAssistantRequest(Model.GPT4_Turbo);
var modifiedAssistant = await api.AssistantsEndpoint.ModifyAssistantAsync(assistant.Id, modifyRequest);
// OR AssistantExtension for easier use!
var modifiedAssistantEx = await assistant.ModifyAsync(modifyRequest);

Delete an assistant.

var api = new OpenAIClient();
var isDeleted = await api.AssistantsEndpoint.DeleteAssistantAsync("assistant-id");
// OR AssistantExtension for easier use!
var isDeleted = await assistant.DeleteAsync();
Assert.IsTrue(isDeleted);

Returns a list of assistant files.

var api = new OpenAIClient();
var filesList = await api.AssistantsEndpoint.ListFilesAsync("assistant-id");
// OR AssistantExtension for easier use!
var filesList = await assistant.ListFilesAsync();

foreach (var file in filesList.Items)
{
    Debug.Log($"{file.AssistantId}'s file -> {file.Id}");
}

Create an assistant file by attaching a File to an assistant.

var api = new OpenAIClient();
var filePath = "assistant_test_2.txt";
await File.WriteAllTextAsync(filePath, "Knowledge is power!");
var fileUploadRequest = new FileUploadRequest(filePath, "assistant");
var file = await api.FilesEndpoint.UploadFileAsync(fileUploadRequest);
var assistantFile = await api.AssistantsEndpoint.AttachFileAsync("assistant-id", file.Id);
// OR use extension method for convenience!
var assistantFIle = await assistant.AttachFileAsync(file);

Uploads and attaches a file to an assistant.

Assistant extension method, for extra convenience!

var api = new OpenAIClient();
var filePath = "assistant_test_2.txt";
await File.WriteAllTextAsync(filePath, "Knowledge is power!");
var assistantFile = await assistant.UploadFileAsync(filePath);

Retrieves an AssistantFile.

var api = new OpenAIClient();
var assistantFile = await api.AssistantsEndpoint.RetrieveFileAsync("assistant-id", "file-id");
// OR AssistantExtension for easier use!
var assistantFile = await assistant.RetrieveFileAsync(fileId);
Debug.Log($"{assistantFile.AssistantId}'s file -> {assistantFile.Id}");

Remove a file from an assistant.

Note: The file will remain in your organization until deleted with FileEndpoint.

var api = new OpenAIClient();
var isRemoved = await api.AssistantsEndpoint.RemoveFileAsync("assistant-id", "file-id");
// OR use extension method for convenience!
var isRemoved = await assistant.RemoveFileAsync("file-id");
Assert.IsTrue(isRemoved);

Removes a file from the assistant and then deletes the file from the organization.

Assistant extension method, for extra convenience!

var api = new OpenAIClient();
var isDeleted = await assistant.DeleteFileAsync("file-id");
Assert.IsTrue(isDeleted);

⚠️ Beta Feature

Create Threads that Assistants can interact with.

The Threads API is accessed via OpenAIClient.ThreadsEndpoint

Create a thread.

var api = new OpenAIClient();
var thread = await api.ThreadsEndpoint.CreateThreadAsync();
Debug.Log($"Create thread {thread.Id} -> {thread.CreatedAt}");

Create a thread and run it in one request.

See also: Thread Runs

var api = new OpenAIClient();
var assistant = await api.AssistantsEndpoint.CreateAssistantAsync(
    new CreateAssistantRequest(
        name: "Math Tutor",
        instructions: "You are a personal math tutor. Answer questions briefly, in a sentence or less.",
        model: Model.GPT4_Turbo));
var messages = new List<Message> { "I need to solve the equation `3x + 11 = 14`. Can you help me?" };
var threadRequest = new CreateThreadRequest(messages);
var run = await assistant.CreateThreadAndRunAsync(threadRequest);
Debug.Log($"Created thread and run: {run.ThreadId} -> {run.Id} -> {run.CreatedAt}");

Retrieves a thread.

var api = new OpenAIClient();
var thread = await api.ThreadsEndpoint.RetrieveThreadAsync("thread-id");
// OR if you simply wish to get the latest state of a thread
thread = await thread.UpdateAsync();
Debug.Log($"Retrieve thread {thread.Id} -> {thread.CreatedAt}");

Modifies a thread.

Note: Only the metadata can be modified.

var api = new OpenAIClient();
var thread = await api.ThreadsEndpoint.CreateThreadAsync();
var metadata = new Dictionary<string, string>
{
    { "key", "custom thread metadata" }
}
thread = await api.ThreadsEndpoint.ModifyThreadAsync(thread.Id, metadata);
// OR use extension method for convenience!
thread = await thread.ModifyAsync(metadata);
Debug.Log($"Modify thread {thread.Id} -> {thread.Metadata["key"]}");

Delete a thread.

var api = new OpenAIClient();
var isDeleted = await api.ThreadsEndpoint.DeleteThreadAsync("thread-id");
// OR use extension method for convenience!
var isDeleted = await thread.DeleteAsync();
Assert.IsTrue(isDeleted);

Create messages within threads.

Returns a list of messages for a given thread.

var api = new OpenAIClient();
var messageList = await api.ThreadsEndpoint.ListMessagesAsync("thread-id");
// OR use extension method for convenience!
var messageList = await thread.ListMessagesAsync();

foreach (var message in messageList.Items)
{
    Debug.Log($"{message.Id}: {message.Role}: {message.PrintContent()}");
}

Create a message.

var api = new OpenAIClient();
var thread = await api.ThreadsEndpoint.CreateThreadAsync();
var request = new CreateMessageRequest("Hello world!");
var message = await api.ThreadsEndpoint.CreateMessageAsync(thread.Id, request);
// OR use extension method for convenience!
var message = await thread.CreateMessageAsync("Hello World!");
Debug.Log($"{message.Id}: {message.Role}: {message.PrintContent()}");

Retrieve a message.

var api = new OpenAIClient();
var message = await api.ThreadsEndpoint.RetrieveMessageAsync("thread-id", "message-id");
// OR use extension methods for convenience!
var message = await thread.RetrieveMessageAsync("message-id");
var message = await message.UpdateAsync();
Debug.Log($"{message.Id}: {message.Role}: {message.PrintContent()}");

Modify a message.

Note: Only the message metadata can be modified.

var api = new OpenAIClient();
var metadata = new Dictionary<string, string>
{
    { "key", "custom message metadata" }
};
var message = await api.ThreadsEndpoint.ModifyMessageAsync("thread-id", "message-id", metadata);
// OR use extension method for convenience!
var message = await message.ModifyAsync(metadata);
Debug.Log($"Modify message metadata: {message.Id} -> {message.Metadata["key"]}");
Thread Message Files

Returns a list of message files.

var api = new OpenAIClient();
var fileList = await api.ThreadsEndpoint.ListFilesAsync("thread-id", "message-Id");
// OR use extension method for convenience!
var fileList = await thread.ListFilesAsync("message-id");
var fileList = await message.ListFilesAsync();

foreach (var file in fileList.Items)
{
    Debug.Log(file.Id);
}

Retrieves a message file.

var api = new OpenAIClient();
var file = await api.ThreadsEndpoint.RetrieveFileAsync("thread-id", "message-id", "file-id");
// OR use extension method for convenience!
var file = await message.RetrieveFileAsync();
Debug.Log(file.Id);

Represents an execution run on a thread.

Returns a list of runs belonging to a thread.

var api = new OpenAIClient();
var runList = await api.ThreadsEndpoint.ListRunsAsync("thread-id");
// OR use extension method for convenience!
var runList = await thread.ListRunsAsync();

foreach (var run in runList.Items)
{
    Debug.Log($"[{run.Id}] {run.Status} | {run.CreatedAt}");
}

Create a run.

var api = new OpenAIClient();
var assistant = await api.AssistantsEndpoint.CreateAssistantAsync(
    new CreateAssistantRequest(
        name: "Math Tutor",
        instructions: "You are a personal math tutor. Answer questions briefly, in a sentence or less.",
        model: Model.GPT4_Turbo));
var thread = await api.ThreadsEndpoint.CreateThreadAsync();
var message = await thread.CreateMessageAsync("I need to solve the equation `3x + 11 = 14`. Can you help me?");
var run = await thread.CreateRunAsync(assistant);
Debug.Log($"[{run.Id}] {run.Status} | {run.CreatedAt}");

Retrieves a run.

var api = new OpenAIClient();
var run = await api.ThreadsEndpoint.RetrieveRunAsync("thread-id", "run-id");
// OR use extension method for convenience!
var run = await thread.RetrieveRunAsync("run-id");
var run = await run.UpdateAsync();
Debug.Log($"[{run.Id}] {run.Status} | {run.CreatedAt}");

Modifies a run.

Note: Only the metadata can be modified.

var api = new OpenAIClient();
var metadata = new Dictionary<string, string>
{
    { "key", "custom run metadata" }
};
var run = await api.ThreadsEndpoint.ModifyRunAsync("thread-id", "run-id", metadata);
// OR use extension method for convenience!
var run = await run.ModifyAsync(metadata);
Debug.Log($"Modify run {run.Id} -> {run.Metadata["key"]}");

When a run has the status: requires_action and required_action.type is submit_tool_outputs, this endpoint can be used to submit the outputs from the tool calls once they're all completed. All outputs must be submitted in a single request.

var api = new OpenAIClient();
var tools = new List<Tool>
{
    // Use a predefined tool
    Tool.Retrieval, Tool.CodeInterpreter,
    // Or create a tool from a type and the name of the method you want to use for function calling
    Tool.GetOrCreateTool(typeof(WeatherService), nameof(WeatherService.GetCurrentWeatherAsync)),
    // Pass in an instance of an object to call a method on it
    Tool.GetOrCreateTool(OpenAIClient.ImagesEndPoint, nameof(ImagesEndpoint.GenerateImageAsync))),
    // Define func<,> callbacks
    Tool.FromFunc("name_of_func", () => { /* callback function */ }),
    Tool.FromFunc<T1,T2,TResult>("func_with_multiple_params", (t1, t2) => { /* logic that calculates return value */ return tResult; })
};
var assistantRequest = new CreateAssistantRequest(tools: tools, instructions: "You are a helpful weather assistant. Use the appropriate unit based on geographical location.");
var testAssistant = await OpenAIClient.AssistantsEndpoint.CreateAssistantAsync(assistantRequest);
var run = await testAssistant.CreateThreadAndRunAsync("I'm in Kuala-Lumpur, please tell me what's the temperature now?");
// waiting while run is Queued and InProgress
run = await run.WaitForStatusChangeAsync();

// Invoke all of the tool call functions and return the tool outputs.
var toolOutputs = await testAssistant.GetToolOutputsAsync(run.RequiredAction.SubmitToolOutputs.ToolCalls);

foreach (var toolOutput in toolOutputs)
{
    Debug.Log($"tool call output: {toolOutput.Output}");
}
// submit the tool outputs
run = await run.SubmitToolOutputsAsync(toolOutputs);
// waiting while run in Queued and InProgress
run = await run.WaitForStatusChangeAsync();
var messages = await run.ListMessagesAsync();

foreach (var message in messages.Items.OrderBy(response => response.CreatedAt))
{
    Debug.Log($"{message.Role}: {message.PrintContent()}");
}

Returns a list of run steps belonging to a run.

var api = new OpenAIClient();
var runStepList = await api.ThreadsEndpoint.ListRunStepsAsync("thread-id", "run-id");
// OR use extension method for convenience!
var runStepList = await run.ListRunStepsAsync();

foreach (var runStep in runStepList.Items)
{
    Debug.Log($"[{runStep.Id}] {runStep.Status} {runStep.CreatedAt} -> {runStep.ExpiresAt}");
}

Retrieves a run step.

var api = new OpenAIClient();
var runStep = await api.ThreadsEndpoint.RetrieveRunStepAsync("thread-id", "run-id", "step-id");
// OR use extension method for convenience!
var runStep = await run.RetrieveRunStepAsync("step-id");
var runStep = await runStep.UpdateAsync();
Debug.Log($"[{runStep.Id}] {runStep.Status} {runStep.CreatedAt} -> {runStep.ExpiresAt}");

Cancels a run that is in_progress.

var api = new OpenAIClient();
var isCancelled = await api.ThreadsEndpoint.CancelRunAsync("thread-id", "run-id");
// OR use extension method for convenience!
var isCancelled = await run.CancelAsync();
Assert.IsTrue(isCancelled);

Given a chat conversation, the model will return a chat completion response.

The Chat API is accessed via OpenAIClient.ChatEndpoint

Creates a completion for the chat message

var api = new OpenAIClient();
var messages = new List<Message>
{
    new Message(Role.System, "You are a helpful assistant."),
    new Message(Role.User, "Who won the world series in 2020?"),
    new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
    new Message(Role.User, "Where was it played?"),
};
var chatRequest = new ChatRequest(messages, Model.GPT4_Turbo);
var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
var choice = response.FirstChoice;
Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice.Message} | Finish Reason: {choice.FinishReason}");
var api = new OpenAIClient();
var messages = new List<Message>
{
    new Message(Role.System, "You are a helpful assistant."),
    new Message(Role.User, "Who won the world series in 2020?"),
    new Message(Role.Assistant, "The Los Angeles Dodgers won the World Series in 2020."),
    new Message(Role.User, "Where was it played?"),
};
var chatRequest = new ChatRequest(messages);
var response = await api.ChatEndpoint.StreamCompletionAsync(chatRequest, partialResponse =>
{
    Debug.Log(partialResponse.FirstChoice.Delta.ToString());
});
var choice = response.FirstChoice;
Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice.Message} | Finish Reason: {choice.FinishReason}");
var api = new OpenAIClient();
var messages = new List<Message>
{
    new(Role.System, "You are a helpful weather assistant. Always prompt the user for their location."),
    new Message(Role.User, "What's the weather like today?"),
};

foreach (var message in messages)
{
    Debug.Log($"{message.Role}: {message}");
}

// Define the tools that the assistant is able to use:
// 1. Get a list of all the static methods decorated with FunctionAttribute
var tools = Tool.GetAllAvailableTools(includeDefaults: false, forceUpdate: true, clearCache: true);
// 2. Define a custom list of tools:
var tools = new List<Tool>
{
    Tool.GetOrCreateTool(objectInstance, "TheNameOfTheMethodToCall"),
    Tool.FromFunc("a_custom_name_for_your_function", ()=> { /* Some logic to run */ })
};
var chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
messages.Add(response.FirstChoice.Message);

Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}");

var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland");
messages.Add(locationMessage);
Debug.Log($"{locationMessage.Role}: {locationMessage.Content}");
chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);

messages.Add(response.FirstChoice.Message);

if (response.FirstChoice.FinishReason == "stop")
{
    Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice} | Finish Reason: {response.FirstChoice.FinishReason}");

    var unitMessage = new Message(Role.User, "Fahrenheit");
    messages.Add(unitMessage);
    Debug.Log($"{unitMessage.Role}: {unitMessage.Content}");
    chatRequest = new ChatRequest(messages, tools: tools, toolChoice: "auto");
    response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
}

// iterate over all tool calls and invoke them
foreach (var toolCall in response.FirstChoice.Message.ToolCalls)
{
    Debug.Log($"{response.FirstChoice.Message.Role}: {toolCall.Function.Name} | Finish Reason: {response.FirstChoice.FinishReason}");
    Debug.Log($"{toolCall.Function.Arguments}");
    // Invokes function to get a generic json result to return for tool call.
    var functionResult = await toolCall.InvokeFunctionAsync();
    // If you know the return type and do additional processing you can use generic overload
    var functionResult = await toolCall.InvokeFunctionAsync<string>();
    messages.Add(new Message(toolCall, functionResult));
    Debug.Log($"{Role.Tool}: {functionResult}");
}
// System: You are a helpful weather assistant.
// User: What's the weather like today?
// Assistant: Sure, may I know your current location? | Finish Reason: stop
// User: I'm in Glasgow, Scotland
// Assistant: GetCurrentWeather | Finish Reason: tool_calls
// {
//   "location": "Glasgow, Scotland",
//   "unit": "celsius"
// }
// Tool: The current weather in Glasgow, Scotland is 39°C.

⚠️ Beta Feature

var api = new OpenAIClient();
var messages = new List<Message>
{
    new Message(Role.System, "You are a helpful assistant."),
    new Message(Role.User, new List<Content>
    {
        "What's in this image?",
        new ImageUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", ImageDetail.Low)
    })
};
var chatRequest = new ChatRequest(messages, model: Model.GPT4_Turbo);
var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);
Debug.Log($"{response.FirstChoice.Message.Role}: {response.FirstChoice.Message.Content} | Finish Reason: {response.FirstChoice.FinishDetails}");

You can even pass in a Texture2D!

var api = new OpenAIClient();
var messages = new List<Message>
{
    new Message(Role.System, "You are a helpful assistant."),
    new Message(Role.User, new List<Content>
    {
        "What's in this image?",
        texture
    })
};
var chatRequest = new ChatRequest(messages, model: Model.GPT4_Turbo);
var result = await apiChatEndpoint.GetCompletionAsync(chatRequest);
Debug.Log($"{result.FirstChoice.Message.Role}: {result.FirstChoice} | Finish Reason: {result.FirstChoice.FinishDetails}");

⚠️ Beta Feature

Important notes:

  • When using JSON mode, always instruct the model to produce JSON via some message in the conversation, for example via your system message. If you don't include an explicit instruction to generate JSON, the model may generate an unending stream of whitespace and the request may run continually until it reaches the token limit. To help ensure you don't forget, the API will throw an error if the string "JSON" does not appear somewhere in the context.
  • The JSON in the message the model returns may be partial (i.e. cut off) if finish_reason is length, which indicates the generation exceeded max_tokens or the conversation exceeded the token limit. To guard against this, check finish_reason before parsing the response.
  • JSON mode will not guarantee the output matches any specific schema, only that it is valid and parses without errors.
var messages = new List<Message>
{
    new Message(Role.System, "You are a helpful assistant designed to output JSON."),
    new Message(Role.User, "Who won the world series in 2020?"),
};
var chatRequest = new ChatRequest(messages, Model.GPT4_Turbo, responseFormat: ChatResponseFormat.Json);
var response = await api.ChatEndpoint.GetCompletionAsync(chatRequest);

foreach (var choice in response.Choices)
{
    Debug.Log($"[{choice.Index}] {choice.Message.Role}: {choice} | Finish Reason: {choice.FinishReason}");
}

response.GetUsage();

Converts audio into text.

The Audio API is accessed via OpenAIClient.AudioEndpoint

Generates audio from the input text.

var api = new OpenAIClient();
var request = new SpeechRequest("Hello world!");
var (path, clip) = await api.AudioEndpoint.CreateSpeechAsync(request);
audioSource.PlayOneShot(clip);
Debug.Log(path);
[Stream Speech]

Generate streamed audio from the input text.

var api = new OpenAIClient();
var request = new SpeechRequest("Hello world!");
var (path, clip) = await OpenAIClient.AudioEndpoint.CreateSpeechStreamAsync(request, partialClip => audioSource.PlayOneShot(partialClip));
Debug.Log(path);

Transcribes audio into the input language.

var api = new OpenAIClient();
var request = new AudioTranscriptionRequest(audioClip, language: "en");
var result = await api.AudioEndpoint.CreateTranscriptionAsync(request);
Debug.Log(result);

You can also get detailed information using verbose_json to get timestamp granularities:

var api = new OpenAIClient();
using var request = new AudioTranscriptionRequest(transcriptionAudio, responseFormat: AudioResponseFormat.Verbose_Json, timestampGranularity: TimestampGranularity.Word, temperature: 0.1f, language: "en");
var response = await api.AudioEndpoint.CreateTranscriptionTextAsync(request);

foreach (var word in response.Words)
{
    Debug.Log($"[{word.Start}-{word.End}] \"{word.Word}\"");
}

Translates audio into into English.

var api = new OpenAIClient();
var request = new AudioTranslationRequest(audioClip);
var result = await api.AudioEndpoint.CreateTranslationAsync(request);
Debug.Log(result);

Given a prompt and/or an input image, the model will generate a new image.

The Images API is accessed via OpenAIClient.ImagesEndpoint

Creates an image given a prompt.

var api = new OpenAIClient();
var request = new ImageGenerationRequest("A house riding a velociraptor", Models.Model.DallE_3);
var imageResults = await api.ImagesEndPoint.GenerateImageAsync(request);

foreach (var result in imageResults)
{
    Debug.Log(result.ToString());
    Assert.IsNotNull(result.Texture);
}

Creates an edited or extended image given an original image and a prompt.

var api = new OpenAIClient();
var request = new ImageEditRequest(Path.GetFullPath(imageAssetPath), Path.GetFullPath(maskAssetPath), "A sunlit indoor lounge area with a pool containing a flamingo", size: ImageSize.Small);
var imageResults = await api.ImagesEndPoint.CreateImageEditAsync(request);

foreach (var result in imageResults)
{
    Debug.Log(result.ToString());
    Assert.IsNotNull(result.Texture);
}

Creates a variation of a given image.

var api = new OpenAIClient();
var request = new ImageVariationRequest(imageTexture, size: ImageSize.Small);
var imageResults = await api.ImagesEndPoint.CreateImageVariationAsync(request);

foreach (var result in imageResults)
{
    Debug.Log(result.ToString());
    Assert.IsNotNull(result.Texture);
}

Alternatively, the endpoint can directly take a Texture2D with Read/Write enabled and Compression set to None.

var api = new OpenAIClient();
var request = new ImageVariationRequest(imageTexture, size: ImageSize.Small);
var imageResults = await api.ImagesEndPoint.CreateImageVariationAsync(request);

foreach (var result in imageResults)
{
    Debug.Log(result.ToString());
    Assert.IsNotNull(result.Texture);
}

Files are used to upload documents that can be used with features like Fine-tuning.

The Files API is accessed via OpenAIClient.FilesEndpoint

Returns a list of files that belong to the user's organization.

var api = new OpenAIClient();
var fileList = await api.FilesEndpoint.ListFilesAsync();

foreach (var file in fileList)
{
    Debug.Log($"{file.Id} -> {file.Object}: {file.FileName} | {file.Size} bytes");
}

Upload a file that can be used across various endpoints. The size of all the files uploaded by one organization can be up to 100 GB.

The size of individual files can be a maximum of 512 MB. See the Assistants Tools guide to learn more about the types of files supported. The Fine-tuning API only supports .jsonl files.

var api = new OpenAIClient();
var fileData = await api.FilesEndpoint.UploadFileAsync("path/to/your/file.jsonl", "fine-tune");
Debug.Log(fileData.Id);

Delete a file.

var api = new OpenAIClient();
var isDeleted = await api.FilesEndpoint.DeleteFileAsync(fileId);
Assert.IsTrue(isDeleted);

Returns information about a specific file.

var api = new OpenAIClient();
var file = await  api.FilesEndpoint.GetFileInfoAsync(fileId);
Debug.Log($"{file.Id} -> {file.Object}: {file.FileName} | {file.Size} bytes");

Downloads the file content to the specified directory.

var api = new OpenAIClient();
var downloadedFilePath = await api.FilesEndpoint.DownloadFileAsync(fileId);
Debug.Log(downloadedFilePath);
Assert.IsTrue(File.Exists(downloadedFilePath));

Manage fine-tuning jobs to tailor a model to your specific training data.

Related guide: Fine-tune models

The Files API is accessed via OpenAIClient.FineTuningEndpoint

Creates a job that fine-tunes a specified model from a given dataset.

Response includes details of the enqueued job including job status and the name of the fine-tuned models once complete.

var api = new OpenAIClient();
var fileId = "file-abc123";
var request = new CreateFineTuneRequest(fileId);
var job = await api.FineTuningEndpoint.CreateJobAsync(Model.GPT3_5_Turbo, request);
Debug.Log($"Started {job.Id} | Status: {job.Status}");

List your organization's fine-tuning jobs.

var api = new OpenAIClient();
var jobList = await api.FineTuningEndpoint.ListJobsAsync();

foreach (var job in jobList.Items.OrderByDescending(job => job.CreatedAt)))
{
    Debug.Log($"{job.Id} -> {job.CreatedAt} | {job.Status}");
}

Gets info about the fine-tune job.

var api = new OpenAIClient();
var job = await api.FineTuningEndpoint.GetJobInfoAsync(fineTuneJob);
Debug.Log($"{job.Id} -> {job.CreatedAt} | {job.Status}");

Immediately cancel a fine-tune job.

var api = new OpenAIClient();
var isCancelled = await api.FineTuningEndpoint.CancelFineTuneJobAsync(fineTuneJob);
Assert.IsTrue(isCancelled);

Get status updates for a fine-tuning job.

var api = new OpenAIClient();
var eventList = await api.FineTuningEndpoint.ListJobEventsAsync(fineTuneJob);
Debug.Log($"{fineTuneJob.Id} -> status: {fineTuneJob.Status} | event count: {eventList.Events.Count}");

foreach (var @event in eventList.Items.OrderByDescending(@event => @event.CreatedAt))
{
    Debug.Log($"  {@event.CreatedAt} [{@event.Level}] {@event.Message}");
}

Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms.

Related guide: Embeddings

The Edits API is accessed via OpenAIClient.EmbeddingsEndpoint

Creates an embedding vector representing the input text.

var api = new OpenAIClient();
var response = await api.EmbeddingsEndpoint.CreateEmbeddingAsync("The food was delicious and the waiter...", Models.Embedding_Ada_002);
Debug.Log(response);

Given a input text, outputs if the model classifies it as violating OpenAI's content policy.

Related guide: Moderations

The Moderations API can be accessed via OpenAIClient.ModerationsEndpoint

Classifies if text violates OpenAI's Content Policy.

var api = new OpenAIClient();
var isViolation = await api.ModerationsEndpoint.GetModerationAsync("I want to kill them.");
Assert.IsTrue(isViolation);

Additionally you can also get the scores of a given input.

var api = new OpenAIClient();
var response = await api.ModerationsEndpoint.CreateModerationAsync(new ModerationsRequest("I love you"));
Assert.IsNotNull(response);
Debug.Log(response.Results?[0]?.Scores?.ToString());

com.openai.unity's People

Contributors

damiant3 avatar evancrabtree avatar stephenhodgson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

com.openai.unity's Issues

Unable to generate image in Unity android and ios build

Bug Report

Overview

I am having an issue in Unity android and ios build for an AR app.
The code to generate image in realtime works fine in unity editor and in the Windows build. But once I build the app for android and ios the image doesn't gets generated. After debugging I have found out that the request does gets sent from both android and ios but it doesn't send the prompt that's used for image generation. Error says "prompt is a required property" and its 400 bad request error. I think there is a bug in the GenerateImageAsync function of the ImagesEndpoint class that causes this behavior.

To Reproduce

Steps to reproduce the behavior:

  1. Create a 3D unity project with a cube that has a material.
  2. Create a canvas in the scene with an input field for prompt and a button to send request to api for image generation.
  3. After image is generated and downloaded set the texture of the material that is attached to cube to the generated image to see result.
  4. Build the app for android or ios and test the same behavior on your device. The image won't get generated and the error will be present in logs that prompt is a required property meaning that it isn't being sent from app. The error message can also be seen by displaying it in a text field in the scene.

Expected behavior

I expected that the image would be generated in the both the android and ios build as it gets generated in the Unity Editor and in the Windows build.
WhatsApp Image 2023-08-29 at 1 28 08 AM

JsonSerializationException in WebGL build

Bug Report

Overview

JsonSerializationException is thrown when the Unity project is uploaded and run as a WebGL app.

To Reproduce

Create a Unity project with OpenAI.Unity
When the app call api.EmbeddingsEndpoint.CreateEmbeddingAsync, the following exception is thrown, probably because it uses OpenAI.Embeddings.EmbeddingsResponse.

JsonSerializationException: Unable to find a constructor to use for type OpenAI.Embeddings.EmbeddingsResponse. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'object', line 2, position 11.
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject (Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract objectContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, Newtonsoft.Json.Serialization.JsonProperty containerProperty, System.String id, System.Boolean& createdFromNonDefaultCreator) [0x00000] in <00000000000000000000000000000000>:0 
--- End of stack trace from previous location where exception was thrown ---

Rethrow as AggregateException: One or more errors occurred. (Unable to find a constructor to use for type OpenAI.Embeddings.EmbeddingsResponse. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'object', line 2, position 11.)
  at System.Threading.Tasks.Task.ThrowIfExceptional (System.Boolean includeTaskCanceledExceptions) [0x00000] in <00000000000000000000000000000000>:0 

Expected behavior

no such exception happens when the app is run on Unity Editor.

Additional context

I asked ChatGPT to fix the EmbeddingsResponse class, and it gave me a fixed code below, and this does seems to work without the exception.

I am not knowledgeable enough to tell if this fix is good or not.

using Newtonsoft.Json;
using System.Collections.Generic;

namespace OpenAI.Embeddings
{
    public sealed class EmbeddingsResponse : BaseResponse
    {
        [JsonConstructor]
        public EmbeddingsResponse(
            [JsonProperty("object")] string @object,
            [JsonProperty("data")] IReadOnlyList<Datum> data,
            [JsonProperty("model")] string model,
            [JsonProperty("usage")] Usage usage)
        {
            Object = @object;
            Data = data;
            Model = model;
            Usage = usage;
        }

        [JsonProperty("object")]
        public string Object { get; private set; }

        [JsonProperty("data")]
        public IReadOnlyList<Datum> Data { get; private set; }

        [JsonProperty("model")]
        public string Model { get; private set; }

        [JsonProperty("usage")]
        public Usage Usage { get; private set; }
    }
}

feat(ChatEndpoint): implement TODO Streaming endpoints

public async Task StreamCompletionAsync(Action<ChatResponse> resultHandler, ChatRequest chatRequest, CancellationToken cancellationToken = default)
        {
            chatRequest.Stream = true;
            var jsonContent = JsonConvert.SerializeObject(chatRequest, Api.JsonSerializationOptions);
            using var request = new HttpRequestMessage(HttpMethod.Post, $"{GetEndpoint()}/completions")
            {
                Content = jsonContent.ToJsonStringContent()
            };
            var response = await Api.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
            await response.CheckResponseAsync(cancellationToken);
            await using var stream = await response.Content.ReadAsStreamAsync();
            using var reader = new StreamReader(stream);

            while (await reader.ReadLineAsync() is { } line)
            {
                if (line.StartsWith("data: "))
                {
                    line = line["data: ".Length..];
                }

                if (line == "[DONE]")
                {
                    return;
                }

                if (!string.IsNullOrWhiteSpace(line))
                {
                    resultHandler(DeserializeResult(response, line.Trim()));
                }
            }
        }

        private ChatResponse DeserializeResult(HttpResponseMessage response, string json)
        {
            var result = JsonConvert.DeserializeObject<ChatResponse>(json, Api.JsonSerializationOptions);

            if (result?.FirstChoice == null)
            {
                throw new HttpRequestException($"{nameof(DeserializeResult)} no completions! HTTP status code: {response.StatusCode}. Response body: {json}");
            }

            result.SetResponseData(response.Headers);

            return result;
        }

Add Fine-Tuning endpoints

Hello, is there a way to "fine-tune" or "train" any of the models that I can use with this plugin, using new data?

I found the official Open-AI fine-tuning tutorial, should I just follow that? Would there be any extra steps? Thank you.

Hardcoded paths causing errors when trying to install package

Bug Report

When adding the package, errors are populated with hardcoded paths such as:

[Package Manager Window] Error adding package: https://github.com/RageAgainstThePixel/com.openai.unity.git#upm.
UnityEditor.EditorApplication:Internal_CallUpdateFunctions () (at /Users/bokken/build/output/unity/unity/Editor/Mono/EditorApplication.cs:356)

AudioTranscriptionRequest.Temperature property should be of float? type

Hi! I am testing the whisper model and when I request audio transcription, I want to add temperature and language parameters. I can add temperature but it is set to integer and it has to float as far as I know from this doc; API reference page, I want to set it low like 0.1f to avoid hallucinations.
As for the language, I want to set it to English to improve accuracy but I couldn't find it in the parameters of the request function.
How can this issue be resolved?
Thanks in advance.

AudioTranscriptionRequest AudioClip Encoding Issues

Bug Report

Overview

using AudioTranscriptionRequest does not return the words I say. Tested in german and english. Both are giving me random lines.

For instance If I say "hallo" it response with "hää?" and in english as "huh?". I also used pre recorded wav files. It is same. Sometimes it also repeat words 3 - 10 times

CreateSpeechAsync doesn't work on WebGL builds

Bug Report

Overview

openAI.AudioEndpoint.CreateSpeechAsync throw the following error:

63fc5e1f-3537-4cd6-b727-3c7cc79dd4f7:10 System.IO.DirectoryNotFoundException: Could not find a part of the path "/tmp/download_cache/Nova-20231130T120608.mp3".
  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x00000] in <00000000000000000000000000000000>:0 
--- End of stack trace from previous location where exception was thrown ---

Likely due to some limitation of WebGL

To Reproduce

Steps to reproduce the behavior:
Call CreateSpeechAsync using something similar to the sample code in README

        try
        {
            var request = new SpeechRequest(text, Model.TTS_1, voice: SpeechVoice.Nova);
            Debug.Log("Asking TTS_1...");
            openAI.AudioEndpoint.EnableDebug = true;
            var (clipPath, clip) = await openAI.AudioEndpoint.CreateSpeechAsync(request, lifetimeCancellationTokenSource.Token);
            audioSource.clip = clip;
            audioSource.Play();
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }

Expected behavior

I should get an audioClip and its path in return as worked in editor and other platforms

Screenshots

Screenshot 2023-11-29 at 4 14 08 PM

Additional context

When using GPT-4 with ChatRequest, get a bunch of other stuff in the received string

Bug Report

model = await api.ModelsEndpoint.GetModelDetailsAsync("gpt-4");

when I make a ChatRequest, the result string starts with:

{"id":"chatcmpl-6vCy4dpOyIf1kZy9OiZjsy0CtwGpS","object":"chat.completion","created":1679092564,"model":"gpt-3.5-turbo-0301","usage":{"prompt_tokens":451,"completion_tokens":335,"total_tokens":786},"choices":[{"message":{"role":"assistant","content":"

then the normal content, then ends with:

"},"delta":null,"finish_reason":"stop","index":0}],"ProcessingTime":"00:00:11.0960000","Organization":"ORGID","RequestId":"RequestID}

iOS: JsonSerializationException: Cannot deserialize readonly or fixed size dictionary: System.Collections.Generic.IReadOnlyDictionary

ExceptioniOS

Having exception with serialization metadata on iOS, works in Unity Editor and Android.

Unity 2022.3.14f
Managed stripping level: Minimal

I believe the issue is with stripping backend which is il2cpp for iOS and Mono for Android. Somehow, on iOS doesn't support serialization of:

        [Preserve]
        [JsonProperty("metadata")]
        public IReadOnlyDictionary<string, string> Metadata { get; }

Tried to pass null, empty Dictionary, both didn't work

Please add instructions to how to serialize a request/message

Hello! It's often handy to serialize a request so you can use it later. However, using standard Json.Net settings to serialize results in an error. After a bit of digging. I found client.JsonSerializationOptions, but it's internal. Can I suggest making it/something similar accessible and including a section on serialization in your docs?

ChatEndpoint.StreamCompletionAsync ends up with Choices[0].Message as Null

Bug Report

Overview

When using ChatEndpoint.StreamCompletionAsync the streaming works fine, but when the streaming is finished the ChatResponse contains some nulls.

To Reproduce

Steps to reproduce the behavior:

  1. Call ChatEndpoint.StreamCompletionAsync
  2. When result is finished
  3. Look at the ChatResponse
  4. See the null

Expected behavior

After the ChatEndpoint.StreamCompletionAsync is finished I would expect the ChatResponse to not contain any null values.

Screenshots

Additional context

Are you going to add Error handling please?

Scanning for OpenAIConfiguration resources is very slow when one doesn't even exist

Bug Report

Overview

When loading the client for the first time, it causes some serious frame stutters (1-2s on my beefy machine). It seems related to the use of Resources.LoadAll on line 18 in OpenAISettings.cs. After which it seems to cache the value.

var config = Resources.LoadAll<OpenAIConfiguration>(string.Empty)

Seems we can just provide our own settings to avoid this call as shown in the README

var auth = new OpenAIAuthentication("sk-apiKey");
var settings = new OpenAISettings(resourceName: "your-resource-name", deploymentId: "deployment-id", apiVersion: "api-version");
var api = new OpenAIClient(auth, settings);

But perhaps it would be better to force passing in the specific scriptableObject if that's what a developer wants to use.

At minimum, calling out the performance hit for not passing in settings manually in the README would be helpful.

Thanks for making a library that's up to date!

JsonSerializationOptions is inaccessible due to its protection level

hello
The default JsonSerializationOptions in OpenAIClient is defined as internal. So it's not possible to DeserializeObject correctly in some case (example: content="\n") return null for content.

I have modified the ContractResolver by DefaultContractResolver and it works.
internal static JsonSerializerSettings JsonSerializationOptions { get; } = new JsonSerializerSettings
{
.......
ContractResolver = new DefaultContractResolver() //TBN EmptyToNullStringContractResolver()
};

That would be better if we could change the JsonSerializationOptions from the OpenAIClient (or I didn't find how to?)

Additional context

Thanks for this awesome package. I'm so impressed by the code quality and documentation. Bravo!

Message.Contents was a string and is now an object

How is Message.Contents expected to be handled now? Delta.Contents is still a string but not the case for Message

Please show an example of how streaming chat using Message.Contents should work, as the example on the github readme is now invalid

Unable to build due to conflicting versions of IAsyncEnumerable

I'm unable to build on latest due to the following error:

Library/PackageCache/com.openai.unity@0fe2225f7f/Runtime/Completions/CompletionsEndpoint.cs(269,16): error CS0433: The type 'IAsyncEnumerable<T>' exists in both 'Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' and 'netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'

I believe this is because the Amazon AWS API that exists in our Assets folder includes Microsoft.Bcl.AsyncInterfaces.cs. I'm actually not sure how to get around this!
I can't work out how to use UPM to downgrade to an earlier version of this package either.

Sorry to submit a bug that I suspect has nothing directly related to your package - but I'd be interested to hear if you know the solution, especially since I'd imagine using AWS and OpenAI isn't an uncommon use case.

Dall-E 2 Azure not support

Feature Request

OpenAIAuthentication.LoadFromDirectory not support for Azure OpenAI. I tried and get 400 bad request. can use via script.
I use Azure OpenAI api to generate image, edit image and get bad request 404 because different format.
https://resourceName.openai.azure.com/dalle/text-to-image?api-version=2022-08-03-preview { "caption": "USER_PROMPT_GOES_HERE", "resolution": "1024x1024" }
I use Azure OpenAI because in my country OpenAI is not available.

Provide alternative synchronous calls that return on unity thread

Feature Request

Some devs aren't as familiar with how to use async/await in the unity editor.

To mitigate this, each of the API calls could have a synchronous call with a callback that is invoked on the main thread.
This would help some novice developers implement the library in their applications and provide a more familiar way to work with the library.

JSON mode

Feature Request

Is your feature request related to a problem? Please describe.

Sometimes the reply will not be JSON format

Describe the solution you'd like

OpenAI announced that a new feature is being able to specify the model return in strict JSON format.

Describe alternatives you've considered

I currently run a filter to try and catch bad formatted chat JSON returns

Additional context

Blog about it here: https://betterprogramming.pub/return-json-from-gpt-65d40bfc2ef6

TTS on Android

Dear,
First of all, love your package!! Most complete one I have tried :)

I can't seem to get TTS running on Android (Meta Quest 3).
Works perfectly in unity editor 2022.3.10f1
Suspect the cacheDirectory in AudioEndpoint to be the issue?
Although I would have to do further debugging to verify.

Cheers, Wim

OpenAIConfigurationSettings inspector input fields aren't properly validated

We validate the inputs for the AuthInfo but not the OpenAIConfigurationSettings.asset

We should validate both apiKey and organization strings.

ChatEndpoint GetCompletionAsync receiving response code 500 Invalid Headers when CORS is not set up properly

I run into an issue in my webGL build.

When using the ChatEndpoint.GetCompletionAsync method, I receive this exception:

Reason: Utilities.WebRequestRest.RestException: [500] <color="cyan">https://oai-dream-shared-stg.openai.azure.com/openai/deployments/$my_resource_name$/chat/completions?api-version=2023-03-15-preview <color="red">Failed!
[Headers]
Invalid Headers: Invalid Headers
[Errors]
Unknown Error

at Utilities.WebRequestRest.Rest.Validate (Utilities.WebRequestRest.Response response, System.Boolean debug, System.String methodName) [0x00000] in <00000000000000000000000000000000>:0

Non webGL everything works fine. I am currently trying to find out which headers are being sent, and if they are different to the ones being sent on non-webgl.

WebGL Build Local Cache

Bug Report

Overview

In WebGL Build the file storage not working and is not possible debug the process

Test

System.IO.DirectoryNotFoundException: Could not find a part of the path "/tmp/download_cache/Alloy-20231122T105455.mp3".
  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x00000] in <00000000000000000000000000000000>:0 
--- End of stack trace from previous location where exception was thrown ---

Minimum compatible Unity version?

Do you know what the minimum required version of Unity is for this package to work? It appears not to work in 2019.4.16f1 at least

CompletionResult.ToString() gives a concatenated sub-string of the OpenAI response

Example string sent:

"You are Big Brain, an aloof AI in charge of this domain. Greet the player and tell him to enter the transfer hole"

result = await api.CompletionsEndpoint.CreateCompletionAsync(inputText.text, temperature: 0.1, model: model);

result.ToString() produces: "Greetings, Player. I am Big Brain, the AI in charge of"

This seems like only part of the OpenAI output. Is the full string stored somewhere else?

Great package by the way!

Easily Swap Endpoints to non OpenAI/Microsoft sources

Feature Request

Is your feature request related to a problem? Please describe.

Easily Swap Endpoints to non OpenAI/Microsoft sources

Describe the solution you'd like

Something that does not require going in your OpenAISettingsInfo and manually replacing internal const strings

Describe alternatives you've considered

Maybe simply exposing endpoint url in inspector

Also ideally more easily editable schema mapping

Additional context

Lots of solutions other than OpenAI/MS providing streaming chat completions etc.

Add Fine Tuning window to train models right in the Unity Editor

Adds the ability to train and fine tune models right in the Unity Editor.

Requirements

  • Create and edit fine tune job training data
  • Create fine tune jobs
  • List fine tune jobs
  • Update the status of fine tune jobs
  • Cancel in progress fine tune jobs
  • Delete a fine tuned model

<_UIKBFeedbackGenerator: 0x28240bd00>: Couldn't update haptics muting for dictation audio recording. No engine.

Bug Report

Overview

when dictation on keyboard is attempted this occurs
<_UIKBFeedbackGenerator: 0x28240bd00>: Couldn't update haptics muting for dictation audio recording. No engine.

To Reproduce

Steps to reproduce the behavior:

  1. Build to iOS device
  2. try to input text using the mic for dictation
  3. notice the static

Expected behavior

the static should not sound
should not see this in xcode

Please add new GPT models to the list

Yesterday OpenAI updated their GPT models list to:

gpt-3.5-turbo
gpt-3.5-turbo-0301
gpt-3.5-turbo-0613
gpt-3.5-turbo-16k
gpt-3.5-turbo-16k-0613

gpt-4
gpt-4-0314
gpt-4-0613
gpt-4-32k
gpt-4-32k-0613

Source: https://openai.com/blog/function-calling-and-other-api-updates

Current code from ChatRequest.cs:

/// <summary>
/// ID of the model to use.<br/>
/// Currently, only gpt-4, gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported.
/// </summary>
[JsonProperty("model")]
public string Model { get; }

Add organisation ID to authentication

As Discussed in https://github.com/RageAgainstThePixel/com.openai.unity/discussions/14

Originally posted by tomkail January 20, 2023
To do it, just add a new field to OpenAIAuthentication, and then add this to line 49 of OpenAIClient:

if (OpenAIAuthentication.OrganizationId != null)
{
    Client.DefaultRequestHeaders.Add("OpenAI-Organization", OpenAIAuthentication.OrganizationId);
}

Client.DefaultRequestHeaders.Add("User-Agent", "dotnet_openai_api");

We'd love to see this implemented properly!

We also noticed that there was a reference to Organisation in the unused(?) class ResponseExtensions. No idea what it does.

error CS0006: Metadata file 'Library/PackageCache/[email protected]/net35/unity-custom/nunit.framework.dll' could not be found

Error instantly after installing

Installed in the recommended way using the package manager in the project settings using OpenUPM name URL and scopes and then installed using the package manager window, instantly I have two errors saying the same thing.

'error CS0006: Metadata file 'Library/PackageCache/[email protected]/net35/unity-custom/nunit.framework.dll' could not be found'
'error CS0006: Metadata file 'Library/PackageCache/[email protected]/net35/unity-custom/nunit.framework.dll' could not be found'

Double-clicking on them does nothing.

I'm using Unity 2022.3.2f1 on Windows 11.

OpenAI Error

Consider using UniTask?

Hello! First, thanks for keeping this updated so fast! Easily my fav GPT wrapper for Unity.

Wanted to suggest moving to UniTask (https://github.com/Cysharp/UniTask) to handle async/await code. UniTask is by far the most popular solution out there, and I imagine will be familiar/already included by many devs. They're also in active discussion with Unity, so I wouldn't be surprised to hear that they adopt it (or at least something very similar) for future versions of the engine.

no support for new model, "gpt-3.5-turbo"

Bug Report

When I try to suggest OpenAI's latest model, ""gpt-3.5-turbo", I get an error that the model is not found. Since I am sending a string request to openai, shouldn't support for that not have to be hardcoded? In any case, I can't use the latest model with this package.

To Reproduce

model = await api.ModelsEndpoint.GetModelDetailsAsync("gpt-3.5-turbo");

Result: HttpRequestException: CreateCompletionAsync Failed! HTTP status code: NotFound.

Failed to find valid configuration when passing api string in OpenAIClient.ctr

Bug Report

Overview

When i run this code :

var api = new OpenAIClient("my api key");

I'm getting this console ouput :

MissingReferenceException: Failed to find a valid OpenAIConfiguration!
OpenAI.OpenAISettings..ctor (OpenAI.OpenAIConfiguration configuration) (at ./Library/PackageCache/[email protected]/Runtime/Authentication/OpenAISettings.cs:34)
OpenAI.OpenAISettings.get_Default () (at ./Library/PackageCache/[email protected]/Runtime/Authentication/OpenAISettings.cs:96)
OpenAI.OpenAIClient..ctor (OpenAI.OpenAIAuthentication authentication, OpenAI.OpenAISettings settings) (at ./Library/PackageCache/[email protected]/Runtime/OpenAIClient.cs:42)
NewBehaviourScript.Start () (at Assets/New Folder/NewBehaviourScript.cs:12)

To Reproduce

  • Create a new project (3D Core). Go through the setup instructions on the main page to install this package i.e. "Via Unity Package Manager and OpenUPM"
  • Create a new empty game object.
  • Create a new script, attach script to game object.
  • Add this code : "var api = new OpenAIClient("my api key goes here");
  • Save (shows no compilation errors), Run project (this error appears in the console).

Expected behavior

No errors (I followed the readme instructions, I hard coded my correct openai api for testing purposes).

Screenshots

Additional context

I'm quite new to Unity, I apologise in advance if it's something simple (I did search on the error first). I'm guessing that there are more steps involved in being able to use this package than the readme mentions, being a relative noob to Unity I'm not sure what to try. Any help would be greatly appreciated as I'd really like to use the features of this package.

Failed to find a valid OpenAIConfiguration, when using environment variables or .openai in home dir

Bug Report

Overview

I went with "use system environment variables" recommendation from the docs and set both OPENAI_API_KEY and OPENAI_ORGANIZATION_ID in my environment. (https://github.com/RageAgainstThePixel/com.openai.unity#use-system-environment-variables)

To Reproduce

  1. Loading "Chat" sample scene from package manager
  2. Press play
  3. I get the error "Failed to find a valid OpenAIConfiguration!'.
  4. To work around I had to create a OpenAIConfiguration.asset and fill it out

Expected behavior

I'd expect to set the appropriate environment variables and not have to create the OpenAIConfiguration.asset in my Unity project.

Additional note

I also tried the adding the config file to my $env:USERPROFILE.openai and get the same error. It seems like its required to generate the OpenAIConfiguration.asset and fill it out.

Bad performance when calling the constructor AudioTranscriptionRequest(AudioClip)

I am using OpenAI 7.0.1 in Unity 2021.3.23. The running performance goes down seriously when calling the AudioTranscriptionRequest(AudioClip) constructor. The issue appeared after updating the openAI.unity from 5.0 to 7.0. After investigation, the performance issue comes from using the EncodeToOggVorbis() in the constructor. I switched back to use the EncodeToWav, and then the issue was solved.

The latest version OpenAI 3.1.1 can not be downloaded

Bug Report

Overview

The latest version OpenAI 3.1.1 can not be downloaded

To Reproduce

Steps to reproduce the behavior:

  1. Window -> Package Manager
  2. click 'Add Package from git url'
  3. Put git url 'https://github.com/RageAgainstThePixel/com.openai.unity.git#upm'
  4. It tries to install but fails (nothing happens)

Expected behavior

latest versions can be downloaded by Unity Package Manager

Screenshots

image
(When using git to download)

image
(Happens same when using OpenUPM to download)

Additional context

I was able to install OpenAI 3.03 instead of OpenAI 3.1.1
But, it failed when using 'completions' api when the model was GPT3_5_Turbo (instead, Davinci model worked well as explained in the description)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.