之前看到 “Show HN: My AI agent built and shipped a browser game on its own” 这个标题时,我的第一反应不是“AI 又能写游戏了”,而是“它到底是怎么把‘写代码、跑测试、部署上线’这一整条链路自己走完的”。因为写一个网页游戏并不难,难的是让 Agent 在无人干预的情况下,把需求变成可运行、可访问、可分享的线上产品。
这篇文章不会停留在“AI 很强大”这种感叹层面,而是完整拆解一个 AI Agent 从零到一构建并发布浏览器游戏的工程流程。内容包括:Agent 的核心工作原理解析、工具调用与执行循环设计、一个可运行的 Agent 示例代码、自动生成游戏代码的过程、自测修复、以及最后发布上线的具体步骤。
如果你正在研究 AI Agent 开发,或者想把手头重复性的“编码 + 部署”任务交给 Agent 来做,这篇文章会很适合你。
1. 背景:为什么“Agent 自主发布游戏”值得关注
1.1 从一条 Hacker News 分享说起
“Show HN” 是 Hacker News 上一个专门展示开发者作品的板块。以往大家展示的是自己亲手写的产品,而这几年开始出现一个趋势:展示 AI Agent 自主完成的项目。
“My AI agent built and shipped a browser game on its own” 这条帖子之所以能引起讨论,不是因为它展示了一个多复杂的 3D 大作,而是它证明了这样一条链路已经可以走通:
任务描述 -> 生成代码 -> 运行测试 -> 修复 Bug -> 部署上线如果你从事开发工作,应该能感知到这件事的分量。过去一年里,AI 辅助编码已经非常普遍,但“辅助”和“自主”之间还有一条很长的路。Agent 自主发布产品,意味着它不只是你的结对编程伙伴,而是可以承担完整执行任务的“初级工程师”。
1.2 AI Agent 到底是什么
这里先做一个概念澄清。AI Agent(智能体)和普通的聊天机器人有本质区别:
- 聊天机器人:你问它答,它只生成文本,不执行动作。
- AI Agent:基于大语言模型(LLM)做决策,同时具备调用工具、执行命令、读取文件、访问网络等能力,并且可以在一个循环里反复执行“思考 -> 行动 -> 观察结果 -> 再思考”的流程,直到完成任务。
所以,一个能开发并发布游戏的 Agent,核心不是“会写代码”,而是“能自主完成一系列需要与环境交互的任务”。写代码只是其中一环,真正困难的是让 Agent 知道下一步该做什么、做完之后如何验证、出错之后如何检查原因。
1.3 为什么选“浏览器游戏”作为案例
浏览器游戏是一个非常合适的 Agent 自主开发实验对象:
- 技术栈简单:纯 HTML + CSS + JavaScript 就能跑,不需要配置复杂的后端环境。
- 可验证性强:游戏能不能运行、能不能计分、点击有没有反应,这些都是明确的验收标准。
- 部署链路短:静态文件可以直接托管到 GitHub Pages 或任意静态服务器。
- 反馈闭环快:代码写错后,刷新页面就能看到现象,Agent 能及时修正。
可以说,浏览器游戏是 AI Agent 开发流程的“最小可行产品”,非常适合用来理解和验证 Agent 的工作方式。
2. 核心概念:AI Agent 的自主工作流
2.1 ReAct 模式:推理与行动交替
AI Agent 的工作方式,本质上可以理解为 ReAct 模式,即 Reasoning + Acting。简单来说:
- 推理(Reasoning):模型根据当前状态,分析“现在目标是什么,还差什么,下一步该做什么”。
- 行动(Acting):模型调用某个工具,比如写文件、执行命令、请求 HTTP 接口。
- 观察(Observing):工具返回结果,模型把结果纳入自己的上下文,继续下一轮推理。
这个循环会一直持续,直到 Agent 认为任务已经完成,或者触发了提前设定的停止条件。
一个典型的 Agent 执行过程可能是这样的:
| 步骤 | Agent 的思考 | Agent 的行动 | 观察结果 |
|---|---|---|---|
| 1 | 需要先创建游戏文件 | 调用 write_file | 文件创建成功 |
| 2 | 需要验证代码是否能运行 | 调用 run_command 启动本地服务 | 服务启动成功 |
| 3 | 页面可能有 JS 报错 | 调用 run_command 做语法检查 | 发现第 30 行变量未定义 |
| 4 | 需要修复这个错误 | 调用 write_file 修改代码 | 修改完成 |
| 5 | 再次验证 | 重新运行测试 | 测试通过 |
| 6 | 任务完成 | 调用 publish 部署 | 返回线上地址 |
可以看到,Agent 并不是“一次性生成所有代码”就结束了,它更像一个真正的开发者:写代码、验证、发现问题、修复、再验证。
2.2 Tool Use:Agent 的“手脚”
大语言模型本身只能输出文本,它之所以能操作电脑、写文件、执行命令,靠的是工具调用(Tool Use),也叫 Function Calling。
在一个 Agent 项目中,工具就是一组预先定义好的函数。每个函数需要包含:
- 函数名称
- 功能描述
- 参数列表
- 函数实现
模型在推理过程中会“决定”调用哪个工具,并给出相应的参数。然后由 Agent 框架去执行这个函数,把返回值交给模型继续分析。
下面是一个 Agent 常用工具集的设计,后续实战部分会用到:
| 工具 | 作用 |
|---|---|
| write_file | 写入或更新文件 |
| read_file | 读取文件内容 |
| list_files | 查看目录结构 |
| run_command | 执行 shell 命令 |
| publish | 部署静态站点并返回 URL |
2.3 记忆与上下文管理
Agent 的“记忆”和人的记忆不太一样。它没有真正的长期记忆,所有信息都必须放进上下文中才能被模型逻辑处理。这也是 Agent 开发中最大的工程难点之一。
当 Agent 执行多轮操作后,工具返回的大量内容会把上下文窗口塞满。常见的解决办法有:
- 截断:日志只保留末尾 N 行。
- 摘要:把较长的历史记录提炼成简短摘要。
- 外部检索:把文件内容存到向量数据库,需要时再取回。
在实战案例中,我们会用最朴素的方式控制上下文:限制工具返回信息长度,并设置最大迭代轮数。
3. 环境准备与工具选型
在开始写代码之前,先梳理一下需要的环境和工具。版本需要根据你的项目实际情况调整,下面以常见环境为例,重点演示配置思路。
3.1 基础环境
- 操作系统:macOS 或 Linux 优先;Windows 用户建议使用 WSL2,避免 shell 命令兼容性问题。
- Python:3.10 及以上版本,用于编写 Agent 主程序。
- Node.js:18 及以上版本,部分测试工具需要。
- Git:用于版本控制和发布。
3.2 LLM 接入方式
本文示例以 OpenAI 风格的 Chat Completions API 和工具调用格式为例。实际项目中,你可以根据自己选择的模型和 SDK 调整。关键点在于模型需要支持 Function Calling / Tool Use,因为这是 Agent 能够自主执行动作的基础。
pip install openai3.3 游戏技术栈
为了降低 Agent 的编码难度和部署成本,游戏采用“单文件架构”:
index.htmlHTML、CSS、JavaScript 全部写在同一个文件里。这样 Agent 只需要维护一个文件,从生成到部署的路径最短,对新手理解 Agent 流程也更友好。
3.4 部署目标
可以选择以下几种静态托管方式:
- GitHub Pages:免费、支持 Git 工作流,适合展示型小游戏。
- Netlify Drop:支持拖拽部署,也有 API 可以供 Agent 调用。
- 自己服务器:适合有独立主机的场景,但需要更严格的安全控制。
本文实战部分以 GitHub Pages 为例,因为你只需要一个 GitHub Token,Agent 就能完成推送和发布。
4. 实战:从需求到部署的完整流程
下面我们进入完整实战。这一节我们会先定义任务,再实现一个最小可用的 Agent 系统,然后让 Agent 自动生成游戏代码、执行测试、修复问题,最后发布上线。
4.1 明确任务描述
Agent 要能自主完成任务,任务描述的清晰度非常关键。模糊的需求会让 Agent 反复猜测,浪费 token 和时间。
这里我们把任务定义为:
开发一个名叫“接苹果”(Catch Apples)的浏览器小游戏。 游戏规则: - 玩家用鼠标左右移动底部的篮子。 - 苹果从屏幕上方不断落下。 - 接住苹果得 1 分。 - 漏掉 3 个苹果,游戏结束。 - 点击“重新开始”按钮可以重新开始游戏。 验收标准: - 使用单个 index.html 文件,包含 HTML、CSS、JavaScript。 - 游戏中不能使用外部依赖。 - 页面在桌面浏览器上打开即可运行。 - 需要显示当前得分和剩余生命。 - 代码需要通过 JavaScript 语法检查。为什么任务描述要这么细? 因为 Agent 自主开发跟团队管理很像,需求越明确,返工越少。尤其是验收标准,它给 Agent 提供了“什么时候才算完成”的判断依据,也让后续的人工审查有据可依。
4.2 实现最小 Agent 系统
接下来我们写一个简化版的 Agent 主程序。这段代码的核心思路是:在循环中让模型决定调用哪个工具,执行工具后把结果交给模型继续思考。
4.2.1 工具定义
工具函数是 Agent 能操作外部环境的桥梁。这里我们先定义几个最基础的工具。
# tools.py import os import subprocess def write_file(path: str, content: str) -> dict: """写入文件,如果目录不存在则创建。""" directory = os.path.dirname(path) if directory: os.makedirs(directory, exist_ok=True) with open(path, "w", encoding="utf-8") as f: f.write(content) return {"status": "ok", "path": path, "size": len(content)} def read_file(path: str) -> dict: """读取指定文件内容。""" try: with open(path, "r", encoding="utf-8") as f: content = f.read() return {"status": "ok", "content": content[:3000]} except Exception as e: return {"status": "error", "message": str(e)} def list_files(path: str = ".") -> dict: """列出目录下的所有文件。""" try: files = os.listdir(path) return {"status": "ok", "files": files} except Exception as e: return {"status": "error", "message": str(e)} def run_command(command: str) -> dict: """执行 shell 命令,返回 stdout 和 stderr。""" try: result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=30 ) return { "status": "ok", "returncode": result.returncode, "stdout": result.stdout[-2000:], "stderr": result.stderr[-2000:], } except subprocess.TimeoutExpired: return {"status": "error", "message": "command timeout"}注意,这里的run_command使用了shell=True,这是为了演示方便。在实际生产项目中,直接让 Agent 以 shell 方式执行命令有相当大的安全风险,应该把命令白名单化,或者把所有执行步骤放进沙箱容器中执行。
4.2.2 Agent 主循环
下面这段代码是 Agent 的核心逻辑:
# agent_loop.py import json from openai import OpenAI # 实际使用时换成你自己的 API Key client = OpenAI(api_key="your-api-key") TOOL_SCHEMAS = [ { "type": "function", "function": { "name": "write_file", "description": "写入一个文件,用于生成或修改代码", "parameters": { "type": "object", "properties": { "path": {"type": "string", "description": "文件路径"}, "content": {"type": "string", "description": "完整文件内容"} }, "required": ["path", "content"] } } }, { "type": "function", "function": { "name": "read_file", "description": "读取文件内容", "parameters": { "type": "object", "properties": { "path": {"type": "string"} }, "required": ["path"] } } }, { "type": "function", "function": { "name": "run_command", "description": "执行 shell 命令,比如启动服务、运行测试", "parameters": { "type": "object", "properties": { "command": {"type": "string"} }, "required": ["command"] } } } ] def execute_tool_call(tool_call): """根据模型返回的工具调用,执行本地函数。""" from tools import write_file, read_file, run_command name = tool_call.function.name args = json.loads(tool_call.function.arguments) if name == "write_file": return write_file(args["path"], args["content"]) elif name == "read_file": return read_file(args["path"]) elif name == "run_command": return run_command(args["command"]) else: return {"status": "error", "message": f"unknown tool: {name}"} def run_agent(task: str, max_iterations: int = 10): """Agent 主循环:思考 -> 行动 -> 观察 -> 再思考。""" messages = [ { "role": "system", "content": ( "你是一个软件工程师 Agent。你的任务是开发一个浏览器小游戏。" "请遵循以下要求:\n" "1. 先规划,再动手。\n" "2. 每个文件写完后,都要验证。\n" "3. 出现错误时,根据报错信息修复。\n" "4. 只有验收标准全部满足,才算完成任务。" ) }, {"role": "user", "content": task} ] for step in range(max_iterations): print(f"===== Step {step + 1} =====") response = client.chat.completions.create( model="gpt-4o", messages=messages, tools=TOOL_SCHEMAS, tool_choice="auto" ) message = response.choices[0].message # 模型认为任务完成,不再调用工具 if message.tool_calls is None: print("Agent 最终回答:", message.content) return True messages.append(message) for tool_call in message.tool_calls: print(f"调用工具: {tool_call.function.name}") print(f"参数: {tool_call.function.arguments}") result = execute_tool_call(tool_call) print(f"工具返回: {json.dumps(result, ensure_ascii=False)[:500]}") messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) print("达到最大迭代次数,任务可能未完成。") return False if __name__ == "__main__": task = """ 开发一个名叫“接苹果”(Catch Apples)的浏览器小游戏。 游戏规则: - 玩家用鼠标左右移动底部的篮子。 - 苹果从屏幕上方不断落下。 - 接住苹果得 1 分。 - 漏掉 3 个苹果,游戏结束。 - 点击“重新开始”按钮可以重新开始游戏。 验收标准: - 使用单个 index.html 文件,包含 HTML、CSS、JavaScript。 - 游戏中不能使用外部依赖。 - 页面在桌面浏览器上打开即可运行。 - 需要显示当前得分和剩余生命。 - 代码需要通过 JavaScript 语法检查。 """ run_agent(task)这是一个非常精简的 Agent 实现。它没有记忆压缩,没有复杂的规划模块,也没有并发执行能力,但它具备了一个 Agent 最核心的能力:能够循环调用工具来改变环境,并通过观察环境反馈来修正下一步动作。
在实际项目中,你可能不会直接使用这种“手写循环”的方式,而是使用 LangChain、AutoGPT、或各种 Agent 框架。但理解这个最小循环的原理,对排查框架里的问题非常有帮助。
4.3 Agent 自动生成游戏代码
当 Agent 收到任务后,它第一步通常是将游戏核心代码写入index.html。以下是 Agent 经过几轮迭代后生成的一个可运行版本,代码里已经包含了我们要求的计分、生命、碰撞检测和重新开始功能。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>接苹果(Catch Apples)</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #1a1a2e; font-family: Arial, sans-serif; } #game-wrapper { text-align: center; } canvas { border: 2px solid #e94560; border-radius: 8px; background: #16213e; cursor: pointer; } #hud { color: #eee; margin-bottom: 8px; font-size: 18px; display: flex; justify-content: space-between; } #restart-btn { margin-top: 12px; padding: 8px 20px; font-size: 16px; background: #e94560; color: #fff; border: none; border-radius: 6px; cursor: pointer; } #restart-btn:hover { background: #c73652; } </style> </head> <body> <div id="game-wrapper"> <div id="hud"> <span>得分: <span id="score">0</span></span> <span>生命: <span id="lives">3</span></span> </div> <canvas id="game" width="480" height="640"></canvas> <br> <button id="restart-btn">重新开始</button> </div> <script> const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); const scoreEl = document.getElementById('score'); const livesEl = document.getElementById('lives'); const restartBtn = document.getElementById('restart-btn'); let score = 0; let lives = 3; let gameOver = false; let lastTime = 0; let appleSpawnInterval = 0; let keyPressed = false; const basket = { width: 80, height: 20, x: canvas.width / 2 - 40, y: canvas.height - 40 }; const apples = []; function resetGame() { score = 0; lives = 3; gameOver = false; apples.length = 0; appleSpawnInterval = 0; updateHUD(); } function updateHUD() { scoreEl.textContent = score; livesEl.textContent = lives; } function spawnApple() { const x = Math.random() * (canvas.width - 24); apples.push({ x: x, y: 0, size: 16, speed: 120 + Math.random() * 80 }); } function drawBasket() { ctx.fillStyle = '#f5c542'; ctx.fillRect(basket.x, basket.y, basket.width, basket.height); ctx.fillStyle = '#d49b2a'; ctx.fillRect(basket.x + 10, basket.y + basket.height, basket.width - 20, 6); } function drawApple(apple) { ctx.beginPath(); ctx.arc(apple.x + apple.size / 2, apple.y + apple.size / 2, apple.size / 2, 0, Math.PI * 2); ctx.fillStyle = '#e94560'; ctx.fill(); } function drawGameOver() { ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#ffffff'; ctx.font = '36px Arial'; ctx.textAlign = 'center'; ctx.fillText('游戏结束', canvas.width / 2, canvas.height / 2 - 10); } function update(deltaTime) { if (gameOver) return; // 生成新苹果 appleSpawnInterval += deltaTime; if (appleSpawnInterval > 1.2) { appleSpawnInterval = 0; spawnApple(); } // 移动苹果 for (let i = apples.length - 1; i >= 0; i--) { const apple = apples[i]; apple.y += apple.speed * deltaTime; // 判断是否被篮子接住 if ( apple.y + apple.size >= basket.y && apple.y + apple.size <= basket.y + basket.height + 10 && apple.x + apple.size >= basket.x && apple.x <= basket.x + basket.width ) { score++; apples.splice(i, 1); updateHUD(); continue; } // 判断是否落地 if (apple.y > canvas.height) { apples.splice(i, 1); lives--; updateHUD(); if (lives <= 0) { gameOver = true; drawGameOver(); return; } } } } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawBasket(); for (const apple of apples) { drawApple(apple); } if (gameOver) { drawGameOver(); } } function gameLoop(timestamp) { const deltaTime = Math.min((timestamp - lastTime) / 1000, 0.05); lastTime = timestamp; update(deltaTime); draw(); requestAnimationFrame(gameLoop); } // 鼠标移动篮子 canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); const mouseX = e.clientX - rect.left; basket.x = mouseX - basket.width / 2; basket.x = Math.max(0, Math.min(canvas.width - basket.width, basket.x)); }); // 重新开始 restartBtn.addEventListener('click', () => { resetGame(); }); resetGame(); requestAnimationFrame(gameLoop); </script> </body> </html>这是 Agent 自动生成代码的典型结果。你会发现它并不是特别复杂,但逻辑完整,能运行,也符合验收标准。
需要说明的是,这里展示的是最终“接近可运行”的版本。真实执行过程中,Agent 可能第一版会漏掉“生命值”“游戏结束”等功能,也可能出现苹果碰撞检测逻辑不准的情况。这些都会在后面的“自动修复”环节被逐一解决。
4.4 让 Agent 自测与修复
生成代码只是第一步,更重要的是验证和修复。
Agent 的验证通常分几个层次:
- 语法检查:用
node --check检查 JS 语法。 - 本地运行:启动一个静态服务。
- 浏览器冒烟测试:用 Playwright 等工具访问页面,检查控制台错误。
- 行为测试:模拟鼠标移动,判断游戏能否执行关键交互。
下面是一条典型的 Agent 验证命令:
node --check index.html不过node --check只能检查独立 JS 文件,不能直接检查 HTML 内嵌的<script>块。实际项目中,Agent 会把脚本提取成临时 JS 文件再检查,或者使用 Playwright 加载页面。
下面是一个用 Node.js 脚本做简单冒烟测试的示例:
// smoke_test.js const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch(); const page = await browser.newPage(); const errors = []; page.on('pageerror', error => errors.push(error.message)); page.on('console', msg => { if (msg.type() === 'error') { errors.push(msg.text()); } }); await page.goto('http://localhost:8000/index.html'); await page.waitForTimeout(2000); // 模拟鼠标移动 const canvas = await page.$('#game'); const box = await canvas.boundingBox(); await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); await page.waitForTimeout(1000); const scoreText = await page.textContent('#score'); console.log('当前得分:', scoreText); console.log('控制台错误:', errors.length ? errors : '无'); await browser.close(); if (errors.length > 0) { process.exit(1); } })();运行方式:
npm init -y npm install playwright node smoke_test.js如果 Agent 在冒烟测试中发现了问题,比如“苹果碰撞检测不生效”或“点击重新开始按钮后分数没有重置”,它会把报错信息或者测试失败信息重新拼进 prompt,再调用write_file修复代码,然后再次运行测试。这个循环会一直继续,直到测试通过。
这就是 Agent 自主开发的“反馈闭环”。它是整个系统中最有工程价值的部分。
4.5 发布上线
本地测试通过后,Agent 需要把游戏发布到线上。以 GitHub Pages 为例,发布流程如下:
- 初始化 Git 仓库。
- 创建并提交
index.html。 - 推送到远程仓库的
main分支或gh-pages分支。 - 通过仓库设置开启 GitHub Pages。
Agent 可以自动执行这些命令,但你需要提前准备好一个有仓库创建权限的 GitHub Token。注意:这里的 Token 应该只授予最小权限,并且用作环境变量,不能写死在代码里。
# 初始化仓库 git init git add index.html git commit -m "feat: catch apples game generated by AI agent" # 添加远程仓库 git remote add origin https://github.com/yourname/catch-apples.git git branch -M main # 推送代码 git push -u origin main推送完成后,在 GitHub 仓库的 Settings -> Pages 中把 Source 设置为Deploy from a branch,分支选择main,目录选择/root。
稍等片刻,游戏就能通过以下地址访问:
https://yourname.github.io/catch-apples/Agent 拿到这个 URL 后,会再次通过 HTTP 请求验证页面是否正常返回 200 状态码。确认无误后,Agent 才会在最终回复中告诉人类:任务已完成,并附上线上地址。
这里要特别提醒:在生产环境发布时,建议先发布到测试路径或者使用独立域名验证,确认无误后再切换到正式入口。尤其是自动发布的流程一旦跑起来,必须有回滚方案。
5. 常见问题与排查思路
AI Agent 自主开发虽然看起来很流畅,但实际运行时几乎必然会遇到各种问题。下面是一些高频问题和对应的排查经验。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Agent 陷入无限循环 | 缺少最大迭代次数限制 | 设置max_iterations,超时强制停止 |
| 生成了代码但无法运行 | 模型“幻觉”或依赖未安装 | 把运行时报错信息回传给模型继续修复 |
| 上下文窗口被塞满 | 工具返回日志太长 | 截断工具输出,或使用摘要压缩历史 |
| Node 命令找不到 | 环境变量未配置 | 在 prompt 中写明 Node 的安装路径 |
| 部署到 GitHub Pages 失败 | Token 权限不足或仓库设置错误 | 检查 Token 权限,使用最小权限原则 |
| 页面打开后空白 | JS 运行时错误 | 用 Playwright 做冒烟测试,捕获 console error |
| Agent 修改了无关文件 | 工具调用边界不清晰 | 在工作目录中创建独立沙箱文件夹 |
| API 调用超时 | 请求耗时过长 | 增加超时时间,并做重试机制 |
下面展开说明两个最常见问题的排查思路。
5.1 Agent 陷入死循环怎么办
现象:Agent 反复执行同一个操作,比如反复写入同一个文件,或者反复运行同一条测试命令,但结果没有任何变化。
排查步骤:
- 检查 Agent 日志,看它在循环中的“推理”内容是否相同。
- 如果模型每次都说“我再试一次”,说明它无法从当前状态中找到有效手段。
- 检查工具返回是否包含足够信息。如果测试命令只返回“fail”,没有具体报错,模型就只能盲猜。
- 提高工具返回的信息质量:把 stderr、退出码、关键日志都返回给 Agent。
解决方案:除了设置最大迭代次数,还要优化工具输出。一个原则是:工具返回的信息要足够让模型判断“为什么失败”,否则修复循环就无从谈起。
5.2 部署后页面空白
现象:GitHub Pages 返回 200,但浏览器打开后是空白页。
排查步骤:
- 按 F12 打开开发者工具查看 Console 面板,定位 JS 报错。
- 检查文件路径。如果使用了相对路径或外部资源,部署环境可能无法加载。
- 检查脚本中是否使用了浏览器不支持的 API。
- 在本地把部署目录用服务跑起来,对比线上表现。
解决方案:在 Agent 的验收标准中强制加入“冒烟测试”环节,用 headless 浏览器访问部署后的 URL,并检查控制台是否有 error。这样可以在上线前就发现问题。
6. 最佳实践与工程建议
经历了“让 Agent 开发并发布一个游戏”的完整流程后,有几个工程上的经验很值得沉淀下来。
6.1 任务描述必须包含验收标准
如果你只告诉 Agent “写一个游戏”,它可能会写出各种你没想到的功能,但偏偏缺了你最想要的那一个。更好的做法是像 4.1 节那样,把规则、操作方式、验收标准全部写清楚。
验收标准的作用有两个:
- 给 Agent 一个明确的“完成”定义,让它知道何时停止。
- 给人工审查提供一个检查清单,方便确认 Agent 没有“自欺欺人”。
6.2 坚持“Agent 生成、人工审查、自动化测试”三件套
即使 Agent 能自主完成整个流程,也不建议完全无人审核。尤其是涉及到线上发布时,一位有经验的开发者做最后 review,能拦截大部分模型“自认为正确但实际错误”的问题。
推荐的流程是:
- Agent 在分支或沙箱环境中生成代码并自测。
- 人工审查代码变更,检查安全问题。
- 自动化测试跑一遍关键路径。
- 合并代码并自动部署。
6.3 安全边界:给 Agent 一个沙箱
如果 Agent 能执行 shell 命令,那么它理论上也能执行任何命令,包括删除文件、获取环境变量、访问网络。因此在正式环境中,必须限制 Agent 的执行范围:
- 使用 Docker 容器运行 Agent,容器内不挂载宿主机敏感目录。
- 在容器外提供文件读写接口,而不是直接给 shell。
- 把
run_command改成命令白名单方式,比如只允许执行node、git、python3 -m http.server。 - 严禁 Agent 直接接触生产数据库或生产服务器,涉及生产变更必须走 CoPilot(人审流程)机制。
6.4 每一次工具调用都要留日志
Agent 开发过程的可追溯性非常重要。记录每次工具调用、参数、返回结果、耗时,能够帮你:
- 定位是哪一步出了问题。
- 复盘 Agent 的决策路径,优化 prompt。
- 评估 token 消耗和成本。
一个最小日志结构可以是 JSON 格式:
{ "step": 3, "tool": "run_command", "args": { "command": "node --check script.js" }, "result": { "returncode": 0, "stdout": "", "stderr": "" }, "timestamp": "2025-01-12T10:30:00Z" }6.5 控制上下文,避免 Token 爆炸
Agent 写游戏这种小项目时上下文压力不大,但如果项目变大,就需要引入“摘要 + 检索”的机制。例如:
- 让 Agent 先列出项目结构,只读取需要修改的文件。
- 工具返回内容限制在 2000 字符以内。
- 对已经完成的历史对话做摘要,压缩到几百字符。
这样既能保持 Agent 对全局的感知,又能控制成本。
6.6 不管用什么框架,核心都是“任务拆解 + 反馈闭环”
目前市面上的 Agent 框架很多,有的是“计划 + 执行”架构,有的是“多 Agent 协作”架构。但剥开外壳,所有 Agent 的核心能力都离不开:
- 理解目标并拆解成步骤。
- 调用工具改变环境。
- 观察结果并调整策略。
理解了这一点,你就能很快上手任何框架,也能在框架出问题时定位到具体环节。
7. 总结与下一步方向
通过这篇文章,我们从概念到实战完整走了一遍 AI Agent 自主开发并发布浏览器游戏的流程。
现在你应该已经理解:
- AI Agent 不只是聊天机器人,它是一个具备“规划 + 工具调用 + 环境反馈”能力的自主系统。
- Agent 开发游戏的过程不是“一次生成”,而是“写代码 -> 验证 -> 报错 -> 修复 -> 再验证”的循环。
- 一个最小 Agent 系统可以由几行核心代码实现,关键点在工具设计和执行循环。
- 自动发布不是不能做,但必须在安全边界内操作,并保留人工审查和回滚机制。
如果你打算继续深入,我建议按以下路线学习:
- 先手写一个更完善的 Agent 循环,加入记忆压缩、命令白名单、日志记录。
- 尝试接入不同模型,观察它们调用工具和修复 Bug 的差异。
- 让 Agent 开发一个带后端接口的小应用,比如“留言板”,这会引入数据库和服务器部署的问题。
- 学习 LangChain、AutoGPT、LangGraph 等框架,了解它们在任务规划和多步骤执行上做了哪些优化。
- 如果你想在企业里落地,重点关注权限控制、审计日志、测试覆盖率和回滚机制,这比模型本身的“聪明程度”更重要。
AI Agent 自主开发的能力每天都在变强。但有一点不会变:它能走多远,很大程度上取决于我们给它设计的工作流边界是否清晰、反馈回路是否高效。希望这篇实战笔记能帮助你迈出第一步,亲手搭一个属于自己的 Agent 开发流水线。
如果你按照流程跑通了自己的小游戏,欢迎在评论区交流过程中踩过的坑。