Tool functions — Python code Claude sẽ gọi

5 — Tool UseTrung cấp15 phút

Tool function là Python function bình thường — nhưng có 2 khác biệt:

Bạn sẽ học được
  • Viết tool function theo best practices: descriptive names, validation, error messages
  • Hiểu Claude "thấy" error → tự retry với corrected params
  • Viết 3 function mẫu: datetime, duration, reminders
  • Connect function với schema

Best practices

1. Descriptive names

Tên function + param phải tự-documenting.

2. Validate inputs

# ❌
def f(x, y): ...

# ✅
def get_current_datetime(date_format): ...
def calculate_shipping_cost(weight, destination): ...

2. Validate inputs

3. Meaningful error messages

def get_weather(location, unit="F"):
    if not location:
        raise ValueError("Location cannot be empty")
    if unit not in ("F", "C"):
        raise ValueError(f"Unit must be F or C, got {unit}")
    # ... actual logic

3. Meaningful error messages

Claude đọc error, có thể retry với correction.

4. Return strings (thường)

Claude dễ process string. Nếu function return dict/list, Claude vẫn OK nhưng string thường preferred:

# ❌
raise Exception("error")

# ✅  
raise ValueError(f"Invalid date format '{fmt}'. Use Python strftime syntax (e.g., '%Y-%m-%d')")

4. Return strings (thường)

# OK
return {"temp": 72, "condition": "sunny"}

# Better for Claude
return "Current weather in SF: 72°F, sunny with 10mph wind"

3 tool functions mẫu

1. get_current_datetime

2. add_duration_to_datetime

from datetime import datetime

def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"):
    """Get current date/time."""
    if not date_format:
        raise ValueError("date_format cannot be empty")
    try:
        return datetime.now().strftime(date_format)
    except ValueError as e:
        raise ValueError(f"Invalid format string: {date_format}. {str(e)}")


# Test
print(get_current_datetime())           # "2026-04-20 14:30:25"
print(get_current_datetime("%H:%M"))    # "14:30"
print(get_current_datetime("%A"))       # "Monday"

2. add_duration_to_datetime

3. set_reminder

from datetime import datetime, timedelta

def add_duration_to_datetime(
    datetime_str: str,
    duration: int,
    unit: str = "days"
) -> str:
    """Add duration to a datetime."""
    if unit not in ("seconds", "minutes", "hours", "days", "weeks"):
        raise ValueError(f"Unit must be seconds/minutes/hours/days/weeks, got {unit}")
    
    try:
        dt = datetime.fromisoformat(datetime_str)
    except ValueError:
        raise ValueError(f"Invalid datetime: '{datetime_str}'. Expected ISO format like '2026-04-20T14:30:00'")
    
    delta = timedelta(**{unit: duration})
    result = dt + delta
    return result.isoformat()


# Test
print(add_duration_to_datetime("2026-04-20T00:00:00", 103, "days"))
# "2026-08-01T00:00:00"

3. set_reminder

def set_reminder(
    reminder_text: str,
    remind_at_iso: str
) -> str:
    """Set a reminder. Returns confirmation."""
    if not reminder_text.strip():
        raise ValueError("Reminder text cannot be empty")
    
    try:
        remind_at = datetime.fromisoformat(remind_at_iso)
    except ValueError:
        raise ValueError(f"Invalid datetime format: {remind_at_iso}")
    
    if remind_at < datetime.now():
        raise ValueError("Reminder time must be in the future")
    
    # Store in DB (simplified)
    reminder_id = f"rem_{int(datetime.now().timestamp())}"
    # save to db...
    
    return f"Reminder set: '{reminder_text}' at {remind_at.strftime('%Y-%m-%d %H:%M')}. ID: {reminder_id}"

Pattern: Try-except với informative error

Claude có thể retry:

  • First call: SELECT * FROM usrs → error "table usrs not found"
  • Retry: SELECT * FROM users → works
def query_database(sql: str) -> str:
    if not sql.strip().lower().startswith("select"):
        raise ValueError("Only SELECT queries allowed. Got: " + sql[:50])
    
    try:
        results = db.execute(sql).fetchall()
        return format_results(results)
    except db.OperationalError as e:
        raise ValueError(f"SQL error: {str(e)}. Check syntax.")
    except db.TimeoutError:
        raise TimeoutError("Query too slow. Try narrower WHERE clause.")

Pattern: Side effects + confirmation

Cho tools có side effect (create, delete, send), return rõ ràng:

Return confirmation giúp Claude biết action thành công.

def send_email(to: str, subject: str, body: str) -> str:
    if not is_valid_email(to):
        raise ValueError(f"Invalid email: {to}")
    
    try:
        email_service.send(to=to, subject=subject, body=body)
        return f"Email sent successfully to {to}. Subject: '{subject}'"
    except EmailServiceError as e:
        raise RuntimeError(f"Email failed: {e}")

Function + schema pairing

Usage:

# tools.py

def get_weather(location: str, unit: str = "F") -> str:
    # ... implementation
    pass

get_weather_schema = {
    "name": "get_weather",
    # ... schema
}


# Dispatcher
TOOLS = {
    "get_weather": get_weather,
    "get_current_datetime": get_current_datetime,
    "add_duration_to_datetime": add_duration_to_datetime,
    "set_reminder": set_reminder,
}


def run_tool(name: str, input: dict) -> str:
    """Execute tool by name with args."""
    if name not in TOOLS:
        raise ValueError(f"Unknown tool: {name}")
    return TOOLS[name](**input)

Function + schema pairing (tiếp)

tool_call = response.content[-1]  # ToolUseBlock
result = run_tool(tool_call.name, tool_call.input)

Security considerations

Tool có thể run code / modify data — nguy hiểm

Validate strictly:

Never. Dùng sandbox (Docker, subprocess với limits).

Rate limiting

Tool gọi external API → có thể abuse.

def execute_python(code: str) -> str:
    # DANGEROUS — code injection
    return str(eval(code))

Rate limiting

Whitelisting

from functools import lru_cache
from time import time

@lru_cache(maxsize=100)
def _cached_weather(location: str, hour: int):
    return weather_api.get(location)


def get_weather(location: str, unit: str = "F"):
    # Cache per hour
    hour = int(time()) // 3600
    return _cached_weather(location, hour)

Whitelisting

Pass user_id trong context từ session, không trust Claude pass.

def get_order_status(order_id: str, user_id: str) -> str:
    order = db.get(order_id)
    if order.user_id != user_id:
        raise PermissionError("Order not yours")
    return order.status

Anti-patterns

❌ No validation

Claude pass location="" → crash or wrong query.

Fix: Validate + raise.

❌ Silent failure

Fix: Raise với message. Claude sẽ retry.

❌ Return nothing

try:
    do_something()
except:
    return ""  # Claude không biết có lỗi

❌ Return nothing

Fix: Return "Saved with ID: xyz". Claude xác nhận.

❌ Complex nested return

def save_to_db(data):
    db.save(data)
    # không return gì

❌ Complex nested return

Claude xử lý OK nhưng string prose thường clearer.

Fix: Return formatted string hoặc flat dict.

return {"a": {"b": {"c": {"d": "value"}}}}

Áp dụng ngay

Bài tập 1: Viết 3 tool function (30 phút)

Chọn 3 tool cho app của bạn. Viết function với:

Test mỗi function với valid + invalid input.

Bài tập 2: Pair với schema (15 phút)

Viết schema cho mỗi function. Commit cả 2 cùng file tools.py:

  • [ ] Type hints
  • [ ] Docstring
  • [ ] Input validation
  • [ ] Error message
  • [ ] Return formatted string
def my_tool(...): ...
my_tool_schema = {...}

Tóm tắt

🎯 Tool function = Python function + robust validation.

🎯 Error message informative — Claude đọc và retry được.

🎯 Return string formatted giúp Claude process dễ hơn.

🎯 Security: validate, sandbox, rate-limit, whitelist.

🎯 Function + schema pair: my_tool + my_tool_schema.

Nội dung này có hữu ích không?