← Kembali ke Blog

Bikin AI Agent Lokal dengan Tool Calling: Ollama + qwen3

Model chat cuma bisa memprediksi token berikutnya. Dia nggak bisa query database kamu, manggil API, atau ngecek cuaca. Itulah jarak antara chatbot dan agent. Tool calling (atau function calling) yang nutup jarak itu. Modelnya nggak ngejalanin apa-apa sendiri; dia cuma balikin nama fungsi dan argumen yang dia mau. Kode kamu yang ngejalanin fungsi itu dan ngasih hasilnya balik. Ulang terus sampai model berhenti minta tool.

Bagian yang paling saya suka: loop ini bisa jalan sepenuhnya lokal pake Ollama dan model open kecil kayak qwen3. Nggak perlu API key, nggak perlu internet, nggak ada biaya per-call. Panduan ini bikin loop itu dari nol di Python, lalu nunjukin ke mana lanjut kalau loop aja udah nggak cukup.

Prerequisites

ollama pull qwen3
  • Python 3.9+ dan SDK ollama: pip install -U ollama

Langkah 1: Jalankan Ollama dan cek model

ollama serve

Pastikan qwen3 ngerespon:

ollama run qwen3 "say hi"

Ollama nyediain HTTP API di localhost:11434. Setiap request cuma POST ke /api/chat berisi daftar messages plus array tools opsional.

Langkah 2: Definisikan tool

Tool itu cuma fungsi plus signature-nya. Di Python SDK kamu nulis fungsi biasa dengan docstring dan nge-pass langsung. Ollama yang ngubah docstring dan type hints jadi JSON Schema yang dipahami model.

from ollama import chat

def get_temperature(city: str) -> str:
    """Get the current temperature for a city.

    Args:
        city: The name of the city

    Returns:
        The current temperature for the city
    """
    temps = {"New York": "22C", "London": "15C", "Tokyo": "18C"}
    return temps.get(city, "Unknown")

messages = [{"role": "user", "content": "What is the temperature in New York?"}]
response = chat(model="qwen3", messages=messages, tools=[get_temperature], think=True)

Dua hal yang gampang kelewat di sini. Pertama, pas model mau pake tool, dia nggak nulis prosa. Dia balikin message dengan field tool_calls berisi nama tool dan argumen yang udah di-parse. Belum ada apa-apa yang beneran jalan. Kedua, SDK vendor (OpenAI, Anthropic) dan yang lokal (Ollama) pake schema tool yang beda, tapi bentuk loop-nya sama di mana pun: declare, decide, execute, feed back.

Langkah 3: Jalankan tool dan kirim hasilnya balik

messages.append(response.message)
if response.message.tool_calls:
    call = response.message.tool_calls[0]
    result = get_temperature(**call.function.arguments)
    messages.append({"role": "tool", "tool_name": call.function.name, "content": str(result)})

    final = chat(model="qwen3", messages=messages, tools=[get_temperature], think=True)
    print(final.message.content)

Hasil tool ditambahin sebagai message dengan role tool, di-key pake tool_name, biar model bisa baca output tool-nya sendiri. Lalu kamu manggil model lagi dan dia nulis jawaban final yang grounded sama hasil itu.

Langkah 4: Agent loop penuh

Satu hasil tool itu satu round trip. Agent beneran itu loop: model bisa manggil beberapa tool, berurutan atau paralel, sampai dia cukup buat jawab. Kontrol alurnya cuma while loop.

from ollama import chat, ChatResponse

def add(a: float, b: float) -> float:
    """Add two numbers. Args: a (first), b (second)."""
    return a + b

def multiply(a: float, b: float) -> float:
    """Multiply two numbers. Args: a (first), b (second)."""
    return a * b

available = {"add": add, "multiply": multiply}
messages = [{"role": "user", "content": "What is (11434+12341)*412?"}]

while True:
    response: ChatResponse = chat(model="qwen3", messages=messages, tools=[add, multiply], think=True)
    messages.append(response.message)
    if not response.message.tool_calls:
        break
    for tc in response.message.tool_calls:
        if tc.function.name in available:
            result = available[tc.function.name](**tc.function.arguments)
            messages.append({"role": "tool", "tool_name": tc.function.name, "content": str(result)})

print(messages[-1].content)

qwen3 juga support parallel tool calls. Butuh cuaca dua kota? Daftarin tool suhu dan tool kondisi sekaligus; model bakal balikin beberapa entri di tool_calls dalam satu turn, kamu jalanin masing-masing, append semua hasil, lalu manggil model lagi.

Kesalahan yang sering bikin nyangkut

  • Nggak semua model support tool calling. qwen3, qwen2.5, llama3.1+, mistral, command-r, dan granite bisa; llama3 lama dan gemma2 nggak. Cek halaman model sebelum pull. Model yang nggak bisa handle tool nggak bakal error, dia cuma hallucinate JSON blob.
  • Jaga deskripsi tool tetap konkret. Yang vague bikin model manggil tool yang nggak dia butuhin.
  • Di model kecil, batasi jumlah tool. Lima-enam tool yang dideskripsiin jelas lebih bagus dari dua puluh yang ambigu.
  • Ollama nge-key role tool pake tool_name, bukan name. Beberapa client pake field milik OpenAI. Kalau loop diem-diem ngabaikan hasil, cek dulu field mana yang kepake buat key result kamu.

Kapan loop aja nggak cukup

While loop ini nutupin agent tunggal yang manggil tool. Begitu kamu butuh graph yang persisten dengan branch, gate persetujuan manusia, atau memory yang selamat pas restart, pindah ke LangGraph. LangGraph nge-model loop yang sama sebagai graph eksplisit dari node dan edge, pake checkpointer buat state, dan tool kamu tetep sama. Ollama duduk di bawah sebagai provider model; LangGraph sebagai lapisan orchestration-nya. Ini keliatan jelas di quickstart LangGraph.

Kesimpulan

Sekarang kamu punya agent lokal yang jalan: Ollama nyediain tools, qwen3 yang mutusin, kode kamu yang ngejalanin, dan hasilnya balik sampai model jawab. Semua jalan di mesin kamu tanpa API key. Next steps: tambah tool yang beneran nyentuh sistem kamu, misalnya penyimpanan notes berbasis SQLite atau HTTP endpoint, lalu pindahin loop-nya ke LangGraph pas branching dan persistence mulai dibutuhin.

Referensi

Butuh Bantuan Implementasi?

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

Konsultasi Gratis