UP-Viagg-io/Viagg-io/Assets/afca/ViaggioAI/Scripts/AIServices/OpenAIServices.cs

365 lines
9.8 KiB
C#
Raw Normal View History

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<EOpenAIServiceState> 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<string> 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<string> OnLastBotErrorChangedEvent;
#endregion
#endregion
#region Private Properties
private AssistantsClient client;
private Assistant assistant;
private Response<ThreadRun> 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<Assistant> assistantResponse = await this.client.CreateAssistantAsync(
new AssistantCreationOptions(assistantModel)
{
Name = assistantName,
Instructions = assistantInstructions,
});
this.assistant = assistantResponse.Value;
Response<AssistantThread> 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<ThreadMessage> 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<bool> listen()
{
Response<ThreadRun> 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<PageableList<ThreadMessage>> 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
}