☰
基于DeepSeek Harness的AI Agent玩转MUD:动作脚本与执行循环实战
2026/10/8 8:33:35 网站建设 项目流程

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 Agent

MUD 全称 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_URL="https://api.deepseek.com/v1" export DEEPSEEK_MODEL="deepseek-chat" export MUD_HOST="127.0.0.1" export MUD_PORT="4000" export MUD_ROLE="test_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.py

import 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, cols=120 ) # 读取欢迎页和初始文本 initial = await self.read_until(self.prompt_marker, timeout=5) 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, timeout=5) 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), timeout=timeout) 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.yaml

agent: 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.yaml

name: 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.py

import 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_tokens=800): resp = requests.post( f"{DEEPSEEK_BASE_URL}/chat/completions", headers={"Authorization": f"Bearer {DEEPSEEK_API_KEY}"}, json={ "model": DEEPSEEK_MODEL, "messages": messages, "temperature": 0.2, "max_tokens": max_tokens, "response_format": {"type": "json_object"}, }, timeout=30, ) 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_rounds=20): logs = [] for step in actions[:max_rounds]: action = step.get("action", "") target = step.get("target", "") if action not in ACTION_ALIAS: logs.append(f"skip 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( host=os.getenv("MUD_HOST", "127.0.0.1"), port=int(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 设计实验。你在这次实验里积累的日志、权限、回滚、异常恢复经验,才是真正可以迁移到生产项目里的资产。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询