Vercel AI SDK useChat Hook ile React'te Streaming AI Yanıtları: Kapsamlı Rehber
Yapay zeka destekli uygulamalar artık her yerde. ChatGPT benzeri bir deneyimi kendi uygulamanıza entegre etmek istiyorsanız, Vercel AI SDK tam da bu iş için tasarlandı. Bu yazıda, SDK'nın en güçlü hook'larından biri olan useChat ile React'te nasıl streaming AI yanıtları oluşturabileceğinizi adım adım göstereceğim.
Streaming Nedir ve Neden Önemlidir?
Klasik bir API çağrısında, sunucu tüm yanıtı hazırlayana kadar beklersiniz. Bir LLM'den gelen yanıt birkaç saniye sürebilir — bu süre boyunca kullanıcı boş bir ekrana bakar. Streaming ise yanıtın parça parça (token token) istemciye gönderilmesi anlamına gelir.
Streaming yaklaşımının avantajları:
- Düşük algılanan gecikme: Kullanıcı, ilk token geldiği anda yanıtı görmeye başlar
- Daha iyi UX: ChatGPT'deki "yazıyor" efektini düşünün — bu streaming'dir
- Erken iptal imkanı: Kullanıcı, yanıt henüz tamamlanmadan isteği durdurabilir
- Bellek verimliliği: Büyük yanıtlar tampon bellekte tutulmadan parça parça işlenir
Vercel AI SDK Nedir?
Vercel AI SDK, yapay zeka uygulamaları geliştirmek için Vercel tarafından oluşturulan açık kaynaklı bir kütüphanedir. İki ana katmandan oluşur:
- AI SDK Core: Model-agnostik bir katman (OpenAI, Anthropic, Google Gemini, Mistral gibi sağlayıcılarla çalışır)
- AI SDK UI: React, Next.js, Svelte ve Vue için hazır UI hook'ları
useChat hook'u, AI SDK UI katmanının parçasıdır ve chat tabanlı arayüzler oluşturmayı inanılmaz derecede kolaylaştırır.
Proje Kurulumu
Öncelikle bir Next.js projesi oluşturalım ve gerekli bağımlılıkları yükleyelim:
npx create-next-app@latest ai-chat-app --typescript --tailwind --app
cd ai-chat-appŞimdi Vercel AI SDK ve bir model sağlayıcı yükleyelim:
npm install ai @ai-sdk/openai.env.local dosyanıza OpenAI API anahtarınızı ekleyin:
OPENAI_API_KEY=sk-your-api-key-hereBackend: API Route Oluşturma
useChat hook'u varsayılan olarak /api/chat endpoint'ine POST isteği gönderir. App Router kullanarak bu route'u oluşturalım:
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o-mini'),
system: 'Sen yardımsever bir Türkçe asistansın. Kullanıcının sorularına açık ve net yanıtlar veriyorsun.',
messages,
});
return result.toDataStreamResponse();
}Bu kodu parçalayalım:
streamText: AI SDK Core'un streaming text generation fonksiyonuopenai('gpt-4o-mini'): Kullanılacak model (maliyet-etkin bir seçim)system: Sistem prompt'u — modelin davranışını belirlermessages: Konuşma geçmişi (useChat tarafından otomatik yönetilir)toDataStreamResponse: Yanıtı Vercel AI SDK'nın data stream protokolüne dönüştürür
Frontend: useChat Hook'u ile Chat Arayüzü
Şimdi asıl sihrin gerçekleştiği kısma geçelim. useChat hook'u ile basit ama güçlü bir chat arayüzü oluşturalım:
// app/page.tsx
'use client';
import { useChat } from 'ai/react';
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading, stop, error } = useChat();
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">🤖 AI Chat Asistan</h1>
{/* Mesaj Listesi */}
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.map((message) => (
<div
key={message.id}
className={`p-4 rounded-lg ${
message.role === 'user'
? 'bg-blue-100 ml-12'
: 'bg-gray-100 mr-12'
}`}
>
<p className="text-sm font-semibold mb-1">
{message.role === 'user' ? '👤 Sen' : '🤖 Asistan'}
</p>
<p className="whitespace-pre-wrap">{message.content}</p>
</div>
))}
</div>
{/* Hata Durumu */}
{error && (
<div className="bg-red-100 text-red-700 p-3 rounded-lg mb-4">
Bir hata oluştu: {error.message}
</div>
)}
{/* Input Formu */}
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Mesajınızı yazın..."
className="flex-1 border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
{isLoading ? (
<button
type="button"
onClick={stop}
className="bg-red-500 text-white px-6 py-2 rounded-lg hover:bg-red-600"
>
Durdur
</button>
) : (
<button
type="submit"
className="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600"
>
Gönder
</button>
)}
</form>
</div>
);
}Bu kadar! Evet, gerçekten bu kadar. useChat hook'u arka planda şunları sizin yerinize hallediyor:
- Mesaj geçmişini yönetme
- API'ye POST isteği gönderme
- Streaming yanıtı gerçek zamanlı parse etme
- Yükleme durumunu takip etme
- Hata yönetimi
useChat Hook'unun Döndürdüğü Değerler
useChat hook'u oldukça zengin bir API sunar. En önemli değerleri inceleyelim:
const {
messages, // Tüm mesajlar dizisi (Message[])
input, // Kontrollü input değeri
handleInputChange, // Input onChange handler
handleSubmit, // Form onSubmit handler
isLoading, // Yanıt beklenirken true
stop, // Streaming yanıtı durdurma fonksiyonu
error, // Hata objesi (varsa)
reload, // Son yanıtı yeniden üretme
append, // Programatik mesaj ekleme
setMessages, // Mesaj dizisini manuel ayarlama
setInput, // Input değerini manuel ayarlama
} = useChat();Gelişmiş Konfigürasyon Seçenekleri
useChat hook'u birçok yapılandırma seçeneği sunar:
const { messages, input, handleSubmit, handleInputChange } = useChat({
// Farklı bir API endpoint'i kullanma
api: '/api/custom-chat',
// Başlangıç mesajları
initialMessages: [
{
id: 'welcome',
role: 'assistant',
content: 'Merhaba! Size nasıl yardımcı olabilirim?',
},
],
// Her API isteğiyle birlikte gönderilecek ek veri
body: {
model: 'gpt-4o',
temperature: 0.7,
},
// HTTP başlıkları
headers: {
'X-Custom-Header': 'value',
},
// Yanıt tamamlandığında çalışan callback
onFinish: (message) => {
console.log('Yanıt tamamlandı:', message.content);
},
// Hata durumunda çalışan callback
onError: (error) => {
console.error('Chat hatası:', error);
},
// Yanıtın her yeni parçasında çalışan callback
onResponse: (response) => {
console.log('HTTP yanıtı alındı:', response.status);
},
});Pratik Örnek: Ek Body Parametreleri ile Dinamik Chat
Gerçek dünya uygulamalarında, kullanıcının model veya sıcaklık seçmesine izin vermek isteyebilirsiniz:
'use client';
import { useChat } from 'ai/react';
import { useState } from 'react';
export default function AdvancedChat() {
const [model, setModel] = useState('gpt-4o-mini');
const [temperature, setTemperature] = useState(0.7);
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
body: {
model,
temperature,
},
});
return (
<div className="max-w-2xl mx-auto p-4">
{/* Ayarlar Paneli */}
<div className="flex gap-4 mb-6 p-4 bg-gray-50 rounded-lg">
<div>
<label className="block text-sm font-medium mb-1">Model</label>
<select
value={model}
onChange={(e) => setModel(e.target.value)}
className="border rounded px-3 py-1"
>
<option value="gpt-4o-mini">GPT-4o Mini</option>
<option value="gpt-4o">GPT-4o</option>
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1">
Sıcaklık: {temperature}
</label>
<input
type="range"
min="0"
max="1"
step="0.1"
value={temperature}
onChange={(e) => setTemperature(parseFloat(e.target.value))}
/>
</div>
</div>
{/* Mesajlar */}
<div className="space-y-3 mb-4">
{messages.map((m) => (
<div key={m.id} className={`p-3 rounded ${
m.role === 'user' ? 'bg-blue-50' : 'bg-green-50'
}`}>
<strong>{m.role === 'user' ? 'Sen' : 'AI'}:</strong>
<p className="whitespace-pre-wrap mt-1">{m.content}</p>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Mesajınızı yazın..."
className="flex-1 border rounded-lg px-4 py-2"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading}
className="bg-blue-500 text-white px-4 py-2 rounded-lg disabled:opacity-50"
>
{isLoading ? '...' : 'Gönder'}
</button>
</form>
</div>
);
}Backend tarafında bu ek parametreleri şöyle alabilirsiniz:
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export async function POST(req: Request) {
const { messages, model, temperature } = await req.json();
const result = streamText({
model: openai(model || 'gpt-4o-mini'),
messages,
temperature: temperature ?? 0.7,
});
return result.toDataStreamResponse();
}Programatik Mesaj Gönderme: append Fonksiyonu
Bazen kullanıcı input'u yerine programatik olarak mesaj göndermek isteyebilirsiniz. Örneğin, hazır soru butonları:
'use client';
import { useChat } from 'ai/react';
const SUGGESTED_QUESTIONS = [
'React 19 ile gelen yenilikler neler?',
'useEffect yerine ne kullanmalıyım?',
'Server Components nasıl çalışır?',
];
export default function ChatWithSuggestions() {
const { messages, input, handleInputChange, handleSubmit, append, isLoading } = useChat();
const handleSuggestionClick = (question: string) => {
append({
role: 'user',
content: question,
});
};
return (
<div className="max-w-2xl mx-auto p-4">
{messages.length === 0 && (
<div className="mb-6">
<p className="text-gray-600 mb-3">Önerilen sorular:</p>
<div className="flex flex-wrap gap-2">
{SUGGESTED_QUESTIONS.map((q) => (
<button
key={q}
onClick={() => handleSuggestionClick(q)}
disabled={isLoading}
className="bg-white border border-gray-200 rounded-full px-4 py-2 text-sm hover:bg-gray-50 transition-colors"
>
{q}
</button>
))}
</div>
</div>
)}
{/* Mesajlar ve form... */}
<div className="space-y-3 mb-4">
{messages.map((m) => (
<div key={m.id} className={`p-3 rounded ${
m.role === 'user' ? 'bg-blue-50 ml-8' : 'bg-gray-50 mr-8'
}`}>
<p className="whitespace-pre-wrap">{m.content}</p>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Mesajınızı yazın..."
className="flex-1 border rounded-lg px-4 py-2"
/>
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded-lg">
Gönder
</button>
</form>
</div>
);
}Markdown Rendering ile Zengin İçerik
AI yanıtları genellikle Markdown formatında gelir. react-markdown ile bunu düzgün render edebilirsiniz:
npm install react-markdownimport ReactMarkdown from 'react-markdown';
// Mesaj render kısmında:
{messages.map((m) => (
<div key={m.id}>
{m.role === 'assistant' ? (
<ReactMarkdown className="prose prose-sm max-w-none">
{m.content}
</ReactMarkdown>
) : (
<p>{m.content}</p>
)}
</div>
))}Otomatik Scroll Yönetimi
Chat arayüzlerinde mesajlar eklendiğinde otomatik olarak en alta scroll etmek önemlidir:
import { useEffect, useRef } from 'react';
import { useChat } from 'ai/react';
export default function ChatWithAutoScroll() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((m) => (
<div key={m.id} className="p-3 rounded bg-gray-50">
{m.content}
</div>
))}
<div ref={messagesEndRef} />
</div>
<form onSubmit={handleSubmit} className="p-4 border-t flex gap-2">
<input
value={input}
onChange={handleInputChange}
className="flex-1 border rounded px-3 py-2"
/>
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
Gönder
</button>
</form>
</div>
);
}Çoklu Chat Oturumları: id Parametresi
Birden fazla chat oturumunu aynı anda yönetmek için id parametresini kullanabilirsiniz:
const chat1 = useChat({ id: 'general-chat' });
const chat2 = useChat({ id: 'code-review-chat' });Her id, bağımsız bir mesaj geçmişi ve state yönetimi sağlar. Bu, çoklu sekme veya panel içeren uygulamalarda son derece kullanışlıdır.
Performans ve Best Practice İpuçları
maxDurationayarlayın: Sunucu tarafında uzun yanıtlar için serverless fonksiyon timeout'unu artırın- Rate limiting uygulayın: API maliyetlerini kontrol altında tutmak için istek sınırlaması ekleyin
- Hata yönetimini ihmal etmeyin:
onErrorcallback'i veerrorstate'ini her zaman kullanın stopfonksiyonunu sunun: Kullanıcıya uzun yanıtları durdurma imkanı verinreloadfonksiyonunu düşünün: Kötü yanıtlar için "Yeniden Dene" butonu ekleyin- Mesaj geçmişi limiti koyun: Çok uzun konuşmalarda token limitine dikkat edin
Sonuç
Vercel AI SDK'nın useChat hook'u, React uygulamalarına streaming AI chat entegrasyonu yapmayı inanılmaz derecede kolaylaştırıyor. Birkaç satır kodla:
- ✅ Gerçek zamanlı streaming yanıtlar
- ✅ Otomatik mesaj geçmişi yönetimi
- ✅ Yükleme ve hata durumu takibi
- ✅ Yanıt durdurma ve yeniden üretme
- ✅ Programatik mesaj gönderme
gibi özelliklere sahip oluyorsunuz. Hook'un arkasındaki soyutlama katmanı, WebSocket veya Server-Sent Events gibi düşük seviyeli detaylarla uğraşmanıza gerek bırakmıyor.
Eğer bir AI chat uygulaması geliştirmeyi düşünüyorsanız, useChat kesinlikle başlangıç noktanız olmalı. Basit bir prototipten production-ready bir uygulamaya kadar ihtiyacınız olan her şeyi sunuyor. Hemen npm install ai komutuyla başlayabilir ve dakikalar içinde çalışan bir AI chat arayüzüne sahip olabilirsiniz.