This commit is contained in:
2025-12-31 17:25:24 +08:00
parent 6f8f205ae9
commit 9bc9930b7b
6 changed files with 99 additions and 110 deletions

View File

@@ -57,7 +57,7 @@ namespace WulaFallenEmpire.EventSystem.AI
private const int CharsPerToken = 4; private const int CharsPerToken = 4;
private const int DefaultReactMaxSteps = 4; private const int DefaultReactMaxSteps = 4;
private const int ReactMaxToolsPerStep = 8; private const int ReactMaxToolsPerStep = 8;
private const float DefaultReactMaxSeconds = 12f; private const float DefaultReactMaxSeconds = 30f;
private int _thinkingPhaseTotal = DefaultReactMaxSteps; private int _thinkingPhaseTotal = DefaultReactMaxSteps;
private static readonly Regex ExpressionTagRegex = new Regex(@"\[EXPR\s*:\s*([1-6])\s*\]", RegexOptions.IgnoreCase); private static readonly Regex ExpressionTagRegex = new Regex(@"\[EXPR\s*:\s*([1-6])\s*\]", RegexOptions.IgnoreCase);
@@ -111,7 +111,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
- Do NOT split multi-item requests across turns. - Do NOT split multi-item requests across turns.
5. **TOOLS**: You MAY call any tools listed in ""# TOOLS (AVAILABLE)"". 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 { ""tool_calls"": [] }. 6. **ANTI-HALLUCINATION**: Never invent tools, parameters, defNames, coordinates, or tool results. If a tool is needed but not available, output { ""tool_calls"": [] }.
7. **STEP BUDGET (OPTIONAL)**: You MAY include { ""meta"": { ""step_budget"": <positive integer> } } to request more tool steps for this turn. 7. **WORKFLOW PREFERENCE**: Prefer the flow Query tools → Action tools → Reply. If action results reveal missing info, you MAY return to Query and then Action again.
8. **NO TAGS**: Do NOT use <think> tags, code fences, or any extra text outside JSON."; 8. **NO TAGS**: Do NOT use <think> tags, code fences, or any extra text outside JSON.";
public AIIntelligenceCore(World world) : base(world) public AIIntelligenceCore(World world) : base(world)
@@ -454,7 +454,14 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
private void SetThinkingPhase(int phaseIndex, bool isRetry) private void SetThinkingPhase(int phaseIndex, bool isRetry)
{ {
_thinkingPhaseIndex = Math.Max(1, Math.Min(_thinkingPhaseTotal, phaseIndex)); if (_thinkingPhaseTotal <= 0 || _thinkingPhaseTotal == int.MaxValue)
{
_thinkingPhaseIndex = Math.Max(1, phaseIndex);
}
else
{
_thinkingPhaseIndex = Math.Max(1, Math.Min(_thinkingPhaseTotal, phaseIndex));
}
_thinkingPhaseRetry = isRetry; _thinkingPhaseRetry = isRetry;
_thinkingStartTime = Time.realtimeSinceStartup; _thinkingStartTime = Time.realtimeSinceStartup;
} }
@@ -664,7 +671,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
$"Final replies are generated later and MUST use: {language}."; $"Final replies are generated later and MUST use: {language}.";
} }
private string GetNativeSystemInstruction() private string GetNativeSystemInstruction(RequestPhase phase)
{ {
string persona = GetActivePersona(); string persona = GetActivePersona();
string personaBlock = persona; string personaBlock = persona;
@@ -684,6 +691,21 @@ 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: Tool workflow is fixed: Phase 1 = Query Tools, Phase 2 = Action Tools, Phase 3 = Reply.");
switch (phase)
{
case RequestPhase.QueryTools:
sb.AppendLine("CURRENT PHASE: Query Tools. Use ONLY query tools (get_*/search_*/analyze_*/recall_memories).");
sb.AppendLine("Do NOT reply in natural language. If no query tools are needed, return no tool calls and leave content empty.");
break;
case RequestPhase.ActionTools:
sb.AppendLine("CURRENT PHASE: Action Tools. Use ONLY action tools (spawn_resources, send_reinforcement, call_bombardment, modify_goodwill, call_prefab_airdrop, set_overwatch_mode, remember_fact).");
sb.AppendLine("Do NOT reply in natural language. If no actions are needed, return no tool calls and leave content empty.");
break;
default:
sb.AppendLine("CURRENT PHASE: Reply. Do NOT call any tools. Reply in natural language only.");
break;
}
sb.AppendLine("IMPORTANT: Long-term memory is not preloaded. Use recall_memories to fetch memories when needed."); 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.");
@@ -822,10 +844,15 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
return sb.ToString().TrimEnd(); return sb.ToString().TrimEnd();
} }
private List<Dictionary<string, object>> BuildNativeToolDefinitions() private List<Dictionary<string, object>> BuildNativeToolDefinitions(RequestPhase phase)
{ {
var available = _tools var available = _tools
.Where(t => t != null) .Where(t => t != null)
.Where(t => phase == RequestPhase.QueryTools
? IsQueryToolName(t.Name)
: phase == RequestPhase.ActionTools
? IsActionToolName(t.Name)
: false)
.OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase) .OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase)
.ToList(); .ToList();
@@ -943,27 +970,14 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
return false; return false;
} }
private static int? ExtractStepBudget(string json)
{
if (string.IsNullOrWhiteSpace(json)) return null;
var match = Regex.Match(json, "\"step_budget\"\\s*:\\s*\"?(\\d+)\"?", RegexOptions.IgnoreCase);
if (!match.Success) return null;
if (int.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int value))
{
return value;
}
return null;
}
private static string BuildReactFormatFixInstruction(string previousOutput) private static string BuildReactFormatFixInstruction(string previousOutput)
{ {
return "# FORMAT FIX (REACT JSON ONLY)\n" + return "# FORMAT FIX (REACT JSON ONLY)\n" +
"Output valid JSON with fields thought/tool_calls.\n" + "Output valid JSON with fields thought/tool_calls.\n" +
"If tools are needed, tool_calls must be non-empty.\n" + "If tools are needed, tool_calls must be non-empty.\n" +
"If no tools are needed, output exactly: {\"tool_calls\": []} (you may include thought).\n" + "If no tools are needed, output exactly: {\"tool_calls\": []} (you may include thought).\n" +
"You MAY include an optional meta block: {\"meta\":{\"step_budget\":<positive integer>}}.\n" +
"Do NOT output any text outside JSON.\n" + "Do NOT output any text outside JSON.\n" +
"Schema: {\"thought\":\"...\",\"tool_calls\":[{\"type\":\"function\",\"function\":{\"name\":\"tool_name\",\"arguments\":{...}}}],\"meta\":{\"step_budget\":7}}\n" + "Schema: {\"thought\":\"...\",\"tool_calls\":[{\"type\":\"function\",\"function\":{\"name\":\"tool_name\",\"arguments\":{...}}}]}\n" +
"\nPrevious output:\n" + TrimForPrompt(previousOutput, 600); "\nPrevious output:\n" + TrimForPrompt(previousOutput, 600);
} }
@@ -1007,9 +1021,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
"You used tool names that are NOT available: " + invalidList + "\n" + "You used tool names that are NOT available: " + invalidList + "\n" +
"Re-emit JSON with only available tools from # TOOLS (AVAILABLE).\n" + "Re-emit JSON with only available tools from # TOOLS (AVAILABLE).\n" +
"If no tools are needed, output exactly: {\"tool_calls\": []}.\n" + "If no tools are needed, output exactly: {\"tool_calls\": []}.\n" +
"You MAY include an optional meta block: {\"meta\":{\"step_budget\":<positive integer>}}.\n" +
"Do NOT output any text outside JSON.\n" + "Do NOT output any text outside JSON.\n" +
"Schema: {\"thought\":\"...\",\"tool_calls\":[{\"type\":\"function\",\"function\":{\"name\":\"tool_name\",\"arguments\":{...}}}],\"meta\":{\"step_budget\":7}}"; "Schema: {\"thought\":\"...\",\"tool_calls\":[{\"type\":\"function\",\"function\":{\"name\":\"tool_name\",\"arguments\":{...}}}]}";
} }
private static bool ShouldRetryTools(string response) private static bool ShouldRetryTools(string response)
@@ -1924,28 +1937,18 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
var client = new SimpleAIClient(apiKey, baseUrl, model, settings.useGeminiProtocol); var client = new SimpleAIClient(apiKey, baseUrl, model, settings.useGeminiProtocol);
_currentClient = client; _currentClient = client;
if (!settings.useGeminiProtocol) // ReAct Tool Loop: Start with null image. The model must request it using analyze_screen or capture_screen if needed.
{
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; string base64Image = null;
float startTime = Time.realtimeSinceStartup; float startTime = Time.realtimeSinceStartup;
int maxSteps = DefaultReactMaxSteps; int maxSteps = int.MaxValue;
float maxSeconds = DefaultReactMaxSeconds; float maxSeconds = DefaultReactMaxSeconds;
int stepBudgetMax = int.MaxValue;
if (settings != null) if (settings != null)
{ {
stepBudgetMax = Math.Max(1, settings.reactMaxStepsMax); maxSeconds = Math.Max(2f, settings.reactMaxSeconds <= 0f ? DefaultReactMaxSeconds : settings.reactMaxSeconds);
maxSteps = Math.Max(1, settings.reactMaxSteps);
maxSeconds = Math.Max(2f, settings.reactMaxSeconds);
} }
_thinkingPhaseTotal = maxSteps; _thinkingPhaseTotal = 0;
string toolPhaseReplyCandidate = null; string toolPhaseReplyCandidate = null;
bool budgetApplied = false;
for (int step = 1; step <= maxSteps; step++) for (int step = 1; step <= maxSteps; step++)
{ {
if (Time.realtimeSinceStartup - startTime > maxSeconds) if (Time.realtimeSinceStartup - startTime > maxSeconds)
@@ -2003,26 +2006,6 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
parsedSource = normalizedFixed; parsedSource = normalizedFixed;
} }
if (!budgetApplied && !string.IsNullOrWhiteSpace(jsonFragment))
{
string budgetSource = jsonFragment;
int? requested = ExtractStepBudget(budgetSource);
if (!requested.HasValue)
{
requested = ExtractStepBudget(parsedSource);
}
if (requested.HasValue)
{
budgetApplied = true;
int clamped = Math.Max(1, Math.Min(stepBudgetMax, requested.Value));
if (clamped > maxSteps)
{
maxSteps = clamped;
_thinkingPhaseTotal = maxSteps;
}
}
}
if (!string.IsNullOrWhiteSpace(parsedSource)) if (!string.IsNullOrWhiteSpace(parsedSource))
{ {
string traceText = parsedSource; string traceText = parsedSource;
@@ -2059,25 +2042,6 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
if (!string.IsNullOrEmpty(normalizedCorrected) && if (!string.IsNullOrEmpty(normalizedCorrected) &&
JsonToolCallParser.TryParseToolCallsFromText(normalizedCorrected, out toolCalls, out jsonFragment)) JsonToolCallParser.TryParseToolCallsFromText(normalizedCorrected, out toolCalls, out jsonFragment))
{ {
if (!budgetApplied && !string.IsNullOrWhiteSpace(jsonFragment))
{
string budgetSource = jsonFragment;
int? requested = ExtractStepBudget(budgetSource);
if (!requested.HasValue)
{
requested = ExtractStepBudget(normalizedCorrected);
}
if (requested.HasValue)
{
budgetApplied = true;
int clamped = Math.Max(1, Math.Min(stepBudgetMax, requested.Value));
if (clamped > maxSteps)
{
maxSteps = clamped;
_thinkingPhaseTotal = maxSteps;
}
}
}
parsedSource = normalizedCorrected; parsedSource = normalizedCorrected;
invalidTools = toolCalls invalidTools = toolCalls
@@ -2216,9 +2180,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
private async Task RunNativeToolLoopAsync(SimpleAIClient client, WulaFallenEmpireSettings settings) private async Task RunNativeToolLoopAsync(SimpleAIClient client, WulaFallenEmpireSettings settings)
{ {
string systemInstruction = GetNativeSystemInstruction();
var messages = BuildNativeHistory(); var messages = BuildNativeHistory();
var tools = BuildNativeToolDefinitions(); RequestPhase phase = RequestPhase.QueryTools;
string finalReply = null; string finalReply = null;
var successfulQueryTools = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var successfulQueryTools = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
@@ -2226,17 +2189,15 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
var failedActionTools = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var failedActionTools = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
float startTime = Time.realtimeSinceStartup; float startTime = Time.realtimeSinceStartup;
int maxSteps = Math.Max(1, settings.reactMaxSteps); int maxSteps = int.MaxValue;
float maxSeconds = Math.Max(2f, settings.reactMaxSeconds); float maxSeconds = Math.Max(2f, settings.reactMaxSeconds <= 0f ? DefaultReactMaxSeconds : settings.reactMaxSeconds);
_thinkingPhaseTotal = maxSteps; _thinkingPhaseTotal = 3;
int strictRetryCount = 0; int strictRetryCount = 0;
int phaseRetryCount = 0;
const int MaxStrictRetries = 2; const int MaxStrictRetries = 2;
const string StrictRetryGuidance = const int MaxPhaseRetries = 2;
"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 && phase != RequestPhase.Reply; step++)
{ {
if (Time.realtimeSinceStartup - startTime > maxSeconds) if (Time.realtimeSinceStartup - startTime > maxSeconds)
{ {
@@ -2247,8 +2208,10 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
break; break;
} }
SetThinkingPhase(step, false); SetThinkingPhase(phase == RequestPhase.QueryTools ? 1 : 2, false);
string systemInstruction = GetNativeSystemInstruction(phase);
var tools = BuildNativeToolDefinitions(phase);
ChatCompletionResult result = await client.GetChatCompletionWithToolsAsync( ChatCompletionResult result = await client.GetChatCompletionWithToolsAsync(
systemInstruction, systemInstruction,
messages, messages,
@@ -2264,15 +2227,26 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
if (result.ToolCalls == null || result.ToolCalls.Count == 0) if (result.ToolCalls == null || result.ToolCalls.Count == 0)
{ {
if (strictRetryCount < MaxStrictRetries) if (!string.IsNullOrWhiteSpace(result.Content) && strictRetryCount < MaxStrictRetries)
{ {
strictRetryCount++; strictRetryCount++;
messages.Add(ChatMessage.User(StrictRetryGuidance)); string strictRetryGuidance = phase == RequestPhase.QueryTools
? "ToolRunner Error: This is Query phase. You MUST call query tools using tool_calls. Do NOT output XML or natural language. If no query tools are needed, return no tool calls and leave content empty."
: "ToolRunner Error: This is Action phase. You MUST call action tools using tool_calls. Do NOT output XML or natural language. If no actions are needed, return no tool calls and leave content empty.";
messages.Add(ChatMessage.User(strictRetryGuidance));
if (Prefs.DevMode) if (Prefs.DevMode)
{ {
WulaLog.Debug($"[WulaAI] Native tool loop retry: missing tool_calls (attempt {strictRetryCount}/{MaxStrictRetries})."); WulaLog.Debug($"[WulaAI] Native tool loop retry: missing tool_calls (attempt {strictRetryCount}/{MaxStrictRetries}).");
} }
step--; continue;
}
strictRetryCount = 0;
phaseRetryCount = 0;
if (phase == RequestPhase.QueryTools)
{
phase = RequestPhase.ActionTools;
continue; continue;
} }
@@ -2280,9 +2254,35 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
} }
strictRetryCount = 0; strictRetryCount = 0;
phaseRetryCount = 0;
if (result.ToolCalls != null && result.ToolCalls.Count > 0) if (result.ToolCalls != null && result.ToolCalls.Count > 0)
{ {
var invalidTools = result.ToolCalls
.Where(c => c != null && !string.IsNullOrWhiteSpace(c.Name))
.Select(c => c.Name)
.Where(n => phase == RequestPhase.QueryTools ? !IsQueryToolName(n) : !IsActionToolName(n))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (invalidTools.Count > 0)
{
if (phaseRetryCount < MaxPhaseRetries)
{
phaseRetryCount++;
string invalidList = string.Join(", ", invalidTools);
string guidance = phase == RequestPhase.QueryTools
? $"ToolRunner Error: Query phase only allows query tools. Invalid: {invalidList}. Re-issue tool_calls with query tools only."
: $"ToolRunner Error: Action phase only allows action tools. Invalid: {invalidList}. Re-issue tool_calls with action tools only.";
messages.Add(ChatMessage.User(guidance));
if (Prefs.DevMode)
{
WulaLog.Debug($"[WulaAI] Native tool loop retry: invalid tools ({invalidList}).");
}
continue;
}
break;
}
int maxTools = ReactMaxToolsPerStep; int maxTools = ReactMaxToolsPerStep;
var callsToExecute = result.ToolCalls.Count > maxTools var callsToExecute = result.ToolCalls.Count > maxTools
? result.ToolCalls.Take(maxTools).ToList() ? result.ToolCalls.Take(maxTools).ToList()
@@ -2476,7 +2476,6 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
{ {
string guidance = "ToolRunner Guidance: Continue with JSON only using {\"thought\":\"...\",\"tool_calls\":[...]}. " + string guidance = "ToolRunner Guidance: Continue with JSON only using {\"thought\":\"...\",\"tool_calls\":[...]}. " +
"If no more tools are needed, output exactly: {\"tool_calls\": []}. " + "If no more tools are needed, output exactly: {\"tool_calls\": []}. " +
"You MAY include {\"meta\":{\"step_budget\":<positive integer>}} to request more steps. " +
"Do NOT output any text outside JSON."; "Do NOT output any text outside JSON.";
if (!JsonToolCallParser.TryParseToolCallsFromText(json ?? "", out var toolCalls, out string jsonFragment)) if (!JsonToolCallParser.TryParseToolCallsFromText(json ?? "", out var toolCalls, out string jsonFragment))

View File

@@ -805,7 +805,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
? Mathf.Max(0f, Time.realtimeSinceStartup - (_core?.ThinkingStartTime ?? 0f)) ? Mathf.Max(0f, Time.realtimeSinceStartup - (_core?.ThinkingStartTime ?? 0f))
: _core?.LastThinkingDuration ?? 0f; : _core?.LastThinkingDuration ?? 0f;
string elapsedText = elapsed > 0f ? elapsed.ToString("0.0", CultureInfo.InvariantCulture) : "0.0"; string elapsedText = elapsed > 0f ? elapsed.ToString("0.0", CultureInfo.InvariantCulture) : "0.0";
return $"{state} (用时 {elapsedText}s · Loop {_core?.ThinkingPhaseIndex ?? 0}/{_core?.ThinkingPhaseTotal ?? 0})"; return $"{state} (用时 {elapsedText}s · Loop {_core?.ThinkingPhaseIndex ?? 0})";
} }
private void DrawReactTracePanel(Rect rect, CachedMessage traceEntry) private void DrawReactTracePanel(Rect rect, CachedMessage traceEntry)
@@ -852,7 +852,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (_core == null) return "Thinking..."; if (_core == null) return "Thinking...";
float elapsedSeconds = Mathf.Max(0f, Time.realtimeSinceStartup - _core.ThinkingStartTime); float elapsedSeconds = Mathf.Max(0f, Time.realtimeSinceStartup - _core.ThinkingStartTime);
string elapsedText = elapsedSeconds.ToString("0.0", CultureInfo.InvariantCulture); string elapsedText = elapsedSeconds.ToString("0.0", CultureInfo.InvariantCulture);
return $"P.I.A is thinking... ({elapsedText}s Loop {_core.ThinkingPhaseIndex}/{_core.ThinkingPhaseTotal})"; return $"P.I.A is thinking... ({elapsedText}s Loop {_core.ThinkingPhaseIndex})";
} }
private void DrawThinkingIndicator(Rect rect) private void DrawThinkingIndicator(Rect rect)

View File

@@ -849,8 +849,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
: _core?.LastThinkingDuration ?? 0f; : _core?.LastThinkingDuration ?? 0f;
string elapsedText = elapsed > 0f ? elapsed.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture) : "0.0"; string elapsedText = elapsed > 0f ? elapsed.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture) : "0.0";
int phaseIndex = _core?.ThinkingPhaseIndex ?? 0; int phaseIndex = _core?.ThinkingPhaseIndex ?? 0;
int phaseTotal = _core?.ThinkingPhaseTotal ?? 0; return $"{state} (用时 {elapsedText}s · Loop {phaseIndex})";
return $"{state} (用时 {elapsedText}s · Loop {phaseIndex}/{phaseTotal})";
} }
private void DrawReactTracePanel(Rect rect, CachedMessage traceEntry) private void DrawReactTracePanel(Rect rect, CachedMessage traceEntry)
@@ -1037,7 +1036,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (_core == null) return "Thinking..."; if (_core == null) return "Thinking...";
float elapsedSeconds = Mathf.Max(0f, Time.realtimeSinceStartup - _core.ThinkingStartTime); float elapsedSeconds = Mathf.Max(0f, Time.realtimeSinceStartup - _core.ThinkingStartTime);
string elapsedText = elapsedSeconds.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture); string elapsedText = elapsedSeconds.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture);
return $"P.I.A is thinking... ({elapsedText}s Loop {_core.ThinkingPhaseIndex}/{_core.ThinkingPhaseTotal})"; return $"P.I.A is thinking... ({elapsedText}s Loop {_core.ThinkingPhaseIndex})";
} }
private void DrawThinkingIndicator(Rect rect) private void DrawThinkingIndicator(Rect rect)

View File

@@ -14,8 +14,6 @@ namespace WulaFallenEmpire
public static bool _showApiKey = false; public static bool _showApiKey = false;
public static bool _showVlmApiKey = false; public static bool _showVlmApiKey = false;
private string _maxContextTokensBuffer; private string _maxContextTokensBuffer;
private string _reactMaxStepsBuffer;
private string _reactMaxStepsMaxBuffer;
private string _reactMaxSecondsBuffer; private string _reactMaxSecondsBuffer;
public WulaFallenEmpireMod(ModContentPack content) : base(content) public WulaFallenEmpireMod(ModContentPack content) : base(content)
@@ -99,14 +97,7 @@ namespace WulaFallenEmpire
listingStandard.GapLine(); listingStandard.GapLine();
listingStandard.Label("<color=cyan>ReAct Loop Settings</color>"); listingStandard.Label("<color=cyan>ReAct Loop Settings</color>");
listingStandard.Label("Default Steps (min 1):"); listingStandard.Label("Steps: Unlimited (step limit removed).");
Rect stepsRect = listingStandard.GetRect(Text.LineHeight);
Widgets.TextFieldNumeric(stepsRect, ref settings.reactMaxSteps, ref _reactMaxStepsBuffer, 1, int.MaxValue);
listingStandard.Label("Max Steps Limit (step_budget upper bound, min 1):");
Rect stepsMaxRect = listingStandard.GetRect(Text.LineHeight);
Widgets.TextFieldNumeric(stepsMaxRect, ref settings.reactMaxStepsMax, ref _reactMaxStepsMaxBuffer, 1, int.MaxValue);
listingStandard.Label("Max Seconds (min 2):"); listingStandard.Label("Max Seconds (min 2):");
Rect secondsRect = listingStandard.GetRect(Text.LineHeight); Rect secondsRect = listingStandard.GetRect(Text.LineHeight);
Widgets.TextFieldNumeric(secondsRect, ref settings.reactMaxSeconds, ref _reactMaxSecondsBuffer, 10f, 600f); Widgets.TextFieldNumeric(secondsRect, ref settings.reactMaxSeconds, ref _reactMaxSecondsBuffer, 10f, 600f);

View File

@@ -23,9 +23,9 @@ namespace WulaFallenEmpire
public float aiCommentaryChance = 0.7f; public float aiCommentaryChance = 0.7f;
public bool commentOnNegativeOnly = false; public bool commentOnNegativeOnly = false;
public string extraPersonalityPrompt = ""; public string extraPersonalityPrompt = "";
public int reactMaxSteps = 4; public int reactMaxSteps = 0; // Deprecated: step limit removed (unlimited).
public int reactMaxStepsMax = 7; public int reactMaxStepsMax = 0; // Deprecated: step limit removed (unlimited).
public float reactMaxSeconds = 60f; public float reactMaxSeconds = 30f;
public bool showReactTraceInUI = false; public bool showReactTraceInUI = false;
public override void ExposeData() public override void ExposeData()
@@ -48,9 +48,9 @@ namespace WulaFallenEmpire
Scribe_Values.Look(ref aiCommentaryChance, "aiCommentaryChance", 0.7f); Scribe_Values.Look(ref aiCommentaryChance, "aiCommentaryChance", 0.7f);
Scribe_Values.Look(ref commentOnNegativeOnly, "commentOnNegativeOnly", false); Scribe_Values.Look(ref commentOnNegativeOnly, "commentOnNegativeOnly", false);
Scribe_Values.Look(ref extraPersonalityPrompt, "extraPersonalityPrompt", ""); Scribe_Values.Look(ref extraPersonalityPrompt, "extraPersonalityPrompt", "");
Scribe_Values.Look(ref reactMaxSteps, "reactMaxSteps", 4); Scribe_Values.Look(ref reactMaxSteps, "reactMaxSteps", 0);
Scribe_Values.Look(ref reactMaxStepsMax, "reactMaxStepsMax", 7); Scribe_Values.Look(ref reactMaxStepsMax, "reactMaxStepsMax", 0);
Scribe_Values.Look(ref reactMaxSeconds, "reactMaxSeconds", 60f); Scribe_Values.Look(ref reactMaxSeconds, "reactMaxSeconds", 30f);
Scribe_Values.Look(ref showReactTraceInUI, "showReactTraceInUI", false); Scribe_Values.Look(ref showReactTraceInUI, "showReactTraceInUI", false);
base.ExposeData(); base.ExposeData();