Click here to Skip to main content
15,796,456 members
Articles / Artificial Intelligence / ChatGPT

C# OpenAI Library that Supports Latest Assistants API that Released 2023.11.06

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
3 Dec 2023CPOL3 min read 10.3K   12   23
C# OpenAI library Assitants, ChatCompletion, FineTuning, ImageGeneration and more
This library, HigLabo.OpenAI, facilitates easy integration with OpenAI's GPT4-turbo, GPT Store, and Assistant API through comprehensive classes like OpenAIClient and XXXParameter, providing streamlined methods for various API endpoints and versatile response handling, empowering developers to efficiently utilize and interact with OpenAI services in C#.

Introduction

This week, OpenAI introduced GPT4-turbo, GPTs, GPT Store, and more. They also released new API called Assistant API. It's new, there is no C# library. So, I developed it.

How to Use?

Download via Nuget

HigLabo.OpenAI

All source code is https://github.com/higty/higlabo/tree/master/Net8

HigLabo.OpenAI is that.

You can see the sample code on 

https://github.com/higty/higlabo/blob/master/Net8/HigLabo.OpenAI.SampleConsoleApp/OpenAIPlayground.cs

 

The main class is OpenAIClient. You create OpenAIClient class for OpenAI API.

C#
var apiKey = "your api key of OpenAI";
var client = new OpenAIClient(apiKey);

For Azure endpoint.

C#
var apiKey = "your api key of OpenAI";
var client = new OpenAIClient(new AzureSettings
(apiKey, "https://tinybetter-work-for-our-future.openai.azure.com/", "MyDeploymentName"));

Call ChatCompletion endpoint.

C#
var p = new ChatCompletionsParameter();
var theme = "How to enjoy coffee";
p.Messages.Add(new ChatMessage(ChatMessageRole.User
    , $"Can you provide me with some ideas for blog posts about {theme}?"));
p.Model = "gpt-3.5-turbo";
var res = await client.ChatCompletionsAsync(p);
foreach (var choice in res.Choices)
{
     Console.Write(choice.Message.Content);
}

Consume ChatCompletion endpoint with server sent event.

C#
var theme = "How to enjoy coffee";
await foreach (var chunk in client.ChatCompletionsStreamAsync
($"Can you provide me with some ideas for blog posts about {theme}?", "gpt-3.5-turbo"))
{
    foreach (var choice in chunk.Choices)
    {
        Console.Write(choice.Delta.Content);
    }
}
Console.WriteLine();
Console.WriteLine("DONE");

ChatCompletion with function calling.

C#
var p = new ChatCompletionsParameter();
//ChatGPT can correct Newyork,Sanflansisco to New york and San Flancisco.
p.Messages.Add(new ChatMessage(ChatMessageRole.User, 
$"I want to know the whether of these locations. Newyork, Sanflansisco, Paris, Tokyo."));
p.Model = "gpt-3.5-turbo";

var tool = new ToolObject("function");
tool.Function = new FunctionObject();
tool.Function.Name = "getWhether";
tool.Function.Description = "This service can get whether of specified location.";
tool.Function.Parameters = new
{
    type = "object",
    properties = new
    {
        locationList = new
        {
            type = "array",
            description = "Location list that you want to know.",
            items = new
            {
                type = "string",
            }
        }
    }
};
p.Tools = new List<ToolObject>();
p.Tools.Add(tool);

var processor = new ChatCompletionStreamProcessor();
//You must set Stream property to true to receive server sent event stream 
//on chat completion endpoint.
p.Stream = true;
await foreach (var chunk in client.GetStreamAsync(p))
{
    foreach (var choice in chunk.Choices)
    {
        Console.Write(choice.Delta.Content);
        processor.Process(chunk);
    }
}
Console.WriteLine();

var f = processor.GetFunctionCall();
if (f != null)
{
    Console.WriteLine("■Function name is " + f.Name);
    Console.WriteLine("■Arguments is " + f.Arguments);
}
Console.WriteLine("■Content is " + processor.GetContent()); 
Console.WriteLine();
Console.WriteLine("DONE");

Upload file for fine tuning or pass to assistants.

C#
var p = new FileUploadParameter();
p.SetFile("my_file.pdf", File.ReadAllBytes("D:\\Data\\my_file.pdf"));
p.Purpose = "assistants";
var res = await client.FileUploadAsync(p);
Console.WriteLine(res);

Image generation.

C#
var res = await client.ImagesGenerationsAsync("Blue sky and green field.");
foreach (var item in res.Data)
{
    Console.WriteLine(item.Url);
}

Create Assistant via API.

C#
var p = new AssistantCreateParameter();
p.Name = "Legal tutor";
p.Instructions = "You are a personal legal tutor. 
                  Write and run code to legal questions based on passed files.";
p.Model = "gpt-4-1106-preview";

p.Tools = new List<ToolObject>();
p.Tools.Add(new ToolObject("code_interpreter"));
p.Tools.Add(new ToolObject("retrieval"));

var res = await client.AssistantCreateAsync(p);
Console.WriteLine(res);

Add files to assistant.

C#
var res = await client.FilesAsync();
foreach (var item in res.Data)
{
    if (item.Purpose == "assistants")
    {
        var res1 = await cl.AssistantFileCreateAsync(id, item.Id);
    }
}

Send message to assistant and retrieve messages.

C#
private async ValueTask SendMessage()
{
    var cl = OpenAIClient;

    var now = DateTimeOffset.Now;
    var threadId = "";
    if (threadId.Length == 0)
    {
        var res = await cl.ThreadCreateAsync();
        threadId = res.Id;
    }
    {
        var p = new MessageCreateParameter();
        p.Thread_Id = threadId;
        p.Role = "user";
        p.Content = "Hello! I want to know how to use OpenAI assistant API.";
        var res = await cl.MessageCreateAsync(p);
    }
    var runId = "";
    {
        var p = new RunCreateParameter();
        p.Assistant_Id = "asst_xxxxxxxxxxxxxx";
        p.Thread_Id = threadId;
        var res = await cl.RunCreateAsync(p);
        runId = res.Id;
    }
    var loopCount = 0;
    Thread.Sleep(3000);
    var interval = 1000;
    while (true)
    {
        Thread.Sleep(interval);
        var p = new RunRetrieveParameter();
        p.Thread_Id = threadId;
        p.Run_Id = runId;
        var res = await cl.RunRetrieveAsync(p);
        if (res.Status != "queued" &&
            res.Status != "in_progress" &&
            res.Status != "cancelling")
        {
            var p1 = new MessagesParameter();
            p1.Thread_Id = threadId;
            p1.QueryParameter.Order = "desc";
            var res1 = await cl.MessagesAsync(p1);
            foreach (var item in res1.Data)
            {
                foreach (var content in item.Content)
                {
                    if (content.Text == null) { continue; }
                    Console.WriteLine(content.Text.Value);
                }
            }
            break;
        }
        loopCount++;
        if (loopCount > 120) { break; }
    }
}

Get run and steps information of thread.

C#
var threadId = "thread_xxxxxxxxxxxx";
var res = await client.RunsAsync(threadId);
foreach (var item in res.Data)
{
    var res1 = await cl.RunStepsAsync(threadId, item.Id);
    foreach (var step in res1.Data)
    {
        if (step.Step_Details != null)
        {
            Console.WriteLine(step.Step_Details.GetDescription());
        }
    }
}

Class Architecture

The main classes are:

  • OpenAIClient
  • XXXParameter
  • XXXAsync
  • XXXResponse
  • RestApiResponse
  • QueryParameter

OpenAIClient

This class manages api key and call endpoint.

Image 1

You can see in intellisense all endpoint as methods.

I provide method for all endpoints with required parameter. These are the easiest way to call endpoint.

C#
var res = await cl.AudioTranscriptionsAsync("GoodMorningItFineDayToday.mp3"
    , new MemoryStream(File.ReadAllBytes("D:\\Data\\Dev\\GoodMorningItFineDayToday.mp3"))
    , "whisper-1");

OpenAI provides three types of endpoints. These content-type are json, form-data, and server-sent-event. The examples are there.

Json endpoint:

Image 2

Form-data endpoint:

Image 3

Stream endpoint:

Image 4

So I provided SendJsonAsync, SendFormDataAsync, GetStreamAsync methods to call these endpoints. These classes manage http header and content-type and handle response correctly.

You can call endpoint with passing parameter object.

C#
var p = new AudioTranscriptionsParameter();
p.SetFile("GoodMorningItFineDayToday.mp3", 
new MemoryStream(File.ReadAllBytes("D:\\Data\\Dev\\GoodMorningItFineDayToday.mp3")));
p.Model = "whisper-1";
var res = await cl.SendFormDataAsync<AudioTranscriptionsParameter, 
          AudioTranscriptionsResponse>(p, CancellationToken.None);

But this method required the type of parameter and response, so I provide method easy to use.

C#
var p = new AudioTranscriptionsParameter();
p.SetFile("GoodMorningItFineDayToday.mp3", 
new MemoryStream(File.ReadAllBytes("D:\\Data\\Dev\\GoodMorningItFineDayToday.mp3")));
p.Model = "whisper-1";
var res = await cl.AudioTranscriptionsAsync(p);

XXXParameter

I provide parameter classes that represent all values of endpoint.

For example, this is create assistants endpoint.

Image 5

This is AssistantCreateParameter class.

C#
/// <summary>
/// Create an assistant with a model and instructions.
/// <seealso href="https://api.openai.com/v1/assistants">
/// https://api.openai.com/v1/assistants</seealso>
/// </summary>
public partial class AssistantCreateParameter : RestApiParameter, IRestApiParameter
{
    string IRestApiParameter.HttpMethod { get; } = "POST";
    /// <summary>
    /// ID of the model to use. You can use the List models API to see all of 
    /// your available models, or see our Model overview for descriptions of them.
    /// </summary>
    public string Model { get; set; } = "";
    /// <summary>
    /// The name of the assistant. The maximum length is 256 characters.
    /// </summary>
    public string? Name { get; set; }
    /// <summary>
    /// The description of the assistant. The maximum length is 512 characters.
    /// </summary>
    public string? Description { get; set; }
    /// <summary>
    /// The system instructions that the assistant uses. 
    /// The maximum length is 32768 characters.
    /// </summary>
    public string? Instructions { get; set; }
    /// <summary>
    /// A list of tool enabled on the assistant. 
    /// There can be a maximum of 128 tools per assistant. 
    /// Tools can be of types code_interpreter, retrieval, or function.
    /// </summary>
    public List<ToolObject>? Tools { get; set; }
    /// <summary>
    /// A list of file IDs attached to this assistant. 
    /// There can be a maximum of 20 files attached to the assistant. 
    /// Files are ordered by their creation date in ascending order.
    /// </summary>
    public List<string>? File_Ids { get; set; }
    /// <summary>
    /// Set of 16 key-value pairs that can be attached to an object. 
    /// This can be useful for storing additional information about the object 
    /// in a structured format. Keys can be a maximum of 64 characters long 
    /// and values can be a maximum of 512 characters long.
    /// </summary>
    public object? Metadata { get; set; }

    string IRestApiParameter.GetApiPath()
    {
        return $"/assistants";
    }
    public override object GetRequestBody()
    {
        return new {
            model = this.Model,
            name = this.Name,
            description = this.Description,
            instructions = this.Instructions,
            tools = this.Tools,
            file_ids = this.File_Ids,
            metadata = this.Metadata,
        };
    }
}

These parameter classes are generated from API document. You can see actual generator code on Github.

XXXAsync

These methods are generated. I generate four methods that you can easily call API endpoint.

An example of AssistantCreate endpoint.

C#
public async ValueTask<AssistantCreateResponse> AssistantCreateAsync(string model)
public async ValueTask<AssistantCreateResponse> 
    AssistantCreateAsync(string model, CancellationToken cancellationToken)
public async ValueTask<AssistantCreateResponse> 
    AssistantCreateAsync(AssistantCreateParameter parameter)
public async ValueTask<AssistantCreateResponse> 
    AssistantCreateAsync(AssistantCreateParameter parameter, 
    CancellationToken cancellationToken)

Essentially, there are two types of methods.

One is pass values only required parameter.

AssistantCreate endpoint required model. So, I generate:

C#
public async ValueTask<AssistantCreateResponse> AssistantCreateAsync(string model)

This method is easy to use to call endpoint with only required parameter.

The other one that you can call api endpoint with all parameter values.

You can create Parameter like this:

C#
var p = new AssistantCreateParameter();
p.Name = "Legal tutor";
p.Instructions = "You are a personal legal tutor. 
                  Write and run code to legal questions based on passed files.";
p.Model = "gpt-4-1106-preview";

p.Tools = new List<ToolObject>();
p.Tools.Add(new ToolObject("code_interpreter"));
p.Tools.Add(new ToolObject("retrieval"));

And pass it to method.

C#
var res = await cl.AssistantCreateAsync(p);

XXXResponse

Response class represents actual response data of api endpoint.

For example, Retrieve assistant endpoint returns assistant object.

Image 6

I provide AssistantObjectResponse. (I created this class not code generation.)

C#
public class AssistantObjectResponse: RestApiResponse
{
    public string Id { get; set; } = "";
    public int Created_At { get; set; }
    public DateTimeOffset CreateTime
    {
        get
        {
            return new DateTimeOffset
               (DateTime.UnixEpoch.AddSeconds(this.Created_At), TimeSpan.Zero);
        }
    }
    public string Name { get; set; } = "";
    public string Description { get; set; } = "";
    public string Model { get; set; } = "";
    public string Instructions { get; set; } = "";
    public List<ToolObject> Tools { get; set; } = new();
    public List<string>? File_Ids { get; set; }
    public object? MetaData { get; set; }

    public override string ToString()
    {
        return $"{this.Id}\r\n{this.Name}\r\n{this.Instructions}";
    }
}

You can get values of response of api endpoint.

RestApiResponse

Sometimes, you want to get meta data of response. You can get response text, request object that create this response, etc.

This is RestApiResponse class.

C#
public abstract class RestApiResponse : IRestApiResponse
{
    object? IRestApiResponse.Parameter
    {
        get { return _Parameter; }
    }
    HttpRequestMessage IRestApiResponse.Request
    {
        get { return _Request!; }
    }
    string IRestApiResponse.RequestBodyText
    {
        get { return _RequestBodyText; }
    }
    HttpStatusCode IRestApiResponse.StatusCode
    {
        get { return _StatusCode; }
    }
    Dictionary<String, String> IRestApiResponse.Headers
    {
        get { return _Headers; }
    }
    string IRestApiResponse.ResponseBodyText
    {
        get { return _ResponseBodyText; }
    }
}

These property are hidden property. You can access it by cast to RestApiResponse class:

C#
var p = new AssistantCreateParameter();
p.Name = "Legal tutor";
p.Instructions = "You are a personal legal tutor. 
                  Write and run code to legal questions based on passed files.";
p.Model = "gpt-4-1106-preview";

var res = await cl.AssistantCreateAsync(p);
var iRes = res as RestApiResponse;
var responseText = iRes.ResponseBodyText;
Dictionary<string, string> responseHeaders = iRes.Headers;
var parameter = iRes.Parameter as AssistantCreateParameter;

You can log response text to your own log database by RestApiResponse.

QueryParameter

Some api endpoint provides filter, paging feature. You can specify condition by QueryParameter class.

Image 7

You can specify order like this:

C#
var p = new MessagesParameter();
p.Thread_Id = "thread_xxxxxxxxxxxx";
p.QueryParameter.Order = "asc";

Conclusion

I am really excited about OpenAI GPTs, GPT store and so on. If you are also interested in OpenAI, my library may help your work. Happy to use!

History

  • 6th November, 2023: OpenAI released assistants API
  • 11th November, 2023: First release of HigLabo.OpenAI
  • 2nd November, 2023: Add send message samples

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO TinyBetter, Inc
Japan Japan
I'm a CEO of TinyBetter, Inc in Japan.

Comments and Discussions

 
QuestionAssistant API. Can't get next page of messages with OpenAIClient.MessagesAsync and p.QueryParameter.After = messages.Last_Id Pin
Olga Orega29-Nov-23 5:43
Olga Orega29-Nov-23 5:43 
AnswerRe: Assistant API. Can't get next page of messages with OpenAIClient.MessagesAsync and p.QueryParameter.After = messages.Last_Id Pin
Higty1-Dec-23 21:22
Higty1-Dec-23 21:22 
GeneralRe: Assistant API. Can't get next page of messages with OpenAIClient.MessagesAsync and p.QueryParameter.After = messages.Last_Id Pin
Olga Orega3hrs 9mins ago
Olga Orega3hrs 9mins ago 
GeneralRe: Assistant API. Can't get next page of messages with OpenAIClient.MessagesAsync and p.QueryParameter.After = messages.Last_Id Pin
Higty52mins ago
Higty52mins ago 
GeneralHow can i use my existing Assistant for chat where input is given by user ? Pin
Deepak Nov202328-Nov-23 19:30
Deepak Nov202328-Nov-23 19:30 
GeneralRe: How can i use my existing Assistant for chat where input is given by user ? Pin
Higty1-Dec-23 20:54
Higty1-Dec-23 20:54 
BugPotential Bug in MessageTextObject under Specific Use Cases in HigLabo.OpenAI API Pin
campill020-Nov-23 23:04
campill020-Nov-23 23:04 
GeneralRe: Potential Bug in MessageTextObject under Specific Use Cases in HigLabo.OpenAI API Pin
Higty21-Nov-23 0:45
Higty21-Nov-23 0:45 
QuestionTypeLoadException on client creation Pin
Member 1614234317-Nov-23 5:24
Member 1614234317-Nov-23 5:24 
AnswerRe: TypeLoadException on client creation Pin
Higty17-Nov-23 10:04
Higty17-Nov-23 10:04 
GeneralRe: TypeLoadException on client creation Pin
Member 1614234319-Nov-23 23:29
Member 1614234319-Nov-23 23:29 
BugCannot create OpenAPIClient Pin
Scot Woodyard13-Nov-23 12:07
Scot Woodyard13-Nov-23 12:07 
GeneralRe: Cannot create OpenAPIClient Pin
Higty13-Nov-23 15:00
Higty13-Nov-23 15:00 
BugRe: Cannot create OpenAPIClient Pin
Scot Woodyard14-Nov-23 8:20
Scot Woodyard14-Nov-23 8:20 
GeneralRe: Cannot create OpenAPIClient Pin
Higty15-Nov-23 10:02
Higty15-Nov-23 10:02 
GeneralRe: Cannot create OpenAPIClient Pin
Nadav Hury15-Nov-23 23:36
Nadav Hury15-Nov-23 23:36 
GeneralRe: Cannot create OpenAPIClient Pin
Higty17-Nov-23 7:36
Higty17-Nov-23 7:36 
GeneralRe: Cannot create OpenAPIClient Pin
Higty17-Nov-23 10:03
Higty17-Nov-23 10:03 
GeneralRe: Cannot create OpenAPIClient Pin
Nadav Hury19-Nov-23 6:38
Nadav Hury19-Nov-23 6:38 
GeneralRe: Cannot create OpenAPIClient Pin
Higty19-Nov-23 13:07
Higty19-Nov-23 13:07 
BugCode project Pin
Omjee Pal13-Nov-23 0:58
Omjee Pal13-Nov-23 0:58 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA11-Nov-23 5:12
professionalȘtefan-Mihai MOGA11-Nov-23 5:12 
GeneralRe: My vote of 5 Pin
Higty11-Nov-23 5:17
Higty11-Nov-23 5:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.