浏览器里跑能操作DOM的智能体

image.png

01 聊天框只会说话,智能体才会动手

2026-07-26,OpenAI 的 GPT-5.6 Sol、xAI 的 Grok 4.5、Meta 的 Muse Spark 1.1 在 48 小时内集中发布,清一色把「编程 / Agent」当成头号卖点。行业里一句话很到位:AI 编程进入了新阶段——不再比谁聊天更顺,而比谁的智能体能真正把事干完。

但前端圈聊 Agent,大多还停在「调个接口、渲染个对话气泡」。你想过没有:智能体能不能直接操作你正在看的网页? 点一下按钮、读一段 DOM 文本、往输入框里填个字——像真人一样。

这不是科幻。Vercel AI SDK 在 2026 年把「客户端工具调用(client-side tool calling)」做成了标准能力:模型在云端推理,动手的活儿交还给浏览器。本文就带你从 0 到 1 跑通一个「网页操作智能体」,而且代码完整可复制。

02 什么是「页面操作智能体」

传统聊天机器人是 输入文字 → 模型返回文字。智能体多了一步:输入文字 → 模型决定调用哪个工具 → 工具在真实环境执行 → 结果喂回模型 → 继续推理,这个循环叫 agentic loop。

放到网页里,「真实环境」就是浏览器 DOM。我们给模型三件工具:

  • readDom(selector):读取某个元素的可见文本;
  • clickElement(selector):点击一个元素;
  • fillInput(selector, value):往输入框填值并触发事件。

模型拿到用户指令后,自己拆解步骤、依次调用工具,最后用自然语言汇报「我点了什么、看到了什么」。关键点在于:工具的「执行」发生在浏览器,模型只负责「决策」。这就是为什么它能在你的真实页面上动手,而不是在一个沙箱里空谈。

03 为什么前端必须自己掌握这套能力

你可能会说:Cursor、Claude Code 不也能改代码?区别在于层级。

Cursor 改的是「源文件」,它的操作停留在磁盘上的文本。而网页智能体操作的是「运行时的页面」——也就是用户真正看到、真正在交互的那一层。这中间有一条鸿沟:源码写得对,不代表运行时 DOM 符合预期;表单的校验逻辑、按钮的禁用态、接口的 loading 态,只有跑起来才知道。

当智能体能读 DOM、能点击事件,它就从一个「代码生成器」升级成「页面操作工」。前端的价值,正从「手写每一行」转向「定义意图 + 监督智能体 + 兜底体验」。这恰恰是 we 前端开发者的新护城河:你比任何人都懂 DOM、事件流和渲染时机,所以你最会设计这些工具。

04 技术骨架:client-side tools 是怎么跑起来的

Vercel AI SDK 的核心设计是「工具定义与执行分离」。服务端在 streamText 里声明工具「长什么样」(名称、描述、参数 schema);真正「怎么执行」可以放在客户端 useChattools 里。

image.png 这样分层的妙处:涉及隐私和真实副作用的操作(改 DOM、发请求)留在浏览器,模型侧只传输「我要调 readDom(#price)」这样的意图,不直接碰你的页面对象。

依赖(版本以 2026 稳定线为准):

npm install ai @ai-sdk/openai @ai-sdk/react zod
# 需要 .env.local 配置 OPENAI_API_KEY=sk-***

05 完整 Demo:一个能动手的网页智能体

下面是一份可直接 npm run dev 跑起来的 Next.js(App Router)最小实现。先写服务端路由,只声明工具:

// app/api/agent/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';

export const maxDuration = 30;

// 服务端只声明工具"长什么样",真正的执行放在浏览器端
export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    // 三强齐发里的代表模型,可换成 grok-4.5 / claude
    model: openai('gpt-5.6-sol'),
    system:
      '你是一个页面操作智能体。优先用工具完成用户指令,' +
      '例如点击按钮、读取某区域文本、在输入框填写内容。' +
      '每一步操作后用简洁中文汇报你做了什么、看到了什么。',
    messages,
    tools: {
      readDom: tool({
        description: '读取页面中某个 CSS 选择器对应的可见文本',
        inputSchema: z.object({ selector: z.string().describe('CSS 选择器,如 "#price"') }),
      }),
      clickElement: tool({
        description: '点击页面上匹配选择器的元素',
        inputSchema: z.object({ selector: z.string() }),
      }),
      fillInput: tool({
        description: '在输入框中填入文本并提交',
        inputSchema: z.object({ selector: z.string(), value: z.string() }),
      }),
    },
    maxSteps: 6, // 允许模型连续调用多次工具,直到任务完成
  });

  return result.toDataStreamResponse();
}

然后是客户端组件,真正去操作 DOM:

// app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { z } from 'zod';

export default function Page() {
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({ api: '/api/agent' }),
    // 客户端工具:真正去操作浏览器 DOM
    tools: {
      readDom: {
        inputSchema: z.object({ selector: z.string() }),
        async execute({ selector }) {
          const el = document.querySelector(selector);
          return el ? (el.textContent ?? '').trim() : `未找到 ${selector}`;
        },
      },
      clickElement: {
        inputSchema: z.object({ selector: z.string() }),
        async execute({ selector }) {
          const el = document.querySelector(selector) as HTMLElement | null;
          if (!el) return `未找到 ${selector}`;
          el.click();
          return `已点击 ${selector}`;
        },
      },
      fillInput: {
        inputSchema: z.object({ selector: z.string(), value: z.string() }),
        async execute({ selector, value }) {
          const el = document.querySelector(selector) as HTMLInputElement | null;
          if (!el) return `未找到 ${selector}`;
          el.value = value;
          el.dispatchEvent(new Event('input', { bubbles: true }));
          return `已在 ${selector} 填入:${value}`;
        },
      },
    },
  });

  return (
    <main style={{ maxWidth: 720, margin: '40px auto', fontFamily: 'system-ui' }}>
      <h1>网页智能体 Demo</h1>
      <p>试试说:「点击 #like 按钮,然后读一下 #status 的文本」</p>

      <div style={{ display: 'flex', gap: 8, margin: '16px 0' }}>
        <button id="like" onClick~={() => {
          const s = document.querySelector('#status');
          if (s) s.textContent = '状态:已点赞 👍';
        }}>👍 点赞</button>
        <span id="status">状态:未操作</span>
      </div>

      <ul>
        {messages.map((m) => (
          <li key={m.id}>
            <b>{m.role === 'user' ? '你' : '智能体'}</b>:
            {m.parts.map((p, i) => {
              if (p.type === 'text') return <span key={i}>{p.text}</span>;
              if (p.type.startsWith('tool-')) {
                const t = p as unknown as { toolName: string; output: unknown };
                return <code key={i}> [工具:{t.toolName} → {JSON.stringify(t.output)}] </code>;
              }
              return null;
            })}
          </li>
        ))}
      </ul>

      <form onSubmit~={(e) => {
        e.preventDefault();
        const input = (e.target as HTMLFormElement).elements.namedItem('prompt') as HTMLInputElement;
        sendMessage({ text: input.value });
        input.value = '';
      }}>
        <input name="prompt" placeholder="给智能体下指令…" style={{ width: '70%', padding: 8 }} />
        <button type="submit" disabled={status !== 'ready'}>发送</button>
      </form>
    </main>
  );
}

跑起来后,输入「点击 #like 按钮,然后读一下 #status 的文本」,你会看到智能体先调用 clickElement,按钮的 onClick 把 #status 改成「已点赞」,接着 readDom 读回新文本,最后用中文汇报结果。一个能在你页面上动手的智能体就成了。

06 三个必须注意的坑

第一,工具审批(approval)。 如果工具副作用大(比如提交订单),别让模型自动执行。AI SDK 支持把工具定义为「需用户确认」,前端弹窗授权后才 addToolResult。本文 Demo 工具无副作用,直接执行没问题,但生产环境一定要按风险分级。

第二,maxSteps 是安全阀。 没有上限,一个「困惑」的模型可能无限循环调用工具。maxSteps: 6 是多数网页智能体的合理起点;研究型任务可以放到 10。

第三,模型选型呼应热点。 这次三强齐发里,GPT-5.6 Sol 工具编排最强、Grok 4.5 性价比最高(价格约 1/7)、Muse Spark 1.1 主打原生多 Agent。Demo 里把 openai('gpt-5.6-sol') 换成对应适配器即可,工具定义完全不用改——这就是「provider-agnostic」工具定义的价值。

07 能落地的场景与边界

真实能用的地方不少:表单自测(让智能体挨个填字段、触发校验、读报错)、E2E 辅助(用自然语言描述路径代替手写 Playwright 脚本)、可访问性巡检(让智能体遍历焦点顺序读 aria 属性)。

但边界要清醒。让 AI 操作 DOM 等于把「点击权」交出去,必须做权限白名单(只允许命中特定选择器的工具)、动作日志(每次操作可追溯)、以及危险操作的人工确认。另外,模型读的是「渲染后的文本」,对 Canvas、Shadow DOM、跨 iframe 内容无能为力——别指望它替你做像素级视觉校验。

金句收一下:模型负责「想」,工具负责「做」,前端负责「定义能做的边界」。

08

你现在的项目里,最想让智能体帮你自动点哪个按钮、读哪段状态?是表单自测、还是埋点校验?

如果让你给这个网页智能体再加一个工具,你会加什么——截图对比、滚动到指定位置、还是模拟拖拽?

把你的场景或想法写在评论区,我们一起把它变成下一段能跑的代码。