前端 AI Agent 编排引擎:从 ReAct 循环到生成式 UI(完整版)

前端 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 标记为已发货,再发个通知"

编排过程:

  1. plan:拆成两个子任务
  2. browser.observe / dom.query 定位订单
  3. form.fill 改状态(中危,弹确认)
  4. 用户确认 → 继续
  5. fetchData 调通知接口
  6. reflect 校验返回
  7. 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 原生工作台(边想边出界面)
相关推荐
MetWeave4 小时前
一朵玫瑰,装下一年的风(《风不是一个数》系列第 5 篇
前端·数据可视化
UNIHHIOT4 小时前
开源一套工业物联网平台:驱动崩了不影响别的协议,64MB 堆跑边缘网关
前端·后端
deli0074 小时前
秒杀超卖实测:1000 个并发请求下 SQL 加锁前后差多少
前端
徐小夕4 小时前
JitKnow知识库4.8发布:本体+知识图谱检索引擎,让AI回答不再“一本正经地胡说八道”
前端·vue.js·github
智迁4 小时前
MySQL 到 Kingbase 实时同步:从 Docker 部署到增删改验证
前端·后端
tryCbest4 小时前
Vue3——第 11 篇:组合式 API(上)——setup 与响应式 API 深度解析
前端·javascript·vue.js
tryCbest5 小时前
Vue3——第 13 篇:计算属性与侦听器——computed 与 watch 的进化
前端·javascript·vue.js
怒放的生命19915 小时前
本地用 pnpm 省磁盘,团队继续用 npm:极简实践
前端·npm·node.js
believed_breadfruit6 小时前
我开源了一个HTML 分页导出工具,解决了复杂页面的内容截断、跨页错乱和长文档白屏问题
前端·javascript·github