zc
This commit is contained in:
Binary file not shown.
@@ -55,10 +55,10 @@ namespace WulaFallenEmpire.EventSystem.AI
|
||||
|
||||
private const int DefaultMaxHistoryTokens = 100000;
|
||||
private const int CharsPerToken = 4;
|
||||
private const int ReactMaxSteps = 4;
|
||||
private const int DefaultReactMaxSteps = 4;
|
||||
private const int ReactMaxToolsPerStep = 8;
|
||||
private const float ReactMaxSeconds = 12f;
|
||||
private const int ThinkingPhaseTotal = ReactMaxSteps;
|
||||
private const float DefaultReactMaxSeconds = 12f;
|
||||
private int _thinkingPhaseTotal = DefaultReactMaxSteps;
|
||||
|
||||
private static readonly Regex ExpressionTagRegex = new Regex(@"\[EXPR\s*:\s*([1-6])\s*\]", RegexOptions.IgnoreCase);
|
||||
private const string AutoCommentaryTag = "[AUTO_COMMENTARY]";
|
||||
@@ -108,7 +108,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
||||
- If the user requests multiple items or information, you MUST output ALL required tool calls in the SAME response.
|
||||
- Do NOT split multi-item requests across turns.
|
||||
5. **TOOLS**: You MAY call any tools listed in ""# TOOLS (AVAILABLE)"".
|
||||
6. **ANTI-HALLUCINATION**: Never invent tools, parameters, defNames, coordinates, or tool results. If a tool is needed but not available, output { ""thought"": ""..."", ""tool_calls"": [], ""final"": """" }.";
|
||||
6. **ANTI-HALLUCINATION**: Never invent tools, parameters, defNames, coordinates, or tool results. If a tool is needed but not available, output { ""thought"": ""..."", ""tool_calls"": [], ""final"": """" }.
|
||||
7. **NO TAGS**: Do NOT use <think> tags, code fences, or any extra text outside JSON.";
|
||||
|
||||
public AIIntelligenceCore(World world) : base(world)
|
||||
{
|
||||
@@ -190,6 +191,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
||||
public float ThinkingStartTime => _thinkingStartTime;
|
||||
public int ThinkingPhaseIndex => _thinkingPhaseIndex;
|
||||
public bool ThinkingPhaseRetry => _thinkingPhaseRetry;
|
||||
public int ThinkingPhaseTotal => _thinkingPhaseTotal;
|
||||
public void InitializeConversation(string eventDefName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(eventDefName))
|
||||
@@ -443,7 +445,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
||||
|
||||
private void SetThinkingPhase(int phaseIndex, bool isRetry)
|
||||
{
|
||||
_thinkingPhaseIndex = Math.Max(1, Math.Min(ThinkingPhaseTotal, phaseIndex));
|
||||
_thinkingPhaseIndex = Math.Max(1, Math.Min(_thinkingPhaseTotal, phaseIndex));
|
||||
_thinkingPhaseRetry = isRetry;
|
||||
_thinkingStartTime = Time.realtimeSinceStartup;
|
||||
}
|
||||
@@ -968,6 +970,48 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
||||
"Schema: {\"thought\":\"...\",\"tool_calls\":[],\"final\":\"...\"}";
|
||||
}
|
||||
|
||||
private static string NormalizeReactResponse(string response)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(response)) return response;
|
||||
string cleaned = response.Trim();
|
||||
cleaned = Regex.Replace(cleaned, @"<think>.*?</think>", "", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
cleaned = Regex.Replace(cleaned, @"```json", "", RegexOptions.IgnoreCase);
|
||||
cleaned = cleaned.Replace("```", "");
|
||||
return cleaned.Trim();
|
||||
}
|
||||
|
||||
private static bool TryGetNonJsonFinal(string response, out string final)
|
||||
{
|
||||
final = null;
|
||||
if (string.IsNullOrWhiteSpace(response)) return false;
|
||||
|
||||
string cleaned = NormalizeReactResponse(response);
|
||||
cleaned = StripToolCallJson(cleaned) ?? cleaned;
|
||||
cleaned = cleaned.Trim();
|
||||
if (string.IsNullOrWhiteSpace(cleaned)) return false;
|
||||
|
||||
final = cleaned;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsToolAvailable(string toolName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(toolName)) return false;
|
||||
if (string.Equals(toolName, "capture_screen", StringComparison.OrdinalIgnoreCase)) return true;
|
||||
return _tools.Any(t => string.Equals(t?.Name, toolName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static string BuildReactToolCorrectionInstruction(IEnumerable<string> invalidTools)
|
||||
{
|
||||
string invalidList = invalidTools == null ? "" : string.Join(", ", invalidTools);
|
||||
return "# TOOL CORRECTION (REACT JSON ONLY)\n" +
|
||||
"You used tool names that are NOT available: " + invalidList + "\n" +
|
||||
"Re-emit JSON with only available tools from # TOOLS (AVAILABLE).\n" +
|
||||
"If no tools are needed, output tool_calls=[] and provide final.\n" +
|
||||
"Do NOT output any text outside JSON.\n" +
|
||||
"Schema: {\"thought\":\"...\",\"tool_calls\":[{\"type\":\"function\",\"function\":{\"name\":\"tool_name\",\"arguments\":{...}}}],\"final\":\"\"}";
|
||||
}
|
||||
|
||||
private static bool ShouldRetryTools(string response)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(response)) return false;
|
||||
@@ -1758,11 +1802,20 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
||||
// Model-Driven Vision: Start with null image. The model must request it using analyze_screen or capture_screen if needed.
|
||||
string base64Image = null;
|
||||
float startTime = Time.realtimeSinceStartup;
|
||||
int maxSteps = DefaultReactMaxSteps;
|
||||
float maxSeconds = DefaultReactMaxSeconds;
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
maxSteps = Math.Max(1, Math.Min(10, settings.reactMaxSteps));
|
||||
maxSeconds = Mathf.Clamp(settings.reactMaxSeconds, 2f, 60f);
|
||||
}
|
||||
_thinkingPhaseTotal = maxSteps;
|
||||
string finalReply = null;
|
||||
|
||||
for (int step = 1; step <= ReactMaxSteps; step++)
|
||||
for (int step = 1; step <= maxSteps; step++)
|
||||
{
|
||||
if (Time.realtimeSinceStartup - startTime > ReactMaxSeconds)
|
||||
if (Time.realtimeSinceStartup - startTime > maxSeconds)
|
||||
{
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
@@ -1782,27 +1835,69 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryParseReactEnvelope(response, out var toolCalls, out string final, out _, out string jsonFragment))
|
||||
string normalizedResponse = NormalizeReactResponse(response);
|
||||
if (!TryParseReactEnvelope(normalizedResponse, out var toolCalls, out string final, out _, out string jsonFragment))
|
||||
{
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
WulaLog.Debug("[WulaAI] ReAct step missing JSON envelope; attempting format fix.");
|
||||
}
|
||||
string fixInstruction = BuildReactFormatFixInstruction(response);
|
||||
string fixInstruction = BuildReactFormatFixInstruction(normalizedResponse);
|
||||
string fixedResponse = await client.GetChatCompletionAsync(fixInstruction, reactContext, maxTokens: 1024, temperature: 0.1f, base64Image: base64Image);
|
||||
if (string.IsNullOrEmpty(fixedResponse) ||
|
||||
!TryParseReactEnvelope(fixedResponse, out toolCalls, out final, out _, out jsonFragment))
|
||||
string normalizedFixed = NormalizeReactResponse(fixedResponse);
|
||||
if (string.IsNullOrEmpty(normalizedFixed) ||
|
||||
!TryParseReactEnvelope(normalizedFixed, out toolCalls, out final, out _, out jsonFragment))
|
||||
{
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
WulaLog.Debug("[WulaAI] ReAct format fix failed.");
|
||||
}
|
||||
if (TryGetNonJsonFinal(response, out string fallbackFinal))
|
||||
{
|
||||
finalReply = fallbackFinal;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var invalidTools = toolCalls
|
||||
.Where(c => !IsToolAvailable(c.Name))
|
||||
.Select(c => c.Name)
|
||||
.Where(n => !string.IsNullOrWhiteSpace(n))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
if (invalidTools.Count > 0)
|
||||
{
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
WulaLog.Debug($"[WulaAI] ReAct step used invalid tools: {string.Join(", ", invalidTools)}");
|
||||
}
|
||||
string correctionInstruction = BuildReactToolCorrectionInstruction(invalidTools);
|
||||
string correctedResponse = await client.GetChatCompletionAsync(correctionInstruction, reactContext, maxTokens: 1024, temperature: 0.1f, base64Image: base64Image);
|
||||
string normalizedCorrected = NormalizeReactResponse(correctedResponse);
|
||||
if (!string.IsNullOrEmpty(normalizedCorrected) &&
|
||||
TryParseReactEnvelope(normalizedCorrected, out toolCalls, out final, out _, out jsonFragment))
|
||||
{
|
||||
invalidTools = toolCalls
|
||||
.Where(c => !IsToolAvailable(c.Name))
|
||||
.Select(c => c.Name)
|
||||
.Where(n => !string.IsNullOrWhiteSpace(n))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (toolCalls != null && toolCalls.Count > 0)
|
||||
{
|
||||
if (invalidTools.Count > 0)
|
||||
{
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
WulaLog.Debug("[WulaAI] Invalid tools remain after correction; skipping tool execution.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
PhaseExecutionResult stepResult = await ExecuteJsonToolsForStep(jsonFragment);
|
||||
if (!string.IsNullOrEmpty(stepResult.CapturedImage))
|
||||
{
|
||||
|
||||
@@ -450,7 +450,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
|
||||
if (_core == null) return "Thinking...";
|
||||
float elapsedSeconds = Mathf.Max(0f, Time.realtimeSinceStartup - _core.ThinkingStartTime);
|
||||
string elapsedText = elapsedSeconds.ToString("0.0", CultureInfo.InvariantCulture);
|
||||
return $"P.I.A is thinking... ({elapsedText}s Phase {_core.ThinkingPhaseIndex}/3)";
|
||||
return $"P.I.A is thinking... ({elapsedText}s Loop {_core.ThinkingPhaseIndex}/{_core.ThinkingPhaseTotal})";
|
||||
}
|
||||
|
||||
private void DrawThinkingIndicator(Rect rect)
|
||||
|
||||
@@ -635,7 +635,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
|
||||
if (_core == null) return "Thinking...";
|
||||
float elapsedSeconds = Mathf.Max(0f, Time.realtimeSinceStartup - _core.ThinkingStartTime);
|
||||
string elapsedText = elapsedSeconds.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture);
|
||||
return $"P.I.A is thinking... ({elapsedText}s Phase {_core.ThinkingPhaseIndex}/3)";
|
||||
return $"P.I.A is thinking... ({elapsedText}s Loop {_core.ThinkingPhaseIndex}/{_core.ThinkingPhaseTotal})";
|
||||
}
|
||||
|
||||
private void DrawThinkingIndicator(Rect rect)
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace WulaFallenEmpire
|
||||
public static bool _showApiKey = false;
|
||||
public static bool _showVlmApiKey = false;
|
||||
private string _maxContextTokensBuffer;
|
||||
private string _reactMaxStepsBuffer;
|
||||
private string _reactMaxSecondsBuffer;
|
||||
|
||||
public WulaFallenEmpireMod(ModContentPack content) : base(content)
|
||||
{
|
||||
@@ -94,6 +96,16 @@ namespace WulaFallenEmpire
|
||||
listingStandard.GapLine();
|
||||
listingStandard.CheckboxLabeled("Wula_EnableDebugLogs".Translate(), ref settings.enableDebugLogs, "Wula_EnableDebugLogsDesc".Translate());
|
||||
|
||||
listingStandard.GapLine();
|
||||
listingStandard.Label("<color=cyan>ReAct Loop Settings</color>");
|
||||
listingStandard.Label("Max Steps (1-10):");
|
||||
Rect stepsRect = listingStandard.GetRect(Text.LineHeight);
|
||||
Widgets.TextFieldNumeric(stepsRect, ref settings.reactMaxSteps, ref _reactMaxStepsBuffer, 1, 10);
|
||||
|
||||
listingStandard.Label("Max Seconds (2-60):");
|
||||
Rect secondsRect = listingStandard.GetRect(Text.LineHeight);
|
||||
Widgets.TextFieldNumeric(secondsRect, ref settings.reactMaxSeconds, ref _reactMaxSecondsBuffer, 2f, 60f);
|
||||
|
||||
listingStandard.GapLine();
|
||||
listingStandard.CheckboxLabeled("Wula_AISettings_AutoCommentary".Translate(), ref settings.enableAIAutoCommentary, "Wula_AISettings_AutoCommentaryDesc".Translate());
|
||||
if (settings.enableAIAutoCommentary)
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace WulaFallenEmpire
|
||||
public float aiCommentaryChance = 0.7f;
|
||||
public bool commentOnNegativeOnly = false;
|
||||
public string extraPersonalityPrompt = "";
|
||||
public int reactMaxSteps = 4;
|
||||
public float reactMaxSeconds = 60f;
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
@@ -44,6 +46,8 @@ namespace WulaFallenEmpire
|
||||
Scribe_Values.Look(ref aiCommentaryChance, "aiCommentaryChance", 0.7f);
|
||||
Scribe_Values.Look(ref commentOnNegativeOnly, "commentOnNegativeOnly", false);
|
||||
Scribe_Values.Look(ref extraPersonalityPrompt, "extraPersonalityPrompt", "");
|
||||
Scribe_Values.Look(ref reactMaxSteps, "reactMaxSteps", 4);
|
||||
Scribe_Values.Look(ref reactMaxSeconds, "reactMaxSeconds", 12f);
|
||||
|
||||
base.ExposeData();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user