İçeriğe geç
Sedat Demir
Geri dön

Vercel AI SDK useChat Hook ile React'te Streaming AI Yanıtları: Kapsamlı Rehber

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ı:


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:

  1. AI SDK Core: Model-agnostik bir katman (OpenAI, Anthropic, Google Gemini, Mistral gibi sağlayıcılarla çalışır)
  2. 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-here

Backend: 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:


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:


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-markdown
import 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ı

  1. maxDuration ayarlayın: Sunucu tarafında uzun yanıtlar için serverless fonksiyon timeout'unu artırın
  2. Rate limiting uygulayın: API maliyetlerini kontrol altında tutmak için istek sınırlaması ekleyin
  3. Hata yönetimini ihmal etmeyin: onError callback'i ve error state'ini her zaman kullanın
  4. stop fonksiyonunu sunun: Kullanıcıya uzun yanıtları durdurma imkanı verin
  5. reload fonksiyonunu düşünün: Kötü yanıtlar için "Yeniden Dene" butonu ekleyin
  6. 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:

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.


Share this post on:

Sonraki Yazı
React Foundation: Linux Foundation Altında Yeni Bir Dönem Başlıyor