Bu eğitim, FastAPI, SSE streaming, konuşma belleği ve model değiştirme özelliklerine sahip, küçük ama prodüksiyona hazır bir chatbot servisi oluşturur. Amaç basit bir demo sunmak değil, bir ürünün arkasına yerleştirebileceğiniz ve güvenle geliştirebileceğiniz bir backend elde etmektir.
Eğer halihazırda OpenAI uyumlu bir SDK'yı LemonData'ya yönlendirdiyseniz, bu makale oradan devam eder. Eğer henüz base URL değişimini yapmadıysanız, önce migration guide içeriğini okuyun. Ana endişeniz yük altında istek şekillendirme ve backoff ise, bu kılavuzu AI API rate limiting guide ile birlikte kullanın.
Neler İnşa Ediyoruz?
Tamamlanan servis altı hareketli parçadan oluşur:
- Smoke testleri için senkron bir
/chatendpoint'i. - Gerçek UI için streaming özellikli bir
/chat/streamendpoint'i. conversation_idile anahtarlanmış konuşma durumu.- Frontend'in rastgele ID'ler talep edememesi için bir model izin listesi (allowlist).
- İlk 429 hatasında çökmeyen hata yönetimi.
- Bellek içi (in-memory) prototipten Redis veya PostgreSQL'e geçiş için net bir yol.
Bu, bir destek botu, dahili bir asistan veya gömülü bir chat widget'ının ilk versiyonunu çalıştırmak için yeterlidir.
Minimum Stack'i Kurun
pip install fastapi uvicorn openai pydantic redis
İlk aşamada redis kurulumunu atlayabilirsiniz, ancak yükseltme yolunun net olması için import işlemini şimdiden bağlamak faydalıdır.
Adım 1: Küçük ve Basit Bir Chat Endpoint'i ile Başlayın
Chatbot çalışmalarında kaybolmanın en hızlı yolu, temel istek yolu stabil hale gelmeden önce websocket'ler, tool use ve agent orkestrasyonu ile başlamaktır. Key, base URL ve model yönlendirmenizin doğru olduğunu kanıtlayan küçük bir endpoint ile başlayın.
from fastapi import FastAPI
from openai import OpenAI
from pydantic import BaseModel
app = FastAPI()
client = OpenAI(
api_key="sk-lemon-xxx",
base_url="https://api.lemondata.cc/v1"
)
class ChatRequest(BaseModel):
message: str
model: str = "gpt-4.1-mini"
conversation_id: str | None = None
@app.post("/chat")
async def chat(req: ChatRequest):
response = client.chat.completions.create(
model=req.model,
messages=[{"role": "user", "content": req.message}]
)
return {"reply": response.choices[0].message.content}
Bir smoke test çalıştırın. Eğer bu başarısız olursa, üzerine yeni özellikler eklemeye devam etmeyin.
Adım 2: Streaming Ekleyin, Çünkü Kullanıcılar Gecikmeyi Ölçmeden Önce Hissederler
Çoğu chatbot ürünü model yavaş olduğu için değil, UI yanıtın tamamı gelene kadar boş kaldığı için yavaş hissedilir. SSE, birçok chat ürünü için yeterlidir ve websocket'lere göre daha düşük operasyonel yüke sahiptir.
from fastapi.responses import StreamingResponse
@app.post("/chat/stream")
async def chat_stream(req: ChatRequest):
def generate():
stream = client.chat.completions.create(
model=req.model,
messages=[{"role": "user", "content": req.message}],
stream=True
)
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
yield f"data: {delta.content}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
Frontend tarafında, en basit tarayıcı tabanlı istemci bile yeterlidir:
async function sendMessage(payload) {
const response = await fetch('/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
console.log(chunk);
}
}
Ürününüz zaten bir tarayıcı istemcisi ve standart HTTP kullanıyorsa, SSE mimariyi daha basit tutar.
Adım 3: Konuşma Durumunu Request Body Dışına Taşıyın
İlk chatbot demoları genellikle tüm transkripti tarayıcıda tutar ve her turda geri gönderir. Bu prototipler için işe yarar. Ancak yeniden denemeler (retries), devam ettirilebilir oturumlar veya sunucu tarafı araçlara ihtiyaç duyduğunuz anda karmaşıklaşır.
Başlangıç için bellek içi (in-memory) bir depo yeterlidir:
from collections import defaultdict
import uuid
conversations: dict[str, list] = defaultdict(list)
SYSTEM_PROMPT = "You are a helpful assistant. Be concise and direct."
def build_messages(conv_id: str, user_msg: str) -> list:
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
history = conversations[conv_id][-20:]
messages.extend(history)
messages.append({"role": "user", "content": user_msg})
conversations[conv_id].append({"role": "user", "content": user_msg})
return messages
Redis'e geçiş yolu çoğunlukla depolama tesisatından ibarettir:
import json
import redis
redis_client = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)
def load_history(conv_id: str) -> list:
raw = redis_client.get(f"chat:{conv_id}")
return json.loads(raw) if raw else []
def save_history(conv_id: str, history: list) -> None:
redis_client.setex(f"chat:{conv_id}", 60 * 60 * 24, json.dumps(history))
Konuşmaların TTL, devam ettirilebilirlik veya çoklu örnek (multi-instance) dağıtıma ihtiyacı varsa Redis kullanın. Transkriptin kendisi ürün verisi ise PostgreSQL kullanın.
Adım 4: Hataları Sadece İstisna Olarak Değil, Ürün Davranışı Olarak Ele Alın
Chatbot'unuz müşteriyle doğrudan temas halindeyse, hata yolu da başarı yolu kadar önemlidir. Kullanıcı, hatanın rate limiting, bakiye veya model kesintisinden kaynaklanıp kaynaklanmadığıyla ilgilenmez. UI'ın donup donmadığıyla ilgilenir.
from openai import APIConnectionError, APIError, RateLimitError
@app.post("/chat/stream")
async def chat_stream(req: ChatRequest):
conv_id = req.conversation_id or str(uuid.uuid4())
messages = build_messages(conv_id, req.message)
def generate():
full_response = []
try:
stream = client.chat.completions.create(
model=req.model,
messages=messages,
stream=True
)
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
full_response.append(delta.content)
yield f"data: {delta.content}\n\n"
except RateLimitError:
yield "data: [ERROR] The model is busy. Please retry in a few seconds.\n\n"
except APIConnectionError:
yield "data: [ERROR] Temporary network issue. Please retry.\n\n"
except APIError as error:
yield f"data: [ERROR] {error.message}\n\n"
else:
conversations[conv_id].append(
{"role": "assistant", "content": "".join(full_response)}
)
finally:
yield "data: [DONE]\n\n"
return StreamingResponse(
generate(),
media_type="text/event-stream",
headers={"X-Conversation-ID": conv_id}
)
Anlamlı bir yük taşıyorsanız, istekleri upstream'e ulaşmadan önce şekillendirmelisiniz. Detaylı kalıplar rate limiting guide içerisinde yer almaktadır, ancak kısa özeti şudur: sınırlı yeniden denemeler kullanın, jitter kullanın ve asla except Exception: time.sleep(1) yapmayın.
Adım 5: Model Değiştirme İçin Serbest Metin Kutusu Değil, Bir İzin Listesi Gerekir
Tek bir API key yüzlerce modele ulaşabilir. Bu, UI'ınızın yüzlerce modeli dışarı açması gerektiği anlamına gelmez. Backend, kullanım durumunuza uygun küçük bir izin listesi (allowlist) yayınlamalıdır.
AVAILABLE_MODELS = {
"fast": "gpt-4.1-mini",
"balanced": "claude-sonnet-4-6",
"reasoning": "o3",
"budget": "deepseek-chat",
}
@app.get("/models")
async def list_models():
return {"models": AVAILABLE_MODELS}
Bu yaklaşım üç faydalı şey sağlar:
- Frontend'in geçersiz veya kullanımdan kaldırılmış model ID'leri talep etmesini engeller.
- Her istemciyi yeniden dağıtmadan (redeploy) bir katmanı daha sonra yeniden eşlemenize olanak tanır.
- Maliyet kontrollerini uygulamak için tek bir yer sağlar.
Ekibiniz hala hangi sağlayıcılar üzerinde standartlaşacağına karar veriyorsa, izin listesini kilitlemeden önce pricing comparison ve OpenRouter vs LemonData karşılaştırması sayfalarını okumaya değer.
Adım 6: Trafik Gelmeden Önce Prodüksiyon Detaylarını Ekleyin
Bir chatbot backend'i, çekirdek chat çağrısı zekice olduğunda değil, çevredeki detaylar ele alındığında prodüksiyon seviyesine ulaşır.
Kontrol listesi kısadır:
- Frontend hatalarını backend loglarıyla ilişkilendirebilmek için request ID'leri ekleyin.
- Kullanıcı başına eşzamanlılık (concurrency) ve istek boyutunu sınırlayın.
- Uzun geçmişleri, token bütçenizi patlatmadan önce budayın.
- Modeli, gecikmeyi (latency), girdi boyutunu ve bitiş nedenini loglayın.
- Kullanıcıya görünen hata mesajlarını dahili hata detaylarından ayırın.
- İlk kesintiden önce fallback'in çalıştığından emin olmak için alternatif bir modeli test edin.
Geçmiş budama (history trimming) basit tutulabilir:
def trim_history(messages: list, max_tokens: int = 8000) -> list:
system = messages[0]
history = messages[1:]
total_chars = len(system["content"])
trimmed = []
for msg in reversed(history):
msg_chars = len(msg["content"])
if total_chars + msg_chars > max_tokens * 4:
break
trimmed.insert(0, msg)
total_chars += msg_chars
return [system] + trimmed
Buradaki amaç token bazında mükemmel bir hesaplama değil, bariz bağlam (context) patlamalarını durdurmaktır.
Demodan Ürüne
Bu backend stabil hale geldiğinde, bir sonraki güncelleme nadiren "daha fazla yapay zeka" olur. Genellikle sıkıcı altyapı işleridir:
- Bir kullanıcının diğerinin konuşmasını okuyamaması için auth (yetkilendirme).
- Oturumların deploy'lardan etkilenmemesi için persistence (kalıcılık).
- Gürültülü bir kullanıcının kotanızı bitirmemesi için rate limiting.
- Chatbot müşteriyle temas halindeyse faturalandırma veya kullanım ilişkilendirme.
- Konuşmaların uzun süreli belleğe ihtiyacı varsa arka plan özetleme (summarization).
Birleşik bir gateway'in yardımcı olmasının nedeni budur. Base URL migrasyonunu tamamladıktan sonra, model değişiklikleri bir platform yeniden yazımı olmaktan çıkar ve konfigürasyon haline gelir.
Smoke Test
uvicorn main:app --reload --port 8000
curl -N -X POST http://localhost:8000/chat/stream \
-H "Content-Type: application/json" \
-d '{"message": "Hello!", "model": "gpt-4.1-mini"}'
Eğer bir turu stream edebiliyor, bir konuşmayı koruyabiliyor ve zorunlu bir hatada temiz bir hata döndürebiliyorsanız, doğru temele sahipsiniz demektir.
Maliyet Tahmini
LemonData üzerinde bir API key oluşturun, OpenAI SDK'nızı https://api.lemondata.cc/v1 adresine yönlendirin ve ayrı sağlayıcı hesaplarını yönetmeden chatbot'unuzun ilk prodüksiyon versiyonunu yayınlayın.
| Model | Günlük Maliyet | Aylık Maliyet |
|---|---|---|
| GPT-4.1-mini | ~$2.40 | ~$72 |
| GPT-4.1 | ~$12.00 | ~$360 |
| Claude Sonnet 4.6 | ~$18.00 | ~$540 |
| DeepSeek V3 | ~$1.68 | ~$50 |
Çoğu konuşma için GPT-4.1-mini kullanmak ve yalnızca kullanıcılar talep ettiğinde Claude Sonnet 4.6'ya yükseltmek, çoğu uygulama için maliyetleri aylık 100 doların altında tutar.
API key'inizi alın: lemondata.cc tek bir endpoint üzerinden 300'den fazla model sunar. İnşa etmeye başlamak için 1$ ücretsiz kredi.
