在 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 配置,包括 memorytools/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 路由则从 memory 获取消息历史,以便客户端重新加载时恢复 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