Langsung ke konten
KamusNgoding
Menengah Ai-agent 6 menit baca

Tools vs. Functions di AI Agent: Perbedaan dan Kapan Menggunakannya

#ai agent #function calling #tools #api

Tools vs. Functions di AI Agent: Perbedaan dan Kapan Menggunakannya

Pendahuluan

Ketika kamu mulai membangun AI Agent yang lebih dari sekadar chatbot, kamu akan menemui dua konsep yang sering membingungkan: Tools dan Functions. Keduanya terdengar mirip, tapi dalam konteks AI Agent modern, keduanya memiliki peran, lingkup, dan cara kerja yang berbeda.

Artikel ini akan membantumu memahami perbedaan mendasar antara keduanya, kapan harus menggunakan salah satu atau bahkan keduanya, serta bagaimana mengimplementasikannya dalam kode nyata. Jika kamu belum familiar dengan konsep ReAct dan bagaimana agen berpikir sebelum bertindak, kamu bisa melihat hubungannya dengan Memahami Chain-of-Thought (CoT) dalam Prompt Engineering untuk membangun fondasi yang lebih kuat.


Memahami ‘Tools’ dalam AI Agent

Tools dalam konteks AI Agent adalah kapabilitas eksternal yang diberikan kepada agen agar bisa berinteraksi dengan dunia luar. Tools memungkinkan agen melakukan hal-hal yang tidak bisa dilakukan model LLM secara native — seperti mencari web, membaca file, menjalankan kode, atau memanggil API.

Tools bersifat deklaratif: kamu mendefinisikan apa yang bisa dilakukan oleh tool tersebut (nama, deskripsi, parameter), lalu agen memutuskan kapan dan bagaimana menggunakannya.

Contoh definisi tools menggunakan OpenAI-style schema:

tools = [
    {
        "type": "function",
        "function": {
            "name": "cek_harga_produk",
            "description": "Cek harga dan ketersediaan stok produk berdasarkan ID produk",
            "parameters": {
                "type": "object",
                "properties": {
                    "product_id": {
                        "type": "string",
                        "description": "ID unik produk, contoh: PRD001"
                    }
                },
                "required": ["product_id"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "buat_pesanan",
            "description": "Buat pesanan untuk produk tertentu",
            "parameters": {
                "type": "object",
                "properties": {
                    "product_id": {"type": "string"},
                    "quantity": {"type": "integer", "minimum": 1},
                    "user_id": {"type": "string"}
                },
                "required": ["product_id", "quantity", "user_id"]
            }
        }
    }
]

Tools diregister ke model LLM dan model bisa memilih untuk memanggil tool mana pun yang relevan. Bayangkan kamu ingin membangun asisten belanja online — agent-nya perlu tool untuk cek stok, cek harga, dan membuat pesanan. Semua kapabilitas itu didefinisikan sebagai tools.


Memahami ‘Functions’ dalam AI Agent

Functions adalah implementasi Python (atau JavaScript/bahasa lain) yang menjalankan logika aktual dari sebuah tool. Kalau tools adalah kontrak/interface, maka functions adalah implementasinya.

Dalam banyak framework (seperti LangChain atau AutoGen), function adalah callable Python biasa yang dipetakan ke definisi tool. Ketika agen memutuskan untuk memanggil tool cek_harga_produk, framework akan mengeksekusi function Python yang sesuai.

def cek_harga_produk(product_id: str) -> dict:
    """
    Implementasi aktual dari tool cek_harga_produk.
    Mengembalikan data harga dan stok dari katalog produk.
    """
    # Simulasi database katalog produk
    catalog = {
        "PRD001": {"nama": "Laptop Gaming ASUS", "harga": 15_000_000, "stok": 3},
        "PRD002": {"nama": "SSD Samsung 1TB",    "harga": 1_200_000,  "stok": 15},
        "PRD003": {"nama": "RAM DDR5 16GB",       "harga": 850_000,   "stok": 0},
    }

    produk = catalog.get(product_id)
    if not produk:
        return {"error": f"Produk dengan ID '{product_id}' tidak ditemukan"}

    return {
        "product_id": product_id,
        "nama": produk["nama"],
        "harga": f"Rp {produk['harga']:,}".replace(",", "."),
        "stok": produk["stok"],
        "tersedia": produk["stok"] > 0
    }


def buat_pesanan(product_id: str, quantity: int, user_id: str) -> dict:
    """
    Membuat pesanan baru dan mengembalikan nomor order.
    """
    import random

    # Cek ketersediaan stok terlebih dahulu
    info = cek_harga_produk(product_id)
    if "error" in info:
        return {"success": False, "error": info["error"]}
    if not info["tersedia"]:
        return {"success": False, "error": f"Stok produk {product_id} habis"}

    order_id = f"ORD-{random.randint(10000, 99999)}"
    return {
        "success": True,
        "order_id": order_id,
        "product_id": product_id,
        "quantity": quantity,
        "status": "pending",
        "pesan": f"Pesanan {order_id} berhasil dibuat untuk user {user_id}"
    }

Functions adalah kode nyata yang dieksekusi. Mereka bisa memanggil API, mengakses database, melakukan komputasi, bahkan menjalankan subproses.


Perbandingan Head-to-Head: Tools vs. Functions

AspekToolsFunctions
SifatDeklaratif (schema/interface)Imperatif (kode eksekusi)
Siapa yang pakaiLLM untuk memilih aksiRuntime untuk eksekusi
FormatJSON SchemaPython/JS callable
Visibilitas LLMYa — LLM “melihat” definisi toolsTidak langsung — LLM tidak tahu isi kode
TujuanMemberitahu LLM apa yang tersediaMenjalankan apa yang diminta LLM
Error handlingDitangani di level schemaDitangani di dalam kode

Hubungan keduanya bisa diilustrasikan seperti ini:

LLM (AI Model)

    │  "Saya perlu cek harga, tool yang tersedia: cek_harga_produk"

Tool Definition (JSON Schema)  ←── Kontrak

    │  Tool dipilih, argumen dibangun

Function Execution (Python)    ←── Implementasi

    │  Hasil dikembalikan ke LLM

LLM memproses hasil dan melanjutkan reasoning

Pola ini sering disebut Tool Calling atau Function Calling, dan menjadi fondasi dari hampir semua AI Agent modern.


Contoh Kasus Nyata: Agent Belanja Online

Mari kita bangun agent sederhana yang bisa menjawab pertanyaan tentang harga produk dan membuat pesanan. Simpan kode ini sebagai agent_belanja.py:

# agent_belanja.py
# Jalankan: python agent_belanja.py
# Pastikan sudah install: pip install openai

import json
import random
from openai import OpenAI

client = OpenAI()  # Menggunakan OPENAI_API_KEY dari environment variable

# ============================================================
# TOOLS: Definisi schema yang dikirim ke LLM
# ============================================================
tools = [
    {
        "type": "function",
        "function": {
            "name": "cek_harga_produk",
            "description": "Cek harga dan ketersediaan stok produk berdasarkan ID produk",
            "parameters": {
                "type": "object",
                "properties": {
                    "product_id": {
                        "type": "string",
                        "description": "ID unik produk, contoh: PRD001"
                    }
                },
                "required": ["product_id"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "buat_pesanan",
            "description": "Buat pesanan untuk produk tertentu setelah pengguna konfirmasi",
            "parameters": {
                "type": "object",
                "properties": {
                    "product_id": {"type": "string", "description": "ID produk"},
                    "quantity": {"type": "integer", "minimum": 1, "description": "Jumlah yang dipesan"},
                    "user_id": {"type": "string", "description": "ID pengguna"}
                },
                "required": ["product_id", "quantity", "user_id"]
            }
        }
    }
]

# ============================================================
# FUNCTIONS: Implementasi aktual yang dieksekusi runtime
# ============================================================
KATALOG = {
    "PRD001": {"nama": "Laptop Gaming ASUS ROG",  "harga": 15_000_000, "stok": 3},
    "PRD002": {"nama": "SSD Samsung 970 EVO 1TB", "harga": 1_200_000,  "stok": 15},
    "PRD003": {"nama": "RAM Corsair DDR5 16GB",   "harga": 850_000,    "stok": 0},
}

def cek_harga_produk(product_id: str) -> dict:
    produk = KATALOG.get(product_id.upper())
    if not produk:
        return {"error": f"Produk '{product_id}' tidak ditemukan. Tersedia: {list(KATALOG.keys())}"}
    return {
        "product_id": product_id.upper(),
        "nama": produk["nama"],
        "harga": f"Rp {produk['harga']:,}".replace(",", "."),
        "stok": produk["stok"],
        "tersedia": produk["stok"] > 0
    }

def buat_pesanan(product_id: str, quantity: int, user_id: str) -> dict:
    # Validasi stok sebelum membuat pesanan
    info = cek_harga_produk(product_id)
    if "error" in info:
        return {"success": False, "error": info["error"]}
    if not info["tersedia"]:
        return {"success": False, "error": f"Stok produk {product_id} habis, tidak bisa memesan"}
    if quantity > KATALOG[product_id.upper()]["stok"]:
        return {"success": False, "error": f"Stok hanya tersisa {KATALOG[product_id.upper()]['stok']} unit"}

    order_id = f"ORD-{random.randint(10000, 99999)}"
    return {
        "success": True,
        "order_id": order_id,
        "product_id": product_id.upper(),
        "quantity": quantity,
        "status": "pending",
        "pesan": f"Pesanan {order_id} berhasil dibuat untuk user {user_id}"
    }

# Mapping nama tool → function Python
FUNCTION_MAP = {
    "cek_harga_produk": cek_harga_produk,
    "buat_pesanan": buat_pesanan,
}

# ============================================================
# AGENT LOOP
# ============================================================
def jalankan_agent(pertanyaan_user: str, user_id: str = "USR001") -> str:
    messages = [
        {
            "role": "system",
            "content": (
                "Kamu adalah asisten belanja online yang membantu pelanggan "
                "memeriksa harga produk dan membuat pesanan. "
                "Selalu cek harga terlebih dahulu sebelum membuat pesanan."
            )
        },
        {"role": "user", "content": pertanyaan_user}
    ]

    MAX_ITERATIONS = 10
    for iterasi in range(MAX_ITERATIONS):
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=tools,
            tool_choice="auto"
        )

        message = response.choices[0].message

        # Tidak ada tool call → jawaban final
        if not message.tool_calls:
            return message.content

        # Tambahkan respons assistant ke history
        messages.append(message)

        # Eksekusi setiap tool call yang diminta LLM
        for tool_call in message.tool_calls:
            nama_function = tool_call.function.name
            argumen = json.loads(tool_call.function.arguments)

            print(f"  [Agent] Memanggil tool: {nama_function}({argumen})")

            if nama_function in FUNCTION_MAP:
                hasil = FUNCTION_MAP[nama_function](**argumen)
            else:
                hasil = {"error": f"Tool '{nama_function}' tidak terdaftar"}

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(hasil, ensure_ascii=False)
            })

    return "Maaf, agent tidak bisa menyelesaikan permintaan dalam batas iterasi yang ditentukan."


# ============================================================
# CONTOH PENGGUNAAN
# ============================================================
if __name__ == "__main__":
    print("=" * 60)
    print("Skenario 1: Cek harga saja")
    print("=" * 60)
    jawaban = jalankan_agent("Berapa harga SSD Samsung PRD002?")
    print(f"Jawaban: {jawaban}\n")

    print("=" * 60)
    print("Skenario 2: Cek harga lalu langsung pesan")
    print("=" * 60)
    jawaban = jalankan_agent(
        "Berapa harga laptop PRD001? Kalau ada stoknya, tolong pesan 1 unit untuk saya.",
        user_id="USR042"
    )
    print(f"Jawaban: {jawaban}\n")

    print("=" * 60)
    print("Skenario 3: Coba pesan produk yang stoknya habis")
    print("=" * 60)
    jawaban = jalankan_agent("Tolong pesan RAM PRD003 sebanyak 2 unit.")
    print(f"Jawaban: {jawaban}\n")

Contoh output yang diharapkan:

============================================================
Skenario 1: Cek harga saja
============================================================
  [Agent] Memanggil tool: cek_harga_produk({'product_id': 'PRD002'})
Jawaban: SSD Samsung 970 EVO 1TB (PRD002) tersedia dengan harga Rp 1.200.000. Stok masih ada 15 unit.

============================================================
Skenario 2: Cek harga lalu langsung pesan
============================================================
  [Agent] Memanggil tool: cek_harga_produk({'product_id': 'PRD001'})
  [Agent] Memanggil tool: buat_pesanan({'product_id': 'PRD001', 'quantity': 1, 'user_id': 'USR042'})
Jawaban: Laptop Gaming ASUS ROG (PRD001) tersedia dengan harga Rp 15.000.000.
Pesanan berhasil dibuat dengan nomor ORD-47291. Selamat berbelanja!

============================================================
Skenario 3: Coba pesan produk yang stoknya habis
============================================================
  [Agent] Memanggil tool: cek_harga_produk({'product_id': 'PRD003'})
  [Agent] Memanggil tool: buat_pesanan({'product_id': 'PRD003', 'quantity': 2, 'user_id': 'USR001'})
Jawaban: Maaf, stok RAM Corsair DDR5 16GB (PRD003) saat ini habis. Silakan coba lagi nanti.

Pola ini bisa dikembangkan lebih jauh — bayangkan jika ingin membangun platform marketplace berskala besar, di mana agent mengakses ratusan tools berbeda untuk melayani jutaan pengguna secara bersamaan.


Troubleshooting: Error yang Sering Muncul

KeyError atau Tool Tidak Ditemukan di function_map

Penyebab: Nama function dalam tool_calls yang dikembalikan LLM tidak cocok dengan key di FUNCTION_MAP. Biasanya terjadi karena typo atau perbedaan penamaan antara definisi tool dan implementasi.

Solusi:

# ❌ SALAH — nama berbeda antara definisi dan implementasi
tools = [{"function": {"name": "cekHargaProduk", ...}}]  # camelCase

FUNCTION_MAP = {
    "cek_harga_produk": cek_harga_produk,  # snake_case → TIDAK COCOK
}

# ✅ BENAR — nama harus SAMA PERSIS
tools = [{"function": {"name": "cek_harga_produk", ...}}]  # snake_case

FUNCTION_MAP = {
    "cek_harga_produk": cek_harga_produk,  # snake_case → COCOK
}

# Tips debugging: tambahkan log ini di agent loop
for tool_call in message.tool_calls:
    nama = tool_call.function.name
    if nama not in FUNCTION_MAP:
        print(f"[ERROR] Tool '{nama}' tidak ada!")
        print(f"  Tools terdaftar: {list(FUNCTION_MAP.keys())}")

JSONDecodeError saat Parsing Arguments

Penyebab: tool_call.function.arguments adalah string JSON, bukan dict. Mengaksesnya langsung seperti dict akan menyebabkan TypeError atau JSONDecodeError. Ini adalah kesalahan paling umum saat pertama kali mengimplementasikan tool calling.

Solusi:

import json

for tool_call in message.tool_calls:
    # ❌ SALAH — arguments adalah string, bukan dict
    # argumen = tool_call.function.arguments["product_id"]  # TypeError!

    # ✅ BENAR — parse dulu dengan json.loads()
    argumen = json.loads(tool_call.function.arguments)

    # Sekarang argumen adalah dict dan bisa di-unpack
    hasil = FUNCTION_MAP[tool_call.function.name](**argumen)
    print(f"Hasil: {hasil}")

Agent Looping Tak Terbatas (Infinite Loop)

Penyebab: Agent terus memanggil tools secara berulang tanpa pernah menghasilkan jawaban final. Terjadi jika function selalu mengembalikan error sehingga LLM tidak tahu cara keluar dari loop, atau jika LLM terjebak dalam pola pemanggilan yang sama berulang kali.

Solusi:

MAX_ITERATIONS = 10  # Selalu set batas maksimal iterasi

def jalankan_agent(pertanyaan: str) -> str:
    messages = [...]

    for iterasi in range(MAX_ITERATIONS):
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=tools,
            tool_choice="auto"
        )
        message = response.choices[0].message

        # Jawaban final → keluar dari loop
        if not message.tool_calls:
            return message.content

        messages.append(message)

        for tool_call in message.tool_calls:
            argumen = json.loads(tool_call.function.arguments)
            nama = tool_call.function.name

            # Pastikan function selalu return dict, bukan None
            func = FUNCTION_MAP.get(nama, lambda **x: {"error": f"Tool '{nama}' tidak dikenal"})
            hasil = func(**argumen)

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(hasil, ensure_ascii=False)
            })

    # Keluar paksa jika melebihi batas
    return "Maaf, agen tidak dapat menyelesaikan permintaan dalam batas iterasi."

Function Mengembalikan None Sehingga Konten Tool Kosong

Penyebab: Function lupa menambahkan return atau hanya mencetak ke console. LLM menerima tool result berisi null dan tidak bisa membuat keputusan yang tepat.

Solusi:

# ❌ SALAH — function tidak return apa-apa
def cek_harga_produk(product_id: str):
    produk = KATALOG.get(product_id)
    print(produk)  # Hanya print, tidak return!

# ✅ BENAR — selalu return dict
def cek_harga_produk(product_id: str) -> dict:
    produk = KATALOG.get(product_id)
    if not produk:
        return {"error": f"Produk '{product_id}' tidak ditemukan"}  # Return error dict
    return {"nama": produk["nama"], "harga": produk["harga"]}       # Return hasil dict

Pertanyaan yang Sering Diajukan

Apa perbedaan utama antara Tools dan Functions di AI Agent?

Tools adalah definisi/kontrak dalam format JSON Schema yang memberitahu LLM tentang kapabilitas apa saja yang tersedia. Functions adalah implementasi kode yang benar-benar dieksekusi saat LLM memutuskan untuk menggunakan sebuah tool. Keduanya bekerja berpasangan: tanpa tool definition, LLM tidak tahu kapabilitas itu ada; tanpa function implementation, tidak ada yang dieksekusi saat tool dipilih.

Bagaimana cara mendaftarkan banyak tools sekaligus tanpa menulisnya manual satu per satu?

Kamu bisa menggunakan framework seperti LangChain yang bisa mengekstrak schema dari docstring dan type hints secara otomatis menggunakan dekorator @tool:

# pip install langchain langchain-openai
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

@tool
def cek_cuaca(kota: str) -> str:
    """Cek cuaca saat ini di sebuah kota di Indonesia."""
    # LangChain otomatis buat tool schema dari type hints dan docstring
    cuaca_data = {"Jakarta": "Cerah, 31°C", "Bandung": "Berawan, 24°C", "Surabaya": "Hujan, 28°C"}
    return cuaca_data.get(kota, f"Data cuaca untuk {kota} tidak tersedia")

@tool
def konversi_suhu(celsius: float) -> str:
    """Konversi suhu dari Celsius ke Fahrenheit dan Kelvin."""
    fahrenheit = (celsius * 9/5) + 32
    kelvin = celsius + 273.15
    return f"{celsius}°C = {fahrenheit:.1f}°F = {kelvin:.1f}K"

# Gunakan kedua tool di atas tanpa perlu tulis JSON Schema manual
llm = ChatOpenAI(model="gpt-4o-mini")
tools = [cek_cuaca, konversi_suhu]
prompt = ChatPromptTemplate.from_messages([
    ("system", "Kamu adalah asisten cuaca yang helpful."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = executor.invoke({"input": "Berapa suhu di Jakarta dalam Fahrenheit?"})
print(result["output"])

Apakah satu function bisa dipetakan ke lebih dari satu tool definition?

Secara teknis bisa, tapi tidak disarankan karena akan membingungkan LLM dalam memilih tool yang tepat. Lebih baik buat tool terpisah dengan deskripsi yang jelas dan berbeda, meskipun keduanya memanggil function yang sama di belakang layar. Pisahkan tanggung jawab agar LLM bisa memilih berdasarkan konteks pertanyaan pengguna.

Kapan sebaiknya menggunakan tool_choice: "required" vs "auto"?

Gunakan "auto" (default) untuk alur percakapan normal di mana LLM memutuskan sendiri kapan perlu memanggil tool. Gunakan "required" hanya jika kamu yakin LLM harus selalu memanggil setidaknya satu tool — misalnya di step pertama pipeline yang wajib mengambil data dulu sebelum menjawab. Hindari overuse "required" karena bisa memaksakan tool call yang tidak perlu dan boros token.

Bagaimana cara menangani tool yang membutuhkan waktu lama (long-running tasks)?

Untuk operasi async seperti ekspor data besar atau pemrosesan file, kembalikan task_id dari function dan sediakan tool terpisah untuk polling status. Konsep ini mirip dengan pola Mediator vs Observer Pattern di mana komponen berkomunikasi secara tidak langsung melalui status events:

import uuid
import time

# Simulasi antrian task
TASK_QUEUE: dict = {}

def mulai_ekspor_data(format: str, filter_tanggal: str) -> dict:
    """Mulai proses ekspor data secara asynchronous."""
    task_id = str(uuid.uuid4())[:8]
    TASK_QUEUE[task_id] = {"status": "running", "progress": 0, "format": format}
    # Di produksi: submit ke Celery, RQ, atau job queue lainnya
    return {
        "task_id": task_id,
        "status": "queued",
        "pesan": f"Ekspor {format} dimulai. Gunakan task_id '{task_id}' untuk cek status."
    }

def cek_status_task(task_id: str) -> dict:
    """Cek status task yang sedang berjalan."""
    task = TASK_QUEUE.get(task_id)
    if not task:
        return {"error": f"Task '{task_id}' tidak ditemukan"}
    return {"task_id": task_id, "status": task["status"], "progress": task.get("progress", 0)}

Kesimpulan

Tools dan Functions adalah dua sisi dari koin yang sama dalam arsitektur AI Agent. Tools adalah apa yang LLM ketahui bisa dilakukan — dideklarasikan dalam JSON Schema dan dikirim sebagai konteks ke model. Functions adalah apa yang benar-benar terjadi ketika LLM memilih untuk menggunakan tool tersebut — kode Python aktual yang dieksekusi oleh runtime.

Pemahaman yang jelas tentang perbedaan ini akan membantumu:

  • Merancang interface tools yang deskriptif agar LLM bisa memilih dengan tepat
  • Menulis functions yang robust dengan error handling yang baik
  • Men-debug agent loop dengan lebih efisien saat ada masalah

Kunci suksesnya sederhana: definisi tool yang jelas membuat LLM cerdas dalam mengambil keputusan, sementara implementasi function yang andal membuat agent reliable dalam eksekusi. Keduanya sama pentingnya — tidak bisa dipisahkan.

Selamat membangun AI Agent-mu sendiri, dan jangan ragu untuk terus bereksperimen! Setiap agent yang kamu bangun hari ini adalah fondasi untuk sistem yang lebih canggih esok hari — KamusNgoding selalu siap menemanimu di setiap langkah perjalananmu.

Artikel Terkait