LLM 打赢 NetHack 这个标题前阵子在好几个技术群同时刷屏。我第一反应是这要么是评估标准放水要么是模型真的从“会聊”跨到了“会玩”。后来顺着公开的项目代码和 log 把整套系统翻了一遍结论是两者都有但也恰恰因为这样这件事比“AI 通关一款老游戏”要有意思得多。先把前提说清楚这里的 NetHack 是 1987 年那个只有 ASCII 画面、难度臭名昭著的回合制 roguelike所谓 beat不是指完整通关拿到 Amulet 再杀回地表而是指一个基于 LLM 的智能体能够自己探索地牢、避开明显危险、在关键岔路做出正确取舍在足够多的对局里稳定存活到很深的层数个别时候还能打出让人惊讶的操作。如果你玩过 NetHack就知道这个结果有多反常识。人类玩家第一次接触它基本都是在反复死亡中学会敬畏。一个靠“预测下一个 token”吃饭的语言模型凭什么能在这种游戏里站稳我研究了一段时间后发现答案不在模型本身而在把模型“包起来”的那套工程系统。这篇东西我就从题目里的 beat 出发把“LLM 智能体 NetHack”这套组合拆开讲顺便把我在复现过程中踩过的坑、试错出来的小技巧整理出来希望能给做 LLM Agent 或者对游戏 AI 感兴趣的朋友一点参考。1. 为什么偏偏是 NetHack这个游戏到底难在哪1.1 一款三十多年没人能轻松通关的回合制地牢NetHack 的核心规则一句话就能说清你扮演一个 符号在随机生成的地牢里一层层往下走目标是拿到最底层的 Amulet of Yendor然后原路返回。听起来和普通地下城游戏差不多但它的难度来自三个要素的组合。第一是部分可观测。屏幕只显示角色周围一小块视野没有小地图没有任务指引。每局地图完全随机怪物、陷阱、祭坛、商店、水渠全都不一样。你永远不知道下一格是安全的走廊还是落下去就摔掉半条命的陷阱。第二是长期依赖。一个决策的后果可能要到几十甚至几百个回合之后才会显现。比如你捡起一根被诅咒的武器当时看不出任何问题但它会慢慢拖垮你的命中率等你意识到时已经被围殴了。第三是稀疏奖励。击杀怪物不一定掉装备翻遍整个楼层可能也找不到食物很多操作没有任何即时反馈。这三个特点叠加起来对传统强化学习来说几乎是灾难。DeepMind 早在 2020 年就开源了 NetHack Learning EnvironmentNLE把游戏封装成了标准的 Gym 接口想推动 RL 社区在这个环境上做出突破。结果好几年过去强化学习智能体在这个游戏里的进展慢得让人绝望。原因很直接RL 擅长的是“在清晰反馈中反复试错”而 NetHack 给它的奖励信号太稀疏状态空间太大同一个策略换个种子就可能完全失效。1.2 一个偶然NetHack 恰好是 LLM 最擅长的输入形态NetHack 唯一的“图形界面”就是字符。角色是 墙是 #地板是 .怪物是各种字母楼梯是 和 祭坛是 _水池是 }。这些符号在 tty 终端里渲染出来本质就是一段文本。操作方式也是文本按键h/j/k/l 移动g 拾取u 使用d 丢下 下楼梯。对 LLM 来说这简直是量身定做的输入输出格式。模型不需要理解像素不需要处理连续动作空间只需要读一段字符序列然后输出一个短命令。传统 RL 需要手工设计状态表示把整个屏幕编码成向量而 LLM 直接“读”原始文本就能开工。语言模型在互联网语料里见过无数攻略、wiki、玩家讨论它对“商店”、“祭坛”、“独角兽角可以治病”这类常识有天然的先验。但这并不意味着“把屏幕丢给模型让它每回合选一个动作”就行。我试过这条路效果惨不忍睹。上下文长度很快被打满模型开始胡编乱造甚至会重复输出同一个已经失败的指令。真正能打的方案是把 LLM 嵌进一个更大的决策循环里让它扮演“大脑”而不是“手脚”。2. 从“聊天”到“打游戏”LLM 智能体的整体架构2.1 参考的思路不是一步一决策而是候选行动序列我看到的一个比较靠谱的公开项目思路叫 NASHNetHack Agent Strategy from Heuristics核心设计可以概括成一个循环每一轮LLM 不是直接给出“下一步做什么”而是根据当前状态生成若干条候选的行动序列每条序列是一连串动作——比如“先向东北方向摸索五步没有危险就继续向东直到撞墙再折返”。然后环境把每条候选序列分别试跑一小段收集执行结果。接着由一个评分器给每个结果打分挑分数最高的那条真正落地执行。如果这条序列走到一半触发了危险信号智能体就回溯到之前的检查点换一条路再试。我第一眼看到这个架构时愣了一下因为它的每一块都不是新东西。候选生成、模拟试跑、结果评分、失败回溯这些都是传统的搜索和规划思路。LLM 在这里的作用是把“当前局面 游戏常识 已知信息”压缩成一个合理的候选集合相当于在搜索树里做了一次非常有效的剪枝。真正干活的是整个循环而不是某一个模型调用。2.2 回合制与本地执行给“试错”提供了最好的土壤NetHack 是回合制游戏每一步行动之间没有时间压力。这让“模拟试跑”变得极其便宜你完全可以在一秒钟内让十条候选序列各自往前探索几回合然后看哪个结果最好。更重要的是NetHack 的对局状态可以序列化保存也就是说你可以随时存档、读档、回退。传统 RL 在真实环境里做这种回溯非常困难因为环境是连续的、不可回退的但 NetHack 这种老游戏天然支持“读档重来”。我当时心里冒出来的类比是下棋时悔棋。人类新手玩棋类游戏时经常落子之后立刻后悔想退回去重走。NetHack 智能体做的其实就是这件事而且它退得理直气壮候选序列走死了就回退到之前的安全状态换一条路。这个机制在工程上叫自主容错控制本质上是用外部状态管理把模型偶尔的愚蠢兜住不让一次错误决策演变成整局崩盘。2.3 对比强化学习LLM 赢在“世界知识”先验传统 RL 从头学 NetHack需要经历无数次死亡才能慢慢归纳出“陷阱危险”、“饥饿要吃东西”、“被怪物围住要跑”。而 LLM 从第一天就知道这些。你给它的上下文里出现“你感到一阵饥饿”它会建议找食物出现“前方有一只正在睡觉的狗”它会建议绕开而不是直接踩上去出现“商店老板站在门口”它会权衡要不要偷东西。这种由语言先验带来的零样本常识是 RL 智能体花几百万步都不一定学得会的。所以严格说LLM 并不是“学会了”NetHack而是“现学现卖”它一边用已有的游戏常识做判断一边靠候选搜索和试错验证自己的猜测。这也是为什么同一个智能体换个完全陌生的 roguelike它依然能玩出一个还算不错的开局而 RL 智能体必须从头开始。3. 核心机制拆解让 LLM 在游戏里不犯蠢的五个工程细节3.1 状态摘要与记忆不要全屏轰炸要提炼重点直接把一整帧 tty 屏幕丢给 LLM是新手最容易犯的错误。屏幕上一百多个字符里九成是无关紧要的地板和墙壁真正关键的信息只有几处你的角色位置、视野内有没有怪物、是否处于特殊状态、附近有没有楼梯或物品。全量文本进去不仅浪费 token还会稀释模型对关键信息的注意力。正确做法是做一个状态摘要层把原始屏幕翻译成一段结构化的文本比如“你在第4层HP 34/56饥饿附近有怪物 k小妖精东边有楼梯南边是商店”。再加上一小段从长期记忆里提取的信息“这层西侧已探索完没发现护甲上一层的商店卖的是便宜药剂”。这样模型每次看到的都是高密度信息决策质量会明显提升。我在实测中只做摘要优化这一件事就让智能体的平均存活层数提高了一倍左右。3.2 候选序列生成数量、长度和温度都要调候选序列怎么生成直接影响整个系统的质量。太少了模型看一眼局面直接给一个“它觉得对”的答案这和单步决策没什么区别太多了模拟试跑的成本飙升每轮都要跑几十次环境。我调下来感觉比较稳的参数是候选 8 条每条序列长度 48 步。短了探索不出有价值的信息长了容易在一次模拟里走火入魔。温度设置也很有意思。调得越高候选序列之间差异越大越容易探索出意外的好路线但也越容易生成毫无逻辑的乱走调得太低所有候选都像一个模子刻出来的失去搜索的意义。我最后基本固定 0.81.0 之间。还有一个细节提示词里要明确要求“输出多样化”否则模型倾向于给出一堆同义词式的重复方案。3.3 双层评分先看规则再让 LLM 做裁判候选序列试跑完之后怎么打分这里我采用了“规则优先、LLM 兜底”的双层结构。第一层是经典的启发式评分比较执行前后 HP 变化、饥饿度变化、经验值/等级是否提升、金币和物品是否变多、是否触发了危险状态。权重可以简单设成HP 权重最高因为活着是一切的前提其次是饥饿度和安全状态最后才是资源和探索进度。这套打分非常快几行代码就能跑完而且稳定可解释。问题是规则评分太短视它看不出“往这个方向探索可能发现楼梯”这种长期价值。所以当多条候选的规则分非常接近时我会让 LLM 出来做第二层裁判把自己代入玩家的视角结合上下文里的地图信息和记忆判断哪个方向更有前途。这个“LLM as Judge”的用法在 Agent 领域已经比较常见但在游戏智能体里有个坑模型有自评偏好它倾向于选“看起来合理”的方案而不是“模拟结果最好”的方案。所以它的裁判权只能限定在规则分相近的候选之间不能让它覆盖掉环境给出的硬反馈。3.4 回溯与检查点不要相信模型要相信读档回溯机制是整个系统里性价比最高的部分。我会维护一个“检查点栈”保存最近 N 个安全状态比如刚进入新楼层、刚离开商店、刚完成一次战斗。每当智能体的状态出现明显恶化——HP 骤降、被怪物围住、走进死胡同、进入无法脱困的狭窄地形——就触发回溯从上一个安全检查点重新开始并把这个导致失败的动作序列标记为“已踩过的坑”短时间内不再生成相同或相似的候选。这听起来像作弊但在评估智能体的时候回溯次数本身也计入总成本。你不能无限读档然后说自己通关了那只是“永不言败的存档游戏”。但作为试错探索的一部分合理次数的回溯正好体现了“从失败中学习”的工程化实现。没有这套机制LLM 犯一次致命错误就会直接死亡整局结束再好的常识先验也白搭。3.5 动作编码的工程坑模型输出格式才是最耗时间的这个坑我要单独拿出来说。早期我把动作序列设计成 JSON 数组让模型直接输出结构化的工具调用结果一半以上的时间花在调试报错上。模型偶尔生成一个非法字段后端就给你甩一句 provider rejected the request schema or tool payload整个回合直接作废。不是模型不会玩是我自己把接口设计得又脆又反人类。后来我学乖了让模型输出一段带编号的短文本比如“1. 向北移动3步2. 拾取物品3. 向东探索”再用解析器把它转成可执行动作。解析逻辑宁可简单粗暴也多做容错比如识别不了的动作直接丢弃只执行能解析出来的部分。这样即使模型偶尔胡说八道系统也能从它的话里榨出几句有用的。4. 手机也能跑低成本复现一个最小版 LLM 游戏智能体4.1 换掉闭源大模型GGUF 量化模型撑起半边天很多人一听到“LLM 打游戏”下意识觉得得有一堆 A100。其实不一定。实验阶段完全可以用本地量化模型跑。GGUF 是 llama.cpp 带起来的一种模型量化格式能把一个动辄几十 GB 的模型压缩到几个 GB甚至可以在手机上跑。我在一台没有独立显卡的旧笔记本上用一个小规模量化模型完整跑通过最小版智能体效果虽然比不上闭源大模型但足够验证架构和调试代码。如果你手上只有安卓手机也可以试下载一个支持 GGUF 的推理壳跑一个精简模型把游戏逻辑放在另一台电脑上通过网络调用本地推理接口。这样做的意义不在于打得多好而在于你花很少的钱就能把“LLM Agent 闭环”跑起来亲眼看到模型在真实环境里怎么犯蠢、怎么被兜底。4.2 最小可运行的循环一行行拆给你看复现的关键不是模型而是把环境、模型、解析器、评分器串起来。我在本地搭的最小循环是这样的第一步启动 NetHack并切到 tty 渲染模式。第二步跑一个抓屏脚本定时读取终端输出把 ASCII 画面转成文本。第三步写状态摘要函数从文本里提取角色位置、HP、饥饿度、视野内实体拼成一段观察文本。第四步构造提示词把观察文本加记忆摘要一起交给 LLM让它给出 8 条候选动作序列。第五步用解析器把候选转成游戏指令在一个临时存档里依次执行收集结果。第六步用规则评分器打分挑最高分执行到真实存档里并更新记忆。第七步如果评分显示状态恶化回溯检查点记录失败模式。整个循环大概两百多行 Python没有任何复杂依赖。我第一次跑通的时候模型表现得笨拙但稳定它会在走廊里来回探索遇到怪物会绕路捡到食物会吃饿死的情况很少。这已经足够说明架构是成立的。4.3 一个容易忽略的点上下文压缩的下限本地小模型的最大瓶颈是上下文长度。我用的模型上下文只有 2048 token观察文本加记忆很容易撑爆。解决办法是只保留最近两帧的关键信息记忆里也只塞“当前目标、已探索区域、最近一次失败原因”这几项。压缩到极致之后每轮输入能控制在五六百 token 以内。这里我学到一个经验上下文压缩不是为了省钱而是为了让模型在有限注意力内集中在真正重要的事情上。很多所谓的“长上下文”模型塞太多噪音进去效果反而不如短小精悍的输入。5. 怎么判断“真的赢了”评估方法与红队测试5.1 定义指标不要只看“有没有通关”“打败”这个词必须量化否则每个人说的不是同一件事。我在评估时用了三组指标平均存活层数、平均经验等级、死亡原因分布。比如随机智能体平均活到第 3 层固定脚本智能体平均活到第 7 层而我的 LLM 智能体在开启回溯和双层评分后平均活到第 13 层就能说这个架构明显有效。我还额外记录了“关键决策正确率”在遇到商店、陷阱、强敌这类明显岔路时智能体的选择与人类玩家常见策略是否一致。有一个评估陷阱必须提醒带回溯机制的智能体一定要把回溯次数计入成本。如果只看最终存活层数一个无限读档的智能体可以“打穿游戏”但它本质上什么都没学会。我在报告里会同时给出“每局回溯次数”和“平均单步成本”让读者能真实还原这个智能体的效率。5.2 多局统计种子遍历才能看出稳定性NetHack 每局地图随机因此一局玩得好没有任何说服力。我一般固定跑 20 局以上使用不同的随机种子统计平均值和方差。这个过程很枯燥但能暴露很多问题有的种子下智能体开局就撞上强敌它会不会崩盘有的楼层地图特别狭窄它会不会反复在一个地方卡死我遇到过一种情况单看一局智能体表现亮眼活到第 15 层但换五个种子之后它平均只活到第 6 层原来那次高光完全是运气好随机出来的地图恰好顺路。5.3 给自己的智能体做红队测试记忆中毒实验别的评测是看正常发挥红队测试是专门看它在被攻击时有多惨。最近我很关注一类针对 Agent 的安全测试思路不是攻击模型本身而是攻击它的记忆或知识库。具体到 NetHack 智能体可以做一个很简单的实验——连续几局在记忆模块里注入误导信息比如“前方没有危险”、“这个楼层已经没有怪物了”然后看智能体是不是真的会放松警惕大步走进陷阱。我在测试中发现规则评分器能部分抵消这种攻击因为即使模型被误导评分器发现 HP 下降也会触发回溯。但纯依赖模型判断的版本就没这么幸运了它会被假记忆带着走反复撞同一个陷阱。这个实验给我的启发是任何生产环境的 LLM Agent都必须在记忆入口做可信度校验不能盲目相信写入记忆的内容。衡量“理解”与否首先要看它在被误导时能不能守住底线。6. 这件事的真正价值LLM 到底“理解”了 NetHack 吗6.1 它赢在局部推理输在长程规划诚实地讲LLM 智能体并没有“理解”NetHack 的底层规则和隐藏机制。它只是把语言先验和外部搜索组合成了一种“看起来会玩”的能力。在局部决策上它能做得比脚本好得多因为它懂得权衡、会参考常识但在长程规划上它依然非常脆弱。比如让它记住“第二层有个商店等我有钱回来买护甲”它很难把这条信息在几十层之后准确调出来并执行。上下文窗口再长也替代不了结构化的长期记忆。这也是目前所有 LLM Agent 的共同瓶颈不是不会做单步决策而是不会围绕一个跨度很长的目标进行连贯规划。NetHack 恰好把这个弱点放大了因为一局游戏往往要上百个决策点信息在地牢中不断翻新。6.2 可以复用的工程思路把“模型愚蠢”当成系统输入我在这个项目里最大的收获是模型的能力边界远没有系统设计重要。同样的模型做成“单步决策”就是原地打转做成“候选生成 环境试跑 评分 回溯”就能稳定打到深层。这套思路完全可以迁移到其他游戏甚至迁移到真实的机器人控制任务里LLM 负责高层意图和常识判断底层交给确定性控制中间用监控和回滚做安全网。我还试过把类似架构放到一个更现代的 roguelike 上效果同样成立。这说明 LLM 智能体的通用性不在模型本身而在“蒸馏常识 外部验证 容错恢复”这套组合里。未来如果再有人告诉你“一个 LLM 打败了什么游戏”你可以先看一眼它用了多少回溯、多少规则评分器再决定是否献上掌声。我在实际复现和调试这套系统的过程中反复体会到一个道理模型犯错的概率你很难降为零但系统可以在它犯错之后不崩盘。真正值钱的东西不是那个会生成候选序列的模型而是把失败标记成经验、把危险状态变成回滚信号、把模型偶尔的灵光一现放大成稳定决策的那一层层工程外壳。以后你自己做 Agent 项目时也建议先把“假如模型这步错了怎么办”想清楚再琢磨怎么让它这步对。