Kamu nggak harus kirim setiap prompt ke API pihak ketiga. Beberapa workload, kayak data sensitif, batch job volume tinggi, atau apapun yang harus jalan offline, lebih cocok dilayani model yang jalan di mesin kamu sendiri. Ollama adalah cara paling gampang untuk sampai ke situ. Dia membungkus llama.cpp di belakang REST API yang rapi, menyediakan binary installer untuk macOS, Linux, dan Windows, dan mengekspos endpoint yang kompatibel dengan OpenAI di http://localhost:11434/v1/chat/completions jadi tool yang udah ada jalan tanpa modifikasi.
Ini walkthrough hands-on. Di akhir, kamu punya Ollama yang jalan, model terkuantisasi yang udah di-pull, Modelfile kustom yang udah di-build, dan script kecil bergaya agent yang nggabungin chat, structured output, dan embeddings dari satu HTTP server.
Prasyarat
- Mesin dengan minimal 8 GB RAM (16 GB lebih nyaman untuk model kelas 7B)
- macOS, Linux, atau Windows (WSL2 jalan di Windows)
- Sekitar 5 GB disk kosong untuk satu model 7B
curldanjqbuat ngetes API
Cek halaman install resmi buat command paling baru sesuai OS kamu. Di Linux, one-liner-nya:
curl -fsSL https://ollama.com/install.sh | sh
Di macOS kamu bisa pake download dari website atau brew install ollama. Installer Windows-nya file .exe biasa dari halaman download.
Setelah install, verifikasi server-nya hidup:
ollama --version
systemctl status ollama # cuma Linux; di macOS app-nya yang launch server
curl http://localhost:11434/api/version
Command terakhir balikin sesuatu kayak {"version":"0.6.0"}. Kalau dapat connection refused, daemon-nya belum jalan. Di Linux dengan systemd, sudo systemctl start ollama. Di macOS, buka app Ollama sekali biar dia daftar sebagai background service.
Langkah 1: Pull Model
ollama pull ngunduh GGUF terkuantisasi dari registry Ollama. Library di ollama.com/library nunjukin apa aja yang tersedia. Untuk run pertama, llama3.2:3b default yang masuk akal — cukup kecil buat jalan di laptop, cukup gede buat berguna.
ollama pull llama3.2:3b
Ini ngunduh sekitar 2 GB. Sambil nunggu, pull juga model embedding — ukurannya kecil dan kamu bakal butuh satu buat kerja retrieval nanti.
ollama pull nomic-embed-text
ollama pull all-minilm
List yang lokal:
ollama list
NAME ID SIZE MODIFIED
llama3.2:3b a80c4f17acd5 2.0 GB 2 minutes ago
nomic-embed-text:latest 0a109f422b47 274 MB 2 minutes ago
all-minilm:latest 1b226e2802db 46 MB 2 minutes ago
Model-nya tetap loaded di memori selama 5 menit setelah request terakhir. Kamu bisa atur ini dengan parameter keep_alive, yang bakal saya bahas di bagian API.
Cek cepat dari CLI:
ollama run llama3.2:3b "Jelasin perbedaan TCP dan UDP dalam dua kalimat."
Command ollama run tanpa prompt nge-start REPL interaktif. Ketik /bye buat keluar.
Langkah 2: Panggil REST API
Semua yang ollama run lakukan sebenernya cuma wrapper tipis di atas HTTP. Dua endpoint yang kamu butuhin adalah POST /api/generate (prompt tunggal) dan POST /api/chat (multi-turn). Spek lengkapnya ada di github.com/ollama/ollama/blob/main/docs/api.md.
/api/generate buat one-shot completion:
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2:3b",
"prompt": "Why is the sky blue?",
"stream": false
}'
Kamu dapat satu objek JSON kalau stream false. Dengan stream: true (default), response-nya adalah stream JSON chunk per token, dipisah newline. Chunk terakhir punya "done": true dan termasuk field timing:
total_duration: wall clock untuk requestload_duration: waktu buat load model ke memoriprompt_eval_count: token di prompt kamueval_count: token yang di-generateeval_duration: waktu yang diabisin buat generate
Buat hitung token per detik, rumus dari docs-nya adalah eval_count / eval_duration * 10^9. Berguna pas kamu bandingin kuantisasi.
/api/chat yang kamu mau untuk percakapan. Array messages nerima role system, user, dan assistant:
curl http://localhost:11434/api/chat -d '{
"model": "llama3.2:3b",
"stream": false,
"messages": [
{"role": "system", "content": "You answer in exactly one sentence."},
{"role": "user", "content": "What is the capital of France?"}
]
}'
Kasih tools buat enable tool calling di model yang support. Model-nya balikin array tool_calls; kamu eksekusi fungsinya, terus kirim hasilnya balik sebagai message dengan role tool. Daftar model yang bisa tool calling searchable di ollama.com.
Langkah 3: Pake Endpoint yang Kompatibel OpenAI
Ini bagian yang bikin Ollama masuk ke stack yang udah ada. Set base_url ke http://localhost:11434/v1 dan kebanyakan client OpenAI langsung jalan. Nggak ada perubahan kode selain URL.
Python pakai library resmi openai:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama", # required sama client-nya, diabaikan Ollama
)
resp = client.chat.completions.create(
model="llama3.2:3b",
messages=[{"role": "user", "content": "Write a haiku about caching."}],
)
print(resp.choices[0].message.content)
Node.js:
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "http://localhost:11434/v1",
apiKey: "ollama",
});
const resp = await client.chat.completions.create({
model: "llama3.2:3b",
messages: [{ role: "user", content: "Write a haiku about caching." }],
});
console.log(resp.choices[0].message.content);
Pola yang sama jalan di SDK JS, Go, dan Rust. Apapun yang nunjuk ke api.openai.com bakal jalan kalau kamu arahin ke server lokal. Artinya LangChain, LlamaIndex, Hermes Agent, dan tool apapun yang ngomong OpenAI Chat Completions API bisa jalan pake model lokal dengan satu perubahan config.
Langkah 4: Dapetin Structured Output
Teks bebas-bebas oke buat chat, tapi kalau kamu bangun agent kamu butuh shape yang bisa diprediksi. Kasih JSON schema di field format dan model bakal generate output yang sesuai. Format schema-nya adalah JSON Schema 2020-12 standar.
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2:3b",
"prompt": "Extract the name, age, and city from: Anna is 31 and lives in Berlin.",
"stream": false,
"format": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"city": {"type": "string"}
},
"required": ["name", "age", "city"]
}
}'
Field response baliknya sebagai string JSON yang parse-nya bersih. Di Python kamu bisa round trip-nya dalam beberapa baris:
import json, urllib.request
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"city": {"type": "string"},
},
"required": ["name", "age", "city"],
}
req = urllib.request.Request(
"http://localhost:11434/api/generate",
data=json.dumps({
"model": "llama3.2:3b",
"prompt": "Extract: Anna is 31 and lives in Berlin.",
"stream": False,
"format": schema,
}).encode(),
headers={"Content-Type": "application/json"},
)
data = json.loads(urllib.request.urlopen(req).read())
print(json.loads(data["response"]))
# {'name': 'Anna', 'age': 31, 'city': 'Berlin'}
Structured output adalah bagian yang bikin model lokal beneran berguna buat pipeline. Skip post-processing pake regex.
Langkah 5: Generate Embeddings
Embeddings datang dari endpoint yang beda dan model yang beda. Jangan coba dapetin dari model chat — output-nya nggak sama dan dimensinya juga bukan yang kamu mau.
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": "Ollama runs large language models locally"
}'
Response-nya adalah list float vector, satu per string input. Buat embed banyak teks sekaligus, kirim array:
curl http://localhost:11434/api/embed -d '{
"model": "nomic-embed-text",
"input": ["first text", "second text", "third text"]
}'
nomic-embed-text ngehasilin vector 768 dimensi; all-minilm 384. Parameter dimensions disupport dan ngizinin kamu truncate, tapi cuma model tertentu yang ngehormatin. Pake model embed yang lebih kecil kalau kamu peduli sama biaya vector DB dan nggak butuh dimensi ekstra.
Parameter truncate (default true) otomatis motong input yang ngelebihin context window. Set ke false kalau kamu mau deteksi overflow sendiri daripada dapet truncation diam-diam.
Langkah 6: Build Modelfile Kustom
Modelfile adalah resep buat model yang dikustomisasi. Kamu mulai dari base, set parameter, dan inject system prompt. Begini cara dapet persona yang konsisten atau tone spesifik domain tanpa nulis ulang system message di setiap call.
# Modelfile
FROM llama3.2:3b
# Temperatur lebih rendah buat output yang lebih deterministik
PARAMETER temperature 0.2
# Perbesar context window kalau RAM kamu cukup
PARAMETER num_ctx 8192
# Stop sequence yang cocok sama chat template model base
PARAMETER stop "<|start_header_id|>"
PARAMETER stop "<|end_header_id|>"
PARAMETER stop "<|eot_id|>"
# System prompt persistent yang dibake ke model
SYSTEM You are a senior backend engineer who reviews pull requests. You respond in three short bullets. You never start with "Certainly" or "Great question". You flag missing tests, race conditions, and unclear naming. You do not flatter the author.
Simpan sebagai Modelfile (tanpa ekstensi) terus build:
ollama create pr-reviewer -f ./Modelfile
ollama run pr-reviewer "Review this PR title: 'Refactor user service'"
Buat liat apa yang udah dibake:
ollama show pr-reviewer --modelfile
Spek Modelfile juga support TEMPLATE buat chat template kustom, ADAPTER buat adapter LoRA, LICENSE buat teks legal, dan MESSAGE buat seed history percakapan. Buat 90% kerja kustomisasi, FROM, PARAMETER, dan SYSTEM udah cukup.
Langkah 7: Sambungin ke Agent Kecil
Ngegabungin semuanya — script yang nerima pertanyaan user, retrieve beberapa doc relevan pake kemiripan embedding, minta model chat jawab dengan sitasi, dan balikin hasilnya sebagai objek JSON terstruktur. Model embedding dan model chat boleh beda.
import json
import urllib.request
import numpy as np
OLLAMA = "http://localhost:11434"
DOCS = [
"Ollama runs llama.cpp under the hood and exposes a REST API.",
"The OpenAI-compatible endpoint lives at /v1/chat/completions.",
"Structured output is enabled by passing a JSON schema in the format field.",
"Modelfiles let you set parameters and a system prompt persistently.",
]
def post(path, body):
req = urllib.request.Request(
f"{OLLAMA}{path}",
data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"},
)
return json.loads(urllib.request.urlopen(req).read())
def embed(texts):
r = post("/api/embed", {"model": "nomic-embed-text", "input": texts})
return np.array(r["embeddings"])
def retrieve(query, k=2):
q = embed([query])[0]
docs = embed(DOCS)
scores = docs @ q / (np.linalg.norm(docs, axis=1) * np.linalg.norm(q))
top = np.argsort(scores)[::-1][:k]
return [DOCS[i] for i in top]
def answer(query):
context = "\n".join(f"- {d}" for d in retrieve(query))
prompt = f"Use the context to answer the question.\n\nContext:\n{context}\n\nQuestion: {query}"
schema = {
"type": "object",
"properties": {
"answer": {"type": "string"},
"sources": {"type": "array", "items": {"type": "string"}},
},
"required": ["answer", "sources"],
}
r = post("/api/generate", {
"model": "llama3.2:3b",
"prompt": prompt,
"stream": False,
"format": schema,
})
return json.loads(r["response"])
if __name__ == "__main__":
print(json.dumps(answer("How do I get structured output from Ollama?"), indent=2))
Jalanin pake python3 agent.py. Model milih dua doc yang paling relevan, nulis jawaban yang ngerujuk ke doc itu, dan balikin objek JSON dengan jawabannya dan list source. Nggak butuh framework — API-nya cukup kecil, lem-nya lebih pendek dari import-nya.
Tuning Performa
Tiga knob yang ngaruh ke throughput dan memori.
Kuantisasi. Tag di library Ollama encode level kuantisasinya. q4_K_M adalah default yang direkomendasiin — dekat sama kualitas full dengan ukuran yang jauh lebih kecil. q8_0 lebih tinggi kualitasnya, lebih gede. Full precision (f16) jarang worth it buat inferensi lokal. Kamu bisa liat yang lagi jalan pake ollama show llama3.2:3b dan liat kolom size.
Context window (num_ctx). Default-nya 2048 buat kebanyakan model. Context yang lebih gede makan lebih banyak RAM dan memperlambat generation, kadang drastis. Set serendah yang masih cukup. Kalau prompt plus output yang diharapkan muat di 1024 token, set num_ctx 1024.
Keep alive. Default-nya 5 menit. Kalau traffic kamu bursty, naikin biar model nggak reload di antara request:
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2:3b",
"prompt": "hi",
"stream": false,
"keep_alive": "30m"
}'
0 unload langsung. Nilai negatif didokumentasiin sebagai "keep forever" tapi kamu bakal kehabisan RAM.
Kapan Pake Ollama vs API Hosted
Ollama pilihan yang tepat kalau:
- Datanya nggak boleh keluar mesin karena alasan compliance
- Latency ke endpoint hosted ketinggian (voice on-device, sensor di edge)
- Kamu lagi batch processing dan biaya per token dari API hosted numpuk
- Kamu mau prototipe tanpa mikirin rate limit
API hosted pilihan yang tepat kalau:
- Model yang kamu butuhin jauh lebih besar dari hardware yang bisa jalanin (70B+ di laptop)
- Kamu butuh kualitas terbaik dan mau bayar buat model top
- Kamu udah bayar API-nya dan biaya marginal dari request lain hampir nol
Jalan tengah yang jujur: pake Ollama buat development dan CI, pindah ke endpoint hosted buat query produksi yang paling berat. Karena surface API-nya identik, ini perubahan satu baris di kebanyakan client.
Lanjut ke Mana
- Referensi API lengkap di
github.com/ollama/ollama/blob/main/docs/api.md— setiap parameter, setiap endpoint, setiap contoh. - Spek Modelfile di
github.com/ollama/ollama/blob/main/docs/modelfile.mdxbuat kustomisasi yang lebih dalam. - Post kompatibilitas OpenAI di
ollama.com/blog/openai-compatibilitybuat daftar field yang disupport dan gap yang diketahui. - Library model di
ollama.com/librarybuat liat apa aja yang tersedia, termasuk model vision, code, dan embedding.