Pengaliran di FastAPI dengan Peristiwa yang Dikirim Server
Bangun endpoint FastAPI yang meneruskan respons pengaliran LLM ke klien peramban menggunakan StreamingResponse dan tipe konten text/event-stream.
Pengaliran di FastAPI dengan Peristiwa yang Dikirim Server adalah pelajaran AI Engineering Academy gratis di CoddyKit. Ini adalah pelajaran 3 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar AI Engineering Academy, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus AI Engineering Academy mencakup 4 pelajaran total.
Mengapa Menggunakan Server-Sent Events untuk Streaming LLM
Server-Sent Events (SSE) adalah standar W3C yang memungkinkan server mendorong aliran peristiwa teks ke klien peramban melalui satu koneksi HTTP yang berumur panjang. Berbeda dari WebSockets, SSE bersifat satu arah (dari server ke klien), bekerja melalui HTTP/1.1 standar, terhubung kembali secara otomatis saat terputus, dan tidak memerlukan pustaka peramban khusus. Sifat-sifat ini menjadikannya transportasi ideal untuk melakukan streaming token LLM dari backend FastAPI ke frontend web.
Format Kabel SSE
SSE mengirim data teks yang diformat sebagai serangkaian bidang yang dipisahkan oleh baris baru. Setiap peristiwa memiliki bidang tipe event opsional, bidang data yang berisi muatan, dan id opsional untuk penyambungan kembali. Peristiwa dipisahkan oleh satu baris kosong. Untuk streaming LLM, kirim setiap token sebagai baris data: token_text\n\n dan peristiwa khusus data: [DONE]\n\n di akhir untuk menandai selesainya stream.
# SSE wire format example
'''
data: The\n\n
data: capital\n\n
data: of\n\n
data: France\n\n
data: is\n\n
data: Paris\n\n
data: [DONE]\n\n
'''
# Each 'data:' line is one event.
# The double newline (\n\n) terminates each event.
# The client receives these as EventSource message events.
# The content-type must be 'text/event-stream'.StreamingResponse di FastAPI
StreamingResponse milik FastAPI menerima generator asinkron yang menghasilkan string dan melakukan streaming string tersebut ke klien. Dengan menetapkan media_type ke 'text/event-stream' dan memformat setiap string yang dihasilkan sebagai peristiwa SSE, Anda mengubah generator asinkron apa pun menjadi stream SSE yang sesuai. FastAPI menangani siklus hidup koneksi, pengosongan penyangga, dan header HTTP secara otomatis.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI
import asyncio
app = FastAPI()
async_client = AsyncOpenAI()
async def token_generator(prompt: str):
stream = await async_client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
stream=True,
)
async for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
yield f'data: {delta}\n\n' # SSE format
yield 'data: [DONE]\n\n'
@app.get('/stream')
async def stream_endpoint(prompt: str):
return StreamingResponse(
token_generator(prompt),
media_type='text/event-stream',
headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'},
)Header HTTP Penting untuk SSE
Tiga header HTTP sangat penting agar SSE bekerja dengan benar melalui proksi dan CDN. Cache-Control: no-cache mencegah perantara menyimpan stream dalam tembolok. Connection: keep-alive menjaga koneksi TCP tetap terbuka. X-Accel-Buffering: no menonaktifkan penyanggaan respons Nginx, yang jika tidak dinonaktifkan akan mengelompokkan potongan dan menggagalkan efek streaming. Tanpa header terakhir ini, Nginx akan menyangga semua output sebelum meneruskannya ke peramban.
from fastapi.responses import StreamingResponse
SSE_HEADERS = {
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no', # disable nginx buffering
'Access-Control-Allow-Origin': '*', # CORS for cross-origin clients
}
@app.get('/chat')
async def chat_stream(prompt: str):
return StreamingResponse(
token_generator(prompt),
media_type='text/event-stream',
headers=SSE_HEADERS,
)Peristiwa SSE Terstruktur dengan Muatan JSON
Untuk API streaming yang lebih kaya, enkode muatan setiap peristiwa sebagai JSON, bukan teks mentah. Dengan begitu, Anda dapat menyertakan metadata bersama token—misalnya tipe token (konten atau pemanggilan alat), ID pesan, atau stempel waktu latensi. Klien peramban mengurai JSON setiap peristiwa dan mengarahkan tipe peristiwa yang berbeda ke komponen UI yang berbeda.
import json
import time
async def json_token_generator(prompt: str, session_id: str):
t_start = time.perf_counter()
stream = await async_client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
stream=True,
)
async for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
payload = json.dumps({
'type': 'token',
'content': delta,
'session_id': session_id,
't_ms': round((time.perf_counter() - t_start) * 1000),
})
yield f'data: {payload}\n\n'
# Send completion event
yield f'data: {json.dumps({"type": "done", "session_id": session_id})}\n\n'Mengonsumsi SSE di Peramban (JavaScript)
API EventSource di sisi peramban terhubung ke endpoint SSE dan memicu peristiwa saat peristiwa tersebut tiba. Untuk streaming token, dengarkan peristiwa message bawaan, uraikan data sebagai JSON atau perlakukan sebagai string mentah, lalu tambahkan setiap token ke DOM. Tangani penanda [DONE] dengan menutup koneksi EventSource.
// Browser-side JavaScript
const prompt = 'Explain hybrid search in one paragraph.';
const url = '/stream?prompt=' + encodeURIComponent(prompt);
const source = new EventSource(url);
const output = document.getElementById('output');
source.onmessage = (event) => {
if (event.data === '[DONE]') {
source.close(); // stop listening
return;
}
output.textContent += event.data; // append each token
};
source.onerror = (err) => {
console.error('SSE error:', err);
source.close();
};Permintaan POST dengan fetch untuk Streaming
EventSource hanya mendukung permintaan GET, sehingga penggunaannya terbatas untuk prompt yang kompleks. Untuk permintaan POST (mengirim isi JSON dengan riwayat percakapan), gunakan API fetch peramban bersama API Streams untuk membaca isi respons secara bertahap. Pola ini digunakan oleh antarmuka web ChatGPT dan sebagian besar UI obrolan LLM produksi.
// Browser-side: POST with fetch and ReadableStream
async function streamPost(messages) {
const response = await fetch('/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({messages}),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
const output = document.getElementById('output');
while (true) {
const {done, value} = await reader.read();
if (done) break;
const text = decoder.decode(value, {stream: true});
// Parse SSE lines
for (const line of text.split('\n')) {
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
output.textContent += line.slice(6);
}
}
}
}Endpoint POST FastAPI untuk Streaming Obrolan
Untuk streaming obrolan berbasis POST, tentukan model Pydantic untuk isi permintaan, terima daftar pesan, lalu lakukan streaming terhadap respons LLM. Dengan demikian, riwayat percakapan lengkap dapat dikirim bersama setiap permintaan, sehingga mendukung aplikasi obrolan dengan banyak giliran. Polanya identik dengan streaming GET, kecuali prompt diambil dari isi permintaan.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
class ChatRequest(BaseModel):
messages: list[dict]
model: str = 'gpt-4o-mini'
@app.post('/chat')
async def chat_post(request: ChatRequest):
async def generate():
stream = await async_client.chat.completions.create(
model=request.model,
messages=request.messages,
stream=True,
)
async for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
yield f'data: {delta}\n\n'
yield 'data: [DONE]\n\n'
return StreamingResponse(
generate(),
media_type='text/event-stream',
headers=SSE_HEADERS,
)Menangani Pemutusan Koneksi Klien
Saat pengguna peramban berpindah halaman atau menutup tab, koneksi HTTP tertutup dan FastAPI memunculkan asyncio.CancelledError dalam generator streaming. Selalu tangani kondisi ini agar permintaan streaming LLM tidak dibiarkan terbuka dan menimbulkan biaya API yang tidak perlu. Bungkus generator dalam try/except untuk CancelledError dan batalkan stream OpenAI saat kondisi tersebut terdeteksi.
from fastapi import Request
@app.get('/stream')
async def stream_with_disconnect(prompt: str, request: Request):
async def generate_with_cancel():
try:
stream = await async_client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
stream=True,
)
async for chunk in stream:
if await request.is_disconnected():
break # client gone, stop generating
delta = chunk.choices[0].delta.content
if delta:
yield f'data: {delta}\n\n'
except asyncio.CancelledError:
pass # client disconnected
finally:
yield 'data: [DONE]\n\n'
return StreamingResponse(generate_with_cancel(), media_type='text/event-stream')Menambahkan Autentikasi Permintaan
Endpoint streaming produksi harus mengautentikasi permintaan untuk mencegah penggunaan LLM tanpa izin. Gunakan Depends milik FastAPI bersama kunci API atau pemeriksaan header JWT. Autentikasi berlangsung sebelum generator dimulai, sehingga bebannya minimal dan stream hanya dimulai setelah pengguna diverifikasi.
from fastapi import Header, HTTPException, Depends
VALID_API_KEYS = {'sk-demo-key-1', 'sk-demo-key-2'}
async def verify_api_key(x_api_key: str = Header(None)):
if x_api_key not in VALID_API_KEYS:
raise HTTPException(status_code=401, detail='Invalid API key')
return x_api_key
@app.post('/chat')
async def authenticated_chat(
request: ChatRequest,
api_key: str = Depends(verify_api_key),
):
async def generate():
stream = await async_client.chat.completions.create(
model=request.model,
messages=request.messages,
stream=True,
)
async for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
yield f'data: {delta}\n\n'
yield 'data: [DONE]\n\n'
return StreamingResponse(generate(), media_type='text/event-stream', headers=SSE_HEADERS)Menguji Endpoint SSE
Uji endpoint streaming dengan TestClient milik FastAPI dalam mode streaming. Gunakan with client.stream('GET', '/stream', params={...}) as r dan lakukan iterasi terhadap r.iter_lines() untuk menerima peristiwa SSE. Dengan demikian, Anda dapat memverifikasi bahwa token diformat dengan benar, penanda DONE dikirim, dan kasus error menghasilkan peristiwa error SSE yang sesuai.
from fastapi.testclient import TestClient
def test_sse_endpoint():
with TestClient(app) as client:
with client.stream('GET', '/stream', params={'prompt': 'Say hi'}) as r:
assert r.status_code == 200
assert 'text/event-stream' in r.headers['content-type']
events = []
for line in r.iter_lines():
if line.startswith('data: '):
events.append(line[6:])
assert events[-1] == '[DONE]'
full_text = ''.join(e for e in events if e != '[DONE]')
assert len(full_text) > 0Pemeriksaan Singkat
Uji pemahaman Anda tentang streaming FastAPI dengan SSE dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda telah mempelajari: Server-Sent Events adalah transportasi HTTP standar untuk melakukan streaming token LLM ke klien peramban; StreamingResponse dengan text/event-stream mengubah generator asinkron apa pun menjadi stream SSE di FastAPI; dan header penting, termasuk X-Accel-Buffering dan Cache-Control, diperlukan agar perilakunya benar di balik proksi. Tangani pemutusan koneksi klien untuk mencegah pemanggilan API LLM yang terlantar. Selanjutnya, kita akan menangani respons streaming yang berisi pemanggilan alat.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Pengaliran di FastAPI dengan Peristiwa yang Dikirim Server” gratis?
Ya — teks lengkap “Pengaliran di FastAPI dengan Peristiwa yang Dikirim Server” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus AI Engineering Academy, upgrade ke CoddyKit PRO. Kursus AI Engineering Academy mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Pengaliran di FastAPI dengan Peristiwa yang Dikirim Server”?
Bangun endpoint FastAPI yang meneruskan respons pengaliran LLM ke klien peramban menggunakan StreamingResponse dan tipe konten text/event-stream. Kamu berlatih AI Engineering Academy dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai AI Engineering Academy?
Tidak diperlukan pengalaman sebelumnya. AI Engineering Academy di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 3 dari 4.
Berapa lama pelajaran “Pengaliran di FastAPI dengan Peristiwa yang Dikirim Server” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran AI Engineering Academy ini?
Ya. Setiap pelajaran AI Engineering Academy menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Memahami Pengaliran Token
- Mengonsumsi Aliran dengan Python SDK
- Pengaliran di FastAPI dengan Peristiwa yang Dikirim Server
- Menangani Pemanggilan Alat dalam Respons yang Dialirkan