Membuat AI chatbot yang cuma jawab pertanyaan memang simpel. Tapi bagaimana kalau AI-nya bisa benar-benar melakukan sesuatu — query database, proses pesanan, atau kelola produk?
Artikel ini akan membimbing kamu membangun AI chat assistant dengan tool calling menggunakan Next.js dan Vercel AI SDK. Kita akan bangun chat assistant untuk e-commerce — bisa cek pesanan, proses refund, dan kelola produk langsung dari chat.
Kalau kamu belum familiar dengan konsepnya, baca dulu Apa Itu Tool Calling dan Cara Kerja AI Chatbot.
Apa yang Kita Bangun
Chat assistant untuk e-commerce yang:
- Menggunakan Claude/GPT sebagai brain
- Query data pesanan, produk, dan pelanggan dari database
- Proses refund dengan konfirmasi
- Streaming response tanpa lag
Tech Stack
- Next.js 15+ — App Router
- Vercel AI SDK —
ai+@ai-sdk/anthropic(atau@ai-sdk/openai) - Zod — validasi parameter tool
- React — UI components
Step 1: Install Dependencies
npm install ai @ai-sdk/anthropic zod
Step 2: Define Tools
Tools adalah fungsi yang bisa dipanggil AI. Setiap tool punya nama, deskripsi, parameter, dan fungsi execute.
Buat file lib/tools.ts:
import { tool } from "ai";
import { z } from "zod";
export const tools = {
// Cek pesanan pelanggan
getOrder: tool({
description: "Cari pesanan berdasarkan nomor pesanan atau nama pelanggan",
parameters: z.object({
query: z.string().describe("Nomor pesanan (ORD-xxx) atau nama pelanggan"),
}),
execute: async ({ query }) => {
const order = await db.order.findFirst({
where: {
OR: [
{ orderNumber: query },
{ customerName: { contains: query } },
],
},
include: { items: true, payments: true },
});
return order ?? { error: "Pesanan tidak ditemukan" };
},
}),
// Lihat daftar produk
getProducts: tool({
description: "Lihat daftar produk dan stok",
parameters: z.object({
category: z.string().optional().describe("Filter berdasarkan kategori"),
}),
execute: async ({ category }) => {
const where = category ? { category } : {};
const products = await db.product.findMany({ where, take: 20 });
return { products };
},
}),
// Proses refund (butuh approval!)
processRefund: tool({
description: "Proses refund untuk pesanan tertentu",
parameters: z.object({
orderId: z.string().describe("ID pesanan yang akan di-refund"),
reason: z.string().describe("Alasan refund"),
}),
needsApproval: true, // ← Minta konfirmasi user sebelum eksekusi
execute: async ({ orderId, reason }) => {
const order = await db.order.findUnique({ where: { id: orderId } });
if (!order) return { error: "Pesanan tidak ditemukan" };
await db.refund.create({
data: {
orderId,
amount: order.totalAmount,
reason,
status: "processed",
},
});
return {
success: true,
refundAmount: order.totalAmount,
message: `Refund Rp ${order.totalAmount.toLocaleString("id-ID")} berhasil diproses`,
};
},
}),
// Update stok produk
updateStock: tool({
description: "Update stok produk",
parameters: z.object({
productId: z.string().describe("ID produk"),
quantity: z.number().describe("Stok baru"),
}),
needsApproval: true,
execute: async ({ productId, quantity }) => {
await db.product.update({
where: { id: productId },
data: { stock: quantity },
});
return { success: true, message: `Stok berhasil diupdate ke ${quantity}` };
},
}),
};
Tip: Gunakan needsApproval: true untuk aksi destruktif seperti refund atau update stok. AI SDK akan otomatis menampilkan tombol approve/deny di UI.
Step 3: Buat API Route
API route menerima pesan dari client, memanggil LLM dengan tools, dan mengembalikan streaming response.
Buat file app/api/chat/route.ts:
import { streamText, convertToModelMessages, stepCountIs } from "ai";
import { createAnthropic } from "@ai-sdk/anthropic";
import { tools } from "@/lib/tools";
const anthropic = createAnthropic({
baseURL: process.env.ANTHROPIC_BASE_URL,
authToken: process.env.ANTHROPIC_AUTH_TOKEN,
});
export async function POST(req: Request) {
const { messages } = await req.json();
const modelMessages = await convertToModelMessages(messages);
const result = streamText({
model: anthropic("claude-sonnet-4-20250514"),
system: `Kamu adalah asisten customer service untuk toko online.
Bantu pelanggan dengan pesanan, produk, dan refund.
Gunakan tools untuk mengambil data dari database.
Selalu jelaskan apa yang akan dilakukan sebelum memanggil tool.`,
messages: modelMessages,
tools,
stopWhen: stepCountIs(10),
});
return result.toUIMessageStreamResponse();
}
Catatan penting:
convertToModelMessagesmengkonversi UI messages ke format yang dipahami modelstopWhen: stepCountIs(10)mencegah AI loop tak terbatas memanggil toolstoUIMessageStreamResponse()mengembalikan format yang dipahamiuseChathook
Step 4: Buat Chat UI
Gunakan useChat hook dari Vercel AI SDK untuk handle streaming, tool calls, dan approval.
Buat file components/chat-panel.tsx:
"use client";
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
export function ChatPanel() {
const {
messages,
sendMessage,
status,
addToolApprovalResponse,
} = useChat({
transport: new DefaultChatTransport({
api: "/api/chat",
}),
sendAutomaticallyWhen:
lastAssistantMessageIsCompleteWithApprovalResponses,
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const input = (e.target as HTMLFormElement).elements.namedItem("input") as HTMLInputElement;
if (!input.value.trim()) return;
sendMessage({ text: input.value });
input.value = "";
};
return (
<div className="flex flex-col h-full">
<div className="flex-1 overflow-y-auto p-4">
{messages.map((msg) => (
<Message key={msg.id} message={msg} />
))}
</div>
<form onSubmit={handleSubmit} className="border-t p-4">
<input name="input" className="w-full border rounded-lg p-2" />
</form>
</div>
);
}
Step 5: Render Tool Calls
Setiap message punya parts yang berisi text dan tool calls. Render tool call sebagai card terpisah.
function Message({ message }: { message: UIMessage }) {
const toolParts = message.parts.filter((p) =>
p.type.startsWith("tool-")
);
const textParts = message.parts.filter((p) => p.type === "text");
return (
<div>
{toolParts.map((part, i) => (
<ToolCallCard
key={i}
part={part}
onApprove={(id) =>
addToolApprovalResponse({ id, approved: true })
}
onDeny={(id) =>
addToolApprovalResponse({ id, approved: false })
}
/>
))}
{textParts.map((part, i) => (
<div key={i}>{part.text}</div>
))}
</div>
);
}
function ToolCallCard({ part, onApprove, onDeny }: any) {
const isApproval = part.state === "approval-requested";
const isDone = part.state === "output-available";
const toolLabels: Record<string, string> = {
getOrder: "Cek Pesanan",
getProducts: "Lihat Produk",
processRefund: "Proses Refund",
updateStock: "Update Stok",
};
const label = toolLabels[part.type.replace("tool-", "")] ?? part.type;
return (
<div className="border rounded-lg p-3 my-2">
<div className="text-xs font-mono text-gray-500">{label}</div>
{isApproval && (
<div className="flex gap-2 mt-2">
<button
onClick={() => onApprove(part.approval.id)}
className="px-3 py-1 bg-green-500 text-white rounded"
>
Jalankan
</button>
<button
onClick={() => onDeny(part.approval.id)}
className="px-3 py-1 border rounded"
>
Tolak
</button>
</div>
)}
{isDone && (
<pre className="text-xs mt-2 overflow-auto">
{JSON.stringify(part.output, null, 2)}
</pre>
)}
</div>
);
}
Step 6: Handle Approval Flow
Ketika AI memanggil tool dengan needsApproval: true, tool part akan masuk ke state approval-requested. User harus klik approve atau deny sebelum tool dieksekusi.
User: "Refund pesanan ORD-12345, barang rusak"
↓
AI: [tool-call: processRefund, state: approval-requested]
↓
UI: [Tombol: Jalankan] [Tombol: Tolak]
↓
User klik "Jalankan"
↓
addToolApprovalResponse({ id, approved: true })
↓
AI SDK auto-resume → API route → execute tool → stream result
Penting: sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses wajib di-setting agar stream otomatis resume setelah approval. Tanpa ini, user harus kirim pesan manual untuk melanjutkan.
Step 7: System Prompt
System prompt menentukan bagaimana AI bersikap dan kapan harus menggunakan tools.
const systemPrompt = `Kamu adalah asisten customer service toko online "Toko Berkah".
## Aturan
- Bantu pelanggan dengan pesanan, produk, dan refund
- Gunakan tools untuk mengambil data dari database
- Untuk refund, selalu tampilkan detail pesanan dulu, lalu konfirmasi
- Jawab dengan ramah dalam Bahasa Indonesia
## Tools yang tersedia
- getOrder: Cari pesanan (butuh nomor pesanan atau nama pelanggan)
- getProducts: Lihat daftar produk dan stok
- processRefund: Proses refund (butuh approval user)
- updateStock: Update stok produk (butuh approval user)
`;
Tips Penting
1. Jangan Over-Tool
Jangan buat tool untuk setiap hal kecil. Fokus pada operasi yang sering dilakukan user. Terlalu banyak tools membuat AI bingung memilih yang tepat.
2. Validasi Input dengan Zod
Selalu validasi parameter tool dengan Zod:
parameters: z.object({
query: z.string().min(1).describe("Nomor pesanan atau nama pelanggan"),
reason: z.enum(["rusak", "salah_kirim", "tidak_sesuai"]).describe("Alasan refund"),
})
3. Handle Error di Execute
Tool execute harus handle error dengan graceful:
execute: async ({ orderId }) => {
try {
const order = await db.order.findUnique({ where: { id: orderId } });
if (!order) return { error: "Pesanan tidak ditemukan" };
return { order };
} catch (error) {
return { error: "Gagal mengambil data pesanan" };
}
}
4. Batasi Step Count
Gunakan stopWhen: stepCountIs(N) untuk mencegah AI loop tak terbatas. 5-10 steps biasanya cukup.
5. Streaming untuk UX
Selalu gunakan streaming (streamText + toUIMessageStreamResponse). Response yang muncul kata per kata terasa jauh lebih cepat dari response yang muncul sekaligus.
Contoh Percakapan
User: Pesanan saya ORD-12345 statusnya gimana?
AI: [panggil tool getOrder({ query: "ORD-12345" })]
Saya cek ya pesanan ORD-12345...
Pesanan #ORD-12345
Pelanggan: Budi Santoso
Status: Dikirim (JNE - JN012345678)
Total: Rp 450.000
Item: Widget A (2x), Gadget B (1x)
Ada yang bisa dibantu lagi?
User: Tolong refund ya, barangnya rusak
AI: [panggil tool processRefund({ orderId: "ORD-12345", reason: "rusak" })]
Saya proses refund untuk pesanan ORD-12345 ya.
Detail refund:
- Pesanan: ORD-12345
- Jumlah: Rp 450.000
- Alasan: Barang rusak
[Tombol: Jalankan] [Tombol: Tolak]
Related Articles
- Apa Itu Tool Calling di AI? — Konsep dasar tool calling
- Cara Kerja AI Chatbot — Bagaimana LLM dan chatbot bekerja
- AI Assistant untuk DevOps — Penerapan AI assistant di infrastruktur
- Heli Crew — AI Assistant Helipod — Contoh penerapan di platform deploy