← Kembali ke Blog

Jalankan Model Tool Calling Lokal buat Agent Kamu Pakai llama.cpp

Agent kamu butuh tool call. Modelnya malah jawab dengan paragraf ramah yang isinya JSON, atau lebih parah, cuma cerita soal cuaca padahal kode kamu sedang menunggu get_current_weather dalam bentuk terstruktur. Menjalankan LLM di lokal sekarang gampang. Yang bikin repot adalah bikin endpoint lokal yang konsisten mengembalikan tool call berformat OpenAI, di hardware yang kebetulan ada di depan kamu.

Panduan ini soal bagian kedua itu. Hasil akhirnya: satu instance llama-server yang bicara /v1/chat/completions dengan tools aktif, satu loop Python yang mengeksekusi tool call tersebut, dan cara mengukur apakah quant yang kamu pilih diam-diam bikin model lebih buruk dalam memanggil tool.

Yang perlu disiapkan

  • Docker, atau toolchain C++ (CMake dan compiler) kalau mau build dari source. Binary siap pakai untuk Linux, macOS, dan Windows juga tersedia di halaman releases.
  • Python 3.10 atau lebih baru, hanya kalau kamu mau convert bobot sendiri dari Hugging Face.
  • RAM atau VRAM yang cukup untuk bobot plus KV cache. Dokumentasi upstream kasih gambaran untuk Llama 3.1:
Model Ukuran asli (bf16) Ukuran quant (Q4_K_M)
8B 32,1 GB 4,9 GB
70B 280,9 GB 43,1 GB
405B 1.625,1 GB 249,1 GB
  • Ruang disk untuk file antara. Proses convert menulis GGUF presisi penuh sebelum di-quant, jadi kamu butuh ruang untuk salinan itu plus hasil akhirnya.

Soal KV cache, memory tumbuh mengikuti -c (ukuran context) dan -np (slot paralel). Flag -ctk dan -ctv membolehkan cache disimpan dengan presisi lebih rendah dari default f16. Build terbaru juga mengaktifkan --fit secara default, yang menyesuaikan argumen yang belum di-set supaya model muat di memory device, dengan -fitt / --fit-target untuk mengatur marginnya dalam MiB.

Langkah 1: pilih quant sebelum download apa pun

Quantization menukar presisi bobot dengan ukuran file. Yang dikorbankan soal kecepatan di format modern lebih kecil dari yang biasanya orang duga. Ini tabel benchmark upstream untuk Llama 3.1 8B, diukur di mesin maintainer llama.cpp:

Quant Ukuran (GiB) Prompt t/s @512 Generate t/s @128
F16 14,96 923,49 29,17
Q8_0 7,95 865,09 50,93
Q6_K 6,14 812,01 58,67
Q5_K_M 5,33 758,69 67,23
Q4_K_M 4,58 821,81 71,93
Q3_K_M 3,74 783,44 71,68
Q2_K 2,95 784,45 79,85

Baris F16 justru yang paling lambat dan file-nya paling besar. Quant 4-bit jalan lebih dari dua kali lebih cepat untuk generation. Angka-angka itu dari satu rig, jadi anggap sebagai bentuk umum, bukan janji. Tapi pelajarannya jelas: turun ke quant lebih kecil membeli RAM, bukan kecepatan. Dokumentasinya juga tegas bahwa quant lebih kecil bisa menurunkan kualitas keseluruhan, sementara dampaknya ke kecepatan dan memory disebut minim.

Default yang praktis:

  • Mulai dari Q4_K_M. Ini titik seimbang yang paling sering dipakai, dan jadi kualifikasi default saat kamu download dari Hugging Face tanpa menyebut quant.
  • Naik ke Q6_K atau Q8_0 kalau memory kamu sisa dan task-nya sensitif ke presisi.
  • Turun ke Q3_K_M atau Q2_K hanya kalau memang tidak ada pilihan lain, dan pakai importance matrix kalau turun.

Langkah 2: dapatkan file GGUF

Dua jalur. Pilih satu.

Download quant yang sudah ada. llama-server bisa menarik langsung dari repo Hugging Face, tanpa langkah download manual:

llama-server -hf bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M

Kalau quant-nya tidak ditulis, default-nya Q4_K_M dan fallback ke file pertama di repo. File-nya masuk ke cache lokal, -cl / --cache-list menampilkan apa yang sudah ada, dan --offline mencegahnya akses jaringan.

Convert hasil fine-tune sendiri. Ini jalur kalau model yang kamu mau belum punya build GGUF:

git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
python3 -m pip install -r requirements.txt

python convert_hf_to_gguf.py   --outfile my-model-bf16.gguf   --outtype bf16   --remote <org>/<model>

./build/bin/llama-quantize my-model-bf16.gguf my-model-Q4_K_M.gguf Q4_K_M

convert_hf_to_gguf.py --remote menarik bobot dari Hugging Face. Kalau modelnya sudah ada di lokal, arahkan ke directory-nya dan hapus flag itu. Kalau model kamu menerima gambar atau audio, bagian LLM saja tidak cukup, kamu butuh file mmproj terpisah untuk encoder dan projector multimodal-nya.

Untuk quant bit rendah, importance matrix adalah pembeda antara model 3-bit yang masih berguna dan yang rusak. llama-imatrix menghitungnya dari file teks kalibrasi, lalu llama-quantize memakainya:

./build/bin/llama-imatrix -m my-model-bf16.gguf -f calibration.txt -o imatrix.gguf -ngl 99
./build/bin/llama-quantize --imatrix imatrix.gguf my-model-bf16.gguf my-model-Q4_K_M.gguf Q4_K_M

Ada juga Space ggml-org/gguf-my-repo di Hugging Face yang bikin quant untuk kamu tanpa setup lokal, disinkronkan dari llama.cpp main setiap enam jam.

Langkah 3: jalankan dengan tools aktif

llama-server --jinja -fa   -hf bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M   -c 8192 -np 2   --host 0.0.0.0 --port 8080   --api-key "$LOCAL_KEY"   --metrics

Fungsi masing-masing flag:

  • --jinja menyalakan Jinja chat template, dan itu fondasi function calling bergaya OpenAI. Build terbaru sudah mengaktifkannya default, tapi menulisnya eksplisit tidak ada ruginya dan menyelamatkan kamu dari sesi debugging di binary lama.
  • -fa menyalakan flash attention. Dokumentasi menandai default-nya auto, jadi eksplisit di sini soal pilihan RAM versus kecepatan, bukan soal benar atau salah.
  • -c 8192 -np 2 memberi kamu dua slot dengan context 8K. Setiap slot tambahan menambah memory KV cache.
  • --api-key lebih penting dari kelihatannya. Tanpa itu server sama sekali tidak punya autentikasi, dan 0.0.0.0 di interface publik berarti GPU kamu terbuka untuk siapa pun yang menemukan port-nya.
  • --metrics membuka endpoint yang kompatibel Prometheus.
  • --sleep-idle-seconds 300 melepas model dan KV cache setelah lima menit tidak ada request, lalu memuatnya lagi saat request berikutnya datang. Berguna di laptop atau mesin yang dipakai bersama.

Kalau tidak mau build sendiri, versi Docker-nya:

docker run --gpus all -p 8080:8080 -v ~/models:/models   ghcr.io/ggml-org/llama.cpp:server-cuda13   -m /models/my-model-Q4_K_M.gguf -c 8192 -np 2 --host 0.0.0.0 --port 8080 --jinja -fa

Image server isinya cuma llama-server, dan varian CUDA, Vulkan, ROCm, SYCL, serta OpenVINO dipublikasikan dengan tag masing-masing.

Sebelum menulis satu baris kode agent, pastikan server-nya setuju dengan kamu soal tools:

curl -s localhost:8080/health
curl -s localhost:8080/props | jq '.chat_template_tool_use'

Kalau chat_template_tool_use kosong, template bawaan model itu tidak punya dukungan tool, dan definisi tool kamu akan diabaikan seberapa rapi pun formatnya. Di kasus itu server mencatat Chat format: Generic di log. Solusinya arahkan ke file template tool_use dari folder models/templates/ di repo, lewat --chat-template-file. Dokumentasi juga menyarankan --chat-template chatml sebagai fallback kasar untuk model tanpa template tool resmi.

Langkah 4: verifikasi tool calling pakai curl

Lakukan ini sebelum masuk ke loop agent. Cara ini memisahkan masalah di sisi server dari masalah di sisi client.

curl -s http://localhost:8080/v1/chat/completions   -H "Authorization: Bearer $LOCAL_KEY"   -H "Content-Type: application/json"   -d '{
    "model": "gpt-3.5-turbo",
    "messages": [
      {"role": "system", "content": "You are a chatbot that uses tools. Do not overthink things."},
      {"role": "user", "content": "What is the weather in Istanbul?"}
    ],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_current_weather",
        "description": "Get the current weather in a given location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {"type": "string", "description": "The city and country, e.g. Paris, France"}
          },
          "required": ["location"]
        }
      }
    }]
  }'

Jawaban yang kamu mau punya finish_reason: "tool" dan array tool_calls di message-nya. Selain itu artinya ada yang spesifik. Kalau argumennya datang sebagai prosa di dalam content, template-nya jatuh ke handler generic dan kamu butuh template tool yang benar. Kalau model malah menjawab pertanyaannya langsung tanpa memanggil tool, tambahkan satu baris di system prompt yang bilang tool itu tersedia dan kapan dipakai. Model instruction-tuned kecil sering ragu memanggil tool kalau tidak diberi tahu.

Langkah 5: loop agent yang menjalankan tool-nya

Sisi client-nya cuma kode OpenAI SDK biasa dengan base URL berbeda. Tidak ada yang khusus llama.cpp di loop ini, dan itu memang gunanya endpoint yang kompatibel OpenAI.

import json
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8080/v1", api_key="local")

def get_current_weather(location: str) -> str:
    # Implementasi asli kamu di sini.
    return json.dumps({"location": location, "temp_c": 17})

TOOLS = [{
    "type": "function",
    "function": {
        "name": "get_current_weather",
        "description": "Get the current weather in a given location",
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string"}},
            "required": ["location"],
        },
    },
}]
HANDLERS = {"get_current_weather": get_current_weather}

messages = [
    {"role": "system", "content": "You call tools when they can answer the question."},
    {"role": "user", "content": "What is the weather in Istanbul?"},
]

final = "no answer produced"
for step in range(6):
    resp = client.chat.completions.create(
        model="local",
        messages=messages,
        tools=TOOLS,
        tool_choice="auto",
        temperature=0,
    )
    msg = resp.choices[0].message
    messages.append(msg.model_dump(exclude_none=True))

    if not msg.tool_calls:
        final = msg.content or ""
        break

    for call in msg.tool_calls:
        args = json.loads(call.function.arguments or "{}")
        result = HANDLERS[call.function.name](**args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": result,
        })
else:
    final = "step limit reached without a final answer"

print(final)

Tiga hal di snippet itu benar-benar bekerja. Batas range(6) menghentikan model yang terus memanggil tool tanpa henti supaya tidak menghabiskan malam kamu. model_dump(exclude_none=True) menjaga message assistant tetap bisa diserialisasi untuk server yang ketat soal field asing. Lalu klausa else pada loop for menangkap kondisi limit, bukan diam-diam mencetak hasil tool seolah-olah itu jawaban akhir.

Dua flag yang perlu kamu tahu di sini. "parallel_tool_calls": true di payload request mengaktifkan beberapa tool call dalam satu turn pada model yang mendukungnya, dan fitur itu mati secara default. Kalau kamu pakai model reasoning, --reasoning-format deepseek di server menaruh teks berpikirnya ke message.reasoning_content alih-alih mengotori message.content, dan --reasoning-budget membatasi berapa token yang boleh dipakai model untuk berpikir.

Langkah 6: ukur quant-nya, jangan berasumsi

Tool calling justru jenis task di mana quant yang lebih murah gagal tanpa suara. Kamu tidak akan dapat exception, yang kamu dapat argumen salah yang kelihatan masuk akal.

llama-bench menjawab pertanyaan throughput tanpa chat client mengganggu:

./build/bin/llama-bench -m my-model-Q4_K_M.gguf -p 512 -n 128 -r 5 -o md --progress
./build/bin/llama-bench -m my-model-Q4_K_M.gguf -ngl 99 -t 8 -p 512 -n 128

-p itu token prompt, -n token yang digenerate, -r jumlah pengulangan. Jalankan perintah pertama untuk setiap quant yang kamu pertimbangkan dan simpan outputnya di repo. Jalankan yang kedua dengan -ngl dan -t berbeda untuk menemukan di titik mana offload berhenti membantu di hardware kamu.

Untuk sisi korektnes, tulis 15 sampai 20 prompt yang satu-satunya jawaban benar adalah tool call dengan argumen persis, lalu jalankan set itu di setiap quant kandidat dan setiap jenis KV cache. Dokumentasi upstream punya peringatan eksplisit di sini: KV quantization ekstrem seperti -ctk q4_0 bisa menurunkan performa tool calling model secara signifikan. Peringatan itu soal cache, bukan bobot, dan gampang kena saat kamu sedang senang karena berhasil menghemat 300MB.

Built-in tools dan MCP, kalau mau jalan pintas

llama-server bisa jadi host agent-nya sendiri. --tools all mengaktifkan tool bawaan (read_file, write_file, edit_file, grep_search, file_glob_search, exec_shell_command, get_info), atau kamu sebut subset dengan koma. --tools-runtime docker:<image> menjalankan tool itu di dalam container alih-alih di host, dan flag yang sama menerima target podman: dan ssh:.

MCP server dipasang lewat file JSON format Cursor:

{
  "mcpServers": {
    "example": { "command": "/path/to/server", "args": [] }
  }
}
llama-server -m my-model-Q4_K_M.gguf --mcp-servers-config mcp.json

Hanya transport stdio yang didukung. Setiap server dijalankan sekali saat startup untuk membaca daftar tool-nya, lalu dimatikan dan dijalankan lagi saat dibutuhkan. Tool-nya muncul sebagai <server>_<tool> di GET /tools. Key timeout_ms mengatur timeout per pemanggilan, default 30 detik.

Catatan keamanan di dokumentasinya layak diulang: jangan aktifkan ini di lingkungan yang tidak dipercaya. Child process-nya jalan dengan privilege yang sama dengan server, exec_shell_command memang shell, dan pembatasan CORS cuma mengikat browser. Client apa pun yang bisa mencapai port-nya bisa mencapai tool-nya. Tetap set --api-key dan bind ke localhost atau interface privat kecuali kamu memang berniat mempublikasikannya.

Kapan pakai llama.cpp, Ollama, atau vLLM

Tool Ini apa Pakai kapan
llama.cpp Inference engine C/C++ plus tooling GGUF. Jalan di CPU serta CUDA, Metal, Vulkan, SYCL, ROCm, dan lainnya. Kamu mau kontrol sampai level flag (tipe KV cache, batch size, offload, grammar, JSON schema) atau deploy ke mesin tanpa GPU datacenter.
Ollama Lapisan distribusi dan UX; README-nya sendiri menyebut llama.cpp sebagai backend yang didukung. Ada library model, ollama run, dan REST API sendiri dengan kompatibilitas OpenAI. Kemudahan lebih penting daripada tuning. Jalur tercepat dari nol ke model chat di laptop.
vLLM Stack serving Python, awalnya dari Sky Computing Lab di Berkeley, dibangun di sekitar PagedAttention dan continuous batching. Banyak request bersamaan di GPU dan throughput jadi kendala. Bicara API OpenAI dan mendukung parser tool calling serta 200+ arsitektur.

Pembagian jujurnya begini. llama.cpp memberi kontrol terbanyak per byte RAM dan dukungan hardware terluas. Ollama membungkusnya jadi sesuatu yang nyaman. vLLM pilihan kamu saat satu GPU harus melayani banyak orang.

Langkah berikutnya

Pin build tag yang sudah kamu tes. llama.cpp merilis nightly build berkelanjutan (tag bNNNN, saat tulisan ini dibuat b11046) berdampingan dengan rilis v0.*, dan permukaan argumennya bergerak. Catat quant, tipe KV cache, -c, -np, dan 20 prompt tool-calling kamu di repo yang sama dengan agent-nya. Saat ada regresi setelah upgrade, catatan itu mengubah perasaan samar menjadi diff. Setelah itu tambahkan --metrics ke apa pun yang sudah memantau service kamu, supaya model yang tidak pernah unload dan jumlah slot yang terlalu tinggi tidak lagi tidak terlihat.

Referensi

Butuh Bantuan Implementasi?

Saya membantu tim mendesain dan membangun infrastruktur cloud scalable, pipeline DevOps, dan sistem production-grade.

Konsultasi Gratis