Kursus
LLM lokal telah mencapai titik balik. Dengan jendela konteks panjang Gemma 4, dukungan multimodal bawaan, dan kemudahan akses Ollama, kini praktis untuk menjalankan asisten coding agen yang mumpuni sepenuhnya di mesin Anda sendiri.
Dalam tutorial ini, saya akan menunjukkan cara membangun asisten coding menggunakan Gemma 4 melalui Ollama, dengan antarmuka Gradio. Aplikasi ini memiliki tata letak panel terpisah dengan editor kode langsung di kiri dan panel chat agen di kanan. Anda dapat mengunggah gambar atau file kode sebagai konteks, mengaktifkan penggunaan tool agar model dapat menjalankan dan memvalidasi kode, serta menyalakan mode thinking untuk masalah yang lebih sulit.
Di akhir tutorial ini, Anda akan memiliki aplikasi lokal yang dapat:
- Menulis, menjelaskan, dan men-debug kode dalam 15+ bahasa
- Menjalankan kode Python dalam subprocess terisolasi dan mengembalikan hasilnya
- Menerima gambar dan file teks sebagai konteks multimodal
- Melakukan streaming respons secara real time dari model Gemma 4 yang berjalan lokal
- Berjalan sebagai loop agen, memanggil tool dan menindaklanjuti berdasarkan hasilnya
Kode lengkap untuk tutorial ini tersedia di sini.
Apa itu Gemma 4?
Gemma 4 adalah keluarga model open-weights dari Google DeepMind, dirancang untuk deployment lokal dan riset. Model ini meneruskan lini Gemma dengan peningkatan pada kemampuan mengikuti instruksi, jendela konteks lebih panjang, serta penanganan input multimodal bawaan dan dibangun dari infrastruktur riset yang sama dengan Gemini 3.

Gambar: Performa model vs Ukuran (sumber: blog Gemma 4)
Model seperti Gemma-4-26B(MOE) dan Gemma-4-31B mencapai skor Elo yang sebanding dengan model yang jauh lebih besar, menunjukkan performa-per-parameter yang kuat. Model 31B saat ini menempati peringkat ke-3 model open di dunia pada papan peringkat teks Arena AI, dan model 26B berada di posisi ke-6. Ini membuat Gemma 4 sangat cocok untuk deployment lokal dan lingkungan dengan sumber daya terbatas tanpa mengorbankan kapabilitas.
Keluarga model Gemma 4
Gemma 4 dirilis dalam empat ukuran fleksibel termasuk Effective 2B (E2B), Effective 4B (E4B), 26B Mixture of Experts (MoE), dan model Dense 31B. Keluarga ini terbagi menjadi dua tingkatan berbeda berdasarkan target deployment:
|
Model |
Arsitektur |
Total Parameter |
Parameter Aktif/Efektif |
Panjang Konteks |
Modalitas |
|
Gemma-4-31B |
Dense Transformer |
31B |
31B |
256K token |
Teks, Visual, Video |
|
Gemma-4-26B-A4B |
MoE (128 Expert) |
26B |
3,8B aktif |
256K token |
Teks, Visual, Video |
|
Gemma-4-E4B |
Dense Transformer |
7,9B (dengan embedding) |
4,5B efektif |
128K token |
Teks, Audio, Visual, Video |
|
Gemma-4-E2B |
Dense Transformer |
5,1B (dengan embedding) |
2,3B efektif |
128K token |
Teks, Audio, Visual, Video |
Mari menelusuri lebih dalam setiap varian:

Gambar: Panduan Visual Gemma 4 (sumber)
- 31B Dense adalah model andalan yang dioptimalkan untuk deployment pusat data dan beban kerja penalaran kompleks. Mendukung jendela konteks 256K token dengan sliding window 1024-token untuk pemrosesan konteks panjang yang efisien.
- 26B-A4B (MoE) adalah model MoE pertama Gemma yang merutekan token melalui 128 expert sambil hanya menjaga 3,8B parameter aktif per forward pass. Ini memberi kualitas mendekati 31B dengan sebagian kecil biaya komputasi per token sehingga cocok untuk serving throughput tinggi.
- E4B dan E2B adalah tier on-device dan mobile. Berbeda dengan varian yang lebih besar, keduanya mencakup input audio bawaan untuk pengenalan ucapan selain visi dan video, menjadikannya model paling kapabel secara multimodal di keluarga ini untuk deployment edge.
Keempat model tersedia di bawah lisensi Apache 2.0 dan dapat dideploy secara lokal melalui Ollama, vLLM, llama.cpp, atau Unsloth.
Untuk tugas pemrograman, Gemma 4 unggul dalam:
- Menulis kode lengkap dan terstruktur beserta penjelasan
- Melakukan penalaran atas codebase yang ada saat diberikan sebagai konteks
- Penggunaan tool dan alur kerja agen saat dipasangkan dengan lapisan orkestrasi
- Menangani gambar bersamaan dengan kode (mis., membaca tangkapan layar UI dan menghasilkan HTML yang sesuai)
Dalam tutorial ini, kita menggunakan varian gemma4:e4b (9,6GB) melalui Ollama yang merupakan versi terkuantisasi dan cocok untuk inferensi lokal pada perangkat konsumen.
Menjalankan Gemma 4 melalui Ollama
Ollama menangani pengunduhan model, kuantisasi, serving, dan menyediakan HTTP API yang kompatibel dengan OpenAI. Untuk tutorial ini, Ollama bertindak sebagai backend inferensi, dan aplikasi kita berkomunikasi dengannya melalui localhost:11434.
Untuk menarik dan menjalankan model:
curl -fsSL https://ollama.com/install.sh | sh
ollama pull gemma4:e4b
ollama serve
Perintah di atas menginstal Ollama secara lokal menggunakan skrip instalasi resmi. Kita kemudian mengunduh varian model gemma 4:e4b secara lokal untuk inferensi on device. Terakhir, ollama serve menjalankan server Ollama, memungkinkan aplikasi kita mengirim permintaan ke model.
Setelah server berjalan, aplikasi Gradio akan terhubung secara otomatis.
Demo Gemma 4: Bangun Asisten Coding dengan Ollama
Di bagian ini, kita akan membangun asisten kode selangkah demi selangkah. Secara garis besar, berikut yang dilakukan aplikasi:
-
Menerima pesan berbahasa natural di panel chat, dengan lampiran gambar atau file opsional
-
Menyuntikkan kode editor saat ini sebagai konteks untuk model
-
Mengirim permintaan streaming ke Gemma 4 melalui endpoint
/api/chatOllama -
Secara opsional memanggil tool (eksekusi kode, evaluasi matematika) dalam loop agen
-
Mendorong blok kode yang diekstrak dari respons ke editor langsung
Mari kita bangun selangkah demi selangkah.

Langkah 1: Instal dependensi
Aplikasi memerlukan beberapa pustaka untuk UI, penanganan gambar, dan komunikasi HTTP. Karena model berjalan secara lokal melalui Ollama, tidak ada dependensi SDK cloud.
pip install gradio requests pillow
Dalam proyek ini, kita akan menggunakan:
-
gradiountuk editor panel terpisah dan UI chat -
requestsuntuk berkomunikasi dengan HTTP API Ollama -
pillowuntuk dukungan penanganan gambar di UI
Ini menjaga lingkungan tetap ringan dan bekerja di mesin apa pun yang menjalankan Ollama, termasuk macOS dengan Apple Silicon, Linux, dan Windows melalui WSL.
Langkah 2: Konfigurasi dan konstanta
Sebelum menulis logika, kita mendefinisikan impor, nama model, URL dasar Ollama, bahasa yang didukung, dan system prompt bawaan. Konstanta ini mengendalikan perilaku seluruh aplikasi.
import base64
import json
import math
import os
import re
import subprocess
import tempfile
from pathlib import Path
import gradio as gr
import requests
OLLAMA_BASE = "http://localhost:11434"
MODEL = "gemma4:e4b"
LANGUAGES = [
"python", "javascript", "typescript", "bash", "sql",
"rust", "go", "java", "c", "cpp", "html", "css",
"json", "yaml", "markdown", "plaintext",
]
TEXT_EXTS = {
".py", ".js", ".ts", ".jsx", ".tsx", ".html", ".css",
".json", ".yaml", ".yml", ".toml", ".md", ".txt",
".csv", ".sql", ".sh", ".bash", ".rs", ".go",
".java", ".c", ".cpp", ".h", ".hpp", ".rb", ".php",
}
IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp"}
DEFAULT_SYSTEM = """\
You are an expert coding assistant. When you write code:
- Always wrap it in a markdown code block with the language tag
- Write complete, working code — not fragments
- Briefly explain what the code does
You have a code-runner tool. Use it to validate logic when helpful.\
"""
Set TEXT_EXTS dan IMAGE_EXTS menggerakkan dua perilaku lampiran berbeda. File teks/kode dibaca dan disuntikkan sebagai konteks ke dalam prompt, sementara file gambar di-base64-encoded dan dikirim dalam field images untuk kemampuan visi Gemma 4.
System prompt DEFAULT_SYSTEM membentuk gaya generasi kode model, secara khusus meminta blok kode lengkap dengan tag bahasa, karena aplikasi mem-parsing ini untuk mendorong kode ke editor.
Langkah 3: Definisikan tool agen
Asisten dapat beroperasi dalam mode agen di mana ia memanggil tool selama inferensi. Kita mendefinisikan dua tool dalam skema function-calling standar yang didukung Ollama:
TOOLS = [
{
"type": "function",
"function": {
"name": "run_code",
"description": (
"Execute Python code in a sandboxed subprocess and return "
"stdout + stderr. Use this to validate, test, or demonstrate code."
),
"parameters": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Python code to run (max ~50 lines, 5 s timeout).",
}
},
"required": ["code"],
},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate a mathematical expression precisely.",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Python-compatible math expression, e.g. 'math.sqrt(2) * 100'",
}
},
"required": ["expression"],
},
},
},
]
Cuplikan kode di atas mendefinisikan tool run_code, yang mengeksekusi Python dalam file sementara melalui subprocess dengan batas waktu 5 detik dan menangkap stdout serta stderr. Ini memungkinkan model memvalidasi logika, menjalankan contoh, atau menghasilkan output langsung, bukan hanya teks statis.
Sementara itu, tool calculate mengevaluasi ekspresi matematika dalam namespace terbatas menggunakan hanya modul math dan built-in yang aman. Ini menangani kasus ketika model memerlukan hasil numerik presisi tanpa menyalakan proses Python penuh.
Langkah 4: Eksekusi tool
Lapisan eksekusi memetakan nama tool ke implementasinya. Ini dipanggil selama loop agen kapan pun model mengembalikan blok tool_calls.
def _run_python(code: str) -> str:
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code)
tmp = f.name
try:
r = subprocess.run(
["python3", tmp],
capture_output=True, text=True, timeout=5,
)
out = (r.stdout + r.stderr).strip()
return out[:3000] if out else "(no output)"
except subprocess.TimeoutExpired:
return "⏱ Timed out (>5 s)"
except Exception as e:
return f"Error: {e}"
finally:
os.unlink(tmp)
def _calculate(expr: str) -> str:
ns = {k: getattr(math, k) for k in dir(math) if not k.startswith("_")}
ns.update({"abs": abs, "round": round})
try:
return str(eval(expr, {"__builtins__": {}}, ns)) # noqa: S307
except Exception as e:
return f"Error: {e}"
def execute_tool(name: str, args: dict) -> str:
if name == "run_code":
return _run_python(args.get("code", ""))
if name == "calculate":
return _calculate(args.get("expression", ""))
return f"Unknown tool: {name}"
Beberapa pilihan desain keamanan yang patut dicatat dari tumpukan eksekusi tool adalah:
-
fungsi _run_python()menulis ke file sementara dan membersihkannya, mencegah file tersisa bahkan saat terjadi error -
Output dibatasi hingga 3.000 karakter untuk menghindari membanjiri konteks dengan cetakan yang masif
-
Fungsi
_calculate()menggunakaneval()dengan__builtins__disetel ke dict kosong, mencegah akses ke built-in Python di luar fungsimathyang secara eksplisit diizinkan -
Timeout adalah jendela keras 5 detik, cukup lama untuk sebagian besar tugas validasi, cukup pendek untuk mencegah proses tak terkendali
Langkah 5: Utilitas pembantu
Sebelum membangun logika chat inti, kita memerlukan beberapa fungsi utilitas yang menangani path file, encoding gambar, dan ekstraksi kode. Fungsi pembantu ini dipanggil di seluruh pipeline streaming.
def encode_image(path: str) -> str | None:
if not path:
return None
try:
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode()
except Exception:
return None
def file_as_context(path: str) -> str | None:
if not path:
return None
p = Path(path)
if p.suffix.lower() not in TEXT_EXTS:
return None
try:
content = p.read_text(encoding="utf-8", errors="replace")[:8000]
lang = p.suffix.lstrip(".")
return f"\n\n**Attached file — {p.name}:**\n```{lang}\n{content}\n```"
except Exception:
return None
def resolve_gradio_path(val) -> str | None:
if val is None:
return None
if isinstance(val, Path):
s = str(val)
return s if s.strip() else None
if isinstance(val, str):
s = val.strip()
return s if s else None
if isinstance(val, dict):
p = val.get("path")
if isinstance(p, str) and p.strip():
return p.strip()
nested = val.get("file")
if isinstance(nested, dict):
np = nested.get("path")
if isinstance(np, str) and np.strip():
return np.strip()
return None
name = getattr(val, "name", None)
if isinstance(name, str) and name.strip():
return name.strip()
return None
def is_image_path(path: str | None) -> bool:
return bool(path) and Path(path).suffix.lower() in IMAGE_EXTS
def extract_last_code_block(text: str) -> tuple[str | None, str]:
blocks = re.findall(r"```(\w*)\n(.*?)```", text, re.DOTALL)
if blocks:
lang, code = blocks[-1]
return code.strip(), lang.strip() or "python"
return None, "python"
def ollama_ok() -> bool:
try:
requests.get(f"{OLLAMA_BASE}/", timeout=2)
return True
except Exception:
return False
def _gradio_content_to_text(content) -> str:
if content is None:
return ""
if isinstance(content, str):
return content
if isinstance(content, list):
parts: list[str] = []
for block in content:
if isinstance(block, dict):
if block.get("type") == "text":
parts.append(str(block.get("text", "")))
elif "text" in block:
parts.append(str(block["text"]))
elif isinstance(block, str):
parts.append(block)
return "".join(parts)
return str(content)
def _append_chat_turn(
history: list | None, user_text: str, assistant_text: str
) -> list:
base = list(history) if history else []
return base + [
{"role": "user", "content": user_text},
{"role": "assistant", "content": assistant_text},
]
def run_code_btn(code: str) -> str:
if not code.strip():
return "Nothing to run."
result = _run_python(code)
return result
Utilitas ini memungkinkan penanganan input multimodal dan output terstruktur dengan mulus:
-
fungsi encode_image(): Fungsi ini mengonversi file gambar menjadi stringbase64agar dapat diteruskan ke fieldimagesOllama. Ini memungkinkan Gemma memproses gambar bersama teks untuk penalaran multimodal. -
fungsi file_as_context(): Fungsi ini membaca file teks/kode dan menyuntikkannya langsung ke prompt sebagai blok markdown terformat. Ini menghindari kebutuhan sistem retrieval terpisah karena semuanya diberikan inline sebagai konteks. -
fungsi resolve_gradio_path(): Fungsi ini menormalkan berbagai format file yang dikembalikan Gradio menjadi path file yang dapat digunakan. Ini mencegah bug kasus tepi dalam penanganan file. -
fungsi is_image_path(): Ini adalah pemeriksaan cepat apakah file tertentu merupakan gambar atau bukan. Ini membantu merutekan input gambar dengan benar. -
fungsi extract_last_code_block(): Berikutnya, kita mengekstrak blok kode terakhir dari respons model. -
fungsi ollama_ok(): Kita memverifikasi bahwa server Ollama lokal berjalan sebelum membuat permintaan. -
fungsi _gradio_content_to_text()dan_append_chat_turn(): Kita meratakan format pesan terstruktur Gradio menjadi teks biasa untuk pemrosesan yang konsisten dan mempertahankan riwayat chat. -
fungsi run_code_btn(): Terakhir, kita mengeksekusi kode yang dihasilkan ketika pengguna menekan tombol Run code di UI menggunakan fungsi ini.
Kini setelah kita memiliki utilitas pembantu, kita dapat mengimplementasikan fungsi inti chat().
Langkah 6: Generator streaming chat inti
Fungsi ini menggerakkan seluruh loop interaksi, dari menyiapkan input hingga streaming respons dan menangani eksekusi tool.
Secara garis besar, fungsi chat():
- membangun riwayat percakapan
- memperkaya input pengguna saat ini dengan konteks (kode, file, gambar)
- mengirim permintaan streaming ke Ollama
- secara opsional mengeksekusi tool (mode agen)
- menghasilkan respons parsial untuk pembaruan UI waktu nyata
def chat(
message, history, image_path, file_path,
editor_code, language, system_prompt,
agentic, thinking, temperature,
):
Fungsi ini mengikuti urutan langkah demi langkah berikut:
Langkah 1: Bangun riwayat pesan
Kita mulai dengan mengonversi giliran chatbot sebelumnya ke format pesan Ollama. System prompt ditambahkan di awal untuk membimbing perilaku model yang memastikan model memiliki konteks percakapan penuh sebelum menghasilkan respons.
for h in history or []:
if not isinstance(h, dict):
continue
role = h.get("role")
if role not in ("user", "assistant"):
continue
messages.append(
{"role": role, "content": _gradio_content_to_text(h.get("content"))}
)
content = message
Langkah 2: Susun giliran pengguna
Selanjutnya, kita membangun pesan pengguna saat ini dengan menyuntikkan konteks tambahan. Jika pengguna sedang bekerja dengan kode, kita menambahkan isi editor:
if editor_code.strip() and editor_code.strip() != STARTER_CODE.strip():
content += (
f"\n\n**Current code in editor ({language}):**\n"
f"```{language}\n{editor_code}\n```"
)
Demikian pula, file yang diunggah ditambahkan sebagai konteks inline dan gambar di-encode base64 dan diteruskan melalui field images. Ini memungkinkan model melakukan penalaran atas teks, kode, dan gambar secara bersamaan.
Langkah 3: Kirim permintaan ke Ollama
Payload mencakup nama model, riwayat pesan, flag streaming, dan opsi sebagai berikut:
payload = {
"model": MODEL,
"messages": messages,
"stream": True,
"options": options,
}
if agentic:
payload["tools"] = TOOLS
Jika mode thinking diaktifkan, options["think"] = True disetel, yang mengaktifkan penalaran chain-of-thought yang diperluas pada model yang didukung.
Langkah 4: Tangani panggilan tool (mode agen)
Jika mode agen diaktifkan, model dapat mengembalikan panggilan tool alih-alih teks. Ini terjadi ketika setiap tool dieksekusi dan hasilnya ditambahkan ke percakapan. Lalu, permintaan tindak lanjut dikirim untuk melanjutkan generasi.
if agentic and msg.get("tool_calls"):
for tc in msg["tool_calls"]:
fn_name = tc["function"]["name"]
fn_args = tc["function"]["arguments"]
result = execute_tool(fn_name, fn_args)
if fn_name == "run_code" and fn_args.get("code"):
new_code = fn_args["code"]
messages.append(msg)
messages.append({"role": "tool", "content": result})
resp2 = requests.post(f"{OLLAMA_BASE}/api/chat", ...)
Ini menciptakan loop agen satu giliran di mana model memanggil tool, melihat hasilnya, lalu melanjutkan menghasilkan respons akhir. Log panggilan tool ditambahkan ke chat untuk pengguna.
Langkah 5: Ekstrak kode dan perbarui editor
Terakhir, setelah streaming selesai, kita mengekstrak blok kode terakhir dari respons:
extracted, _ = extract_last_code_block(full_response)
if extracted:
new_code = extracted
Secara keseluruhan, model menghasilkan kode, editor diperbarui secara otomatis dan tidak diperlukan copy-paste manual.
Langkah 7: Tata letak UI Gradio
Dengan logika inti sudah siap, kini kita merancang antarmuka pengguna menggunakan Gradio. Tujuannya adalah membuat tata letak yang mendukung alur kerja pemrograman dan interaksi berbantuan AI secara berdampingan.
Kita menggunakan gr.Blocks untuk mendefinisikan tata letak dua kolom:
- panel kiri berfokus pada pengeditan dan eksekusi kode
- panel kanan menangani chat, lampiran, dan kontrol model
with gr.Blocks(title="Gemma 4 · Code Assistant") as demo:
with gr.Row(equal_height=False):
# LEFT: Code Editor
with gr.Column(scale=11):
with gr.Row():
lang_sel = gr.Dropdown(choices=LANGUAGES, value="python", label="Language")
run_btn = gr.Button("Run Code", elem_classes=["run-btn"])
clear_ed = gr.Button("Clear")
code_editor = gr.Code(
value=STARTER_CODE,
language="python",
label="Editor",
lines=24,
interactive=True,
)
run_output = gr.Textbox(
label="Output",
lines=6,
interactive=False,
elem_id="run-output",
)
# RIGHT: Chat
with gr.Column(scale=9):
chatbot = gr.Chatbot(value=[], elem_id="chatbot", height=430)
with gr.Row():
image_upload = gr.Image(label="Image (vision)", type="filepath")
file_upload = gr.File(label="Code / text file")
with gr.Row():
msg_input = gr.Textbox(placeholder="Ask the agent...", scale=6)
send_btn = gr.Button("Send", variant="primary")
with gr.Row():
agentic_cb = gr.Checkbox(label="Enable Agentic", value=True)
thinking_cb = gr.Checkbox(label="Enable Thinking", value=False)
clear_chat = gr.Button("Clear chat")
with gr.Accordion("Settings", open=False):
sys_prompt = gr.Textbox(value=DEFAULT_SYSTEM, label="System prompt")
temperature = gr.Slider(minimum=0.0, maximum=2.0, value=0.7)
Mari pahami bagaimana UI bekerja bersama semua komponen yang telah kita bahas di atas:
- Ruang kerja kode: Panel kiri mencakup pemilih bahasa, editor kode, serta kontrol run/clear. Output eksekusi ditampilkan dalam textbox yang disorot.
- Chat dan interaksi: Panel kanan berisi chatbot untuk percakapan, kotak input untuk kueri, serta dukungan lampiran seperti gambar untuk penalaran multimodal dan file untuk injeksi konteks. Toggle tambahan mengaktifkan penggunaan tool agen dan mode penalaran opsional.
- Tata letak dan kegunaan: Antarmuka menggunakan tata letak dua kolom dengan pembagian 55/45, memprioritaskan editor kode sambil menjaga chat tetap mudah diakses. Keseimbangan ini memastikan alur kerja mulus antara pemrograman, bantuan AI, dan eksekusi.
Langkah 8: Pengkabelan event
Event kirim dan submit berbagi input dan output yang sama, sehingga baik klik tombol maupun menekan Enter di kotak teks memicu perilaku identik:
_inputs = [
msg_input, chatbot, image_upload, file_upload,
code_editor, lang_sel,
sys_prompt, agentic_cb, thinking_cb, temperature,
]
_outputs = [chatbot, code_editor, msg_input]
send_btn.click(fn=respond, inputs=_inputs, outputs=_outputs)
msg_input.submit(fn=respond, inputs=_inputs, outputs=_outputs)
Pembungkus respond() di sini mengonversi objek path file Gradio menjadi string path file biasa sebelum meneruskannya ke generator streaming. Kemudian menghasilkan (history, code, "") pada setiap chunk sehingga string kosong mengosongkan input pesan pada yield pertama.
Langkah 9: Tema dan CSS
Untuk meningkatkan kegunaan dan konsistensi visual, kita menambahkan tema khusus dan lapisan CSS di atas gaya default Gradio. Ini memberi aplikasi tampilan yang rapi terinspirasi GitHub dengan dukungan mode terang dan gelap. Langkah ini sepenuhnya opsional.
Kita mulai dengan mendefinisikan tema dasar menggunakan gr.themes.Base:
THEME = gr.themes.Base(
primary_hue = "blue",
secondary_hue = "slate",
neutral_hue = "slate",
font = [gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
font_mono = [gr.themes.GoogleFont("JetBrains Mono"), "monospace"],
)
Ini menetapkan palet warna yang konsisten dan tipografi UI yang bersih (Inter) dengan font monospace ramah developer (JetBrains Mono). Selanjutnya, kita menimpa variabel CSS kunci untuk mengontrol tampilan aplikasi:
gradio-app {
--body-background-fill: #f0f3f7;
--block-background-fill: #ffffff;
--body-text-color: #1f2328;
--button-primary-background-fill: #1a7f37;
}
body.dark gradio-app {
--body-background-fill: #0d1117;
--block-background-fill: #1c2128;
--body-text-color: #e6edf3;
--button-primary-background-fill: #238636;
}
Di sini, blok pertama mendefinisikan tema terang, sementara blok kedua menimpa nilai untuk mode gelap. Meski opsional, penambahan tema khusus dan CSS secara signifikan meningkatkan pengalaman pengguna.
Dengan UI kini tersusun dan ditata sepenuhnya, kita siap ke langkah terakhir yaitu meluncurkan aplikasi.
Langkah 10: Peluncuran
Terakhir, aplikasi diluncurkan dengan:
if __name__ == "__main__":
print(f"Model : {MODEL}")
print(f"Ollama : {OLLAMA_BASE}")
if not ollama_ok():
print("Ollama not detected — run ollama serve before chatting")
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
theme=THEME,
css=CSS,
)
Di sini, server_name="0.0.0.0" membuat aplikasi dapat diakses pada jaringan lokal Anda dan menyetel share=False menjaga semuanya tetap lokal. Pemeriksaan ollama_ok() melakukan GET cepat ke localhost:11434 dan memberi peringatan saat startup jika Ollama tidak berjalan, alih-alih memunculkan error hanya saat pengguna pertama kali mengirim pesan.
Terakhir, buka http://localhost:7860 setelah aplikasi berjalan.
Berikut alur eksekusi lengkap dari perspektif pengguna:

-
Pengguna mengetik pesan di panel chat, opsional mengunggah gambar atau file kode dan menyalakan mode agen/thinking
-
Saat submit, fungsi
respond()membangun permintaan lengkap ke Ollama, termasuk kode editor saat ini dan lampiran apa pun sebagai konteks -
Permintaan melakukan streaming token demi token dan respons parsial dihasilkan ke chatbot Gradio untuk rendering langsung
-
Jika mode agen aktif dan model memutuskan memanggil tool, tool berjalan secara lokal, hasil ditambahkan ke percakapan, dan permintaan lanjutan melakukan streaming jawaban akhir
-
Setelah streaming selesai, blok kode berpagar terakhir dalam respons diekstrak dan otomatis dimasukkan ke editor
Kesimpulan
Dalam tutorial ini, kita membangun asisten coding AI sepenuhnya lokal menggunakan Gemma 4, Ollama, dan Gradio. Aplikasi mendukung input multimodal, penggunaan tool nyata, respons streaming, dan editor kode langsung, semuanya berjalan di mesin Anda sendiri tanpa API eksternal.
Pendekatan konteks-utuh dengan menyuntikkan kode editor langsung ke setiap prompt lebih sederhana daripada RAG untuk alur kerja satu file dan sangat efektif untuk tugas seperti menjelaskan, merapikan, atau memperluas kode yang sedang Anda sunting.
Dari sini, Anda dapat memperluas proyek ke beberapa arah:
- Tambahkan panel pohon file untuk mendukung proyek multi-file dan sisipkan file yang dipilih sebagai konteks
- Tambahkan dukungan untuk model Ollama tambahan melalui dropdown, memungkinkan pengguna beralih antara Gemma 4, Llama 3, dan lainnya
- Persistenkan riwayat percakapan ke disk agar sesi bertahan saat aplikasi di-restart
Saya adalah Google Developers Expert di ML (Gen AI), Kaggle 3x Expert, dan Women Techmakers Ambassador dengan pengalaman lebih dari 3 tahun di bidang teknologi. Saya ikut mendirikan startup health-tech pada 2020 dan sedang menempuh studi magister ilmu komputer di Georgia Tech dengan spesialisasi machine learning.
Gemma 4 FAQs
Apakah ini memerlukan GPU?
Tidak harus. Gemma 4 e4b adalah model terkuantisasi yang dapat berjalan di CPU, meskipun GPU akan sangat meningkatkan kecepatan inferensi.
Apa perbedaan antara mode agen dan chat reguler?
Dalam mode reguler, model hanya melakukan streaming teks. Dalam mode agen, model dapat memanggil run_code atau calculate, memeriksa hasilnya, dan mengintegrasikannya sebelum menyelesaikan respons.
Bagaimana editor memperbarui otomatis saat agen menulis kode?
Fungsi pembantu extract_last_code_block() memindai respons asisten untuk blok kode berpagar terakhir dan mendorongnya ke editor setelah streaming selesai. Inilah alasan system prompt menginstruksikan model agar selalu membungkus kode dalam blok berpagar dengan tag bahasa.
Apa yang terjadi jika Ollama tidak berjalan?
Pemeriksaan ollama_ok() menangkap ini saat startup dan pada setiap pengiriman chat. Jika Ollama tidak dapat dijangkau, chat mengembalikan pesan error terformat alih-alih crash.

