资讯中心

从零构建LLM Agent玩Dwarf Fortress:环境接口与工程化实践

📅 2026/8/27 2:28:02
从零构建LLM Agent玩Dwarf Fortress:环境接口与工程化实践
你第一次看到“Building an LLM Agent to Play Dwarf Fortress”这个项目标题时大概率有两种反应一种是觉得“这不就是给游戏加个 AI 外挂嘛”另一种是认为“LLM Agent 玩文字冒险游戏应该挺简单”。这两种判断都不太准确。Dwarf Fortress 不是那种“看一眼画面就能做决策”的游戏。它的底层是一个完整的模拟世界几十个矮人各有心情、记忆、技能和社交关系食物、木材、石材、金属、酒精、衣物各有状态地下还有怪物、河流、温度、重力、流体扩散这些系统在同时运转。你甚至很难把“当前游戏状态”完整地翻译给一个模型。我开始接触这类项目时最强烈的感受是真正的难点根本不在调用模型而在把一个开放、复杂、长时程的游戏世界压缩成一个 Agent 可以持续感知、规划、行动、反思的环境接口。这个项目真正值得研究的不是“模型会不会玩游戏”而是“当我们把一个复杂系统暴露给 LLM 时应该暴露什么、隐藏什么、怎么反馈、怎么避免它越玩越乱”。这篇文章我想从实践角度拆一遍为什么选 Dwarf Fortress最小可运行的环境接口怎么搭Agent 循环里哪些设计会决定成败以及从“能跑一回合”到“能稳定运营几年”之间到底还差多少工程能力。1. 为什么拿 Dwarf Fortress 当 LLM Agent 试验场1.1 表面是玩游戏实际是在做环境接口化很多人理解“用 LLM 玩游戏”会直接带入游戏外挂的思路模型看到屏幕输出按键指令然后游戏角色动了。但这个思路在 Dwarf Fortress 面前基本失效。先不说图形界面信息量大单说游戏逻辑你不可能让模型盯着像素画面去理解“当前矮人为什么心情不好”“为什么酒精库存告急”“为什么地下挖到了危险区域”。这些信息散落在几十个界面、几百个实体和复杂的关系网络中靠截图根本无法传递。所以做这类项目第一件事从来不是给模型写 prompt而是设计一层“环境接口”。这层接口负责把游戏世界的复杂状态变成模型能理解的文本或结构化数据同时把模型的决策变成游戏能执行的指令。一旦你把这个接口做出来你其实已经做了一件比“让 AI 玩游戏”更通用的事把任意一个复杂系统改造成一个 LLM 可以操作的环境。这正是 LLM Agent 落地到真实业务时最常遇到的形态——不是模型直接控制真实世界而是通过接口层理解状态、发出操作、接收反馈。1.2 复杂度和文本化为什么是必要条件选 Dwarf Fortress 不是因为它简单恰恰是因为它够复杂。先看它的状态空间。一个正常运行几年的存档里可能有几十个矮人、上百件物品、复杂的任务队列和不断变化的环境事件。想用传统脚本硬编码玩法几乎不可能覆盖所有情况。而 LLM Agent 的核心能力恰好是不依赖穷举规则而是根据当前状态做开放式的推理和规划。再看它的信息形态。虽然 Dwarf Fortress 有图形版但它的底层数据本身就是强结构化的物品有类型、状态、位置矮人有技能、需求、情绪任务有阶段、材料、目标。这些信息天然适合被导出成 JSON、文本摘要或表格喂给 LLM 做推理。换句话说你不需要做“视觉识别”这一步只需要做“状态抽取”。这就有意思了。它有点像把真实世界里的 ERP 系统、监控系统或运维后台暴露给 Agent数据是结构化的但体量大、关系复杂、存在长期依赖单纯靠“上一帧的状态”决策根本不够。还有一个被低估的点Dwarf Fortress 是单机游戏失败成本低。你可以随时重开也可以保存存档做回放。对 Agent 开发来说这是几乎完美的实验环境——你能把一次决策失败变成一次可复现的实验而不是生产事故。1.3 适合谁不适合谁这个项目不是所有人都该去复刻。如果你是刚接触 LLM 开发我更建议先从工具调用、RAG、单轮问答这类项目起步。Dwarf Fortress Agent 需要你同时处理游戏状态抽取和环境封装LLM 上下文管理与长期记忆工具调用的参数校验和错误恢复长时间运行时的稳定性与日志回放游戏机制本身的深度理解。这些能力叠加在一起已经不是入门项目了。但反过来说一旦你能把一个 Dwarf Fortress Agent 从零搭到能自主运营几年你学到的不是“怎么玩游戏”而是“怎么把一个复杂系统可靠地暴露给 LLM”这个能力在 Agent 工程化里非常稀缺。注意如果你只是想快速体验让模型做决策完全不必从 Dwarf Fortress 开始。先做一个五子棋或者回合制小游戏跑通“状态读取—模型决策—动作执行—结果反馈”这个最小循环再逐步增加复杂度。2. 先搭一个“可被 LLM 理解”的游戏环境2.1 你不能让 LLM 看像素你得先做状态层我在看这类项目时最关注的第一件事是他们的状态层是怎么设计的。也就是说当模型需要做决策时它到底能“看到”什么。对 Dwarf Fortress 这类游戏最合理的方式是引入一个外部接口框架。社区里常见的选择是 dfhack它提供一套对游戏内存数据的读写能力也能通过脚本导出结构化数据。你可以把游戏内当前时间、矮人列表、库存、任务队列、警告事件等导出成 JSON 或者摘要文本。这层状态抽取的理想输出长什么样我用一个简化示例来展示{ game_date: Mid-Summer, Year 2, fort_name: Torchclod, population: { total: 23, idle: 5, mining: 3, crafting: 4, fighting: 1 }, resources: { food: 342, drink: 128, wood: 56, stone: 210, metal: 18 }, active_warnings: [ goblin_siege_detected, food_stockpile_nearly_empty, noble_dissatisfied ], recent_events: [ Urist McCraftsman cancelled task: no material, A dwarf became unhappy after sleeping without a bed ], pending_orders: [ build 4 beds, brew 20 drinks, train military squad ] }我不建议一次性把完整游戏状态都丢给模型。LLM 的上下文窗口是有限的而且信息过载会导致决策质量下降。更好的做法是像人玩策略游戏一样先看“全局摘要”再按需展开细节。这个设计指向一个核心原则给模型的信息不是越多越好而是越可决策越好。2.2 最小闭环状态快照 - 行动 - 结果反馈环境接口的核心是一个闭环它由四部分组成状态快照把游戏当前关键信息抽成结构化文本模型推理LLM 根据状态输出下一步要执行的动作动作执行程序把模型输出解析成游戏指令并执行结果反馈执行后把“发生了什么”再次压缩成文本反馈给模型。看起来非常简单但实际落地时几乎每个环节都有坑。状态快照的难点在于压缩你不能把所有矮人的完整属性都发给模型只能挑当前决策最相关的。比如正在处理“矮人不开心”这个问题你就要把心情、需求、睡眠质量、社交状态抽出来如果正在处理库存告急就要把原材料、生产队列、可用工作台抽出来。模型推理的难点在于动作空间设计你不能让模型完全自由地用自然语言输出“去挖矿”“去造床”“去找怪物”这太模糊了。你需要定义好动作列表让模型做“选择题 参数填充”。比如{ thought: 食物库存告急需要派更多矮人从事农业和烹饪同时检查是否因为季节导致作物停产。, action: assign_task, params: { task: farming, dwarf_ids: [3, 7, 12], priority: high } }动作执行的问题就更实际了模型输出的 dwarf_id 可能不存在任务可能被取消材料可能不足。这些异常不能简单报错而要把“为什么失败”重新反馈给模型。2.3 环境封装层至少要做四件事从工程角度看环境封装层决定了整个 Agent 能不能稳定跑下去。我总结下来至少要做四件事第一状态抽取。从游戏底层数据中导出结构化状态并按当前目标做摘要。第二动作白名单。定义 Agent 可以执行的操作集每个操作都要有明确的参数 schema而不是让模型自由发挥。第三结果解释器。把游戏执行后的响应翻译成模型能理解的反馈包括“动作成功”“部分成功”“失败原因”“产生了哪些新事件”。第四存档与回放。每次决策前导出状态快照每次动作后记录结果。这不仅是调试工具也是后续评估 Agent 表现的基础。这里有个很容易忽略的点Agent 的环境接口不只是一个“中间件”它实际上是你对问题域的理解边界。你在状态里漏了什么模型就永远看不到什么你在动作里禁了什么模型就永远做不了什么。所以环境接口的设计质量直接封死了 Agent 的能力上限。3. Agent 的核心循环从“模型对话”到“目标管理”3.1 一个循环里到底发生了什么当我看到一个 Agent 项目说自己“让 LLM 玩 Dwarf Fortress”我最想看的不是 prompt而是它把一次“决策循环”设计成了什么样。从最简单的版本开始一个循环可以拆成五步读取当前状态快照把系统提示词、长期目标、短期状态、最近历史记录一起发给 LLM让 LLM 输出推理过程和结构化动作 JSON解析 JSON校验参数调用游戏执行器把执行结果、游戏事件、新的状态摘要追加回对话历史。这个循环看起来和普通聊天差不多但有一个关键差异它不是“一轮问答”而是“一轮多步目标管理”。LLM 不能每轮都从头思考“我要干什么”而是要在长期目标、短期状态、历史反馈之间做权衡。这引出一个非常实际的挑战如果每轮都把完整历史发给模型上下文很快就会爆炸。3.2 上下文窗口不是用来刷新的关键在压缩与摘要Dwarf Fortress 的一个游戏内年度可能要执行成百上千次决策。假设每次交互产生 500 token 的历史记录跑到第三年就是几十万 token。这已经远远超出普通上下文窗口的承受范围。我在实践中更推荐的方案是分级记忆短期记忆保留最近 10 到 20 轮决策包含每轮的动作、反馈和状态快照摘要。中期摘要每隔一定轮次比如每 20 轮生成一份“最近发生了什么”的摘要记录关键事件、资源变化、任务完成情况。长期知识库把游戏中的重要事实、重大警告、长期目标持久化到外部存储里比如“第三年冬天前必须储备 300 份食物”“地下三层发现了危险生物”。这些信息不一定每轮都发给模型而是按需检索。这个设计有点像人类玩策略游戏的记忆方式你不会记得每一帧画面但你记得“上季度发生过什么”“当前最重要的目标是什么”。上下文管理不是尽量塞更多信息而是让模型在有限的上下文中始终掌握最关键的信息。3.3 工具调用和参数解析把自然语言翻译成游戏指令这部分是最容易出问题的地方也是 LLM Agent 工程化里绕不开的坎。模型输出的是自然语言和结构化 JSON但游戏执行器需要的是精确指令。比如模型想“让矮人 Urist 去挖矿”实际游戏指令可能需要指定具体矿道坐标、指定任务优先级、处理任务取消条件。这个翻译过程不能完全依赖模型自觉环境层必须做严格的 schema 校验。一个常见做法是给每个动作定义一个 JSON Schema{ action: assign_mining_task, params: { dwarf_id: 3, tile_coord: {x: 12, y: 34, z: 1}, priority: high } }如果模型输出的参数类型不对、没有对应矮人、坐标超出地图范围执行器必须返回“参数校验失败”和具体原因。这样 LLM 可以基于错误信息修正自己的输出而不是硬着头皮执行一个无效动作。这背后是一个更大的观点Agent 的可靠性一半靠模型的能力另一半靠外围工程的“防御性设计”。模型可能犯错但工程体系要能识别错误、解释错误并让模型得到下一次尝试的机会。3.4 为什么不能只靠提示词要设计记忆和反思有些项目会把所有希望寄托在系统提示词上比如写“你是一个 Dwarf Fortress 专家要合理分配资源”。但真实运行起来会发现模型面对一个复杂世界时经常出现目标漂移。所谓目标漂移就是模型在前几轮还能记得“我要保证食物充足”跑到第 30 轮它可能已经完全投入到某个矮人的社交问题里忘了整体资源规划。这不是模型智力不够而是长时程任务里目标记忆需要由外部系统来维护。所以我建议在 Agent 循环里增加一个“反思步”这不是可选的而是必要的。具体做法是每 N 轮让模型停下来输出一份复盘当前长期目标是什么最近发生了哪些关键变化我的哪些决策是有效的哪些无效未来 10 轮我应该优先做什么。然后把这份复盘存储到长期记忆里在后续循环中作为“中期摘要”的一部分重新注入上下文。这样做的好处是即使模型在某一轮上下文里丢失了全局视角复盘机制也能把它拉回来。4. 从跑通到持续运行工程化才是最难的4.1 状态与内存的边界、隔离和持久化当你让 Agent 跑十几分钟后很多问题会开始浮现。最典型的一个是Agent 的“记忆”和游戏的“真实状态”产生偏差。模型的短期摘要可能写着“食物充足”但游戏里食物已经被耗尽了。这个偏差的产生原因是多方面的可能是摘要生成滞后可能是模型推理错误也可能是状态抽取只做了局部快照。解决这个问题的思路是每次决策前都要用最新的状态快照校准模型对世界的认知。不要把模型的记忆当作事实来源游戏状态才是唯一的事实来源。模型历史里的摘要只能当作辅助信息一旦与现实冲突以环境层的状态为准。这类问题的本质是数据一致性问题。长期运行的系统里内存、存储、外部状态之间需要明确的边界和同步策略。LLM Agent 也不例外。4.2 行动失败、非法输入、目标漂移错误排查链路当 Agent 运行异常时我习惯按下面这个顺序排查看现象是报错、卡住、无响应、还是输出异常决策。看状态快照模型在做决策时看到的环境数据是否完整、准确。看动作解析模型输出的参数是否通过 schema 校验执行器是否正常执行。看历史记录上一轮反馈是否被正确追加到模型上下文。看模型输出是否出现了格式错误、JSON 截断、动作不在白名单内。这套链路与普通后端服务排障很像但有一个新增环节你要理解模型是在什么上下文基础上做决策的。所以日志里至少要同时记录“模型看到的输入”和“模型输出的结果”不能只记录结果。如果模型反复输出同一个非法动作优先检查参数 schema 是否写得太复杂或者动作白名单是否覆盖了足够多的常见决策。如果模型总是忘记长期目标优先检查复盘机制是否真的在持续生效而不是只在调试时开一次。4.3 日志、回放、评估没有这三个就别谈迭代一个 LLM Agent 项目能不能持续改进取决于你有没有能力回答三个问题上次跑挂之前发生了什么这个策略比上个策略好多少模型某轮决策失败是真失败还是偶发波动为了回答这三个问题你需要三样工程基础设施日志、回放、评估。日志要记录每一轮完整的输入输出包括状态快照、模型响应、执行结果、错误信息。回放是指你能把某段历史重新跑一遍观察不同模型或不同 prompt 下的表现差异。评估指的是为 Agent 定义一组可量化的指标而不是只看“游戏有没有继续跑”。对于 Dwarf Fortress Agent我建议至少跟踪这些指标指标说明存活天数游戏内持续运营天数是最直接的长期指标矮人数量变化判断 Agent 是否在维持人口发展资源自给率食物、饮品、木材、石材是否长期稳定任务完成率计划执行的行动中真正完成的比例无效动作率参数校验失败或执行失败的比例目标漂移次数通过复盘记录统计长期目标被遗忘的频率这些指标并不复杂但它们决定了你是在“写一个能跑的小玩具”还是在“做一个能迭代的 Agent 系统”。4.4 长期运行要做的事稳定性、重试、安全护栏最后是稳定性问题。LLM 生成的输出天然有不确定性所以长期运行时你必须假设“每一轮都可能失败”。我建议至少做以下几件事输出格式校验与重试模型输出解析失败时带着错误信息重试 2 到 3 次而不是直接崩溃。动作安全护栏对可能导致“不可逆后果”的动作比如删除储存区、解散任务、破坏建筑设置额外确认机制。例行存档每隔固定游戏日期或者固定轮次做一次存档快照异常时可以恢复到最近检查点。熔断机制如果连续多次执行失败或模型连续输出无效动作Agent 应该进入暂停状态等待人工介入。这已经和普通业务系统的工程化思路非常接近了。区别只在于这里的“外部依赖”变成了一个概率性输出的模型。这也是为什么我认为 Dwarf Fortress Agent 是一个极好的工程训练场。一个长期主义的观点如果你的目标不是“玩出成绩”而是“把 Agent 工程化能力练熟”那么 Dwarf Fortress 的随机性和复杂度恰好能逼你面对那些真实 Agent 系统一定会遇到的边界问题。这些问题很难在教程项目里遇到但在真实生产环境里它们就是决定系统能不能长期跑下去的关键。5. 把“让 Agent 玩 Dwarf Fortress”变成你自己的经验5.1 一个可复用的四层框架当你把项目拆开会发现适合迁移到其他 LLM Agent 项目的不是“Dwarf Fortress 的具体玩法”而是一个四层框架第一层是环境层。它负责把复杂系统变成模型可感知的状态同时把模型决策变成实际动作。它在任何时候都是唯一的“真实来源”。第二层是决策层。它负责读取当前状态结合短期记忆和长期目标输出结构化的行动方案。这一层可以替换成任何模型甚至不同推理策略。第三层是记忆层。它负责管理短期历史、中期摘要和长期知识库让模型在有限上下文里始终拥有足够的决策信息。第四层是评估层。它负责记录日志、计算指标、生成回放让每一次运行都能被分析和比较。这四层不绑定任何具体游戏。你可以用它对接一个监控告警系统也可以对接一个电商运营后台或者一个文档处理流水线。真正通用的东西从来不是某一段代码而是这套“从复杂环境到可控 Agent”的方法。5.2 从零开始的路径建议如果只看标题觉得心动我更建议按下面这个顺序逐步推进第一步先做一个“最小状态快照 单轮决策”的 Demo。不追求复杂动作只让模型看到当前资源数据输出一个简单的行动建议然后手动执行。第二步把动作执行器做出来。至少支持四五个常用动作比如分配挖矿任务、设置工作优先级、检查库存、查看矮人状态。跑通“状态到行动”的闭环。第三步加上循环和反馈。让 Agent 能连续执行多个步骤并把每轮反馈追加进上下文。先不追求稳定能跑 20 轮不崩溃就算成功。第四步引入记忆和摘要。解决上下文爆炸和目标漂移问题开始设计长期记忆机制。第五步做日志和评估。给每个游戏日打日志记录关键指标跑几轮完整实验比较不同策略的效果。最后才是工程化加固存档回滚、异常重试、安全护栏、熔断机制让它能持续稳定运行。如果你之前没有玩过 Dwarf Fortress建议先手动玩一段时间至少理解什么叫“要塞运营”什么叫“行业流水线”什么叫“矮人心情系统”。不要跳过这一步——你对游戏机制的理解深度决定了你做出来的环境接口的质量。5.3 真正的长期价值把复杂过程做成可复用流程回到最初那个判断这个项目真正值得借鉴的不是“让模型玩一个复杂游戏”这个结果而是“把复杂过程做成可复用流程”这一整套方法论。当我们说“让 LLM 操作一个系统”时本质上是把人对系统的理解封装成模型可以消费的接口。你做环境层时需要理解游戏机制你做决策层时需要理解模型的推理边界你做记忆层时需要理解长时程任务的认知规律你做评估层时需要理解如何衡量一个 Agent 是否真的在进步。这四件事放到任何行业都成立。Dwarf Fortress 只是提供了一个低保真、低风险、高复杂度的沙盒。如果你正在规划自己的 Agent 项目我的建议是先别急着上复杂框架也别急着追求“一次跑通几十年”。先把环境层做厚把决策循环做稳把记忆和评估补齐。你会发现当这些基础能力都到位之后换一个游戏、换一套业务也只是换一层环境接口的事。这类项目的终极目标不是训练出一个“能通关的模型”而是沉淀出一套“让复杂系统变得可被智能体使用”的工程方法。当你开始用这个视角看问题时一个 Dwarf Fortress Agent 的价值已经远超游戏本身。