Bedah Arsitektur ACG — Tutorial Lengkap (Step-by-step)
Panduan teknis untuk membangun Auto Content Generator (ACG). Fokus: alur end-to-end, komponen utama, contoh implementasi, dan catatan etika/operasional. Ada contoh kode, diagram, dan runbook deploy minimal.
1. Overview & ruang lingkup
Ringkasan singkat sebelum tenggelam ke teknis: ACG mengambil data publik (RSS / halaman web / API), mengekstrak isi, menilai kualitas/duplikasi, lalu menghasilkan draf artikel menggunakan model bahasa (LLM) atau template. Sistem ini harus dipakai sebagai alat bantu riset/editorial — bukan mesin untuk mem-publish konten berhak cipta 1:1.
Target tutorial ini:
- Jelaskan alur end-to-end dari input (kata kunci) sampai output (draft yang bisa di-review).
- Tampilkan implementasi minimal yang bisa dipakai untuk proof-of-concept.
- Berikan praktik terbaik (politeness, audit, dedupe, QA).
2. Diagram arsitektur (visual)
Diagram sederhana untuk peta mental — kamu akan merujuk ke bagian-bagian berikutnya untuk detailnya.
Diagram ini modular: setiap kotak bisa menjadi microservice, worker, atau library lokal tergantung skala.
3. Step-by-step — Pembahasan tiap komponen
3.1 Input
Sumber input bisa berupa:
- Kata kunci (manual atau dari UI). Sistem pakai keyword ini untuk mencari sumber atau merancang prompt.
- Daftar seed URL / domain
- Feed RSS / JSON API / Google News API (jika punya akses legal)
- Webhook: notifikasi ketika sumber baru terbit
Catatan: simpan versi input (waktu, user, kata kunci) untuk audit.
3.2 Harvester (Fetcher)
Pekerjaan: mengambil HTML atau feed dari web. Hal-hal wajib:
- Patuh robots.txt: periksa apakah user-agent boleh mengakses URL.
- User-Agent yang transparan: contoh
ACG-Bot/1.0 (+https://domainmu.id/contact). - Rate-limiting & backoff: jangan bombardir situs, gunakan delay dan eksponensial backoff.
- Headless vs requests: requests cukup untuk situs statis; gunakan Playwright / Puppeteer untuk situs JS-heavy.
- Proxy & IP rotation: pakai hati-hati — rotasi IP bisa dianggap circumvention jika abusive.
Contoh alur fetch sederhana:
# pseudocode: fetch with robots + polite delay
if not robots_allow(url, user_agent):
skip()
resp = requests.get(url, headers={"User-Agent":UA}, timeout=15)
sleep(min_delay)
save_raw_html(url, resp.text)
3.3 Extractor (content extraction)
Tujuan: ambil title, publish_date (kalau ada), author, canonical, konten teks bersih (body), dan daftar gambar. Tools populer: readability-lxml, trafilatura, newspaper3k. Untuk JS-heavy, ambil snapshot HTML via Playwright lalu ekstrak.
Hal yang harus dicek:
- Apakah ekstraktor memotong informasi penting (caption, list, code block)?
- Normalisasi encoding & whitespace.
- Sertakan metadata
<meta name="description">sebagai excerpt cadangan.
3.4 Store: raw + metadata
Simpan dua lapis: raw_html (untuk audit) dan parsed_json (title, text, images, canonical, fingerprint). Rekomendasi penyimpanan:
- Object storage (S3 / MinIO) untuk raw HTML dan gambar
- Relational DB (Postgres) untuk metadata dan indeks
- Search index (Elasticsearch / OpenSearch) untuk query cepat
Skema minimal metadata (JSON):
{
"source_url": "...",
"title": "...",
"author": "...",
"published_at": "...",
"fetched_at": "...",
"text": "...",
"raw_html_path": "s3://bucket/xxx.html",
"fingerprint": "...",
"language": "id"
}
3.5 Indexing & Dedupe
Kenapa penting: konten yang sama atau sangat mirip harus dihentikan di tahap awal agar LLM tidak menghasilkan artikel tiruan atau plagiarisme
Teknik sederhana: SHA256 fingerprint dari teks -> cek exact-duplicate.
Near-duplicate (rekomendasi produksi): MinHash + LSH atau SimHash. Contoh alur MinHash:
# pip install datasketch
from datasketch import MinHash, MinHashLSH
def shingles(text, k=5):
words = text.split()
return [" ".join(words[i:i+k]) for i in range(max(0,len(words)-k+1))]
def minhash_of_text(text, num_perm=128):
m = MinHash(num_perm=num_perm)
for sh in set(shingles(text)):
m.update(sh.encode('utf8'))
return m
# index:
lsh = MinHashLSH(threshold=0.8, num_perm=128)
# lsh.insert(key, minhash)
# query: lsh.query(minhash_of_text(new_text))
3.6 Generator / Rewriter (LLM)
Fungsi: Menggabungkan referensi, menyusun ulang, dan menambahkan analisis orisinal. Prinsip penting:
- Jangan copy-paste literal — batasi kutipan verbatim maksimal 25 kata per sumber (atau sesuai kebijakan hukum).
- Tambahkan nilai orisinal — notify editor bila tulisan generatif kurang dari 30% konten orisinal.
- Prompt engineering — beri konteks, instruksi editorial, tone, panjang target, dan sumber referensi.
Contoh template prompt:
Gunakan referensi berikut (judul + link). Buat artikel bahasa Indonesia (EYD) berjudul menarik, lead 2 kalimat, isi 500-800 kata, minimal 30% analisis orisinal. Jangan menyalin lebih dari 25 kata per kutipan. Cantumkan daftar sumber di akhir.
3.7 Post-process
Pemeriksaan dan perbaikan sebelum draft dikirim ke editor/draft CMS:
- Plagiarism check (Copyscape / layanan pihak ketiga atau internal search terhadap indeks konten).
- Readability score (Flesch atau alternatif bahasa Indonesia) — saran perbaikan panjang kalimat & paragraf.
- SEO: meta title, meta description, canonical, struktur heading (H1/H2).
- Image handling: download gambar, buat caption, periksa lisensi.
- Entity validation: validasi nama, angka, tanggal dengan cross-check ke sumber primer.
3.8 Publisher
Publikasikan ke CMS via API. Prinsip:
- Kirim sebagai
draft— wajib moderasi manusia. - Sertakan metadata lengkap & link ke sumber (atribusi).
- Catat post_id dan simpan audit log untuk rollback & traceability.
Contoh payload WordPress:
{
"title": "Judul Artikel",
"content": "Isi ...
",
"status": "draft",
"meta": {"sources": ["https://...","https://..."]}
}
3.9 Monitoring, Observability & DLQ
Komponen observability:
- Structured logs (JSON) per job: source_url, job_id, status, timings.
- Metrics: jobs/sec, errors, queue length (Prometheus).
- DLQ (dead-letter queue) untuk job gagal yang perlu analisa manual.
- Retention raw HTML untuk audit (misal 90 hari atau sesuai kebijakan).
4. Contoh kode penting (POC kecil)
4.1 Fetch & robots (Python)
import requests, time
import urllib.robotparser as robotparser
from urllib.parse import urlparse
UA = "ACG-Bot/1.0 (+https://domainmu.id/contact)"
def obeys_robots(url):
parsed = urlparse(url)
rp = robotparser.RobotFileParser()
rp.set_url(f"{parsed.scheme}://{parsed.netloc}/robots.txt")
rp.read()
return rp.can_fetch(UA, url)
def fetch_url(url):
if not obeys_robots(url):
raise PermissionError("robots disallow")
r = requests.get(url, headers={"User-Agent":UA}, timeout=15)
time.sleep(1.0) # politeness
return r.status_code, r.text
4.2 Ekstraksi dengan readability
from readability import Document
from bs4 import BeautifulSoup
def extract_main(html):
doc = Document(html)
title = doc.short_title()
summary = doc.summary() # html
soup = BeautifulSoup(summary, "html.parser")
paragraphs = [p.get_text(strip=True) for p in soup.find_all("p")]
text = "\n\n".join(p for p in paragraphs if p)
return {"title":title, "text": text}
4.3 MinHash + LSH (dedupe)
# pip install datasketch
from datasketch import MinHash, MinHashLSH
def shingles(text, k=5):
w = text.split()
return [" ".join(w[i:i+k]) for i in range(max(0,len(w)-k+1))]
def minhash(text, num_perm=128):
m = MinHash(num_perm=num_perm)
for s in set(shingles(text)):
m.update(s.encode('utf8'))
return m
lsh = MinHashLSH(threshold=0.8, num_perm=128)
# untuk setiap dokumen:
# m = minhash(doc_text)
# lsh.insert(doc_id, m)
# untuk cek:
# q = minhash(new_text)
# res = lsh.query(q)
4.4 Placeholder LLM call
import requests, os
LLM_API_URL = os.getenv("LLM_API_URL","")
LLM_API_KEY = os.getenv("LLM_API_KEY","")
def call_llm(prompt):
if not LLM_API_URL:
return "[LLM MISSING] - gunakan provider LLM"
headers = {"Authorization":f"Bearer {LLM_API_KEY}"}
r = requests.post(LLM_API_URL, json={"prompt":prompt}, headers=headers, timeout=30)
r.raise_for_status()
return r.json().get("text","")
5. Orkestrasi & deployment (Docker + Celery minimal)
Arsitektur produksi biasanya punya komponen: web API (scheduler), queue broker (Redis/RabbitMQ), worker (Celery), database (Postgres), object storage (S3), dan optional headless (Playwright) container.
Contoh docker-compose (skeleton)
version: '3.8'
services:
redis:
image: redis:7
restart: unless-stopped
postgres:
image: postgres:15
environment:
POSTGRES_DB: acg
POSTGRES_USER: acg
POSTGRES_PASSWORD: acgpass
web:
build: .
ports: ["8000:8000"]
depends_on: ["redis","postgres"]
environment:
- DATABASE_URL=postgresql://acg:acgpass@postgres/acg
- CELERY_BROKER_URL=redis://redis:6379/0
worker:
build: .
command: celery -A tasks worker --loglevel=info
depends_on: ["redis","postgres"]
Jaga agar job fetch / extract / generate dipisah menjadi task terpisah sehingga retries dan DLQ mudah dikelola.
6. Testing, QA & Validasi kualitas
Checklist minimal sebelum publish:
- Unit test untuk extractor (pastikan teks diekstrak sesuai target).
- Integration test pipeline fetch → extract → index → generate (mock LLM).
- Plagiarism threshold — tentukan ambang batas (contoh: jika > 0.7 similarity ke sumber tertentu, flag untuk review).
- Editorial review — semua artikel LLM masuk ke queue editor (tidak auto-publish).
- UAT dengan sample data: 50-200 artikel untuk mengukur false positive/negative dedupe.
Automasi QA: lakukan sampling, hitung readability, cek entitas penting (person/place/date) terhadap sumber primer.
7. Scaling & performa
Pertimbangan saat beban meningkat:
- Cache hasil fetch untuk domain yang sama (ETag / If-Modified-Since).
- Load-balance workers dan shard LSH index (LSH mendukung sharding).
- Gunakan CDN/edge caching untuk assets gambar.
- Rate-limit global & per-domain agar tidak diblokir.
Data flow dan bottleneck
| Komponen | Bottleneck umum | Solusi |
|---|---|---|
| Fetcher | IP rate limit, DNS | Proxy pool, backoff, cache |
| Extractor | CPU-bound (parsing) | Scale workers, gunakan C-extension |
| LLM | Latency & cost | Batching prompt, local model, cache hasil |
| Index/LSH | Memory | Shard index, disk-backed index |
8. Troubleshooting umum
- Terblokir (403/429): hentikan sementara, gunakan exponential backoff, cek robots.txt dan contact admin situs jika perlu.
- Konten JS-heavy tidak muncul: gunakan Playwright untuk snapshot HTML yang lengkap.
- Duplikasi tak terdeteksi: tingkatkan jumlah shingles atau turunkan threshold LSH.
- LLM menghasilkan fakta salah: tambahkan post-processing verifikasi numerik / cross-check ke sumber primer.
9. Etika, legal, audit trail
Jangan malas bacanya. Ini bagian yang sering diabaikan, lalu ketahuan oleh pemilik konten atau search engine.
- Hak cipta — jangan publish ulang konten berbayar atau berlisensi tanpa izin.
- Atribusi — selalu cantumkan sumber sebagai daftar referensi di akhir draf.
- Data retention — simpan raw_html untuk audit setidaknya 30-90 hari.
- Transparansi — internal: tandai dokumen yang dihasilkan AI agar editor paham prosesnya.
- Ketentuan lokal — periksa peraturan setempat terkait reproduksi konten dan personal data.
Praktik terbaik: gunakan ACG sebagai tool riset dan draft. Keputusan final dan publikasi harus dipegang tim editorial manusia.
10. Appendix — dependency & runbook
Dependencies (pip)
pip install requests beautifulsoup4 readability-lxml datasketch python-dotenv playwright
playwright install
# optional
pip install trafilatura newspaper3k
Runbook singkat
- Clone repo POC.
- Set env vars:
LLM_API_URL,LLM_API_KEY,WP_URL,WP_USER,WP_APP_PASS. - Jalankan docker-compose:
docker-compose up -d. - Start worker:
celery -A tasks worker --loglevel=info. - Monitoring: akses Prometheus/Grafana (jika dikonfigurasi).
Checklist sebelum publish
- Semua artikel LLM berstatus
draft. - Plagiarism score < batas (misal < 0.3) atau flagged.
- Semua sumber tercantum di metadata.
- Raw HTML tersimpan dan dapat diakses untuk audit.
Contoh Quick Test
# 1) Tambahkan seed URL (non-paywall)
# 2) Jalankan pipeline untuk 5-10 URL
# 3) Periksa draft hasil: teks, atribusi, plagiarism score
# 4) Jika lulus, editor review, lalu publish manual