一八零、AG-UI:构建AI前端交互的统一协议

一八零、AG-UI:构建AI前端交互的统一协议

AG-UI:构建AI前端交互的统一协议

引言

随着人工智能技术的飞速发展,AI Agent已经从概念走向实际应用。然而,在将这些智能体集成到前端应用中时,开发者面临着一个关键挑战:如何实现AI Agent与用户界面之间的高效、标准化交互?

AG-UI(Agent User Interaction Protocol)正是为解决这一痛点而诞生的开源协议。它不仅仅是一个技术规范,更是连接AI智能体与前端应用的桥梁,让开发者能够构建真正智能化的用户体验。它们能够让开发者构建出真正嵌入UI应用、感知上下文、实时协同的智能体,而不仅仅是一个在后台提供文本答案的API服务。

一、AG-UI是什么?

1.1 核心定义

AG-UI(Agent-User Interaction Protocol) 是由CopilotKit团队提出的开源、轻量级协议,专门用于规范AI Agent与前端用户界面之间的通信流程,是一套开源的Agent与UI界面之间的交互协议。它的核心使命是:

标准化前端应用与AI智能体的连接方式,通过开放协议实现通用的AI驱动系统通信。
在这里插入图片描述

1.2 协议定位

在这里插入图片描述

在AI生态系统中,AG-UI与其他协议形成互补关系:

  • MCP(Model Context Protocol):定义AI模型调用外部工具的协议规范
  • A2A(Agent-to-Agent Protocol):规范智能体之间的通信协议
  • AG-UI专注于智能体与用户(前端应用)之间的交互规范

这种分工明确的设计让AG-UI能够专注于解决人机交互层面的问题,而不与其他协议产生冲突。

二、为什么需要AG-UI?

在AG-UI出现之前,AI应用开发面临诸多挑战:

技术碎片化
  • 每个AI框架(LangGraph、CrewAI、Mastra等)都有独特的事件机制和API
  • 不同模型提供商的接口格式各异,增加适配成本
  • 缺乏统一的前后端同步机制
实时性困难
  • 用户期待实时的流式响应体验
  • 传统的请求-响应模式无法满足复杂AI工作流需求
  • 状态同步和进度反馈机制不完善
人机协作缺失
  • 用户难以对AI执行过程进行实时干预
  • 缺乏标准化的人机协作(Human-in-the-loop)机制
  • 工具调用和状态共享缺乏统一规范

这些问题会导致开发者在和多个AI服务对接或构建复杂AI应用时重写大量代码。

假设有一套通信协议,可以统一前端应用和后端Agent之间的交互格式,做到前后端解耦,让大家各司其职。AG-UI的出现,就是为了解决这个问题

三、AG-UI核心架构

3.1 整体架构设计

在这里插入图片描述
  • Application:直接与用户交互的前端应用层,比如ChatGPT、Cursor等任何AI powered应用。
  • AG-UI Client:在前端侧负责与后端Agent进行通信,使用AG-UI协议。
  • Agent:后端Agent通常和AI服务或其他Agent对接,用来处理用户请求。

3.2 核心特性

统一事件流

AG-UI 的核心是单一序列的 JSON 事件,简化了代理与前端的实时同步,确保流畅无碍的通信,不论代理使用何种"语言"或内部实现。

实时交互

支持 LLM 逐步生成的 token 立即显示,提供流畅、自然的用户体验,并实现人机协同工作流程。

工具编排

现代代理可调用函数、运行代码、访问 API。AG-UI 确保前端能实时显示进度和结果,支持人工批准并无缝恢复运行。

共享状态

通过高效传输差异更新,代理能够生成逐步演进的计划、表格或代码文件夹,节省带宽并保持同步状态。

并发与取消

支持用户同时发出多个查询、中途停止或切换线程,通过线程 ID、运行 ID 和有序的关闭路径确保稳定运行。

安全边界

**协议内建权限管理、身份认证等机制,**提供企业级的 CORS,确保数据传输的安全性和可靠性。

3.3 事件Events

AG-UI定义了17种标准化事件类型,构成完整的AI交互生命周期管理体系。基于官方文档,以下是完整的事件系统架构:

EventType 枚举定义
enum EventType { TEXT_MESSAGE_START = "TEXT_MESSAGE_START", TEXT_MESSAGE_CONTENT = "TEXT_MESSAGE_CONTENT", TEXT_MESSAGE_END = "TEXT_MESSAGE_END", TOOL_CALL_START = "TOOL_CALL_START", TOOL_CALL_ARGS = "TOOL_CALL_ARGS", TOOL_CALL_END = "TOOL_CALL_END", TOOL_CALL_RESULT = "TOOL_CALL_RESULT", STATE_SNAPSHOT = "STATE_SNAPSHOT", STATE_DELTA = "STATE_DELTA", MESSAGES_SNAPSHOT = "MESSAGES_SNAPSHOT", RAW = "RAW", CUSTOM = "CUSTOM", RUN_STARTED = "RUN_STARTED", RUN_FINISHED = "RUN_FINISHED", RUN_ERROR = "RUN_ERROR", STEP_STARTED = "STEP_STARTED", STEP_FINISHED = "STEP_FINISHED", } 
AG-UI事件分类总览
事件分类事件类型事件名称功能描述使用场景
生命周期事件(5)流程控制、错误处理RUN_STARTED运行开始标记Agent执行开始初始化UI状态,显示加载状态
STEP_STARTED步骤开始标记单个步骤开始显示当前执行步骤
STEP_FINISHED步骤完成标记单个步骤完成更新步骤状态,显示进度
RUN_FINISHED运行完成标记整个执行完成清理状态,显示最终结果
RUN_ERROR运行错误标记执行出现错误错误处理,显示错误信息
文本消息事件(3)实时对话、流式输出TEXT_MESSAGE_START消息开始开始新的文本消息创建消息容器
TEXT_MESSAGE_CONTENT消息内容流式传输消息内容实时显示打字效果
TEXT_MESSAGE_END消息结束标记消息传输完成完成消息渲染
工具调用事件(4)功能扩展、透明度TOOL_CALL_START工具调用开始开始调用外部工具显示工具调用状态
TOOL_CALL_ARGS工具参数传输工具调用参数显示调用参数信息
TOOL_CALL_RESULT工具调用结果返回工具执行结果显示工具返回的数据
TOOL_CALL_END工具调用结束工具调用完成显示调用结果
状态管理事件(3)数据同步、一致性STATE_SNAPSHOT状态快照完整状态数据同步完整应用状态
STATE_DELTA状态变更增量状态更新高效更新部分状态
MESSAGES_SNAPSHOT消息快照完整消息历史同步对话历史
特殊事件(2)系统集成、定制化RAW原始事件未处理的原始数据调试和扩展用途
CUSTOM自定义事件用户定义的事件特殊业务逻辑处理
事件流程示例
sequenceDiagram participant User as 👤 用户 participant Frontend as 🖥️ 前端 participant Agent as 🤖 AI Agent participant Tool as 🛠️ 工具 User->>Frontend: 发送消息 Frontend->>Agent: 用户输入 Agent->>Frontend: RUN_STARTED Agent->>Frontend: STEP_STARTED Agent->>Frontend: TEXT_MESSAGE_START Agent->>Frontend: TEXT_MESSAGE_CONTENT (流式) Agent->>Frontend: TEXT_MESSAGE_CONTENT (流式) Agent->>Frontend: TEXT_MESSAGE_END Agent->>Frontend: TOOL_CALL_START Agent->>Frontend: TOOL_CALL_ARGS Agent->>Tool: 执行工具 Tool->>Agent: 工具结果 Agent->>Frontend: TOOL_CALL_RESULT Agent->>Frontend: TOOL_CALL_END Agent->>Frontend: STATE_SNAPSHOT Agent->>Frontend: STEP_FINISHED Agent->>Frontend: RUN_FINISHED Frontend->>User: 显示完整响应 

3.4 Agents 智能体

Agent是AG-UI里的核心组成部分,负责处理前端发起的请求,和LLM交互并生成响应(响应需遵循Events格式),同时还要管理对话状态和消息历史。在Agent底层可以和其他任意的AI服务连接(比如任意的LLM、定制的AI系统、RAG、其他Agent等等)。

Agent具备丰富的交互能力:

  • 基本的文本传输: 通过TEXT_MESSAGE_*系列事件实现流式文本交互
  • 工具调用: 在AG-UI的规范下,Agent可以使用哪些Tools是由前端告知的。当Agent决策需要使用工具时,可以通过一系列事件(TOOL_CALL_START -> TOOL_CALL_ARGS -> TOOL_CALL_END)通知到前端,前端在收到事件后,可决定是否给用户展示对应交互,告知需要调用哪些工具以及对应参数,由用户决定是否调用。调用结果会通过Message传递给Agent。
  • 状态管理: Agent可以向前端传递最新完整的状态(STATE_SNAPSHOT)或增量同步状态(STATE_DELTA),可以让前端应用从中断状态恢复。
  • 多Agent交互: Agent可以通过A2A等其他协议和其他Agent交互,此过程可以不让前端用户感知(只要不发送Event就行)。
  • Human-in-the-Loop控制: 可以将人在回路的控制能力作为Tool注入给Agent,由Agent决定在必要时候让人进行决策(仍然通过工具调用链路)

3.5 Messages

传统与LLM通信时,message中的role通常被分为systemuserassistant。在AG-UI中,developer、工具调用的结果也被当成一种消息类型(tool)。

核心特性:

  • 流式传输: 通过TEXT_MESSAGE_* 实现打字机效果
  • 角色管理: 支持developer、user、assistant、system、tool等多种角色
  • 消息历史: MESSAGES_SNAPSHOT提供完整消息快照(上下文)
  • 工具集成: 工具调用结果作为独立的消息类型处理

对话示例:

 [ // User { id: "msg_1", role: "user", content: "What's the weather in New York?", }, // Assistant response with tool call { id: "msg_2", role: "assistant", content: "Let me check the weather for you.", toolCalls: [ { id: "call_1", type: "function", function: { name: "get_weather", arguments: '{"location": "New York", "unit": "celsius"}', }, }, ], }, // 注意,这里的工具调用其实是由Agent通知给前端,由前端用户决定是否调用工具,并把调用结果传给Agent,让Agent继续后续流程 // Tool result { id: "result_1", role: "tool", content: '{"temperature": 22, "condition": "Partly Cloudy", "humidity": 65}', toolCallId: "call_1", }, // Assistant's final response using tool results { id: "msg_3", role: "assistant", content: "The weather in New York is partly cloudy with a temperature of 22°C and 65% humidity.", }, ] 

3.6 状态管理

除了基本的同步聊天消息之外,可以同步任意的状态,让人和AI的操作可以衔接操作。

官方提供了一个比较有意思的Demo:让AI优化一份菜谱,菜谱在前端使用富交互展示的,随着AI Agent的优化,前端交互可实时进行更新。

https://feature-viewer-langgraph.vercel.app/feature/shared_state

核心特性:

  • 状态快照: STATE_SNAPSHOT提供完整应用状态的一次性同步,一般用于初始状态同步、中断状态恢复等
  • 增量更新: STATE_DELTA支持高效的部分状态更新,使用JSON Patch,可以流式快速更新
  • 复杂交互: 前端应用可以实现更复杂交互的流式更新,给用户更好的交互体验

3.7 工具调用 Tools

需要和前端交互的工具可以在前端定义,并通过协议传给Agent,当Agent认为需要使用工具时,会使用Event向前端发消息,前端可向用户展示被调用的工具名称和参数,让用户决定是否继续。

在AG-UI中,工具可以:

  • 获取更多的信息: 如天气查询、搜索引擎等
  • 操作外部系统: 如发送邮件、创建文档等
  • 让人进行信息输入或二次确认: Human-in-the-loop机制

核心特性:

  • 前端定义: 工具由前端定义并传递给Agent
  • 用户控制: 用户可以决定是否执行工具调用
  • 参数透明: TOOL_CALL_ARGS支持复杂参数的流式传输
  • 结果返回: TOOL_CALL_RESULT提供工具执行结果的标准化返回
  • 执行监控: 完整的工具调用生命周期跟踪

工具调用流程:

// 1. 开始工具调用 - Agent通知前端需要调用工具 const toolStart: ToolCallStartEvent = { type: EventType.TOOL_CALL_START, toolCallId: "tool_456", toolCallName: "get_weather", parentMessageId: "msg_123" } // 2. 传输参数 - 显示给用户工具调用的参数 const toolArgs: ToolCallArgsEvent = { type: EventType.TOOL_CALL_ARGS, toolCallId: "tool_456", delta: '{"location": "New York", "unit": "celsius"}' } // 3. 返回结果 - 用户确认后,前端执行工具并返回结果 const toolResult: ToolCallResultEvent = { type: EventType.TOOL_CALL_RESULT, messageId: "msg_124", toolCallId: "tool_456", content: '{"temperature": 22, "condition": "Partly Cloudy", "humidity": 65}', role: "tool" } // 4. 结束调用 - 工具调用完成 const toolEnd: ToolCallEndEvent = { type: EventType.TOOL_CALL_END, toolCallId: "tool_456" } 

工具调用事件序列:
ToolCallStart -> ToolCallArgs -> ToolCallResult -> ToolCallEnd

四、AG-UI的技术优势

4.1 灵活性与兼容性

事件结构灵活性
  • 事件无需完全匹配AG-UI格式,只需保持兼容性
  • 现有框架可以最小化改动适配AG-UI
  • 支持渐进式迁移策略

4.2 开发者友好性

丰富的SDK支持
  • TypeScript SDK:提供完整的类型定义和开发工具
  • Python SDK:支持主流AI框架集成
  • 多语言扩展:社区驱动的其他语言实现
现成的集成方案

AG-UI已与多个主流AI框架深度集成:

FrameworkStatusAG-UI Resources
No-framework✅ Supported➡️ Docs coming soon
LangGraph✅ Supported➡️ Demo
Mastra✅ Supported➡️ Demo
CrewAI✅ Supported➡️ Demo
AG2✅ Supported➡️ Demo
Agno✅ Supported➡️ Docs
LlamaIndex✅ Supported➡️ Docs
Pydantic AI🛠️ In Progress
Vercel AI SDK🛠️ In Progress
Google ADK🛠️ In Progress
OpenAI Agent SDK💡 Open to Contributions
AWS Bedrock Agents💡 Open to Contributions
Cloudflare Agents💡 Open to Contributions
Strands Agents SDK💡 Open to Contributions
Language SDKStatusAG-UI Resources
.NET🛠️ In Progress➡️ PR
Nim🛠️ In Progress➡️ PR
Rust🛠️ In Progress

五、演示Demo

5.1 **CopilotKit的演示Demo **

****iCode 仓库

CopilotKit 可以被看作 AG-UI 协议实现的一个框架。它提供了完整的前后端集成方案,使开发者能够快速将 AI Copilot引入应用,包括:TypeScript/React 前端组件库、Python/Node SDK、以及可选的云端代理服务等。
  • 后端Agent:使用Python + LangGraph构建的一个Workflow智能体。
  • 前端应用:使用React+TS构建简单的Demo。
  • Copilot助手:前端接入CopilotKit给应用嵌入的智能助手。
5.1.1 后端python + Copilotkit LangGraph SDK
pip install copilotkit ... 
 import os from dotenv import load_dotenv load_dotenv() from fastapi import FastAPI import uvicorn from copilotkit.integrations.fastapi import add_fastapi_endpoint from copilotkit import CopilotKitRemoteEndpoint, LangGraphAgent from sample_agent.agent import graph app = FastAPI() sdk = CopilotKitRemoteEndpoint( agents=[ LangGraphAgent( name="sample_agent", description="一个模拟智能体", graph=graph, ) ], ) add_fastapi_endpoint(app, sdk, "/copilotkit") def main(): port = int(os.getenv("PORT", "8080")) uvicorn.run( "sample_agent.demo:app", host="0.0.0.0", port=port, reload=True, ) if __name__ == "__main__": main() 
5.1.2 前端React + Copilotkit React-ui
  1. 创建前端应用
npx create-next-app@latest 
  1. 集成Copilot SDK
npm install @copilotkit/react-ui @copilotkit/react-core npm install @copilotkit/runtime class-validator 
  1. 增加路由
... const runtime = new CopilotRuntime({ remoteEndpoints: [{url: "http://localhost:8080/copilotkit"},], }); exportconst POST = async (req: NextRequest) => { const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ runtime, serviceAdapter, endpoint: "/api/copilotkit", }); return handleRequest(req); }; 
  1. 配置全局 CopilotKit 功能(agent是后端agent的名字)
<CopilotKit agent="sample_agent" runtimeUrl="/api/copilotkit" showDevConsole={false} > {children} </CopilotKit> 
  1. 增加Copilot界面,与Agent对话,实现智能交互体验。可以选择sidebar(边栏)、popup(弹出式)、chat(聊天)等多种UI形式。这里我们添加一个sidebar形式的Copilot到主页。
import { CopilotSidebar } from "@copilotkit/react-ui"; import { useCoAgent, useCoAgentStateRender,useCopilotAction ,useLangGraphInterrupt} from "@copilotkit/react-core"; export default function App() { return ( <> <Home/> <CopilotSidebar defaultOpen={true} instructions={"您应尽可能地帮助用户。请根据您拥有的数据以最佳方式回答问题。"} labels={{ title: "智能AI Copilot", initial: `# 👋 您好! 我是你的智能Copilot。演示功能: - **共享状态**: 搜索历史实时的展示 - **前端工具**: 调用前端工具打招呼 - **生成式UI**: 获取天气信息展示卡片 - **HITL流程**: 工具调用的人工审核` }}/> </> ); } 
5.1.3 工具调用(不仅可以调用后端设置的工具(比如搜索、访问数据库、MCP),还可以调用前端定义的UI“工具”(比如更改样式)
  1. 使用langchain_mcp_adapters.client 提供的MCPClient连接tavily-mcp,注册agent工具
from langchain_mcp_adapters.client import MultiServerMCPClient ... async def get_all_tools(): """ 统一的工具准备函数,避免重复初始化MCP客户端 Returns: list: 包含所有可用工具的列表 """ global _all_tools # 如果已经初始化过,直接返回 if _all_tools is not None: return _all_tools # 创建MCP客户端以获取搜索工具 try: client = MultiServerMCPClient( { "tavily-mcp": { "command": "npx", "args": ["-y", "tavily-mcp"], "env": {**os.environ}, "transport": "stdio" } } ) # 获取MCP工具 mcp_tools = await client.get_tools() _all_tools = mcp_tools + [get_weather] logger.info(f"工具初始化成功,可用工具: {[tool.name for tool in _all_tools]}") except Exception as e: logger.warning(f"⚠️ MCP工具初始化失败: {e}") # 如果MCP工具失败,只使用邮件工具 _all_tools = [get_weather] logger.info(f"使用备用工具: {[tool.name for tool in _all_tools]}") return _all_tools 
  1. 使用useCopilotAction hook来注册一个前端Action,创建一个前端Action向用户弹出简单的Alert消息和天气获取
... useCopilotAction({ name: "get_weather", description: "获取指定位置的天气信息。", available: "disabled", // 保持为disabled,确保不被当作前端工具 render: ({status, args, result}) => { return ( <p className="text-gray-500 mt-2"> {status !== "complete" && "Calling weather API..."} {status === "complete" && <WeatherCard location={args.location} result={result} themeColor="#3b82f6" />} </p> ); }, }); useCopilotAction({ name: "sayHello", // Action 名称,Agent 将通过此名称来调用工具 description: "向指定用户问好", // 对该 Action 的描述(供 Agent 理解用途) parameters: [ // 定义参数列表 { name: "name", type: "string", description: "要问好的对象名字" } ], render: "正在发送问候...", // (可选) 执行时在Chat中显示的提示文本 handler: async ({ name }) => { // 定义具体执行逻辑的函数(异步支持) alert(`Hello, ${name}!`); // 这里在浏览器弹出提示框 return('问候已发送给' + name); // 返回结果给agent } }); ... 
  1. 并将前端Action作为工具给Agent使用
 def should_continue(state: AgentState): last_message = state["messages"][-1] if not hasattr(last_message, 'tool_calls') or not last_message.tool_calls: return END # 检查工具调用是前端还是后端 tool_call_name = last_message.tool_calls[0].get("name") frontend_actions = state["copilotkit"]["actions"] is_frontend_action = any( action.get("name") == tool_call_name for action in frontend_actions ) # 如果是前端动作,则结束,让copilotkit前端处理 if is_frontend_action: return END else: # 否则,转到后端工具节点 return "tool_node" 
5.1.4 状态共享
  1. 定义Agent的State
class AgentState(CopilotKitState): search_history: list[dict] = [] 
  1. 增加搜索记录
... # 更新状态信息 updated_state = {"messages": response} # 如果是搜索工具,更新搜索历史 - 搜索开始阶段 if response.tool_calls[0].get("name") in ["tavily-search", "tavily-extract", "tavily-crawl"]: search_history = state.get("search_history", []) search_query = response.tool_calls[0].get("args", {}) # 创建搜索历史记录 - 开始时标记为未完成 search_record = { "query": search_query.get("query", ""), "completed": False, "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), "tool_name": response.tool_calls[0].get("name") } logger.info(f"🔍 添加搜索查询到历史 (开始): {search_record}") search_history.append(search_record) updated_state["search_history"] = search_history return updated_state ... 
  1. 前端使用CopilotKit提供的useCoAgent这个hook函数,useCoAgentStateRender获取实时状态,
... const {state} = useCoAgent<AgentState>({ name: "sample_agent", initialState: { search_history: [] }, }) ... useCoAgentStateRender<AgentState>({ name: "sample_agent", render: ({ status, state, nodeName }) => { return ( <div> {state.search_history?.map((search, index) => ( <div key={index}> {search.completed ? "✅" : "❌"} 正在执行:{search.query} {search.completed ? "" : "..."} </div> ))} </div> ) }, }); ... const [localHistory, setLocalHistory] = useState<AgentState['search_history']>([]); useEffect(() => { if (state.search_history && state.search_history.length > 0 ) { const latestSearch = state.search_history[0]; if(latestSearch.query.length <=0){ return; } setLocalHistory(prevHistory => { if (prevHistory.length > 0 && prevHistory[prevHistory.length - 1].query === latestSearch.query) { const newHistory = [...prevHistory]; newHistory[prevHistory.length - 1] = latestSearch; return newHistory; } else { return [...prevHistory, latestSearch]; } }); } }, [state.search_history]); 
5.1.5 HITL(Human-in-the-loop)人工审核(人机协作)流程
  1. Agent增加中断环节
approval_request = { "type": "tool_approval_request", "tool_name": tool_call.get("name"), "tool_args": tool_call.get("args", {}), "tool_id": tool_call.get("id"), "timestamp": "2025-07-08" } # 拒绝 approve_status = interrupt(approval_request) if approve_status in ["rejected", "reject"]: .... # 如果审核通过,执行工具调用 elif approve_status in ["approved", "approve"]: .... 
  1. 前端增加中断反馈 hook
 useLangGraphInterrupt({ render: ({ event, resolve }) => { const { tool_name, tool_args } = event.value; return ( <div className="bg-gradient-to-br from-blue-50 to-indigo-50 border border-blue-200 rounded-2xl p-6 my-4 shadow-lg"> {/* 标题 */} <div className="flex items-center gap-3 mb-4"> <div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center"> <span className="text-lg">🔧</span> </div> <div> <h3 className="text-lg font-bold text-gray-800">工具调用审核</h3> <p className="text-sm text-gray-600">请确认是否执行以下工具调用</p> </div> </div> {/* 工具信息 */} <div className="bg-white rounded-xl p-4 mb-4 border border-gray-100"> <div className="grid grid-cols-1 gap-3"> <div> <label className="block text-xs font-medium text-gray-500 mb-1">工具名称</label> <div className="bg-gray-50 px-3 py-2 rounded-lg"> <code className="text-blue-600 font-mono text-sm">{tool_name}</code> </div> </div> <div> <label className="block text-xs font-medium text-gray-500 mb-1">参数</label> <div className="bg-gray-50 px-3 py-2 rounded-lg max-h-24 overflow-y-auto"> <pre className="text-xs text-gray-700 whitespace-pre-wrap font-mono"> {JSON.stringify(tool_args, null, 2)} </pre> </div> </div> </div> </div> {/* 操作按钮 */} <div className="mt-4"> <div className="flex gap-2"> <button type="button" onClick={() => resolve("approve")} className="flex-1 bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2 text-sm" > <span>✅</span> 通过 </button> <button type="button" onClick={() => resolve("reject")} className="flex-1 bg-red-500 hover:bg-red-600 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2 text-sm" > <span>❌</span> 拒绝 </button> </div> </div> </div> ); } }); 

5.2 nodejs示例

import express, {Request, Response} from 'express'; import dotenv from 'dotenv'; dotenv.config(); import {RunAgentInputSchema, RunAgentInput, EventType, Message} from '@ag-ui/core'; import {EventEncoder} from '@ag-ui/encoder'; import {v4 as uuidv4} from 'uuid'; import OpenAI from 'openai'; const app = express(); app.use(express.json()); app.post('/awp', async (req: Request, res: Response) => { console.log('app.post > req:'); try { // 解析请求体 const input: RunAgentInput = RunAgentInputSchema.parse(req.body); // 设置 SSE headers res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const encoder = new EventEncoder(); // 发送 started 事件 const runStarted = { type: EventType.RUN_STARTED, threadId: input.threadId, runId: input.runId }; res.write(encoder.encode(runStarted)); // 初始化 OpenAI 客户端 const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // 将 前端传入的 AG-UI 消息转换为 OpenAI 消息格式 const requestMessages = input.messages .filter((msg: Message) => ['user', 'system', 'assistant'].includes(msg.role)) .map((msg: Message) => ({ role: msg.role as 'user' | 'system' | 'assistant', content: msg.content || '' })); // 生成消息 ID const messageId = uuidv4(); // 发送 ‘文本消息开始’ 事件 const textMessageStart = { type: EventType.TEXT_MESSAGE_START, messageId, role: 'assistant' }; res.write(encoder.encode(textMessageStart)); // 创建流式传输完成请求 const stream = await client.chat.completions.create({ model: 'gpt-4o', messages: requestMessages, stream: true }); console.log('🚀 > app.post > stream:', stream); // 处理流并发送 ‘文本消息内容’ 事件 for await (const chunk of stream) { if (chunk.choices[0]?.delta?.content) { const content = chunk.choices[0].delta.content; const textMessageContent = { type: EventType.TEXT_MESSAGE_CONTENT, messageId, delta: content }; res.write(encoder.encode(textMessageContent)); } } // 发送 ‘文本消息结束’ 事件 const textMessageEnd = { type: EventType.TEXT_MESSAGE_END, messageId }; res.write(encoder.encode(textMessageEnd)); console.log('🚀 > app.post > TEXT_MESSAGE_END:'); // 发送 finished 事件 const runFinished = { type: EventType.RUN_FINISHED, threadId: input.threadId, runId: input.runId }; res.write(encoder.encode(runFinished)); console.log('🚀 > app.post > RUN_FINISHED:'); // 结束响应 res.end(); } catch (error) { res.status(422).json({error: 'Internal Server Error'}); } }); app.listen(3001, () => { console.log('Server running on http://localhost:3001'); }); 

六、总结

AG-UI 协议和 CopilotKit 框架代表了智能应用开发的新规范。它们不仅解决了 AI 集成的技术难题,更重要的是建立了一套标准化的开发模式,让每个开发者都能轻松构建智能应用。从前端工程师的角度,无需深究复杂的后端 AI 推理流程,只需使用熟悉的 React 组件和 Hooks,即可调动强大的AI为应用服务。从后端AI工程师的角度,你也无需操心前端展示,只要按照协议产出标准事件,UI 就会自动配合渲染。这种清晰的前后端职责分离与协同,使AI应用开发更加高效与标准化。

对于开发者:这意味着更高的开发效率和更低的学习成本
对于企业:这意味着更快的 AI 能力落地和更好的用户体验
对于用户:这意味着更自然的人机交互和更智能的应用体验


参考资源:

Read more

Ubuntu/Debian VPS 上 Apache Web 服务器的完整配置教程

Apache 是互联网上最流行的 Web 服务器之一,用于托管超过半数活跃网站。尽管市面上存在许多可用的 Web 服务器,但由于 Apache 的普遍性,了解其工作原理仍然具有重要意义。 本文将分享 Apache 的通用配置文件及其可配置选项。文中将以 Ubuntu/Debian 系统的 Apache 文件布局为例进行说明,这种布局方式与其他 Linux 发行版的配置层级结构有所不同。 版本兼容性 说明 :本教程已在 Ubuntu 22.04 LTS、Ubuntu 24.04 LTS、Ubuntu 25.04 以及 Debian 11、Debian 12 系统上通过验证测试。所有展示的命令和配置均兼容上述版本,且 Apache 配置结构与命令(如 a2ensite、

前端存储三剑客:localStorage、sessionStorage、cookie 超详细对比

前端存储三剑客:localStorage、sessionStorage、cookie 超详细对比

在前端开发中,数据本地存储是提升用户体验、优化性能、实现持久化状态的核心技术。我们最常用的就是 localStorage、sessionStorage 和 cookie 这三种方案,但很多开发者容易混淆它们的用法、存储特性和适用场景。 这篇博客就用最清晰、最实用的方式,一次性讲透三者的区别、用法和最佳实践。 一、先搞懂核心概念 * cookie:最早的客户端存储方案,会随 HTTP 请求自动发送到服务器,主要用于身份验证、会话保持。 * localStorage:HTML5 新增的本地存储,持久化存储,手动清除才会消失,不参与网络请求。 * sessionStorage:HTML5 新增的会话存储,页面会话期间有效,关闭标签页 / 浏览器就清空。 二、核心区别一张表看懂 表格 特性localStoragesessionStoragecookie生命周期永久有效,手动清除仅当前会话(关闭标签 / 浏览器失效)可设置过期时间,默认会话级存储容量约 5MB约 5MB很小,仅 4KB与服务端通信不参与不参与自动携带在

我用 Vibe Code 做出了漂亮的 Web 应用,但 AI 依然无法为 Google Search 自动生成一个简单的 Sitemap

我用 Vibe Code 做出了漂亮的 Web 应用,但 AI 依然无法为 Google Search 自动生成一个简单的 Sitemap 在最近一段时间里,我看到很多开发者和创业者开始用 AI 工具做网站、Web 应用这些东西,比如所谓的 vibe coding 平台:快速生成页面、美观的前端、自动部署等等。乍一看体验很棒,但当你开始关注 SEO 和搜索引擎索引时,这一切就变得很不那么简单了。 我自己做过很多网站的 SEO,这本应该是个“十分钟搞定”的事儿 —— “生成 sitemap.xml,提交到 Google Search Console,搞定。” 但是在实际操作中,问题远比想象复杂。 项目背景 我做的第一个项目是一个在线餐厅目录:收集了所有提供食物过敏菜单的餐厅信息,供过敏患者快速查询。

Qwen3-1.7B支持流式响应?实战验证与前端集成教程

Qwen3-1.7B支持流式响应?实战验证与前端集成教程 最近在折腾大模型应用开发,特别是想给前端加个实时聊天的效果,就一直在找支持流式输出的轻量级模型。Qwen3系列开源后,我第一时间注意到了1.7B这个版本——参数小,部署快,但官方文档里关于流式响应的说明不太详细。 所以,我决定自己动手验证一下:Qwen3-1.7B到底支不支持流式响应?如果支持,怎么在前端项目里用起来?这篇文章就是我的实战记录,从环境搭建、接口测试到前端集成,一步步带你走通整个流程。 1. 环境准备与快速启动 要在本地或者云端快速体验Qwen3-1.7B,最省事的方法就是直接用现成的Docker镜像。这里我以ZEEKLOG星图平台的镜像为例,带你快速启动一个可用的环境。 1.1 启动Jupyter Notebook环境 1. 找到Qwen3-1.7B的镜像并启动。平台通常会提供一个预装好所有依赖的容器。 2. 容器启动后,直接打开提供的Jupyter Notebook链接。你会看到一个熟悉的网页界面,里面已经配置好了Python环境和必要的库。 这样,我们就不用操心安装PyTorch、Tran