Python & AI Tutorials Logo
LangChain & LangGraph

13. Menghubungkan Tool ke LLM

Di Bab 12 kita sudah membangun tool yang bisa digunakan oleh agen. Sekarang saatnya menghubungkan tool ini ke LLM agar agen benar-benar bisa menggunakannya.

Seberapa pun bagusnya sebuah tool dibuat, LLM tidak bisa menggunakannya tanpa tahu bahwa tool itu ada. LLM perlu tahu apa saja yang tersedia dan memilih tool yang tepat untuk setiap permintaan. Namun, seperti yang kita pelajari di Bab 12, LLM tidak mengeksekusi tool secara langsung. Sebaliknya, LLM meminta "panggil tool ini dengan argumen-argumen ini," dan kode agen kita yang memanggil tool tersebut. Seluruh mekanisme ini disebut tool calling.

Di bab ini kita akan mengikat tool ke LLM, mengeksekusi permintaan tool call, dan mengembalikan hasilnya ke LLM — menerapkan siklus secara lengkap. Siklus ini adalah fondasi dari agent loop yang akan kita bangun di Bab 14.

13.1) Mengikat Tool dan Memeriksa Permintaan Tool Call

Agar LLM bisa menggunakan tool, pertama-tama LLM perlu tahu apa saja yang tersedia. Ketika kita memberikan name, description, dan input schema dari setiap tool ke LLM, LLM jadi tahu kapan dan bagaimana menggunakan masing-masing tool. Proses ini disebut tool binding.

Setelah tool diikat, LLM akan melakukan salah satu dari dua hal ketika menerima sebuah pertanyaan. Jika tidak ada tool yang dibutuhkan, LLM merespons dengan teks seperti biasa. Jika tool dibutuhkan, LLM mengembalikan permintaan terstruktur: "panggil tool ini dengan argumen-argumen ini." Agen kita kemudian memeriksa permintaan ini dan memanggil tool yang ditentukan.

Mari kita mulai dengan cara mengikat tool.

13.1.1) Mengikat Tool dengan bind_tools()

Tool binding ditangani oleh satu metode: bind_tools(). Setiap chat model yang mendukung tool calling menyediakan metode ini. Memanggil bind_tools() mengembalikan objek model baru dengan tool yang sudah diikat padanya.

Mari kita ikat tool get_weather yang sudah kita buat di Bab 12.

python
from langchain_openai import ChatOpenAI
from langchain.tools import tool
 
llm = ChatOpenAI(model="gpt-5-mini")
 
@tool
def get_weather(city: str) -> str:
    """Mendapatkan cuaca saat ini untuk kota tertentu."""
    return f"It's always sunny in {city}!"
 
# Ikat tool ke model — objek model baru dengan tool yang sudah terikat akan dikembalikan
llm_with_tools = llm.bind_tools([get_weather])

Hanya itu yang diperlukan. llm_with_tools yang dikembalikan adalah model yang mengenal tool get_weather. Perhatikan bahwa llm yang asli sama sekali tidak berubah — ia tetap tidak tahu apa-apa tentang tool apa pun.

bind_tools() menerima sebuah daftar tool sebagai argumennya dan mengembalikan objek model baru dengan tool tersebut terikat. Secara internal, ia mengubah metadata setiap tool (name, description, dan input schema) menjadi format yang dipahami oleh penyedia LLM, sehingga skema tool dikirim bersama setiap pemanggilan model.

13.1.2) Memeriksa Permintaan Tool Call

Apa yang terjadi ketika kita mengirim pertanyaan tentang cuaca ke model yang sudah punya tool cuaca terikat?

python
response = llm_with_tools.invoke("What's the weather in Paris?")
 
print(f"type: {type(response)}\n")
print(f"tool_calls: {response.tool_calls}\n")
print(f"content: {repr(response.content)}\n")

Output:

type: <class 'langchain_core.messages.ai.AIMessage'>
 
tool_calls: [{'name': 'get_weather', 'args': {'city': 'Paris'}, 'id': 'call_hgXrHGD', 'type': 'tool_call'}]
 
content: ''

Karena llm_with_tools masih merupakan chat model, ia mengembalikan sebuah AIMessage. Tetapi ada dua hal yang berbeda dari respons ini.

Pertama, response.tool_calls berisi daftar permintaan tool call. Setiap permintaan adalah sebuah dictionary dengan empat key:

  • name — nama tool yang akan dipanggil. Ini sesuai dengan name tool dari metadatanya.
  • args — argumen yang akan dilewatkan ketika memanggil tool. Model melihat input schema dan pertanyaan pengguna untuk menyusun {'city': 'Paris'}.
  • id — pengenal unik untuk panggilan ini. LLM menggunakan ini nanti untuk mencocokkan hasil eksekusi tool dengan permintaan tool call.
  • type — selalu 'tool_call'.

Kedua, response.content adalah string kosong. Respons ini bukan jawaban teks final — melainkan sebuah permintaan tool call.

Untuk menentukan apakah respons merupakan permintaan tool call, periksa tool_calls, bukan content. Jika tool_calls tidak kosong, itu adalah permintaan tool call. Jika kosong, LLM telah memberikan jawaban teks secara langsung.

13.2) Mengeksekusi Tool dan Mengembalikan Hasilnya

LLM telah meminta sebuah tool call get_weather. Sekarang kode agen kita perlu mengeksekusi tool tersebut dan mengembalikan hasilnya ke LLM.

Memproses permintaan tool call melibatkan tiga langkah:

  1. Ekstrak nama tool dan argumen dari tool_calls dan eksekusi tool yang sesuai.
  2. Konversi hasil eksekusi menjadi sebuah ToolMessage.
  3. Kirim seluruh percakapan (pertanyaan pengguna + permintaan tool call dari LLM + hasil eksekusi tool) kembali ke LLM untuk menerima sebuah respons.

Berikut adalah diagram dari proses ini:

LLMget_weatherAgenPenggunaLLMget_weatherAgenPengguna"What's the weather in Paris?"HumanMessageAIMessage (tool_calls: get_weather)get_weather(city='Paris')"It's always sunny in Paris!"ToolMessageAIMessage ("The weather in Paris is sunny!")"The weather in Paris is sunny!"

13.2.1) Mengeksekusi Tool dan Membuat ToolMessage

Pertama, mari kita ekstrak nama tool dan argumen dari tool_calls dan jalankan tool yang sesuai. Untuk mencari tool berdasarkan nama, kita membuat sebuah dictionary yang menggunakan nama tool sebagai key.

python
# Sebuah dictionary untuk mencari tool berdasarkan nama
tool_map = {get_weather.name: get_weather}

Selanjutnya, kita ambil sebuah permintaan tool call dari tool_calls, temukan tool yang cocok di tool_map, lalu eksekusi.

python
if response.tool_calls:
    tool_call = response.tool_calls[0]
    # {'name': 'get_weather', 'args': {'city': 'Paris'}, 'id': 'call_hgXrHGD', 'type': 'tool_call'}
 
    selected_tool = tool_map[tool_call["name"]]
    result = selected_tool.invoke(tool_call["args"])
    print(result)  # Output: It's always sunny in Paris!

Untuk mengembalikan hasil eksekusi tool ke LLM, kita perlu membungkusnya dalam sebuah ToolMessage. Sebuah ToolMessage memiliki dua field wajib:

  • content — hasil eksekusi tool sebagai string.
  • tool_call_idid dari tool_calls. LLM menggunakan ini untuk mengidentifikasi permintaan mana yang sesuai dengan hasil tersebut.
python
from langchain_core.messages import ToolMessage
 
if response.tool_calls:
    tool_call = response.tool_calls[0]
 
    selected_tool = tool_map[tool_call["name"]]
    result = selected_tool.invoke(tool_call["args"])
 
    tool_message = ToolMessage(
        content=result,
        tool_call_id=tool_call["id"],   # harus cocok dengan id dari permintaan
    )
    print(tool_message)

Output:

content="It's always sunny in Paris!" tool_call_id='call_hgXrHGD'

Pada kode di atas, kita melewatkan args untuk mengeksekusi tool, lalu menggabungkan hasilnya dengan tool_call_id untuk membangun ToolMessage.

Ada cara yang lebih sederhana untuk melakukan ini dalam satu langkah. Alih-alih hanya melewatkan args ke .invoke(), lewatkan seluruh dictionary tool_call. LangChain akan mengeksekusi tool dan mengembalikan sebuah ToolMessage secara otomatis.

python
if response.tool_calls:
    tool_call = response.tool_calls[0]
 
    selected_tool = tool_map[tool_call["name"]]
    tool_message = selected_tool.invoke(tool_call)
    print(tool_message)

Output:

content="It's always sunny in Paris!" tool_call_id='call_hgXrHGD'

Hasil yang sama, tetapi kodenya jauh lebih sederhana dan tidak ada kemungkinan terjadinya ketidakcocokan tool_call_id. Kita akan menggunakan pendekatan ini ke depannya.

13.2.2) Mengirim Hasil Tool ke LLM

Sekarang mari kita kembalikan hasil eksekusi tool ke LLM. Kita mengirim seluruh riwayat percakapan — pertanyaan pengguna, permintaan tool call dari LLM, dan hasil eksekusi tool — kembali ke LLM, dan LLM menghasilkan jawaban berdasarkan hasil tool tersebut.

Mari kita gabungkan seluruh proses dari awal hingga akhir dalam satu potongan kode.

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.tools import tool
 
# Definisikan tool dan ikat
llm = ChatOpenAI(model="gpt-5-mini")
 
@tool
def get_weather(city: str) -> str:
    """Mendapatkan cuaca saat ini untuk kota tertentu."""
    return f"It's always sunny in {city}!"
 
llm_with_tools = llm.bind_tools([get_weather])
tool_map = {get_weather.name: get_weather}
 
# Langkah 1: Kirim pertanyaan pengguna dan terima respons dari LLM
messages = [HumanMessage("What's the weather in Paris?")]
ai_msg = llm_with_tools.invoke(messages)
messages.append(ai_msg)
 
# Langkah 2: Jika sebuah tool call diminta, eksekusi tool dan tambahkan hasilnya
if ai_msg.tool_calls:
    tool_call = ai_msg.tool_calls[0]
    selected_tool = tool_map[tool_call["name"]]
    tool_message = selected_tool.invoke(tool_call)
    messages.append(tool_message)
 
    # Langkah 3: Kirim seluruh riwayat percakapan kembali ke LLM
    final_response = llm_with_tools.invoke(messages)
    print(final_response.content)

Output:

The current weather in Paris is sunny. Have a great day!

LLM menghasilkan jawaban teks final berdasarkan hasil eksekusi tool. Kembali di 13.1, content adalah string kosong — tetapi setelah mengembalikan hasil tool, kita menerima jawaban yang sebenarnya.

Mari kita periksa pesan apa saja yang ada di list messages pada Langkah 3.

python
for m in messages:
    print(f"{type(m).__name__}: {m.content!r}")

Output:

HumanMessage: "What's the weather in Paris?"
AIMessage: ''
ToolMessage: "It's always sunny in Paris!"

Tiga pesan tersimpan secara berurutan: pertanyaan pengguna, permintaan tool call dari LLM (content kosong), dan hasil eksekusi tool. LLM melihat pertanyaan pengguna, memutuskan bahwa ia membutuhkan sebuah tool, dan meminta sebuah panggilan get_weather. Kode agen mengeksekusi get_weather dan menambahkan hasilnya ke message list. Setelah memiliki semua yang dibutuhkan, LLM menghasilkan respons finalnya.

13.3) Memberikan LLM Beberapa Tool

Agen di dunia nyata biasanya memiliki beberapa tool. Semua yang sudah kita bahas sejauh ini bekerja persis sama dengan beberapa tool.

Mari kita tambahkan calculate dan search_web di samping get_weather untuk mengikat tiga tool.

python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.tools import tool
 
llm = ChatOpenAI(model="gpt-5-mini")
 
@tool
def get_weather(city: str) -> str:
    """Mendapatkan cuaca saat ini untuk kota tertentu."""
    return f"It's always sunny in {city}!"
 
@tool
def calculate(expression: str) -> str:
    """Mengevaluasi ekspresi aritmatika sederhana seperti '123 * 456'."""
    return str(eval(expression))  # Peringatan: eval() adalah risiko keamanan. Jangan gunakan di produksi.
 
@tool
def search_web(query: str) -> str:
    """Mencari di web untuk informasi terkini tentang suatu topik."""
    return f"Top result for '{query}': ..."
 
tools = [get_weather, calculate, search_web]
llm_with_tools = llm.bind_tools(tools)
tool_map = {t.name: t for t in tools}

Mari kita ajukan pertanyaan perhitungan.

python
response = llm_with_tools.invoke("What is 123 multiplied by 456?")
print(response.tool_calls)

Output:

[{'name': 'calculate', 'args': {'expression': '123 * 456'}, 'id': 'call_xyz789', 'type': 'tool_call'}]

LLM meminta sebuah panggilan calculate. Ia membandingkan description setiap tool dengan pertanyaan pengguna dan menentukan bahwa calculate adalah tool yang tepat.

Kita bisa mengeksekusi tool dan mendapatkan jawaban final menggunakan pola yang sama dari 13.2.

python
messages = [HumanMessage("What is 123 multiplied by 456?")]
ai_msg = llm_with_tools.invoke(messages)
messages.append(ai_msg)
 
if ai_msg.tool_calls:
    for tool_call in ai_msg.tool_calls:
        selected_tool = tool_map[tool_call["name"]]
        tool_message = selected_tool.invoke(tool_call)
        messages.append(tool_message)
 
    final_response = llm_with_tools.invoke(messages)
    print(final_response.content)  # Output: 123 multiplied by 456 is 56,088.

Di 13.2 kita hanya menangani permintaan pertama dengan tool_calls[0], tetapi di sini kita melakukan iterasi atas semua permintaan dengan sebuah loop for. Alasannya adalah karena LLM bisa meminta beberapa tool call sekaligus.

Beberapa Permintaan Tool Secara Bersamaan

Mari kita lihat apa yang terjadi ketika beberapa tool diminta pada waktu yang sama. Kodenya sama — hanya pertanyaannya yang berubah.

python
messages = [HumanMessage("What's the weather in Tokyo, and what is 123 * 456?")]
ai_msg = llm_with_tools.invoke(messages)
messages.append(ai_msg)
 
if ai_msg.tool_calls:
    for tool_call in ai_msg.tool_calls:
        print(f"Tool: {tool_call['name']}")
        selected_tool = tool_map[tool_call["name"]]
        tool_message = selected_tool.invoke(tool_call)
        messages.append(tool_message)
 
    final_response = llm_with_tools.invoke(messages)
    print(final_response.content)

Output:

Tool: get_weather
Tool: calculate
The weather in Tokyo is currently sunny, and 123 multiplied by 456 is 56,088.

LLM meminta get_weather dan calculate sekaligus, dan loop for mengeksekusi semua permintaan di tool_calls serta menambahkan hasilnya ke message list. LLM kemudian menggunakan hasil-hasil ini untuk menghasilkan jawaban final.