ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LLM Agent 玩《矮人要塞》:状态观测、决策循环与代码实现

LLM Agent 玩《矮人要塞》:状态观测、决策循环与代码实现 用 LLM Agent 玩《矮人要塞》状态观测、决策循环与完整代码实现如果你最近在关注 LLM Agent 的进展会发现这个领域已经悄悄分成了两个世界。一边是常见的工具型 Agent它们调用 API、读写数据库、操作办公软件工作流里塞满了 Function Calling另一边则进入更“野生”的环境——不是 API而是真正的模拟世界比如《矮人要塞》Dwarf Fortress。后者看起来像一场行为艺术但实际上是 Agent 工程能力的一类极端压力测试。《矮人要塞》几乎是在故意为难 AgentASCII 字符界面、海量模拟状态、没有明确任务线、一局游戏可以持续几十个小时。把 LLM Agent 接进去本质上不是在做一个游戏外挂而是在验证一套“感知 → 规划 → 行动”的闭环系统在真实复杂环境里能不能持续、稳定、有目标地工作。这个能力恰好是通用 LLM 应用中最难做好的部分。这篇文章不打算只讲概念。我会从架构、环境准备、状态观测、决策循环到代码实现完整梳理构建一个玩《矮人要塞》的 LLM Agent 需要解决的关键问题。读完你会得到两个层面的收获一是能照着搭建一个最小可运行的游戏 Agent 骨架二是理解这类 Agent 真正难在哪——状态裁剪、动作约束、长期记忆、成本控制这些经验可以直接迁移到其他复杂系统里。1. 这篇文章真正要解决的问题很多人第一次看到“LLM Agent 玩《矮人要塞》”这个标题会下意识把它理解成一个游戏脚本告诉模型“去挖矿”然后模型输出方向键。但实际上这类项目从一开始就要面对五个很现实的问题。第一个问题是状态空间。一个运行中的矮人要塞里有几十个矮人每个矮人有情绪、技能、任务、位置、需求同时还有物资库存、工作队列、建筑状态、敌人威胁、季节变化。把所有状态直接塞给 LLMtoken 会瞬间爆炸。第二个问题是动作空间。游戏支持的操作非常丰富从宏观指令到微观的键盘菜单操作全都可行模型必须明确知道自己能动什么、不能动什么。第三个问题是长期目标。Agent 不是做一次决策就结束而是要像人类玩家一样把“建立一座繁荣堡垒”这样的长期目标拆解成一批短期动作。第四个问题是成本与延迟。每一轮“看状态、出动作、执行”都要消耗一次模型调用如果循环频率过高不仅费用高游戏本身也未必跟得上。最后一个问题是不可逆性。一次错误的挖矿指令可能导致塌方、淹水甚至灭团因此 Agent 的执行层必须支持备份、回滚和安全校验。正是这些问题让“玩《矮人要塞》”变成了一个有分量的工程题目。它不像文字冒险游戏那样依赖少量文本描述也不像 API 工具调用那样有标准输入输出它更接近“部分可观测的复杂系统”Agent 只能看到自己选择的视角必须在信息不完整的情况下做出决策并且承担后果。所以这篇文章的读者画像很清晰已经在做 Agent 应用开发但主要停留在工具调用层面想进一步接触复杂环境的人对游戏 AI 感兴趣的研究者和工程师以及想用低成本方式验证自己 Agent 工程能力的人。如果你只是想要一个“一键让游戏自动运行”的外挂那这个方向并不适合你因为它追求的不是最高效的按键序列而是 LLM 在复杂环境中的决策能力。2. 《矮人要塞》为什么是 Agent 的“试金石”先简单说下《矮人要塞》是什么。它是一款由 Tarn Adams 和 Zach Adams 兄弟开发的模拟游戏没有传统意义上的图形界面默认用 ASCII 字符绘制世界。游戏里包含完整的世界生成、地理变化、资源分布、矮人个体需求、水源、岩浆、季节、野生动物甚至历史事件系统。在“堡垒模式”下玩家要管理一群矮人让他们挖矿、种植、制造、防御、繁衍最终建立能长期运行的堡垒。为什么说它适合做 Agent 试金石因为传统游戏 AI 方法在这里基本失效。基于强化学习的方案通常依赖稠密奖励信号和明确目标但《矮人要塞》里没有“每走一步加一分”的奖励机制目标完全开放状态也不是一个规整的二维网格。你可以把游戏状态导出成结构化数据但怎么定义“做得好”就很主观这让训练目标变得很难设计。传统脚本式 AI 也很难应对这种复杂性因为规则组合爆炸开发成本极高。如果用表格对比常见环境会看得更清楚环境类型状态表示目标结构动作空间对 LLM Agent 的挑战文字冒险游戏一小段文本线性任务受限命令低适合入门验证API 工具调用结构化参数由用户指定函数集合中核心是格式和工具选择Minecraft部分网格状态开放目标连续移动操作较高需要长期规划与空间感知NetHack / Roguelike网格属性明确但复杂方向动作组合较高局部观测和随机性强矮人要塞大规模模拟状态完全开放多层次动作组合极高状态规模大动作不可逆这个表格想说明的核心判断是环境的复杂度并不来自画面而来自“状态密度”和“目标开放性”。《矮人要塞》的 ASCII 界面看似原始但它背后是极其密集的状态空间而且每一个决策都可能影响后续几十个游戏步骤。正因为如此这个方向对 LLM Agent 的工程能力提出了明确要求。模型必须学会忽略无关信息只关注当前目标相关的状态必须能把一个宏大目标拆解成可执行的小步骤必须在动作执行失败后识别异常并调整计划还必须能在有限的上下文窗口里维护跨长时间跨度的记忆。这套能力组合其实和真实世界的运维系统、生产控制系统高度相似。所以用《矮人要塞》作为研究对象本质上是在训练 Agent 的“环境适应力”。3. LLM Agent 的核心概念与整体架构在写代码之前有必要把一些基础概念对齐否则后面看到“观察”“决策”“动作”这些词时容易混淆。LLM Agent 并不是简单地把 ChatGPT 接进来而是以一个或多个大模型为核心构建一个与环境交互的闭环系统。这个闭环通常包括感知模块、决策模块、执行模块和记忆模块。感知模块负责把环境状态转换成模型能理解的结构化文本决策模块负责根据当前状态、历史信息和目标输出下一步动作执行模块负责把模型输出的动作翻译成真实环境的操作记忆模块则负责管理跨步骤的信息避免每一步都从零开始。这类 Agent 和普通“对话机器人”最大的区别在于它要有工具调用能力和状态感知能力。工具调用在技术上的体现通常是 Function Calling 或 JSON 输出约束模型不只说“我建议怎么做”而是输出一个可被代码解析的结构化动作。状态感知则要求 Agent 在每次决策前都能拿到当前环境的摘要而不是只靠上一轮的回复继续推理。在这个项目里整个架构可以拆成一条信息流游戏引擎持续运行DFHack 作为桥接层从游戏内部读取状态并执行命令Python 桥接层负责调用 DFHack、采集状态 JSON、把状态压缩成文本LLM 决策模块接收状态摘要、历史记录和目标描述输出动作 JSON动作经白名单校验后再回到 DFHack 执行形成闭环。每个模块的职责可以这样理解DFHack 桥接层解决“怎么看到游戏内部”的问题。游戏本身没有对外 APIDFHack 提供了一组脚本和命令接口能读取单位、物品、任务、区域等信息也能修改游戏状态。状态观测层解决“看什么”的问题。从 DFHack 拿到的数据非常庞大必须按目标裁剪只输出当前决策真正需要的字段。决策层解决“下一步干什么”的问题。这里的关键不是模型有多强而是 Prompt 和输出约束设计得好不好模型是否清楚自己的目标、可用动作和限制条件。执行层解决“怎么把想法落地”的问题。动作必须经过白名单、参数校验和可回滚判断再转换成 DFHack 命令或模拟按键。记忆层解决“怎么记住之前的事”的问题。短期记忆管理最近几步的决策历史长期记忆保存里程碑、区域标记和战略意图。这套架构的通用性很强。把“游戏引擎”换成“电商系统”“证券交易系统”“工厂控制台”整体逻辑几乎不用变。这也是我强调“这个方向值钱的是工程经验”的原因。4. 环境准备与前置条件如果按照最保守的方案搭建实验环境你需要的不是一台能跑大模型的 GPU 服务器而是一个能运行《矮人要塞》的普通电脑。下面是一份实验环境清单具体版本请以实际项目为准这里重点演示通用思路。第一是游戏本体。《矮人要塞》目前有经典版和 Steam 版两种发行方式Steam 版对新手友好很多并且与 DFHack 的匹配度较高。实际上无论选择哪个版本第一步都是安装游戏并确认它能正常启动、创建世界、进入堡垒模式。第二是 DFHack。这是一个开源的游戏修改框架核心价值在于给游戏提供了脚本接口和命令行工具。安装时最重要的是版本匹配DFHack 的版本必须与游戏版本对应否则加载脚本时会报错。Steam 版通常可以通过创意工坊或官方安装方式获得经典版则要把 DFHack 文件解压到游戏目录。第三是 Python 环境。建议使用 Python 3.10 或更高版本需要安装的依赖包括 requests 或 openai、pyyaml、pydantic。如果你用的是本地推理可以考虑 Ollama它提供一个兼容 OpenAI 的 HTTP 接口把 base_url 指向本地 11434 端口即可。项目骨架阶段用本地小模型验证逻辑后续再换成能力更强的模型这样成本比较可控。第四是可选组件。如果你想做长期记忆可以引入 SQLite 或者向量数据库如果想通过模拟按键的方式操作游戏还需要 pyautogui 或 pydirectinput 这类库。不过我更推荐优先使用 DFHack 命令接口因为按键模拟依赖 GUI 刷新时序稳定性更差。安装完成后有一个非常重要的验证步骤先打开游戏并进入一个堡垒存档然后在命令行执行dfhack-run命令看是否能正常返回。能返回说明 DFHack 已经工作后续的脚本调用才有基础。如果这一步失败后面所有工作都无法继续必须先检查 DFHack 与游戏版本是否匹配。这里还要强调一个实验规范永远使用复制的存档进行 Agent 实验。游戏里一次错误操作可能让整个堡垒崩溃把存档目录复制一份的成本远低于排查问题的时间成本。5. 核心流程拆解观察、决策、执行、记忆整个 Agent 的循环可以概括为四个步骤观察、决策、执行、记忆。下面把每一步拆开讲你才能真正理解代码为什么那样写。5.1 观察层怎么把游戏状态变成文本观察层的目标不是“拿到所有数据”而是“拿到当前决策所需要的数据”。从 DFHack 的 Lua 接口可以直接访问游戏内部对象比如遍历所有单位、读取矮人的职业、位置、任务状态。但直接把这些数据丢给 LLM 会带来两个问题一是上下文窗口放不下二是无关信息严重干扰推理。因此观察层通常做两件事。第一是按目标裁剪状态例如当前目标是“检查矮人工作状态”就只提取矮人列表、当前任务、空闲状态如果是“管理库存”就提取资源清单和库存上限。第二是转换成 JSON 或键值文本让模型能稳定解析。实际项目中状态 JSON 还会加上时间戳和游戏 tick 计数方便 Agent 判断状态是否真的发生了变化。体现到 DFHack 脚本里就是写一个 Lua 脚本把指定信息打印出来由 Python 侧捕获。由于 DFHack 的 API 随版本变化下面脚本是结构示例实际字段名需要查阅自己安装的 DFHack 文档。-- 文件路径hack/scripts/dwarf_summary.lua -- 示例脚本输出当前矮人的简要状态 -- 注意DFHack Lua API 因版本而异请以实际版本文档为准 local utils require(utils) local function is_active_dwarf(unit) if not unit then return false end if unit.flags.dead or unit.flags.unborn then return false end return true end local units df.global.world.units.all local result {} for _, unit in ipairs(units) do if is_active_dwarf(unit) and unit.profession then local entry { id unit.id, name unit.name and unit.name.first_name or ?, profession tostring(unit.profession), has_job unit.job ~ nil, pos unit.pos and { unit.pos.x, unit.pos.y, unit.pos.z } or nil } table.insert(result, entry) end end print(dfhack.json_encode(result))这段脚本做的事情很简单遍历游戏中的所有单位过滤掉死亡和未出生的单位然后输出每个矮人的名字、职业、是否有工作以及坐标。真实项目里还要加入心情、技能、当前任务目标等字段但最小骨架从这个粒度开始就够了。5.2 决策层让 LLM 输出可执行动作决策层是 Agent 的核心。设计重点不是“让模型自由发挥”而是“让模型在受限动作空间里做选择”。我会在系统 Prompt 里明确告诉模型第一你现在是什么角色、管理哪个堡垒第二你的长期目标是什么第三当前可用的动作类型有哪些每个动作的参数是什么第四输出必须是 JSON不允许解释性文字。如果模型输出了非 JSON 内容解析层要能容错并重试。动作白名单非常重要。DFHack 提供了大量命令其中不少是可以摧毁地图、删除实体、修改物理模拟的“危险操作”。如果不加白名单模型可能在一盘普通的测试中直接触发岩浆或者删除所有矮人。不是模型有恶意而是 LLM 对动作后果没有常识。白名单应该在配置文件中定义并由执行层强制校验。决策层的输入包括三部分状态文本、历史摘要、目标描述。历史摘要不能太长通常只保留最近几步的动作和关键状态变化否则 token 会线性膨胀。目标描述可以是用户指定的短句比如“在冬天前准备 50 份食物”也可以是 Agent 自己根据当前状态生成的阶段性目标。# 文件路径agent/llm_client.py import json from openai import OpenAI class LLMClient: def __init__(self, base_url: str, model: str, temperature: float 0.2): self.client OpenAI(base_urlbase_url, api_keynot-needed) self.model model self.temperature temperature def decide( self, system_prompt: str, state_text: str, history_text: str, goal_text: str ) - dict: user_content ( f当前状态\n{state_text}\n\n f历史动作\n{history_text}\n\n f当前目标{goal_text}\n\n 请输出下一个动作的 JSON不要包含其他文字。 ) resp self.client.chat.completions.create( modelself.model, temperatureself.temperature, messages[ {role: system, content: system_prompt}, {role: user, content: user_content}, ], ) content resp.choices[0].message.content # 实际项目中要增加容错解析逻辑 return json.loads(content)这里把api_key写死为 not-needed是因为 Ollama 这类本地服务不校验 key。如果你接的是云端模型需要换成真实的密钥并放入环境变量不要硬编码在代码里。5.3 执行层动作到游戏操作的翻译执行层解决的问题是模型输出了动作 JSON代码怎么让它变成游戏内的操作。一般有两条路径。路径 A 是调用 DFHack 命令。这种方式稳定性最高不依赖 GUI适合批量操作和后台运行。缺点是你需要针对每个动作类型写命令映射有些游戏行为可能无法仅靠 DFHack 命令完成。路径 B 是模拟键盘操作用 pyautogui 模拟人类按键好处是“像真人一样操作游戏”坏处是 UI 刷新时序不确定调试起来很麻烦。我建议做一层抽象把动作类型定义为中间表示底层再选择使用 DFHack 命令还是按键模拟。这样以后替换执行方式不需要改动决策层代码。无论走哪条路径执行之前都必须做安全校验最简单的校验就是检查动作类型是否在白名单里参数格式是否正确以及当前游戏是否暂停。下面的 Python 代码演示了执行层的骨架# 文件路径agent/env.py import json import subprocess from typing import Any, Dict class DwarfFortressEnv: def __init__(self, dfhack_run: str dfhack-run): self.dfhack_run dfhack_run self._action_whitelist { dig_designation, set_task, pause, unpause, } def observe(self, script_name: str) - Dict[str, Any]: 调用 DFHack 脚本获取游戏状态。 实际项目中还要处理超时、日志噪声和 JSON 提取。 result subprocess.run( [self.dfhack_run, script_name], capture_outputTrue, textTrue, timeout30, ) # 这里简单直接解析 stdout真实场景需要做噪音过滤 return json.loads(result.stdout.strip()) def act(self, action: Dict[str, Any]) - bool: action_type action.get(type) if action_type not in self._action_whitelist: raise ValueError(faction not allowed: {action_type}) if action_type dig_designation: # 示例映射到 DFHack 的指定道具命令 # 具体命令以你安装的 DFHack 版本为准 self._run_dfhack_command(designate, action.get(args)) elif action_type pause: self._run_dfhack_command(pause) elif action_type unpause: self._run_dfhack_command(unpause) # 返回是否触发状态变化实际需要判断前后状态差异 return True def _run_dfhack_command(self, command: str, args: dict None): cmd [self.dfhack_run, command] if args: cmd.append(json.dumps(args)) subprocess.run(cmd, checkFalse, timeout30)5.4 记忆层短期与长期记忆层是很多 Agent 项目最容易偷懒的部分但也是最值得投入的地方。短期记忆主要负责管理最近几步的决策历史目的是让模型知道“我刚才做了什么”避免反复执行同一个动作。最简做法是在 Python 里维护一个队列每次循环完成后把当前步的摘要追加进去决策时把最近 N 步拼进 Prompt。长期记忆则负责保存跨时间尺度的信息例如“地图东北区域已经挖空了”“当前食物储备低于安全线”“三个月前发生过一次入侵”。长期记忆如果只靠 Prompt 累积很快就会超出上下文窗口所以通常需要两层设计第一层是“战略笔记本”保存当前阶段最重要的目标和事实第二层是向量数据库存历史事件决策前做相似度检索只把相关的历史片段取回。在小规模项目中我建议先用“战略笔记本”也就是让 LLM 在每轮决策后输出一个状态总结由 Python 侧写入一个文本文件。这样做有两个好处一是实现简单不依赖向量数据库二是文本内容完全可审计调试时可以打开文件看模型到底记住了什么。6. 完整示例与代码实现前面讲完了每个模块的设计这一节把代码串起来。示例代码是教学骨架简化了很多容错逻辑但目录结构和调用关系是真实的工程形态。6.1 项目目录结构dwarf_agent/ ├── config.yaml ├── requirements.txt ├── main.py ├── agent/ │ ├── __init__.py │ ├── env.py │ ├── llm_client.py │ └── loop.py └── dfhack_scripts/ └── dwarf_summary.luaconfig.yaml放模型和环境的配置main.py是入口agent/env.py负责与游戏交互agent/llm_client.py负责与大模型交互agent/loop.py负责把观察、决策、行动串起来dfhack_scripts/dwarf_summary.lua是放在游戏 scripts 目录里的状态采集脚本。6.2 配置文件# 文件路径dwarf_agent/config.yaml llm: base_url: http://localhost:11434/v1 model: qwen2.5:14b temperature: 0.2 max_tokens: 2048 env: dfhack_run: dfhack-run # 每次最多执行的步数 max_steps: 50 # 短期间隔秒数避免游戏状态来不及刷新 step_interval: 1.0 agent: # 白名单动作 action_whitelist: - pause - unpause # 历史摘要保留轮数 history_window: 4 # 战略笔记本路径 notebook_path: memory/strategy.md这里的模型名qwen2.5:14b只是示例你可以按本机 Ollama 里实际拉取的模型修改。如果使用云端模型只需要改base_url和model字段。6.3 主循环代码接下来是main.py它是整个 Agent 的入口。# 文件路径dwarf_agent/main.py import json import time import yaml from agent.env import DwarfFortressEnv from agent.llm_client import LLMClient def load_config(pathconfig.yaml): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def main(): config load_config() env DwarfFortressEnv(config[env][dfhack_run]) llm LLMClient( base_urlconfig[llm][base_url], modelconfig[llm][model], temperatureconfig[llm][temperature], ) system_prompt 你是一个矮人要塞的AI管理助手。你的任务是帮助矮人堡垒稳定运行。 你必须根据当前状态、历史动作和当前目标输出一个动作JSON。 可用的动作类型有 - pause暂停游戏 - unpause取消暂停游戏 只输出JSON不要输出解释文字。 goal_text 让堡垒保持安静运行 history [] for step in range(config[env][max_steps]): # 观察 state env.observe(dwarf_summary) # 历史摘要 history_text \n.join(history[-config[agent][history_window]:]) # 决策 action llm.decide( system_promptsystem_prompt, state_textjson.dumps(state, ensure_asciiFalse), history_texthistory_text, goal_textgoal_text, ) # 执行 env.act(action) # 记录历史 history.append( fstep{step} action{json.dumps(action, ensure_asciiFalse)} ) print(f[step {step}] action{action}) time.sleep(config[env][step_interval]) if __name__ __main__: main()这份代码已经能形成完整循环但在真实项目中你还必须补上两类逻辑。第一类是异常处理模型输出可能不是合法 JSONLLM 服务可能超时DFHack 命令可能执行失败这些都要有明确的失败降级策略。第二类是“状态变化检测”光发送动作还不够要判断游戏状态是否真的发生了变化如果没有变化应该暂停循环或者向模型反馈“动作未生效”。6.4 运行命令先安装依赖再启动cd dwarf_agent pip install -r requirements.txt python main.py如果一切正常你会看到类似下面的输出[step 0] action{type: pause} [step 1] action{type: unpause} [step 2] action{type: pause}虽然一直暂停和取消暂停看起来很傻但至少说明观察、决策、执行链路已经跑通。下一步才是定义更有意义的动作类型例如处理工作队列、检查库存、指定挖矿区域。7. 运行结果与效果验证很多 Agent 项目倒在“能跑”和“真的完成任务”之间。代码不报错不代表 Agent 在干活。所以验证阶段必须定义一个可以量化的小任务。我推荐从“三分钟实验”开始。用一个复制出来的新存档给 Agent 设定一个非常简单的目标比如“查看当前矮人空闲数量”然后记录 50 步内它能完成多少次可解析的动作、状态是否变化、是否达到了目标。观察指标可以有四类。第一类是动作合法性比例即模型输出能被成功解析且通过白名单校验的比例。第二类是状态变化率即每步执行后游戏状态是否真的发生变化如果动作合法但状态不变说明执行层可能没生效。第三类是目标达成率即在一定步数内是否完成用户设定的任务。第四类是资源消耗包括 LLM 调用次数、token 总数和单步延迟。验证成功的最低标准是Agent 能在不崩溃、不重复同一步、不执行危险动作的前提下连续运行几十步并对状态变化做出反应。能达到这个标准再谈优化和扩展。如果发现状态一直不变第一步要检查的不是模型而是执行层。先用命令行单独执行一次 DFHack 命令确认真实环境里动作是否生效再回到 Python 层检查subprocess.run的返回码和标准错误输出。很多时候问题出在 DFHack 命令名称或参数格式与当前版本不匹配而不是模型“太笨”。8. 常见问题与排查思路开发这类 Agent 时有一些问题几乎是必然遇到的。我把它们整理成一张排查表问题现象可能原因排查方式解决方案dfhack-run命令找不到DFHack 未安装或不在 PATH执行which dfhack-run或检查游戏目录在代码中配置完整路径或设置环境变量DFHack 脚本执行没有输出游戏未加载世界或堡垒先手动进入堡垒模式在 Agent 启动前检查存档状态模型输出 JSON 解析失败模型返回了 Markdown 代码块或解释文字查看模型原始返回值增加正则提取 JSON 的逻辑或使用强制 JSON 输出动作合法但游戏无变化游戏处于暂停状态或命令被忽略检查暂停状态和 DFHack 日志在执行动作前先处理暂停状态并等待一帧刷新游戏状态太大token 消耗太快状态序列化字段过多历史保留过长打印每次 Prompt 的长度按目标裁剪状态历史只用摘要模型反复执行相同动作历史信息没有传回模型检查history是否为空把最近几步动作加入 Prompt 中LLM API 超时本地模型推理过慢或网络问题单独调用一次 API 测延迟减小模型参数量或降低循环频率这张表的重点是执行层的问题通常比模型层的问题更常见。很多开发者看到 Agent 循环“不干活”第一反应是换更大的模型但其实问题往往出在 DFHack 命令映射、游戏状态刷新或者状态裁剪上。9. 最佳实践与工程建议把这个项目从“能跑”推向“可靠”有六个经验值得记下来。第一状态观测是核心不是模型。一个高质量的“状态裁剪器”比一个大模型更能提升 Agent 表现。你可以在开发初期花最多时间设计状态摘要格式字段名尽量稳定数值尽量可比较并且加上时间戳。不要让模型去猜某个字段到底是“当前值”还是“累计值”。第二动作空间必须是白名单。把dig_designation、set_task这类抽象动作类型和 DFHack 的具体命令分开。白名单放在配置文件中方便审计。危险动作默认拒绝除非你在实验环境中明确打开。第三备份和回滚是硬要求。每次实验前复制存档目录Agent 执行了“不可逆操作”后立即切换备份。很多看似聪明的 Agent 行为只有在错误操作导致堡垒崩溃时才会暴露问题。第四用本地小模型先跑通闭环。项目骨架阶段用 Ollama 拉一个 7B 或 14B 模型把循环逻辑、状态裁剪、执行层调通后再换更强的模型。这样做的原因是闭环问题比模型能力问题更本质如果用大模型去调试小模型也能暴露的工程问题会浪费大量 token 和时间。第五日志与回放必不可少。每一轮都记录时间戳、状态摘要、模型原始输出、解析后动作、执行结果。出现问题时你能精确回放 Agent 当时的“视野”而不是靠感觉猜。一条好的日志格式是 JSON Lines每行一个事件。第六评估先行。不要等代码写完了再想“什么算完成”。在项目启动时就要写清楚里程碑任务比如“在 100 步内让一个矮人挖出 10 格矿”“在冬天前库存食物数量不小于 50”。这样 Agent 每一步是否能推进就有明确判断依据。10. 总结与后续学习方向构建一个玩《矮人要塞》的 LLM Agent真正的难点不再于“接入大模型”而在于把一个不透明的复杂系统变成 Agent 可以感知、决策和操作的对象。这篇文章从状态观测、决策循环、动作执行和记忆管理四个层面拆解了完整架构并给出了可运行的 Python 骨架代码。你可以先复制这份骨架用本地模型跑通最小闭环再逐步加入目标管理、记忆检索和更丰富的动作映射。接下来值得深入的方向有三个。一是继续挖掘 DFHack 的能力把更多游戏状态结构化成 JSON让 Agent 拥有更全面的“视野”二是改进长期记忆架构例如用向量检索替代简单的文本文件让 Agent 能跨数百步记住战略决策三是把动作空间设计成分层结构让 LLM 只负责高层计划底层交由搜索或规则引擎执行。如果你对 Agent 在复杂系统中的应用感兴趣建议从今天的骨架开始动手。先别急着优化模型先把“从状态到动作的可观测闭环”跑稳。你会发现一个能稳定跑满 50 步的小 Agent比一个偶尔灵光一现的大模型更接近工程可用的状态。
返回列表