using System.Collections; using System.Collections.Generic; using UnityEngine; using Azure.AI.OpenAI.Assistants; using Azure; using System; using System.Threading.Tasks; using System.Linq; #region Enums public enum EOpenAIServiceState { StartingUp = 0, WaitingForInstructionsReply = 10, Ready = 20, WaitingForReply = 30, } #endregion public class OpenAIServices : MonoBehaviour { #region Inspector Properties [Header("Config Values")] [SerializeField] private bool debugModeIsActive; [SerializeField] private bool ignoreReplyToStartInstructions; [SerializeField] private float clientInitDelay; [SerializeField] private float responsePollingInterval; [SerializeField] private string azureResourceUrl; [SerializeField] private string azureApiKey; [SerializeField] private string assistantModel; [SerializeField] private string assistantName; [SerializeField] private string assistantInstructions; [SerializeField] private string assistantStartInstructions; #endregion #region Public Properties #region OpenAIServiceState private EOpenAIServiceState? pendingStateToChangeToInMainThread = null; private EOpenAIServiceState _openAIServiceState = EOpenAIServiceState.StartingUp; public EOpenAIServiceState OpenAIServiceState { get { return this._openAIServiceState; } set { if (value != this._openAIServiceState) { Debug.Log($"OpenAIServiceState changed, new value= {value}"); this._openAIServiceState = value; this.OnOpenAIServiceStateChangedEvent?.Invoke(this, value); } } } public event EventHandler OnOpenAIServiceStateChangedEvent; #endregion #region LastBotReply private string _lastBotReply = null; public string LastBotReply { get { return this._lastBotReply; } set { if (value != this._lastBotReply) { this.logIfInDebugMode($"LastBotReply changed, new value= {value}"); this._lastBotReply = value; if (value != null) { this.OnLastBotReplyChangedEvent?.Invoke(this, value); } } } } public event EventHandler OnLastBotReplyChangedEvent; #endregion #region LastBotError private string _lastBotError = null; public string LastBotError { get { return this._lastBotError; } set { if (value != this._lastBotError) { this.logIfInDebugMode($"LastBotError changed, new value= {value}"); this._lastBotError = value; this.OnLastBotErrorChangedEvent?.Invoke(this, value); } } } public event EventHandler OnLastBotErrorChangedEvent; #endregion #endregion #region Private Properties private AssistantsClient client; private Assistant assistant; private Response runResponse; private AssistantThread thread; private string lastMessageReceived = ""; private string lastTextReceived; private string lastError; private bool ignoreIncomingReplies = false; #endregion #region Framework Functions void OnEnable() { this.init(); } void Update() { this.doMainThreadTasks(); } async void OnDisable() { await this.client.DeleteThreadAsync(this.thread.Id); } #endregion #region Events #endregion #region Public Functions public async void SendTextToBot(string textToSend) { this.logIfInDebugMode($"OpenAIServices SendTextToBot: {textToSend}"); this.LastBotReply = null; this.LastBotError = null; await this.send(textToSend); } public void Stop() { this.ignoreIncomingReplies = true; } #endregion #region Private Functions private async void init() { this.logIfInDebugMode("Init started"); this.OpenAIServiceState = EOpenAIServiceState.StartingUp; this.client = new AssistantsClient(new Uri(this.azureResourceUrl), new AzureKeyCredential(this.azureApiKey)); await Task.Delay(TimeSpan.FromSeconds(this.clientInitDelay)); Response assistantResponse = await this.client.CreateAssistantAsync( new AssistantCreationOptions(assistantModel) { Name = assistantName, Instructions = assistantInstructions, }); this.assistant = assistantResponse.Value; Response threadResponse = await this.client.CreateThreadAsync(); this.thread = threadResponse.Value; this.runResponse = await client.CreateRunAsync( this.thread.Id, new CreateRunOptions(assistant.Id) { AdditionalInstructions = assistantStartInstructions, }); ThreadRun run = runResponse.Value; this.logIfInDebugMode($"Init completed, ignoreReplyToStartInstructions={this.ignoreReplyToStartInstructions}"); this.ignoreIncomingReplies = this.ignoreReplyToStartInstructions; this.OpenAIServiceState = EOpenAIServiceState.WaitingForInstructionsReply; await this.listen(); } private async Task send(string text) { this.logIfInDebugMode($"OpenAIServices Sending: {text} to Bot"); Response messageResponse = await client.CreateMessageAsync(this.thread.Id, MessageRole.User, text); ThreadMessage message = messageResponse.Value; this.runResponse = await client.CreateRunAsync(this.thread.Id, new CreateRunOptions(assistant.Id)); ThreadRun run = this.runResponse.Value; this.ignoreIncomingReplies = false; this.logIfInDebugMode($"OpenAIServices {text} sent to Bot"); this.OpenAIServiceState = EOpenAIServiceState.WaitingForReply; await this.listen(); } private async Task listen() { Response run; do { await Task.Delay(TimeSpan.FromSeconds(this.responsePollingInterval)); run = await this.client.GetRunAsync(this.thread.Id, this.runResponse.Value.Id); } while (Application.isPlaying && (run.Value.Status == RunStatus.Queued || run.Value.Status == RunStatus.InProgress)); if (run.Value.Status != RunStatus.Completed) { this.lastError = $"Status: {run.Value.Status}, Grund: {run.Value.LastError.Message}"; this.logIfInDebugMode($"Status: {run.Value.Status}, Code: {run.Value.LastError.Code}, Message: {run.Value.LastError.Message}"); return false; } else { Response> afterRunMessagesResponse = await client.GetMessagesAsync(this.thread.Id); var messages = afterRunMessagesResponse.Value; // Note: messages iterate from newest to oldest, with the messages[0] being the most recent if (messages.FirstId != this.lastMessageReceived) { var threadMessage = messages.First(); foreach (MessageContent contentItem in threadMessage.ContentItems) { if (contentItem is MessageTextContent textItem) { if (this.ignoreIncomingReplies) { // Service has been stopped -> ignore reply this.logIfInDebugMode($"Ignoring text reply from Bot: {textItem.Text}"); } else { this.logIfInDebugMode($"Got text reply from Bot: {textItem.Text}"); this.lastTextReceived = textItem.Text; } } else if (contentItem is MessageImageFileContent imageFileItem) { this.logIfInDebugMode($"Got image reply from Bot, FileId: {imageFileItem.FileId}"); } } if (this.OpenAIServiceState == EOpenAIServiceState.WaitingForInstructionsReply) { this.pendingStateToChangeToInMainThread = EOpenAIServiceState.Ready; } else if (this.OpenAIServiceState == EOpenAIServiceState.WaitingForReply) { this.pendingStateToChangeToInMainThread = EOpenAIServiceState.Ready; } this.lastMessageReceived = threadMessage.Id; } return true; } } private void doMainThreadTasks() { if (this.pendingStateToChangeToInMainThread != null) { this.OpenAIServiceState = (EOpenAIServiceState)this.pendingStateToChangeToInMainThread; this.pendingStateToChangeToInMainThread = null; } if (this.lastTextReceived != null) { this.LastBotReply = this.lastTextReceived; this.lastTextReceived = null; } if (this.lastError != null) { this.LastBotError = this.lastError; this.lastError = null; } } private void logIfInDebugMode(string message) { if (!this.debugModeIsActive) { return; } Debug.Log($"(OpenAIServices) => {message}"); } #endregion }