
MUD 这种以纯文字描述构建世界的“老古董”游戏最近反而成了 AI Agent 开发者的新试验田。没有图形界面、没有标准化 API只有一大堆半结构化的文字输出玩家靠敲命令推进一切。让大模型替玩家刷 MUD 等级表面看是游戏挂机问题实际上是你把 LLM 接到真实外部环境后必然要面对的感知、决策、执行、异常恢复这一整条链路。很多人以为让 AI 玩 MUD只要把每一轮游戏文本都丢给模型让它输出“下一步指令”就行。真实跑一次就会发现这种“每一步都问模型”的模式撑不了几分钟延迟高、token 消耗快、模型经常输出无效命令哪怕是最简单的打老鼠任务也会在某个循环里卡死。DeepSeek Harness 这类 Agent 执行框架真正解决的就是“模型决策”和“环境执行”之间的断层。这篇文章会从工程拆解的角度带你走一遍最小可复现实验用 DeepSeek 或任意 OpenAI 兼容模型作为大脑用一套 Harness 风格的执行器作为手脚用一个 MUD 连接器收发文字最终让 AI 生成动作脚本并执行。整个过程不需要图形界面也不依赖具体游戏客户端重点是把 Agent 的骨架搭出来。先给结论如果你只是把模型 API 接入项目却没有设计工具边界、动作白名单和执行循环AI 打 MUD 基本等于空转真正让“自动战斗脚本”可控的关键在于把模型的自由文本输出限制为一套可校验、可回滚、可监控的动作原语。1. 这篇文章真正要解决的问题很多刚接触 AI Agent 的人会有一个误解让 AI 做事只要提示词写得足够好就行。放到 MUD 自动战斗这个场景里这个误解会被放大得非常明显。你让模型输出“kill rat”模型确实能输出但它不知道这条命令是否真的发到了服务器不知道攻击有没有生效不知道血量是不是已经太低。换句话说模型只负责“说”不负责“做”更不负责“确认做完了”。MUD 自动战斗看起来是个娱乐场景实际拆开是三层工程问题感知层从 MUD 返回的大段文本里提取当前房间、血量、魔法值、敌人状态。决策层根据当前状态选择攻击、施法、使用道具、逃跑还是休息。执行层把模型给出的动作转换成 MUD 协议命令发送出去并读取结果。这三层缺一不可。DeepSeek Harness 类框架的价值不是让模型变得更聪明而是把这三层粘合在一起形成一个可以循环运转的 Agent 系统。还有一个容易被忽略的问题成本。如果每一步都调用大模型刷一个等级可能消耗几十万 token而且每步等待好几秒。更合理的做法是让 AI 先生成一段“战斗脚本”再由轻量执行器按照脚本循环执行模型只在状态异常或脚本执行失败时介入。这个设计思想与 Harness 中 Skill 和 Tool 的分工是一致的。什么样的人适合读这篇文章正在搭建 AI Agent、做自动化测试、研究游戏 AI、或者想从“聊天机器人”走向“自动化操作”的开发者都可以从中找到可借鉴的方案。如果你期待的是绕过游戏服务条款、抓包改数据之类的“外挂方案”那这篇文章不适合你。下文所有内容都基于一个前提你拥有目标 MUD 服务器的合法使用权限或只在本地测试环境验证。2. 核心概念DeepSeek Harness、Skill 与 MUD 自动战斗不同社区项目对“Harness”的理解并不完全一致有的把它叫作 Agent 框架有的叫执行器有的叫工作流引擎。为了不绑死在某个具体发行版上我们先统一概念边界。概念通俗解释对应到 MUD 自动战斗模型负责理解和生成文本的大语言模型判断“该打哪个怪”“血量太低要不要撤退”Harness连接模型和外部工具的调度框架把模型输出的动作指令变成真实的网络操作Skill可以被模型调用的技能包或脚本定义战斗脚本的输入输出格式、动作范围Tool / 插件具体的外部功能接口MUD 连接器负责收发游戏文本Agent模型 Harness Skill 记忆的整体系统能自己刷等级的虚拟玩家这段拆解非常重要。很多人看 DeepSeek Harness 的时候容易把它误认为是一个“更强的模型”。实际上Harness 负责的是工程控制模型说“我想攻击老鼠”Harness 决定这条指令能不能执行、怎么执行、执行后如何把结果回传给模型。放到 MUD 场景SDK 或框架内部可能不叫 Harness但只要它具备“模型调用 工具注册 循环执行”这三个能力本质就是同一套模式。明白了这层你换任何框架都只是换配置文件和 API 写法而已。Skill 是这套体系里最容易理解错的概念。Skill 不是让模型写任意 Python 代码而是给模型划定一个可操作空间。比如在 MUD 战斗 Skill 里模型只能从attack、cast、use、rest、flee这几个动作里选。执行器只认这些动作模型说什么“输出一段任意代码”都不会被执行。这个约束是保证 Agent 安全运行的第一道防线。小结论学 Agent 不要先去背某个框架的命令行参数先理解“模型负责决策、Harness 负责执行、Skill 负责限制行动边界”这三层关系。后面所有代码都是围绕这三层展开的。3. MUD 为什么适合验证 AI AgentMUD 全称 Multi-User Dungeon是一种通过文字描述房间、怪物、道具和玩家状态的联机游戏。玩家输入look查看周围输入kill rat攻击老鼠输入flee逃跑。它诞生于个人电脑还很原始的年代却因为交互形式上天然适配文本模型在 AI Agent 时代重新有了用武之地。为什么说 MUD 是 AI Agent 的绝佳训练场核心原因是它的输入输出都是文本不需要图像识别、不需要 GUI 解析大模型可以直接理解。但与此同时它又没有简单到可以靠固定脚本一路通关怪物会反击、房间之间会迷路、状态会有随机变化。这个“不确定的文本世界”恰好能测试模型在动态环境中做决策的能力。如果把“AI 刷 MUD 等级”当成一个技术任务可以拆成四个阶段登录与初始化连接服务器进入角色获取初始状态。观察状态读取当前房间描述、角色血量、魔法值、敌人列表。执行战斗根据状态生成动作序列循环攻击目标。异常处理血量不足时撤退休息迷路时回溯战斗结束后寻找下一个目标。传统脚本能完成前三个阶段但遇到异常情况基本就崩了。AI 带来的增量价值在第四阶段它可以理解“当前状态不对劲”然后重新制定策略。比如模型发现角色血量只剩 10%就会自动生成包含flee和rest的新脚本而不是继续盲目攻击。这也是本文选择 MUD 的原因它不是最强的生产场景却是最能说明 Agent 核心机制的最小范例。你在这个场景里跑通的能力可以直接迁移到文本客服自动处理、运维日志巡检、浏览器自动化等实际项目中。4. 环境搭建与前置条件开始写代码之前先把环境准备好。本实验不依赖任何商业游戏客户端推荐使用本地或私有部署的开源 MUD 服务端。确保有合法测试账号不要在正式运营服务器上做这种实验。建议环境如下Python 3.10 及以上支持 asyncio。一个可访问的模型接口DeepSeek API或本地部署的 OpenAI 兼容服务。一个 MUD 服务器地址通常是 Telnet 协议默认端口常见为 4000 等。网络连通性本机访问 API 和 MUD 服务器都必须通。先创建虚拟环境并安装依赖python -m venv .venv source .venv/bin/activate # Windows 下使用.venv\Scripts\activate pip install -U pip pip install telnetlib3 requests pyyaml如果你的 DeepSeek Harness 项目本身有官方安装脚本请以项目 README 为准。这里安装的telnetlib3是 MUD 连接器依赖requests是模型接口调用依赖pyyaml用于读取 Harness 配置。然后配置环境变量export DEEPSEEK_API_KEY你的API Key export DEEPSEEK_BASE_URLhttps://api.deepseek.com/v1 export DEEPSEEK_MODELdeepseek-chat export MUD_HOST127.0.0.1 export MUD_PORT4000 export MUD_ROLEtest_warrior如果你希望通过本地模型离线运行只需把DEEPSEEK_BASE_URL换成本地服务地址例如http://127.0.0.1:8000/v1。只要这个本地服务提供 OpenAI 兼容接口下面的代码不需要改动。Windows 用户需要特别注意目录权限。社区里经常遇到SetNamedSecurityInfoW failed (win32)错误多数是因为工作目录位于需要高级权限的路径或者程序尝试修改某个文件的 ACL。建议把所有实验代码放在用户目录下例如C:\Users\你的用户名\mud-agent不要放在C:\Program Files这类系统保护目录里。5. 核心流程拆解让 AI 生成战斗脚本而不是每条指令都问模型这是整个方案里最关键的设计判断。很多 Agent 教程教的是“ReAct 模式”模型观察、模型思考、模型行动循环往复。这种模式适合问题比较复杂、每步都需要重新推理的场景但不适合 MUD 自动战斗。MUD 战斗的特点是重复动作多、状态变化快、对延迟敏感。每打一只老鼠都要调用一次模型既不经济也不稳定。更好的结构是两层设计第一层策略生成器。模型根据角色状态和敌人信息生成一段动作脚本。第二层循环执行器。执行器按脚本里的动作原语循环执行不需要每步都问模型。第三层异常介入。当执行结果与预期不符或角色状态发生变化时再调用模型重新生成脚本。这样的 Agent 才是真正“能干活”的系统而不是一个每次都要想半天的聊天机器人。具体到 MUD 场景动作原语可以这样定义动作对应 MUD 命令说明attackkill 目标名攻击当前敌人castcast 技能名 目标名使用法术useuse 道具名使用道具restrest / sleep休息或睡觉恢复fleeflee从战斗中逃跑模型不直接输出任意命令只负责从这些动作中选择并组合成脚本。脚本格式可以设计成 JSON例如{ actions: [ {action: attack, target: rat}, {action: attack, target: rat}, {action: rest} ] }这样设计有三个好处安全模型无法让 Agent 执行白名单之外的操作。可控执行器能对每个动作做参数校验。可回滚每一轮生成的脚本都可以存档出了问题可以切回之前的版本。执行循环不需要复杂核心逻辑就是“读取状态 - 生成脚本 - 执行脚本 - 校验结果”。如果结果里出现“你已死亡”“你不在这里”“你的血量不足”等异常信息就停止当前脚本重新让模型规划。6. 完整示例代码实现下面给出一个最小可运行的工程骨架。代码不依赖特定 DeepSeek Harness 发行版核心结构是通用的连接器负责收发 MUD 文本配置负责描述模型和环境主循环负责任务调度。你完全可以把这里的思路迁移到任何 Agent 框架里。6.1 第一步实现 MUD 连接器文件mud_connector.pyimport asyncio import telnetlib3 class MudConnector: def __init__(self, host: str, port: int, prompt_marker: str ): self.host host self.port port self.prompt_marker prompt_marker self.reader None self.writer None async def connect(self): # 建立 Telnet 连接 self.reader, self.writer await telnetlib3.open_connection( self.host, self.port, cols120 ) # 读取欢迎页和初始文本 initial await self.read_until(self.prompt_marker, timeout5) return initial async def send(self, command: str) - str: # MUD 命令通常以换行结尾 self.writer.write(command.rstrip(\r\n) \r\n) # 读取服务端返回直到出现命令提示符或超时 return await self.read_until(self.prompt_marker, timeout5) async def read_until(self, marker: str, timeout: float 5.0) - str: data try: while marker not in data: chunk await asyncio.wait_for(self.reader.read(1024), timeouttimeout) if not chunk: break data chunk except asyncio.TimeoutError: pass return data这个连接器做的事情很简单连接 MUD 服务器发送命令读取返回文本。代码里的prompt_marker是 MUD 客户端每行命令前的提示符通常像或HP:100你需要根据实际服务器调整。注意read_until是简化版本真实 MUD 服务器输出可能包含大量 ANSI 颜色码。如果返回文本里混入特殊字符可以在连接器里加一层清洗函数把\x1b[...m之类的字符去掉。6.2 第二步配置 Harness 与 Skill文件config.yamlagent: model: deepseek-chat base_url: https://api.deepseek.com/v1 max_retries: 3 timeout_seconds: 30 mud: host: 127.0.0.1 port: 4000 prompt_marker: skills: - mud_fight_skill这个配置把 Agent 的模型参数、MUD 服务器信息和技能列表分开管理。如果你使用本地模型只需修改base_url和model两个字段。文件skills/mud_fight_skill.yamlname: mud_fight_skill description: MUD 自动战斗动作脚本生成技能 input_schema: state_text: type: string description: MUD 当前房间、血量、敌人等文字状态 max_actions: type: integer default: 20 output_schema: actions: type: array description: 动作原语列表 items: action: string target: string allowed_actions: - attack - cast - use - rest - flee这不是某个具体框架的官方字段而是一种通用的描述方式。你换成官方 Harness 格式时核心概念是一样的定义输入参数、输出格式、可允许的动作范围。allowed_actions是安全边界比任何提示词都可靠。6.3 第三步Agent 主循环文件agent_loop.pyimport asyncio import json import os import requests from mud_connector import MudConnector DEEPSEEK_API_KEY os.getenv(DEEPSEEK_API_KEY, ) DEEPSEEK_BASE_URL os.getenv(DEEPSEEK_BASE_URL, https://api.deepseek.com/v1) DEEPSEEK_MODEL os.getenv(DEEPSEEK_MODEL, deepseek-chat) ACTION_ALIAS { attack: kill, cast: cast, use: use, rest: rest, flee: flee, } def call_llm(messages, max_tokens800): resp requests.post( f{DEEPSEEK_BASE_URL}/chat/completions, headers{Authorization: fBearer {DEEPSEEK_API_KEY}}, json{ model: DEEPSEEK_MODEL, messages: messages, temperature: 0.2, max_tokens: max_tokens, response_format: {type: json_object}, }, timeout30, ) resp.raise_for_status() return resp.json()[choices][0][message][content] def parse_action_script(text: str): try: return json.loads(text) except json.JSONDecodeError: return {actions: []} async def run_action_script(conn: MudConnector, actions, max_rounds20): logs [] for step in actions[:max_rounds]: action step.get(action, ) target step.get(target, ) if action not in ACTION_ALIAS: logs.append(fskip unknown action: {action}) continue command ACTION_ALIAS[action] if target: command f{command} {target} output await conn.send(command) logs.append(f {command}\n{output}) await asyncio.sleep(0.5) return \n.join(logs) async def main(): conn MudConnector( hostos.getenv(MUD_HOST, 127.0.0.1), portint(os.getenv(MUD_PORT, 4000)), ) await conn.connect() await conn.send(os.getenv(MUD_ROLE, test_warrior)) system_prompt ( 你是 MUD 游戏中的战斗策略助手。 根据当前状态生成动作原语 JSON只能使用 attack/cast/use/rest/flee 动作。 输出格式为{actions:[{action:attack,target:rat}]} ) state_text await conn.send(look) messages [ {role: system, content: system_prompt}, {role: user, content: f当前状态\n{state_text}\n请生成战斗动作脚本。}, ] for round_no in range(3): llm_output call_llm(messages) action_script parse_action_script(llm_output) result await run_action_script(conn, action_script.get(actions, [])) print(f[round {round_no}] {result[-800:]}) state_text await conn.send(look) messages.append({role: assistant, content: llm_output}) messages.append( { role: user, content: f执行结果\n{result}\n当前状态\n{state_text}\n继续或停止, } ) if not action_script.get(actions): break if __name__ __main__: asyncio.run(main())这段代码包含三个关键逻辑第一call_llm封装了模型调用。通过标准 requests 访问 OpenAI 兼容接口不绑定任何特定 SDK。如果你的模型服务不支持response_format参数直接删掉这一行即可。第二parse_action_script把模型输出解析成 JSON。模型并不可靠可能输出多余解释文字这里只保留合法 JSON 解析结果。解析失败时返回空 actions避免程序崩溃。第三run_action_script是安全执行器。它不执行模型直接给出的字符串命令而是把白名单动作映射成 MUD 命令。模型说attack就映射为kill说rest就映射为rest。模型永远无法跳出白名单。6.4 第四步运行命令运行时先确认环境变量已经设置然后执行python agent_loop.py如果一切正常你会看到类似下面的输出[round 0] kill rat 你攻击老鼠老鼠反击但伤害不大。 kill rat 你击败了一只老鼠。 rest 你开始休息体力逐渐恢复。这段输出只是示例。具体文本完全取决于你的 MUD 服务器。7. 运行结果与效果验证跑完一轮后不要只看模型有没有输出要验证三件事。第一命令是否真的发到了 MUD 服务器。最简单的验证方式是看连接器返回的文本。每执行一条命令日志里都应该记录 命令和对应的服务器响应。如果只有命令没有响应说明read_until的提示符配置有问题或者连接已经断开。第二角色状态是否在朝预期方向变化。战斗脚本执行后角色经验值、怪物数量、血量状态应该有变化。你可以手动在 MUD 里look或score也可以让 Agent 在每轮循环后自动拉取状态文本并打印摘要。第三异常时模型是否能介入。故意让脚本打一个不存在的目标观察执行结果里是否出现类似“这里没有这个目标”的文本。如果执行器把这些文本传回给模型模型应该能意识到脚本有问题并生成新的动作序列。如果模型直接忽略异常继续执行就要检查 messages 里异常文本是否被正确放到了用户消息里。更严谨的验证方式是加日志。建议把每一轮的以下信息写入本地文件时间戳 当前状态文本 模型生成的原始输出 解析后的动作脚本 执行结果文本 是否触发异常介入有了这些日志你才能判断某个回合失败时是模型决策错了、解析失败了、还是执行器命令打错了。没有日志的 Agent 实验出了问题几乎无法排查。8. 常见问题与排查思路问题现象可能原因排查方式解决方案Python 包无法安装网络源不可用或 Python 版本不匹配查看完整报错和 pip 版本换国内镜像源升级或降级 Python 版本Windows 报SetNamedSecurityInfoW failed (win32)工作目录处于受保护路径或文件 ACL 设置失败检查报错堆栈涉及哪个目录将项目移动到用户目录以普通用户运行Skill 读取文件权限异常服务用户没有目标目录读取权限查看进程用户和目录所有者调整权限或在容器内以固定 UID 运行模型接入失败或超时Base URL / API Key 配置错误curl 测试模型接口连通性核对环境变量确认模型服务兼容 OpenAI 接口模型返回内容无法解析为 JSONtemperature 过高或提示词约束不足打印原始模型输出降低 temperature增加 few-shot 示例启用 JSON mode战斗脚本卡死不动连接器等待提示符超时或脚本没有退出条件查看日志里最后一条发送命令设置最大执行轮次增加异常关键词检测本地模型生成的动作不准确小参数模型指令遵循能力弱对比 deepseek-chat 与本地模型输出改用更大模型或把动作候选直接写进提示词想回退到之前的脚本没有版本管理检查 skills 目录是否有 git 记录所有 Skill 文件纳入版本控制回退时切 tag这里特别说明一下 Windows 权限问题。SetNamedSecurityInfoW是 Windows 系统用来修改文件或目录安全描述符的底层 API。很多工具在安装插件、写入配置时会调用它。如果返回失败通常不是因为代码写错了而是当前进程没有对目标路径设置 ACL 的权限。最容易的解决办法就是不要把这些文件放在系统盘受保护目录也不要用管理员权限强行运行。保持最小权限运行反而能避开这类问题。另一个高频问题是“DeepSeek Harness 能不能完全离线运行”。答案是能。MUD 连接器和执行器本来就是本地进程唯一依赖外部的是模型接口。把模型部署到内网服务器或局域网机器上再把配置里的base_url指向内网地址整个系统就不需要访问公网了。9. DeepSeek Harness 的最佳实践与工程建议如果你把上面这套骨架用在实际项目中下面的几条建议值得认真考虑。第一动作白名单是底线。模型输出天然不可控你可以在提示词里写一万遍“不要执行危险命令”都不如执行器里只允许attack/cast/use/rest/flee五个动作来得可靠。任何 Agent 工程都应该先定义动作边界再开放模型调用。第二每次实验都要可回放。Agent 的失败往往不是瞬间发生的而是经过多轮交互累积出来的。建议保存每一轮的状态快照、模型输入、模型输出、执行结果。出了问题的时候用这部分数据做回放对比才能找到是决策层的问题还是执行层的问题。第三Skill 文件要纳入版本管理。热词里经常出现“deepseek harness 代码回退”说明很多人在调试 Agent 时会频繁修改 Skill 配置。不要直接改线上文件先把改造版复制成skill_v2.yaml跑通后再替换。每次替换都打一个 tag回退成本会低很多。第四模型选择要分场景。如果只是快速验证流程优先用 DeepSeek API省心且稳定。如果数据敏感或需要完全内网运行再考虑本地模型。本地模型的优势是隐私和成本可控但小模型的指令遵循能力可能不够战斗脚本生成结果会明显差一截。建议先跑通流程再根据自己的需求权衡换哪个模型。第五尽量隔离测试环境。MUD 服务器如果是远程正式服任何一条错误命令都可能造成不可逆后果。最好把服务端跑在本地 Docker 容器里Agent 先在这个环境里模拟上千次战斗验证稳定后再考虑更复杂的环境。生产环境里永远不要跑没有经过回放测试的新脚本。第六多 Agent 协作是后续方向。单 Agent 做 MUD 战斗已经能闭环但遇到探索地图、买卖装备、组队配合这类任务时一个 Agent 会手忙脚乱。后期可以让一个 Agent 负责战斗指挥另一个 Agent 负责地图记忆和目标规划再通过 Harness 的调度机制把两者连接起来。这套思路比堆提示词要可靠得多。10. 总结与后续学习方向这篇文章并不是在教你“刷等级”而是用 MUD 自动战斗这个场景把 Agent 自动化中最重要的机制讲清楚模型负责决策Harness 负责执行Skill 负责限制边界。没有这套分层任何大模型都只能停留在“聊天”层面无法真正操作系统。你可以继续深入的方向有几个。一是把状态摘要升级成结构化状态。目前只是把原始文本直接交给模型效果还不够稳定。更合理的方式是从文本里抽取 HP、MP、坐标、敌人列表用 JSON 格式传给模型。二是扩展动作原语。除了战斗还可以增加move、buy、sell、give等动作让 Agent 能完成更复杂的任务流。三是引入多个 Specialist Agent。一个模型负责战斗操作另一个模型负责地图导航第三个模型负责异常判断。你不需要一次性把系统做得很复杂但理解了这个方向后续的扩展空间会大很多。最后再强调一遍边界。AI 自动战斗脚本能跑通不代表可以随意用在正式运营的游戏中。更好的玩法是搭一个本地 MUD 服务端把整个工程当作一次 Agent 设计实验。你在这次实验里积累的日志、权限、回滚、异常恢复经验才是真正可以迁移到生产项目里的资产。