This commit is contained in:
2025-12-22 15:11:24 +08:00
parent 970101eaa7
commit 39812b070c
121 changed files with 541 additions and 5630 deletions

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using UnityEngine.Networking;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire.EventSystem.AI
@@ -21,7 +22,7 @@ namespace WulaFallenEmpire.EventSystem.AI
_model = model;
}
public async Task<string> GetChatCompletionAsync(string instruction, List<(string role, string message)> messages)
public async Task<string> GetChatCompletionAsync(string instruction, List<(string role, string message)> messages, int? maxTokens = null, float? temperature = null)
{
if (string.IsNullOrEmpty(_baseUrl))
{
@@ -39,6 +40,15 @@ namespace WulaFallenEmpire.EventSystem.AI
jsonBuilder.Append("{");
jsonBuilder.Append($"\"model\": \"{_model}\",");
jsonBuilder.Append("\"stream\": false,"); // We request non-stream, but handle stream if returned
if (maxTokens.HasValue)
{
jsonBuilder.Append($"\"max_tokens\": {Math.Max(1, maxTokens.Value)},");
}
if (temperature.HasValue)
{
float clamped = Mathf.Clamp(temperature.Value, 0f, 2f);
jsonBuilder.Append($"\"temperature\": {clamped.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture)},");
}
jsonBuilder.Append("\"messages\": [");
// System instruction

View File

@@ -5,6 +5,8 @@ using System.Linq;
using System.Reflection;
using System.Text;
using Verse;
using System.Text.RegularExpressions;
using WulaFallenEmpire.EventSystem.AI.UI;
namespace WulaFallenEmpire.EventSystem.AI.Tools
{
@@ -91,6 +93,13 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
idx++;
}
string toolHistory = BuildToolHistory(count);
if (!string.IsNullOrWhiteSpace(toolHistory))
{
sb.AppendLine();
sb.AppendLine(toolHistory);
}
return sb.ToString().TrimEnd();
}
catch (Exception ex)
@@ -99,6 +108,54 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
}
}
private static string BuildToolHistory(int maxCount)
{
var window = Dialog_AIConversation.Instance ?? Find.WindowStack.WindowOfType<Dialog_AIConversation>();
if (window == null) return "AI Tool History: none found.";
var history = window.GetHistorySnapshot();
if (history == null || history.Count == 0) return "AI Tool History: none found.";
var entries = new List<(string ToolXml, string ToolResult)>();
for (int i = history.Count - 1; i >= 0; i--)
{
var entry = history[i];
if (!string.Equals(entry.role, "tool", StringComparison.OrdinalIgnoreCase)) continue;
string toolResult = entry.message ?? "";
for (int j = i - 1; j >= 0; j--)
{
var prev = history[j];
if (string.Equals(prev.role, "assistant", StringComparison.OrdinalIgnoreCase) && IsXmlToolCall(prev.message))
{
entries.Add((prev.message ?? "", toolResult));
i = j;
break;
}
}
if (entries.Count >= maxCount) break;
}
if (entries.Count == 0) return "AI Tool History: none found.";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < entries.Count; i++)
{
if (i > 0) sb.AppendLine();
sb.AppendLine(entries[i].ToolXml.Trim());
sb.AppendLine(entries[i].ToolResult.Trim());
}
return sb.ToString().TrimEnd();
}
private static bool IsXmlToolCall(string response)
{
if (string.IsNullOrWhiteSpace(response)) return false;
return Regex.IsMatch(response, @"<([a-zA-Z0-9_]+)(?:>.*?</\1>|/>)", RegexOptions.Singleline);
}
private static IEnumerable<NotificationEntry> ReadLetters(int fallbackNow)
{
var list = new List<NotificationEntry>();
@@ -253,4 +310,3 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
}
}
}

View File

@@ -146,6 +146,29 @@ namespace WulaFallenEmpire.EventSystem.AI.Utils
}
}
// Allow CJK subsequence matches (e.g. "零件" matches "零部件").
if (!string.IsNullOrEmpty(normalizedQuery) && normalizedQuery.Length >= 2 && IsCjkString(normalizedQuery))
{
int bestTargetLen = 0;
if (IsCjkString(normalizedLabel) && IsCjkSubsequence(normalizedQuery, normalizedLabel))
{
bestTargetLen = normalizedLabel.Length;
}
if (IsCjkString(normalizedDefName) && IsCjkSubsequence(normalizedQuery, normalizedDefName))
{
if (bestTargetLen == 0 || normalizedDefName.Length < bestTargetLen)
{
bestTargetLen = normalizedDefName.Length;
}
}
if (bestTargetLen > 0)
{
float coverage = (float)normalizedQuery.Length / Math.Max(1, bestTargetLen);
score = Math.Max(score, 0.50f + 0.30f * coverage);
}
}
bool queryLooksLikeFood =
tokens.Any(t => t == "meal" || t == "food" || t.Contains("meal") || t.Contains("food")) ||
lowerQuery.Contains("\u996D") || // 饭
@@ -243,6 +266,27 @@ namespace WulaFallenEmpire.EventSystem.AI.Utils
(c >= '\uF900' && c <= '\uFAFF');
}
private static bool IsCjkString(string s)
{
if (string.IsNullOrEmpty(s)) return false;
for (int i = 0; i < s.Length; i++)
{
if (!IsCjkChar(s[i])) return false;
}
return true;
}
private static bool IsCjkSubsequence(string query, string target)
{
if (string.IsNullOrEmpty(query) || string.IsNullOrEmpty(target)) return false;
int qi = 0;
for (int ti = 0; ti < target.Length && qi < query.Length; ti++)
{
if (target[ti] == query[qi]) qi++;
}
return qi == query.Length;
}
private static string NormalizeKey(string s)
{
if (string.IsNullOrEmpty(s)) return "";
@@ -253,4 +297,3 @@ namespace WulaFallenEmpire.EventSystem.AI.Utils
}
}
}