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);真正「怎么执行」可以放在客户端 useChat 的 tools 里。
这样分层的妙处:涉及隐私和真实副作用的操作(改 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
你现在的项目里,最想让智能体帮你自动点哪个按钮、读哪段状态?是表单自测、还是埋点校验?
如果让你给这个网页智能体再加一个工具,你会加什么——截图对比、滚动到指定位置、还是模拟拖拽?
把你的场景或想法写在评论区,我们一起把它变成下一段能跑的代码。