Function Calling
Gọi hàm — Khi AI biết dùng công cụ
Bạn hỏi một trợ lý AI: "Thời tiết Hà Nội hôm nay thế nào?". Mô hình được huấn luyện đến tháng 3/2025 và hôm nay là 18/4/2026. Nó sẽ xử lý ra sao?
Ẩn dụ: LLM là lễ tân khách sạn
Hãy tưởng tượng LLM là lễ tân khách sạn. Khách gõ cửa hỏi: "Tôi muốn ăn mì Ý lúc 19h tối nay." Lễ tân không tự vào bếp nấu. Thay vào đó:
- Lễ tân đọc yêu cầu của khách.
- Tra sổ xem có những bộ phận nào phục vụ được (bếp, dọn phòng, spa).
- Viết phiếu yêu cầugửi bếp: "Mì Ý, bàn 204, 19:00".
- Bếp thực thi (đây là hệ thống bên ngoài).
- Bếp trả lại phiếu xác nhận cho lễ tân; lễ tân thông báo lại cho khách bằng ngôn ngữ thân thiện.
LLM với function calling hoạt động đúng như vậy: nó không tự nấu (thực thi), chỉ viết phiếu (sinh JSON) và thông báo lại khi có kết quả. Ranh giới "lễ tân không vào bếp" chính là ranh giới giữa phần sinh văn bản và phần thực thi hành động.
Hình minh họa
Vòng lặp gọi hàm 5 bước — chọn công cụ và bước qua từng pha
Chọn một trong ba công cụ bên dưới, rồi nhấn "Bước tiếp theo" để thấy LLM, hệ thống và công cụ phối hợp thế nào.
Chữ ký hàm:
get_weather(city: string)
Lấy thời tiết hiện tại của một thành phố (nhiệt độ, độ ẩm, điều kiện).
Câu hỏi mẫu người dùng có thể đặt:
"Thời tiết Hà Nội hôm nay thế nào?"
1. Nhận câu hỏi
Người dùng gõ một câu hỏi bằng ngôn ngữ tự nhiên. Câu hỏi này đi vào cửa sổ ngữ cảnh cùng với danh sách công cụ có sẵn.
Người dùng:
"Thời tiết Hà Nội hôm nay thế nào?"
Người dùng hỏi: "Gửi email cho An báo rằng ngày mai trời mưa ở Đà Nẵng". AI cần gọi mấy hàm và theo thứ tự nào?
Bạn có hai tool: search_kb(query) (tìm trong knowledge base nội bộ) và search_web(query) (tìm Google). Mô tả cả hai đều là "tìm kiếm thông tin". Điều gì sẽ xảy ra?
Giải thích
Function calling (hay tool use) là một cơ chế cho phép Mô hình ngôn ngữ lớn (LLM) tạm dừng việc sinh văn bản tự do, và thay vào đó sinh một khối dữ liệu có cấu trúc (thường là JSON) mô tả ý định gọi một hàm bên ngoài với tham số cụ thể. Ứng dụng nhận khối JSON này, thực thi hàm thật, đóng gói kết quả lại thành một thông điệp với vai trò tool, và gửi ngược vào LLM để mô hình tổng hợp câu trả lời cuối cùng cho người dùng.
Có thể hình dung function calling như một hợp đồng ba bên:
- Developer định nghĩa danh sách tool kèm JSON Schema.
- LLM đọc câu hỏi người dùng, chọn tool phù hợp, sinh tham số.
- Ứng dụng thực thi tool thật, trả kết quả cho LLM tổng hợp.
Hình thức, một vòng gọi hàm có thể viết ngắn gọn như sau. Gọi là câu hỏi người dùng, là tập tool có sẵn, và là trạng thái nội bộ (cửa sổ ngữ cảnh). Mô hình tính:
Sau khi hệ thống thực thi, kết quả được đưa ngược vào ngữ cảnh. LLM được gọi lại với ngữ cảnh mở rộng:
Vòng lặp có thể kéo dài nhiều lượt cho đến khi LLM quyết định không cần gọi thêm tool nào nữa — khi đó nó trả về câu trả lời dạng văn bản tự nhiên.
Dưới đây là ví dụ triển khai đầy đủ một vòng function calling với SDK OpenAI Python, bao gồm cả phần xử lý kết quả tool và gọi lại mô hình lần 2 để tổng hợp:
from openai import OpenAI
import json, requests
client = OpenAI()
# ─── 1. Khai báo tool bằng JSON Schema ──────────────────────────────
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": (
"Lấy thời tiết hiện tại (nhiệt độ, điều kiện) "
"cho một thành phố cụ thể. "
"Chỉ dùng khi người dùng hỏi về thời tiết THỜI GIAN THỰC."
),
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Tên thành phố, ví dụ 'Ha Noi'",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"default": "celsius",
},
},
"required": ["city"],
},
},
}
]
# ─── 2. Hàm thực thi thật (demo, gọi API mở) ────────────────────────
def get_weather(city: str, unit: str = "celsius") -> dict:
# Trong thực tế, gọi API của wttr.in / OpenWeather / v.v.
# Ở đây fake cho ngắn.
return {"temp": 32, "unit": unit, "condition": "Nắng nhẹ", "city": city}
# ─── 3. Lượt 1: hỏi LLM, có thể sẽ sinh tool call ───────────────────
messages = [
{"role": "system", "content": "Bạn là trợ lý tiếng Việt."},
{"role": "user", "content": "Thời tiết Hà Nội hôm nay thế nào?"},
]
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
)
msg = resp.choices[0].message
messages.append(msg) # giữ lại message có tool_calls để context nhất quán
# ─── 4. Nếu LLM quyết định gọi tool, ta thực thi ────────────────────
if msg.tool_calls:
for call in msg.tool_calls:
name = call.function.name
args = json.loads(call.function.arguments)
if name == "get_weather":
result = get_weather(**args)
else:
result = {"error": f"Tool {name} chưa được hỗ trợ"}
# Gửi kết quả ngược vào ngữ cảnh với role="tool"
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False),
})
# ─── 5. Lượt 2: LLM đọc tool_result và tổng hợp ─────────────────
final = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
)
print(final.choices[0].message.content)
else:
# LLM tự trả lời, không cần tool
print(msg.content)Nếu bạn muốn thử nhanh trong Jupyter Notebook, dưới đây là cấu hình tối thiểu để chạy một cell có function calling mà không cần viết app đầy đủ:
# Cell 1: cài dependency
!pip install -q openai python-dotenv
import os
from dotenv import load_dotenv
load_dotenv() # đọc OPENAI_API_KEY từ file .env cùng thư mục
# Cell 2: thiết lập client và helper gọi tool
from openai import OpenAI
import json
client = OpenAI()
def call_with_tools(user_msg: str, tools: list, tool_fns: dict,
model: str = "gpt-4o-mini"):
"""
Vòng lặp function calling tối giản. Lặp tối đa 5 lượt để
tránh mô hình gọi tool vô hạn (runaway loop).
"""
messages = [{"role": "user", "content": user_msg}]
for _ in range(5):
resp = client.chat.completions.create(
model=model, messages=messages, tools=tools,
)
msg = resp.choices[0].message
messages.append(msg)
if not msg.tool_calls:
return msg.content # LLM kết thúc
# Thực thi tool(s)
for call in msg.tool_calls:
fn = tool_fns[call.function.name]
args = json.loads(call.function.arguments)
result = fn(**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False),
})
return "[WARN] Đã đạt giới hạn 5 lượt tool call."
# Cell 3: thử ngay
tools = [...] # như đã định nghĩa ở trên
tool_fns = {"get_weather": get_weather}
print(call_with_tools("Thời tiết Đà Nẵng?", tools, tool_fns)).env (thêm .env vào .gitignore) và đọc bằng python-dotenv. Mất key công khai trên GitHub có thể khiến bạn mất vài triệu đồng trong vài giờ — bot quét leak key rất nhanh.asyncio.gather) để giảm độ trễ.Ứng dụng thực tế
- Trợ lý nội bộ doanh nghiệp: gọi tool truy vấn CRM, HR system, tra cứu chính sách công ty — thay thế một phần tổng đài.
- Agent tự động hoá văn phòng: kết nối Gmail, Calendar, Slack. AI lên lịch, gửi follow-up, tóm tắt thread chat.
- Coding assistant: tool
read_file,run_tests,search_code— giống cách các IDE AI hiện đại hoạt động. - RAG nâng cao: kết hợp với RAG — tool
search_knowledge_baselà một function call mà LLM chỉ gọi khi cần tra cứu tài liệu. - Thương mại điện tử: tool đặt hàng, kiểm tra tồn kho, tính phí ship — chatbot mua hàng thay vì form truyền thống.
Bẫy và sai lầm phổ biến
- Quên vòng lặp thứ hai: gọi LLM 1 lần, thấy tool_call, thực thi, rồi quên gọi LLM lại để tổng hợp. Kết quả: người dùng thấy JSON thô hoặc không thấy gì. Khắc phục: luôn viết logic vòng lặp từ đầu — kiểm tra
msg.tool_calls, nếu có thì thực thi, append role="tool", rồi gọi LLM thêm một lượt nữa. - Mô tả tool quá mơ hồ:"hàm hữu ích", "công cụ tiện lợi" — LLM không chọn đúng được. Viết mô tả với công thức 3 phần: (1) tool này làm gì, (2) khi nào NÊN dùng, (3) khi nào KHÔNG nên dùng.
- Quá nhiều tool trong một lượt: khi danh sách tool vượt quá 15-20, chất lượng chọn tool tụt mạnh. Cân nhắc tool routing: dùng LLM nhỏ chọn nhóm tool trước, rồi mới gọi LLM chính với nhóm đã lọc. Đây cũng là một chiến lược giảm chi phí token rất hiệu quả vì mỗi tool schema chiếm vài trăm tới vài nghìn token trong cửa sổ ngữ cảnh.
- Tin tham số mà LLM sinh ra:luôn validate ở phía ứng dụng (type check, whitelist giá trị, sanitize). LLM có thể "ảo giác" tên bảng, tên cột, email — không bao giờ truyền thẳng vào SQL hay shell. Đặc biệt nguy hiểm khi tool chạm vào OS, network, hay database có quyền ghi.
- Không log tool call: khi agent làm sai, bạn không biết sai ở bước nào. Luôn log
(tool_name, args, result, latency, user_id). Trong production, nên có dashboard xem top 10 tool được gọi nhiều nhất, tỷ lệ lỗi theo tool, và p95 latency. - Bỏ qua human-in-the-loop cho hành động nguy hiểm: xoá database, gửi tiền, gửi email đại trà — phải có bước xác nhận của con người, đừng để AI tự quyết. Cách tiếp cận thực dụng: phân tool thành 3 tier — read-only (tự chạy), ghi tái tạo được (tự chạy nhưng có undo), ghi không hồi phục (phải confirm).
- Dùng function calling cho mọi thứ: nếu task chỉ cần văn bản thuần (tóm tắt, dịch, viết bài), thêm tool chỉ làm tăng chi phí và độ trễ mà không tạo giá trị. Chỉ bật tool khi kiến thức tĩnh của LLM không đủ để hoàn thành nhiệm vụ.
- Không test tool call với input cạnh (edge case): tool có hoạt động đúng khi tham số là chuỗi rỗng? Tiếng Nhật? Emoji? Giá trị âm? Viết unit test cho tool handler riêng biệt — không phụ thuộc LLM — để tool luôn đúng hợp đồng.
Bảy nguyên tắc thiết kế tool tốt
- Ít nhưng tốt hơn nhiều mà dở: 5 tool có mô tả rõ ràng hoạt động tốt hơn 30 tool mô tả mơ hồ.
- Một tool — một trách nhiệm: đừng gộp
manage_userlàm cả create/update/delete. Tách thànhcreate_user,update_user,delete_user— LLM chọn dễ hơn và bạn debug dễ hơn. - Idempotent khi có thể: tool gọi lại nhiều lần với cùng tham số nên cho kết quả giống nhau. Nếu không, dùng idempotency_key.
- Output có cấu trúc rõ: trả JSON với field tên nghĩa, tránh chuỗi dài lẫn lộn số liệu. LLM đọc JSON có cấu trúc dễ hơn nhiều so với văn bản thô.
- Lỗi nói được:khi tool thất bại, trả thông điệp lỗi mà LLM có thể hiểu và truyền đạt lại cho người dùng (ví dụ: "city not found" chứ không phải "error 500").
- Giữ tool ngắn gọn trong context: mô tả tool càng dài càng tốn token. Cân bằng: đủ thông tin để LLM chọn đúng, nhưng không lặp lại những gì đã rõ từ tên tool.
- Versioned tool: khi đổi schema của tool trong production, đặt tên mới (
get_weather_v2) thay vì sửa tại chỗ — tránh mô hình đang chạy bị lỗi giữa chừng.
Khác biệt giữa các nhà cung cấp
Function calling là một khái niệm chung, nhưng tên gọi và API cụ thể khác nhau giữa các nhà cung cấp:
- OpenAI gọi là
toolsvàtool_calls. Trường cũfunctionsđã deprecated. Hỗ trợ parallel tool calls từ GPT-4 Turbo trở đi. - Anthropic Claude dùng trường
toolsvớiinput_schemathay choparameters. Response cóstop_reason = "tool_use"và block kiểutool_use. Parallel tool calls hỗ trợ từ Claude 3.5 Sonnet. - Google Gemini dùng
functionDeclarationsvà response cófunctionCall. Cần gửi kết quả lại bằng rolefunction(không phảitool). - Các framework trung gian như LangChain, LlamaIndex, Vercel AI SDK, Mastra cung cấp abstraction chung — bạn viết tool một lần, chạy với nhiều nhà cung cấp. Đánh đổi: thêm một lớp phụ thuộc.
- Mô hình open-source (Llama 3.1+, Mistral, Qwen 2.5, DeepSeek) cũng hỗ trợ function calling, nhưng định dạng và chất lượng khác nhau. Nếu tự host, đọc kỹ tài liệu của từng model card — có thể cần template prompt riêng.
Quy trình gỡ lỗi khi agent hành xử sai
Khi agent của bạn gọi sai tool hoặc truyền sai tham số, đừng vội sửa prompt. Hãy làm theo thứ tự sau:
- Xem ngữ cảnh thật mà LLM thấy: in toàn bộ
messagesgửi vào API — bao gồm system prompt, lịch sử chat, mô tả tool. Nhiều lỗi đến từ việc system prompt xung đột với mô tả tool. - Kiểm tra JSON mà LLM sinh ra: tham số sai? LLM có hiểu ý tham số không? Có thể mô tả tham số cần ví dụ cụ thể hơn.
- Thử câu hỏi biến thể: đổi cách diễn đạt của người dùng để kiểm tra tool selection có robust không.
- Sửa mô tả tool, không sửa prompt chính: đa phần lỗi chọn tool được sửa ở cấp tool description, không phải ở cấp system prompt.
- Nếu vẫn sai, giảm số tool: tắt bớt tool không cần thiết. Càng ít tool, LLM càng khó nhầm.
- AI không tự thực thi hàm — nó chỉ sinh JSON mô tả ý định gọi hàm. Hệ thống bên ngoài mới là bên thực thi, rồi đưa kết quả lại cho AI tổng hợp.
- Vòng lặp chuẩn có 5 pha: nhận câu hỏi → LLM phân tích → sinh JSON tool_call → hệ thống thực thi → LLM tổng hợp. Cần gọi LLM HAI LẦN trong một vòng.
- Mỗi tool được khai báo bằng JSON Schema (name, description, parameters). Chất lượng mô tả quyết định LLM chọn đúng hay sai — viết như dạy intern.
- Function calling là nền tảng để xây AI Agent: mở cánh cửa cho LLM tương tác API, database, email, file — vượt qua giới hạn kiến thức huấn luyện.
- Parallel tool calls cho phép LLM gọi nhiều tool trong một lượt; thực thi song song giảm độ trễ. Chain of tools dùng khi hàm sau phụ thuộc kết quả hàm trước.
- Cảnh giác prompt injection qua tool input, luôn validate tham số ở phía ứng dụng, giới hạn số lượt lặp, và đặt human-in-the-loop cho hành động không hồi phục được.
Kiểm tra hiểu biết
Trong function calling, AI có trực tiếp thực thi hàm (gọi API, đọc file) không?