Merge branch 'programming'

This commit is contained in:
Nadine Ganz 2026-09-02 17:01:08 +02:00
commit 8b0824f1eb
7 changed files with 2095 additions and 131 deletions

View File

@ -1 +1 @@
2026-05-21T15:25:17.3240080Z
2026-09-02T13:58:29.6844630Z

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2b804bd4e70ce9248926fab60d69232d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -2676,7 +2676,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: v. 2025.09.1601
m_text: v. 2026.08.2501
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
@ -3838,12 +3838,9 @@ MonoBehaviour:
m_EditorClassIdentifier:
debugModeIsActive: 0
ignoreReplyToStartInstructions: 1
clientInitDelay: 0
responsePollingInterval: 0.5
azureResourceUrl: https://aoviaggiofc.openai.azure.com/
azureApiKey: e9071c900f1c4d84a81ae2864ee8c32f
azureApiKey: cc40a4dc3a50453c829a6ff2e9f5011d
assistantModel: gpt-4o
assistantName: Viaggio
assistantInstructions: You will have conversation with a user.
assistantStartInstructions: Keep your answers short.
--- !u!1 &8843532098182290353
@ -3956,12 +3953,9 @@ MonoBehaviour:
m_EditorClassIdentifier:
debugModeIsActive: 0
ignoreReplyToStartInstructions: 1
clientInitDelay: 0
responsePollingInterval: 0.5
azureResourceUrl: https://aoviaggiofc.openai.azure.com/
azureApiKey: e9071c900f1c4d84a81ae2864ee8c32f
azureApiKey: cc40a4dc3a50453c829a6ff2e9f5011d
assistantModel: gpt-4o
assistantName: Viaggio
assistantInstructions: 'I will use you as a intent recognizer. You will receive
Data as follows: Key="{Key1}", Text="{Text1}" Key="{Key2}", Text="{Text2}" Key="{Key3}",
Text="{Text3}" Input="{Input}" Try to match the meaning of the Text of each

View File

@ -1,11 +1,9 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Azure.AI.OpenAI.Assistants;
using Azure;
using System;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.Networking;
#region Enums
@ -31,12 +29,6 @@ public class OpenAIServices : MonoBehaviour
[SerializeField]
private bool ignoreReplyToStartInstructions;
[SerializeField]
private float clientInitDelay;
[SerializeField]
private float responsePollingInterval;
[SerializeField]
private string azureResourceUrl;
@ -46,9 +38,6 @@ public class OpenAIServices : MonoBehaviour
[SerializeField]
private string assistantModel;
[SerializeField]
private string assistantName;
[SerializeField]
private string assistantInstructions;
@ -142,15 +131,11 @@ public class OpenAIServices : MonoBehaviour
#region Private Properties
private AssistantsClient client;
private UnityWebRequest activeRequest;
private Assistant assistant;
private string previousResponseId;
private Response<ThreadRun> runResponse;
private AssistantThread thread;
private string lastMessageReceived = "";
private int requestGeneration;
private string lastTextReceived;
@ -172,14 +157,17 @@ public class OpenAIServices : MonoBehaviour
this.doMainThreadTasks();
}
async void OnDisable()
void OnDisable()
{
if (this?.client == null || this?.thread?.Id == null)
this.requestGeneration++;
if (this.activeRequest != null)
{
return;
this.activeRequest.Abort();
this.activeRequest = null;
}
await this.client.DeleteThreadAsync(this.thread.Id);
this.previousResponseId = null;
}
#endregion
@ -212,48 +200,23 @@ public class OpenAIServices : MonoBehaviour
private async void init()
{
this.IsInitialized = false;
this.previousResponseId = null;
this.logIfInDebugMode("OpenAIServices Init started");
try
{
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();
await this.requestResponse(this.assistantStartInstructions);
this.logIfInDebugMode($"Init completed, ignoreReplyToStartInstructions={this.ignoreReplyToStartInstructions}");
}
catch (Exception ex)
{
this.lastError = ex.ToString();
this.handleRequestError(ex.ToString());
}
}
@ -261,89 +224,181 @@ public class OpenAIServices : MonoBehaviour
{
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();
await this.requestResponse(text);
this.logIfInDebugMode($"OpenAIServices {text} sent to Bot");
}
private async Task<bool> listen()
private async Task<bool> requestResponse(string text)
{
Response<ThreadRun> run;
do
if (this.activeRequest != null)
{
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}");
this.handleRequestError("A response request is already in progress.");
return false;
}
else
JObject requestBody = new JObject
{
Response<PageableList<ThreadMessage>> afterRunMessagesResponse = await client.GetMessagesAsync(this.thread.Id);
var messages = afterRunMessagesResponse.Value;
["model"] = this.assistantModel,
["instructions"] = this.assistantInstructions,
["input"] = text,
["store"] = true,
};
// Note: messages iterate from newest to oldest, with the messages[0] being the most recent
if (messages.FirstId != this.lastMessageReceived)
if (!string.IsNullOrEmpty(this.previousResponseId))
{
requestBody["previous_response_id"] = this.previousResponseId;
}
string url = $"{this.azureResourceUrl.TrimEnd('/')}/openai/v1/responses";
byte[] requestData = Encoding.UTF8.GetBytes(requestBody.ToString());
UnityWebRequest request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST)
{
uploadHandler = new UploadHandlerRaw(requestData),
downloadHandler = new DownloadHandlerBuffer(),
};
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("api-key", this.azureApiKey);
this.activeRequest = request;
int generation = this.requestGeneration;
try
{
request.SendWebRequest();
while (!request.isDone)
{
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;
await Task.Yield();
}
if (generation != this.requestGeneration)
{
return false;
}
if (request.result != UnityWebRequest.Result.Success)
{
if (this.isActiveAndEnabled)
{
this.handleRequestError(this.getRequestError(request));
}
return false;
}
JObject response = JObject.Parse(request.downloadHandler.text);
string responseId = response.Value<string>("id");
string responseText = this.getOutputText(response);
if (string.IsNullOrEmpty(responseId) || string.IsNullOrEmpty(responseText))
{
this.handleRequestError("The Responses API returned no response ID or output text.");
return false;
}
this.previousResponseId = responseId;
// Note: messages iterate from newest to oldest, with the messages[0] being the most recent
if (this.ignoreIncomingReplies)
{
// Service has been stopped -> ignore reply
this.logIfInDebugMode($"Ignoring text reply from Bot: {responseText}");
}
else
{
this.logIfInDebugMode($"Got text reply from Bot: {responseText}");
this.lastTextReceived = responseText;
}
this.pendingStateToChangeToInMainThread = EOpenAIServiceState.Ready;
this.IsInitialized = true;
return true;
}
catch (Exception ex)
{
if (this.isActiveAndEnabled)
{
this.handleRequestError(ex.ToString());
}
return false;
}
finally
{
if (this.activeRequest == request)
{
this.activeRequest = null;
}
request.Dispose();
}
}
private string getOutputText(JObject response)
{
string outputText = response.Value<string>("output_text");
if (!string.IsNullOrEmpty(outputText))
{
return outputText;
}
JArray outputItems = response.Value<JArray>("output");
if (outputItems == null)
{
return null;
}
foreach (JToken outputItem in outputItems)
{
JArray contentItems = outputItem.Value<JArray>("content");
if (contentItems == null)
{
continue;
}
foreach (JToken contentItem in contentItems)
{
if (contentItem.Value<string>("type") == "output_text")
{
return contentItem.Value<string>("text");
}
}
}
return null;
}
private string getRequestError(UnityWebRequest request)
{
string responseText = request.downloadHandler?.text;
if (!string.IsNullOrEmpty(responseText))
{
try
{
JObject response = JObject.Parse(responseText);
string message = response["error"]?.Value<string>("message");
if (!string.IsNullOrEmpty(message))
{
return $"HTTP {request.responseCode}: {message}";
}
}
catch (Exception ex)
{
this.logIfInDebugMode($"Could not parse error response: {ex.Message}");
}
}
return $"HTTP {request.responseCode}: {request.error}";
}
private void handleRequestError(string error)
{
this.lastError = error;
this.pendingStateToChangeToInMainThread = EOpenAIServiceState.Ready;
this.IsInitialized = true;
this.logIfInDebugMode(error);
}
private void doMainThreadTasks()

File diff suppressed because it is too large Load Diff