前端 AI Agent 编排引擎:从 ReAct 循环到生成式 UI(完整版)
引言
2026 年的前端不再是"调接口画页面",而是把页面变成 Agent 的可执行环境:
- LLM 负责"想"(规划、决策、反思)
- 前端负责"做"(工具注册、状态流转、组件生成、人工兜底)
- 编排引擎负责"管"(图执行、中断恢复、流式事件、权限边界)
本文给你一套可落地的浏览器/前端 Agent 编排引擎,不依赖后端黑盒,核心在 TS 层自研:
- Agent Loop:Plan → Act → Observe → Reflect
- 图编排:节点、边、条件路由、检查点
- 工具系统:前端工具 + 云端工具 + 浏览器操作
- 事件协议:AG-UI 风格流式事件
- 生成式 UI:工具调用直接变组件
- 人机协同:Interrupt / 审批 / 回滚
- 端侧混合:WebGPU 规划 + 云端复杂推理
一、整体架构(前端视角)
scss
┌────────────────────────────────────────────┐
│ UI Layer │
│ 聊天流 / 步骤时间线 / 生成式卡片 / 中断弹窗 │
└───────────────┬────────────────────────────┘
│ SSE / WebSocket / EventBus
┌───────────────▼────────────────────────────┐
│ Orchestration Engine │
│ GraphRunner · Scheduler · Checkpoint · Guard │
└───────┬───────────────┬───────────────┬─────┘
│ │ │
┌───────▼──────┐ ┌──────▼──────┐ ┌─────▼──────┐
│ Planner(LLM) │ │ Tool Registry│ │ Memory │
│ WebGPU/云端 │ │ 前端/浏览器 │ │ 短期/长期 │
└──────────────┘ └─────────────┘ └────────────┘
原则(生产级):
- AI 做难的部分,代码做确定部分
- 工具优先:Agent 只通过工具改变世界
- 状态可观测:思考/调用/结果全走事件流
- 高风险必须人确认
二、核心数据模型
2.1 事件协议(AG-UI 风格)
css
// agent/protocol.ts
export type AgentEvent =
| { type: 'run:start'; runId: string; threadId: string }
| { type: 'think'; text: string } // 思考流
| { type: 'tool:call'; callId: string; name: string; args: any; status: 'pending' }
| { type: 'tool:result'; callId: string; ok: boolean; result: any; error?: string }
| { type: 'state:snapshot'; state: AgentState }
| { type: 'state:delta'; patch: JsonPatchOp[] }
| { type: 'interrupt'; kind: 'approve' | 'choice' | 'form'; payload: any }
| { type: 'token'; text: string }
| { type: 'run:end'; status: 'success' | 'error' | 'interrupted' }
| { type: 'log'; level: 'info' | 'warn' | 'error'; message: string };
export interface AgentState {
goal: string;
step: number;
maxSteps: number;
status: 'idle' | 'planning' | 'acting' | 'reflecting' | 'waiting_human';
scratchpad: { role: 'thought' | 'action' | 'observation'; content: string }[];
shared: Record<string, any>; // 共享状态(UI 直接绑定)
tools: string[];
}
type JsonPatchOp =
| { op: 'add'; path: string; value: any }
| { op: 'replace'; path: string; value: any }
| { op: 'remove'; path: string };
三、工具系统(Tool Registry)
3.1 工具定义
typescript
// agent/tool.ts
import { z } from 'zod';
export interface ToolContext {
signal: AbortSignal;
emit: (e: Partial<AgentEvent>) => void;
}
export interface AgentTool<TIn = any, TOut = any> {
name: string;
description: string;
risk: 'low' | 'medium' | 'high';
input: z.ZodSchema<TIn>;
// 是否需要人工确认
confirm?: boolean;
execute: (input: TIn, ctx: ToolContext) => Promise<TOut>;
}
export class ToolRegistry {
private tools = new Map<string, AgentTool>();
register(tool: AgentTool) {
this.tools.set(tool.name, tool);
}
get(name: string) {
return this.tools.get(name);
}
list() {
return Array.from(this.tools.values()).map(t => ({
name: t.name,
description: t.description,
risk: t.risk
}));
}
async call(name: string, rawArgs: any, ctx: ToolContext) {
const tool = this.tools.get(name);
if (!tool) throw new Error(`Tool not found: ${name}`);
const args = tool.input.parse(rawArgs); // 校验
ctx.emit({ type: 'tool:call', callId: '', name, args, status: 'pending' });
// 高风险 + 需要确认 → 抛中断
if (tool.risk === 'high' && tool.confirm) {
throw new HumanInterrupt({ type: 'approve', tool: name, args });
}
try {
const result = await withTimeout(tool.execute(args, ctx), 8000, ctx.signal);
ctx.emit({ type: 'tool:result', callId: '', ok: true, result });
return result;
} catch (error: any) {
ctx.emit({ type: 'tool:result', callId: '', ok: false, error: error.message });
throw error;
}
}
}
function withTimeout<T>(p: Promise<T>, ms: number, signal: AbortSignal): Promise<T> {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), ms);
signal.addEventListener('abort', () => ctrl.abort());
return Promise.race([
p,
new Promise<T>((_, rej) => ctrl.signal.addEventListener('abort', () => rej(new Error('timeout'))))
]).finally(() => clearTimeout(t));
}
3.2 注册前端工具
javascript
// agent/tools/frontend.ts
export const tools: AgentTool[] = [
{
name: 'dom.query',
description: '查询页面元素文本/属性',
risk: 'low',
input: z.object({ selector: z.string() }),
async execute({ selector }) {
const el = document.querySelector(selector);
return el ? { text: el.textContent, exists: true } : { exists: false };
}
},
{
name: 'form.fill',
description: '填写表单字段',
risk: 'medium',
input: z.object({ selector: z.string(), value: z.string() }),
confirm: true,
async execute({ selector, value }) {
const el = document.querySelector(selector) as HTMLInputElement;
if (!el) throw new Error('元素不存在');
el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
return { ok: true };
}
},
{
name: 'fetchData',
description: '调用业务接口',
risk: 'low',
input: z.object({ url: z.string(), method: z.enum(['GET', 'POST']), body: z.any().optional() }),
async execute({ url, method, body }) {
const res = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined
});
return res.json();
}
},
{
name: 'navigate',
description: '跳转路由',
risk: 'medium',
input: z.object({ path: z.string() }),
async execute({ path }) {
history.pushState({}, '', path);
window.dispatchEvent(new PopStateEvent('popstate'));
return { ok: true };
}
}
];
四、Planner:LLM 决策核心
4.1 云端 Planner(复杂推理)
typescript
// agent/planner/CloudPlanner.ts
export interface Planner {
plan(state: AgentState): Promise<{
thought: string;
action: { tool: string; args: any } | null;
done: boolean;
answer?: string;
}>;
}
export class CloudPlanner implements Planner {
constructor(private endpoint: string, private model = 'deepseek-reasoner') {}
async plan(state: AgentState) {
const res = await fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: this.model,
messages: buildMessages(state),
tools: state.tools,
stream: true
})
});
// 解析流式 tool_call(略,实际用 SSE 读)
return parseStream(res.body!);
}
}
4.2 端侧 Planner(WebGPU,轻量)
typescript
// agent/planner/EdgePlanner.ts
import { useBrowserLLM } from '../ai/useBrowserLLM';
export class EdgePlanner implements Planner {
private llm = useBrowserLLM('Xenova/Qwen1.5-0.5B-Chat');
async plan(state: AgentState) {
// 只做"下一步动作"结构化输出
const prompt = `目标:${state.goal}\n历史:${state.scratchpad.map(s=>s.content).join('\n')}
只输出JSON:{thought,tool,args,done}`;
this.llm.send([{ role: 'user', content: prompt }]);
// 等待流式结束,解析 JSON(见前文流式方案)
return parseStructured(this.llm.output);
}
}
混合策略:
- 端侧:意图识别、参数校验、简单路由
- 云端:任务拆解、多跳推理、RAG
五、图编排引擎(Graph Runner)
5.1 节点与图
typescript
// agent/graph.ts
export type NodeFn = (ctx: RunContext) => Promise<void>;
export interface GraphNode {
id: string;
run: NodeFn;
next?: string | ((ctx: RunContext) => string);
}
export class AgentGraph {
private nodes = new Map<string, GraphNode>();
add(node: GraphNode) {
this.nodes.set(node.id, node);
}
compile() {
return this;
}
async run(start: string, ctx: RunContext) {
let current = start;
while (current) {
const node = this.nodes.get(current);
if (!node) throw new Error(`Missing node: ${current}`);
await node.run(ctx);
if (ctx.state.status === 'waiting_human') break; // 中断
if (ctx.state.step++ > ctx.state.maxSteps) {
ctx.emit({ type: 'log', level: 'warn', message: '步数超限' });
break;
}
const next = typeof node.next === 'function' ? node.next(ctx) : node.next;
current = next ?? 'END';
}
}
}
5.2 标准 ReAct 图
php
// agent/graphs/react.ts
export function buildReActGraph(planner: Planner, registry: ToolRegistry) {
const graph = new AgentGraph();
graph.add({
id: 'plan',
async run(ctx) {
ctx.emit({ type: 'run:start', runId: ctx.runId, threadId: ctx.threadId });
const decision = await planner.plan(ctx.state);
ctx.state.scratchpad.push({ role: 'thought', content: decision.thought });
ctx.emit({ type: 'think', text: decision.thought });
if (decision.done) {
ctx.finalAnswer = decision.answer!;
ctx.state.status = 'idle';
ctx.emit({ type: 'token', text: decision.answer! });
ctx.emit({ type: 'run:end', status: 'success' });
return;
}
ctx.pendingAction = decision.action;
},
next: 'act'
});
graph.add({
id: 'act',
async run(ctx) {
if (!ctx.pendingAction) return;
const { tool, args } = ctx.pendingAction;
try {
const result = await registry.call(tool, args, {
signal: ctx.signal,
emit: ctx.emit
});
ctx.state.scratchpad.push({
role: 'observation',
content: `${tool} => ${JSON.stringify(result)}`
});
ctx.emit({ type: 'state:delta', patch: [{ op: 'add', path: '/shared/lastResult', value: result }] });
} catch (e: any) {
if (e instanceof HumanInterrupt) {
ctx.state.status = 'waiting_human';
ctx.emit({ type: 'interrupt', kind: e.payload.type, payload: e.payload });
return;
}
ctx.state.scratchpad.push({ role: 'observation', content: `error: ${e.message}` });
}
},
next: 'reflect'
});
graph.add({
id: 'reflect',
async run(ctx) {
ctx.emit({ type: 'state:snapshot', state: ctx.state });
// 简单反思:是否达成目标
const last = ctx.state.scratchpad[ctx.state.scratchpad.length - 1];
if (last?.role === 'observation' && last.content.includes('"ok":true')) {
// 继续规划
}
},
next: 'plan'
});
return graph.compile();
}
六、运行上下文与检查点
ini
// agent/RunContext.ts
export class RunContext {
runId = crypto.randomUUID();
threadId: string;
signal: AbortSignal;
abort: AbortController;
emit: (e: AgentEvent) => void;
state: AgentState;
pendingAction: { tool: string; args: any } | null = null;
finalAnswer = '';
checkpoint: any = null;
constructor(opts: {
threadId: string;
goal: string;
emit: (e: AgentEvent) => void;
restore?: any;
}) {
this.threadId = opts.threadId;
this.emit = opts.emit;
this.abort = new AbortController();
this.signal = this.abort.signal;
this.state = opts.restore ?? {
goal: opts.goal,
step: 0,
maxSteps: 12,
status: 'planning',
scratchpad: [],
shared: {},
tools: []
};
}
saveCheckpoint() {
this.checkpoint = structuredClone(this.state);
}
restoreCheckpoint() {
if (this.checkpoint) this.state = structuredClone(this.checkpoint);
}
}
检查点用途:
- 每一步存 IndexedDB
- 刷新页面可恢复 Agent 运行
- 工具失败可回滚到上一步
七、人机协同(Interrupt)
scala
// agent/HumanInterrupt.ts
export class HumanInterrupt extends Error {
constructor(public payload: any) {
super('human-in-the-loop');
}
}
// UI 层订阅
useAgentStream(stream => {
if (stream.type === 'interrupt') {
showApprovalCard(stream.payload, (decision) => {
// 把用户决定写回共享状态,恢复图
resumeRun(decision);
});
}
});
典型场景:
- 删除数据 → 弹确认框
- 多选方案 → 卡片选择
- 表单补全 → 用户填字段再继续
八、生成式 UI(Tool → Component)
typescript
// ui/GenerativeSurface.tsx
'use client';
import { useAgentStream } from './useAgentStream';
const toolUI: Record<string, React.FC<any>> = {
'dom.query': (props) => <DomInspectCard {...props} />,
'fetchData': (props) => <DataTable loading={props.loading} data={props.result} />,
'form.fill': (props) => <FormPreview {...props} />
};
export function GenerativeSurface() {
const { events, state } = useAgentStream();
return (
<div className="agent-surface">
{events.map((e, i) => {
if (e.type === 'tool:call') {
const Comp = toolUI[e.name];
return Comp ? <Comp key={i} status="pending" args={e.args} /> : <ToolShell key={i} name={e.name} />;
}
if (e.type === 'tool:result') {
const Comp = toolUI[e.name];
return Comp ? <Comp key={i} status="done" result={e.result} /> : null;
}
if (e.type === 'think') return <ThinkBubble key={i} text={e.text} />;
return null;
})}
{/* 共享状态直接驱动仪表盘 */}
<StateBinder state={state} />
</div>
);
}
核心思想:
- 聊天流是"审计日志"
- 共享 state 是"真相来源"
- 工具调用天然映射组件
九、浏览器自动化 Agent(Browser Use 风格)
javascript
// agent/tools/browser.ts
export const browserTools: AgentTool[] = [
{
name: 'browser.click',
description: '点击可交互元素',
risk: 'medium',
input: z.object({ index: z.number() }),
async execute({ index }, ctx) {
const el = getInteractiveElements()[index];
el?.click();
ctx.emit({ type: 'log', level: 'info', message: `click #${index}` });
return { ok: true };
}
},
{
name: 'browser.observe',
description: '读取页面可访问性树',
risk: 'low',
input: z.object({}),
async execute() {
return { tree: snapshotAccessibilityTree() }; // 剪枝后的元素列表
}
}
];
// 观察-决策-执行循环
async function browserStep(ctx: RunContext) {
const tree = await registry.call('browser.observe', {}, ctx);
ctx.state.shared.page = tree;
// planner 基于 tree 决定下一步 click/index
}
要点:
- 页面状态剪枝(只留可交互元素 + 稳定 id)
- 每步只发一个原子动作
- 硬步数预算,防无限循环
十、记忆与上下文管理
csharp
// agent/memory/MemoryStore.ts
export class MemoryStore {
private short: ChatMessage[] = [];
private long: VectorStore;
push(msg: ChatMessage) {
this.short.push(msg);
if (this.short.length > 20) {
this.compress();
}
}
private async compress() {
// 摘要化早期消息
const summary = await summarize(this.short.slice(0, 10));
this.short = [summary, ...this.short.slice(10)];
}
async retrieve(query: string, k = 3) {
const vec = await embed(query);
return this.long.search(vec, k);
}
}
上下文策略:
- Skill/Tool 描述常驻(渐进式披露)
- 正文按需加载
- Token 预算硬上限
十一、权限与风控
typescript
// agent/guard.ts
export function guardToolCall(tool: string, args: any, user: UserProfile) {
const rules = {
'form.fill': args.selector.includes('sensitive') ? 'block' : 'allow',
'navigate': args.path.startsWith('/admin') && !user.isAdmin ? 'block' : 'allow',
'db.delete': 'human_confirm'
};
return rules[tool] ?? 'allow';
}
分级:
- 低危只读:自动执行 + 审计
- 中危可回滚:执行 + 通知
- 高危不可逆:强制二次确认/多因子
十二、完整实战:自然语言操作后台
用户说:"把订单 88231 标记为已发货,再发个通知"
编排过程:
plan:拆成两个子任务browser.observe/dom.query定位订单form.fill改状态(中危,弹确认)- 用户确认 → 继续
fetchData调通知接口reflect校验返回run:end输出总结 + 生成结果卡片
前端看到:
- 思考气泡
- 工具卡片(加载→成功)
- 确认弹窗
- 最终生成"操作报告"组件
十三、性能与工程化
- Planner 推理放 Worker(不卡主线程)
- 工具执行可取消(AbortController)
- 事件流用 SSE / BroadcastChannel
- 检查点存 IndexedDB
- 模型权重用 Service Worker 缓存(端侧)
- 每一步可回放(时间旅行调试)
- 前端埋点:首思考延迟、工具耗时、中断率
十四、生产 Checklist
- ✅ Agent 不是黑盒:所有步骤可观测
- ✅ 工具必须 schema 校验
- ✅ 高风险必须人工中断
- ✅ 状态单一数据源(state + patch)
- ✅ 规划与执行分离(云端+端侧混合)
- ✅ 页面操作原子化、可回滚
- ✅ 上下文有压缩与预算
- ✅ 流式 UI 与聊天流解耦
- ✅ 检查点/恢复/步数上限齐备
十五、总结
前端 AI Agent 编排引擎的本质:
把 LLM 的"不确定性"关进图编排的"确定性笼子"里,让 UI 成为 Agent 的双手和眼睛。
关键能力矩阵:
- 编排:图 + 状态机 + 检查点
- 决策:云端复杂规划 + 端侧轻量控制
- 执行:前端工具 + 浏览器操作 + 业务 API
- 交互:流式事件 + 生成式 UI + 人机协同
- 底线:权限、审计、可恢复、不失控
这套引擎可以直接支撑:
- 智能后台(自然语言操作用户/订单/报表)
- 低代码 Agent(拖工具节点生成流程)
- 浏览器自动化(Web Agent)
- AI 原生工作台(边想边出界面)