跳到主要内容

在 SvelteKit 项目中集成 Mastra

本指南将介绍如何使用 Mastra 构建一个可调用 Tool 的 AI Agent,然后通过路由直接导入并调用该 Agent,将其连接到 SvelteKit。

你将使用 AI SDK UI 创建美观的交互式聊天体验。

开始之前
开始之前的直接链接

  • 你需要受支持的模型 Provider所提供的 API 密钥。如果没有偏好,请使用 OpenAI
  • 安装 Node.js v22.13.0 或更高版本

创建新的 SvelteKit 应用(可选)
创建新的 SvelteKit 应用(可选)的直接链接

如果已有使用 Tailwind 的 SvelteKit 应用,请跳到下一步。

运行以下命令以创建新的 SvelteKit 应用

npx 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)并输入密钥:

npx mastra@latest init

这会创建一个 src/mastra 文件夹,其中包含示例天气 Agent 和以下文件:

  • index.ts - Mastra 配置,包括 memory
  • tools/weather-tool.ts - 获取指定位置天气的 Tool
  • agents/weather-agent.ts- 使用该 Tool 的天气 Agent 及其提示词

接下来,你会从 SvelteKit 路由调用 weather-agent.ts

安装 AI SDK UI
安装 AI SDK UI的直接链接

安装 AI SDK UI 和 Mastra adapter:

npm install @mastra/ai-sdk@latest @ai-sdk/svelte ai

创建聊天路由
创建聊天路由的直接链接

创建 src/routes/api/chat/+server.ts

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 一起运行。

src/routes/+page.ts
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 替换为以下内容。

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的直接链接

  1. 使用 npm run dev 运行 SvelteKit 应用
  2. 打开聊天页面:http://localhost:5173
  3. 尝试询问天气。如果 API 密钥配置正确,你会收到响应

后续步骤
后续步骤的直接链接

恭喜你使用 SvelteKit 构建了 Mastra Agent!🎉

接下来,你可以使用自己的 Tool 和逻辑扩展项目:

  • 进一步了解 Agent
  • 为 Agent 添加自己的 Tool
  • 为 Agent 添加类似人类的 memory

准备就绪后,可进一步了解 Mastra 与 AI SDK UI 和 SvelteKit 的集成方式,以及如何将 Agent 部署到任意位置: