This commit is contained in:
2025-12-27 21:58:51 +08:00
parent 9d4a7c5e8e
commit 8f17366bd3
6 changed files with 1093 additions and 0 deletions

View File

@@ -0,0 +1,278 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire.EventSystem.AI.Agent
{
/// <summary>
/// 自主 Agent 循环 - 持续观察游戏并做出决策
/// 用户只需给出开放式指令如"帮我挖铁"或"帮我玩殖民地"
/// </summary>
public class AutonomousAgentLoop : GameComponent
{
public static AutonomousAgentLoop Instance { get; private set; }
// Agent 状态
private bool _isRunning;
private string _currentObjective;
private float _lastDecisionTime;
private int _decisionCount;
private readonly List<string> _actionHistory = new List<string>();
// 配置
private const float DecisionIntervalSeconds = 3f; // 每 3 秒决策一次
private const int MaxActionsPerObjective = 100;
// 事件
public event Action<string> OnDecisionMade;
public event Action<string> OnObjectiveComplete;
public event Action<string> OnError;
public bool IsRunning => _isRunning;
public string CurrentObjective => _currentObjective;
public int DecisionCount => _decisionCount;
public AutonomousAgentLoop(Game game)
{
Instance = this;
}
/// <summary>
/// 开始执行开放式目标
/// </summary>
public void StartObjective(string objective)
{
if (string.IsNullOrWhiteSpace(objective))
{
OnError?.Invoke("目标不能为空");
return;
}
_currentObjective = objective;
_isRunning = true;
_decisionCount = 0;
_actionHistory.Clear();
_lastDecisionTime = Time.realtimeSinceStartup;
WulaLog.Debug($"[AgentLoop] Started objective: {objective}");
Messages.Message($"AI Agent 开始执行: {objective}", MessageTypeDefOf.NeutralEvent);
// 立即执行第一次决策
_ = ExecuteDecisionCycleAsync();
}
/// <summary>
/// 停止 Agent
/// </summary>
public void Stop()
{
if (!_isRunning) return;
_isRunning = false;
WulaLog.Debug($"[AgentLoop] Stopped after {_decisionCount} decisions");
Messages.Message($"AI Agent 已停止,执行了 {_decisionCount} 次决策", MessageTypeDefOf.NeutralEvent);
}
public override void GameComponentTick()
{
if (!_isRunning) return;
// 检查是否到达决策间隔
if (Time.realtimeSinceStartup - _lastDecisionTime < DecisionIntervalSeconds) return;
// 检查是否超过最大操作次数
if (_decisionCount >= MaxActionsPerObjective)
{
Messages.Message($"AI Agent: 已达到最大操作次数 ({MaxActionsPerObjective}),暂停执行", MessageTypeDefOf.CautionInput);
Stop();
return;
}
_lastDecisionTime = Time.realtimeSinceStartup;
// 异步执行决策
_ = ExecuteDecisionCycleAsync();
}
/// <summary>
/// 执行一次决策循环: Observe → Think → Act
/// </summary>
private async Task ExecuteDecisionCycleAsync()
{
try
{
// 1. Observe - 收集游戏状态
var gameState = StateObserver.CaptureState();
string stateText = gameState.ToPromptText();
// 2. 构建决策提示词
string prompt = BuildDecisionPrompt(stateText);
// 3. Think - 调用 AI 获取决策
var settings = WulaFallenEmpireMod.settings;
if (settings == null || string.IsNullOrEmpty(settings.apiKey))
{
OnError?.Invoke("API Key 未配置");
Stop();
return;
}
var client = new SimpleAIClient(settings.apiKey, settings.baseUrl, settings.model);
// 使用 VLM 如果启用且配置了
string decision;
if (settings.enableVlmFeatures && !string.IsNullOrEmpty(settings.vlmApiKey))
{
// 使用 VLM 分析屏幕
string base64Image = ScreenCaptureUtility.CaptureScreenAsBase64();
var vlmClient = new SimpleAIClient(settings.vlmApiKey, settings.vlmBaseUrl, settings.vlmModel);
decision = await vlmClient.GetVisionCompletionAsync(
GetAgentSystemPrompt(),
prompt,
base64Image,
maxTokens: 512,
temperature: 0.3f
);
}
else
{
// 纯文本模式
var messages = new List<(string role, string message)>
{
("user", prompt)
};
decision = await client.GetChatCompletionAsync(GetAgentSystemPrompt(), messages, 512, 0.3f);
}
if (string.IsNullOrEmpty(decision))
{
WulaLog.Debug("[AgentLoop] Empty decision received");
return;
}
_decisionCount++;
WulaLog.Debug($"[AgentLoop] Decision #{_decisionCount}: {decision.Substring(0, Math.Min(100, decision.Length))}...");
// 4. Act - 执行决策
ExecuteDecision(decision);
// 5. 记录历史
_actionHistory.Add($"[{_decisionCount}] {decision.Substring(0, Math.Min(50, decision.Length))}");
if (_actionHistory.Count > 20)
{
_actionHistory.RemoveAt(0);
}
OnDecisionMade?.Invoke(decision);
// 6. 检查是否完成目标
if (decision.Contains("<objective_complete") || decision.Contains("目标已完成"))
{
OnObjectiveComplete?.Invoke(_currentObjective);
Stop();
}
}
catch (Exception ex)
{
WulaLog.Debug($"[AgentLoop] Error in decision cycle: {ex.Message}");
OnError?.Invoke(ex.Message);
}
}
private string BuildDecisionPrompt(string gameStateText)
{
var sb = new StringBuilder();
sb.AppendLine("# 当前任务");
sb.AppendLine($"**目标**: {_currentObjective}");
sb.AppendLine($"**已执行决策次数**: {_decisionCount}");
sb.AppendLine();
sb.AppendLine(gameStateText);
if (_actionHistory.Count > 0)
{
sb.AppendLine();
sb.AppendLine("# 最近操作历史");
foreach (var action in _actionHistory)
{
sb.AppendLine($"- {action}");
}
}
sb.AppendLine();
sb.AppendLine("# 请决定下一步操作");
sb.AppendLine("分析当前状态,输出一个 XML 工具调用来推进目标。");
sb.AppendLine("如果目标已完成,输出 <objective_complete/>。");
sb.AppendLine("如果不需要操作(等待中),输出 <no_action/>。");
return sb.ToString();
}
private string GetAgentSystemPrompt()
{
return @"你是一个自主 RimWorld 游戏 AI Agent。你的任务是独立完成用户给出的开放式目标。
# 核心原则
1. **自主决策**: 不要等待用户指示,主动分析情况并采取行动
2. **循序渐进**: 每次只执行一个操作,观察结果后再决定下一步
3. **问题应对**: 遇到障碍时自己想办法解决
4. **目标导向**: 始终围绕目标推进,避免无关操作
# 可用工具
- get_game_state: 获取详细游戏状态
- designate_mine: <designate_mine><x>X坐标</x><z>Z坐标</z><radius>可选半径</radius></designate_mine> 标记采矿
- draft_pawn: <draft_pawn><pawn_name>名字</pawn_name><draft>true/false</draft></draft_pawn> 征召殖民者
- analyze_screen: <analyze_screen><context>分析目标</context></analyze_screen> 分析屏幕需要VLM
- visual_click: <visual_click><x>0-1比例</x><y>0-1比例</y></visual_click> 模拟点击
# 输出格式
直接输出一个 XML 工具调用,不要解释。
如果目标已完成: <objective_complete/>
如果需要等待: <no_action/>
# 注意事项
- 坐标使用游戏内整数坐标,不是屏幕比例
- 优先使用 API 工具designate_mine 等),视觉工具用于 mod 内容
- 保持简洁高效";
}
private void ExecuteDecision(string decision)
{
// 解析并执行 AI 的决策
// 从 AIIntelligenceCore 借用工具执行逻辑
var core = AIIntelligenceCore.Instance;
if (core == null)
{
WulaLog.Debug("[AgentLoop] AIIntelligenceCore not available");
return;
}
// 提取工具调用并执行
// 暂时使用简单的正则匹配,实际应整合 AIIntelligenceCore 的解析逻辑
if (decision.Contains("<no_action") || decision.Contains("<objective_complete"))
{
// 不需要执行
return;
}
// 委托给 AIIntelligenceCore 执行工具
// TODO: 整合更完善的工具执行逻辑
WulaLog.Debug($"[AgentLoop] Executing: {decision}");
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref _currentObjective, "agentObjective", "");
Scribe_Values.Look(ref _isRunning, "agentRunning", false);
Scribe_Values.Look(ref _decisionCount, "agentDecisionCount", 0);
}
}
}