Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Custom Assistant in Sample ChatBehaviour #162

Open
chameleon-training opened this issue Jan 3, 2024 · 8 comments
Open

Custom Assistant in Sample ChatBehaviour #162

chameleon-training opened this issue Jan 3, 2024 · 8 comments
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Comments

@chameleon-training
Copy link

I have been searching for a long time until I came across your code. I am looking for a way to use the assistant I trained with OpenAI via the API in Unity. I installed the package and got an overview with the sample. I am really impressed with your work. I have tried to extend the sample so that I can access my assistant, but unfortunately, I failed. Can you tell me what I need to adjust in the sample?

I have studied your documentation, but unfortunately, I'm struggling to get started as the topic of assistant + threads together is very complicated.

It would be a dream if you could extend the Sample ChatBehaviour so that one could enter the ID of their own assistant.

If that's too much to ask, I can absolutely understand. In that case, it would at least be helpful if you could explain the implementation startup in the documentation a bit more for dummies. ;-)

@chameleon-training chameleon-training added the enhancement New feature or request label Jan 3, 2024
@StephenHodgson
Copy link
Member

Tbh I haven't had much of a chance to play with the new API myself in any meaningful capacity.

I'll see what I can do about adding a new sample for assistants api specifically.

@StephenHodgson StephenHodgson added good first issue Good for newcomers help wanted Extra attention is needed labels Jan 3, 2024
@neohun
Copy link

neohun commented Jan 10, 2024

I have used assistants recently. The following should get you going.
Edit: the code is edited for a better flexibility, clarity and robustness.

public class OpenAIEx {

    static readonly string apiKey = "yourApiKey";

    /// <summary> Create an assistant thread and run for the message. </summary>
    /// <param name="message"> The input message that you want to send to assistant. </param>
    /// <param name="assistantID"> Assistant ID </param>
    /// <returns> Run response when it's complete. </returns> 
    public static async Task<RunResponse> AssistantThread(string message, string assistantID) {
        // use try-catch for possible errors. 
        try {
            var api = new OpenAIClient(apiKey);
            // retrieve the assistant. 
            var assistant = await api.AssistantsEndpoint.RetrieveAssistantAsync(assistantID);
            // create a thread as async
            var thread = await api.ThreadsEndpoint.CreateThreadAsync();
            // create the message in the thread as async. 
            _ = await thread.CreateMessageAsync(message);

            // run the thread to return a response
            var run = await thread.CreateRunAsync(assistant);
            // write the run status to console just for clarity. 
            Debug.Log($"[{run.Id}] {run.Status} | {run.CreatedAt}");

            //  wait for run to complete to return the result. 
            run = await run.WaitForRunCompleteAsync();
            // return the run response to retrieve the message. 
            return run;
        }
        catch (Exception e) {
            Debug.LogException(e);
            return null; // return null when an error occurs.
        }
    }

}


public static class OpenAI_Extensions {

    public static async Task<RunResponse> WaitForRunCompleteAsync(this RunResponse run) {
        // wait while it is running. 
        while (run.IsRunning()) {
            run = await run.WaitForStatusChangeAsync();
            // debug to see steps
            Debug.Log($"[{run.Id}] status: {run.Status} | {run.CreatedAt}");
        }
        // return the response. 
        return run;
    }


    /// <summary> check whether it's still running. </summary>
    static bool IsRunning(this RunResponse runResponse) {
        // check whether it is running still. 
        return (runResponse.Status == RunStatus.Queued
             || runResponse.Status == RunStatus.InProgress
             || runResponse.Status == RunStatus.Cancelling);
    }

}
// *** Example Usage ***
async void Run() {

    string inputMessage = "your message";
    // pass your message and assistantID to get the response. 
    var result = await OpenAIEx.AssistantThread(inputMessage, "yourAssistantID");
    // check whether the status is completed to process the result.
    if (result.Status == RunStatus.Completed) {
        // list the messages from the result. 
        var messages = await result.ListMessagesAsync();
        // reverse the list because the first message is the last response. 
        var allMessages = messages.Items.Reverse().ToList();

        // write all messages for the run. 
        foreach (var message in allMessages) {
            // write the message role and content to console. 
            Debug.Log($"{message.Role}: {message.PrintContent()}");
        }
    }
    else {
        Debug.Log($"Assistant Run Failed | Status: -> {result.Status}"); 
    }
    
}

@neohun
Copy link

neohun commented Jan 10, 2024

Also I want to point a minor issue while I'm here. It's about "Items" property of "ListResponse" class which is used for parsing "data" of the response but I think it causes confusion because I was following some tutorials which use python API and it's named as "data" there so I had to look all properties one by one. I think another property named "data" can be created and "Items" can be made obsolete just for the sake of clarity and parity with Python API.

@StephenHodgson
Copy link
Member

Just some notes about example:

Probably better not to use a static class since you may need different endpoints, especially for Azure.

It's generally frowned upon to discard your tasks _ = await Run.Task; because you may not properly bubble up errors and handle exceptions correctly.

Instead always use try/catch, especially in unity, so that exceptions are handled properly and don't get stuck in the task queue and execute continuously.

@chameleon-training
Copy link
Author

Thank you guys, I'll try to integrate my assistant based on that. This will definitely help me...You guys are awesome!

@neohun
Copy link

neohun commented Jan 11, 2024

It's generally frowned upon to discard your tasks _ = await Run.Task; because you may not properly bubble up errors and handle exceptions correctly.

The code was just an example but you're right so I have fixed the problems you have pointed out..
But about _ discards I have tested and there was no problem when using them because when an error is thrown by an awaited function, try-catch works for the internal errors even if a discard is used.
Discard is just for the clarity to indicate that the return value is not used to avoid confusion.

Also you can add the example for documentation if you want so other people who want to use the assistants can benefit from it.

Thanks for the library by the way It has saved us from bunch of troubles (:

@neohun
Copy link

neohun commented Jan 11, 2024

Thank you guys, I'll try to integrate my assistant based on that. This will definitely help me...You guys are awesome!

You are welcome, good luck.

@StephenHodgson
Copy link
Member

StephenHodgson commented Jan 11, 2024

But about _ discards I have tested and there was no problem when using them because when an error is thrown by an awaited function, try-catch works for the internal errors even if a discard is used.
Discard is just for the clarity to indicate that the return value is not used to avoid confusion.

I'm telling you from experience, don't use them in the Unity runtime environment. It's bad practice. Instead use async void for a fire and forget method, and wrap it with a try /catch.

If it was pure C#, I'd have no problem with it.

@RageAgainstThePixel RageAgainstThePixel deleted a comment from neohun Jan 11, 2024
@RageAgainstThePixel RageAgainstThePixel locked and limited conversation to collaborators Jan 11, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed
Development

No branches or pull requests

3 participants