zc
This commit is contained in:
Binary file not shown.
@@ -69,6 +69,7 @@ namespace WulaFallenEmpire.EventSystem.AI
|
|||||||
public bool AnyToolSuccess;
|
public bool AnyToolSuccess;
|
||||||
public bool AnyActionSuccess;
|
public bool AnyActionSuccess;
|
||||||
public bool AnyActionError;
|
public bool AnyActionError;
|
||||||
|
public string CapturedImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string DefaultPersona = @"
|
private const string DefaultPersona = @"
|
||||||
@@ -471,7 +472,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
$"You will produce the natural-language reply later and MUST use: {language}.";
|
$"You will produce the natural-language reply later and MUST use: {language}.";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetToolSystemInstruction(RequestPhase phase)
|
private string GetToolSystemInstruction(RequestPhase phase, bool hasImage)
|
||||||
{
|
{
|
||||||
string phaseInstruction = GetPhaseInstruction(phase).TrimEnd();
|
string phaseInstruction = GetPhaseInstruction(phase).TrimEnd();
|
||||||
string toolsForThisPhase = BuildToolsForPhase(phase);
|
string toolsForThisPhase = BuildToolsForPhase(phase);
|
||||||
@@ -485,7 +486,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
"Query tools exist but are disabled in this phase (not listed here).\n"
|
"Query tools exist but are disabled in this phase (not listed here).\n"
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
|
|
||||||
if (WulaFallenEmpireMod.settings?.enableVlmFeatures == true && WulaFallenEmpireMod.settings?.useNativeMultimodal == true)
|
if (hasImage && WulaFallenEmpireMod.settings?.enableVlmFeatures == true)
|
||||||
{
|
{
|
||||||
phaseInstruction += "\n- NATIVE MULTIMODAL: A current screenshot of the game is attached to this request. You can see the game state directly. Use it to determine coordinates for visual tools or to understand the context.";
|
phaseInstruction += "\n- NATIVE MULTIMODAL: A current screenshot of the game is attached to this request. You can see the game state directly. Use it to determine coordinates for visual tools or to understand the context.";
|
||||||
}
|
}
|
||||||
@@ -833,6 +834,16 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
OnMessageReceived?.Invoke(cleanedResponse);
|
OnMessageReceived?.Invoke(cleanedResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CheckVisualIntent(string message)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(message)) return false;
|
||||||
|
string[] keywords = new string[] {
|
||||||
|
"屏幕", "画面", "截图", "看", "找", "显示", // CN
|
||||||
|
"screen", "screenshot", "image", "view", "look", "see", "find", "visual", "scan" // EN
|
||||||
|
};
|
||||||
|
return keywords.Any(k => message.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0);
|
||||||
|
}
|
||||||
private async Task RunPhasedRequestAsync()
|
private async Task RunPhasedRequestAsync()
|
||||||
{
|
{
|
||||||
if (_isThinking) return;
|
if (_isThinking) return;
|
||||||
@@ -858,18 +869,13 @@ 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;
|
||||||
|
|
||||||
// 只有当启用了 VLM 特性,且开启了原生多模态模式时,才截图并在请求中包含图片
|
// Model-Driven Vision: Start with null image. The model must ask for it using <analyze_screen/> or <capture_screen/> if needed.
|
||||||
string base64Image = null;
|
string base64Image = null;
|
||||||
if (settings.enableVlmFeatures && settings.useNativeMultimodal)
|
|
||||||
{
|
// If VLM is enabled, we allow the tool use.
|
||||||
base64Image = ScreenCaptureUtility.CaptureScreenAsBase64();
|
if (settings.enableVlmFeatures && settings.showThinkingProcess)
|
||||||
if (settings.showThinkingProcess)
|
|
||||||
{
|
|
||||||
AddAssistantMessage("<i>[P.I.A] 正在扫描当前战区情况...</i>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (settings.showThinkingProcess)
|
|
||||||
{
|
{
|
||||||
|
// Optional: We can still say "Analyzing data link..."
|
||||||
AddAssistantMessage("<i>[P.I.A] 正在分析数据链路...</i>");
|
AddAssistantMessage("<i>[P.I.A] 正在分析数据链路...</i>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,8 +885,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
WulaLog.Debug($"[WulaAI] ===== Turn 1/3 ({queryPhase}) =====");
|
WulaLog.Debug($"[WulaAI] ===== Turn 1/3 ({queryPhase}) =====");
|
||||||
}
|
}
|
||||||
|
|
||||||
string queryInstruction = GetToolSystemInstruction(queryPhase);
|
string queryInstruction = GetToolSystemInstruction(queryPhase, !string.IsNullOrEmpty(base64Image));
|
||||||
string queryResponse = await client.GetChatCompletionAsync(queryInstruction, BuildToolContext(queryPhase), maxTokens: 128, temperature: 0.1f, base64Image: base64Image);
|
string queryResponse = await client.GetChatCompletionAsync(queryInstruction, BuildToolContext(queryPhase), maxTokens: 2048, temperature: 0.1f, base64Image: base64Image);
|
||||||
if (string.IsNullOrEmpty(queryResponse))
|
if (string.IsNullOrEmpty(queryResponse))
|
||||||
{
|
{
|
||||||
AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate());
|
AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate());
|
||||||
@@ -897,6 +903,16 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
}
|
}
|
||||||
|
|
||||||
PhaseExecutionResult queryResult = await ExecuteXmlToolsForPhase(queryResponse, queryPhase);
|
PhaseExecutionResult queryResult = await ExecuteXmlToolsForPhase(queryResponse, queryPhase);
|
||||||
|
|
||||||
|
// DATA FLOW: If Query Phase captured an image, propagate it to subsequent phases.
|
||||||
|
if (!string.IsNullOrEmpty(queryResult.CapturedImage))
|
||||||
|
{
|
||||||
|
base64Image = queryResult.CapturedImage;
|
||||||
|
if (settings.showThinkingProcess)
|
||||||
|
{
|
||||||
|
AddAssistantMessage("<i>[P.I.A] 视觉传感器已激活,图像已捕获...</i>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!queryResult.AnyToolSuccess && !_queryRetryUsed)
|
if (!queryResult.AnyToolSuccess && !_queryRetryUsed)
|
||||||
{
|
{
|
||||||
@@ -912,7 +928,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
"Output the XML tag only and NOTHING else.\n" +
|
"Output the XML tag only and NOTHING else.\n" +
|
||||||
"\nLast user request:\n" + lastUserMessage;
|
"\nLast user request:\n" + lastUserMessage;
|
||||||
|
|
||||||
string retryDecision = await client.GetChatCompletionAsync(retryInstruction, new List<(string role, string message)>(), maxTokens: 16, temperature: 0.1f);
|
string retryDecision = await client.GetChatCompletionAsync(retryInstruction, new List<(string role, string message)>(), maxTokens: 256, temperature: 0.1f);
|
||||||
if (!string.IsNullOrEmpty(retryDecision) && ShouldRetryTools(retryDecision))
|
if (!string.IsNullOrEmpty(retryDecision) && ShouldRetryTools(retryDecision))
|
||||||
{
|
{
|
||||||
if (Prefs.DevMode)
|
if (Prefs.DevMode)
|
||||||
@@ -921,9 +937,9 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetThinkingPhase(1, true);
|
SetThinkingPhase(1, true);
|
||||||
string retryQueryInstruction = GetToolSystemInstruction(queryPhase) +
|
string retryQueryInstruction = GetToolSystemInstruction(queryPhase, !string.IsNullOrEmpty(base64Image)) +
|
||||||
"\n\n# RETRY\nYou chose to retry. Output XML tool calls only (or <no_action/>).";
|
"\n\n# RETRY\nYou chose to retry. Output XML tool calls only (or <no_action/>).";
|
||||||
string retryQueryResponse = await client.GetChatCompletionAsync(retryQueryInstruction, BuildToolContext(queryPhase), maxTokens: 128, temperature: 0.1f, base64Image: base64Image);
|
string retryQueryResponse = await client.GetChatCompletionAsync(retryQueryInstruction, BuildToolContext(queryPhase), maxTokens: 2048, temperature: 0.1f, base64Image: base64Image);
|
||||||
if (string.IsNullOrEmpty(retryQueryResponse))
|
if (string.IsNullOrEmpty(retryQueryResponse))
|
||||||
{
|
{
|
||||||
AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate());
|
AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate());
|
||||||
@@ -955,9 +971,10 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetThinkingPhase(2, false);
|
SetThinkingPhase(2, false);
|
||||||
string actionInstruction = GetToolSystemInstruction(actionPhase);
|
string actionInstruction = GetToolSystemInstruction(actionPhase, !string.IsNullOrEmpty(base64Image));
|
||||||
var actionContext = BuildToolContext(actionPhase, includeUser: true);
|
var actionContext = BuildToolContext(actionPhase, includeUser: true);
|
||||||
string actionResponse = await client.GetChatCompletionAsync(actionInstruction, actionContext, maxTokens: 128, temperature: 0.1f);
|
// Important: Pass base64Image to Action Phase as well if available, so visual_click works.
|
||||||
|
string actionResponse = await client.GetChatCompletionAsync(actionInstruction, actionContext, maxTokens: 2048, temperature: 0.1f, base64Image: base64Image);
|
||||||
if (string.IsNullOrEmpty(actionResponse))
|
if (string.IsNullOrEmpty(actionResponse))
|
||||||
{
|
{
|
||||||
AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate());
|
AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate());
|
||||||
@@ -991,7 +1008,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
"- <visual_hotkey><key>String (e.g. 'enter', 'esc', 'space')</key></visual_hotkey>\n" +
|
"- <visual_hotkey><key>String (e.g. 'enter', 'esc', 'space')</key></visual_hotkey>\n" +
|
||||||
"- <visual_wait><seconds>Float</seconds></visual_wait>\n" +
|
"- <visual_wait><seconds>Float</seconds></visual_wait>\n" +
|
||||||
"\nPrevious output:\n" + TrimForPrompt(actionResponse, 600);
|
"\nPrevious output:\n" + TrimForPrompt(actionResponse, 600);
|
||||||
string fixedResponse = await client.GetChatCompletionAsync(fixInstruction, actionContext, maxTokens: 128, temperature: 0.1f);
|
string fixedResponse = await client.GetChatCompletionAsync(fixInstruction, actionContext, maxTokens: 2048, temperature: 0.1f);
|
||||||
bool fixedHasXml = !string.IsNullOrEmpty(fixedResponse) && IsXmlToolCall(fixedResponse);
|
bool fixedHasXml = !string.IsNullOrEmpty(fixedResponse) && IsXmlToolCall(fixedResponse);
|
||||||
bool fixedIsNoActionOnly = fixedHasXml && IsNoActionOnly(fixedResponse);
|
bool fixedIsNoActionOnly = fixedHasXml && IsNoActionOnly(fixedResponse);
|
||||||
bool fixedHasActionTool = fixedHasXml && HasActionToolCall(fixedResponse);
|
bool fixedHasActionTool = fixedHasXml && HasActionToolCall(fixedResponse);
|
||||||
@@ -1023,7 +1040,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
"Output the XML tag only and NOTHING else.\n" +
|
"Output the XML tag only and NOTHING else.\n" +
|
||||||
"\nLast user request:\n" + lastUserMessage;
|
"\nLast user request:\n" + lastUserMessage;
|
||||||
|
|
||||||
string retryDecision = await client.GetChatCompletionAsync(retryInstruction, new List<(string role, string message)>(), maxTokens: 16, temperature: 0.1f);
|
string retryDecision = await client.GetChatCompletionAsync(retryInstruction, new List<(string role, string message)>(), maxTokens: 256, temperature: 0.1f);
|
||||||
if (!string.IsNullOrEmpty(retryDecision) && ShouldRetryTools(retryDecision))
|
if (!string.IsNullOrEmpty(retryDecision) && ShouldRetryTools(retryDecision))
|
||||||
{
|
{
|
||||||
if (Prefs.DevMode)
|
if (Prefs.DevMode)
|
||||||
@@ -1032,10 +1049,10 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetThinkingPhase(2, true);
|
SetThinkingPhase(2, true);
|
||||||
string retryActionInstruction = GetToolSystemInstruction(actionPhase) +
|
string retryActionInstruction = GetToolSystemInstruction(actionPhase, !string.IsNullOrEmpty(base64Image)) +
|
||||||
"\n\n# RETRY\nYou chose to retry. Output XML tool calls only (or <no_action/>).";
|
"\n\n# RETRY\nYou chose to retry. Output XML tool calls only (or <no_action/>).";
|
||||||
var retryActionContext = BuildToolContext(actionPhase, includeUser: true);
|
var retryActionContext = BuildToolContext(actionPhase, includeUser: true);
|
||||||
string retryActionResponse = await client.GetChatCompletionAsync(retryActionInstruction, retryActionContext, maxTokens: 128, temperature: 0.1f);
|
string retryActionResponse = await client.GetChatCompletionAsync(retryActionInstruction, retryActionContext, maxTokens: 2048, temperature: 0.1f, base64Image: base64Image);
|
||||||
if (string.IsNullOrEmpty(retryActionResponse))
|
if (string.IsNullOrEmpty(retryActionResponse))
|
||||||
{
|
{
|
||||||
AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate());
|
AddAssistantMessage("Wula_AI_Error_ConnectionLost".Translate());
|
||||||
@@ -1066,7 +1083,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
"- <visual_hotkey><key>String</key></visual_hotkey>\n" +
|
"- <visual_hotkey><key>String</key></visual_hotkey>\n" +
|
||||||
"- <visual_wait><seconds>Float</seconds></visual_wait>\n" +
|
"- <visual_wait><seconds>Float</seconds></visual_wait>\n" +
|
||||||
"\nPrevious output:\n" + TrimForPrompt(retryActionResponse, 600);
|
"\nPrevious output:\n" + TrimForPrompt(retryActionResponse, 600);
|
||||||
string retryFixedResponse = await client.GetChatCompletionAsync(retryFixInstruction, retryActionContext, maxTokens: 128, temperature: 0.1f);
|
string retryFixedResponse = await client.GetChatCompletionAsync(retryFixInstruction, retryActionContext, maxTokens: 2048, temperature: 0.1f);
|
||||||
bool retryFixedHasXml = !string.IsNullOrEmpty(retryFixedResponse) && IsXmlToolCall(retryFixedResponse);
|
bool retryFixedHasXml = !string.IsNullOrEmpty(retryFixedResponse) && IsXmlToolCall(retryFixedResponse);
|
||||||
bool retryFixedIsNoActionOnly = retryFixedHasXml && IsNoActionOnly(retryFixedResponse);
|
bool retryFixedIsNoActionOnly = retryFixedHasXml && IsNoActionOnly(retryFixedResponse);
|
||||||
bool retryFixedHasActionTool = retryFixedHasXml && HasActionToolCall(retryFixedResponse);
|
bool retryFixedHasActionTool = retryFixedHasXml && HasActionToolCall(retryFixedResponse);
|
||||||
@@ -1211,6 +1228,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
var nonActionToolsInActionPhase = new List<string>();
|
var nonActionToolsInActionPhase = new List<string>();
|
||||||
StringBuilder combinedResults = new StringBuilder();
|
StringBuilder combinedResults = new StringBuilder();
|
||||||
StringBuilder xmlOnlyBuilder = new StringBuilder();
|
StringBuilder xmlOnlyBuilder = new StringBuilder();
|
||||||
|
string capturedImageForPhase = null;
|
||||||
|
|
||||||
bool countActionSuccessOnly = phase == RequestPhase.ActionTools;
|
bool countActionSuccessOnly = phase == RequestPhase.ActionTools;
|
||||||
|
|
||||||
@@ -1231,6 +1249,18 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toolName.Equals("analyze_screen", StringComparison.OrdinalIgnoreCase) || toolName.Equals("capture_screen", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// Intercept Vision Request: Capture screen and return it.
|
||||||
|
// We skip the tool's internal execution to save time/tokens, as the purpose is just to get the image into the context.
|
||||||
|
capturedImageForPhase = ScreenCaptureUtility.CaptureScreenAsBase64();
|
||||||
|
combinedResults.AppendLine($"Tool '{toolName}' Result: Screen captured successfully. Context updated for next phase.");
|
||||||
|
successfulToolCall = true;
|
||||||
|
successfulTools.Add(toolName);
|
||||||
|
executed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (xmlOnlyBuilder.Length > 0) xmlOnlyBuilder.AppendLine().AppendLine();
|
if (xmlOnlyBuilder.Length > 0) xmlOnlyBuilder.AppendLine().AppendLine();
|
||||||
xmlOnlyBuilder.Append(toolCallXml);
|
xmlOnlyBuilder.Append(toolCallXml);
|
||||||
|
|
||||||
@@ -1344,7 +1374,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
{
|
{
|
||||||
AnyToolSuccess = successfulToolCall,
|
AnyToolSuccess = successfulToolCall,
|
||||||
AnyActionSuccess = successfulActions.Count > 0,
|
AnyActionSuccess = successfulActions.Count > 0,
|
||||||
AnyActionError = failedActions.Count > 0
|
AnyActionError = failedActions.Count > 0,
|
||||||
|
CapturedImage = capturedImageForPhase
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,8 +130,9 @@ namespace WulaFallenEmpire.EventSystem.AI.Agent
|
|||||||
string decision;
|
string decision;
|
||||||
string base64Image = null;
|
string base64Image = null;
|
||||||
|
|
||||||
// 如果启用了视觉特性且开启了原生多模态,则在决策前截图
|
// 如果启用了视觉特性,则在决策前截图 (Autonomous Loop 默认认为是开启视觉即全自动,或者我们可以加逻辑判断,但暂时保持 VLM 开启即截图对于 Agent Loop 来说更合理,因为它需要时刻观察)
|
||||||
if (settings.enableVlmFeatures && settings.useNativeMultimodal)
|
// 实际上,Agent Loop 通常需要全视觉,所以我们这里只检查 enableVlmFeatures
|
||||||
|
if (settings.enableVlmFeatures)
|
||||||
{
|
{
|
||||||
base64Image = ScreenCaptureUtility.CaptureScreenAsBase64();
|
base64Image = ScreenCaptureUtility.CaptureScreenAsBase64();
|
||||||
if (settings.showThinkingProcess)
|
if (settings.showThinkingProcess)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using UnityEngine.Networking;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Verse;
|
using Verse;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace WulaFallenEmpire.EventSystem.AI
|
namespace WulaFallenEmpire.EventSystem.AI
|
||||||
{
|
{
|
||||||
@@ -27,12 +28,21 @@ namespace WulaFallenEmpire.EventSystem.AI
|
|||||||
|
|
||||||
public async Task<string> GetChatCompletionAsync(string instruction, List<(string role, string message)> messages, int? maxTokens = null, float? temperature = null, string base64Image = null)
|
public async Task<string> GetChatCompletionAsync(string instruction, List<(string role, string message)> messages, int? maxTokens = null, float? temperature = null, string base64Image = null)
|
||||||
{
|
{
|
||||||
|
// 1. Gemini Mode
|
||||||
if (_useGemini)
|
if (_useGemini)
|
||||||
{
|
{
|
||||||
return await GetGeminiCompletionAsync(instruction, messages, maxTokens, temperature, base64Image);
|
string geminiResponse = await GetGeminiCompletionAsync(instruction, messages, maxTokens, temperature, base64Image);
|
||||||
|
|
||||||
|
// Fallback: If failed and had image, retry without image
|
||||||
|
if (geminiResponse == null && !string.IsNullOrEmpty(base64Image))
|
||||||
|
{
|
||||||
|
WulaLog.Debug("[WulaAI] [WARNING] Visual request failed (likely model incompatible). Retrying text-only...");
|
||||||
|
return await GetGeminiCompletionAsync(instruction, messages, maxTokens, temperature, null);
|
||||||
|
}
|
||||||
|
return geminiResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAI / Compatible Mode
|
// 2. OpenAI / Compatible Mode
|
||||||
if (string.IsNullOrEmpty(_baseUrl))
|
if (string.IsNullOrEmpty(_baseUrl))
|
||||||
{
|
{
|
||||||
WulaLog.Debug("[WulaAI] Base URL is missing.");
|
WulaLog.Debug("[WulaAI] Base URL is missing.");
|
||||||
@@ -83,11 +93,27 @@ namespace WulaFallenEmpire.EventSystem.AI
|
|||||||
}
|
}
|
||||||
jsonBuilder.Append("]}");
|
jsonBuilder.Append("]}");
|
||||||
|
|
||||||
return await SendRequestAsync(endpoint, jsonBuilder.ToString(), _apiKey);
|
string response = await SendRequestAsync(endpoint, jsonBuilder.ToString(), _apiKey);
|
||||||
|
|
||||||
|
// Fallback: If failed and had image, retry without image
|
||||||
|
if (response == null && !string.IsNullOrEmpty(base64Image))
|
||||||
|
{
|
||||||
|
WulaLog.Debug("[WulaAI] [WARNING] Visual request failed (likely model incompatible). Retrying text-only...");
|
||||||
|
return await GetChatCompletionAsync(instruction, messages, maxTokens, temperature, null);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GetGeminiCompletionAsync(string instruction, List<(string role, string message)> messages, int? maxTokens = null, float? temperature = null, string base64Image = null)
|
private async Task<string> GetGeminiCompletionAsync(string instruction, List<(string role, string message)> messages, int? maxTokens = null, float? temperature = null, string base64Image = null)
|
||||||
{
|
{
|
||||||
|
// Ensure messages is not empty to avoid Gemini 400 Error (Invalid Argument)
|
||||||
|
if (messages == null) messages = new List<(string role, string message)>();
|
||||||
|
if (messages.Count == 0)
|
||||||
|
{
|
||||||
|
// Gemini API 'contents' cannot be empty. We add a dummy prompt to trigger the model.
|
||||||
|
messages.Add(("user", "Start."));
|
||||||
|
}
|
||||||
|
|
||||||
// Gemini API URL
|
// Gemini API URL
|
||||||
string baseUrl = _baseUrl;
|
string baseUrl = _baseUrl;
|
||||||
if (string.IsNullOrEmpty(baseUrl) || !baseUrl.Contains("googleapis.com"))
|
if (string.IsNullOrEmpty(baseUrl) || !baseUrl.Contains("googleapis.com"))
|
||||||
@@ -141,7 +167,17 @@ namespace WulaFallenEmpire.EventSystem.AI
|
|||||||
{
|
{
|
||||||
if (Prefs.DevMode)
|
if (Prefs.DevMode)
|
||||||
{
|
{
|
||||||
WulaLog.Debug($"[WulaAI] Sending request to {endpoint}");
|
string logUrl = endpoint;
|
||||||
|
if (logUrl.Contains("key="))
|
||||||
|
{
|
||||||
|
logUrl = Regex.Replace(logUrl, @"key=[^&]*", "key=[REDACTED]");
|
||||||
|
}
|
||||||
|
WulaLog.Debug($"[WulaAI] Sending request to {logUrl}");
|
||||||
|
|
||||||
|
// Log request body (truncated to avoid spamming base64)
|
||||||
|
string logBody = jsonBody;
|
||||||
|
if (logBody.Length > 3000) logBody = logBody.Substring(0, 3000) + "... [Truncated]";
|
||||||
|
WulaLog.Debug($"[WulaAI] Request Payload:\n{logBody}");
|
||||||
}
|
}
|
||||||
|
|
||||||
using (UnityWebRequest request = new UnityWebRequest(endpoint, "POST"))
|
using (UnityWebRequest request = new UnityWebRequest(endpoint, "POST"))
|
||||||
@@ -167,6 +203,10 @@ namespace WulaFallenEmpire.EventSystem.AI
|
|||||||
}
|
}
|
||||||
|
|
||||||
string response = request.downloadHandler.text;
|
string response = request.downloadHandler.text;
|
||||||
|
if (Prefs.DevMode)
|
||||||
|
{
|
||||||
|
WulaLog.Debug($"[WulaAI] Response Body:\n{TruncateForLog(response)}");
|
||||||
|
}
|
||||||
return ExtractContent(response);
|
return ExtractContent(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,7 +308,38 @@ namespace WulaFallenEmpire.EventSystem.AI
|
|||||||
else if (c == 't') sb.Append('\t');
|
else if (c == 't') sb.Append('\t');
|
||||||
else if (c == '"') sb.Append('"');
|
else if (c == '"') sb.Append('"');
|
||||||
else if (c == '\\') sb.Append('\\');
|
else if (c == '\\') sb.Append('\\');
|
||||||
else sb.Append(c);
|
else if (c == 'u')
|
||||||
|
{
|
||||||
|
// Handle Unicode escape sequence \uXXXX
|
||||||
|
if (i + 4 < json.Length)
|
||||||
|
{
|
||||||
|
string hex = json.Substring(i + 1, 4);
|
||||||
|
if (int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out int charCode))
|
||||||
|
{
|
||||||
|
sb.Append((char)charCode);
|
||||||
|
i += 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fallback if parsing fails
|
||||||
|
sb.Append("\\u");
|
||||||
|
sb.Append(hex);
|
||||||
|
i += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("\\u");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c == '/')
|
||||||
|
{
|
||||||
|
sb.Append('/');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
escaped = false;
|
escaped = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -132,7 +132,8 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
|||||||
if (!showAllNeeds && !isLow) continue;
|
if (!showAllNeeds && !isLow) continue;
|
||||||
|
|
||||||
string marker = isLow ? "!" : "";
|
string marker = isLow ? "!" : "";
|
||||||
sb.Append($"{marker}{need.LabelCap} ({need.CurLevelPercentage:P0})");
|
// Add explicit polarity to guide AI interpretation
|
||||||
|
sb.Append($"{marker}{need.LabelCap}: {need.CurLevelPercentage:P0} (Higher is Better)");
|
||||||
if (Prefs.DevMode && need.def != null)
|
if (Prefs.DevMode && need.def != null)
|
||||||
{
|
{
|
||||||
sb.Append($"[{need.def.defName}]");
|
sb.Append($"[{need.def.defName}]");
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
if (settings.enableVlmFeatures)
|
if (settings.enableVlmFeatures)
|
||||||
{
|
{
|
||||||
listingStandard.CheckboxLabeled("优先使用原生多模态模式", ref settings.useNativeMultimodal, "直接在思考阶段将截图发送给主模型(推荐,速度更快,需模型支持视角)");
|
|
||||||
listingStandard.CheckboxLabeled("在 UI 中显示中间思考过程", ref settings.showThinkingProcess, "显示 AI 执行工具时的状态反馈");
|
listingStandard.CheckboxLabeled("在 UI 中显示中间思考过程", ref settings.showThinkingProcess, "显示 AI 执行工具时的状态反馈");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
// 视觉功能配置
|
// 视觉功能配置
|
||||||
public bool enableVlmFeatures = false;
|
public bool enableVlmFeatures = false;
|
||||||
public bool useNativeMultimodal = true; // 默认启用原生多模态
|
|
||||||
public bool showThinkingProcess = true; // 是否显示中间思考过过程
|
public bool showThinkingProcess = true; // 是否显示中间思考过过程
|
||||||
|
|
||||||
public override void ExposeData()
|
public override void ExposeData()
|
||||||
@@ -38,7 +37,6 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
// 简化后的视觉配置
|
// 简化后的视觉配置
|
||||||
Scribe_Values.Look(ref enableVlmFeatures, "enableVlmFeatures", false);
|
Scribe_Values.Look(ref enableVlmFeatures, "enableVlmFeatures", false);
|
||||||
Scribe_Values.Look(ref useNativeMultimodal, "useNativeMultimodal", true);
|
|
||||||
Scribe_Values.Look(ref showThinkingProcess, "showThinkingProcess", true);
|
Scribe_Values.Look(ref showThinkingProcess, "showThinkingProcess", true);
|
||||||
|
|
||||||
base.ExposeData();
|
base.ExposeData();
|
||||||
|
|||||||
3
Tools/mimo-v2-flash.txt
Normal file
3
Tools/mimo-v2-flash.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
https://api.xiaomimimo.com/v1
|
||||||
|
mimo-v2-flash
|
||||||
|
sk-cuwai2jix0zwrghj307pmvdpmtoc74j4uv9bejglxcs89tnx
|
||||||
Reference in New Issue
Block a user