365 lines
9.8 KiB
C#
Executable File
365 lines
9.8 KiB
C#
Executable File
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
|
|
|
|
}
|