news 2026/9/12 15:07:09

AI Agent 自主开发浏览器游戏:从零搭建到自动化部署全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent 自主开发浏览器游戏:从零搭建到自动化部署全流程

之前看到一条很有意思的帖子:有人让 AI Agent 自己从想法开始,完成了选题、编码、测试甚至部署上线,最终交付了一个能直接打开的浏览器小游戏。很多人第一反应是“这有什么难的”,但真正操作过 Agent 开发的人会知道,从一段提示词到一个能稳定运行、并且真的发布到公网的项目,中间隔着非常多问题。上下文丢失、代码跑不通、循环卡死、发布失败,每一个坑都足以让整个流程中断。

这篇文章打算把这条链路完整拆开:AI Agent 自主开发到底是怎么运作的,为什么浏览器游戏适合作为 Agent 自驱项目,以及如果要自己复现一遍,从环境准备、Agent 循环编写、工具配置,到测试修复和发布,每一步应该怎么做。文章内容既有概念讲解,也有可以直接参照的代码骨架,适合正在研究 AI Agent 开发、想用 Agent 做自动化项目的开发者阅读。

1. 背景与核心概念:从“会写代码”到“能发布产品”

1.1 AI Agent 到底是什么

要理解这个项目的价值,先要把 AI Agent 和普通的大模型聊天工具区分开。我们平时使用 ChatGPT、Claude、Kimi 时,是在“对话模式”里让模型生成文字或代码片段,模型本身不执行代码,也不操作文件系统,它只是输出内容。AI Agent 则不同,它是在大模型能力之上,增加了任务拆解、工具调用、结果检查、失败重试这几个模块。

一个典型 AI Agent 可以做的事情包括:

  • 读取当前目录下的文件结构。
  • 创建、修改代码文件。
  • 调用终端命令执行程序。
  • 运行测试并读取测试结果。
  • 根据报错信息重新修改代码。
  • 重复“执行 → 检查 → 修复”循环,直到任务完成。
  • 最终调用部署命令,把应用发布到线上。

换句话说,大模型是“大脑”,工具调用是“手”,循环控制是“工作流程”。AI Agent 的意义在于,它把人类从“复制代码 → 粘贴到编辑器 → 手动运行 → 看报错 → 再问模型 → 再复制”这种半自动操作里解放出来,让模型直接接管整个执行过程。

1.2 为什么浏览器游戏适合作为 Agent 自驱项目

浏览器游戏是一个非常适合 Agent 练手的项目类型,原因有几点:

第一,技术栈简单直观。一个浏览器游戏只需要 HTML、CSS 和 JavaScript 三个文件就够了,不需要启动后端服务、不需要配置数据库、不需要处理复杂的第三方依赖。环境越简单,Agent 可控性越强,失败点越少。

第二,效果反馈直观。游戏能否运行、界面是否正常、点击是否响应、得分是否增加,这些都可以在浏览器中直接观察,或者通过 Node.js 执行自动化脚本来验证。这种强反馈非常适合 Agent 的自我纠错闭环。

第三,发布链路成熟。静态网页可以非常方便地托管到 GitHub Pages、Netlify、Vercel、Cloudflare Pages 等平台,整个发布过程可以由命令行工具自动完成。Agent 在代码完成后,只差最后一步“发布”,而这一步同样可以被 Agent 接管。

第四,项目边界清晰。“做一个乒乓球游戏”“做一个贪吃蛇游戏”“做一个躲避障碍物的小游戏”,这类任务描述简单,验收标准明确,模型不会因为需求模糊而陷入无效生成。

当然,Agent 是否能独立完成整个项目,关键不在游戏本身,而在于它的工程闭环是否完整。下面我们来拆解这个闭环。

1.3 Agent 自主开发的边界与局限

在开始实战前,有必要对“自主开发”有一个清醒的认识。目前 AI Agent 能独立完成的,更多是任务边界清晰、工具链成熟、反馈信号明确的小型项目。它并不像人类工程师那样具备长期记忆、全局架构设计能力和复杂业务理解能力。

实际操作中会遇到的典型问题包括:

  • Agent 写出的代码在小范围测试时正常,但一旦修改某个函数,其他依赖该函数的地方没有同步更新。
  • Agent 在长对话中会遗忘早期定义的需求细节,导致生成内容前后不一致。
  • Agent 循环在某个报错上来回打转,反复生成近似但无法解决问题的代码。
  • Agent 调用工具时可能误删文件、覆盖已有配置,或者在没有权限的情况下执行了高危险命令。

这些局限性并不是否定 Agent 的能力,而是提醒我们:如果想让 Agent 自主开发项目,必须给它搭建足够清晰的工作环境、足够严格的验收机制,并且尽可能把危险操作隔离在沙箱范围内。这也是后面几个章节要解决的重点问题。

2. Agent 自主开发的核心工作流

2.1 大模型与 Agent 的关系

大模型本身不具备“行动”能力,它只负责根据输入生成文本。要让模型真正完成一个项目,需要把它放在一个循环中。这个循环的基本结构是:

分析当前状态 → 生成下一步动作 → 执行动作 → 获取执行结果 → 再次分析

每次循环中,模型都会接收到一组描述当前任务状态的文本。这组文本通常包括:

  • 任务目标:项目要做什么,最终交付物是什么。
  • 当前文件结构:项目目录下有哪些文件。
  • 最近一次动作的结果:比如代码运行后的输出、测试报告、报错信息。
  • 可用的工具列表:模型可以选择调用哪些工具来完成操作。

模型根据这些信息,输出一个动作指令。Agent 运行时解析这个指令,调用对应的工具函数,再把工具返回的结果拼接成新的文本,交给模型继续处理。这样的循环反复执行,直到模型判断任务完成,或者达到了预设的最大迭代次数。

2.2 从任务规划到工具调用的闭环

在一个完整的 Agent 开发任务中,典型的动作序列如下:

  1. 创建项目目录。
  2. 初始化基础文件,比如index.htmlstyle.cssgame.js
  3. 编写基础代码。
  4. 启动本地服务器或执行静态检查。
  5. 运行自动化测试,验证页面元素是否正常加载。
  6. 如果测试失败,读取报错日志,修改代码。
  7. 重复测试和修复,直到通过。
  8. 执行部署命令,把静态文件发布到线上托管平台。
  9. 访问线上地址,确认页面可以正常打开。

每一步都通过工具调用实现。工具可以是 AI Agent 运行时提供的内置函数,比如:

  • read_file(path):读取文件内容。
  • write_file(path, content):写入或覆盖文件。
  • list_files(directory):列出目录下的文件。
  • run_command(command):执行终端命令。
  • http_request(url):发送 HTTP 请求。

Agent 框架的价值,就是把这些工具封装成模型可以理解的形式,并在模型输出动作后可靠地执行它们。

2.3 反馈与异常恢复机制

闭环里最容易出问题的是“反馈”环节。代码执行后,“成功”和“失败”并不总是那么明确。比如一个游戏页面虽然能打开,但屏幕上的元素布局错乱;命令行工具返回了 0 退出码,但实际上并没有完成预期操作;测试脚本本身写得不对,导致测试结果失真。

因此,Agent 的反馈机制需要尽可能提供结构化信息。常见做法包括:

  • 命令执行后同时返回退出码、标准输出、标准错误。
  • 测试脚本使用明确的断言,失败时输出具体的错误信息。
  • Agent 每完成一步,都把当前文件列表和关键代码片段保存为状态快照。
  • 设置最大重试次数,避免死循环。

理解了这个基本工作流,下面就可以搭环境、写代码了。

3. 环境准备与工具选型

这篇文章的核心不是推荐某一个具体框架,而是给出一套可以灵活组合的技术方案。因为 Agent 相关框架更新非常快,版本差异很大,直接写死某个版本号意义不大。以下以常见的 Python 和 Node.js 环境为例,重点演示思路。

3.1 运行环境

建议环境如下:

  • 操作系统:macOS / Linux / Windows 均可,Windows 下建议使用 PowerShell 或 WSL。
  • 编程语言:Python 3.9 或以上,用于编写 Agent 控制逻辑。
  • Node.js 16 或以上,用于本地运行和调试浏览器游戏。
  • Git:用于代码版本管理。
  • 代码编辑器:VS Code 即可,配合终端使用。

如果你准备让 Agent 调用大模型 API,还需要一个可用的 API Key,并在本地配置好环境变量。

3.2 LLM API 的选择

目前主流的做法是调用大模型厂商提供的 API,常见的有 OpenAI、Anthropic、Google Gemini,以及国内多家大模型平台提供的兼容接口。无论选择哪一家,都要注意以下几点:

  • 模型需要支持函数调用(Function Calling)或工具调用(Tool Use),这是 Agent 循环能否成立的关键能力。
  • API 的上下文长度要足够大,因为 Agent 每次循环都要把文件内容、终端输出、指令信息拼进上下文。
  • 需要根据项目复杂度设置合理的最大 token 上限,避免长输出被截断。
  • 不同模型的行为差异很大,建议先用小任务测试模型的工具调用稳定性,再放到完整项目里使用。

由于 API 配置方式会随着官方文档快速变化,这里不贴具体密钥配置,而是给一个通用的环境变量示例:

export LLM_API_KEY="your-api-key" export LLM_BASE_URL="https://api.example.com/v1" export LLM_MODEL="your-model-name"

3.3 Agent 框架的选择思路

编写 Agent 有两种路线:一种是直接使用现成的 Agent 框架,另一种是自己实现一个精简的 Agent 循环。

现成框架的优势是工具调度、循环控制、上下文管理都已经处理好,缺点是框架本身有学习成本,而且很多框架仍在快速迭代,API 可能频繁变化。常见开源方案有 AutoGen、LangGraph,以及一些面向编码场景的 Agent 工具。这类工具比较适合不想从零造轮子、并且愿意跟随社区更新的开发者。

自研精简循环则适合学习原理、调试问题,也更适合这篇文章后续的代码演示。自研循环的核心代码量其实不大,大概几十到上百行就能完成一个最简版本,并且能让你清楚地看到每一步 Agent 是如何决策的。

3.4 浏览器游戏的发布渠道

浏览器游戏如果只包含静态文件,最简单的发布方式是托管到静态网站服务上。常见的免费选项包括 GitHub Pages、Netlify、Vercel、Cloudflare Pages。它们的共同特点是:支持从 Git 仓库自动构建,也支持通过命令行工具直接上传本地目录。

Agent 发布时,可以选择以下任意一种方式:

  • 使用gh命令行工具创建仓库并推送代码,然后开启 GitHub Pages。
  • 使用 Netlify CLI 执行netlify deploy --prod --dir=dist
  • 使用 Vercel CLI 执行vercel --prod

这些命令通常需要提前在本地登录授权,建议在运行 Agent 前先手动完成一次认证,避免 Agent 在无人干预的情况下卡在交互式登录环节。

4. 实战:让 Agent 从零构建并发布一个浏览器游戏

下面进入完整实战,目标非常清晰:让 Agent 独立完成一个浏览器小游戏的开发与发布。我们选一个比较经典、不容易出错的例子:接水果小游戏。玩家通过鼠标或键盘控制一个篮子,接住从屏幕顶部掉落的水果,每接住一个得分增加,漏掉则减少生命值。

为了聚焦在 Agent 工作流本身,我们先把任务定义清楚,然后实现一个最小可运行的 Agent 调度器。

4.1 需求定义与项目初始化

Agent 启动前,我们需要给它一个明确的任务描述。任务描述越具体,Agent 的产出越可控。这里给出一个示例任务说明:

目标:创建一个可玩的网页接水果游戏。 项目目录:fruits-game 文件: - index.html:游戏页面主结构。 - style.css:页面和游戏元素样式。 - game.js:游戏逻辑,包含画布绘制、水果下落、玩家移动、得分与生命值。 功能需求: 1. 使用 HTML5 Canvas 绘制游戏。 2. 水果从画布顶部随机水平位置生成,并以固定速度下落。 3. 玩家通过键盘左右键控制底部篮子移动。 4. 接住水果得分增加 10 分,水果掉落则生命值减 1。 5. 生命值为 0 时游戏结束,显示最终得分。 6. 点击按钮可重新开始游戏。 额外要求: - 页面需要适配常见屏幕宽度。 - 游戏启动后自动运行,无需额外操作。 - 代码保持简洁,不带外部依赖。

在实际项目中,你可以把这段任务描述保存为TASK.md文件,Agent 每次循环都会读取该文件,避免上下文遗忘。

初始化目录只需要一条命令:

mkdir -p fruits-game && cd fruits-game && echo "" > TASK.md

4.2 编写 Agent 执行循环

接下来实现一个精简的 Agent 循环。这里为了演示原理,不使用任何第三方框架,而是直接用大模型 API 完成工具调用。以 OpenAI 风格的接口为例,核心逻辑是:把系统提示词、任务描述、当前状态、工具定义发送给模型,模型返回工具调用指令,运行时执行指令并反馈结果,循环往复。

# agent_loop.py # 这是一个精简版 Agent 执行循环,用于演示核心机制。 # 需要安装 openai 库:pip install openai # 注意:不同版本 SDK 的接口可能略有差异,请以官方文档为准。 import json import os from openai import OpenAI client = OpenAI( api_key=os.environ.get("LLM_API_KEY"), base_url=os.environ.get("LLM_BASE_URL"), ) TOOLS = [ { "type": "function", "function": { "name": "run_command", "description": "在项目目录中执行 shell 命令", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "要执行的 shell 命令" } }, "required": ["command"] } } }, { "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", "description": "文件路径" } }, "required": ["path"] } } } ] def run_command(command): """执行命令并返回输出。允许命令中携带 cd 操作,便于切换目录。""" import subprocess result = subprocess.run( command, shell=True, capture_output=True, text=True, cwd=os.getcwd(), timeout=60 ) output = "" if result.stdout: output += result.stdout if result.stderr: output += result.stderr return output, result.returncode def write_file(path, content): """写入文件,自动创建路径。""" os.makedirs(os.path.dirname(path) or ".", exist_ok=True) with open(path, "w", encoding="utf-8") as f: f.write(content) return f"文件已写入: {path}", 0 def read_file(path): """读取文件内容。""" try: with open(path, "r", encoding="utf-8") as f: return f.read(), 0 except FileNotFoundError: return f"文件不存在: {path}", 1 def execute_tool(name, arguments): """工具分发入口。""" args = json.loads(arguments) if name == "run_command": return run_command(args.get("command", "")) elif name == "write_file": return write_file(args.get("path", ""), args.get("content", "")) elif name == "read_file": return read_file(args.get("path", "")) return f"未定义工具: {name}", 1 def build_messages(task_file, status_file, history): """组装发送给模型的上下文。""" task_content = read_file(task_file)[0] status_content = read_file(status_file)[0] system_prompt = ( "你是一名全栈工程师,负责把一个网页小游戏项目从零开发到发布。\n" "你会获得一个工具列表,请根据当前状态决定下一步动作。\n" "每次只调用一个工具,等待执行结果后再继续。\n" "当任务完成时,输出 action=done 以及完成说明。\n" ) messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"任务描述:\n{task_content}\n\n当前状态:\n{status_content}"} ] messages.extend(history) return messages def agent_loop(max_steps=50): """主循环。""" history = [] task_file = "TASK.md" status_file = "STATUS.md" # 初始化状态文件 write_file(status_file, "项目刚创建,还没有任何代码。\n") for step in range(max_steps): print(f"----- Step {step + 1} -----") messages = build_messages(task_file, status_file, history) response = client.chat.completions.create( model=os.environ.get("LLM_MODEL", "gpt-4o-mini"), messages=messages, tools=TOOLS, tool_choice="auto" ) message = response.choices[0].message if message.tool_calls: for tool_call in message.tool_calls: name = tool_call.function.name arguments = tool_call.function.arguments print(f"调用工具: {name} -> {arguments}") result, code = execute_tool(name, arguments) print(f"工具返回: {result[:500]}") # 把工具调用和结果加入历史 history.append({ "role": "assistant", "tool_calls": [ { "id": tool_call.id, "type": "function", "function": { "name": name, "arguments": arguments } } ] }) history.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result }) else: content = message.content or "" print(f"模型回复: {content[:500]}") if "action=done" in content: print("Agent 判断任务完成,退出循环。") write_file(status_file, "任务完成。请验证产物。\n") return True # 非工具回复也加入历史,避免模型重复发言 history.append({"role": "assistant", "content": content}) print("达到最大步数,强制退出。") return False if __name__ == "__main__": agent_loop()

这段代码虽然精简,但已经具备了一个 Agent 循环的核心能力:模型可以生成工具调用指令,运行时执行指令并把结果回传。真实场景里还需要加上更完善的错误捕获、上下文裁剪、历史消息压缩、以及并发控制,但原理是一致的。

4.3 定义 Agent 可使用的命令与约束

为了让 Agent 更安全地工作,工具列表不应该是无限制的。实际项目里,建议把可用命令限制在少数几个:

# 允许的命令示例 ls -la node game.js python3 -m http.server 8080 npm test git status git add . git commit -m "feat: init game" git push origin main

不建议开放的命令包括:

  • rm -rf:删除文件,风险极高。
  • curl下载未知脚本并执行:容易引入外部风险。
  • sudo:需要权限提升,Agent 环境中应该禁用。
  • 任何涉及修改系统配置、环境变量、密钥的操作。

你可以在 Agent 的run_command函数里增加一个白名单校验,只有符合规则的命令才允许执行。例如:

BLOCKLIST = ["rm -rf", "sudo", "curl", "wget", "chmod 777"] def is_safe_command(command): for bad in BLOCKLIST: if bad in command: return False return True

如果命令触发了黑名单,直接返回错误信息,避免对宿主机造成不可控影响。

4.4 让 Agent 生成游戏代码

当 Agent 循环准备好之后,剩下的工作就是让模型自己动手。为了让过程更可观察,建议在STATUS.md文件中持续记录当前进度。状态文件的内容由 Agent 每个阶段更新一次,示例:

- [x] 创建项目目录 - [x] 生成 index.html - [x] 生成 style.css - [ ] 生成 game.js - [ ] 本地运行验证 - [ ] 自动化测试 - [ ] 部署到线上 - [ ] 访问确认

Agent 每完成一步,会调用write_file更新状态。这样一方面可以帮助模型自己记住进度,另一方面也方便你在 Agent 中断时快速定位问题。

需要说明的是,Agent 生成的游戏代码并不一定是“一次到位”的。在一些情况下,模型第一次生成的代码可能存在语法错误,或者逻辑不符合预期。此时 Agent 需要通过运行命令读取报错信息,再针对性地修改代码。这也是为什么工具列表里必须有run_commandread_file这两个函数。

我们也可以预演一下最终游戏结果的核心代码。以接水果游戏为例,模型最终可能会生成类似下面这样的代码框架:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>接水果小游戏</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="game-wrapper"> <canvas id="gameCanvas" width="600" height="400"></canvas> <div class="hud"> <span>得分: <b id="score">0</b></span> <span>生命: <b id="lives">3</b></span> </div> <button id="restartBtn">重新开始</button> </div> <script src="game.js"></script> </body> </html>
/* style.css */ * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #1a1a2e; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: Arial, sans-serif; color: #fff; } .game-wrapper { text-align: center; background: #16213e; padding: 20px; border-radius: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); } canvas { display: block; margin: 0 auto 16px; background: #0f3460; border-radius: 8px; touch-action: none; } .hud { display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 18px; }
// game.js const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const scoreEl = document.getElementById('score'); const livesEl = document.getElementById('lives'); const restartBtn = document.getElementById('restartBtn'); let score = 0; let lives = 3; let gameOver = false; let basket = { x: canvas.width / 2 - 40, y: canvas.height - 40, width: 80, height: 20 }; let fruits = []; let fruitSpeed = 2; let spawnInterval = 40; let frame = 0; let keys = {}; function resetGame() { score = 0; lives = 3; gameOver = false; fruits = []; frame = 0; updateHud(); } function updateHud() { scoreEl.textContent = score; livesEl.textContent = lives; } function spawnFruit() { const x = Math.random() * (canvas.width - 24); fruits.push({ x: x, y: -24, size: 24, speed: fruitSpeed + Math.random() * 1.5 }); } function drawBasket() { ctx.fillStyle = '#e94560'; ctx.fillRect(basket.x, basket.y, basket.width, basket.height); } function drawFruits() { ctx.fillStyle = '#f5a623'; for (let fruit of fruits) { ctx.beginPath(); ctx.arc(fruit.x + fruit.size / 2, fruit.y + fruit.size / 2, fruit.size / 2, 0, Math.PI * 2); ctx.fill(); } } function update() { if (gameOver) return; if (keys['ArrowLeft'] && basket.x > 0) { basket.x -= 6; } if (keys['ArrowRight'] && basket.x + basket.width < canvas.width) { basket.x += 6; } if (frame % spawnInterval === 0) { spawnFruit(); } for (let i = fruits.length - 1; i >= 0; i--) { let fruit = fruits[i]; fruit.y += fruit.speed; // 判断接住 if ( fruit.y + fruit.size >= basket.y && fruit.y + fruit.size <= basket.y + basket.height && fruit.x + fruit.size >= basket.x && fruit.x <= basket.x + basket.width ) { fruits.splice(i, 1); score += 10; updateHud(); continue; } // 判断掉落 if (fruit.y > canvas.height) { fruits.splice(i, 1); lives -= 1; updateHud(); if (lives <= 0) { gameOver = true; } } } frame++; } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawBasket(); drawFruits(); if (gameOver) { ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#fff'; ctx.font = '28px Arial'; ctx.textAlign = 'center'; ctx.fillText('游戏结束', canvas.width / 2, canvas.height / 2 - 10); ctx.font = '16px Arial'; ctx.fillText('最终得分: ' + score, canvas.width / 2, canvas.height / 2 + 30); } } function gameLoop() { update(); draw(); requestAnimationFrame(gameLoop); } document.addEventListener('keydown', (e) => { keys[e.key] = true; if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { e.preventDefault(); } }); document.addEventListener('keyup', (e) => { keys[e.key] = false; }); restartBtn.addEventListener('click', resetGame); resetGame(); gameLoop();

以上代码只用于演示 Agent 最终可能生成的产物形态。在实际运行中,这段代码并不一定由你手动编写,而是 Agent 在循环中逐步生成和修正的。你需要关注的重点是 Agent 如何验证这段代码能正常工作。

4.5 自动测试与修复闭环

代码生成后,Agent 不能只在“文件已经写入”之后就宣布成功。合理的做法是增加一个验证阶段。接口验证和页面验证可以分开来看。

首先是接口验证。如果你的游戏逻辑被封装成了可调用的函数,可以通过 Node.js 编写一个简单的单元测试来验证。对于纯浏览器 Canvas 游戏来说,直接在 Node 里跑 DOM 相关的代码比较麻烦,所以更常用的验证方式是:

  • 启动本地静态服务器。
  • 使用无头浏览器(例如 Playwright)打开页面。
  • 检查页面标题、Canvas 元素、按钮是否存在。
  • 检查页面上是否有 JS 报错。
  • 如果条件允许,模拟一次按键操作,确认游戏界面没有崩溃。

示例验证脚本如下:

# 安装 Playwright npm init -y npm install @playwright/test npx playwright install chromium
// verify-game.js const { test, expect } = require('@playwright/test'); test('游戏页面可以正常打开并显示画布', async ({ page }) => { await page.goto('http://localhost:8080'); // 检查标题 await expect(page).toHaveTitle(/接水果/); // 检查画布存在 const canvas = page.locator('#gameCanvas'); await expect(canvas).toBeVisible(); // 检查按钮存在 const restartBtn = page.locator('#restartBtn'); await expect(restartBtn).toBeVisible(); // 等待一段时间,确认没有崩溃 await page.waitForTimeout(1000); // 截图存档 await page.screenshot({ path: 'game-screenshot.png', fullPage: true }); });

Agent 循环中,当测试失败时,脚本会把失败原因、截图路径、控制台日志传给模型。模型根据这些反馈修改游戏代码。这个循环可能会重复几次,直到测试通过。

如果希望在无人值守的情况下自动验证,Agent 需要具备以下判断逻辑:

如果测试命令退出码为 0 且关键断言全部通过: 进入下一阶段 否则: 读取测试输出中的错误信息 把错误信息和当前代码文件一起发送给模型 让模型生成修复后的代码 重新运行测试

4.6 部署与展示

当本地验证通过后,Agent 可以进入发布阶段。这里以 GitHub Pages 为例,因为它的配置最简单,而且不涉及本地额外的命令行工具。

# 初始化 Git 仓库 git init git add . git commit -m "feat: complete fruits game" # 在 GitHub 上创建仓库(需要提前安装 gh 命令行工具) gh repo create fruits-game --public --source=. --remote=origin --push # 开启 GitHub Pages,使用 main 分支 gh api repos/{owner}/fruits-game/pages \ -X POST \ -f "source[branch]=main" \ -f "source[path]=/"

如果你使用其他托管平台,命令会略有不同:

# Netlify 部署 netlify deploy --prod --dir=. # Vercel 部署 vercel --prod

部署完成后,Agent 还需要访问线上地址,确认页面能正常打开。这也是一个可以自动化的环节。例如使用curl检查页面返回状态码,或者用 Playwright 直接访问线上域名。

curl -I https://你的用户名.github.io/fruits-game/

如果返回200 OK,发布成功的概率就非常高了。

5. 常见错误与排查思路

Agent 自主开发项目时,报错类型和人类开发遇到的报错高度相似,只是排查过程中没有人类工程师在旁边直接判断。下面整理了几类高频问题。

5.1 高频异常与处理建议

问题现象常见原因解决思路
Agent 循环在同一个报错上反复重试,没有进展模型没有从报错中提取到关键信息,或者每次生成的代码差异很小暂停循环,手动读取最新的报错日志,把日志原文和受影响的代码文件一起重新提交给模型,并要求先解释报错原因再改代码
Agent 执行命令超时本地服务器进程没有退出,或命令等待输入在命令中加入 timeout 限制,例如timeout 10 node server.js;避免启动阻塞型服务时使用无超时命令
Agent 写入的文件内容被截断模型输出 token 达到上限,或历史消息过长压缩了输出空间用 write_file 分段写入代码;把大型文件按函数拆分为多个文件;清理历史消息,保留最近几轮工具调用结果
模型声称任务完成,但页面并没有真正生效模型只检查了文件写入成功,没有进行实际页面验证在 Agent 循环中增加强制验证阶段,只有接口测试通过才能继续;不让模型只凭“文件存在”判断成功
发布后线上页面 404项目文件没有提交到正确分支,或 GitHub Pages 配置路径不对检查仓库文件结构,确认根目录下有 index.html;确认 Pages 开启,并且指定了正确分支和目录
Agent 执行环境被意外修改开放的 shell 命令执行了危险操作对所有命令做白名单校验,禁止删除、权限提升、下载执行等高风险操作;生产环境使用隔离环境运行 Agent
“provider did not respond in time” 或执行被终止API 服务超时、请求过大、网络不稳定,或者 Agent 循环被外部中断增加超时重试机制,单次请求设置较长超时时间;压缩上下文;将长任务拆成多个子任务,分段恢复执行

这类问题大多不是 Agent“笨”,而是上下文和反馈机制没有设计好。好的反馈信息是 Agent 能持续前进的核心。

6. 最佳实践与工程建议

经过多次 Agent 驱动项目的实践,下面这些经验对成功率提升非常明显。

6.1 任务拆解策略

不要把一个复杂项目一次性丢给 Agent。拆解粒度尽量控制在“一次循环能完成一个可验证的小步骤”的级别。例如:

  • 先让 Agent 编写一个空的 HTML 页面,并确认能打开。
  • 再让 Agent 加入 Canvas 画布。
  • 再让 Agent 实现角色移动。
  • 接着实现物品生成。
  • 最后实现计分和游戏结束逻辑。

每一步都伴随验证。这样即使某一步出错,也只需要修复局部代码,不会影响整个项目结构。

6.2 上下文管理

Agent 的最大瓶颈是上下文长度。项目代码越长、工具调用记录越多,可用上下文就越少。建议采用以下策略:

  • 固定保留TASK.md中的原始需求。
  • 不把完整代码文件反复发送给模型,只发送最近修改部分和报错信息。
  • 定期压缩历史消息。例如只保留最近 10 轮工具调用结果。
  • 状态文件保持精简,不记录冗余历史。
  • 分模块管理代码文件,避免单个文件过大。

6.3 安全与权限边界

Agent 可以运行 shell 命令,这意味着它拥有你在当前终端中的权限。为了安全,要注意:

  • 在专用目录下运行 Agent,不要让 Agent 直接操作系统根目录或项目外目录。
  • 使用白名单命令限制工具能力。
  • 不要把生产环境密钥直接写入环境变量。Agent 代码一旦被提示词注入,可能造成密钥泄露。
  • 涉及云平台部署时,优先使用独立的部署账号或临时凭证。
  • 发布到公网的代码默认视为公开代码,确认代码里没有敏感信息。
  • 如果 Agent 需要访问 GitHub 远程仓库,建议使用最小权限的 Personal Access Token(PAT),不要使用具备全部权限的账号令牌。

6.4 结果验收标准

在 Agent 任务开始时,就要定义“完成”的含义。建议至少包含以下验收条件:

  • 项目目录包含需求中要求的全部文件。
  • 本地服务器可正常启动。
  • 自动化测试全部通过。
  • 线上地址返回 200 状态码。
  • 页面截图作为交付物存档。

如果条件允许,还可以用无头浏览器录制一段 10 秒操作视频,用于最终人工确认。

7. 总结与下一步实践

AI Agent 独立开发并发布浏览器游戏,本质上是一次“任务理解 → 工具调用 → 结果反馈 → 修复迭代 → 部署上线”的闭环演练。这个闭环不只在游戏场景有效,它同样适用于文档生成、数据清洗、API 调用、原型设计等多种任务。

如果看完这篇文章,你想自己动手验证一遍,建议按照下面的顺序尝试:

  • 先搭建一个最小 Agent 循环,不要一上来就接入复杂框架。
  • 选择一个小型静态项目作为练手,比如一个时钟页面、一个待办清单。
  • 手动执行一次完整的开发流程,记录每一步的报错和反馈,再把这些经验写成提示词模板。
  • 逐步增加自动化验证和发布步骤,最后再尝试让 Agent 全自动运行。

一步到位很困难,但把流程拆成可控的小步骤后,Agent 的完成度会明显提高。后续你还可以进一步研究:Agent 的记忆能力、长上下文管理、多 Agent 协作、以及如何把 Agent 能力集成到自己的工程链路中。这些都是 AI Agent 开发方向里非常有价值的延伸话题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 16:19:26

一次渲染三语宣传片:Remotion 多语言视频生成完整指南

一次渲染三语宣传片&#xff1a;Remotion 多语言视频生成完整指南 【免费下载链接】remotion &#x1f3a5; Make videos programmatically with React 项目地址: https://gitcode.com/GitHub_Trending/re/remotion 同一条视频每种语言做一遍、工程推倒重来&#xff0c;…

作者头像 李华
网站建设 2026/9/4 16:19:02

Matlab PortfolioCVaR实战:从VaR到CVaR的组合优化与有效前沿

简介&#xff1a;本资源是一套基于Matlab金融工具箱的CVaR投资组合优化实践代码&#xff0c;面向计算机、电子信息工程、数学等专业的本科生与研究生&#xff0c;用于课程设计、期末大作业及毕业设计中的金融建模与风险量化实践。代码依托PortfolioCVaR对象实现条件风险价值&am…

作者头像 李华
网站建设 2026/9/4 12:51:34

前端面试“八股文”背后:深度不足的表现与提升建议

最近一段时间&#xff0c;陆陆续续面了不少公司&#xff0c;也帮朋友做了几轮模拟面试&#xff0c;加上自己在工作里带人、做技术评审&#xff0c;越来越有一种强烈的感受&#xff1a; 现在前端面试的“八股文”背得越来越溜&#xff0c;但真正问到技术深度的时候&#xff0c;…

作者头像 李华
网站建设 2026/9/2 5:05:18

数据分析师笔试高频考点:SQL、概率统计与业务分析全复盘

作为一名把几年时间都耗在数据分析这条路上的从业者&#xff0c;看到"京东2018秋招数据分析工程师笔试题"这个标题&#xff0c;第一反应不是去回忆当时的考题&#xff0c;而是感慨那几年正好是互联网数据分析岗从"会用Excel的运营"向"懂统计会写SQL的…

作者头像 李华