diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 23b3ffe5..71358ee2 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs b/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs index d4b4d392..64492290 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs @@ -489,6 +489,10 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori if (hasImage && WulaFallenEmpireMod.settings?.enableVlmFeatures == true) { phaseInstruction += "\n- NATIVE MULTIMODAL: A current screenshot of the game is attached to this request. You can see the game state directly. Use it to determine coordinates for visual tools or to understand the context."; + if (phase == RequestPhase.ActionTools) + { + phaseInstruction += "\n- VISUAL PHASE RULE: This phase is for ACTIONS only. If you want to describe the screen to the user, wait for the next phase (Reply Phase). Output XML actions only here."; + } } string actionWhitelist = phase == RequestPhase.ActionTools @@ -1150,7 +1154,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori AddAssistantMessage("[P.I.A] 正在汇总战报并建立通讯记录..."); } - string reply = await client.GetChatCompletionAsync(replyInstruction, BuildReplyHistory()); + // VISUAL CONTEXT FOR REPLY: Pass the image so the AI can describe what it sees. + string reply = await client.GetChatCompletionAsync(replyInstruction, BuildReplyHistory(), base64Image: base64Image); if (string.IsNullOrEmpty(reply)) { AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate()); diff --git a/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs b/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs index c6345057..163daf01 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs @@ -60,23 +60,44 @@ namespace WulaFallenEmpire.EventSystem.AI 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)},"); + var validMessages = messages.Where(m => + { + string r = (m.role ?? "user").ToLowerInvariant(); + return r != "toolcall"; + }).ToList(); + jsonBuilder.Append("\"messages\": ["); if (!string.IsNullOrEmpty(instruction)) { - jsonBuilder.Append($"{{\"role\": \"system\", \"content\": \"{EscapeJson(instruction)}\"}},"); + jsonBuilder.Append($"{{\"role\": \"system\", \"content\": \"{EscapeJson(instruction)}\"}}"); + if (validMessages.Count > 0) jsonBuilder.Append(","); } - for (int i = 0; i < messages.Count; i++) + // Find the index of the last user message to attach the image to + int lastUserIndex = -1; + if (!string.IsNullOrEmpty(base64Image)) { - var msg = messages[i]; + for (int i = validMessages.Count - 1; i >= 0; i--) + { + string r = (validMessages[i].role ?? "user").ToLowerInvariant(); + if (r != "ai" && r != "assistant" && r != "tool" && r != "system") + { + lastUserIndex = i; + break; + } + } + } + + for (int i = 0; i < validMessages.Count; i++) + { + var msg = validMessages[i]; string role = (msg.role ?? "user").ToLowerInvariant(); if (role == "ai" || role == "assistant") role = "assistant"; else if (role == "tool") role = "system"; - else if (role == "toolcall") continue; jsonBuilder.Append($"{{\"role\": \"{role}\", "); - if (i == messages.Count - 1 && role == "user" && !string.IsNullOrEmpty(base64Image)) + if (i == lastUserIndex && !string.IsNullOrEmpty(base64Image)) { jsonBuilder.Append("\"content\": ["); jsonBuilder.Append($"{{\"type\": \"text\", \"text\": \"{EscapeJson(msg.message)}\"}},"); @@ -89,7 +110,7 @@ namespace WulaFallenEmpire.EventSystem.AI } jsonBuilder.Append("}"); - if (i < messages.Count - 1) jsonBuilder.Append(","); + if (i < validMessages.Count - 1) jsonBuilder.Append(","); } jsonBuilder.Append("]}"); @@ -215,6 +236,35 @@ namespace WulaFallenEmpire.EventSystem.AI { if (string.IsNullOrWhiteSpace(json)) return null; + // Handle SSE Stream (data: ...) + // Some endpoints return SSE streams even if stream=false is requested. + // We strip 'data:' prefix and aggregate the content deltas. + if (json.TrimStart().StartsWith("data:")) + { + StringBuilder sb = new StringBuilder(); + string[] lines = json.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string line in lines) + { + string trimmed = line.Trim(); + if (trimmed.StartsWith("data:") && !trimmed.Contains("[DONE]")) + { + string chunkJson = trimmed.Substring(5).Trim(); + // Extract content from this chunk + string chunkContent = ExtractContentFromSingleJson(chunkJson); + if (!string.IsNullOrEmpty(chunkContent)) + { + sb.Append(chunkContent); + } + } + } + return sb.ToString(); + } + + return ExtractContentFromSingleJson(json); + } + + private string ExtractContentFromSingleJson(string json) + { try { // 1. Gemini format