Membangun backend yang mampu memproses data non-terstruktur (seperti teks bebas, email, atau log) secara manual sering kali menjadi bottleneck. Solusi tradisional menggunakan regex atau NLP rules-based cepat menjadi rapuh saat variasi data meningkat.
Tutorial ini membahas arsitektur REST API yang menggunakan Large Language Models (LLM) sebagai engine logika inti. Kami akan menggunakan FastAPI untuk framework web, Pydantic untuk validasi data, dan Ollama untuk menjalankan model AI secara lokal (on-premise) demi keamanan data dan latensi rendah.
Arsitektur Sistem
Sistem ini terdiri dari tiga komponen utama:
- API Gateway (FastAPI): Menerima request HTTP, memvalidasi input, dan mengelola lifecycle request.
- AI Engine (Ollama): Menjalankan model LLM (misalnya
llama3ataumistral) untuk mengekstrak entitas atau mengklasifikasikan data. - Data Layer (Pydantic): Memastikan output dari LLM (yang bersifat probabilistik) dikonversi menjadi struktur data yang valid dan konsisten sebelum dikirim ke client.
Prasyarat & Persiapan
Sebelum memulai, pastikan lingkungan pengembangan Anda memenuhi syarat berikut:
- Python 3.10+: Versi minimal untuk fitur typing modern.
- Ollama: Terinstal dan berjalan di localhost.
bash # Cek instalasi Ollama ollama --version - Model AI: Tarik model yang ringan namun akurat untuk ekstraksi data.
bash # Tarik model Mistral 7B (optimal untuk CPU/GPU entry-level) ollama pull mistral - Paket Python:
bash pip install fastapi uvicorn pydantic requests
Langkah Demi Langkah
Step 1: Inisialisasi Proyek dan Struktur Direktori
Buat struktur proyek yang rapi untuk memisahkan logika API dan logika AI.
mkdir ai-api-tutorial
cd ai-api-tutorial
mkdir -p app/core app/api app/schemas
touch app/main.py app/core/ai_engine.py app/api/routes.py app/schemas/data.py
Step 2: Definisi Skema Data (Pydantic)
LLM sering kali mengembalikan JSON yang tidak konsisten. Kita harus mendefinisikan skema target yang ketat.
Buka app/schemas/data.py:
from pydantic import BaseModel, Field
from typing import Optional
class InvoiceInput(BaseModel):
raw_text: str = Field(..., description="Teks mentah dari invoice atau email")
class InvoiceData(BaseModel):
vendor_name: str
total_amount: float
currency: str
due_date: Optional[str] = None
confidence_score: float = Field(..., ge=0, le=1)
Step 3: Implementasi AI Engine
Kita akan membuat wrapper untuk berkomunikasi dengan Ollama. Gunakan prompt engineering yang ketat untuk memaksa output JSON.
Buka app/core/ai_engine.py:
import requests
import json
from typing import Dict, Any
class OllamaClient:
def __init__(self, model_name: str = "mistral", base_url: str = "http://localhost:11434"):
self.model_name = model_name
self.base_url = base_url
def extract_data(self, prompt: str, context: str) -> Dict[str, Any]:
"""
Mengirim prompt ke Ollama dan memparse respons JSON.
"""
url = f"{self.base_url}/api/generate"
payload = {
"model": self.model_name,
"prompt": prompt,
"stream": False,
"options": {
"temperature": 0.0, # Penting: Set 0 untuk deterministik
"top_p": 0.9
}
}
try:
response = requests.post(url, json=payload, timeout=60)
response.raise_for_status()
result = response.json()
raw_output = result.get("response", "")
# Bersihkan output jika LLM menyertakan markdown code blocks
if "```json" in raw_output:
raw_output = raw_output.split("```json")[1].split("```")[0]
elif "```" in raw_output:
raw_output = raw_output.split("```")[1].split("```")[0]
return json.loads(raw_output)
except json.JSONDecodeError:
raise ValueError("LLM gagal mengembalikan JSON valid")
except requests.RequestException as e:
raise ConnectionError(f"Gagal terhubung ke Ollama: {e}")
Step 4: Membuat Endpoint REST API
Sekarang hubungkan semuanya di FastAPI.
Buka app/main.py:
from fastapi import FastAPI, HTTPException
from app.schemas.data import InvoiceInput, InvoiceData
from app.core.ai_engine import OllamaClient
import json
app = FastAPI(title="AI Invoice Processor")
ai_engine = OllamaClient()
PROMPT_TEMPLATE = """
Kamu adalah asisten data yang ahli dalam ekstraksi informasi.
Analisis teks berikut dan ekstrak data invoice.
Kembalikan HANYA objek JSON valid dengan format:
{{
"vendor_name": "string",
"total_amount": float,
"currency": "string",
"due_date": "string atau null",
"confidence_score": float
}}
Teks:
{context}
"""
@app.post("/api/v1/invoice/parse", response_model=InvoiceData)
async def parse_invoice(input_data: InvoiceInput):
"""
Endpoint untuk memparse teks mentah menjadi data terstruktur.
"""
try:
# 1. Siapkan Prompt
prompt = PROMPT_TEMPLATE.format(context=input_data.raw_text)
# 2. Panggil AI Engine
raw_result = ai_engine.extract_data(prompt, input_data.raw_text)
# 3. Validasi dengan Pydantic
# Pydantic akan melempar ValidationError jika struktur tidak cocok
validated_data = InvoiceData(**raw_result)
return validated_data
except ValueError as e:
raise HTTPException(status_code=500, detail=f"Error parsing AI: {str(e)}")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
Step 5: Menjalankan Server
Jalankan aplikasi menggunakan Uvicorn.
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
Step 6: Pengujian dengan cURL
Kirim request POST dengan contoh data invoice.
curl -X POST "http://localhost:8000/api/v1/invoice/parse" \
-H "Content-Type: application/json" \
-d '{
"raw_text": "Dear Team, please find attached the invoice from Acme Corp. Total amount is $1,500.00 USD. Payment due by 2023-12-01. Regards, John."
}'
Respons yang diharapkan:
{
"vendor_name": "Acme Corp",
"total_amount": 1500.0,
"currency": "USD",
"due_date": "2023-12-01",
"confidence_score": 0.95
}
Tips Optimasi & Keamanan
- Temperature 0.0: Selalu set
temperatureke 0 untuk tugas ekstraksi data. Nilai > 0 akan membuat output tidak konsisten (non-deterministik) yang berbahaya untuk pipeline data. - Rate Limiting: LLM lokal memakan banyak resource. Implementasikan rate limiting di level API (misalnya menggunakan
slowapi) untuk mencegah overload server. - Validasi Skema Ketat: Jangan pernah percaya output LLM secara mentah. Selalu validasi tipe data (float vs string) menggunakan Pydantic. Jika LLM mengembalikan "1500" (string) padahal butuh float, Pydantic akan menangkap error tersebut.
- Caching: Jika input teks sering berulang, cache hasil ekstraksi menggunakan Redis atau in-memory cache untuk mengurangi beban komputasi AI.
- Fallback Logic: Jika
confidence_scoredari LLM di bawah ambang batas (misal 0.8), arahkan data ke antrean untuk review manual alih-alih menyimpannya otomatis.
Troubleshooting Error Umum
| Error | Penyebab | Solusi |
|---|---|---|
ConnectionError: Failed to connect |
Ollama tidak berjalan atau port salah. | Pastikan ollama serve aktif dan port 11434 terbuka. |
JSONDecodeError |
LLM menambahkan teks penjelasan di luar JSON. | Perbaiki prompt: "Kembalikan HANYA JSON, tanpa markdown". |
ValidationError |
Field wajib tidak ditemukan di output LLM. | Tambahkan field Optional di Pydantic jika data bisa kosong, atau perbaiki prompt agar lebih spesifik. |
Timeout |
Model terlalu besar untuk hardware. | Gunakan model lebih kecil (misal llama3:8b atau phi3) atau optimalkan hardware. |
Kesimpulan
Integrasi AI ke dalam REST API memungkinkan otomatisasi proses yang sebelumnya membutuhkan logika aturan yang kompleks. Dengan menggunakan FastAPI untuk struktur web dan Ollama untuk inferensi lokal, Anda mendapatkan sistem yang cepat, privat, dan mudah di-deploy.
Kunci keberhasilan terletak pada prompt engineering yang ketat dan validasi data yang robust. Jangan pernah melewatkan langkah validasi Pydantic, karena output AI bersifat probabilistik dan harus dinormalisasi sebelum masuk ke database produksi.
Untuk kebutuhan dokumentasi API yang lebih kompleks atau penulisan konten teknis terkait proyek ini, Anda dapat memanfaatkan tools seperti Notion AI untuk menyusun arsitektur sistem atau Jasper untuk menulis dokumentasi pengguna yang lebih ramah non-teknis.