网页端嵌入 Agent 对接前端方案

本文将深入探讨「网页端嵌入AI」的核心概念与实战技巧,帮助你快速掌握关键要点。让我们开始吧!

网页端嵌入 Agent 对接前端方案

1. 引言

当前前端项目正从被动展示走向主动交互,AI Agent 嵌入网页端可自动化 UI 操作、优化布局并辅助编码。本文聚焦如何在前端(React/Vue3)中集成 AI Agent,涵盖客户端工具定义、安全通信及流式输出等核心环节。读完本文,你将掌握:React 集成 AG-UI 组件、Vue3 对接 Agent 流式输出、前端 Agent 安全通信实现、LangChain 前端 Agent 对接的基本方法。

2. 核心概念:AI Agent 在前端的运行模式

2.1 网页端嵌入 AI Agent 前端方案的本质

网页端嵌入 AI Agent 前端方案的本质是客户端与服务器之间的协同工作模式。负责推理和决策的 LLM 运行在服务端,而前端主要负责 UI 交互和本地环境操作。Agent 在客户端与服务器间协同,由服务器协调工具调用,前端负责浏览器端特有的操作:获取 GPS 位置、访问剪贴板、操作 DOM 元素、调用浏览器 API 等。

以微软 AG-UI 框架为例,Agent 实例在服务端创建,但工具函数可能被标记为"前端执行",当 LLM 决策需要客户端能力时,服务端会向客户端下发工具调用请求,由前端执行后将结果返回。这种分工既利用了服务端的计算资源、保护了 API 密钥,又保留了前端对浏览器环境的控制权限。在实际工程中,服务端与前端通过标准协议(如 JSON-RPC)交换工具调用ID和参数,前端无须直接暴露 API 密钥或 LLM 配置,从而保障了安全性。

2.2 React 集成 Agent UI 组件------AG-UI 的使用模式

微软开源的 @microsoft/ag-ui-react 库提供了一套开箱即用的 React 组件集合,用于快速在 React 应用中嵌入 AI Agent 界面。它包含 AIAgentAgentConversationAgentToolList 等组件,开发者只需在组件树中引入 <AIAgent>,传入必要属性即可使页面具备 Agent 对话与工具调用能力。

AG-UI 的核心设计思路是声明式组件 + 工具函数自动路由。开发者在项目中预先定义前端工具函数(如获取用户位置、切换主题、读取浏览器存储),并用 [Description] 属性标注。通过 AIFunctionFactory.Create 将这些函数包装为 AITool 对象,创建 Agent 实例时传入工具数组,框架会自动确定哪些工具需要在前端执行,并在对话期间接管调用与响应。

此外,AG-UI 提供了内置的 Agent 状态管理(thinking / waiting / error),结合 React Suspense 或自定义 Loading 组件,可以显著减少状态边界处理的开发成本。

2.3 前端 Agent 工具函数定义方法

前端 Agent 工具函数是 Agent 在客户端环境中的可调用能力单元,每个工具函数对应一个具体的浏览器端操作。定义方法主要有以下步骤:

  • 函数签名 :工具函数必须是静态方法或独立函数,返回类型为 string 或可被 JSON 序列化的对象。返回信息将直接作为 LLM 理解的结果。
  • 元数据标注 :通过 [Description("...")] 标注函数功能、参数含义、返回格式。服务端 LLM 会根据描述决定何时调用该工具。

描述应精准简练,例如 [Description("获取用户当前地理位置,返回经纬度字符串。")]

  • 包装为 AITool :使用 AIFunctionFactory.Create(GetUserLocation) 将普通函数转为 AITool 对象,框架会提取其描述、参数类型约束等元数据。

  • 注册到 Agent :创建 Agent 实例时,将 AITool[] 数组传入 tools 属性,Agent 即可在对话中识别并调度这些工具。

重要原则 :工具函数尽量避免产生副作用,或必须前置确认对话框;输出结果应在合理长度内(建议不超过 2000 字符),避免 token 浪费。对于会修改页面状态的操作(如切换主题),通常不返回复杂对象,只返回固定字符串(例如 Theme toggled successfully.)。

3. 实战:React 中使用 AG-UI 集成 Agent

3.1 项目初始化

在现有 React 项目(确保 React 版本 ≥ 17.0.2)中安装 AG-UI 库:

bash 复制代码
npm install @microsoft/ag-ui-react @microsoft/ag-client

在应用入口处引入 Provider 组件,用于提供 Agent 客户端实例:

jsx 复制代码
// index.jsx
import { ChatClientProvider } from '@microsoft/ag-ui-react';
import { ChatClient } from '@microsoft/ag-client';
import App from './App';

// 需替换为真实服务端 endpoint,该 endpoint 负责连接 LLM 并协调工具调用
const chatClient = new ChatClient({ endpoint: "https://your-server.com/agent" });

ReactDOM.render(
  <ChatClientProvider client={chatClient}>
    <App />
  </ChatClientProvider>,
  document.getElementById('root')
);

3.2 定义前端工具

假设我们需要 Agent 能够获取用户 GPS 位置和切换深色模式。定义两个前端工具函数:

csharp 复制代码
// 后端 C# / .NET 示例(也可以在 JS/TS 中用类似方式)
[Description("Get the user's current location from GPS.")]
static string GetUserLocation()
{
    // 实际应调用浏览器 Geolocation API,此处仅做示意
    // 前端通过 Bridge 层(JSInterop)从浏览器获取数据
    return "Amsterdam, Netherlands (52.37°N, 4.90°E)";
}

[Description("Toggle dark/light theme in current page.")]
static string ToggleDarkMode()
{
    // 切换主题逻辑(前端执行)
    return "Dark mode toggled to true.";
}

// 创建前端工具数组
AITool[] frontendTools = {
    AIFunctionFactory.Create(GetUserLocation),
    AIFunctionFactory.Create(ToggleDarkMode)
};

在实践中,由于工具函数必须在前端浏览器环境执行,通常通过 JSInterop(例如 Blazor 场景)或直接在前端 JavaScript 中定义对应函数,然后将函数引用传递给 AG-UI 的 AIFunctionFactory。React 组件内可直接定义纯前端函数。

3.3 创建 Agent 实例并挂载

在服务端或前端初始化时,通过已配置好的 chatClient 创建 Agent 实例:

jsx 复制代码
// App.jsx
import { useChatClient, AIAgent, AgentConversation } from '@microsoft/ag-ui-react';

function App() {
  const chatClient = useChatClient();
  
  // 创建 Agent 实例
  const agent = chatClient.AsAIAgent({
    name: "agui-client",
    description: "An agent that can access client-side capabilities.",
    tools: frontendTools  // 上文定义的 frontendTools
  });

  return (
    <div>
      <h2>AI Agent 助手</h2>
      {/* AG-UI 内置组件自动处理对话与工具调用 */}
      <AIAgent agent={agent}>
        <AgentConversation />
      </AIAgent>
    </div>
  );
}

AG-UI 前端工具调用实战 :当用户输入"我在哪里?",LLM 决定调用 GetUserLocation 工具,服务端将工具调用请求(带有 tool_call_id)下发给前端。AG-UI 组件自动执行对应的前端函数,将结果(如经度纬度)回传给服务端,最终以文本形式展示给用户。整个流程对开发者透明,仅需关注工具函数的定义和描述。

4. 实战:Vue3 对接 Agent 流式输出(SSE 实现)

4.1 为什么选择 SSE

AI Agent 的响应通常以流式方式生成,前端逐块展示 token,提升用户体验。在 Vue3 项目中对接流式输出,推荐使用 SSE(Server-Sent Events)。原因如下:SSE 是浏览器原生支持的轻量文本流协议,无需像 WebSocket 那样手动管理握手、帧解析和心跳;它适用于常规的单向文本流场景------Agent 服务端向客户端流式发送生成结果。

对于多数前端对话场景,客户端的输入可通过常规 POST 请求发送,不需要全双工通信。实现复杂度与维护成本均低于 WebSocket。此外,SSE 天然支持断线重连(Last-Event-ID),这在 Agent 长回答场景中尤为重要。

4.2 前端实现:Vue3 Composition API + ReadableStream

vue 复制代码
<template>
  <div>
    <div v-for="msg in messages" :key="msg.id">
      <strong>{{ msg.role }}:</strong> {{ msg.content }}
    </div>
    <button @click="cancelRequest" v-if="generating">中止</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const messages = ref([]);
const generating = ref(false);
let abortController = null;

async function sendMessage(text) {
  // 将用户消息加入对话
  messages.value.push({ role: 'user', content: text, id: Date.now() });
  
  abortController = new AbortController();
  generating.value = true;
  
  try {
    const response = await fetch('/api/agent/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ message: text, history: messages.value.slice(0, -1) }),
      signal: abortController.signal
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    
    // 使用 ReadableStream 逐块读取 SSE 数据流
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let assistantContent = '';
    let assistantMsgId = Date.now() + 1;
    
    // 创建助理消息容器,先展示空内容并动态追加
    messages.value.push({ role: 'assistant', content: '', id: assistantMsgId });
    const msgIndex = messages.value.length - 1;

while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      
      const chunk = decoder.decode(value, { stream: true });
      // SSE 数据格式: data: {"token": "..."} 每行
      const lines = chunk.split('\n');
      for (const line of lines) {
        if (line.startsWith('data:')) {
          try {
            const json = JSON.parse(line.slice(5).trim());
            if (json.token) {
              assistantContent += json.token;
              // 利用 Vue3 响应式特性更新消息内容
              messages.value[msgIndex].content = assistantContent;
            }
          } catch (e) {
            // 忽略解析错误(如非 JSON 行)
          }
        }
      }
    }
  } catch (err) {
    if (err.name !== 'AbortError') {
      console.error('SSE 数据接收错误:', err);
    }
  } finally {
    generating.value = false;
  }
}

function cancelRequest() {
  if (abortController) {
    abortController.abort();
  }
}
</script>

要点说明

  • 使用 AbortController 支持用户中止请求,避免 Agent 生成过长内容时无法停止。
  • 逐块解码时设置 { stream: true },保证 UTF-8 字符边界正确。
  • SSE 数据行格式取决于后端实现,这里假设每行 data: {"token":"xxx"} 后跟 \n\n 作为事件分隔。

前端按行解析、拼接到 ref 中触发视图更新。

  • 注意:当大量 token 连续推送时,频繁触发 Vue 响应式更新可能产生性能问题;可在回调中加入节流或批处理(比如每50ms批量更新一次)。

4.3 后端配合(FastAPI 示例)

python 复制代码
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import asyncio

app = FastAPI()

async def agent_stream(message: str, history: list):
    """模拟 Agent 流式生成,实际调用 LLM 或 agent.invoke"""
    # 假设 agent 是一个可流式调用的实例
    # async for token in agent.astream(input={"message": message, "history": history}):
    #     yield f"data: {json.dumps({'token': token})}\n\n"
    #     await asyncio.sleep(0.01)  # 控制推送速率
    response = "这是模拟的流式输出内容。

"
    for char in response:
        yield f"data: {json.dumps({'token': char})}\n\n"
        await asyncio.sleep(0.02)

@app.post("/api/agent/chat")
async def chat(request: Request):
    data = await request.json()
    message = data.get("message")
    history = data.get("history", [])
    
    return StreamingResponse(
        agent_stream(message, history),
        media_type="text/event-stream",
        headers={
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        }
    )

后端必须设置 Content-Type: text/event-stream,否则前端 ReadableStream 将无法按预期工作。Java Spring Boot 中可对应使用 SseEmitter

提示 :生产环境下对 SSE 流要进行连接数控制和超时管理。可在前端实现重连逻辑(指数退避,初始延迟 1s,最大 30s),浏览器原生 EventSource 可简化实现,但无法自定义 POST 请求方法。若需要 POST(传用户消息体较大),则推荐使用 fetch + ReadableStream 方案。

5. 安全通信:前端 Agent 安全通信实现

5.1 核心风险识别

前端 Agent 工具函数暴露了浏览器端能力,引入的安全风险不可忽视。主要风险包括:

  • 工具函数可能被滥用读取敏感数据:如 navigator.cookielocalStorage、GPS 坐标。

  • LLM 幻觉可能导致服务器错误地调度一个工具函数,如果该函数涉及写操作(如修改 localStorage、建 cookie、发送 HTTP 请求),可能造成用户数据损坏或隐私泄漏。

  • 中间人攻击:如果通信链路未加密,攻击者可能篡改服务器下发的工具调用指令,诱导前端执行恶意操作。

5.2 前端 Agent 安全通信实现方案

5.2.1 工具白名单与权限分级

所有工具函数必须注册在前端明确定义的白名单中,前端绝不能执行由服务端动态注入的任意函数名。权限分级建议:

等级 描述 示例工具
L0 无敏感信息、任意调用 获取当前时间、温度转换
L1 读取非私有信息,需一次授权 获取地理位置
L2 读取/写入用户数据 写剪贴板、改主题
L3 高危操作(写 cookie、发送网络请求) 需每次确认

实现上,可在工具函数定义阶段附加 PermissionLevel 属性,框架在执行工具前检查授权状态。

5.2.2 通信使用 HTTPS + 签名

所有 Agent 与服务器间的通信必须通过 HTTPS。关键调用(如工具执行结果返回)可增加签名机制:服务器在下发 tool_call_id 时附带 HMAC 签名,前端执行后返回时携带签名,服务器验签通过才接受结果。这样可以防止结果被篡改或者重放攻击。

5.2.3 敏感操作的用户二次确认

对于需要修改用户数据或触发系统副作用的工具(L2 及以上),前端必须在执行前弹出确认对话框(如弹窗提示"助手想要修改您的主题,是否允许?")。用户授权结果应短时缓存(例如 5 分钟内同一工具不再重复确认),避免打断交互流程。

5.2.4 示例:AG-UI 中的权限控制

在 AG-UI 中可以通过 ToolPolicy 扩展实现权限控制:

typescript 复制代码
// 伪代码示意:工具权限策略
const toolPolicy = new ToolPolicy({
  onBeforeExecute: async (toolName, args, permissionLevel) => {
    if (permissionLevel >= 2) {
      const confirmed = await showConfirmDialog(`允许执行 ${toolName} 吗?

`);
      return confirmed;
    }
    return true;
  }
});

createAgent({
  tools: frontendTools.map(t => ({ tool: t, policy: toolPolicy })),
});

6. 进阶:LangChain 前端 Agent 对接教程与多 Agent 协同

6.1 LangChain 前端 Agent 对接的基本方法

当项目需要更灵活的前端 Agent 逻辑时(例如自定义 LLM 调用、状态机管理),可使用 LangChain 的 JavaScript SDK 创建前端 Agent。基本模式如下:

typescript 复制代码
import { ChatOpenAI } from "@langchain/openai";
import { AgentExecutor, createReactAgent } from "@langchain/langgraph";

// 定义前端工具:使用 @langchain/core 的 Tool 类
const getLocationTool = new Tool({
  name: "get_location",
  description: "获取用户当前地理位置。",
  func: async () => {
    // 调用浏览器 Geolocation API
    return new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(
        (pos) => resolve(`${pos.coords.latitude},${pos.coords.longitude}`),
        (err) => reject("无法获取位置")
      );
    });
  }
});

// 创建 Agent 实例(前端可执行模式)
const agent = await createReactAgent({
  llm: new ChatOpenAI({ model: "gpt-4o", temperature: 0 }),
  tools: [getLocationTool],
});

const agentExecutor = new AgentExecutor({ agent });

// 调用
const result = await agentExecutor.invoke({ input: "我在哪里?" });

但需要注意,前端直接使用 LLM 的 API key 有暴露风险。LangChain 前端 Agent 对接时,建议将 ChatOpenAI 的调用代理到自己后端,或者使用 callbacks 将函数调用请求发送到后端进行推理。LangChain 新增的 RemoteRun 模式支持这种模式:前端只定义工具执行逻辑,LLM 推理全部通过后端 RemoteRunnable 完成。

6.2 多 Agent 协同与路由

在天猫行业中后台研发场景中(参考搜索资料),复杂的前端任务通常需要多个 Agent 协同:一个 Agent 负责 UI 扫描与实际分析,一个负责布局优化,一个负责组件重构。在多 Agent 架构下,前端需要管理多个 Agent 实例及其工具集。

实现方案:

  1. 服务端路由层 :前端仅维护一个统一的 Agent 入口,通过请求中的 requestType 字段让服务端路由到对应下游 Agent。服务端返回的流式数据中包含 agentId 标识来当前响应的 Agent 身份。
  2. 前端命名空间隔离 :每个 Agent 对应一个工具命名空间,工具函数名称加上 agentId 前缀以避免冲突。

每个 Agent 实例独立管理对话历史和状态,利用 React/Vue 的 Context API 或状态管理库(Pinia/Zustand)按 agentId 创建独立 store。

  1. 踩坑点:上下文串扰:同一页面存在多个 Agent 实例时,浏览器内存中的会话历史可能被重写。

根本原因在于使用了全局共享的变量或数组存储历史。解决方案 :为每个 Agent 创建唯一 agentId,历史记录、AbortController、重连定时器等全部存储在该 ID 为 key 的 Map 中,避免交叉影响。


延伸阅读

RAG 生产部署与性能监控

Agent 开发与生产级部署

RAG 实战全链路系列目录

相关推荐
Maimai108088 小时前
React 多步骤表单工程化落地:从 Zod Schema、React Hook Form 到 Zustand 持久化
前端·javascript·react.js·前端框架·状态模式
Maimai108089 小时前
React Query + Zustand 正确结合方式:不要把接口数据复制进 Store
前端·javascript·react.js·前端框架·web3·状态模式
Maimai108089 小时前
Zustand 项目落地:从全局状态、Store 拆分到真实业务封装
前端·react.js·前端框架·状态模式
不是山谷.:.9 小时前
前端零基础入门:WebSocket 全解析
前端·笔记·websocket·状态模式
前端不太难10 小时前
如何优化鸿蒙 App 的启动速度?
华为·状态模式·harmonyos
木斯佳11 小时前
前端八股文面经大全:腾讯云智前端一面(2026-05-13)·面经深度解析
前端·状态模式
前端不太难11 小时前
CPU+GPU:开启AI推理新时代
人工智能·状态模式