Astro 프로젝트에 Mastra를 통합하세요
이 가이드에서는 Mastra를 사용하여 Tool 호출 AI Agent를 구축한 다음 경로에서 직접 Agent를 가져오고 호출하여 Astro에 연결합니다.
AI SDK UI와 AI Elements를 사용하여 아름다운 대화형 채팅 환경을 만듭니다.
이 가이드에서는 React 및 전체 서버 측 렌더링(SSR)과 함께 Astro를 사용하는 방법을 보여주지만, Astro를 Mastra와 함께 사용하는 방법은 다양합니다. 파일별로 SSR을 활성화하고 Svelte, Vue, Solid, Preact 같은 다른 프레임워크를 사용할 수도 있습니다. Astro로 채팅 인터페이스를 구축하고 Astro에서 기본 방식으로 엔드포인트를 호출할 수 있습니다.
시작하기 전에시작하기 전에에 대한 직접 링크
- 지원되는 Model Provider의 API 키가 필요합니다. 선호하는 Provider가 없다면 OpenAI를 사용하세요.
- Node.js
v22.13.0이상 설치
새 Astro 앱 만들기(선택 사항)새 Astro 앱 만들기(선택 사항)에 대한 직접 링크
이미 Astro 앱이 있다면 다음 단계로 건너뛰세요. Astro 앱은 다음과 같이 설정되어야 합니다:
- SSR 사용(
astro.config.mjs에서output: "server") - React 통합 사용
- Tailwind 설치 온디맨드 렌더링을 지원하기 위해 이 가이드에서는 Node.js 어댑터를 사용하지만, 지원되는 서버 어댑터라면 무엇이든 사용할 수 있습니다.
- npm
- pnpm
- Yarn
- Bun
npm create astro@latest mastra-astro -- --add node --add react --add tailwind --install --skip-houston --template minimal --git
pnpm create astro mastra-astro --add node --add react --add tailwind --install --skip-houston --template minimal --git
yarn create astro mastra-astro --add node --add react --add tailwind --install --skip-houston --template minimal --git
bunx create-astro mastra-astro --add node --add react --add tailwind --install --skip-houston --template minimal --git
이 명령은 mastra-astro라는 프로젝트를 생성하지만 원하는 이름으로 바꿀 수 있습니다.
cd로 프로젝트 디렉터리로 이동한 후 astro.config.mjs 파일을 편집하여 output 설정을 "server"로 지정합니다.
// @ts-check
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'
import react from '@astrojs/react'
import tailwindcss from '@tailwindcss/vite'
// https://astro.build/config
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone',
}),
integrations: [react()],
vite: {
plugins: [tailwindcss()],
},
})
경로를 해석할 수 있도록 tsconfig.json을 편집합니다.
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
},
// ...
},
}
마스트라 초기화마스트라 초기화에 대한 직접 링크
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
그러면 예제 날씨 Agent와 다음 파일을 포함하는 src/mastra 폴더가 생성됩니다.
index.ts- Memory를 포함한 마스트라 구성tools/weather-tool.ts- 특정 위치의 날씨를 가져오는 Toolagents/weather-agent.ts- Tool을 사용하는 Prompt가 있는 기상 요원
다음 단계에서는 Astro 라우트에서 weather-agent.ts를 호출합니다.
AI SDK UI 및 AI 요소 설치AI SDK UI 및 AI 요소 설치에 대한 직접 링크
Mastra 어댑터와 함께 AI SDK UI를 설치합니다.
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/ai-sdk@latest @ai-sdk/react ai
pnpm add @mastra/ai-sdk@latest @ai-sdk/react ai
yarn add @mastra/ai-sdk@latest @ai-sdk/react ai
bun add @mastra/ai-sdk@latest @ai-sdk/react ai
다음으로 AI 요소를 초기화합니다. 메시지가 표시되면 기본 옵션을 선택합니다.
- npm
- pnpm
- Yarn
- Bun
npx ai-elements@latest
pnpm dlx ai-elements@latest
yarn dlx ai-elements@latest
bun x ai-elements@latest
그러면 전체 AI Elements UI 구성 요소 라이브러리가@/components/ai-elements folder.
채팅 경로 만들기채팅 경로 만들기에 대한 직접 링크
만들다src/pages/api/chat.ts:
import type { APIRoute } from 'astro'
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: APIRoute = 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: APIRoute = 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 라우트는 Prompt를 받아 Agent의 응답을 AI SDK 형식으로 스트리밍하고, GET 라우트는 Memory에서 메시지 기록을 가져와 클라이언트가 다시 로드될 때 UI를 hydrate할 수 있게 합니다.
채팅 구성 요소 만들기채팅 구성 요소 만들기에 대한 직접 링크
만들다src/components/chat.tsx:
import '@/styles/global.css'
import { useEffect, useState } from 'react'
import { DefaultChatTransport, type ToolUIPart } from 'ai'
import { useChat } from '@ai-sdk/react'
import {
PromptInput,
PromptInputBody,
PromptInputTextarea,
} from '@/components/ai-elements/prompt-input'
import {
Conversation,
ConversationContent,
ConversationScrollButton,
} from '@/components/ai-elements/conversation'
import { Message, MessageContent, MessageResponse } from '@/components/ai-elements/message'
import { Tool, ToolHeader, ToolContent, ToolInput, ToolOutput } from '@/components/ai-elements/tool'
function Chat() {
const [input, setInput] = useState<string>('')
const { messages, setMessages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
})
useEffect(() => {
const fetchMessages = async () => {
const res = await fetch('/api/chat')
const data = await res.json()
setMessages([...data])
}
fetchMessages()
}, [setMessages])
const handleSubmit = async () => {
if (!input.trim()) return
sendMessage({ text: input })
setInput('')
}
return (
<div className="relative size-full h-screen w-full p-6">
<div className="flex h-full flex-col">
<Conversation className="h-full">
<ConversationContent>
{messages.map(message => (
<div key={message.id}>
{message.parts?.map((part, i) => {
if (part.type === 'text') {
return (
<Message key={`${message.id}-${i}`} from={message.role}>
<MessageContent>
<MessageResponse>{part.text}</MessageResponse>
</MessageContent>
</Message>
)
}
if (part.type?.startsWith('tool-')) {
return (
<Tool key={`${message.id}-${i}`}>
<ToolHeader
type={(part as ToolUIPart).type}
state={(part as ToolUIPart).state || 'output-available'}
className="cursor-pointer"
/>
<ToolContent>
<ToolInput input={(part as ToolUIPart).input || {}} />
<ToolOutput
output={(part as ToolUIPart).output}
errorText={(part as ToolUIPart).errorText}
/>
</ToolContent>
</Tool>
)
}
return null
})}
</div>
))}
<ConversationScrollButton />
</ConversationContent>
</Conversation>
<PromptInput onSubmit={handleSubmit} className="mt-20">
<PromptInputBody>
<PromptInputTextarea
onChange={e => setInput(e.target.value)}
className="md:leading-10"
value={input}
placeholder="Type your message..."
disabled={status !== 'ready'}
/>
</PromptInputBody>
</PromptInput>
</div>
</div>
)
}
export default Chat
이 컴포넌트는 useChat()을 api/chat 엔드포인트에 연결하여 Prompt를 전송하고 응답을 청크 단위로 스트리밍합니다.
<MessageResponse> 컴포넌트로 응답 텍스트를 렌더링하고, <Tool> 컴포넌트로 Tool 호출을 표시합니다.
채팅 구성 요소 렌더링채팅 구성 요소 렌더링에 대한 직접 링크
마지막 단계는 인덱스 페이지에 채팅 구성 요소를 렌더링하는 것입니다. 편집하다src/pages/index.astro:
---
import Chat from '@/components/chat';
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content="{Astro.generator}" />
<title>Astro</title>
</head>
<body>
<Chat client:load />
</body>
</html>
Chat 컴포넌트를 가져와 클라이언트 측에서 실행되도록 client:load 디렉티브와 함께 body에 추가합니다.
Agent 테스트Agent 테스트에 대한 직접 링크
- Astro 앱을 실행해보세요
npm run dev - 다음에서 채팅을 엽니다.http://localhost:4321
- 날씨에 대해 물어보세요. API 키가 올바르게 설정되면 응답을 받게 됩니다.
다음 단계다음 단계에 대한 직접 링크
Astro로 Mastra Agent를 구축한 것을 축하합니다! 🎉
여기에서 자신만의 Tool와 논리를 사용하여 프로젝트를 확장할 수 있습니다.