diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll
index b9b27524..8cff6e72 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/UI/Dialog_AIConversation.cs b/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs
index 19e91808..6b478305 100644
--- a/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs
+++ b/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs
@@ -270,13 +270,19 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
"- call_bombardment\n" +
"- modify_goodwill\n" +
"If no action is required, output exactly: .\n" +
- "Other tools are still available if needed.\n"
+ "Query tools exist but are disabled in this phase (not listed here).\n"
+ : string.Empty;
+ string actionWhitelist = phase == RequestPhase.ActionTools
+ ? "ACTION PHASE VALID TAGS ONLY:\n" +
+ ", , , , \n" +
+ "INVALID EXAMPLES (do NOT use now): , \n"
: string.Empty;
return string.Join("\n\n", new[]
{
phaseInstruction,
string.IsNullOrWhiteSpace(actionPriority) ? null : actionPriority.TrimEnd(),
+ string.IsNullOrWhiteSpace(actionWhitelist) ? null : actionWhitelist.TrimEnd(),
ToolRulesInstruction.TrimEnd(),
toolsForThisPhase
}.Where(part => !string.IsNullOrWhiteSpace(part)));
@@ -288,6 +294,11 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
var available = _tools
.Where(t => t != null)
+ .Where(t => phase == RequestPhase.QueryTools
+ ? IsQueryToolName(t.Name)
+ : phase == RequestPhase.ActionTools
+ ? IsActionToolName(t.Name)
+ : true)
.OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
@@ -328,6 +339,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
"- Prefer query tools (get_*/search_*).\n" +
"- You MAY call multiple tools in one response, but keep it concise.\n" +
"- If the user requests multiple items or information, you MUST output ALL required tool calls in this SAME response.\n" +
+ "- Action tools are available in PHASE 2 only; do NOT use them here.\n" +
"After this phase, the game will automatically proceed to PHASE 2.\n" +
"Output: XML only.\n",
RequestPhase.ActionTools =>
@@ -379,7 +391,38 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
};
}
- private List<(string role, string message)> BuildToolContext(int maxToolResults = 2)
+ private static bool IsActionToolName(string toolName)
+ {
+ return toolName == "spawn_resources" ||
+ toolName == "send_reinforcement" ||
+ toolName == "call_bombardment" ||
+ toolName == "modify_goodwill";
+ }
+
+ private static bool IsQueryToolName(string toolName)
+ {
+ if (string.IsNullOrWhiteSpace(toolName)) return false;
+ return toolName.StartsWith("get_", StringComparison.OrdinalIgnoreCase) ||
+ toolName.StartsWith("search_", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static string SanitizeToolResultForActionPhase(string message)
+ {
+ if (string.IsNullOrWhiteSpace(message)) return message;
+ string sanitized = message;
+ sanitized = Regex.Replace(sanitized, @"Tool\s+'[^']+'\s+Result(?:\s+\(Invisible\))?:", "Query Result:");
+ sanitized = Regex.Replace(sanitized, @"Tool\s+'[^']+'\s+Result\s+\(Invisible\):", "Query Result:");
+ return sanitized;
+ }
+
+ private static string TrimForPrompt(string text, int maxChars)
+ {
+ if (string.IsNullOrWhiteSpace(text)) return "";
+ if (text.Length <= maxChars) return text;
+ return text.Substring(0, maxChars) + "...(truncated)";
+ }
+
+ private List<(string role, string message)> BuildToolContext(RequestPhase phase, int maxToolResults = 2, bool includeUser = true)
{
if (_history == null || _history.Count == 0) return new List<(string role, string message)>();
@@ -400,7 +443,12 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
{
if (string.Equals(_history[i].role, "tool", StringComparison.OrdinalIgnoreCase))
{
- toolEntries.Add(_history[i]);
+ string msg = _history[i].message;
+ if (phase == RequestPhase.ActionTools)
+ {
+ msg = SanitizeToolResultForActionPhase(msg);
+ }
+ toolEntries.Add((_history[i].role, msg));
}
}
@@ -409,10 +457,12 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
toolEntries = toolEntries.Skip(toolEntries.Count - maxToolResults).ToList();
}
- var context = new List<(string role, string message)>
+ bool includeUserFallback = includeUser || toolEntries.Count == 0;
+ var context = new List<(string role, string message)>();
+ if (includeUserFallback)
{
- _history[lastUserIndex]
- };
+ context.Add(_history[lastUserIndex]);
+ }
context.AddRange(toolEntries);
return context;
}
@@ -459,7 +509,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
}
string queryInstruction = GetToolSystemInstruction(queryPhase);
- string queryResponse = await client.GetChatCompletionAsync(queryInstruction, BuildToolContext(), maxTokens: 128, temperature: 0.1f);
+ string queryResponse = await client.GetChatCompletionAsync(queryInstruction, BuildToolContext(queryPhase), maxTokens: 128, temperature: 0.1f);
if (string.IsNullOrEmpty(queryResponse))
{
_currentResponse = "Wula_AI_Error_ConnectionLost".Translate();
@@ -501,7 +551,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
SetThinkingPhase(1, true);
string retryQueryInstruction = GetToolSystemInstruction(queryPhase) +
"\n\n# RETRY\nYou chose to retry. Output XML tool calls only (or ).";
- string retryQueryResponse = await client.GetChatCompletionAsync(retryQueryInstruction, BuildToolContext(), maxTokens: 128, temperature: 0.1f);
+ string retryQueryResponse = await client.GetChatCompletionAsync(retryQueryInstruction, BuildToolContext(queryPhase), maxTokens: 128, temperature: 0.1f);
if (string.IsNullOrEmpty(retryQueryResponse))
{
_currentResponse = "Wula_AI_Error_ConnectionLost".Translate();
@@ -529,7 +579,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
SetThinkingPhase(2, false);
string actionInstruction = GetToolSystemInstruction(actionPhase);
- string actionResponse = await client.GetChatCompletionAsync(actionInstruction, BuildToolContext(), maxTokens: 128, temperature: 0.1f);
+ var actionContext = BuildToolContext(actionPhase, includeUser: true);
+ string actionResponse = await client.GetChatCompletionAsync(actionInstruction, actionContext, maxTokens: 128, temperature: 0.1f);
if (string.IsNullOrEmpty(actionResponse))
{
_currentResponse = "Wula_AI_Error_ConnectionLost".Translate();
@@ -540,9 +591,28 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
{
if (Prefs.DevMode)
{
- WulaLog.Debug("[WulaAI] Turn 2/3 missing XML; treating as ");
+ WulaLog.Debug("[WulaAI] Turn 2/3 missing XML; attempting XML-only conversion.");
+ }
+ string fixInstruction = actionInstruction +
+ "\n\n# FORMAT FIX\n" +
+ "Your last output was not valid XML.\n" +
+ "Convert the intended action into VALID XML tool calls or .\n" +
+ "Output XML only. No commentary.\n" +
+ "You MUST use only action tools.\n" +
+ "\nPrevious output:\n" + TrimForPrompt(actionResponse, 600);
+ string fixedResponse = await client.GetChatCompletionAsync(fixInstruction, actionContext, maxTokens: 128, temperature: 0.1f);
+ if (!string.IsNullOrEmpty(fixedResponse) && IsXmlToolCall(fixedResponse))
+ {
+ actionResponse = fixedResponse;
+ }
+ else
+ {
+ if (Prefs.DevMode)
+ {
+ WulaLog.Debug("[WulaAI] Turn 2/3 conversion failed; treating as ");
+ }
+ actionResponse = "";
}
- actionResponse = "";
}
PhaseExecutionResult actionResult = await ExecuteXmlToolsForPhase(actionResponse, actionPhase);
@@ -569,8 +639,9 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
SetThinkingPhase(2, true);
string retryActionInstruction = GetToolSystemInstruction(actionPhase) +
- "\n\n# RETRY\nYou chose to retry. Output XML tool calls only (or ).";
- string retryActionResponse = await client.GetChatCompletionAsync(retryActionInstruction, BuildToolContext(), maxTokens: 128, temperature: 0.1f);
+ "\n\n# RETRY\nYou chose to retry. Output XML tool calls only (or ).";
+ var retryActionContext = BuildToolContext(actionPhase, includeUser: true);
+ string retryActionResponse = await client.GetChatCompletionAsync(retryActionInstruction, retryActionContext, maxTokens: 128, temperature: 0.1f);
if (string.IsNullOrEmpty(retryActionResponse))
{
_currentResponse = "Wula_AI_Error_ConnectionLost".Translate();
@@ -581,9 +652,28 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
{
if (Prefs.DevMode)
{
- WulaLog.Debug("[WulaAI] Retry action phase missing XML; treating as ");
+ WulaLog.Debug("[WulaAI] Retry action phase missing XML; attempting XML-only conversion.");
+ }
+ string retryFixInstruction = retryActionInstruction +
+ "\n\n# FORMAT FIX\n" +
+ "Your last output was not valid XML.\n" +
+ "Convert the intended action into VALID XML tool calls or .\n" +
+ "Output XML only. No commentary.\n" +
+ "You MUST use only action tools.\n" +
+ "\nPrevious output:\n" + TrimForPrompt(retryActionResponse, 600);
+ string retryFixedResponse = await client.GetChatCompletionAsync(retryFixInstruction, retryActionContext, maxTokens: 128, temperature: 0.1f);
+ if (!string.IsNullOrEmpty(retryFixedResponse) && IsXmlToolCall(retryFixedResponse))
+ {
+ retryActionResponse = retryFixedResponse;
+ }
+ else
+ {
+ if (Prefs.DevMode)
+ {
+ WulaLog.Debug("[WulaAI] Retry action conversion failed; treating as ");
+ }
+ retryActionResponse = "";
}
- retryActionResponse = "";
}
actionResult = await ExecuteXmlToolsForPhase(retryActionResponse, actionPhase);
@@ -622,14 +712,31 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
replyInstruction += "\nIMPORTANT: An action tool failed. You MUST acknowledge the failure and MUST NOT claim success.";
}
- string reply = await client.GetChatCompletionAsync(replyInstruction, _history);
+ string reply = await client.GetChatCompletionAsync(replyInstruction, BuildReplyHistory());
if (string.IsNullOrEmpty(reply))
{
_currentResponse = "Wula_AI_Error_ConnectionLost".Translate();
return;
}
- if (IsXmlToolCall(reply))
+ bool replyHadXml = IsXmlToolCall(reply);
+ string strippedReply = StripXmlTags(reply)?.Trim() ?? "";
+ if (replyHadXml || string.IsNullOrWhiteSpace(strippedReply))
+ {
+ string retryReplyInstruction = replyInstruction +
+ "\n\n# RETRY (REPLY OUTPUT)\n" +
+ "Your last reply included XML or was empty. Tool calls are DISABLED.\n" +
+ "You MUST reply in natural language only. Do NOT output any XML.\n";
+ string retryReply = await client.GetChatCompletionAsync(retryReplyInstruction, BuildReplyHistory(), maxTokens: 256, temperature: 0.3f);
+ if (!string.IsNullOrEmpty(retryReply))
+ {
+ reply = retryReply;
+ replyHadXml = IsXmlToolCall(reply);
+ strippedReply = StripXmlTags(reply)?.Trim() ?? "";
+ }
+ }
+
+ if (replyHadXml)
{
string cleaned = StripXmlTags(reply)?.Trim() ?? "";
if (string.IsNullOrWhiteSpace(cleaned))
@@ -682,21 +789,6 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
return default;
}
- static bool IsActionToolName(string toolName)
- {
- return toolName == "spawn_resources" ||
- toolName == "send_reinforcement" ||
- toolName == "call_bombardment" ||
- toolName == "modify_goodwill";
- }
-
- static bool IsQueryToolName(string toolName)
- {
- if (string.IsNullOrWhiteSpace(toolName)) return false;
- return toolName.StartsWith("get_", StringComparison.OrdinalIgnoreCase) ||
- toolName.StartsWith("search_", StringComparison.OrdinalIgnoreCase);
- }
-
int maxTools = MaxToolsPerPhase(phase);
int executed = 0;
bool executedActionTool = false;
@@ -920,6 +1012,49 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
return stripped;
}
+ private List<(string role, string message)> BuildReplyHistory()
+ {
+ if (_history == null || _history.Count == 0) return new List<(string role, string message)>();
+
+ int lastUserIndex = -1;
+ for (int i = _history.Count - 1; i >= 0; i--)
+ {
+ if (string.Equals(_history[i].role, "user", StringComparison.OrdinalIgnoreCase))
+ {
+ lastUserIndex = i;
+ break;
+ }
+ }
+
+ var filtered = new List<(string role, string message)>();
+ for (int i = 0; i < _history.Count; i++)
+ {
+ var entry = _history[i];
+ if (string.Equals(entry.role, "tool", StringComparison.OrdinalIgnoreCase))
+ {
+ if (lastUserIndex != -1 && i > lastUserIndex)
+ {
+ filtered.Add(entry);
+ }
+ continue;
+ }
+
+ if (!string.Equals(entry.role, "assistant", StringComparison.OrdinalIgnoreCase))
+ {
+ filtered.Add(entry);
+ continue;
+ }
+
+ string stripped = StripXmlTags(entry.message)?.Trim() ?? "";
+ if (!string.IsNullOrWhiteSpace(stripped))
+ {
+ filtered.Add(entry);
+ }
+ }
+
+ return filtered;
+ }
+
private string StripExpressionTags(string text)
{
if (string.IsNullOrEmpty(text)) return text;