From 4edf8676d9ae550ab535a1664d7f3d8058f6834e Mon Sep 17 00:00:00 2001 From: trkim Date: Thu, 20 Nov 2025 17:50:56 +0900 Subject: [PATCH 1/4] Modify to add to the download list if the model is not included in the build. --- Runtime/LLMManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From f38d0b00d6e03dd94843f30ad32ff04f335e7c49 Mon Sep 17 00:00:00 2001 From: trkim Date: Thu, 20 Nov 2025 17:53:04 +0900 Subject: [PATCH 2/4] Fix: Android grammar loading --- Runtime/LLMCharacter.cs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/Runtime/LLMCharacter.cs b/Runtime/LLMCharacter.cs index 0bf91a15..6da6b0f1 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)) From f2827d9e8ed3e6e5327c71bb39c017f50560776d Mon Sep 17 00:00:00 2001 From: trkim Date: Fri, 21 Nov 2025 13:28:42 +0900 Subject: [PATCH 3/4] fix: Bypasses the issue where errors occur due to antivirus program detection when using the temporaryCachePath. --- Runtime/LLMBuilder.cs | 47 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) 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); From 1271ec1e8dfe64b9a3cd3ab6bcc3fb946506cba6 Mon Sep 17 00:00:00 2001 From: trkim Date: Fri, 21 Nov 2025 14:54:20 +0900 Subject: [PATCH 4/4] add: Exception handling when the response from llama.cpp is an error --- Runtime/LLMCaller.cs | 12 ++++++++++-- Runtime/LLMCharacter.cs | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) 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 6da6b0f1..f1eac9a8 100644 --- a/Runtime/LLMCharacter.cs +++ b/Runtime/LLMCharacter.cs @@ -627,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(); }