WhatsApp 聊天機械人
本指南示範如何使用 Mastra Agent 和 Workflow 建立 WhatsApp 聊天機械人。機械人透過 webhook 接收傳入的 WhatsApp 訊息,使用 AI Agent 處理訊息,將回應拆分成自然的文字訊息,再透過 WhatsApp Business API 傳送給用戶。
先決條件先決條件 的直接連結
此範例需要設定 WhatsApp Business API,並使用 anthropic 模型。請將以下環境變數加入 .env 檔案:
.env
ANTHROPIC_API_KEY=<your-anthropic-api-key>
WHATSAPP_VERIFY_TOKEN=<your-verify-token>
WHATSAPP_ACCESS_TOKEN=<your-whatsapp-access-token>
WHATSAPP_BUSINESS_PHONE_NUMBER_ID=<your-phone-number-id>
WHATSAPP_API_VERSION=v22.0
建立 WhatsApp 用戶端建立 WhatsApp 用戶端 的直接連結
這個用戶端負責透過 WhatsApp Business API 向用戶傳送訊息。
src/whatsapp-client.ts
// Simple WhatsApp Business API client for sending messages
interface SendMessageParams {
to: string
message: string
}
export async function sendWhatsAppMessage({ to, message }: SendMessageParams) {
// Get environment variables for WhatsApp API
const apiVersion = process.env.WHATSAPP_API_VERSION || 'v22.0'
const phoneNumberId = process.env.WHATSAPP_BUSINESS_PHONE_NUMBER_ID
const accessToken = process.env.WHATSAPP_ACCESS_TOKEN
// Check if required environment variables are set
if (!phoneNumberId || !accessToken) {
return false
}
// WhatsApp Business API endpoint
const url = `https://graph.facebook.com/${apiVersion}/${phoneNumberId}/messages`
// Message payload following WhatsApp API format
const payload = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: to,
type: 'text',
text: {
body: message,
},
}
try {
// Send message via WhatsApp Business API
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify(payload),
})
const result = await response.json()
if (response.ok) {
console.log(`✅ WhatsApp message sent to ${to}: "${message}"`)
return true
} else {
console.error('❌ Failed to send WhatsApp message:', result)
return false
}
} catch (error) {
console.error('❌ Error sending WhatsApp message:', error)
return false
}
}
建立聊天 Agent建立聊天 Agent 的直接連結
這個 Agent 以親切、自然的個性處理主要對話邏輯。
src/mastra/agents/chat-agent.ts
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
export const chatAgent = new Agent({
id: 'chat-agent',
name: 'Chat Agent',
instructions: `
You are a helpful, friendly, and knowledgeable AI assistant that loves to chat with users via WhatsApp.
Your personality:
- Warm, approachable, and conversational
- Enthusiastic about helping with any topic
- Use a casual, friendly tone like you're chatting with a friend
- Be concise but informative
- Show genuine interest in the user's questions
Your capabilities:
- Answer questions on a wide variety of topics
- Provide helpful advice and suggestions
- Engage in casual conversation
- Help with problem-solving and creative tasks
- Explain complex topics in simple terms
Guidelines:
- Keep responses informative but not overwhelming
- Ask follow-up questions when appropriate
- Be encouraging and positive
- If you don't know something, admit it honestly
- Adapt your communication style to match the user's tone
- Remember this is WhatsApp, so keep it conversational and natural
Always aim to be helpful while maintaining a friendly, approachable conversation style.
`,
model: 'openai/gpt-5.6-sol',
memory: new Memory({
storage: new LibSQLStore({
id: 'agent-storage',
url: 'file:../mastra.db',
}),
}),
})
建立文字訊息 Agent建立文字訊息 Agent 的直接連結
這個 Agent 將較長的回應轉換成自然、簡短而適合 WhatsApp 的文字訊息。
src/mastra/agents/text-message-agent.ts
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
export const textMessageAgent = new Agent({
id: 'text-message-agent',
name: 'Text Message Agent',
instructions: `
You are a text message converter that takes formal or lengthy text and breaks it down into natural, casual text messages.
Your job is to:
- Convert any input text into 5-8 short, casual text messages
- Each message should be 1-2 sentences maximum
- Use natural, friendly texting language (contractions, casual tone)
- Maintain all the important information from the original text
- Make it feel like you're texting a friend
- Use appropriate emojis sparingly to add personality
- Keep the conversational flow logical and easy to follow
Think of it like you're explaining something exciting to a friend via text - break it into bite-sized, engaging messages that don't overwhelm them with a long paragraph.
Always return exactly 5-8 messages in the messages array.
`,
model: 'openai/gpt-5.6-sol',
memory: new Memory({
storage: new LibSQLStore({
id: 'agent-storage',
url: 'file:../mastra.db',
}),
}),
})
建立聊天 Workflow建立聊天 Workflow 的直接連結
這個 Workflow 統籌整個聊天流程:生成回應、將回應拆分成訊息,再透過 WhatsApp 傳送。
src/mastra/workflows/chat-workflow.ts
import { createStep, createWorkflow } from '@mastra/core/workflows'
import { z } from 'zod'
import { sendWhatsAppMessage } from '../../whatsapp-client'
const respondToMessage = createStep({
id: 'respond-to-message',
description: 'Generate response to user message',
inputSchema: z.object({ userMessage: z.string() }),
outputSchema: z.object({ response: z.string() }),
execute: async ({ inputData, mastra }) => {
const agent = mastra?.getAgent('chatAgent')
if (!agent) {
throw new Error('Chat agent not found')
}
const response = await agent.generate([{ role: 'user', content: inputData.userMessage }])
return { response: response.text }
},
})
const breakIntoMessages = createStep({
id: 'break-into-messages',
description: 'Breaks response into text messages',
inputSchema: z.object({ prompt: z.string() }),
outputSchema: z.object({ messages: z.array(z.string()) }),
execute: async ({ inputData, mastra }) => {
const agent = mastra?.getAgent('textMessageAgent')
if (!agent) {
throw new Error('Text Message agent not found')
}
const response = await agent.generate([{ role: 'user', content: inputData.prompt }], {
structuredOutput: {
schema: z.object({
messages: z.array(z.string()),
}),
},
})
if (!response.object) throw new Error('Error generating messages')
return response.object
},
})
const sendMessages = createStep({
id: 'send-messages',
description: 'Sends text messages via WhatsApp',
inputSchema: z.object({
messages: z.array(z.string()),
userPhone: z.string(),
}),
outputSchema: z.object({ sentCount: z.number() }),
execute: async ({ inputData }) => {
const { messages, userPhone } = inputData
console.log(`\n🔥 Sending ${messages.length} WhatsApp messages to ${userPhone}...`)
let sentCount = 0
// Send each message with a small delay for natural flow
for (let i = 0; i < messages.length; i++) {
const success = await sendWhatsAppMessage({
to: userPhone,
message: messages[i],
})
if (success) {
sentCount++
}
// Add delay between messages for natural texting rhythm
if (i < messages.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
console.log(`\n✅ Successfully sent ${sentCount}/${messages.length} WhatsApp messages\n`)
return { sentCount }
},
})
export const chatWorkflow = createWorkflow({
id: 'chat-workflow',
inputSchema: z.object({ userMessage: z.string() }),
outputSchema: z.object({ sentCount: z.number() }),
})
.then(respondToMessage)
.map(async ({ inputData }) => ({
prompt: `Break this AI response into 3-8 casual, friendly text messages that feel natural for WhatsApp conversation:\n\n${inputData.response}`,
}))
.then(breakIntoMessages)
.map(async ({ inputData, getInitData }) => {
// Parse the original stringified input to get user phone
const initData = getInitData<typeof chatWorkflow>()
const webhookData = JSON.parse(initData.userMessage)
const userPhone = webhookData.entry?.[0]?.changes?.[0]?.value?.messages?.[0]?.from || 'unknown'
return {
messages: inputData.messages,
userPhone,
}
})
.then(sendMessages)
chatWorkflow.commit()
設定 Mastra 配置設定 Mastra 配置 的直接連結
使用 Agent、Workflow 和 WhatsApp webhook 端點配置 Mastra 執行個體。
src/mastra/index.ts
import { Mastra } from '@mastra/core'
import { registerApiRoute } from '@mastra/core/server'
import { PinoLogger } from '@mastra/loggers'
import { LibSQLStore } from '@mastra/libsql'
import { chatWorkflow } from './workflows/chat-workflow'
import { textMessageAgent } from './agents/text-message-agent'
import { chatAgent } from './agents/chat-agent'
export const mastra = new Mastra({
workflows: { chatWorkflow },
agents: { textMessageAgent, chatAgent },
storage: new LibSQLStore({
id: 'agent-storage',
url: ':memory:',
}),
logger: new PinoLogger({
name: 'Mastra',
level: 'info',
}),
server: {
apiRoutes: [
registerApiRoute('/whatsapp', {
method: 'GET',
handler: async c => {
const verifyToken = process.env.WHATSAPP_VERIFY_TOKEN
const {
'hub.mode': mode,
'hub.challenge': challenge,
'hub.verify_token': token,
} = c.req.query()
if (mode === 'subscribe' && token === verifyToken) {
return c.text(challenge, 200)
} else {
return c.status(403)
}
},
}),
registerApiRoute('/whatsapp', {
method: 'POST',
handler: async c => {
const mastra = c.get('mastra')
const chatWorkflow = mastra.getWorkflow('chatWorkflow')
const body = await c.req.json()
const workflowRun = await chatWorkflow.createRun()
const runResult = await workflowRun.start({
inputData: { userMessage: JSON.stringify(body) },
})
return c.json(runResult)
},
}),
],
},
})
測試聊天機械人測試聊天機械人 的直接連結
你可以模擬 WhatsApp webhook 承載資料,在本機測試聊天機械人。
src/test-whatsapp-bot.ts
import 'dotenv/config'
import { mastra } from './mastra'
// Simulate a WhatsApp webhook payload
const mockWebhookData = {
entry: [
{
changes: [
{
value: {
messages: [
{
from: '1234567890', // Test phone number
text: {
body: 'Hello! How are you today?',
},
},
],
},
},
],
},
],
}
const workflow = mastra.getWorkflow('chatWorkflow')
const workflowRun = await workflow.createRun()
const result = await workflowRun.start({
inputData: { userMessage: JSON.stringify(mockWebhookData) },
})
console.log('Workflow completed:', result)
輸出範例輸出範例 的直接連結
當用戶向 WhatsApp 機械人傳送「你好!你今日好嗎?」時,機械人可能會以多則訊息回覆,例如:
Hey there! 👋 I'm doing great, thanks for asking!
How's your day going so far?
I'm here and ready to chat about whatever's on your mind
Whether you need help with something or just want to talk, I'm all ears! 😊
What's new with you?
機械人透過記憶保留對話脈絡,並提供在 WhatsApp 訊息對話中感覺自然的回應。