跳至主要內容

使用 OpenUI

OpenUI 是生成式 UI 的開放標準。它將精簡、串流優先的語言(OpenUI Lang)與 React runtime 及內建元件庫搭配使用,讓模型輸出能在串流時轉譯為結構化 UI。

OpenUI 透過 AG-UI protocol 連接至 Mastra。@ag-ui/mastra adapter 會包裝 Mastra Agent 並發出 AG-UI 事件,再由 OpenUI 的 agUIAdapter() 在使用者端解析。

提示

如需完整且可實際運作的範例,請參閱 OpenUI 儲存庫中的 mastra-chat 範例。

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

將 Mastra 嵌入 Next.js API 路由,並透過 AG-UI protocol 將 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 金鑰加入 .env.local

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

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

  2. 安裝 Mastra 套件及適用於 Mastra 的 AG-UI adapter:

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

    @ag-ui/mastra 會使用可發出 AG-UI protocol 事件的 MastraAgent 包裝 Mastra Agent。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' }
    },
    })

    使用 MastraAgent 包裝 Mastra Agent。注入產生的 system prompt,讓 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 handler,將 Agent 的 AG-UI 事件以 Server-Sent Events(SSE)形式串流:

    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 adapter,並將 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 prop 控制模型可產生哪些元件。以自己的元件庫取代 openuiChatLibrary,即可限制或擴充輸出。

  5. 啟動開發伺服器:

    npm run dev

    開啟 http://localhost:3000。現在可以透過 OpenUI 聊天介面與 Mastra Agent 聊天;模型串流時,結構化 UI 會逐步轉譯。

使用 AG-UI 串流
「使用 AG-UI 串流」的直接連結

OpenUI 使用 AG-UI protocol,這是一種不受傳輸方式限制、供 AI Agent 使用的型別化事件串流。@ag-ui/mastra 會將 Mastra Agent 轉換為此 protocol:

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

threadId 可跨請求將對話串連起來,而 runId 則識別單次執行。每個請求都應產生新的 runId,並在使用者端持久保存 threadId

元件庫
「元件庫」的直接連結

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

內建元件庫
「內建元件庫」的直接連結

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

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

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

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

若要限制或擴充輸出,請在 src/library.ts 中定義自己的元件庫,並匯出元件子集。將該元件庫傳入 <AgentInterface />,並在每次元件庫變更時重新產生 system prompt:

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

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