Mengintegrasikan LLM ke aplikasi nyata: OpenAI-compatible API, streaming server-side, function/tool calling, dan JSON schema untuk structured output. Episode ini berisi praktik membangun backend FastAPI dan UI Next.js dengan streaming token langsung dari model.

Setelah di episode 4 kalian belajar memilih model dan mengevaluasi prompt, episode ini menjawab pertanyaan paling praktis seorang backend developer: bagaimana cara memasang LLM ke aplikasi web yang benar-benar dipakai orang? Prompt yang bagus tidak berguna jika integrasinya jelek — timeout, output tidak ter-parse, atau UI yang menunggu 20 detik tanpa umpan balik.
Di dunia kerja, aplikasi AI produksi nyaris selalu punya pola yang sama: backend memanggil LLM (sering via OpenAI-compatible API), mengembalikan hasil terstruktur ke frontend, dan menampilkan respons secara streaming agar terasa hidup. Ketiga hal itu — kompatibilitas, structured output, dan streaming — menjadi fokus episode ini.
Kabar baik untuk kalian: hampir semua penyedia (Anthropic, Gemini, Ollama, vLLM, dan ribuan proxy) kini menyediakan endpoint bergaya OpenAI. Artinya satu kode SDK bisa berpindah provider hanya dengan mengubah base_url dan api_key:
from openai import OpenAI
# Ollama lokal (tanpa key)
ollama = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
# vLLM self-host
vllm = OpenAI(base_url="http://10.0.0.5:8000/v1", api_key="token-xyz")
# Proxy OpenRouter (satu key, banyak model)
router = OpenAI(base_url="https://openrouter.ai/api/v1", api_key="sk-or-...")
resp = router.chat.completions.create(
model="anthropic/claude-sonnet-4",
messages=[{"role": "user", "content": "Halo!"}],
)Implikasi arsitektural: jangan mengunci aplikasi ke satu provider. Karena antarmukanya sama, kalian bisa A/B test, failover, dan pindah model tanpa menulis ulang kode — pola ini kita pakai lagi di episode 21 (cost optimization) dan 23 (ekosistem).
Di episode 3 kita memaksa JSON lewat instruksi prompt. Sekarang kita naik ke level berikutnya: JSON schema native yang dijamin oleh API — model harus menghasilkan objek yang valid sesuai skema.
from openai import OpenAI
import json
client = OpenAI()
schema = {
"name": "product_review",
"strict": True,
"schema": {
"type": "object",
"properties": {
"sentiment": {"type": "string",
"enum": ["positive", "negative", "neutral"]},
"score": {"type": "integer", "minimum": 1, "maximum": 5},
"topics": {"type": "array", "items": {"type": "string"}},
},
"required": ["sentiment", "score", "topics"],
"additionalProperties": False,
},
}
resp = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_schema", "json_schema": schema},
messages=[{"role": "user",
"content": "Analisis ulasan: 'Streamingnya cepat tapi harga naik.'"}],
)
data = json.loads(resp.choices[0].message.content)
print(data) # {'sentiment': 'neutral', 'score': 3, 'topics': [...]}Dengan schema native, kalian tidak perlu lagi regex-repair atau retry karena JSON rusak — ini menghilangkan salah satu bug paling umum di aplikasi LLM produksi.
Fondasi agents (episode 9-11) adalah function calling: kalian mendefinisikan fungsi yang bisa dipanggil model, lalu model memutuskan kapan memanggilnya dan dengan argumen apa. Model tidak mengeksekusi — ia hanya mengembalikan nama fungsi + argumen terstruktur.
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Dapatkan suhu & kondisi cuaca suatu kota",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user", "content": "Cuaca di Jakarta hari ini?"}]
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
)
tool_call = resp.choices[0].message.tool_calls[0]
print(tool_call.function.name, tool_call.function.arguments)
# get_weather {"city":"Jakarta"}Pola lengkapnya (eksekusi, balas hasil, loop) kita bangun di episode 9. Di episode ini cukup pahami: model adalah pengambil keputusan, kode adalah eksekutor.
Server harus meneruskan token LLM ke frontend secepat mungkin. Pola standarnya: backend FastAPI men-streaming SSE (text/event-stream), frontend membaca aliran dengan fetch + ReadableStream.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import OpenAI
app = FastAPI()
client = OpenAI()
@app.post("/chat")
def chat(question: str):
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": question}],
stream=True,
)
def generate():
for chunk in stream:
token = chunk.choices[0].delta.content or ""
if token:
yield f"data: {token}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")Alasan pentingnya streaming: model reasoning/panjang bisa butuh 10-60 detik untuk menghasilkan jawaban lengkap. Tanpa streaming, pengguna menatap layar kosong; dengan streaming, token pertama muncul dalam milidetik — perbedaan pengalaman yang sangat terasa.
Di sisi frontend, baca aliran dan tampilkan token yang masuk:
"use client"
import { useState } from "react"
export default function ChatBox() {
const [output, setOutput] = useState("")
async function send(q: string) {
const res = await fetch("/api/chat?question=" + encodeURIComponent(q))
const reader = res.body!.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
setOutput((prev) => prev + decoder.decode(value))
}
}
return (
<button onClick={() => send("Apa itu RAG?")}>Kirim</button>
)
}Di proyek Next.js production, pola yang sama bisa ditangani di Route Handler (app/api/chat/route.ts) yang meneruskan streaming ke client — dan streaming juga menjadi dasar dari AI SDK (useChat) yang mengurus buffering token untuk kalian.
Warning
Jangan jalankan pemanggilan LLM langsung dari browser (client-side) untuk produksi. API key akan bocor, biaya bisa disalahgunakan, dan kalian kehilangan kontrol rate limiting. Selalu proxy lewat backend/serverless — API key hanya hidup di server.
Inti yang harus dibawa pulang:
Di episode 6 selanjutnya kita akan membahas Embeddings & Vector Databases — representasi teks sebagai vektor, cosine similarity & semantic search, lalu ChromaDB, Qdrant, Pinecone, dan Weaviate dengan indexing, metadata filtering, dan hybrid search. Sampai jumpa di episode 6!