diff --git a/Runtime/LLMBuilder.cs b/Runtime/LLMBuilder.cs index 6725cd43..5e9cdc9b 100644 --- a/Runtime/LLMBuilder.cs +++ b/Runtime/LLMBuilder.cs @@ -15,7 +15,7 @@ namespace LLMUnity public class LLMBuilder : AssetPostprocessor { static List movedPairs = new List(); - public static string BuildTempDir = Path.Combine(Application.temporaryCachePath, "LLMUnityBuild"); + public static string BuildTempDir = Path.Combine(Directory.GetParent(Application.dataPath).FullName, "LLMUnityBuild"); static string movedCache = Path.Combine(BuildTempDir, "moved.json"); [InitializeOnLoadMethod] @@ -36,6 +36,28 @@ public static string PluginLibraryDir(string platform, bool relative = false) return Path.Combine(PluginDir(platform, relative), LLMUnitySetup.libraryName); } + public static void Retry(System.Action action, int retries = 10, int delayMs = 100) + { + for (int i = 0; i < retries; i++) + { + try + { + action(); + return; + } + catch (IOException) + { + if (i == retries - 1) throw; + System.Threading.Thread.Sleep(delayMs); + } + catch (System.UnauthorizedAccessException) + { + if (i == retries - 1) throw; + System.Threading.Thread.Sleep(delayMs); + } + } + } + /// /// Performs an action for a file or a directory recursively /// @@ -46,7 +68,7 @@ public static void HandleActionFileRecursive(string source, string target, Actio { if (File.Exists(source)) { - actionCallback(source, target); + Retry(() => actionCallback(source, target)); } else if (Directory.Exists(source)) { @@ -106,8 +128,8 @@ public static bool DeletePath(string path) LLMUnitySetup.LogError($"Safeguard: {path} will not be deleted because it may not be safe"); return false; } - if (File.Exists(path)) File.Delete(path); - else if (Directory.Exists(path)) Directory.Delete(path, true); + if (File.Exists(path)) Retry(() => File.Delete(path)); + else if (Directory.Exists(path)) Retry(() => Directory.Delete(path, true)); return true; } @@ -266,8 +288,21 @@ public static void Reset() bool refresh = false; foreach (var pair in movedPairs) { - if (pair.source == "") refresh |= DeletePath(pair.target); - else refresh |= MoveAction(pair.target, pair.source, false); + if (pair.source == "") + { + refresh |= DeletePath(pair.target); + } + else + { + if (File.Exists(pair.source) || Directory.Exists(pair.source)) + { + refresh |= DeletePath(pair.target); + } + else + { + refresh |= MoveAction(pair.target, pair.source, false); + } + } } if (refresh) AssetDatabase.Refresh(); DeletePath(movedCache); diff --git a/Runtime/LLMCaller.cs b/Runtime/LLMCaller.cs index 4b69137d..81b38594 100644 --- a/Runtime/LLMCaller.cs +++ b/Runtime/LLMCaller.cs @@ -209,10 +209,18 @@ protected virtual Ret ConvertContent(string response, ContentCallback< } response = $"{{\"data\": [{responseArray}]}}"; } - return getContent(JsonUtility.FromJson(response)); + try + { + return getContent(JsonUtility.FromJson(response)); + } + catch (Exception e) + { + LLMUnitySetup.LogError($"Error converting response: {e.Message}\nResponse: {response}"); + return default; + } } - protected virtual void CancelRequestsLocal() {} + protected virtual void CancelRequestsLocal() { } protected virtual void CancelRequestsRemote() { diff --git a/Runtime/LLMCharacter.cs b/Runtime/LLMCharacter.cs index 0bf91a15..f1eac9a8 100644 --- a/Runtime/LLMCharacter.cs +++ b/Runtime/LLMCharacter.cs @@ -137,6 +137,8 @@ public class LLMCharacter : LLMCaller protected SemaphoreSlim chatLock = new SemaphoreSlim(1, 1); protected string chatTemplate; protected ChatTemplate template = null; + protected Task grammarTask; + /// \endcond /// @@ -157,7 +159,7 @@ public override void Awake() int slotFromServer = llm.Register(this); if (slot == -1) slot = slotFromServer; } - InitGrammar(); + grammarTask = InitGrammar(); InitHistory(); } @@ -273,19 +275,33 @@ protected virtual async Task InitNKeep() return true; } - protected virtual void InitGrammar() + protected virtual async Task InitGrammar() { grammarString = ""; grammarJSONString = ""; if (!String.IsNullOrEmpty(grammar)) { - grammarString = File.ReadAllText(LLMUnitySetup.GetAssetPath(grammar)); - if (!String.IsNullOrEmpty(grammarJSON)) - LLMUnitySetup.LogWarning("Both GBNF and JSON grammars are set, only the GBNF will be used"); + await LLMUnitySetup.AndroidExtractAsset(grammar, true); + string path = LLMUnitySetup.GetAssetPath(grammar); + if (File.Exists(path)) + { + grammarString = File.ReadAllText(path); + if (!String.IsNullOrEmpty(grammarJSON)) + LLMUnitySetup.LogWarning("Both GBNF and JSON grammars are set, only the GBNF will be used"); + } + else + { + LLMUnitySetup.LogError($"Grammar file {path} not found!"); + } } else if (!String.IsNullOrEmpty(grammarJSON)) { - grammarJSONString = File.ReadAllText(LLMUnitySetup.GetAssetPath(grammarJSON)); + await LLMUnitySetup.AndroidExtractAsset(grammarJSON, true); + string path = LLMUnitySetup.GetAssetPath(grammarJSON); + if (File.Exists(path)) + grammarJSONString = File.ReadAllText(path); + else + LLMUnitySetup.LogError($"Grammar file {path} not found!"); } } @@ -327,10 +343,10 @@ public virtual async Task SetGrammarFile(string path, bool gnbf) #if UNITY_EDITOR if (!EditorApplication.isPlaying) path = LLMUnitySetup.AddAsset(path); #endif - await LLMUnitySetup.AndroidExtractAsset(path, true); if (gnbf) grammar = path; else grammarJSON = path; - InitGrammar(); + grammarTask = InitGrammar(); + await grammarTask; } /// @@ -524,6 +540,7 @@ public virtual async Task Chat(string query, Callback callback = await LoadTemplate(); if (!CheckTemplate()) return null; if (!await InitNKeep()) return null; + if (grammarTask != null) await grammarTask; ChatRequest request = await PromptWithQuery(query); string result = await CompletionRequest(request, callback); @@ -562,6 +579,7 @@ public virtual async Task Complete(string prompt, Callback callb // call the callback function while the answer is received // call the completionCallback function when the answer is fully received await LoadTemplate(); + if (grammarTask != null) await grammarTask; ChatRequest request = GenerateRequest(prompt); string result = await CompletionRequest(request, callback); @@ -595,6 +613,7 @@ public virtual async Task Warmup(string query, EmptyCallback completionCallback await LoadTemplate(); if (!CheckTemplate()) return; if (!await InitNKeep()) return; + if (grammarTask != null) await grammarTask; ChatRequest request; if (String.IsNullOrEmpty(query)) @@ -608,6 +627,8 @@ public virtual async Task Warmup(string query, EmptyCallback completionCallback } request.n_predict = 0; + request.grammar = null; + request.json_schema = null; await CompletionRequest(request); completionCallback?.Invoke(); } diff --git a/Runtime/LLMManager.cs b/Runtime/LLMManager.cs index 72e58a06..e4e475d4 100644 --- a/Runtime/LLMManager.cs +++ b/Runtime/LLMManager.cs @@ -643,7 +643,7 @@ public static void SaveToDisk() List modelEntriesBuild = new List(); foreach (ModelEntry modelEntry in modelEntries) { - if (!modelEntry.includeInBuild) continue; + if (!modelEntry.includeInBuild && string.IsNullOrEmpty(modelEntry.url)) continue; modelEntriesBuild.Add(modelEntry.OnlyRequiredFields()); } string json = JsonUtility.ToJson(new LLMManagerStore