# Tool calling: mô hình chỉ đề xuất, còn code của bạn mới thực thi

> Khi agent “tự hoàn tiền” trong buổi demo, thứ thực sự cầm tiền của khách hàng là đoạn vòng lặp vài chục dòng do FDE viết, chứ không phải mô hình.

Bản gốc: https://fdetimes.net/vi/bach-khoa/tool-calling-va-agent/

Buổi demo đang suôn sẻ. Agent chăm sóc khách hàng vừa nhận câu “đơn giao sai màu, hoàn tiền giúp tôi” và trả lời rằng yêu cầu hoàn tiền đã được tạo. Giám đốc vận hành phía khách hàng nhíu mày rồi hỏi một câu mà FDE nào cũng sẽ gặp: “Vậy là con AI này tự động vào hệ thống thanh toán của chúng tôi à?”

Câu trả lời đúng là không, và FDE phải giải thích được vì sao không. Nếu bạn chỉ nói “mô hình gọi API”, khách sẽ hình dung một cỗ máy tự do bấm nút trong hệ thống của họ. Nếu bạn hiểu đúng cơ chế, bạn chỉ ra được cho họ chỗ đặt chốt chặn, và cuộc trao đổi về bảo mật sẽ đi theo hướng khác hẳn.

Kỹ năng này là nền móng của mọi deployment có agent. Ai hiểu nhầm tool calling sẽ viết integration lỏng lẻo. Ai hiểu đúng sẽ thiết kế được một tầng tool mà cả đội bảo mật lẫn agent đều tin tưởng.

## Mô hình không bấm nút nào cả

Tài liệu của Anthropic mô tả hợp đồng này rất thẳng: mô hình không bao giờ tự thực thi gì. Nó phát ra một yêu cầu có cấu trúc, code của bạn chạy thao tác, và kết quả quay lại cuộc hội thoại.

Google viết gần như cùng một câu cho Gemini API: mô hình không tự chạy hàm, ứng dụng trích tên hàm và đối số rồi tự thực thi.

OpenAI mô tả cùng luồng đó theo từng bước. Ứng dụng nhận tool call, chạy code phía ứng dụng với input lấy từ tool call, rồi gửi request thứ hai kèm output. Ba nhà cung cấp lớn dùng ba API khác nhau, nhưng cùng một kiến trúc. Mô hình chỉ đóng vai người đề xuất, còn quyền thực thi thuộc về code của bạn.

Có một ngoại lệ cần biết. Với một số tool như web_search hay code_execution, Anthropic tự thực thi phía server, và bạn không bao giờ phải tạo khối tool_result cho chúng. Còn mọi tool chạm vào hệ thống của khách hàng, như database, CRM hay cổng thanh toán, đều là tool do bạn định nghĩa và do code của bạn chạy.

**Điểm mấu chốt:** Mô hình viết ra yêu cầu, còn code của bạn quyết định có làm theo hay không.

## Mỗi lần gọi tool là một vòng khứ hồi

Bài của đội kỹ sư Anthropic gọi tool là một loại phần mềm mới: hợp đồng giữa hệ thống tất định và agent phi tất định. Vì mô hình không chạy được code của bạn, mỗi lần gọi tool là một vòng khứ hồi: mô hình hỏi, bạn thực thi, bạn báo lại, mô hình đi tiếp.

Khi gặp tool do bạn định nghĩa, API trả về khối `tool_use` gồm tên tool và JSON đối số, và bạn gửi `tool_result` trong request kế tiếp.

Trên thực tế, vòng lặp này rất ngắn: còn `stop_reason == "tool_use"` thì thực thi tool và tiếp tục cuộc hội thoại, gặp lý do dừng khác thì thoát. Dưới đây là ví dụ giả định cho tình huống hoàn tiền ở đầu bài, viết bằng Python với SDK của Anthropic:

```python
TOOLS = [{
"name": "issue_refund",
"description": "Tạo yêu cầu hoàn tiền CHỜ DUYỆT cho đơn đã giao.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"amount": {"type": "number"},
"reason": {"type": "string",
"enum": ["damaged", "late", "wrong_item"]}
},
"required": ["order_id", "amount", "reason"]
}
}]

def run_tool(name, args):
if name != "issue_refund":
return f"Tool '{name}' không tồn tại. Tool hợp lệ: issue_refund."
order = db.get_order(args["order_id"])
if order is None:
return (f"Không có đơn {args['order_id']}. "
"Hãy hỏi lại khách mã đơn dạng DH-xxxxxx.")
if args["amount"] > order.total:
return (f"Số tiền {args['amount']} vượt tổng đơn {order.total}.

"
f"Hãy đề xuất số tiền <= {order.total}.")
ticket = refunds.create_pending(order.id, args["amount"], args["reason"])
return f"Đã tạo yêu cầu {ticket.id}, trạng thái: chờ nhân viên duyệt."

messages = [{"role": "user",
"content": "Đơn DH-104233 giao sai màu, hoàn tiền giúp tôi."}]

for _ in range(8):  # giới hạn số vòng, không để agent chạy vô tận
resp = client.messages.create(model=MODEL, max_tokens=1024,
tools=TOOLS, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason != "tool_use":
break
results = [{"type": "tool_result", "tool_use_id": b.id,
"content": run_tool(b.name, b.input)}
for b in resp.content if b.type == "tool_use"]
messages.append({"role": "user", "content": results})
```

Hãy đọc lại đoạn code bằng con mắt của vị giám đốc vận hành kia. Mô hình chỉ đưa ra ba giá trị: mã đơn, số tiền, lý do. Việc tra đơn, so số tiền với tổng đơn, và tạo một yêu cầu *chờ duyệt* thay vì chuyển tiền ngay đều là quyết định nằm trong `run_tool`, tức là do bạn viết, bạn test, bạn chịu trách nhiệm.

## Ba lớp chặn, và lớp nào không thay được lớp nào

Lớp đầu tiên là schema. OpenAI khuyên dùng enum và cấu trúc object để loại bỏ trạng thái không hợp lệ ngay từ schema. Trường `reason` ở trên chỉ nhận ba giá trị, nên agent không thể bịa ra lý do “khách quen”.

OpenAI còn có strict mode: trong định nghĩa function của OpenAI, bật `strict` lên `true` thì lời gọi hàm bám đúng schema một cách đáng tin cậy, thay vì chỉ cố gắng hết sức.

Nếu bạn dùng API khác, hãy kiểm tra tài liệu của chính API đó xem có cơ chế tương đương hay không.

Nhưng một JSON đúng schema vẫn có thể sai về nghiệp vụ. Schema không biết đơn DH-104233 có tổng bao nhiêu tiền, cũng không biết khách này đã được hoàn tiền tuần trước. Vì thế lớp thứ hai, phần kiểm tra trong code, là thứ không thể bỏ, dù bạn đã bật strict mode.

Lớp thứ ba hay bị xem nhẹ nhất: thông báo lỗi. Đội kỹ sư Anthropic khuyên phản hồi lỗi của tool nên nói rõ cần cải thiện cụ thể điều gì, thay vì trả về mã lỗi khó hiểu hay traceback. Câu “Hãy đề xuất số tiền &lt;= 450000” cho agent một đường để tự sửa ở vòng sau. Còn `ValueError at line 42` chỉ khiến nó đoán mò.

## Tự làm cho dự án của bạn

Hãy bắt đầu bằng việc liệt kê mọi hành động agent cần làm và chia chúng thành hai nhóm: chỉ đọc và có ghi. Với nhóm có ghi, hãy hỏi khách hàng ngay từ customer discovery hành động nào phải có người duyệt, rồi thiết kế tool trả về trạng thái “chờ duyệt” như ví dụ trên, thay vì thực thi thẳng.

Tiếp theo, viết schema hẹp nhất có thể. Trường nào có tập giá trị cố định thì dùng enum, và chỉ đánh dấu bắt buộc những gì thật sự cần. Sau đó viết `run_tool` sao cho mỗi nhánh lỗi trả về một câu agent đọc xong biết phải làm gì tiếp.

Cuối cùng là để ý kích thước kết quả. Một tool tra cứu trả về 5.000 dòng log sẽ làm đầy context rất nhanh. Anthropic đề xuất kết hợp phân trang, chọn khoảng, lọc và cắt ngắn cho những phản hồi dạng này, và khi cắt thì nên kèm một câu hướng dẫn agent cách xin trang tiếp theo.

## Những lỗi khiến deployment đổ vỡ

Lỗi phổ biến nhất là tin rằng mô hình “biết” quy tắc vì bạn đã ghi trong system prompt. Prompt chỉ là lời dặn, còn code mới là luật. Mọi ràng buộc liên quan đến tiền, quyền truy cập hay dữ liệu cá nhân đều phải nằm trong `run_tool`.

Lỗi thứ hai là vòng lặp không có giới hạn. Nếu tool cứ trả lỗi mà agent cứ thử lại, `while True` sẽ đốt token mãi mãi. Một trần số vòng cộng với log từng lời gọi là chi phí rất nhỏ so với một sự cố lúc nửa đêm.

Lỗi thứ ba là ném nguyên exception vào `tool_result`. Lỗi kiểu này vừa vô ích với agent, vừa có thể lộ đường dẫn file hay cấu trúc database của khách hàng ra ngoài cuộc hội thoại.

Nếu bạn đang nhắm tới vai trò FDE, đây là kỹ năng nên thể hiện rõ khi ứng tuyển: gặp JD nào yêu cầu kinh nghiệm tích hợp LLM hay xây agent, hãy coi đó là chỗ để kể chi tiết về tầng tool bạn từng làm. Trong CV, đừng chỉ ghi “tích hợp LLM”.

Hãy viết cụ thể bạn đặt validation ở đâu, tool nào bắt buộc có người duyệt, bạn phân trang kết quả ra sao. Người phỏng vấn sẽ hiểu ngay bạn biết quyền thực thi nằm ở phía nào.

Lần tới có khách hàng hỏi “AI có tự làm được không?”, đừng vội trấn an. Hãy mở `run_tool` ra và cho họ xem từng dòng code quyết định điều gì được phép xảy ra.

**Thử ngay tuần này:**

- Viết lại vòng lặp trong bài cho một tool đọc dữ liệu thật ở công ty bạn, chẳng hạn tra cứu đơn hàng hoặc ticket, kèm giới hạn số vòng.
- Rà một tool bạn đang có và viết lại từng thông báo lỗi để mỗi câu trả lời được câu hỏi: agent nên gọi lại với đối số nào?
- Thêm một dòng vào CV mô tả tầng tool bạn thiết kế: validation, phân trang, và hành động nào bắt buộc có người duyệt.

## Nguồn

- [How tool use works](https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works)

- [Function calling with the Gemini API | Google AI for Developers](https://ai.google.dev/gemini-api/docs/function-calling)

- [Function calling](https://developers.openai.com/api/docs/guides/function-calling)

- [Writing effective tools for agents — with agents](https://www.anthropic.com/engineering/writing-tools-for-agents)
