< BACK विंटेज टेलीफोन स्विचबोर्ड जिसमें उलझे हुए तांबे की तारें हैं, एक अकेली गर्म ऊपरी बत्ती से रोशन, 35mm फिल्म पर शूट किया गया

Vercel AI SDK और Supabase के साथ एक AI Agent बनाएँ

तीन हफ्ते पहले एडिनबर्ग स्थित एक SaaS संस्थापक क्लाइंट ने मुझे कॉल किया और पूछा कि क्या मैं एक "स्मार्ट असिस्टेंट" बना सकता हूँ जो उसके प्रोडक्ट डेटाबेस के बारे में सवालों का जवाब दे सके, पिछली बातचीत को याद रख सके, और सपोर्ट टिकट बनाने जैसी कार्रवाइयाँ ले सके। बजट तंग था, डेडलाइन और भी तंग थी। मैं कुछ समय से Vercel AI SDK में ठीक से खोदखाद करने के लिए सोच रहा था, और यह पल ठीक लगा।

जो इसके बाद हुआ वह तीन दिन की असली निर्माण था: कुछ सुंदर बिट्स, कुछ शर्मनाक गलतियाँ, बहुत सारे सोर्स कोड को पढ़ना। यह पोस्ट वह काम करने वाला ट्यूटोरियल है जो मैं शुरुआत में चाहता था।

---

हम वास्तव में क्या बना रहे हैं

एक AI agent। चैटबॉट नहीं। यह भेद लोगों के सोचने से ज्यादा मायने रखता है।

एक चैटबॉट इनपुट लेता है और आउटपुट देता है। एक एजेंट वह करता है और यह तय करता है कि कौन से टूल्स को कॉल करना है, किस क्रम में करना है, और जब कुछ विफल हो तो खुद को वापस लूप कर सकता है। इसके पास सेशन के पार मेमोरी होती है। यह सिर्फ जवाब देने के बजाय कार्य कर सकता है।

हमारा एजेंट यह करेगा:

  • यूजर्स से प्राकृतिक भाषा के सवाल लेना
  • टूल कॉल्स का उपयोग करके Supabase Postgres डेटाबेस को क्वेरी करना
  • सेशन के पार कनवर्सेशन हिस्ट्री को याद रखना (Supabase में सहेजा हुआ)
  • संरचित, आधारभूत उत्तर देना

स्टैक Next.js (App Router), Vercel AI SDK, Supabase (डेटाबेस और ऑथ दोनों के लिए), और OpenAI का gpt-4o मॉडल है। आप OpenAI को Anthropic या Mistral से बदल सकते हैं, करीब दस लाइनों के बदलाव से; SDK प्रदाता को साफ तरीके से अलग करता है।

---

प्रोजेक्ट सेटअप

एक ताज़ा Next.js प्रोजेक्ट के साथ शुरुआत करें।

`` npx create-next-app@latest ai-agent --typescript --app --tailwind cd ai-agent ``

जिन dependencies की आपको वाकई जरूरत है, उन्हें install करें:

`` npm install ai @ai-sdk/openai @supabase/supabase-js @supabase/ssr zod ``

Zod आपके tool inputs पर schema validation करता है। इसके बिना आप model पर भरोसा कर रहे हैं कि वह sensible arguments pass करे, जो आमतौर पर करता है जब तक कि नहीं करता।

अपने environment variables को .env.local में set करें:

`` OPENAI_API_KEY=sk-... NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... SUPABASE_SERVICE_ROLE_KEY=eyJ... ``

Service role key को server-side ही use करें। Anon key client-side auth flows के लिए ठीक है। इन दोनों को कभी मत मिलाइए। (मैंने किया, 2022 में एक staging environment में, और गलती से हर user को एक client के CRM का admin-level read access दे दिया। शुक्रवार का दोपहर बहुत मज़ेदार नहीं रहा।)

---

Supabase Setup करना

आपको Supabase से दो चीजें चाहिए: आपके actual data के लिए एक table, और conversation memory के लिए एक table।

डेटा टेबल

इस ट्यूटोरियल के लिए, मान लीजिए आप एक प्रोडक्ट कैटलॉग के ऊपर बिल्ड कर रहे हैं। Supabase SQL एडिटर में यह चलाएँ:

``sql create table products ( id uuid primary key default gen_random_uuid(), name text not null, description text, price_gbp numeric(10, 2), category text, in_stock boolean default true, created_at timestamptz default now() ); ``

इसे 20-30 पंक्तियों से भरें। वास्तविक डेटा टेस्टिंग को काफी बेहतर बनाता है, मैं हमेशा इसके लिए Mockaroo का उपयोग करता हूँ क्योंकि यह "string1, string2" जैसी बकवास की जगह डोमेन-विशिष्ट मान जेनरेट करता है।

मेमोरी टेबल

यह वह जगह है जहाँ सेशन के बीच बातचीत का इतिहास रहता है।

```sql create table conversation_messages ( id uuid primary key default gen_random_uuid(), session_id text not null, role text not null check (role in ('user', 'assistant', 'tool')), content jsonb not null, created_at timestamptz default now() );

create index on conversation_messages (session_id, created_at); ```

content पर jsonb टाइप जानबूझकर है। AI SDK संदेश कंटेंट को संरचित ऑब्जेक्ट (टेक्स्ट पार्ट्स, टूल कॉल पार्ट्स, टूल रिज़ल्ट पार्ट्स) के रूप में पास करता है, सादे स्ट्रिंग्स के रूप में नहीं। इसे टेक्स्ट के रूप में स्टोर करना और फिर इसे वापस पार्स करने की कोशिश करना एक सिरदर्द है जिसकी आपको जरूरत नहीं है।

---

कोर एजेंट रूट

app/api/agent/route.ts बनाएँ। यहीं असली लॉजिक रहती है।

```typescript import { openai } from '@ai-sdk/openai'; import { streamText, tool } from 'ai'; import { createClient } from '@supabase/supabase-js'; import { z } from 'zod';

const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY! );

export async function POST(req: Request) { const { messages, sessionId } = await req.json();

// Supabase से history लोड करें const { data: history } = await supabase .from('conversation_messages') .select('role, content') .eq('session_id', sessionId) .order('created_at', { ascending: true }) .limit(40);

const priorMessages = (history ?? []).map((row) => ({ role: row.role, content: row.content, }));

const allMessages = [...priorMessages...messages];

const result = await streamText({ model: openai('gpt-4o'), system: You are a helpful product assistant. You have access to a product database. Always use the queryProducts tool when the user asks about products, pricing, or availability. Never guess at product details. If the tool returns no results, say so plainly., messages: allMessages, tools: { queryProducts: tool({ description: 'Query the product catalogue by category, name, or stock status.', parameters: z.object({ category: z.string().optional().describe('Product category to filter by'), searchTerm: z.string().optional().describe('Name or keyword to search'), inStockOnly: z.boolean().optional().describe('Filter to in-stock products only'), }), execute: async ({ category, searchTerm, inStockOnly }) => { let query = supabase.from('products').select('*');

if (category) query = query.eq('category', category); if (inStockOnly) query = query.eq('in_stock', true); if (searchTerm) query = query.ilike('name', %${searchTerm}%);

const { data, error } = await query.limit(10);

if (error) return { error: error.message }; return { products: data ?? [] }; }, }), }, maxSteps: 5, onFinish: async ({ response }) => { // नए संदेशों को persist करें const newMessages = response.messages.map((msg) => ({ session_id: sessionId, role: msg.role, content: msg.content, }));

await supabase.from('conversation_messages').insert(newMessages); }, });

return result.toDataStreamResponse(); } ```

यहाँ कुछ चीजें हैं जिन्हें समझना जरूरी है।

maxSteps: 5 एजेंट लूप है। SDK टूल्स को कॉल करता रहेगा और परिणाम वापस मॉडल को देता रहेगा, पाँच बार तक, फिर अंतिम उत्तर को मजबूर करता है। इसे बहुत कम सेट करें तो एजेंट बीच में हार मान लेता है। बहुत ज्यादा सेट करें तो एक उलझा हुआ मॉडल जल्दी API खर्च बढ़ा सकता है। पाँच अधिकांश कार्यों के लिए एक समझदारी भरा डिफ़ॉल्ट है।

onFinish कॉलबैक वह जगह है जहाँ आप मेमोरी persist करते हैं। स्ट्रीम पूरी होने से पहले संदेशों को सेव करने की कोशिश न करें, आपके पास अभी तक पूरे टूल कॉल/परिणाम जोड़ी नहीं होंगी।

---

फ्रंटएंड बनाना

इसे सरल रखें। AI SDK से एक useChat हुक लगभग सभी भारी काम कर देता है।

```typescript // app/page.tsx 'use client';

import { useChat } from 'ai/react'; import { useState } from 'react';

export default function AgentPage() { const [sessionId] = useState(() => crypto.randomUUID());

const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ api: '/api/agent', body: { sessionId }, });

return ( <div className="max-w-2xl mx-auto p-6"> <div className="space-y-4 mb-6"> {messages.map((m) => ( <div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}> <span className="inline-block bg-gray-100 rounded px-3 py-2 text-sm"> {typeof m.content === 'string' ? m.content : '[tool interaction]'} </span> </div> ))} </div> <form onSubmit={handleSubmit} className="flex gap-2"> <input value={input} onChange={handleInputChange} className="flex-1 border rounded px-3 py-2 text-sm" placeholder="Ask about products..." disabled={isLoading} /> <button type="submit" disabled={isLoading} className="bg-black text-white px-4 py-2 rounded text-sm"> Send </button> </form> </div> ); } ```

sessionId को page mount होने पर एक बार generate किया जाता है। किसी असल ऐप में, इसे अपने auth user की ID या एक persisted session cookie से जोड़ें। अन्यथा हर page refresh एक fresh memory wipe होता है।

---

Agent को असल में उपयोगी बनाना

सुनिए: एक basic agent जो database को query करता है, वह एक demo है। एक useful agent edge cases को हैंडल करता है। विशेष रूप से:

Tool Errors को Gracefully Handle करें

अगर आपका execute फंक्शन throw करता है, तो SDK इसे पकड़ता है और error स्ट्रिंग को model के पास वापस भेजता है। Model आमतौर पर error को user को समझाने की कोशिश करेगा, जो ठीक है। लेकिन आप अपने execute फंक्शन को errors को data के रूप में return करना चाहते हैं न कि throw करना, model returned error objects को caught exceptions से ज़्यादा अच्छे से handle करता है मेरे अनुभव में।

Grounding और Hallucination

System prompt बहुत मायने रखता है। "कभी product details पर अनुमान न लगाएं" — यह fluff नहीं है, इस instruction के बिना, gpt-4o कभी-कभी product specs को fabricate कर देगा जब query कुछ नहीं return करे। सच में, मैंने इसे guard के बिना test किया, एक ऐसे product के बारे में पूछा जो exist नहीं करता, और model ने एक plausible-sounding price और description invent कर दिया। शानदार और बिल्कुल बेकार।

History Length को Control करना

History से 40 messages load करना (ऊपर .limit(40)) एक reasonable ceiling है। इससे आगे आप पुरानी context पर tokens बर्बाद कर रहे हैं जो शायद ही कभी मदद करते हैं। लंबे समय तक चलने वाले agents के लिए, summarisation को देखें: हर 30 messages पर, model से अब तक की conversation को summarise करने के लिए कहें और उसे एक single "summary" message के रूप में store करें।

Vercel AI SDK docs पर multi-step tool calls पर ज़्यादा गहराई है अगर आप यह जानना चाहते हैं कि tool result parts कैसे structured हैं।

---

Vercel पर Deploy करना

अगर आप App Router use कर रहे हैं, तो यह genuinely simple है।

  1. GitHub को push करें।
  2. Vercel डैशबोर्ड में repo को import करें।
  3. प्रोजेक्ट सेटिंग्स में अपने environment variables जोड़ें।
  4. Deploy करें।

एक बात का ध्यान रखें: streaming responses के लिए एक runtime चाहिए जो Web Streams को support करता हो। Vercel पर App Router यह out of the box handle करता है। अगर आप Pages Router पर हैं और पुराने Express-style API का उपयोग कर रहे हैं, तो आपको चीजों को अलग तरीके से configure करना होगा। सच कहूँ तो, बस App Router use करें।

Supabase connection pooling को भी check करना लायक है। Free tier पर Supabase projects का connection limit लगभग 60 है। अगर आप हर agent request पर multiple queries के साथ database को hit कर रहे हैं, तो आप load के तहत expected से ज्यादा तेजी से उसे saturate कर सकते हैं। Production deployments के लिए Supabase के connection pooler (PgBouncer) को transaction mode में use करें।

---

अगली बार मैं क्या अलग करूँ

जब मैंने Edinburgh client के लिए यह ship किया, तो कुछ चीजें समस्या पैदा कीं:

  • मैंने शुरुआत में message content को Supabase में text के रूप में store किया, jsonb के रूप में नहीं। String से structured tool messages को reconstruct करना genuinely दर्दनाक था और मैंने इस पर एक पूरा afternoon बर्बाद कर दिया।
  • मैंने route पर rate limiting add नहीं की। Client की team ने तुरंत ही long, complex queries के साथ इसे UAT के दौरान hammer करना शुरू कर दिया। Anything को humans के पास hand करने से पहले Upstash Rate Limit जैसी कोई चीज add करें।
  • मैंने शुरुआती टेस्टिंग में maxSteps को 10 पर सेट किया। एक बुरी तरह से लिखी गई क्वेरी पर मॉडल छह टूल कॉल के माध्यम से लूप हुआ इससे पहले कि वह निष्कर्ष निकाले कि प्रोडक्ट मौजूद नहीं है। यानी छह डेटाबेस राउंड-ट्रिप और बहुत सारे टोकन। पाँच लगभग हमेशा काफी होता है।

कोर आर्किटेक्चर, हालांकि, ठीक से काम करता रहा। Vercel AI SDK ने मुझे अपना स्वयं का स्ट्रीमिंग पार्सर और टूल-कॉल स्टेट मशीन लिखने से बचाया, जो अकेले ही डिपेंडेंसी के लायक था।

---

FAQ

Vercel AI SDK के साथ OpenAI के अलावा कौन से मॉडल काम करते हैं?

SDK Anthropic (Claude 3.5 Sonnet और अन्य), Google (Gemini), Mistral, Cohere, और @ai-sdk/anthropic जैसे प्रोवाइडर पैकेज के माध्यम से अन्य को सपोर्ट करता है। प्रोवाइडर को स्वैप करना ज्यादातर मामलों में सिर्फ एक लाइन है, openai('gpt-4o') को anthropic('claude-3-5-sonnet-20241022') से बदलें और स्ट्रीमिंग, टूल-कॉलिंग, और useChat हुक सभी एक जैसे काम करते हैं। कुछ प्रोवाइडर्स के पास टूल-कॉलिंग सपोर्ट को लेकर कुछ खामियाँ हैं, इसलिए कमिट करने से पहले SDK कम्पैटिबिलिटी टेबल देखें।

क्या एजेंट Supabase में वापस लिख सकता है, सिर्फ पढ़ नहीं?

हाँ, और यह वह जगह है जहाँ एजेंट दिलचस्प और खतरनाक दोनों तरीके से काम करते हैं। आप एक createSupportTicket या updateProductStock टूल उसी तरह लिख सकते हैं जैसे आपने queryProducts लिखा। execute फंक्शन सिर्फ select की जगह insert या update चलाता है। मैं किसी भी टेबल पर row-level security policies की दृढ़ता से सिफारिश करूँगा जिसमें एजेंट लिख सकता है, और विनाशकारी ऑपरेशन (delete) को पूरी तरह टूल सेट से बाहर रखें जब तक आपके पास आपके UI में एक कन्फर्मेशन स्टेप न हो।

मैं एथेंटिकेशन को कैसे संभालूँ ताकि यूजर सिर्फ अपना डेटा देख सकें?

सबसे साफ तरीका: सर्विस रोल की के बजाय यूजर के JWT (एक कुकी या Authorization हेडर से) का उपयोग करके route हैंडलर के अंदर Supabase क्लाइंट जेनरेट करें। इस तरह Supabase की row-level security policies अपने आप लागू होती हैं। @supabase/ssr पैकेज के पास Next.js कुकीज़ से सेशन खींचने के लिए हेल्पर्स हैं। यूजर IDs को एजेंट के लिए सादा पैरामीटर के रूप में पास न करें, अगर सिस्टम प्रॉम्प्ट पूरी तरह से सही न हो तो मॉडल को किसी दूसरे यूजर के डेटा को क्वेरी करने के लिए धोखा दिया जा सकता है।

क्या Vercel AI SDK प्रोडक्शन के लिए तैयार है?

मैं इसे कुछ सावधानियों के साथ प्रोडक्शन-तैयार कहूँगा। यह सक्रिय रूप से मेंटेन किया जाता है, Vercel तेजी से फीचर्स रिलीज करता है, और कोर स्ट्रीमिंग और टूल-कॉलिंग प्रिमिटिव्स स्थिर हैं। जो चीजें ज्यादा बदलती हैं वह एक्सपेरिमेंटल फीचर्स हैं (जैसे generateObject जटिल यूनियन स्कीमा के साथ)। अपने वर्जन को पिन करें और अपग्रेड करने से पहले चेंजलॉग पढ़ें। Seahawk के पास अभी इस पर दो लाइव क्लायंट प्रोजेक्ट चल रहे हैं बिना किसी समस्या के, लेकिन हम आक्रामक रूप से वर्जन-पिन करते हैं।

विशेष रूप से Supabase क्यों और Railway पर Postgres या PlanetScale नहीं?

Supabase आपको Postgres के साथ एक टाइप्ड क्लायंट, ऑथ, रीयल-टाइम, स्टोरेज, और एज फंक्शन्स एक छत के नीचे देता है। इस तरह के प्रोजेक्ट के लिए, ऑथ इंटीग्रेशन और क्विक इटरेशन के लिए SQL एडिटर वास्तव में समय बचाता है। यह कहा जा रहा है कि असली एजेंट कोड किसी भी Postgres-कम्पेटिबल डेटाबेस के साथ काम करता है। Supabase क्लायंट को pg या Drizzle ORM से बदलें और कोई स्ट्रक्चरल चीज नहीं बदलती।

---

ईमानदारी से कहूँ तो, यह स्टैक मुझे हैरान कर गया कि शून्य से एक काम करने वाले, मेमोरी-परसिस्टेंट एजेंट तक जाना कितना तेज है। Vercel AI SDK बहुत सारे अदृश्य काम करता है: स्ट्रीमिंग प्रोटोकॉल, टूल कॉल सीरिएलाइजेशन, प्रोवाइडर एब्सट्रैक्शन। Supabase परसिस्टेंस और ऑथ को हैंडल करता है बिना आपसे ज्यादा माँगे। बची हुई जटिलता, जो वह हिस्सा है जिसे कोई एब्सट्रैक्ट नहीं कर सकता, वह है एक सिस्टम प्रॉम्प्ट लिखना जो मॉडल को वास्तव में सही तरीके से व्यवहार करे। वह हिस्सा इटरेशन लेता है। सख्त शुरू करें, धीरे-धीरे आराम दें, और सबसे खराब तरीके से फ्रेज किए गए सवालों के साथ टेस्ट करें जो आप सोच सकते हैं।

संबंधित पढ़ें: 2026 में AI सर्च कीवर्ड रिसर्च: यह क्या है, परंपरागत सर्च क्यों महत्वपूर्ण है, तकनीकी SEO, और AI सर्च।

< BACK