拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Pigame实战:用LLM Agent的observe/move玩黑盒浏览器游戏

Pigame实战:用LLM Agent的observe/move玩黑盒浏览器游戏

1. 背景与核心概念

1.1 Pigame 到底是什么

Pigame 是一个很有意思的开源实验项目,核心思路是:让一个大语言模型(LLM)作为“玩家”,通过observe(观察)和move(移动/操作)这两个基础工具,去玩那些我们平时在浏览器里打开的各种小游戏。这里的“黑盒浏览器游戏”(black-box browser games)指的是,模型不需要理解游戏内部代码、DOM 结构、渲染逻辑,也不需要对游戏进行任何改造,只需要像人一样“看屏幕、做操作”,就能完成游戏任务。

用更通俗的话来说,Pigame 相当于给大模型装上了一双“眼睛”和一双“手”。眼睛负责截取浏览器画面,把视觉信息传给模型;手负责执行键盘、鼠标操作,让模型可以点击按钮、移动角色、触发事件。整个过程是循环的:观察、思考、操作、再观察、再思考、再操作……直到游戏通关或者达到某个目标条件。

这个项目之所以值得关注,是因为它代表了大模型应用的一个重要方向:把 LLM 从“文本对话”扩展到“图形界面交互”。传统的 LLM 应用大多停留在聊天、问答、代码生成、文档处理等文本场景,而 Pigame 尝试让模型与现实世界中“可见的、动态的、需要实时反馈”的环境进行交互,这是一个跨越。

1.2 为什么叫 black-box(黑盒)

很多做前端自动化的人会想到 Playwright、Selenium 这类工具,它们通过选择器定位按钮、输入框,从而操作页面。但这类方式依赖一个前提:你能拿到 DOM 结构、能看到页面源码、能定位元素。Pigame 的思路完全不同,它不依赖任何内部结构,模型看到的只有截图,操作的也只有模拟键鼠。

这种黑盒模式有三个明显好处:

第一,通用性强。只要是能在浏览器里跑的游戏,不管是用 Canvas 画的、WebGL 渲染的、还是纯 DOM 拼出来的,都可以尝试让模型去玩。

第二,接近人类交互方式。人类玩游戏时,也不会去读游戏的源码,而是看画面、按键盘、点鼠标。Pigame 让模型走的正是这条路。

第三,屏蔽了技术栈差异。不同游戏可能使用不同框架,黑盒方式不需要为每个游戏单独写适配逻辑。

当然,黑盒也并非没有代价。没有 DOM 信息,模型对界面的理解完全依赖视觉,对截图质量、图像分辨率、颜色对比度都有要求。此外,操作反馈存在延迟,模型的决策速度也是影响成功率的重要因素。

1.3 Pigame 与 Agent 的关系

最近一两年,“LLM Agent” 这个概念非常火热。所谓 Agent,指的是能够感知环境、做出决策、执行动作的智能体。Pigame 本质上就是一个典型的 LLM Agent 示例:

  • 感知层:浏览器截图作为视觉输入。
  • 决策层:大模型根据当前画面和目标任务,决定下一步做什么。
  • 执行层:通过move工具模拟键盘/鼠标操作,改变游戏状态。

observe和move是 Pigame 设计中最基础的两个工具。observe负责获取信息,move负责改变环境,它们构成一个最简单的“感知—行动”闭环。这个闭环虽然简单,但足够说明 Agent 的基本工作原理。

对于想学习 LLM Agent 实现的开发者来说,Pigame 是一个非常适合入手的项目。它比那些复杂的 ReAct 框架、AutoGPT 类工具要轻量得多,代码量不大,逻辑清晰,容易读懂,也容易二次开发。

2. 环境准备与版本说明

2.1 运行环境要求

Pigame 是一个比较新的开源项目,依赖的库和工具版本变化可能较快。本文以通用环境为例讲解,具体版本需要根据你克隆代码时的实际情况调整。

基础环境建议如下:

  • 操作系统:Linux / macOS / Windows 均可,但 Linux 环境对浏览器自动化支持更稳定。
  • Python:建议使用 Python 3.10 或更高版本,项目中通常需要类型标注和异步支持。
  • Node.js 环境:如果项目通过 npm 管理浏览器自动化依赖,则需安装 Node.js 16 以上。
  • 浏览器:Chrome 或 Chromium,用于渲染游戏页面并截图。
  • OpenAI 兼容 API 或本地 LLM 推理服务:作为决策大脑,可以通过 HTTP API 调用。

需要注意的是,Pigame 这个项目名称有两个容易混淆的来源:一个是树莓派(Raspberry Pi)相关的工具,另一个是开源语音助手项目 Piper。本文中的 Pigame 特指“pi+LLM plays black-box browser games via observe/move tools”,与树莓派无关,不要混淆。

2.2 克隆项目与安装依赖

首先把项目克隆到本地:

git clone https://github.com/your-project-pigame.git cd pigame

进入项目后,一般会有requirements.txt或pyproject.toml文件,安装 Python 依赖:

python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install -r requirements.txt

如果项目包含 Node.js 部分的自动化脚本,需要安装 npm 依赖:

npm install

安装完成后,可以先查看项目结构:

pigame/ ├── README.md ├── requirements.txt ├── pyproject.toml ├── pigame/ │ ├── __init__.py │ ├── tools.py # observe / move 工具定义 │ ├── agent.py # LLM 决策逻辑 │ ├── browser.py # 浏览器控制与截图 │ └── config.py # 配置项 ├── games/ │ └── demo_game.html # 内置测试游戏 └── examples/ └── run_demo.py # 运行示例

项目结构可能因版本不同而有所调整,但大体思路一致:核心封装在pigame包内,游戏和示例分别放在独立目录中。

2.3 配置 LLM API

Pigame 的决策核心是 LLM,因此需要配置大模型 API。常见方式是通过环境变量读取:

export OPENAI_API_KEY="sk-xxxx" export OPENAI_BASE_URL="https://api.openai.com/v1"

如果你使用的是本地推理服务,可以把OPENAI_BASE_URL指向本地地址,比如:

export OPENAI_BASE_URL="http://localhost:8000/v1" export OPENAI_API_KEY="local"

这样做的目的是让 Pigame 的客户端代码只依赖 OpenAI 兼容协议,不关心底层实际是哪个模型服务。国内可用的多种大模型服务都提供兼容接口,配置方式类似。

2.4 验证环境是否就绪

在正式运行之前,可以先写一个简单的验证脚本,确认浏览器环境和 LLM API 都能正常工作:

# 文件路径:examples/check_env.py import os from pigame.browser import Browser from pigame.llm import LLMClient def main(): browser = Browser(headless=True) browser.open("data:text/html,<h1>Pigame Env Check</h1>") img = browser.screenshot() print("浏览器截图成功,图片大小:", len(img), "bytes") client = LLMClient(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL")) response = client.chat("你好,请回复:OK") print("LLM 回复:", response) browser.close() if __name__ == "__main__": main()

运行:

python examples/check_env.py

如果看到截图成功和 LLM 回复,说明环境没问题。这个验证步骤很关键,能帮我们把“环境问题”和“项目逻辑问题”分离开。

3. 核心原理拆解:observe 与 move

3.1 observe 工具做了什么

observe这个工具的名字看起来很直白,但在 Pigame 的实现中,它不仅仅是“截图”这么简单。一次完整的observe通常包含以下几个环节:

第一步,浏览器打开指定游戏页面,调整窗口尺寸到固定大小。这是为了控制截图的稳定性和一致性,避免不同窗口大小导致画面内容变化。

第二步,调用浏览器截图接口,获取当前帧的 PNG 图片。截图时需要注意隐藏鼠标光标,同时保证游戏画面完整可见。

第三步,对截图进行必要的压缩和编码处理,把图片转换为 base64 或其他格式,以便放入 LLM 的请求消息中。

第四步,把带有图片的消息发送给模型,让模型“看到”当前画面。

换句话讲,observe就是一个把像素信息转成模型可理解的多模态输入的过程。如果模型本身是纯文本模型,不支持图像输入,那么还可以在 observe 环节加入图像描述模块,先用一个视觉模型把截图转成文字描述,再把描述喂给决策模型。不过这样做会增加延迟和成本,Pigame 的理想状态是直接使用支持视觉的多模态大模型。

3.2 move 工具做了什么

move是对浏览器操作的抽象封装。在浏览器自动化领域,最底层的操作就是鼠标事件和键盘事件。Pigame 的move工具通常支持以下类型:

  • 点击操作:移动鼠标到坐标点,按下并释放左键。
  • 按键操作:按下某个键盘按键,比如方向键、空格键、字母键。
  • 组合操作:先按下某个键,再点击某个位置,比如按住 Shift 进行加速。
  • 等待操作:让游戏画面自然更新一段时间,模拟人类“观察后停顿”的节奏。

这里有一个细节值得思考:为什么用move而不是更细粒度的click、press_key分开命名?因为对于 LLM 来说,工具数量越少,决策负担越轻。统一成一个move工具、通过参数区分动作类型,是比较聪明的设计。这降低了模型在“选择工具”层面的负担,让模型更多地专注于“做什么、怎么做”。

一个典型的 move 调用可能长这样:

{ "tool": "move", "action": "click", "x": 320, "y": 240 }

或者:

{ "tool": "move", "action": "key", "key": "ArrowRight", "duration": 0.5 }

3.3 观察—思考—行动的循环

Pigame 的核心循环可以拆解为四个阶段:

第一阶段,观察。模型调用observe,获取当前游戏画面。

第二阶段,推理。模型结合上一轮的操作历史,分析当前场景,判断游戏状态是否发生改变,是否接近目标。

第三阶段,决策。模型决定下一步动作,是继续移动、点击某个按钮,还是转向其他策略。

第四阶段,执行。系统解析模型的输出,调用move工具在浏览器中执行对应操作,然后回到第一阶段。

这个循环和强化学习中的“状态—动作—奖励”框架非常相似,只不过这里的“策略”不是训练出来的神经网络,而是大模型的上下文推理能力。大模型不需要经历成千上万次试错来学习游戏规则,它可以直接借助常识和视觉理解能力,快速推断出游戏的基本玩法和策略。

当然,这个优势也是有限度的。对于需要长时间规划、操作频率极高、反馈信号微弱的游戏,模型可能会陷入无意义的循环。后续章节会给出一些优化思路。

3.4 提示词设计

Pigame 能玩得好不好,提示词设计是一个关键因素。在设计 system prompt 时,我们需要明确告诉模型几个信息:

第一,目标任务是什么。比如“最终目标是让角色到达旗帜处”,模型才知道方向。

第二,可用工具是什么。列出 observe 和 move 的用法、参数说明,避免模型发明出不存在的操作。

第三,输出格式要求。为了让系统能够解析模型的输出,可以要求模型返回 JSON 格式的操作指令。

第四,历史上下文长度限制。游戏状态长时间运行会导致历史消息过长,最后要做截断,只保留最近几轮的关键信息。

一段示意性的 system prompt 可能如下:

你是一个浏览器游戏玩家。你可以通过 observe 工具观察游戏画面,通过 move 工具控制键盘鼠标。 你的目标是完成游戏关卡。请一步一步分析画面内容,选择一个合适的动作。 输出格式:请严格输出 JSON,不要输出其他内容。 例如: {"action": "move", "type": "key", "key": "ArrowRight", "duration": 0.3} 注意: 1. 每次只能执行一个动作。 2. 如果画面没有变化,考虑等待或尝试不同操作。 3. 动作不要过于频繁,给游戏渲染留出时间。

这里有一个实用技巧:提示词中不仅要给出“允许做什么”,还要给出“禁止做什么”。比如“不要连续多次执行相同操作”“不要尝试用工具访问页面源码”,这类约束能明显减少模型跑偏的概率。

4. 完整实战:让 Pigame 玩一个简易跳跃游戏

4.1 搭建内置演示游戏

为了快速验证 Pigame 的能力,我们可以在本地搭建一个简单的 Canvas 游戏页面。这里提供一个最小可用的跳跃游戏 HTML,它包含一个角色、一个障碍物和一个旗帜,角色碰到旗帜即为过关。

<!-- 文件路径:games/demo_jump.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Pigame Demo</title> <style> body { margin: 0; background: #222; display: flex; justify-content: center; align-items: center; height: 100vh; } canvas { background: #fff; border: 2px solid #333; } #status { position: absolute; top: 10px; left: 10px; color: #fff; font-family: monospace; } </style> </head> <body> <canvas id="game" width="640" height="360"></canvas> <div id="status">未开始</div> <script> const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); let x = 30, y = 280, vx = 0, vy = 0; const ground = 300; const obstacle = { x: 400, y: 270, w: 30, h: 30 }; const flag = { x: 550, y: 230, w: 20, h: 40 }; const gravity = 0.4; const jumpForce = -8; let gameOver = false; let win = false; document.addEventListener('keydown', (e) => { if (e.key === 'ArrowRight') vx = 3; if (e.key === 'ArrowLeft') vx = -3; if (e.key === 'ArrowUp' && y >= ground) vy = jumpForce; }); document.addEventListener('keyup', (e) => { if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') vx = 0; }); function update() { if (gameOver || win) return; vy += gravity; x += vx; y += vy; if (y > ground) { y = ground; vy = 0; } if (x > 600) x = 600; if (x < 0) x = 0; // 碰撞检测:障碍物 if (x + 30 > obstacle.x && x < obstacle.x + obstacle.w && y + 30 > obstacle.y) { gameOver = true; document.getElementById('status').innerText = 'Game Over'; } // 到达旗帜 if (x + 30 > flag.x && x < flag.x + flag.w && y + 30 > flag.y) { win = true; document.getElementById('status').innerText = 'Win!'; } } function draw() { ctx.clearRect(0, 0, 640, 360); ctx.fillStyle = '#4caf50'; ctx.fillRect(0, ground, 640, 360 - ground); ctx.fillStyle = '#f44336'; ctx.fillRect(obstacle.x, obstacle.y, obstacle.w, obstacle.h); ctx.fillStyle = '#ffeb3b'; ctx.fillRect(flag.x, flag.y, flag.w, flag.h); ctx.fillStyle = '#2196f3'; ctx.fillRect(x, y, 30, 30); } function loop() { update(); draw(); requestAnimationFrame(loop); } loop(); </script> </body> </html>

这个游戏虽然简单,但已经包含了一个“键盘控制—移动—碰撞检测—胜利条件”的完整闭环。角色蓝色方块,障碍物红色方块,旗帜黄色方块,玩家需要用方向键控制角色向右移动并跳过障碍物,最终碰到旗帜。

4.2 编写 Pigame 核心封装

接下来编写 Pigame 的核心工具封装。首先是浏览器控制模块,负责启动浏览器、打开页面、截图。

# 文件路径:pigame/browser.py import base64 import time from playwright.sync_api import sync_playwright class Browser: def __init__(self, headless: bool = True): self.headless = headless self.playwright = None self.browser = None self.page = None def start(self): self.playwright = sync_playwright().start() self.browser = self.playwright.chromium.launch(headless=self.headless) self.page = self.browser.new_page(viewport={"width": 640, "height": 360}) self.page.set_default_timeout(8000) def open(self, url_or_path: str): if self.page: self.page.goto(url_or_path) def screenshot(self) -> str: # 返回 base64 编码的截图 buffer = self.page.screenshot() return base64.b64encode(buffer).decode("utf-8") def click(self, x: int, y: int): self.page.mouse.click(x, y) def press_key(self, key: str, duration: float = 0.1): self.page.keyboard.down(key) time.sleep(duration) self.page.keyboard.up(key) def wait(self, ms: int): time.sleep(ms / 1000) def close(self): if self.browser: self.browser.close() if self.playwright: self.playwright.stop()

这里选用 Playwright 作为浏览器自动化库,因为它并发支持好、截图方便、API 设计也比较现代。实际项目中,也可以替换为 Selenium 或 pyppeteer,但建议优先使用 Playwright。

然后是 LLM 封装,用于与模型服务通信。

# 文件路径:pigame/llm.py import base64 import os import requests class LLMClient: def __init__(self, api_key: str, base_url: str, model: str = "gpt-4o"): self.api_key = api_key self.base_url = base_url.rstrip("/") self.model = model def chat_with_image(self, system_prompt: str, user_text: str, image_base64: str) -> str: headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", } payload = { "model": self.model, "messages": [ {"role": "system", "content": system_prompt}, { "role": "user", "content": [ {"type": "text", "text": user_text}, { "type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}, }, ], }, ], "temperature": 0.2, } resp = requests.post(f"{self.base_url}/chat/completions", json=payload, headers=headers) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"]

4.3 实现 observe 与 move 工具层

工具层的代码就是把 Browser 和 LLMClient 组合起来,向 Agent 提供统一接口。

# 文件路径:pigame/tools.py import json class ToolExecutor: def __init__(self, browser): self.browser = browser def observe(self) -> str: img_b64 = self.browser.screenshot() return img_b64 def execute_move(self, move_cmd: str) -> str: try: cmd = json.loads(move_cmd) except json.JSONDecodeError as e: return f"JSON解析失败: {e}" action = cmd.get("action") if action == "click": x = cmd.get("x", 0) y = cmd.get("y", 0) self.browser.click(int(x), int(y)) return f"已点击 ({x}, {y})" elif action == "key": key = cmd.get("key") duration = float(cmd.get("duration", 0.1)) self.browser.press_key(key, duration) return f"已按键 {key}, 持续 {duration} 秒" elif action == "wait": ms = int(cmd.get("ms", 300)) self.browser.wait(ms) return f"等待 {ms} 毫秒" else: return f"未知动作: {action}"

这里有个设计细节:observe返回的是图片 base64 字符串,但模型对话接口通常也希望看到图片。为了让 process 简洁,负责组装请求的任务可以放在 Agent 层完成,工具层只负责采集信息。

4.4 编写 Agent 主循环

Agent 是决策循环的核心,它负责调用 observe、组装消息、调用 LLM、解析输出、执行 move。

# 文件路径:pigame/agent.py import json import os SYSTEM_PROMPT = """ 你是一个浏览器游戏玩家。你可以通过观察工具获取游戏画面截图,通过移动工具控制键盘鼠标。 你的目标是让蓝色方块角色避开红色障碍物,最终碰到黄色旗帜。 所有操作都必须以 JSON 格式输出,格式如下: {"action": "key", "key": "ArrowRight", "duration": 0.3} {"action": "key", "key": "ArrowUp", "duration": 0.1} {"action": "wait", "ms": 300} {"action": "click", "x": 320, "y": 240} 规则: 1. 每次输出一个动作,不要解释,不要输出多余文字。 2. 观察画面后再决定动作。 3. 遇到障碍物时,使用 ArrowUp 跳跃。 4. 如果没有障碍物,持续向右移动。 """.strip() class Agent: def __init__(self, browser, llm_client, executor): self.browser = browser self.llm = llm_client self.executor = executor self.history = [] def step(self) -> str: img_b64 = self.executor.observe() history_text = "\n".join(self.history[-6:]) user_prompt = f"当前游戏历史:\n{history_text}\n请基于当前画面输出下一步动作:" response = self.llm.chat_with_image(SYSTEM_PROMPT, user_prompt, img_b64) self.history.append(response) result = self.executor.execute_move(response) return result def run(self, max_steps: int = 60): for i in range(max_steps): print(f"Step {i+1}") result = self.step() print(f" 执行结果: {result}") if self.check_win(): print("检测到胜利状态!") break def check_win(self) -> bool: # 这里可以通过截图中的颜色统计来简单判断旗帜是否被碰到 # 或者读取页面中的状态文本 text = self.browser.page.locator("#status").inner_text() return "Win" in text

check_win方法在这里直接读取页面文本,算是一个“半黑盒”的辅助判断。如果我们想保持严格黑盒,可以改为分析截图像素颜色,比如检测蓝色方块和黄色旗帜是否重叠。但读取状态文本在演示项目中是完全可以接受的,实际项目可以结合 OCR 或视觉模型。

4.5 编写运行入口

最后,编写一个总的运行脚本,串联所有模块。

# 文件路径:examples/run_demo.py import os from pigame.browser import Browser from pigame.llm import LLMClient from pigame.tools import ToolExecutor from pigame.agent import Agent def main(): browser = Browser(headless=False) # 有头模式方便观察过程 browser.start() browser.open("file:///absolute/path/to/games/demo_jump.html") llm = LLMClient( api_key=os.getenv("OPENAI_API_KEY", "sk-local"), base_url=os.getenv("OPENAI_BASE_URL", "http://localhost:8000/v1"), model=os.getenv("LLM_MODEL", "gpt-4o"), ) executor = ToolExecutor(browser) agent = Agent(browser=browser, llm_client=llm, executor=executor) try: agent.run(max_steps=50) finally: browser.close() if __name__ == "__main__": main()

运行前,把demo_jump.html的路径替换成你本机的绝对路径,或者通过 Python 的os.path.abspath拼接,避免路径问题。运行命令:

python examples/run_demo.py

如果一切正常,你会看到浏览器弹出游戏页面,然后模型一步步输出操作指令,蓝色方块逐步向右移动,直到触发胜利条件。

5. 常见问题与排查思路

5.1 模型输出格式不稳定

最常遇到的问题之一,是模型没有按要求输出纯净 JSON,而是夹带了 Markdown 代码块标记或者自然语言解释。这会导致json.loads解析失败,Agent 直接报错。

这个问题的根源在于,大模型的输出往往带有“对话惯性”,用户稍微改变措辞,模型就可能偏离格式要求。

解决方案有以下几种:

第一种,在后端解析时做容错处理,把响应中的 ````json、``` ` 等标记剥掉,只保留大括号内的内容。

第二种,使用更强制的结构化输出。部分模型服务支持 JSON Mode,可以在请求中开启response_format={"type": "json_object"},强制模型输出合法 JSON。

第三种,给模型一个输出示例,并且在提示词中把示例放在最显眼的位置,减少模型的发挥空间。

5.2 模型执行动作过于频繁

有些模型在理解游戏动态时,会倾向于“每帧截图、每帧操作”,导致动作非常密集。这不仅消耗 API 资源,也可能导致游戏角色反应过度,甚至因为操作间隔太短,跳跃和移动冲突。

解决思路是在 Agent 层加入最小间隔限制。比如,在两轮操作之间强制等待至少 300 毫秒,或是在提示词中限制“每次动作后等待画面反馈”。

更稳妥的做法是引入一个简单的冷却机制:

last_action_time = 0 min_interval = 0.5 # 秒 def step_with_cool_down(agent): global last_action_time elapsed = time.time() - last_action_time if elapsed < min_interval: time.sleep(min_interval - elapsed) result = agent.step() last_action_time = time.time() return result

5.3 模型无法识别 Canvas 绘制的游戏角色

如果游戏完全用 Canvas 渲染,截图中的颜色对比度又不够高,模型可能无法判断哪些是障碍物、哪些是背景。此时可以优化截图阶段,适当调高截图对比度,或者用图像预处理给不同颜色物体加上边缘高亮。

另外也可以在描述截图时给模型更多提示,比如在 user prompt 中附加“画面中有蓝色方块、红色方块、黄色方块,请根据颜色识别角色和障碍物”,这能让视觉理解更稳定。

5.4 浏览器启动失败

常见原因包括:Chromium 没有安装、Playwright 浏览器内核未下载、系统缺少依赖库。解决方法:

playwright install chromium playwright install-deps

安装完成后重新运行验证脚本。如果仍然失败,检查是否有多个 Playwright 版本共存。

5.5 API 请求超时或速率限制

视觉模型处理截图通常比纯文本慢,再加上多轮循环,可能在长时间运行时触发 API 的速率限制。处理思路是降低截图分辨率、减少历史消息长度、提高单次请求超时时间、并做好重试机制:

def api_call_with_retry(func, max_retries=3): for i in range(max_retries): try: return func() except Exception as e: if i == max_retries - 1: raise print(f"API 调用失败,重试 {i+1}/{max_retries}: {e}") time.sleep(2 ** i)

5.6 排查问题清单

问题现象可能原因解决思路
模型回复无法解析输出格式不纯,含额外文字使用 JSON Mode、剥除多余标记、增加示例
截图显示空白页面尚未加载完成在goto后增加wait_for_load_state
角色原地不动模型重复输出等待指令限制 wait 次数、增加 action 多样性奖励提示
操作报错坐标越界或按键名错误校验 key 名,点击坐标限制在 viewport 内
长时间不获胜Agent 陷入无意义循环增加状态变化检测,连续 N 步无变化时强制切换动作

这张表可以当作排错时的快速索引,遇到不确定的情况,按“先看日志、再看提示词、最后看网络”的顺序排查。

6. 最佳实践与工程建议

6.1 环境抽象与依赖注入

Pigame 的代码中,Browser、LLMClient、ToolExecutor 之间的耦合要尽量降低。建议采用依赖注入的方式,让 Agent 不直接 import 具体的 Browser 实现,而是接收一个实现了相同接口的对象。这样后续如果要切换成真实浏览器、云浏览器或者模拟环境,就不需要改动 Agent 内部逻辑。

接口层面至少需要四个方法:

  • observe():返回观察结果。
  • click(x, y):点击坐标。
  • press_key(key, duration):按键。
  • wait(ms):等待。

这几种操作可以抽象成一个基类,不同实现各自继承即可。

6.2 关于关于 prompt 版本管理

用过几次就会发现,提示词微调对 Agent 行为影响极大。建议把 system prompt 单独放到一个配置文件中,比如prompts/system.md,并在代码中读取。

# 文件路径:config.py import os PROMPT_PATH = os.path.join(os.path.dirname(__file__), "..", "prompts", "system.md") def load_prompt(): with open(PROMPT_PATH, "r", encoding="utf-8") as f: return f.read().strip()

这样调整策略时不需要改代码,只需要改 Markdown 文件,对非开发者协作也比较友好。

6.3 状态检测与失败退出机制

Agent 每轮执行动作后,最好对比当前截图与上一轮截图是否有明显变化。如果连续多轮截图几乎一样,说明模型可能陷入了“无效输出”状态。此时可以强制让 Agent 换一种策略,比如随机跳跃、按下不同按键,打破僵局。

由于直接比较两张图片的像素比较复杂,可以用哈希值做一个粗粒度的变化判断:

import hashlib def image_hash(img_b64: str) -> str: return hashlib.md5(img_b64.encode("utf-8")).hexdigest()

在 Agent 中维护最近三张截图的哈希值,如果全部相同,则判定为卡死。

6.4 历史上下文压缩

游戏场景中,模型需要参考近期操作,但历史操作不能无限堆积。建议只保留最近几轮的消息,或者对历史操作做一个摘要:

前 5 步操作:向右移动、向右移动、跳跃、向右移动、向右移动。 当前状态:角色在障碍物左侧,尚未越过。

用摘要代替完整历史,不仅能减少 token 消耗,也能让模型关注当下更重要。

6.5 安全与合规边界

Pigame 是一个公开的、面向普通浏览器的自动化实验项目,开发者在使用时需要特别注意几个边界:

第一,只能在合法授权的页面上运行。不要用 Pigame 去操作他人网站、游戏账号或需要登录的服务。

第二,不要用于绕过验证码、自动抢购、刷票、作弊等违规用途。这类行为违反网站服务协议,也会给个人带来风险。

第三,浏览器自动化操作一旦涉及支付、个人信息等敏感场景,必须经过授权,并在测试环境验证。

第四,大模型输出存在不确定性,执行动作前应增加确认机制,尤其是涉及删除、提交、购买等不可逆操作时。

6.6 性能与成本优化

视觉模型的 API 调用成本通常比文本模型高,截图分辨率越大,token 消耗越大。可以通过三个手段控制成本:

  • 缩放截图:把 640x360 的截图缩放为 320x180 再传给模型。
  • 降低频率:每两帧截一次图,而不是每帧都请求模型。
  • 使用本地模型:如果机器配置足够,可以部署一个小型的视觉语言模型,把 API 成本降为零。

对于玩简单游戏来说,小模型的整体效果不一定差,有时反而因为“思维更直接”而表现稳定。

7. 总结与进阶方向

Pigame 演示了一个非常完整的 LLM Agent 闭环:感知、推理、行动、反馈。通过observe和move两个工具,它让大语言模型真正走进了浏览器游戏世界。这个项目的核心价值不在于“让模型玩游戏”本身,而在于为理解 LLM Agent 的工程实现提供了一套轻量、可读、易扩展的参考代码。

这篇文章中,我们已经完成了从环境配置、核心原理、完整实战到问题排查的闭环。你掌握了以下关键点:

  • Pigame 的黑盒交互模式:不依赖 DOM,只看截图做操作。
  • observe 和 move 工具的定义与实现,以及它们在 Agent 循环中的作用。
  • 如何用 Playwright 操控浏览器并截图。
  • 如何用 OpenAI 兼容 API 接入视觉模型。
  • 如何设计 system prompt,让模型稳定输出可执行动作。
  • 常见踩坑点,包括格式解析、动作频率、卡死检测和 API 限流。

下一步可以尝试的方向有几个:

第一,换一个更复杂的游戏,比如包含多个障碍物、多层地图的横版跳跃游戏,观察模型在更长规划链条中的表现。

第二,引入记忆机制。让模型把“已探索区域”“已知陷阱”记录到外部 memory 中,而不是全部依赖上下文。

第三,尝试使用不同的模型,对比它们在同一游戏上的表现差异,包括指令遵从度、视觉理解力和策略稳定性。

第四,把 observe 的输出从截图扩展到 OCR 文字识别,结合鼠标键盘操作,这样 Pigame 的能力边界就不只是游戏,还可以扩展到网页表单填写、验证码解码等 GUI 自动化场景。

说到底,Pigame 这类项目最吸引人的地方,不是“AI 多会玩游戏”,而是它把抽象的“智能体”概念,用最简单可见的方式摆在了开发者面前。你不仅可以看到代码如何运行,还能看到模型每一步的思考输出。这种“透明感”对学习 Agent 开发来说,是极其宝贵的。如果你也想动手试一下,建议就从复制上面的 demo 开始,换上你自己的模型 API,再把游戏改成你想要的难度,很快你就能体会到让机器“看见世界、做出动作”的乐趣。

返回列表