This commit is contained in:
2025-12-31 17:00:50 +08:00
parent fbe48438a2
commit 6f8f205ae9
3 changed files with 34 additions and 52 deletions

View File

@@ -51,8 +51,6 @@ namespace WulaFallenEmpire.EventSystem.AI
private readonly List<string> _actionFailedLedger = new List<string>(); private readonly List<string> _actionFailedLedger = new List<string>();
private readonly HashSet<string> _actionFailedLedgerSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _actionFailedLedgerSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private SimpleAIClient _currentClient; private SimpleAIClient _currentClient;
private string _memoryContext;
private string _memoryContextQuery;
private bool _memoryUpdateInProgress; private bool _memoryUpdateInProgress;
private const int DefaultMaxHistoryTokens = 100000; private const int DefaultMaxHistoryTokens = 100000;
@@ -494,8 +492,6 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
private void ClearHistory() private void ClearHistory()
{ {
_history.Clear(); _history.Clear();
_memoryContext = null;
_memoryContextQuery = null;
try try
{ {
var historyManager = Find.World?.GetComponent<AIHistoryManager>(); var historyManager = Find.World?.GetComponent<AIHistoryManager>();
@@ -563,8 +559,6 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
string safeQuery = query ?? ""; string safeQuery = query ?? "";
if (IsAutoCommentaryMessage(safeQuery)) if (IsAutoCommentaryMessage(safeQuery))
{ {
_memoryContextQuery = "";
_memoryContext = "";
if (Prefs.DevMode) if (Prefs.DevMode)
{ {
WulaLog.Debug("[WulaAI] Memory context skipped (auto commentary)."); WulaLog.Debug("[WulaAI] Memory context skipped (auto commentary).");
@@ -572,30 +566,16 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
return; return;
} }
_memoryContextQuery = safeQuery;
_memoryContext = BuildMemoryContext(_memoryContextQuery);
if (Prefs.DevMode) if (Prefs.DevMode)
{ {
string preview = TrimForPrompt(_memoryContextQuery, 80); string preview = TrimForPrompt(safeQuery, 80);
int length = _memoryContext?.Length ?? 0; WulaLog.Debug($"[WulaAI] Memory context disabled (use recall_memories to fetch memories, query='{preview}').");
WulaLog.Debug($"[WulaAI] Memory context refreshed (query='{preview}', length={length}).");
} }
} }
private string GetMemoryContext() private string GetMemoryContext()
{ {
if (string.IsNullOrWhiteSpace(_memoryContext)) return "";
{
string query = _memoryContextQuery;
if (string.IsNullOrWhiteSpace(query))
{
query = GetLastUserMessageForMemory();
}
_memoryContextQuery = query ?? "";
_memoryContext = BuildMemoryContext(_memoryContextQuery);
}
return _memoryContext ?? "";
} }
private string GetLastUserMessageForMemory() private string GetLastUserMessageForMemory()
@@ -664,12 +644,6 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
? (persona + "\n" + ToolRulesInstruction + "\n" + toolsForThisPhase) ? (persona + "\n" + ToolRulesInstruction + "\n" + toolsForThisPhase)
: persona; : persona;
string memoryContext = GetMemoryContext();
if (!string.IsNullOrWhiteSpace(memoryContext))
{
fullInstruction += memoryContext;
}
string language = LanguageDatabase.activeLanguage?.FriendlyNameNative ?? "English"; string language = LanguageDatabase.activeLanguage?.FriendlyNameNative ?? "English";
var eventVarManager = Find.World?.GetComponent<EventVariableManager>(); var eventVarManager = Find.World?.GetComponent<EventVariableManager>();
int goodwill = eventVarManager?.GetVariable<int>("Wula_Goodwill_To_PIA", 0) ?? 0; int goodwill = eventVarManager?.GetVariable<int>("Wula_Goodwill_To_PIA", 0) ?? 0;
@@ -693,8 +667,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
private string GetNativeSystemInstruction() private string GetNativeSystemInstruction()
{ {
string persona = GetActivePersona(); string persona = GetActivePersona();
string memoryContext = GetMemoryContext(); string personaBlock = persona;
string personaBlock = string.IsNullOrWhiteSpace(memoryContext) ? persona : (persona + memoryContext);
string language = LanguageDatabase.activeLanguage?.FriendlyNameNative ?? "English"; string language = LanguageDatabase.activeLanguage?.FriendlyNameNative ?? "English";
var eventVarManager = Find.World?.GetComponent<EventVariableManager>(); var eventVarManager = Find.World?.GetComponent<EventVariableManager>();
@@ -711,6 +684,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
sb.AppendLine(goodwillContext); sb.AppendLine(goodwillContext);
sb.AppendLine($"IMPORTANT: Reply in the following language: {language}."); sb.AppendLine($"IMPORTANT: Reply in the following language: {language}.");
sb.AppendLine("IMPORTANT: Use tools to fetch in-game data or perform actions. Do NOT invent tool results."); sb.AppendLine("IMPORTANT: Use tools to fetch in-game data or perform actions. Do NOT invent tool results.");
sb.AppendLine("IMPORTANT: Long-term memory is not preloaded. Use recall_memories to fetch memories when needed.");
sb.AppendLine("IMPORTANT: When the user asks for an item by name, call search_thing_def to confirm the exact defName before spawning."); sb.AppendLine("IMPORTANT: When the user asks for an item by name, call search_thing_def to confirm the exact defName before spawning.");
sb.AppendLine("You MAY include [EXPR:n] (n=1-6) to set your expression."); sb.AppendLine("You MAY include [EXPR:n] (n=1-6) to set your expression.");
return sb.ToString().TrimEnd(); return sb.ToString().TrimEnd();
@@ -735,8 +709,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
private string GetToolSystemInstruction(RequestPhase phase, bool hasImage) private string GetToolSystemInstruction(RequestPhase phase, bool hasImage)
{ {
string persona = GetActivePersona(); string persona = GetActivePersona();
string memoryContext = GetMemoryContext(); string personaBlock = persona;
string personaBlock = string.IsNullOrWhiteSpace(memoryContext) ? persona : (persona + memoryContext);
string phaseInstruction = GetPhaseInstruction(phase).TrimEnd(); string phaseInstruction = GetPhaseInstruction(phase).TrimEnd();
string toolsForThisPhase = BuildToolsForPhase(phase); string toolsForThisPhase = BuildToolsForPhase(phase);
string actionPriority = phase == RequestPhase.ActionTools string actionPriority = phase == RequestPhase.ActionTools
@@ -770,6 +743,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
{ {
personaBlock, personaBlock,
phaseInstruction, phaseInstruction,
"IMPORTANT: Long-term memory is not preloaded. Use recall_memories to fetch memories when needed.",
string.IsNullOrWhiteSpace(actionPriority) ? null : actionPriority.TrimEnd(), string.IsNullOrWhiteSpace(actionPriority) ? null : actionPriority.TrimEnd(),
string.IsNullOrWhiteSpace(actionWhitelist) ? null : actionWhitelist.TrimEnd(), string.IsNullOrWhiteSpace(actionWhitelist) ? null : actionWhitelist.TrimEnd(),
ToolRulesInstruction.TrimEnd(), ToolRulesInstruction.TrimEnd(),
@@ -870,8 +844,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
private string GetReactSystemInstruction(bool hasImage) private string GetReactSystemInstruction(bool hasImage)
{ {
string persona = GetActivePersona(); string persona = GetActivePersona();
string memoryContext = GetMemoryContext(); string personaBlock = persona;
string personaBlock = string.IsNullOrWhiteSpace(memoryContext) ? persona : (persona + memoryContext);
string language = LanguageDatabase.activeLanguage?.FriendlyNameNative ?? "English"; string language = LanguageDatabase.activeLanguage?.FriendlyNameNative ?? "English";
var eventVarManager = Find.World?.GetComponent<EventVariableManager>(); var eventVarManager = Find.World?.GetComponent<EventVariableManager>();
@@ -884,6 +857,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine(personaBlock); sb.AppendLine(personaBlock);
sb.AppendLine("IMPORTANT: Long-term memory is not preloaded. Use recall_memories to fetch memories when needed.");
sb.AppendLine(); sb.AppendLine();
sb.AppendLine(ToolRulesInstruction.TrimEnd()); sb.AppendLine(ToolRulesInstruction.TrimEnd());
sb.AppendLine(); sb.AppendLine();
@@ -2255,6 +2229,12 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
int maxSteps = Math.Max(1, settings.reactMaxSteps); int maxSteps = Math.Max(1, settings.reactMaxSteps);
float maxSeconds = Math.Max(2f, settings.reactMaxSeconds); float maxSeconds = Math.Max(2f, settings.reactMaxSeconds);
_thinkingPhaseTotal = maxSteps; _thinkingPhaseTotal = maxSteps;
int strictRetryCount = 0;
const int MaxStrictRetries = 2;
const string StrictRetryGuidance =
"ToolRunner Error: Your last response was rejected because tool_calls was empty. " +
"You MUST call tools via the tool_calls field. Do NOT output XML or natural language. " +
"If no tools are needed, return no tool calls and leave content empty.";
for (int step = 1; step <= maxSteps; step++) for (int step = 1; step <= maxSteps; step++)
{ {
@@ -2282,6 +2262,25 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
return; return;
} }
if (result.ToolCalls == null || result.ToolCalls.Count == 0)
{
if (strictRetryCount < MaxStrictRetries)
{
strictRetryCount++;
messages.Add(ChatMessage.User(StrictRetryGuidance));
if (Prefs.DevMode)
{
WulaLog.Debug($"[WulaAI] Native tool loop retry: missing tool_calls (attempt {strictRetryCount}/{MaxStrictRetries}).");
}
step--;
continue;
}
break;
}
strictRetryCount = 0;
if (result.ToolCalls != null && result.ToolCalls.Count > 0) if (result.ToolCalls != null && result.ToolCalls.Count > 0)
{ {
int maxTools = ReactMaxToolsPerStep; int maxTools = ReactMaxToolsPerStep;

View File

@@ -15,15 +15,6 @@ namespace WulaFallenEmpire.EventSystem.AI.Utils
public static class JsonToolCallParser public static class JsonToolCallParser
{ {
private static string NormalizeToolNameFromId(string id)
{
if (string.IsNullOrWhiteSpace(id)) return null;
string trimmed = id.Trim();
if (trimmed.StartsWith("call_", StringComparison.OrdinalIgnoreCase)) return null;
if (trimmed.StartsWith("toolu_", StringComparison.OrdinalIgnoreCase)) return null;
return trimmed;
}
public static bool TryParseToolCalls(string input, out List<ToolCallInfo> toolCalls) public static bool TryParseToolCalls(string input, out List<ToolCallInfo> toolCalls)
{ {
toolCalls = null; toolCalls = null;
@@ -55,10 +46,6 @@ namespace WulaFallenEmpire.EventSystem.AI.Utils
TryGetValue(callObj, "arguments", out argsObj); TryGetValue(callObj, "arguments", out argsObj);
} }
if (string.IsNullOrWhiteSpace(name))
{
name = NormalizeToolNameFromId(TryGetString(callObj, "id"));
}
if (string.IsNullOrWhiteSpace(name)) continue; if (string.IsNullOrWhiteSpace(name)) continue;
if (!TryNormalizeArguments(argsObj, out Dictionary<string, object> args, out string argsJson)) if (!TryNormalizeArguments(argsObj, out Dictionary<string, object> args, out string argsJson))
@@ -180,10 +167,6 @@ namespace WulaFallenEmpire.EventSystem.AI.Utils
TryGetValue(callObj, "arguments", out argsObj); TryGetValue(callObj, "arguments", out argsObj);
} }
if (string.IsNullOrWhiteSpace(name))
{
name = NormalizeToolNameFromId(TryGetString(callObj, "id"));
}
if (string.IsNullOrWhiteSpace(name)) continue; if (string.IsNullOrWhiteSpace(name)) continue;
if (!TryNormalizeArguments(argsObj, out Dictionary<string, object> args, out string argsJson)) if (!TryNormalizeArguments(argsObj, out Dictionary<string, object> args, out string argsJson))