ai agent --- AGUI 协议,流式渲染组件

一. AGUI 协议是什么

AG-UI(Agent-User Interaction Protocol,智能体-用户交互协议) 他是前端和智能体交互的协议。AG-UI 跑在 HTTP 上面,但不是 HTTP 的替代品。AG-UI 是"寄生在 HTTP 之上的一套语义方言
前端用http协议调用大模型接口,此时大模型运行需要很长的时间,才能给我结果。AG-UI 管的不是"调用"这个动作,是调用之后那漫长的几分钟里发生的事
AG-UI 默认的传输方式就是 HTTP SSE(Server-Sent Events),它还能跑在 WebSocket、Webhook 上

综上所述,AG_UI是跑在http上的协议,但它不是"大模型界的 HTTP",它是"Agent 执行过程的直播协议"------借 HTTP 的道,把 Agent 干活的全过程实时转播给前端,还允许观众中途喊停。

如果你的需求只是"前端发句话、拿回大模型回复",那 AG-UI 对你毫无价值 ,Vercel AI SDK 可以直接搞定。只有当你开始头疼"用户盯着转圈干等""Agent 调了什么工具我不知道""这个危险操作要不要让人确认一下"------这些过程性问题,AG-UI 才登场。

二. Vercel AI SDK是什么

2.1 Vercel AI SDK是什么

官网地址ai-sdk.dev/docs/ai-sdk...

Vercel 出的一套 TypeScript 工具箱,让你用同一套代码调遍所有大模型。 ​ 装它只要 npm i ai。开源(Apache-2.0),是 TS 圈做 AI 应用事实上的默认选择。

说白了,他就是一个能够调用大模型,实现前后端代码的包。他里面包含以下三个部分:

1. ai 包:写 agent 逻辑

2. @ai-sdk/openai、 @ai-sdk/anthropic 等包:对接不同的大模型,就和 langchain 的 ChatModel 一样

3. @ai-sdk/react、@ai-sdk/vue 等包:对接后端接口,实现页面渲染

他里面有前端所需要的工具,也有后端所需要的的工具,你可以选择它实现前后端关于大模型的代码。

2.2 Vercel AI SDK和AG-UI的关系

Vercel AI SDK 回答"模型和工具怎么接进我的应用",AG-UI 回答"Agent 的运行过程怎么持续传给前端"。他们两个关注点不一样。

2.3 Vercel AI SDK 和 @langchain的关系

你可能又会问 @langchain 也能在用一套代码调遍所有大模型,那它和 Vercel AI SDK 之间的区别是什么?

**** 关心的问题
Vercel AI SDK "用户看到的界面怎么丝滑?字怎么一个一个蹦出来?"
LangChain "这个 Agent 跑 20 分钟崩了怎么办?中间要人审批怎么办?状态存哪?"

说白了,Vercel AI SDK关注的更多的是前端的事情,LangChain关注的是后端的事情,他们的侧重点不一样。

他们三个之间的关系如图所示:

js 复制代码
[前端 UI]  ==== 中间这条线 ====  [后端 Agent]  ----  [大模型]
   ↑                  ↑                 ↑              ↑
useChat            ★问题在这★      LangChain      OpenAI API
(Vercel AI SDK)

LangChain 和 Vercel AI SDK 是两个家族的工具箱 ,各自带一根自家规格的线。你只用一家的东西时,线是配套的;你要把两家的东西接起来、或者以后想随便换,就该买一根标准线------那就是 AG-UI。

三. 在项目里 AGUI, Vercel Ai SDK 和 langchain怎么选择?

当你做为一个架构师,你到底应该怎么选择技术方案?

  • 1.一个全栈 TS 项目,一个人维护 ------ 直接用 Vercel 那条线最快。此时也不需要AGUI的,因为 **前后端 属于同一个家族,加 AG-UI 纯属多余。
  • 2.只是简单问答、没有工具调用和长任务 ------ 过程性能力你根本用不上,所以不要用AGUI。此时你用LangChain 或者 Vercel AI SDK 都行。

3.1 何时使用AGUI?

信号 为什么这时需要 AG-UI
前端不是 Vercel 生态 你要接 React Native、Flutter、Kotlin、Python 客户端、或者自己写的任意 UI ------ useChat 用不了,需要一份中立格式
后端可能换 今天是 LangGraph,明年想换 CrewAI。用 AG-UI,前端一行不动(因为这些框架官方都支持 AG-UI
前后端两拨人 / 两个仓库 需要一份白纸黑字的契约,吵架时能拿出来对照
要 AG-UI 独有的语义 JSON Patch 状态增量同步、跨 run 的 interrupt/resume(Agent 暂停几小时后等你审批再继续)、子 Agent 归属追踪
同一个后端要喂多个前端 Web、CLI、IDE 插件、企业微信...... 后端只发一种格式

任何情况下, AGUI 都不是必须的。

因为 LangChain 的流可以转成 AI SDK 格式。 AG-UI 不是必需品,它只是一份"保险"。

如果你的后端可能会换,或者后端是 Python 写的、前端是另一拨人在维护​ ------ 那么 AG-UI 就该在第一天写上,如果后期再补就会很难受。

3.2 列举三个可用的架构模板

处境 结果
团队是 Python / 有数据科学栈 LangChain 是主场;Vercel AI SDK 直接出局(它只有 TS)
全栈 TS / Next.js 团队 Vercel AI SDK 主场;LangChain.js 是次一等选择(功能少于 Python 版)
混合(Python 算 + TS 编排) 两个都用,但边界必须划清楚

3.2.1 前后端都用 Vercel

TS 全栈、需求中等(最常见,也最推荐起步)

js 复制代码
前端 useChat  ←── AI SDK 自己的 UI message stream 协议 ──←  后端 streamText

此时,前端用Vercel AI SDK的 useChat ,后端也用 Vercel AI SDK 的 streamText,那中间走的是它自带的私有协议text-start / text-delta / text-end 那套)。里面没有用 LangChainAG-UI

这种所述,反转成本最低,如果要添加 AG-UILangChain 的门都还开着。

3.2.2 LangChain 后端 + Vercel 前端

一般场景:Python 后端 + Web 前端

js 复制代码
Vercel AI SDK useChat  ←── (适配器转换,或 AG-UI)──←  LangChain create_agent / LangGraph

此时你会发现一个问题:LangChain 返回出来的流,格式和 Vercel 的对不上。 怎么办?

两个选择:

  • 1.适配器转换:把 LangChain 的输出转成 AI SDK 的 UI message stream 格式(有现成适配器,或自己用 LangChain 的 streamEvents 手写一个转换层)。仍然不需要 AG-UI。

    1. 利用AG-UI:让后端直接吐 AG-UI 事件,前端换成能消费 AG-UI 的客户端。这时 AG-UI 登场。

综上所述:如果后端 LangGraph 且前端只有 React → 用适配器就够;如果前端会扩展或后端会换(Python换成其他语言) → 上 AG-UI。

3.3.3 任意前后端

企业级多端 + 长任务 Agent

js 复制代码
任意前端(Web / CLI / 移动端,各自实现 AG-UI client)  ←── AG-UI 事件流 ──←  任意后端(LangGraph(持久化、HITL、LangSmith))

这种场景就是:企业网里面他既有pc端,还有小程序,app等等,其实这个时候你最该引入AG-UI。 当任意前后端的时候,就必须要用AG-UI 事件流,来连接前后端数据。

这是 AG-UI 真正的主场。

3.3.4 AG-UI 和 Vercel AI SDK的type区别

四. createAgent是什么

之前我们写model是这样的

现在使用createAgent是这样的

createAgent的优点,他会根据tooldescription去自主选择到底要使用哪个tool,不再像model一样,先绑定tool,绑定好以后还要用for循环,看看tool_calls里面的需要用哪个tool,然后再比对使用。

明显少了很多代码。

所以以后直接使用createAgent就好了。

五. 用 Vercel AI SDK 和 langchain 实现流式组件渲染

5.1 后端--nestjs

5.1.1 安装,配置常量

js 复制代码
nest new agui-backend
cd agui-backend
pnpm install @langchain/core @langchain/openai @nestjs/config zod
pnpm install ai @ai-sdk/langchain

配置 .env

js 复制代码
OPENAI_API_KEY=sk-xx
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
MODEL_NAME=qwen-plus

BOCHA_API_KEY=sk-xx

博查apikey:open.bochaai.com/api-keys

app.module.ts的配置

由于前后端属于2个不同的项目,他们的服务启动后,会有两个不同的端口号,这时候,访问接口就会存在跨域问题,我们在后端项目的main.ts里面配置如下:

enableCors表示容许跨域

5.1.2 代码

js 复制代码
nest g resource ai --no-spec
ai.module.ts

module里面的代码和以前的没有变,就是提取创建model和搜索网页的tool。

js 复制代码
import { Module } from '@nestjs/common';
import { AiService } from './ai.service.js';
import { AiController } from './ai.controller.js';
import { ConfigService } from '@nestjs/config';
import { ChatOpenAI } from '@langchain/openai';
import { tool } from '@langchain/core/tools';
import z from 'zod';

@Module({
  controllers: [AiController],
  providers: [
    AiService,
    {
      provide: 'CHAT_MODEL',
      inject: [ConfigService],
      useFactory: (config: ConfigService) => {
        const model = new ChatOpenAI({
          modelName: config.get('MODEL_NAME'),
          apiKey: config.get('OPENAI_API_KEY'),
          configuration: {
            baseURL: config.get('OPENAI_BASE_URL'),
          },
        });
        return model;
      },
    },
    {
      provide: 'WEB_SEARCH_TOOL',
      inject: [ConfigService],
      useFactory: (config: ConfigService) => {
        const websearchSchema = z.object({
          query: z
            .string()
            .min(1)
            .describe('搜索关键词,比如:公司年报,某个事件等等'),
          count: z
            .number()
            .int()
            .min(1)
            .max(20)
            .describe('返回结果数量,默认为10条'),
        });

        return tool(
          async ({ query, count }) => {
            const apikey = config.get('BOCHA_API_KEY');

            if (!apikey) {
              throw new Error('请配置 BOCHA_API_KEY 环境变量');
            }

            const url = 'https://api.bochaai.com/v1/web-search';

            const body = {
              query,
              freshness: 'noLimit',
              summary: true,
              count: count ?? 10,
            };

            const response = await fetch(url, {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${apikey}`,
              },
              body: JSON.stringify(body),
            });

            if (!response.ok) {
              const err = await response.text();
              return `搜索失败:${err}`;
            }

            try {
              let json = await response.json();
              if (json.code != 200 || !json.data) {
                return '搜索失败:${json.msg}';
              }

              const webpage = json.data.webpages?.value ?? [];

              if (!webpage.length) {
                return `搜索结果为空`;
              }

              const formatted = webpage
                .map(
                  (page: any, idx: number) => `
                引用: ${idx + 1}
                标题: ${page.name}
                URL: ${page.url}
                摘要: ${page.summary}
                网站名称: ${page.siteName}
                网站图标: ${page.siteIcon}
                发布时间: ${page.dateLastCrawled}
              `,
                )
                .join('\n\n');

              return formatted;
            } catch (e) {
              return `搜索失败:${e}`;
            }
          },
          {
            name: 'web_search',
            description:
              '使用 Bocha Web Search API 搜索互联网网页。输入为搜索关键词(可选 count 指定结果数量),返回包含标题、URL、摘要、网站名称、图标和时间等信息的结果列表。',
            schema: websearchSchema,
          },
        );
      },
    },
  ],
})
export class AiModule {}
ai.service.ts

之前我们用new ChatOpenAI初始化出来的model直接调用stream或者invoke来执行大模型,执行后回来需要调用agent tool就需要用循环语句,一个一个地找,然后在处理。现在有了createAgent,我们只需要将model还有tool传进这个方法,之前做的脏活累活,他都会帮我我们做了。

在service里面将入参 UIMessage 用toBaseMessages转化LangChain可用的message。

大模型执行完以后,将他返回的langchain message用toUIMessageStream处理成UIMessage。

js 复制代码
import { Inject, Injectable } from '@nestjs/common';
import { ChatOpenAI } from '@langchain/openai';
import {
  AIMessage,
  AIMessageChunk,
  createAgent,
  HumanMessage,
  SystemMessage,
  ToolMessage,
} from 'langchain';
import { UIMessage } from 'ai';
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';

@Injectable()
export class AiService {
  private readonly agent: ReturnType<typeof createAgent>;

  constructor(
    @Inject('CHAT_MODEL') model: ChatOpenAI,
    @Inject('WEB_SEARCH_TOOL') private readonly webSearchTool: any,
  ) {
    this.agent = createAgent({
      model,
      tools: [this.webSearchTool],
      systemPrompt:
        '你是 AI 助手,需要最新信息、事实核查或联网信息时,请使用 web_search 工具搜索后再作答。',
    });
  }

  async stream(messages: UIMessage[]) {
    const lcMessages = await toBaseMessages(messages);// 转换 UIMessage 到 LangChain 的 Message[]
    const lgStream = await this.agent.stream(
      {
        messages: lcMessages,
      },
      {
        streamMode: ['messages', 'values'],
        recursionLimit: 12,
      },
    );

    return toUIMessageStream(lgStream);// 转换 LangChain 的 AsyncGenerator<AIMessageChunk> 到 UIMessageStream
  }
}
ai.controller.ts

这里如果是流式输出,我们之前用的SSE来实现。现在有了VercelAISDK,就直接用post请求就好了。

js 复制代码
import {
  BadRequestException,
  Body,
  Controller,
  Get,
  Post,
  Query,
  Res,
  Sse,
} from '@nestjs/common';
import type { Response } from 'express';
import { AiService } from './ai.service.js';
import { pipeUIMessageStreamToResponse, UIMessage } from 'ai';

@Controller('ai')
export class AiController {
  constructor(private readonly aiService: AiService) {}

  // 这里用的ai就不要再用SSE了,他里面已经帮我封装好了,直接用这个就好。

  @Post('chat')
  async postChat(
    @Body() body: { messages?: UIMessage[] },
    @Res({ passthrough: false }) res: Response,
  ): Promise<void> {
    if (!body?.messages || !Array.isArray(body.messages)) {
      throw new BadRequestException('Invalid JSON');
    }

    const stream = await this.aiService.stream(body.messages);
    pipeUIMessageStreamToResponse({ response: res, stream }); // 最近修改的代码片段结束处
  }
}

老的SSE请求如下:

对比两者代码,你会发现:现在的代码比之前的已经精简很多倍。

5.1.3 测试

js 复制代码
 curl.exe --% -N -sS -g -X POST http://127.0.0.1:3000/ai/chat -H "Content-Type: application/json" -d "{\"messages\":[{\"id\":\"1\",\"role\":\"user\",\"parts\":[{\"type\":\"text\",\"text\":\"北京今天的天气\"}]}]}"

看到红框标注,就说明接口已经开发成功。

前端就是依靠type不同的类型来处理具体的样式。

5.2 前端

用户问大模型问题后,大模型利用vercel AI SDK给前端特定格式的数据。前端拿到数据,根据不同的type解析成具体的样式,比如,我说请给我写一段加法js代码案例,放到代码块里面。

比如:今日金价用表格展示出来。

如果前端不做处理,你的页面是这样的

使用样式处理后,你的ai agent是这样的

综上所述,怎么搜索是后端的事情,具体数据怎么展示还是前端的事情,大模型给我们的数据永远是一堆文本,AGUI和Vercel AI SDK 将文本流转化成一个json对象,前端根据这个对象里面的属性值,来判断具体怎么渲染。

现在有个现成的解析包叫streamdown,他就能将markdown格式的文本按照一定的样式显示在页面上。

5.2.1 安装资源包

Vercel AI SDK 的前端包名是ai,不管你选的是vue,还是react,都可以用@ai-sdk来连接。

  • react 项目,用@ai-sdk/react来对接。

  • vue 项目,用@ai-sdk/vue来对接。

具体安装如下:

js 复制代码
npx create-vite agui-frontend

cd agui-frontend

pnpm install @ai-sdk/react ai

pnpm install streamdown @streamdown/code @streamdown/mermaid

npm i -D tailwindcss @tailwindcss/vite

5.2.2 解释streamdown

streamdown @streamdown/code @streamdown/mermaidVercel 出的一套 AI 流式 Markdown 渲染方案,一个主包 + 两个可选插件。

  • streamdown ------ 主包(必装),把 AI 返回的 Markdown 文本渲染成漂亮 HTML 的 React 组件。直接显示就是一堆带 #* 的纯文本。streamdown 把它渲染成真正的标题、列表、加粗、代码块。

  • @streamdown/code ------ 代码高亮插件(可选)把代码显示在代码块里面。

  • @streamdown/mermaid ------ 图表插件(可选)

5.2.3 解释Tailwind

Streamdown 强制依赖 Tailwind,所以需要安装tailwindcss @tailwindcss/vite

js 复制代码
npm i -D tailwindcss @tailwindcss/vite

安装完毕以后还需要在vite.config.ts配置tailwindcss

js 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
})

src/index.css里面,全局配置CSS样式

js 复制代码
/* src/index.css 或 src/globals.css */
@import "tailwindcss";

@source "../node_modules/streamdown/dist/*.js";
@source "../node_modules/@streamdown/code/dist/*.js";
@source "../node_modules/@streamdown/mermaid/dist/*.js";

在使用Streamdown组件的时候,添加import "streamdown/styles.css";

5.2.4 重点代码解析

用了vercel AI SDK 我们的流式输出的文本都不用在拼接了,他能直接实现流式打印。

message就是后端返回的那个json,你从message里面就能拿到很多属性值,parts就是一些特殊处理的部分。

根据part.type的类型不同,做不同的处理。

这个就是代码块处理的地方,你把mermaid, code: codePlugin放到plugin里面他会自动处理。

ToolMessagePart进入的地方

接口数据对照:

text-start 代表文本流开始

text-delta 是流式的文本数据

text-end 代表文本流结束 tool-input-start 代表开始接收到 tool 的参数

tool-input-delta 是流式的 tool call 的参数

tool-input-available 代表 tool 的参数接收完

tool-output-available 代表有了 tool 的调用结果,可以从 output 里取

5.3测试

启动前后端代码

六. 用Vercel AI SDK做前后端,实现流式组件渲染

6.1 生成项目

创建一个nextjs项目,Vercel AI SDK 前后端都可以用js写。

js 复制代码
# 1. 建 Next.js 项目
npx create-next-app@latest my-app --yes
cd my-app

# 2. 装 AI SDK(装完就是 AI 项目了)
npm i ai @ai-sdk/react zod

# 3. 配 key
echo "OPENAI_API_KEY=sk-xxx" > .env.local

# 4. 跑
npm run dev

--yes = TypeScript + Tailwind + ESLint + App Router + Turbopack,全程默认。

6.2 目录

生成项目的目录结构如下:

js 复制代码
my-app/
├── app/
│   ├── api/chat/route.ts   ← 后端(就这一个文件)
│   ├── page.tsx            ← 前端
│   └── layout.tsx
└── .env.local

6.3 后端代码 app/api/chat/route.ts

js 复制代码
import { streamText, convertToModelMessages, stepCountIs, tool, type UIMessage } from 'ai'
import { z } from 'zod'

export const maxDuration = 30

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json()

  const result = streamText({
    model: 'openai/gpt-5.2',                       // 换模型改这行
    instructions: '你是一个有用的助手。',
    messages: await convertToModelMessages(messages),
    stopWhen: stepCountIs(5),                      // 多步工具调用上限
    tools: {
      getWeather: tool({
        description: '查询城市天气',
        inputSchema: z.object({ city: z.string() }),
        execute: async ({ city }) => ({ city, temp: 26, desc: '晴' }),
      }),
    },
  })

  return result.toUIMessageStreamResponse()
}

6.4 前端代码:app/page.tsx

js 复制代码
'use client'
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { useState } from 'react'

export default function Chat() {
  const [input, setInput] = useState('')
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({ api: '/api/chat' }),
  })

  return (
    <div className="mx-auto max-w-2xl p-4">
      {messages.map(m => (
        <div key={m.id} className="whitespace-pre-wrap py-2">
          <b>{m.role === 'user' ? '你' : 'AI'}:</b>
          {m.parts.map((p, i) => {
            if (p.type === 'text') return <span key={i}>{p.text}</span>
            if (p.type === 'tool-getWeather') return <span key={i}>(查天气中...)</span>
            return null
          })}
        </div>
      ))}

      <form onSubmit={e => {
        e.preventDefault()
        sendMessage({ text: input })
        setInput('')
      }}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          disabled={status === 'streaming'}
          placeholder="说点什么..."
        />
        <button type="submit">发送</button>
      </form>
    </div>
  )
}

前后端都用 AI SDK"的好处:前端 useChat 和后端 streamText 说同一种方言,中间零转换层。

6.5 总结

前后端整体只用三个地方应用 Vercel AI SDK 就解决了很多脏活累活。

import { streamText, convertToModelMessages, stepCountIs, tool, type UIMessage } from 'ai'

import { useChat } from '@ai-sdk/react'

import { DefaultChatTransport } from 'ai'

Vercel AI SDK 只干一件事:把"模型 → 流 → 前端状态"这条链路上的所有脏活标准化。

你的代码里它只露脸 5 次,但省掉的是协议设计、流式解析、工具循环、状态管理 这四块------这才是"前后端都用同一个 SDK"最大的价值:两端说同一种方言,中间零转换层。

七.用 AG-UI 创建项目,实现流式组件渲染

@latest为最新版本

js 复制代码
npx create-ag-ui-app my-agent-app
npx create-ag-ui-app@latest my-agent-app

运行中优先选择:

  • LangGraph (JavaScript) ------ 单项目、单进程,npm run dev 一把梭,最省事。适合先跑通看效果。
  • LangGraph (Python, FastAPI) ------ 会生成 agent-py/ + ui-react/ 两个目录,后端是独立 FastAPI,更接近生产架构,但要配 Python 环境。(后期学完Python,我们可以试试这个,到时候我会把案例补充在后面,现在就用js即可。)

上述选项选择完毕以后,他就会自动拉模板代码

  • ai@ag-ui/*@copilotkit/* 等依赖
  • 把前端和 AG-UI 的事件接线接好

等待登录,登录后才能继续安装。

相关推荐
十二教育2 小时前
企业网盘选型分析
人工智能
~央千澈~2 小时前
优雅草科技卓伊凡PC电脑清理器-优雅草清理器2026年9月14日发布
人工智能
Keep_Trying_Go2 小时前
Kaggle CLI Datasets上传文件教程(保姆级)
人工智能·计算机视觉·kaggle
用户8181870627462 小时前
第26章 Kafka vs RocketMQ vs RabbitMQ真实选型对比
java·后端
桃西西呀2 小时前
提前一天预报雾霾:手搓神经网络,讲清学习率、初始化、正则化
人工智能·深度学习·llm
小兔崽子去哪了2 小时前
深度学习 2 / CNN 神经网络
后端·python
这张生成的图像能检测吗2 小时前
(论文速读)FiDeSR:高保真保细节一步扩散超分辨率
图像处理·人工智能·深度学习·计算机视觉·扩散模型·图像超分
网易易盾2 小时前
应用加固如何应对AI辅助逆向?从单点防护到持续对抗
人工智能·安全
维核科技2 小时前
自动驾驶商业化提速:Robotaxi 开始收费,无人重卡走向量产
人工智能