🎯 Mục tiêu bài học
Bài trước bạn đã học tool calling cơ bản. Bài này sẽ đi sâu vào xây dựng custom tools phức tạp cho production agents.
Sau bài này, bạn sẽ:
✅ Build custom tools với @tool decorator ✅ Structured tools với Pydantic ✅ Async tools cho performance ✅ Tool composition patterns
🛠️ LangChain @tool Decorator
Basic Tool
1from langchain_core.tools import tool23@tool4def search_product(query: str) -> str:5 """Tìm kiếm sản phẩm trong database.6 7 Args:8 query: Tên hoặc mô tả sản phẩm cần tìm9 """10 # Simulate database search11 products = {12 "laptop": "MacBook Air M3 - 25.990.000 VND",13 "phone": "iPhone 15 - 22.990.000 VND",14 "tablet": "iPad Air - 16.990.000 VND",15 }16 17 for key, value in products.items():18 if key in query.lower():19 return value20 21 return "Không tìm thấy sản phẩm."2223# Tool properties24print(search_product.name) # "search_product"25print(search_product.description) # From docstring26print(search_product.args_schema) # Auto-generated from type hintsLLM dùng docstring để quyết định khi nào dùng tool. Viết docstring rõ ràng, mô tả chính xác tool làm gì.
Multi-Parameter Tool
1@tool2def calculate_shipping(3 weight_kg: float,4 destination: str,5 express: bool = False6) -> str:7 """Tính phí vận chuyển dựa trên cân nặng và địa chỉ.8 9 Args:10 weight_kg: Cân nặng kiện hàng (kg)11 destination: Thành phố đích (VD: "Hà Nội", "TP.HCM")12 express: Giao hàng nhanh (True/False)13 """14 base_rate = 15000 # VND/kg15 16 city_multiplier = {17 "Hà Nội": 1.0,18 "TP.HCM": 1.0,19 "Đà Nẵng": 1.2,20 }21 22 multiplier = city_multiplier.get(destination, 1.5)23 cost = weight_kg * base_rate * multiplier24 25 if express:26 cost *= 1.527 28 return f"Phí ship {weight_kg}kg đến {destination}: {cost:,.0f} VND"Checkpoint
Bạn đã hiểu cách tạo custom tool với @tool decorator và tại sao docstring quan trọng chưa?
📐 Structured Tools với Pydantic
Complex Input Schema
1from pydantic import BaseModel, Field2from langchain_core.tools import StructuredTool3from typing import Optional4from datetime import date56class BookingInput(BaseModel):7 """Input schema for hotel booking."""8 city: str = Field(description="Thành phố (VD: Đà Nẵng)")9 check_in: str = Field(description="Ngày check-in (YYYY-MM-DD)")10 check_out: str = Field(description="Ngày check-out (YYYY-MM-DD)")11 guests: int = Field(default=2, description="Số khách")12 max_price: Optional[int] = Field(default=None, description="Giá tối đa/đêm (VND)")1314def search_hotels(15 city: str,16 check_in: str,17 check_out: str,18 guests: int = 2,19 max_price: int = None20) -> str:21 """Tìm kiếm khách sạn."""22 results = f"Found 5 hotels in {city} for {guests} guests "23 results += f"from {check_in} to {check_out}"24 if max_price:25 results += f" under {max_price:,} VND/night"26 return results2728hotel_tool = StructuredTool.from_function(29 func=search_hotels,30 name="search_hotels",31 description="Tìm kiếm và so sánh khách sạn",32 args_schema=BookingInput33)Tool with Return Schema
1class WeatherResult(BaseModel):2 city: str3 temperature: float4 condition: str5 humidity: int67@tool(response_format="content_and_artifact")8def get_weather(city: str) -> tuple:9 """Lấy thông tin thời tiết.10 11 Args:12 city: Tên thành phố13 """14 # Simulate API call15 data = WeatherResult(16 city=city,17 temperature=28.5,18 condition="Partly cloudy",19 humidity=7520 )21 22 content = f"{city}: {data.temperature}°C, {data.condition}, Humidity: {data.humidity}%"23 return content, data.model_dump()Checkpoint
Bạn đã hiểu cách sử dụng Pydantic schema để define complex tool inputs chưa?
💻 Real-World Tool Patterns
Database Query Tool
1import sqlite323@tool4def query_database(sql_query: str) -> str:5 """Execute SQL query trên database sản phẩm.6 7 Args:8 sql_query: SQL query (SELECT only). Tables: products, orders, customers.9 """10 # Safety check11 if not sql_query.strip().upper().startswith("SELECT"):12 return "Error: Only SELECT queries are allowed."13 14 dangerous_keywords = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER"]15 for keyword in dangerous_keywords:16 if keyword in sql_query.upper():17 return f"Error: {keyword} is not allowed."18 19 try:20 conn = sqlite3.connect("store.db")21 cursor = conn.cursor()22 cursor.execute(sql_query)23 results = cursor.fetchall()24 columns = [desc[0] for desc in cursor.description]25 conn.close()26 27 if not results:28 return "No results found."29 30 # Format as table31 output = " | ".join(columns) + "\n"32 output += "-" * 50 + "\n"33 for row in results:34 output += " | ".join(str(v) for v in row) + "\n"35 36 return output37 except Exception as e:38 return f"Query error: {str(e)}"API Integration Tool
1import json23@tool4def search_web(query: str, num_results: int = 3) -> str:5 """Tìm kiếm thông tin trên web.6 7 Args:8 query: Search query9 num_results: Số kết quả trả về (1-5)10 """11 # Using SerpAPI or similar12 # In production, use actual API13 import requests14 15 params = {16 "q": query,17 "num": min(num_results, 5),18 }19 20 # Simulated response21 results = [22 {"title": "Result 1", "snippet": "Description...", "link": "https://example.com"},23 ]24 25 output = ""26 for i, r in enumerate(results, 1):27 output += f"{i}. {r['title']}\n {r['snippet']}\n {r['link']}\n\n"28 29 return outputFile Operations Tool
1import os23@tool4def read_document(filepath: str) -> str:5 """Đọc nội dung file văn bản.6 7 Args:8 filepath: Đường dẫn file (chỉ .txt, .md, .csv)9 """10 allowed_extensions = [".txt", ".md", ".csv"]11 ext = os.path.splitext(filepath)[1].lower()12 13 if ext not in allowed_extensions:14 return f"Error: Only {allowed_extensions} files are supported."15 16 if not os.path.exists(filepath):17 return f"Error: File not found: {filepath}"18 19 try:20 with open(filepath, "r", encoding="utf-8") as f:21 content = f.read()22 23 # Limit output size24 if len(content) > 3000:25 content = content[:3000] + "\n... (truncated)"26 27 return content28 except Exception as e:29 return f"Error reading file: {str(e)}"Checkpoint
Bạn đã hiểu cách build real-world tools (database, API, file) với security checks chưa?
⚡ Async Tools
1import asyncio2import aiohttp34@tool5async def fetch_url(url: str) -> str:6 """Fetch content from a URL.7 8 Args:9 url: URL to fetch10 """11 async with aiohttp.ClientSession() as session:12 async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:13 if resp.status == 200:14 text = await resp.text()15 return text[:2000] # Truncate16 return f"Error: HTTP {resp.status}"1718@tool19async def parallel_search(queries: list) -> str:20 """Search multiple queries in parallel.21 22 Args:23 queries: List of search queries24 """25 async def search_one(q):26 # Simulate async search27 await asyncio.sleep(0.1)28 return f"Results for '{q}': ..."29 30 tasks = [search_one(q) for q in queries]31 results = await asyncio.gather(*tasks)32 33 return "\n".join(results)Checkpoint
Bạn đã hiểu cách tạo async tools để cải thiện performance chưa?
🛠️ Tool Registration with Agent
1from langchain_openai import ChatOpenAI2from langgraph.prebuilt import create_react_agent34# Collect all tools5tools = [6 search_product,7 calculate_shipping,8 hotel_tool,9 get_weather,10 query_database,11 search_web,12 read_document,13]1415# Create agent with tools16llm = ChatOpenAI(model="gpt-4o-mini")17agent = create_react_agent(llm, tools)1819# Run20result = agent.invoke({21 "messages": [("user", "Tìm laptop giá tốt và tính ship về Đà Nẵng 2kg")]22})2324for msg in result["messages"]:25 print(f"{msg.type}: {msg.content[:200]}")Checkpoint
Bạn đã hiểu cách register và sử dụng nhiều tools cùng lúc với agent chưa?
🎯 Tổng kết
📝 Quiz
-
LLM dùng gì để quyết định khi nào gọi tool?
- Tool name và docstring/description
- Source code của tool
- Return type
- File location
-
Tại sao cần Pydantic schema cho tools?
- Validate input, clear description cho mỗi parameter
- Chạy nhanh hơn
- Bắt buộc
- Đẹp hơn
-
Safety best practice cho database tool?
- Chỉ cho phép SELECT, block dangerous keywords
- Cho full access
- Không cần validate
- Block tất cả queries
Key Takeaways
- @tool decorator — Cách nhanh nhất tạo LangChain tool
- Docstring matters — LLM dùng description để chọn tool
- Pydantic schema — Type safety và clear parameter description
- Security — Always validate input, limit permissions
- Async tools — Cải thiện performance cho I/O operations
Câu hỏi tự kiểm tra
- Khi tạo custom tool bằng @tool decorator, những yếu tố nào ảnh hưởng đến việc LLM chọn tool?
- Tại sao cần sử dụng Pydantic schema khi định nghĩa input cho tools?
- Những biện pháp security nào cần áp dụng khi build database query tool?
- Async tools mang lại lợi ích gì so với synchronous tools?
🎉 Tuyệt vời! Bạn đã hoàn thành bài học Building Custom Tools!
Tiếp theo: Hãy cùng học cách xử lý lỗi và giúp agent chọn đúng tool!
🚀 Bài tiếp theo
Error Handling & Tool Selection — Xử lý lỗi và giúp agent chọn đúng tool!
