diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 8864b24b..1dd70555 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.pdb b/1.6/1.6/Assemblies/WulaFallenEmpire.pdb index ea7f4a7f..e93967c4 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.pdb and b/1.6/1.6/Assemblies/WulaFallenEmpire.pdb differ diff --git a/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs b/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs index fa346194..91b33f0b 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs @@ -2064,6 +2064,12 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori var client = new SimpleAIClient(apiKey, baseUrl, model, settings.useGeminiProtocol); _currentClient = client; + if (!settings.useGeminiProtocol) + { + await RunNativeToolLoopAsync(client, settings); + return; + } + // Model-Driven Vision: Start with null image. The model must request it using analyze_screen or capture_screen if needed. string base64Image = null; @@ -2360,10 +2366,13 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori var successfulQueryTools = new HashSet(StringComparer.OrdinalIgnoreCase); var successfulActionTools = new HashSet(StringComparer.OrdinalIgnoreCase); var failedActionTools = new HashSet(StringComparer.OrdinalIgnoreCase); + bool loggedQueryPhase = false; + bool loggedActionPhase = false; + bool loggedReplyPhase = false; int maxSteps = int.MaxValue; float maxSeconds = Math.Max(2f, settings.reactMaxSeconds <= 0f ? DefaultReactMaxSeconds : settings.reactMaxSeconds); - _thinkingPhaseTotal = 0; + _thinkingPhaseTotal = 3; int strictRetryCount = 0; int phaseRetryCount = 0; const int MaxStrictRetries = 2; @@ -2380,6 +2389,20 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori break; } + if (Prefs.DevMode) + { + if (phase == RequestPhase.QueryTools && !loggedQueryPhase) + { + WulaLog.Debug("[WulaAI] ===== Turn 1/3 (QueryTools) ====="); + loggedQueryPhase = true; + } + else if (phase == RequestPhase.ActionTools && !loggedActionPhase) + { + WulaLog.Debug("[WulaAI] ===== Turn 2/3 (ActionTools) ====="); + loggedActionPhase = true; + } + } + SetThinkingPhase(phase == RequestPhase.QueryTools ? 1 : 2, false); string systemInstruction = GetNativeSystemInstruction(phase); @@ -2389,7 +2412,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori messages, tools, maxTokens: 2048, - temperature: 0.2f); + temperature: 0.2f, + toolChoice: "auto"); if (result == null) { @@ -2624,7 +2648,13 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori } } - finalReply = await client.GetChatCompletionAsync(replyInstruction, BuildReplyHistory(), base64Image: null); + if (Prefs.DevMode && !loggedReplyPhase) + { + WulaLog.Debug("[WulaAI] ===== Turn 3/3 (Reply) ====="); + loggedReplyPhase = true; + } + + finalReply = await client.GetChatCompletionAsync(replyInstruction, BuildReplyHistory(), base64Image: null, toolChoice: "none"); if (string.IsNullOrEmpty(finalReply)) { AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate()); @@ -2639,7 +2669,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori "\n\n# RETRY (REPLY OUTPUT)\n" + "Your last reply included tool call JSON or was empty. Tool calls are DISABLED.\n" + "You MUST reply in natural language only. Do NOT output any tool call JSON.\n"; - string retryReply = await client.GetChatCompletionAsync(retryReplyInstruction, BuildReplyHistory(), maxTokens: 256, temperature: 0.3f); + string retryReply = await client.GetChatCompletionAsync(retryReplyInstruction, BuildReplyHistory(), maxTokens: 256, temperature: 0.3f, toolChoice: "none"); if (!string.IsNullOrEmpty(retryReply)) { finalReply = retryReply; @@ -2960,6 +2990,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori + + diff --git a/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs b/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs index a3a6010b..c18dacdd 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs @@ -68,7 +68,7 @@ namespace WulaFallenEmpire.EventSystem.AI _useGemini = useGemini; } - public async Task GetChatCompletionAsync(string instruction, List<(string role, string message)> messages, int? maxTokens = null, float? temperature = null, string base64Image = null) + public async Task GetChatCompletionAsync(string instruction, List<(string role, string message)> messages, int? maxTokens = null, float? temperature = null, string base64Image = null, string toolChoice = null) { // 1. Gemini Mode if (_useGemini) @@ -101,6 +101,7 @@ namespace WulaFallenEmpire.EventSystem.AI jsonBuilder.Append("\"stream\": false,"); if (maxTokens.HasValue) jsonBuilder.Append($"\"max_tokens\": {Math.Max(1, maxTokens.Value)},"); if (temperature.HasValue) jsonBuilder.Append($"\"temperature\": {temperature.Value.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture)},"); + if (!string.IsNullOrWhiteSpace(toolChoice)) jsonBuilder.Append($"\"tool_choice\": \"{EscapeJson(toolChoice)}\","); var validMessages = messages.Where(m => { @@ -167,7 +168,7 @@ namespace WulaFallenEmpire.EventSystem.AI return response; } - public async Task GetChatCompletionWithToolsAsync(string instruction, List messages, List> tools, int? maxTokens = null, float? temperature = null) + public async Task GetChatCompletionWithToolsAsync(string instruction, List messages, List> tools, int? maxTokens = null, float? temperature = null, string toolChoice = null) { if (_useGemini) { @@ -185,7 +186,7 @@ namespace WulaFallenEmpire.EventSystem.AI if (_baseUrl.EndsWith("/chat/completions")) endpoint = _baseUrl; else if (!_baseUrl.EndsWith("/v1")) endpoint = $"{_baseUrl}/v1/chat/completions"; - string jsonBody = BuildChatRequestBody(instruction, messages, tools, maxTokens, temperature); + string jsonBody = BuildChatRequestBody(instruction, messages, tools, maxTokens, temperature, toolChoice); string response = await SendRequestRawAsync(endpoint, jsonBody, _apiKey); if (response == null) return null; @@ -306,7 +307,7 @@ namespace WulaFallenEmpire.EventSystem.AI } } - private string BuildChatRequestBody(string instruction, List messages, List> tools, int? maxTokens, float? temperature) + private string BuildChatRequestBody(string instruction, List messages, List> tools, int? maxTokens, float? temperature, string toolChoice) { var body = new Dictionary { @@ -316,6 +317,7 @@ namespace WulaFallenEmpire.EventSystem.AI if (maxTokens.HasValue) body["max_tokens"] = Math.Max(1, maxTokens.Value); if (temperature.HasValue) body["temperature"] = temperature.Value; + if (!string.IsNullOrWhiteSpace(toolChoice)) body["tool_choice"] = toolChoice; var messageList = new List(); if (!string.IsNullOrEmpty(instruction))