This commit is contained in:
2025-12-31 12:28:02 +08:00
parent 909dea4ffb
commit 2af305de68
3 changed files with 79 additions and 9 deletions

View File

@@ -113,7 +113,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
- 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 { ""tool_calls"": [] }.
7. **NO TAGS**: Do NOT use <think> tags, code fences, or any extra text outside JSON.";
7. **STEP BUDGET (OPTIONAL)**: You MAY include { ""meta"": { ""step_budget"": <positive integer> } } to request more tool steps for this turn.
8. **NO TAGS**: Do NOT use <think> tags, code fences, or any extra text outside JSON.";
public AIIntelligenceCore(World world) : base(world)
{
@@ -923,14 +924,27 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
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)
{
return "# FORMAT FIX (REACT JSON ONLY)\n" +
"Output valid JSON with fields thought/tool_calls.\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" +
"You MAY include an optional meta block: {\"meta\":{\"step_budget\":<positive integer>}}.\n" +
"Do NOT output any text outside JSON.\n" +
"Schema: {\"thought\":\"...\",\"tool_calls\":[{\"type\":\"function\",\"function\":{\"name\":\"tool_name\",\"arguments\":{...}}}]}\n" +
"Schema: {\"thought\":\"...\",\"tool_calls\":[{\"type\":\"function\",\"function\":{\"name\":\"tool_name\",\"arguments\":{...}}}],\"meta\":{\"step_budget\":7}}\n" +
"\nPrevious output:\n" + TrimForPrompt(previousOutput, 600);
}
@@ -964,8 +978,9 @@ 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" +
"Re-emit JSON with only available tools from # TOOLS (AVAILABLE).\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" +
"Schema: {\"thought\":\"...\",\"tool_calls\":[{\"type\":\"function\",\"function\":{\"name\":\"tool_name\",\"arguments\":{...}}}]}";
"Schema: {\"thought\":\"...\",\"tool_calls\":[{\"type\":\"function\",\"function\":{\"name\":\"tool_name\",\"arguments\":{...}}}],\"meta\":{\"step_budget\":7}}";
}
private static bool ShouldRetryTools(string response)
@@ -1832,13 +1847,16 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
int maxSteps = DefaultReactMaxSteps;
float maxSeconds = DefaultReactMaxSeconds;
int stepBudgetMax = int.MaxValue;
if (settings != null)
{
maxSteps = Math.Max(1, Math.Min(10, settings.reactMaxSteps));
maxSeconds = Mathf.Clamp(settings.reactMaxSeconds, 2f, 60f);
stepBudgetMax = Math.Max(1, settings.reactMaxStepsMax);
maxSteps = Math.Max(1, settings.reactMaxSteps);
maxSeconds = Math.Max(2f, settings.reactMaxSeconds);
}
_thinkingPhaseTotal = maxSteps;
string toolPhaseReplyCandidate = null;
bool budgetApplied = false;
for (int step = 1; step <= maxSteps; step++)
{
if (Time.realtimeSinceStartup - startTime > maxSeconds)
@@ -1862,6 +1880,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
}
string normalizedResponse = NormalizeReactResponse(response);
string parsedSource = normalizedResponse;
if (!JsonToolCallParser.TryParseToolCallsFromText(normalizedResponse, out var toolCalls, out string jsonFragment))
{
if (LooksLikeNaturalReply(normalizedResponse))
@@ -1890,6 +1909,27 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
}
break;
}
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;
}
}
}
var invalidTools = toolCalls
@@ -1907,9 +1947,30 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
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) &&
if (!string.IsNullOrEmpty(normalizedCorrected) &&
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;
invalidTools = toolCalls
.Where(c => !IsToolAvailable(c.Name))
.Select(c => c.Name)
@@ -2046,7 +2107,9 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
private async Task<PhaseExecutionResult> ExecuteJsonToolsForStep(string json)
{
string guidance = "ToolRunner Guidance: Continue with JSON only using {\"thought\":\"...\",\"tool_calls\":[...]}. " +
"If no more tools are needed, output exactly: {\"tool_calls\": []}. Do NOT output any text outside JSON.";
"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.";
if (!JsonToolCallParser.TryParseToolCallsFromText(json ?? "", out var toolCalls, out string jsonFragment))
{

View File

@@ -15,6 +15,7 @@ namespace WulaFallenEmpire
public static bool _showVlmApiKey = false;
private string _maxContextTokensBuffer;
private string _reactMaxStepsBuffer;
private string _reactMaxStepsMaxBuffer;
private string _reactMaxSecondsBuffer;
public WulaFallenEmpireMod(ModContentPack content) : base(content)
@@ -98,9 +99,13 @@ namespace WulaFallenEmpire
listingStandard.GapLine();
listingStandard.Label("<color=cyan>ReAct Loop Settings</color>");
listingStandard.Label("Max Steps (1-10):");
listingStandard.Label("Default Steps:");
Rect stepsRect = listingStandard.GetRect(Text.LineHeight);
Widgets.TextFieldNumeric(stepsRect, ref settings.reactMaxSteps, ref _reactMaxStepsBuffer, 1, 20);
Widgets.TextFieldNumeric(stepsRect, ref settings.reactMaxSteps, ref _reactMaxStepsBuffer, 1, int.MaxValue);
listingStandard.Label("Max Steps Limit (step_budget upper bound):");
Rect stepsMaxRect = listingStandard.GetRect(Text.LineHeight);
Widgets.TextFieldNumeric(stepsMaxRect, ref settings.reactMaxStepsMax, ref _reactMaxStepsMaxBuffer, 1, int.MaxValue);
listingStandard.Label("Max Seconds (2-60):");
Rect secondsRect = listingStandard.GetRect(Text.LineHeight);

View File

@@ -24,6 +24,7 @@ namespace WulaFallenEmpire
public bool commentOnNegativeOnly = false;
public string extraPersonalityPrompt = "";
public int reactMaxSteps = 4;
public int reactMaxStepsMax = 7;
public float reactMaxSeconds = 60f;
public bool showReactTraceInUI = false;
@@ -48,6 +49,7 @@ namespace WulaFallenEmpire
Scribe_Values.Look(ref commentOnNegativeOnly, "commentOnNegativeOnly", false);
Scribe_Values.Look(ref extraPersonalityPrompt, "extraPersonalityPrompt", "");
Scribe_Values.Look(ref reactMaxSteps, "reactMaxSteps", 4);
Scribe_Values.Look(ref reactMaxStepsMax, "reactMaxStepsMax", 7);
Scribe_Values.Look(ref reactMaxSeconds, "reactMaxSeconds", 60f);
Scribe_Values.Look(ref showReactTraceInUI, "showReactTraceInUI", false);