Tutorial ini ajarin kamu bikin AI agent dengan tool use dari nol di Python, cuma pake Anthropic SDK. Nggak ada LangChain, nggak ada LangGraph, nggak ada abstraksi. Kamu nulis agent loop sendiri, define tool sendiri, dan handle conversation sendiri.
Ngerti yang terjadi di dalam agent.run() itu bedanya antara debug agent dan nebak-nebak. Kalau ada yang error dan itu pasti terjadi, kamu perlu tau masalahnya di layer mana: definisi tool, eksekusi tool, message history, atau reasoning model.
Tutorial ini bikin agent yang jalan dari nol step by step. Selesai nanti kamu punya CLI tool yang bisa search web, hitung matematika, dan inget apa yang kamu bilang tiga pesan lalu.
Prerequisites
- Python 3.10 atau lebih baru (cek dengan
python3 --version) - API key Anthropic (dapat di console.anthropic.com)
- Familiar basic sama function dan dictionary di Python
Yang Kita Bangun
CLI agent dengan tiga tool:
- Web search pakai Tavily API (free tier: 1.000 pencarian/bulan)
- Kalkulator buat ekspresi matematika
- File reader buat ngambil informasi dari file lokal
Agent mutusin tool mana yang dipanggil berdasarkan pertanyaan kamu, jalanin tool-nya, dan pake hasilnya buat bikin jawaban. Kalau satu tool call belum cukup, dia bisa panggil tool lain. Ini loop inti di balik semua agent, dari Claude Code sampai ChatGPT plugins.
Step 1: Install Dependencies
mkdir agent-from-scratch && cd agent-from-scratch
python3 -m venv .venv
source .venv/bin/activate
pip install anthropic tavily-python
Set API key kamu:
export ANTHROPIC_API_KEY="sk-ant-..."
export TAVILY_API_KEY="tvly-..."
Key Tavily gratis di basic tier. Daftar di tavily.com kalau belum punya.
Step 2: Define Tool Kamu
API tool use Anthropic expecting JSON schema buat tiap tool. Model baca schema ini buat mutusin tool mana yang cocok sama pertanyaan user.
# tools.py
"""Definisi tool buat agent."""
import json
import os
from tavily import TavilyClient
tavily = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
TOOLS = [
{
"name": "web_search",
"description": "Search web buat info terkini. Pake ini kalau kamu perlu cari fakta, berita terbaru, atau riset topik.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Query pencarian"
}
},
"required": ["query"]
}
},
{
"name": "calculate",
"description": "Hitung ekspresi matematika. Pake buat kalkulasi.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Ekspresi matematika, contoh: '2 + 3 * 4'"
}
},
"required": ["expression"]
}
},
{
"name": "read_file",
"description": "Baca isi file teks lokal. Pake buat ngambil informasi yang user simpen di file.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path file yang mau dibaca"
}
},
"required": ["path"]
}
}
]
def execute_tool(name: str, input_data: dict) -> str:
"""Jalanin tool berdasarkan nama, return hasil sebagai string."""
try:
if name == "web_search":
result = tavily.search(query=input_data["query"], max_results=3)
snippets = []
for r in result.get("results", []):
snippets.append(f"{r['title']}
{r['url']}
{r['content']}")
return "
".join(snippets) if snippets else "Nggak ada hasil ditemukan."
elif name == "calculate":
allowed = {"__builtins__": {}}
result = eval(input_data["expression"], allowed)
return str(result)
elif name == "read_file":
path = input_data["path"]
if not os.path.exists(path):
return f"Error: file nggak ditemukan di {path}"
with open(path) as f:
content = f.read()
if len(content) > 5000:
content = content[:5000] + "
... (dipotong)"
return content
else:
return f"Tool nggak dikenal: {name}"
except Exception as e:
return f"Error jalanin {name}: {str(e)}"
Beberapa keputusan desain:
- List
TOOLSpake format schema tool Anthropic langsung. Nggak ada wrapper, nggak ada translation layer framework. Ini yang diliat model. execute_toolitu function biasa dengan switch statement. Kalau debug tool error, kamu langsung nyampe ke masalahnya.- Kalkulator pake
eval()dengan builtins kosong. Buat production, pake math parser yang proper kayaknumexpratauasteval. Ini cuma biar contohnya pendek. - File reader dipotong di 5.000 karakter. Ngirim file 100MB ke model bikin token sia-sia dan cepet kena context limit.
Step 3: Bangun Agent Loop
Ini intinya. Loop ini ngelakuin tiga hal: kirim pesan ke model, cek apakah model mau panggil tool, jalanin tool, dan ulang sampai model kasih jawaban teks.
# agent.py
"""Agent loop. Nggak ada framework, cuma API calls."""
import anthropic
from tools import TOOLS, execute_tool
client = anthropic.Anthropic()
SYSTEM_PROMPT = """Kamu adalah asisten yang helpful dengan akses ke tools.
Pake tools kalau itu bantu jawab pertanyaan user.
Kalau tool call gagal, jelasin apa yang salah dan coba pendekatan lain.
Selalu cantumkan sumber kalau pake hasil web search."""
def run_agent(user_message: str, history: list[dict] | None = None) -> tuple[str, list[dict]]:
"""Jalanin agent loop, return (jawaban_akhir, history_updated)."""
if history is None:
history = []
history.append({"role": "user", "content": user_message})
while True:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
system=SYSTEM_PROMPT,
tools=TOOLS,
messages=history
)
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
history.append({"role": "assistant", "content": response.content})
history.append({"role": "user", "content": tool_results})
elif response.stop_reason == "end_turn":
text = ""
for block in response.content:
if block.type == "text":
text += block.text
history.append({"role": "assistant", "content": response.content})
return text, history
else:
text = ""
for block in response.content:
if block.type == "text":
text += block.text
return text or f"Stop reason nggak dikenal: {response.stop_reason}", history
Struktur loop-nya sama dengan pattern yang dipake semua agent framework di bawah tenda:
- Kirim pesan ke model
- Kalau model bilang
tool_use, jalanin tools dan tambahin hasilnya sebagai user message - Kalau model bilang
end_turn, ambil teksnya dan return
Detail kuncinya ada di langkah 2: tool results masuk ke percakapan sebagai user message dengan tool_result blocks. Ini cara Anthropic API kerja. Model liat output tool di giliran berikutnya dan mutusin apa yang harus dilakukan.
Perhatikan loop while True. Model bisa panggil beberapa tool berurutan sebelum kasih jawaban akhir. Misalnya, user tanya "Berapa populasi Tokyo dan berapa 15% dari angka itu?" Model panggil web_search buat populasi, terus panggil calculate dengan hasilnya, terus kasih jawaban akhir. Semua dalam satu agent loop.
Step 4: Tambah CLI Interface
# main.py
"""CLI interface buat agent."""
from agent import run_agent
def main():
print("Agent siap. Ketik 'quit' buat keluar, 'clear' buat reset history.
")
history = []
while True:
try:
user_input = input("You: ").strip()
except (EOFError, KeyboardInterrupt):
print("
Bye.")
break
if not user_input:
continue
if user_input.lower() == "quit":
break
if user_input.lower() == "clear":
history = []
print("History direset.
")
continue
answer, history = run_agent(user_input, history)
print(f"
Agent: {answer}
")
if __name__ == "__main__":
main()
Step 5: Test
python main.py
Coba percakapan ini:
You: Berapa harga Bitcoin sekarang?
Agent: [search web, kasih harga terkini dengan sumber]
You: Berapa 15% dari angka itu?
Agent: [pake konteks sebelumnya, hitung 15%, kasih hasilnya]
You: Simpen hasil kalkulasi itu di file notes.txt
Agent: [pake read_file buat cek file ada nggak, terus respon]
Pertanyaan kedua jalan karena conversation history include harga Bitcoin dari jawaban pertama. Model punya konteks yang cukup buat hitung 15% tanpa search ulang.
Step 6: Handle Failure Mode yang Umum
Tool call error
Kalau tool gagal, model dapet error message dan bisa coba cara lain. Function execute_tool return error string tanpa raise exception, jadi model selalu dapet respon.
Context window overflow
Percakapan panjang kena context limit model. Ini simple guard:
def trim_history(history: list[dict], max_messages: int = 40) -> list[dict]:
"""Keep context awal dengan trim pesan lama."""
if len(history) <= max_messages:
return history
return history[:2] + history[-(max_messages - 2):]
Ini preserve user message pertama (yang sering punya konteks penting) dan pesan-pesan terbaru. Buat production, pake prompt caching Anthropic buat ngurangi cost di bagian conversation yang nggak berubah.
Hallucinated tool calls
Kadang model coba pake tool yang nggak ada. API handle ini: kalau model panggil tool yang nggak ada di list TOOLS kamu, dia raise error. Catch dan tambahin pesan yang bilang ke model tool mana yang tersedia.
try:
response = client.messages.create(...)
except anthropic.BadRequestError as e:
if "tool" in str(e).lower():
history.append({"role": "assistant", "content": [
{"type": "text", "text": f"Tool call gagal: {e}. Tool yang tersedia: web_search, calculate, read_file"}
]})
continue
raise
Kapan Pake Framework Saja
Tutorial ini nunjukin fundamentals. Framework ada karena fundamentals makin complicated di skala besar:
- LangGraph nambahin state machines, persistence, human-in-the-loop checkpoints, dan parallel tool execution. Pake kalau agent kamu punya branching logic atau perlu survive crash.
- Claude Agent SDK ngasih pre-built patterns buat tool use, multi-turn conversations, dan integrasi MCP. Pake kalau mau ship cepet dan pattern-nya cocok sama use case kamu.
- OpenAI Agents SDK nambahin routing, guardrails, dan handoffs antar agent. Pake kalau butuh multi-agent orchestration dengan safety checks.
Pilihan framework tergantung bentuk masalah kamu, bukan mana yang "terbaik." Kalau agent kamu bisa diekspresi dalam 100 baris Python kayak yang ini, framework itu overhead. Kalau butuh persistence, retries, parallel execution, atau human review, baru pake framework.
Langkah Selanjutnya
- Tambah prompt caching buat ngurangi cost di system prompt yang diulang. Cache hit Anthropic cost 90% lebih murah dari prompt fresh.
- Tambah streaming pakai
client.messages.stream()biar user liat token satu per satu, nggak nunggu full response. - Tambah MCP biar agent kamu bisa akses database, API, dan tools lain tanpa hardcoded.
- Baca dokumentasi tool use lengkap di docs.anthropic.com/en/docs/build-with-claude/tool-use.