Cách xây dựng AI agent với Claude API: Hướng dẫn từng bước
Khi bạn đọc xong bài này, bạn sẽ có một AI agent hoạt động thực sự trong tay.
Bài viết này là tutorial triển khai từng bước — không chỉ giải thích lý thuyết. Sản phẩm hoàn thành là "Python AI agent có thể tìm kiếm web, tính toán và đọc file."
Kiến thức nền: xem bài Claude là gì và Claude API là gì.
Bạn sẽ xây dựng gì trong bài này?
Mô tả project
Những gì sẽ xây dựng trong bài này:
- Tên: Trợ lý nghiên cứu thông minh
- Chức năng: Tìm kiếm thông tin web, tính toán, đọc file local
- Công nghệ: Python + Anthropic SDK
Yêu cầu trước khi bắt đầu
- Python 3.10 trở lên
- Anthropic API key (xem Claude API là gì)
- Kiến thức Python cơ bản (hàm, dict, list)
- Thời gian: khoảng 45–60 phút
Bước 1 — Thiết lập môi trường
Cài đặt thư viện
# Tạo thư mục project
mkdir claude-agent && cd claude-agent
# Tạo môi trường ảo (khuyến nghị)
python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
# Cài các package cần thiết
pip install anthropic python-dotenv requests
Cấu hình API Key
Tạo file .env:
ANTHROPIC_API_KEY=sk-ant-api03-key-của-bạn-ở-đây
Quan trọng: Thêm .env vào .gitignore để tránh vô tình push lên GitHub.
Bước 2 — Hiểu Tool Use trong Claude API
Tool Use là gì?
Tool Use là cơ chế Claude sử dụng khi "chỉ kiến thức của mình thôi chưa đủ" — Claude gọi tool bên ngoài.
Code của bạn:
1. Truyền "danh sách tools" cho Claude
2. Gửi câu hỏi người dùng
Claude:
3. Phán đoán "cần dùng tool" → Trả về block tool_use
Code của bạn:
4. Đọc block tool_use và thực thi tool
5. Gửi kết quả về Claude dưới dạng tool_result
Claude:
6. Dùng kết quả tool để tạo phản hồi cuối cùng
Cấu trúc định nghĩa tool
{
"name": "search_web",
"description": "Tìm kiếm thông tin trên internet. Dùng khi cần thông tin mới nhất.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Từ khóa tìm kiếm"
}
},
"required": ["query"]
}
}
Bước 3 — Xây dựng tools đơn giản
Tạo file tools.py:
Tool tìm kiếm thông tin
import requests
def search_web(query: str, num_results: int = 5) -> str:
"""Tìm kiếm đơn giản với DuckDuckGo API"""
try:
url = "https://api.duckduckgo.com/"
params = {
"q": query,
"format": "json",
"no_html": "1",
"no_redirect": "1"
}
response = requests.get(url, params=params, timeout=10)
data = response.json()
results = []
if data.get("AbstractText"):
results.append(f"Tóm tắt: {data['AbstractText'][:500]}")
for topic in data.get("RelatedTopics", [])[:num_results]:
if isinstance(topic, dict) and "Text" in topic:
results.append(f"- {topic['Text'][:200]}")
if results:
return "\n".join(results)
return "Không tìm thấy thông tin liên quan."
except requests.RequestException as e:
return f"Lỗi kết nối: {str(e)}"
Tool tính toán
import math
def calculate(expression: str) -> str:
"""Tính toán an toàn"""
try:
safe_globals = {
"__builtins__": {},
"math": math,
"abs": abs, "round": round,
"min": min, "max": max,
"sum": sum, "pow": pow,
"int": int, "float": float
}
clean_expression = expression.replace("_", "")
result = eval(clean_expression, safe_globals)
if isinstance(result, float):
return f"Kết quả: {result:,.2f}"
return f"Kết quả: {result:,}"
except ZeroDivisionError:
return "Lỗi: Không thể chia cho 0"
except Exception as e:
return f"Lỗi tính toán: {str(e)}"
Tool đọc file
import os
def read_file(filepath: str) -> str:
"""Đọc file văn bản"""
safe_path = os.path.abspath(filepath)
current_dir = os.path.abspath(".")
if not safe_path.startswith(current_dir):
return "Lỗi: Không được phép truy cập file ngoài thư mục hiện tại."
if not os.path.exists(safe_path):
return f"Lỗi: File không tồn tại — {filepath}"
try:
with open(safe_path, "r", encoding="utf-8") as f:
content = f.read()
if len(content) > 5000:
return content[:5000] + "\n\n... (đã cắt bớt do quá dài)"
return content
except UnicodeDecodeError:
return "Lỗi: File không phải định dạng văn bản (UTF-8)"
except Exception as e:
return f"Lỗi đọc file: {str(e)}"
Bước 4 — Kết nối tools với Claude
Tạo file agent.py:
Gửi request với tools
import anthropic
import os
from dotenv import load_dotenv
from tools import search_web, calculate, read_file
load_dotenv()
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
TOOLS = [
{
"name": "search_web",
"description": "Tìm kiếm thông tin trên internet. Dùng khi cần thông tin mới nhất hoặc sự kiện hiện tại.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Từ khóa tìm kiếm"}
},
"required": ["query"]
}
},
{
"name": "calculate",
"description": "Thực hiện tính toán toán học chính xác. Dùng cho mọi phép tính số học.",
"input_schema": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "Biểu thức toán học Python hợp lệ"}
},
"required": ["expression"]
}
},
{
"name": "read_file",
"description": "Đọc nội dung file văn bản trong thư mục hiện tại.",
"input_schema": {
"type": "object",
"properties": {
"filepath": {"type": "string", "description": "Đường dẫn đến file"}
},
"required": ["filepath"]
}
}
]
TOOL_MAP = {
"search_web": search_web,
"calculate": calculate,
"read_file": read_file
}
Xử lý tool_use response
def process_tool_calls(response_content):
"""Xử lý các block tool_use và trả về tool_result"""
tool_results = []
for block in response_content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
print(f" -> Dùng tool: {tool_name}({tool_input})")
if tool_name in TOOL_MAP:
result = TOOL_MAP[tool_name](**tool_input)
else:
result = f"Lỗi: Tool '{tool_name}' không tồn tại"
print(f" <- Kết quả: {str(result)[:100]}...")
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
return tool_results
Vòng lặp agentic
def run_agent(user_query: str, max_iterations: int = 10) -> str:
"""Vòng lặp chính của AI agent"""
messages = [{"role": "user", "content": user_query}]
iteration = 0
while iteration < max_iterations:
iteration += 1
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system="""Bạn là trợ lý nghiên cứu thông minh với các công cụ:
- search_web: Tìm kiếm thông tin
- calculate: Thực hiện tính toán
- read_file: Đọc file
Luôn trả lời bằng tiếng Việt. Sử dụng tools khi cần để đưa ra câu trả lời chính xác.""",
tools=TOOLS,
messages=messages
)
if response.stop_reason == "end_turn":
final_text = ""
for block in response.content:
if hasattr(block, "text"):
final_text += block.text
return final_text
if response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
tool_results = process_tool_calls(response.content)
messages.append({"role": "user", "content": tool_results})
return "Đã đạt giới hạn vòng lặp. Vui lòng thử lại."
Bước 5 — Chạy agent đầu tiên
Test với câu hỏi thực tế
from agent import run_agent
# Test 1: Tính toán
run_agent("Nếu đầu tư 10 triệu VND với lãi suất 8%/năm trong 5 năm, lãi kép là bao nhiêu?")
# Test 2: Tìm kiếm
run_agent("Python là gì và phiên bản mới nhất là gì?")
# Test 3: Đọc file
with open("test_data.txt", "w", encoding="utf-8") as f:
f.write("Doanh thu tháng 1: 50 triệu\nDoanh thu tháng 2: 65 triệu\nDoanh thu tháng 3: 72 triệu")
run_agent("Đọc file test_data.txt và tính tổng doanh thu 3 tháng đầu năm.")
Debug lỗi thường gặp
| Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
AuthenticationError | API key không hợp lệ | Kiểm tra file .env, copy lại key |
RateLimitError | Vượt quá rate limit | Thêm time.sleep(30) rồi thử lại |
ValidationError: tool_use_id | ID tool_result không khớp | Kiểm tra có copy đúng block.id không |
| Vòng lặp vô hạn | stop_reason luôn là "tool_use" | Kiểm tra max_iterations |
Nâng cao: Multi-turn agent
Quản lý conversation history
class AgentSession:
def __init__(self):
self.history = []
def chat(self, user_message: str) -> str:
self.history.append({"role": "user", "content": user_message})
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
tools=TOOLS,
messages=self.history
)
if response.stop_reason == "end_turn":
reply = response.content[0].text
self.history.append({"role": "assistant", "content": reply})
return reply
return ""
def reset(self):
self.history = []
Triển khai agent lên production
Best practices bảo mật
- Nguyên tắc quyền tối thiểu: Chỉ cung cấp đúng tools cần thiết
- Sanitize input: Không đưa trực tiếp input người dùng vào prompt
- Giới hạn rate: Mỗi user chỉ được N request mỗi ngày
- Ghi log: Lưu lại tất cả tool call để kiểm tra sau
Xử lý rate limit
import time
import anthropic
def call_with_retry(func, max_retries=3):
"""Retry với exponential backoff"""
for attempt in range(max_retries):
try:
return func()
except anthropic.RateLimitError as e:
if attempt == max_retries - 1:
raise e
wait_time = (2 ** attempt) * 5 # 5, 10, 20 giây
print(f"Rate limited. Đợi {wait_time} giây...")
time.sleep(wait_time)
return None
FAQ
Có cần LangChain để xây dựng agent không?
Không. Chỉ cần Anthropic SDK native là đủ. LangChain thêm abstraction layer — tiện nhưng không bắt buộc. SDK native đơn giản và dễ hiểu hơn.
Managed Agents và Tools API khác nhau thế nào?
Tools API (Function Calling) là "linh kiện" của vòng lặp agent. Managed Agents là hạ tầng quản lý agent của Anthropic. Bài này triển khai vòng lặp agent tự xây bằng Tools API.
Chi phí production ước tính bao nhiêu?
Giả sử 100 user/ngày, 5 câu hỏi/người, Sonnet, trung bình 500 input + 1.000 output token: ~$3.360/tháng. Chuyển sang Haiku giảm còn ~$280/tháng.
Kết luận và bước tiếp theo
Những gì đã xây dựng trong bài này: 3 tools (search_web, calculate, read_file), vòng lặp agent hoàn chỉnh, và xử lý lỗi rate limit.
Muốn tìm hiểu kiến trúc nâng cao hơn (Orchestrator + nhiều Subagent), xem bài Claude Managed Agents là gì.
Tổng quan về Claude xem tại Claude là gì.