হোম/Roadmap/Chapter 11.02
Phase 11 · Chapter 11.02

Intermediate: Chatbot API + Image Classifier Service

দুটো production-quality intermediate project — একটা LLM-based chatbot, আরেকটা CV image classifier।

Project A

Customer Support Chatbot

  • LLM: OpenAI gpt-4o-mini / Groq llama-3 / local Ollama।
  • System prompt + conversation history।
  • Streaming response (SSE)।
  • React frontend, FastAPI backend, Redis session store।
Chatbot Backend

Streaming FastAPI

pythonproduction
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from openai import OpenAI
import redis, json

app = FastAPI()
client = OpenAI()
r = redis.Redis(decode_responses=True)

SYSTEM = "You are a helpful support agent for an e-commerce store. Reply in Bangla."

class Msg(BaseModel):
    session_id: str
    text: str

@app.post("/chat")
async def chat(msg: Msg):
    history = json.loads(r.get(f"chat:{msg.session_id}") or "[]")
    history.append({"role": "user", "content": msg.text})

    def stream():
        full = ""
        resp = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role":"system","content":SYSTEM}, *history],
            stream=True,
        )
        for chunk in resp:
            delta = chunk.choices[0].delta.content or ""
            full += delta
            yield f"data: {json.dumps({'delta': delta})}\n\n"
        history.append({"role":"assistant","content":full})
        r.setex(f"chat:{msg.session_id}", 3600, json.dumps(history[-20:]))
        yield "data: [DONE]\n\n"

    return StreamingResponse(stream(), media_type="text/event-stream")
React Frontend

SSE consumer

typescriptproduction
async function sendMessage(text: string) {
  const res = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ session_id: sessionId, text }),
  });
  const reader = res.body!.getReader();
  const decoder = new TextDecoder();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    const lines = decoder.decode(value).split("\n\n");
    for (const line of lines) {
      if (!line.startsWith("data: ")) continue;
      const data = line.slice(6);
      if (data === "[DONE]") return;
      const { delta } = JSON.parse(data);
      appendToMessage(delta);
    }
  }
}
Project B

Image Classifier Service

  • Pre-trained ResNet50 / EfficientNet (ImageNet)।
  • Upload endpoint — multipart file।
  • S3-compatible storage (R2 / MinIO)।
  • Async job queue (Celery + Redis) for batch।
Image API

FastAPI + Torch

pythonproduction
from fastapi import FastAPI, UploadFile, File
import torch, io
from torchvision import models, transforms
from PIL import Image

app = FastAPI()
device = "cuda" if torch.cuda.is_available() else "cpu"
model = models.resnet50(weights="IMAGENET1K_V2").to(device).eval()
labels = [l.strip() for l in open("imagenet_classes.txt")]

tfm = transforms.Compose([
    transforms.Resize(256), transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),
])

@app.post("/classify")
async def classify(file: UploadFile = File(...)):
    img = Image.open(io.BytesIO(await file.read())).convert("RGB")
    x = tfm(img).unsqueeze(0).to(device)
    with torch.no_grad():
        probs = torch.softmax(model(x)[0], dim=0)
    top5 = torch.topk(probs, 5)
    return {
        "predictions": [
            {"label": labels[i], "score": float(p)}
            for p, i in zip(top5.values, top5.indices)
        ]
    }
Async Pipeline

Celery worker for batch

pythonproduction
# tasks.py
from celery import Celery
celery = Celery("img", broker="redis://redis:6379/0", backend="redis://redis:6379/1")

@celery.task
def classify_batch(s3_keys: list[str]):
    results = []
    for k in s3_keys:
        img = download_from_s3(k)
        results.append(classify_image(img))
    return results

# producer
job = classify_batch.delay(["uploads/a.jpg", "uploads/b.jpg"])
# status: job.state, result: job.get(timeout=60)
Deployment

docker-compose

yamlproduction
services:
  api:    { build: ./api, ports: ["8000:8000"], depends_on: [redis] }
  worker: { build: ./api, command: celery -A tasks worker, depends_on: [redis] }
  redis:  { image: redis:7-alpine }
  web:    { build: ./web, ports: ["3000:3000"] }
Pitfalls

Intermediate-এ যা ভাঙে

  • LLM API key client-side leak — সব time backend proxy করো।
  • Streaming response Nginx buffer-এ আটকে যায় — proxy_buffering off
  • Image upload size limit — FastAPI default 1MB।
  • Torch CPU image 2GB — slim image বানাও CUDA ছাড়া।
  • Conversation history unbounded → context overflow + cost explosion।
Deliverable

Portfolio checklist

  1. 2 live demo URL (chatbot UI + image classifier)।
  2. Architecture diagram (Excalidraw)।
  3. Cost analysis: $/1000 chat, $/1000 classify।
  4. README-এ rate-limit, auth, error handling document।
Takeaway

মূল কথা

Intermediate = real user, real data, real cost। API call, streaming, async queue, storage — সব integrate করতে শিখলে।

← Roadmap-এ ফিরুন
পরবর্তী: Advanced: RAG, AI SaaS, Recommenderশীঘ্রই