跳至主要內容

使用 OpenUI

OpenUI 是生成式 UI 的開放標準。它結合精簡、以串流為先的語言(OpenUI Lang)、React 執行環境和內置元件庫,讓模型輸出可在串流期間呈現為結構化 UI。

OpenUI 透過 AG-UI 協定連接至 Mastra。@ag-ui/mastra 轉接器會封裝 Mastra Agent 並發出 AG-UI 事件,而 OpenUI 的 agUIAdapter() 會在用戶端解析這些事件。

提示

如需完整的可運作範例,請參閱 OpenUI 程式碼庫中的 mastra-chat 範例。

整合指南
整合指南 的直接連結

將 Mastra 嵌入 Next.js API 路由,並透過 AG-UI 協定把 OpenUI <AgentInterface /> 聊天介面連接至該路由。

  1. 建立新的 OpenUI 應用程式基本結構:

    npx @openuidev/cli@latest create --name openui-mastra-chat

    前往新建立的項目目錄:

    cd openui-mastra-chat

    建立好的應用程式是採用以下結構的 Next.js 項目:

    openui-mastra-chat
    └── src
    ├── app
    │ ├── api
    │ │ └── chat
    │ │ └── route.ts
    │ ├── globals.css
    │ ├── layout.tsx
    │ └── page.tsx
    ├── generated
    │ └── system-prompt.txt
    └── library.ts

    聊天路由位於 src/app/api/chat/route.ts,聊天介面位於 src/app/page.tsx,元件庫則位於 src/library.ts。OpenUI CLI 會根據你的元件庫寫入 src/generated/system-prompt.txt;每當元件庫有變更時,都要重新產生此檔案。

    將你的 OpenAI API 金鑰加入 .env.local

    .env.local
    OPENAI_API_KEY=sk-...
    備註

    OpenUI 需要模型 Provider 的 API 金鑰。你可以使用 Mastra 支援的任何 Provider,並在下一步調整 Agent 設定。

  2. 安裝 Mastra 依賴套件及適用於 Mastra 的 AG-UI 轉接器:

    npm install @mastra/core @ag-ui/mastra @ag-ui/core zod

    @ag-ui/mastra 會把 Mastra Agent 封裝在會發出 AG-UI 協定事件的 MastraAgent 中。OpenUI 的 agUIAdapter() 會在用戶端接收這些事件。

  3. 開啟 src/app/api/chat/route.ts。使用來自 @mastra/core/toolscreateTool,定義 Agent 所需的任何 Tool:

    src/app/api/chat/route.ts
    import { createTool } from '@mastra/core/tools'
    import { z } from 'zod'

    const getWeather = createTool({
    id: 'get_weather',
    description: 'Get current weather for a city.',
    inputSchema: z.object({ location: z.string().describe('City name') }),
    execute: async ({ location }) => {
    return { location, temperature_celsius: 22, condition: 'Clear' }
    },
    })

    將 Mastra Agent 封裝在 MastraAgent 中。注入產生的系統提示,讓 Agent 知道如何使用 OpenUI 元件庫:

    src/app/api/chat/route.ts
    import { MastraAgent } from '@ag-ui/mastra'
    import { Agent } from '@mastra/core/agent'
    import { readFileSync } from 'fs'
    import { join } from 'path'

    const systemPrompt = readFileSync(join(process.cwd(), 'src/generated/system-prompt.txt'), 'utf-8')

    const agent = new MastraAgent({
    agent: new Agent({
    id: 'openui-agent',
    name: 'OpenUI Agent',
    instructions: `You are a helpful assistant. Use tools when relevant.\n\n${systemPrompt}`,
    model: {
    id: 'openai/gpt-5.6-sol',
    apiKey: process.env.OPENAI_API_KEY,
    },
    tools: { getWeather },
    }),
    resourceId: 'chat-user',
    })

    匯出 POST 處理常式,以 Server-Sent Events (SSE) 串流傳送 Agent 的 AG-UI 事件:

    src/app/api/chat/route.ts
    import type { Message } from '@ag-ui/core'
    import { NextRequest } from 'next/server'

    export async function POST(req: NextRequest) {
    const { messages, threadId }: { messages: Message[]; threadId: string } = await req.json()
    const encoder = new TextEncoder()

    const stream = new ReadableStream({
    start(controller) {
    const subscription = agent
    .run({ messages, threadId, runId: crypto.randomUUID(), tools: [], context: [] })
    .subscribe({
    next: event => {
    controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`))
    },
    complete: () => {
    controller.enqueue(encoder.encode('data: [DONE]\n\n'))
    controller.close()
    },
    error: error => {
    controller.enqueue(
    encoder.encode(`data: ${JSON.stringify({ error: error.message })}\n\n`),
    )
    controller.close()
    },
    })

    req.signal.addEventListener('abort', () => subscription.unsubscribe())
    },
    })

    return new Response(stream, {
    headers: {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache, no-transform',
    Connection: 'keep-alive',
    },
    })
    }
  4. 將 OpenUI <AgentInterface /> 聊天介面連接至路由。使用 fetchLLM() 建立 llm 轉接器,並將 streamAdapter 設為 agUIAdapter(),讓 OpenUI 知道要解析 AG-UI 事件。

    src/app/page.tsx
    'use client'

    import '@openuidev/react-ui/components.css'

    import { AgentInterface, agUIAdapter, fetchLLM } from '@openuidev/react-ui'
    import { openuiChatLibrary } from '@openuidev/react-ui/genui-lib'

    const llm = fetchLLM({
    url: '/api/chat',
    streamAdapter: agUIAdapter(),
    })

    export default function Page() {
    return (
    <div className="relative h-screen w-screen overflow-hidden">
    <AgentInterface llm={llm} componentLibrary={openuiChatLibrary} />
    </div>
    )
    }

    componentLibrary 屬性控制模型可產生哪些元件。你可以把 openuiChatLibrary 換成自己的元件庫,以限制或擴充輸出。

  5. 啟動開發伺服器:

    npm run dev

    開啟 http://localhost:3000。你現在可以透過 OpenUI 聊天介面與 Mastra Agent 對話,而模型在串流傳送內容時,結構化 UI 亦會逐步呈現。

使用 AG-UI 進行串流傳送
使用 AG-UI 進行串流傳送 的直接連結

OpenUI 會接收 AG-UI 協定,這是一種不受傳輸方式限制、適用於 AI Agent 的已定義類型事件串流。@ag-ui/mastra 會將 Mastra Agent 轉換成此協定:

  • 伺服器會呼叫 agent.run({ messages, threadId, runId, ... }),並將每個發出的事件序列化為 SSE 訊息。
  • 用戶端會將 fetchLLM({ streamAdapter: agUIAdapter() }) 傳遞至 <AgentInterface />,由後者把 SSE 串流解析成驅動 OpenUI Lang 呈現的內部事件。

threadId 會把跨請求的對話連繫起來,而 runId 則識別單次執行。請為每個請求產生新的 runId,並在用戶端保存 threadId

元件庫
元件庫 的直接連結

OpenUI 會根據元件庫產生 UI。元件庫定義可使用的元件、其屬性,以及如何指示模型使用這些元件。

內置元件庫
內置元件庫 的直接連結

@openuidev/react-ui 提供兩個可直接使用的元件庫:

  • openuiChatLibrary:聊天介面元件(卡片、表單、表格、圖表)。
  • openuiDashboardLibrary:儀表板和資料密集型介面的元件。

將元件庫傳遞至 <AgentInterface componentLibrary={...} />,即可讓模型使用其中的元件。

自訂元件庫
自訂元件庫 的直接連結

如要限制或擴充輸出,請在 src/library.ts 定義自己的元件庫,並匯出部分元件。將該元件庫傳遞至 <AgentInterface />,並在元件庫每次有變更時重新產生系統提示:

npx @openuidev/cli generate src/library.ts --out src/generated/system-prompt.txt

產生的提示會由 API 路由讀取並合併至 Agent 的 instructions,讓 Agent 確切知道可以發出哪些元件。