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

📅 发布时间:2026/9/29 18:58:11
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 特指“piLLM 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 APIPigame 的决策核心是 LLM因此需要配置大模型 API。常见方式是通过环境变量读取export OPENAI_API_KEYsk-xxxx export OPENAI_BASE_URLhttps://api.openai.com/v1如果你使用的是本地推理服务可以把OPENAI_BASE_URL指向本地地址比如export OPENAI_BASE_URLhttp://localhost:8000/v1 export OPENAI_API_KEYlocal这样做的目的是让 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(headlessTrue) browser.open(data:text/html,h1Pigame Env Check/h1) img browser.screenshot() print(浏览器截图成功图片大小, len(img), bytes) client LLMClient(api_keyos.getenv(OPENAI_API_KEY), base_urlos.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 与 move3.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 langzh-CN head meta charsetUTF-8 titlePigame 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 idgame width640 height360/canvas div idstatus未开始/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(headlessself.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: fBearer {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: fdata:image/png;base64,{image_base64}}, }, ], }, ], temperature: 0.2, } resp requests.post(f{self.base_url}/chat/completions, jsonpayload, headersheaders) 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 fJSON解析失败: {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(fStep {i1}) 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 textcheck_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(headlessFalse) # 有头模式方便观察过程 browser.start() browser.open(file:///absolute/path/to/games/demo_jump.html) llm LLMClient( api_keyos.getenv(OPENAI_API_KEY, sk-local), base_urlos.getenv(OPENAI_BASE_URL, http://localhost:8000/v1), modelos.getenv(LLM_MODEL, gpt-4o), ) executor ToolExecutor(browser) agent Agent(browserbrowser, llm_clientllm, executorexecutor) try: agent.run(max_steps50) 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 result5.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_retries3): for i in range(max_retries): try: return func() except Exception as e: if i max_retries - 1: raise print(fAPI 调用失败重试 {i1}/{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, encodingutf-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再把游戏改成你想要的难度很快你就能体会到让机器“看见世界、做出动作”的乐趣。