在你的 SvelteKit 專案中整合 Mastra
本指南將帶你使用 Mastra 建立可呼叫 Tool 的 AI Agent,接著直接從路由匯入並呼叫 Agent,將它連接至 SvelteKit。
你將使用 AI SDK UI,建立美觀且可互動的聊天體驗。
開始之前「開始之前」的直接連結
- 你需要受支援模型 Provider 的 API 金鑰。如果沒有偏好,請使用 OpenAI。
- 安裝 Node.js
v22.13.0或更新版本
建立新的 SvelteKit 應用程式(選用)「建立新的 SvelteKit 應用程式(選用)」的直接連結
如果你已經有使用 Tailwind 的 SvelteKit 應用程式,請跳到下一個步驟。
執行下列指令以建立新的 SvelteKit 應用程式:
- npm
- pnpm
- Yarn
- Bun
npx sv create mastra-svelte --template minimal --types ts --add tailwindcss="plugins:forms" --install npm
pnpm dlx sv create mastra-svelte --template minimal --types ts --add tailwindcss="plugins:forms" --install npm
yarn dlx sv create mastra-svelte --template minimal --types ts --add tailwindcss="plugins:forms" --install npm
bun x sv create mastra-svelte --template minimal --types ts --add tailwindcss="plugins:forms" --install npm
這會建立名為 mastra-svelte 的專案,但你可以替換成任何想要的名稱。Tailwind 是為了稍後設定樣式而加入。
初始化 Mastra「初始化 Mastra」的直接連結
進入 SvelteKit 專案:
cd mastra-svelte
執行 mastra init。出現提示時,選擇 Provider(例如 OpenAI)並輸入金鑰:
- npm
- pnpm
- Yarn
- Bun
npx mastra@latest init
pnpm dlx mastra@latest init
yarn dlx mastra@latest init
bun x mastra@latest init
這會建立 src/mastra 資料夾,其中包含範例天氣 Agent 和下列檔案:
index.ts- Mastra 設定,包含記憶功能tools/weather-tool.ts- 擷取指定位置天氣資訊的 Toolagents/weather-agent.ts- 使用該 Tool 的天氣 Agent,並包含提示詞
在接下來的步驟中,你會從 SvelteKit 路由呼叫 weather-agent.ts。
安裝 AI SDK UI「安裝 AI SDK UI」的直接連結
安裝 AI SDK UI 與 Mastra adapter:
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/ai-sdk@latest @ai-sdk/svelte ai
pnpm add @mastra/ai-sdk@latest @ai-sdk/svelte ai
yarn add @mastra/ai-sdk@latest @ai-sdk/svelte ai
bun add @mastra/ai-sdk@latest @ai-sdk/svelte ai
建立聊天路由「建立聊天路由」的直接連結
建立 src/routes/api/chat/+server.ts:
import type { RequestHandler } from './$types'
import { handleChatStream } from '@mastra/ai-sdk'
import { toAISdkV5Messages } from '@mastra/ai-sdk/ui'
import { createUIMessageStreamResponse } from 'ai'
import { mastra } from '../../../mastra'
const THREAD_ID = 'example-user-id'
const RESOURCE_ID = 'weather-chat'
export const POST: RequestHandler = async ({ request }) => {
const params = await request.json()
const stream = await handleChatStream({
mastra,
agentId: 'weather-agent',
params: {
...params,
memory: {
...params.memory,
thread: THREAD_ID,
resource: RESOURCE_ID,
},
},
})
return createUIMessageStreamResponse({ stream })
}
export const GET: RequestHandler = async () => {
const memory = await mastra.getAgentById('weather-agent').getMemory()
let response = null
try {
response = await memory?.recall({
threadId: THREAD_ID,
resourceId: RESOURCE_ID,
})
} catch {
console.log('No previous messages found.')
}
const uiMessages = toAISdkV5Messages(response?.messages || [])
return Response.json(uiMessages)
}
POST 路由會接收提示詞,並以 AI SDK 格式串流回傳 Agent 的回應;GET 路由則從記憶中擷取訊息記錄,讓使用者端重新載入時能還原 UI 狀態。
若要呼叫 GET handler,你需要建立 src/routes/+page.ts 檔案。它的 load() 函式會與 +page.svelte 一起執行。
import type { UIDataTypes, UIMessage, UITools } from 'ai'
import type { PageLoad } from './$types'
export const load: PageLoad = async ({ fetch }) => {
const response = await fetch('/api/chat')
const initialMessages = (await response.json()) as UIMessage<unknown, UIDataTypes, UITools>[]
return { initialMessages }
}
加入聊天 UI「加入聊天 UI」的直接連結
將 src/routes/+page.svelte 替換為下列內容。
<script lang="ts">
import { Chat } from '@ai-sdk/svelte';
import { DefaultChatTransport, type ToolUIPart } from 'ai';
let input = $state('');
const { data } = $props();
let messages = $derived(data.initialMessages)
const chat = new Chat({
transport: new DefaultChatTransport({
api: '/api/chat'
}),
get messages() {
return messages;
}
});
function handleSubmit(event: SubmitEvent) {
event.preventDefault();
chat.sendMessage({ text: input });
input = '';
}
const STATE_TO_LABEL_MAP: Record<ToolUIPart["state"], string> = {
"input-streaming": "Pending",
"input-available": "Running",
// @ts-expect-error - Only available in AI SDK v6
"approval-requested": "Awaiting Approval",
"approval-responded": "Responded",
"output-available": "Completed",
"output-error": "Error",
"output-denied": "Denied",
};
</script>
<main class="max-w-3xl mx-auto p-6 size-full h-screen">
<div class="flex flex-col h-full">
<div class="flex-1 min-h-0 overflow-y-auto" data-name="conversation">
<div data-name="conversation-content" class="flex flex-col gap-8">
{#each chat.messages as message, messageIndex (messageIndex)}
<div>
{#each message.parts as part, partIndex (partIndex)}
{#if part.type === 'text'}
<div data-name="message" class={[message.role === 'user' && 'ml-auto justify-end', 'group flex w-full max-w-[95%] flex-col gap-2', message.role === 'user' ? 'is-user' : 'is-assistant']}>
<div data-name="message-content" class={["is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm",
"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-blue-100 group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
"group-[.is-assistant]:text-foreground"]}>
<div data-name="message-response" class="size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0">
{part.text}
</div>
</div>
</div>
{:else if part.type.startsWith('tool-')}
<div data-name="tool" class="not-prose mb-6 w-full rounded-lg border border-gray-300 shadow">
<details data-name="tool-header" class="w-full p-3 hover:cursor-pointer">
<summary class="font-medium text-sm">{(part as ToolUIPart).type.split("-").slice(1).join("-")} - {STATE_TO_LABEL_MAP[(part as ToolUIPart).state ?? 'output-available']}</summary>
<div data-name="tool-content" class="">
<div data-name="tool-input" class="space-y-2 overflow-hidden py-4">
<div class="font-medium text-muted-foreground text-xs uppercase tracking-wide">Parameters</div>
<pre class="w-full overflow-x-auto rounded-md border border-gray-300 bg-gray-50 p-3 text-sm"><code>{JSON.stringify((part as ToolUIPart).input, null, 2)}</code></pre>
</div>
<div data-name="tool-output" class="space-y-2 overflow-hidden py-4">
<div class="font-medium text-muted-foreground text-xs uppercase tracking-wide">{(part as ToolUIPart).errorText ? 'Error' : 'Result'}</div>
<pre class="w-full overflow-x-auto rounded-md border border-gray-300 bg-gray-50 p-3 text-sm"><code>{JSON.stringify((part as ToolUIPart).output, null, 2)}</code></pre>
{#if (part as ToolUIPart).errorText}
<div data-name="tool-error" class="text-red-600">
{(part as ToolUIPart).errorText}
</div>
{/if}
</div>
</div>
</details>
</div>
{/if}
{/each}
</div>
{/each}
</div>
</div>
<form class="w-full grid grid-cols-[1fr_auto] gap-6 shrink-0 pt-4" onsubmit={handleSubmit} data-name="prompt-input">
<input name="chat-input" class="rounded-lg border border-gray-300 shadow h-10" placeholder="City name" bind:value={input} />
<button class="bg-blue-600 text-white shadow-lg border border-blue-400 px-4 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]" type="submit">Send</button>
</form>
</div>
</main>
此頁面會將 Chat 連接至 api/chat 端點,將提示詞傳送至該端點,並以分段串流方式回傳回應。
它會使用自訂訊息和 Tool 元件算繪回應文字。
測試你的 Agent「測試你的 Agent」的直接連結
- 使用
npm run dev執行 SvelteKit 應用程式 - 在 http://localhost:5173 開啟聊天介面
- 試著詢問天氣。如果 API 金鑰設定正確,你會收到回應
後續步驟「後續步驟」的直接連結
恭喜你使用 SvelteKit 建立了 Mastra Agent!🎉
接下來,你可以使用自己的 Tool 和邏輯擴充專案:
準備好後,進一步了解 Mastra 如何與 AI SDK UI 和 SvelteKit 整合,以及如何將 Agent 部署至任何地方:
- 將 Mastra 與 AI SDK UI 整合
- 將 Agent 部署至任何地方
- 試用非官方 Svelte AI Elements