Thêm tools và function calling cho AI Agents
Thiết kế một tool báo giá vận chuyển giả lập, kiểm tra tham số ở executor và trả kết quả có cấu trúc cho model.
Tổng quan
Dành cho người học muốn tự hoàn thành bài thực hành thêm tools và function calling cho ai agents, với dữ liệu mẫu có thể kiểm tra.
- Viết schema hẹp thay vì cho model thực thi mã tùy ý.
- Kiểm tra dữ liệu dù schema có strict=true.
- Giữ call_id và dữ liệu lỗi trong lượt trả kết quả.
Bạn sẽ hoàn thành: shipping.py có schema, executor và test cho đầu vào hợp lệ/sai.
Chuẩn bị
- Python 3.10+; đã hiểu vòng lặp Responses API ở bài agent đầu tiên.
Executor và test chạy offline. Phần tích hợp model tùy chọn dùng SDK openai và quyền API có tính phí như bài trước.
Thực hành từng bước
Bước 1. Chọn một tác vụ chỉ đọc
Tool chỉ ước tính giá theo một công thức hư cấu: local có phí gốc 3, regional có phí gốc 6, mỗi kg cộng 2 đơn vị DEMO. Nó không tạo đơn vận chuyển, truy cập website hay tính thuế thật. Viết rõ đơn vị và giới hạn 0 < weight_kg <= 100.
Bước 2. Viết schema và executor
Lưu code vào shipping.py. Dùng Decimal cho kết quả có hai chữ số thập phân, từ chối bool và các khóa dư. Model chỉ đề xuất tên và JSON arguments; ứng dụng mới quyết định có gọi hàm hay không.
import json
from decimal import Decimal
SCHEMA = {"type": "function", "name": "shipping_quote",
"description": "Fictional training shipping estimate, not a purchase.",
"strict": True, "parameters": {"type": "object", "properties": {
"zone": {"type": "string", "enum": ["local", "regional"]},
"weight_kg": {"type": "number"}},
"required": ["zone", "weight_kg"], "additionalProperties": False}}
def execute_tool(name, raw):
if name != "shipping_quote":
raise ValueError("unknown tool")
args = json.loads(raw)
if not isinstance(args, dict) or set(args) != {"zone", "weight_kg"}:
raise ValueError("unexpected arguments")
zone, weight = args["zone"], args["weight_kg"]
if zone not in ("local", "regional"):
raise ValueError("invalid zone")
if type(weight) not in (int, float) or not 0 < weight <= 100:
raise ValueError("invalid weight")
base = Decimal("3") if zone == "local" else Decimal("6")
amount = base + Decimal(str(weight)) * Decimal("2")
return {"amount": str(amount.quantize(Decimal("0.01"))),
"currency": "DEMO", "fictional": True}
if __name__ == "__main__":
assert execute_tool("shipping_quote", '{"zone":"local","weight_kg":2}')["amount"] == "7.00"
for raw in ['{"zone":"other","weight_kg":1}',
'{"zone":"local","weight_kg":true}',
'{"zone":"local","weight_kg":-1}']:
try:
execute_tool("shipping_quote", raw)
except ValueError:
continue
raise AssertionError("invalid input accepted")
print("shipping checks passed")Bước 3. Chạy test độc lập
python3 shipping.pyThêm trường hợp JSON sai cú pháp, trường thiếu và weight_kg=0. Khi kiểm tra tên tool lạ, lỗi phải xảy ra trước khi công thức được chạy. Mở rộng schema khi thật sự cần, không chấp nhận mọi dict chỉ để tránh lỗi model.
Bước 4. Nối vào vòng lặp model đã có
Trong bản sao agent.py từ bài trước, import SCHEMA và execute_tool từ shipping.py, thay tools=TOOLS bằng tools=[SCHEMA], và thay execute(call.name, call.arguments) bằng execute_tool(call.name, call.arguments). Đổi instructions để luôn gọi shipping_quote trước khi báo giá, nhắc rõ DEMO không phải tiền thật. Giữ nguyên phần history.extend(response.output), function_call_output và call_id.
Ước tính phí DEMO cho kiện 2 kg ở zone local bằng tool shipping_quote. Nếu thiếu zone hoặc khối lượng, hỏi thêm thay vì đoán. Không tạo đơn hay yêu cầu thanh toán.Bước 5. Trả lỗi mà không làm mất trace
Khi args sai, vòng lặp bài trước gửi JSON error gắn với call_id để model sửa yêu cầu hoặc giải thích giới hạn. Không biến lỗi thành amount=0, vì đó có thể bị hiểu là miễn phí. Giới hạn lượt và timeout vẫn áp dụng cho tool mới.
Kiểm tra kết quả
- local/2 kg cho amount="7.00" và fictional=true.
- Zone lạ, bool, số âm và tham số dư đều bị từ chối.
Xử lý lỗi thường gặp
Model trả JSON trông đúng nhưng code lỗi
Nguyên nhân: Tên trường, kiểu hoặc đơn vị không khớp hợp đồng.
Cách xử lý: Kiểm tra schema và executor cùng lúc; log dữ liệu mẫu không nhạy cảm để tái hiện.
Model đề xuất tool ngoài allowlist
Nguyên nhân: Đầu ra model chưa được kiểm soát ở dispatcher.
Cách xử lý: Từ chối tên lạ; không dùng eval, exec hoặc getattr tùy ý để gọi hàm.
Bài tập & mở rộng
- Thêm tool chỉ đọc tra vùng phục vụ với bộ kiểm tra riêng.
- Dùng cùng quy tắc allowlist cho tool tìm nguồn trong bài research assistant.
Strict schema không kiểm chứng tính đúng của dữ liệu nguồn, quyền truy cập hay an toàn nghiệp vụ. DEMO không phải loại tiền hoặc báo giá thật.
