> Discover all available pages from the documentation index: https://mastra.zisheng.pro/zh-TW/llms.txt # 使用 OpenUI [OpenUI](https://openui.com) 是生成式 UI 的開放標準。它將精簡、串流優先的語言(OpenUI Lang)與 React runtime 及內建元件庫搭配使用,讓模型輸出能在串流時轉譯為結構化 UI。 OpenUI 透過 [AG-UI protocol](https://docs.ag-ui.com) 連接至 Mastra。`@ag-ui/mastra` adapter 會包裝 Mastra `Agent` 並發出 AG-UI 事件,再由 OpenUI 的 `agUIAdapter()` 在使用者端解析。 > **提示:** 如需完整且可實際運作的範例,請參閱 OpenUI 儲存庫中的 [`mastra-chat`](https://github.com/thesysdev/openui/tree/main/examples/mastra-chat) 範例。 ## 整合指南 將 Mastra 嵌入 Next.js API 路由,並透過 AG-UI protocol 將 OpenUI `` 聊天介面連接至該路由。 1. 建立新的 OpenUI 應用程式架構: **npm**: ```bash npx @openuidev/cli@latest create --name openui-mastra-chat ``` **pnpm**: ```bash pnpm dlx @openuidev/cli@latest create --name openui-mastra-chat ``` **Yarn**: ```bash yarn dlx @openuidev/cli@latest create --name openui-mastra-chat ``` **Bun**: ```bash bun x @openuidev/cli@latest create --name openui-mastra-chat ``` 前往剛建立的專案目錄: ```bash cd openui-mastra-chat ``` 建立出的應用程式是採用下列結構的 Next.js 專案: ```bash 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`: ```bash OPENAI_API_KEY=sk-... ``` > **備註:** OpenUI 需要模型 Provider 金鑰。你可以使用 Mastra 支援的任何 Provider,並在下一步調整 Agent 組態。 2. 安裝 Mastra 套件及適用於 Mastra 的 AG-UI adapter: **npm**: ```bash npm install @mastra/core @ag-ui/mastra @ag-ui/core zod ``` **pnpm**: ```bash pnpm add @mastra/core @ag-ui/mastra @ag-ui/core zod ``` **Yarn**: ```bash yarn add @mastra/core @ag-ui/mastra @ag-ui/core zod ``` **Bun**: ```bash bun add @mastra/core @ag-ui/mastra @ag-ui/core zod ``` `@ag-ui/mastra` 會使用可發出 [AG-UI protocol](https://docs.ag-ui.com) 事件的 `MastraAgent` 包裝 Mastra `Agent`。OpenUI 的 `agUIAdapter()` 會在使用者端使用這些事件。 3. 開啟 `src/app/api/chat/route.ts`。使用 `@mastra/core/tools` 的 `createTool`,定義 Agent 所需的任何 Tool: ```typescript 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 元件庫: ```typescript 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)形式串流: ```typescript 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 `` 聊天介面連接至路由。使用 `fetchLLM()` 建構 `llm` adapter,並將 `streamAdapter` 設為 `agUIAdapter()`,讓 OpenUI 知道要解析 AG-UI 事件。 ```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 (
) } ``` `componentLibrary` prop 控制模型可產生哪些元件。以自己的元件庫取代 `openuiChatLibrary`,即可限制或擴充輸出。 5. 啟動開發伺服器: **npm**: ```bash npm run dev ``` **pnpm**: ```bash pnpm run dev ``` **Yarn**: ```bash yarn dev ``` **Bun**: ```bash bun run dev ``` 開啟 。現在可以透過 OpenUI 聊天介面與 Mastra Agent 聊天;模型串流時,結構化 UI 會逐步轉譯。 ## 使用 AG-UI 串流 OpenUI 使用 [AG-UI protocol](https://docs.ag-ui.com),這是一種不受傳輸方式限制、供 AI Agent 使用的型別化事件串流。`@ag-ui/mastra` 會將 Mastra `Agent` 轉換為此 protocol: - 伺服器呼叫 `agent.run({ messages, threadId, runId, ... })`,並將發出的每個事件序列化為 SSE 訊息。 - 使用者端將 `fetchLLM({ streamAdapter: agUIAdapter() })` 傳入 ``,後者會將 SSE 串流解析為驅動 OpenUI Lang 轉譯的內部事件。 `threadId` 可跨請求將對話串連起來,而 `runId` 則識別單次執行。每個請求都應產生新的 `runId`,並在使用者端持久保存 `threadId`。 ## 元件庫 OpenUI 會根據元件庫產生 UI。元件庫會定義可用的元件、其 props,以及如何指示模型使用這些元件。 ### 內建元件庫 `@openuidev/react-ui` 提供兩個可直接使用的元件庫: - `openuiChatLibrary`:用於聊天介面的元件(卡片、表單、表格、圖表)。 - `openuiDashboardLibrary`:用於儀表板與資料密集介面的元件。 將元件庫傳入 ``,即可讓模型使用其中的元件。 ### 自訂元件庫 若要限制或擴充輸出,請在 `src/library.ts` 中定義自己的元件庫,並匯出元件子集。將該元件庫傳入 ``,並在每次元件庫變更時重新產生 system prompt: **npm**: ```bash npx @openuidev/cli generate src/library.ts --out src/generated/system-prompt.txt ``` **pnpm**: ```bash pnpm dlx @openuidev/cli generate src/library.ts --out src/generated/system-prompt.txt ``` **Yarn**: ```bash yarn dlx @openuidev/cli generate src/library.ts --out src/generated/system-prompt.txt ``` **Bun**: ```bash bun x @openuidev/cli generate src/library.ts --out src/generated/system-prompt.txt ``` API 路由會讀取產生的 prompt,並將其合併至 Agent 的 `instructions`,讓 Agent 確切知道可發出哪些元件。