[Tech Blog #15 | AI & GenAI] Làm Chủ Function Calling Với LLM API: Biến AI Thành "Cỗ Máy Thực Thi Hành Động" Bằng Python
![[Tech Blog #15 | AI & GenAI] Làm Chủ Function Calling Với LLM API: Biến AI Thành "Cỗ Máy Thực Thi Hành Động" Bằng Python](/uploads/ai-images/cover-function-calling-llm-python.png)
1. Đặt vấn đề: Giới hạn của một "Chatbot chỉ biết nói suông"
Ở các bài viết trước, chúng ta đã biết cách dùng Prompt Engineering ([Tech Blog #03]) và xây dựng hệ thống RAG tra cứu tài liệu tĩnh ([Tech Blog #11]).
Tuy nhiên, trong các bài toán thực tế của doanh nghiệp, người dùng không chỉ hỏi về chính sách hay kiến thức sách vở. Họ đòi hỏi AI phải tương tác được với thế giới thực:
- "Kiểm tra giúp tôi đơn hàng #DH9981 đã giao đến đâu rồi?"
- "Hôm nay tài khoản của tôi còn bao nhiêu tiền?"
- "Gửi email báo cáo doanh thu tuần này cho anh Nam phòng Kế toán."
Bản thân LLM là một mô hình toán học tĩnh khép kín. Nó không có quyền kết nối trực tiếp vào Database, không có quyền bấm gửi email, và cũng không thể tự chạy code.
Để giải quyết bài toán này, các nhà phát triển đã trang bị cho LLM một siêu năng lực có tên là Function Calling (hoặc Tool Calling).
2. Function Calling là gì? Bản chất của "Bộ não điều phối"
Một hiểu lầm rất phổ biến của người mới bắt đầu: "Function Calling nghĩa là LLM tự chạy code trên máy chủ của bạn". Hoàn toàn sai!
LLM không bao giờ tự thực thi code. Thay vào đó, nó đóng vai trò là một Bộ não điều phối (Brain / Orchestrator):
- Bạn đưa cho LLM một danh sách "Thực đơn các công cụ" mà bạn có (ví dụ: hàm
tra_cuu_don_hang(ma_don)). - Khi người dùng hỏi: "Đơn #DH9981 ở đâu rồi?", LLM sẽ đọc câu hỏi, hiểu ý định của người dùng, và trả về một gói tin JSON thông báo:
"Này lập trình viên, tôi cần bạn chạy hàmtra_cuu_don_hangvới tham sốma_don = 'DH9981'". - Mã nguồn Python của bạn nhận lệnh, thực thi hàm thật (chọc vào Database thật), lấy ra kết quả:
{"trang_thai": "Dang giao", "shipper": "0901234567"}. - Bạn ném kết quả thực tế này ngược lại cho LLM. LLM đọc kết quả và viết thành câu trả lời lịch sự cho người dùng: "Đơn hàng #DH9981 của bạn đang được giao, số điện thoại shipper là 0901234567 nhé!".
[Người dùng] ──── (1. Hỏi câu hỏi) ────> [LLM (Bộ Não)]
│
(2. Trả về tên hàm + tham số JSON)
│
▼
[Database / API Thật] <── (3. Python chạy hàm thật) ──┘
│
└─── (4. Nạp kết quả DB vào lại) ───> [LLM (Tổng hợp câu trả lời)]
│
[Người dùng] <──── (5. Nhận kết quả mượt mà) ────────────┘
3. Bảng so sánh: Prompting thuần túy vs. RAG vs. Function Calling
| Tiêu chí | Prompting Thuần Túy | RAG (Retrieval-Augmented) | Function Calling (Tool Use) |
|---|---|---|---|
| Mục đích chính | Xử lý văn bản có sẵn trong ngữ cảnh. | Tra cứu kho tài liệu nội bộ, sách cẩm nang tĩnh. | Lấy dữ liệu thời gian thực (Real-time), gọi API, thực thi tác vụ. |
| Nguồn dữ liệu | Trí nhớ có sẵn của LLM. | Vector Database (Embeddings). | Cơ sở dữ liệu quan hệ (SQL), REST APIs, CRM, ERP. |
| Tính hai chiều | ❌ Chỉ đọc (Read-only). | ❌ Chỉ đọc (Read-only). | ✅ Đọc và Ghi (Read & Write): Có thể cập nhật DB, tạo vé hỗ trợ, gửi email. |
| Độ chính xác phép tính | ⚠️ LLM tính toán số học rất dễ sai. | ⚠️ Vẫn phụ thuộc vào việc suy luận của LLM. | 🟢 Chính xác 100% vì việc tính toán do hàm Python thực hiện. |
4. Thực hành thực chiến: Xây dựng Chatbot tra cứu đơn hàng bằng Python
Dưới đây là đoạn mã Python hoàn chỉnh (sử dụng chuẩn OpenAI SDK / tương thích với Gemini, OpenRouter) mô phỏng quy trình 2 lượt (Two-turn conversation) của Function Calling:
import os
import json
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "your-api-key-here"))
# ==============================================================================
# BƯỚC 1: ĐỊNH NGHĨA HÀM PYTHON THỰC TẾ (MOCK DATABASE)
# ==============================================================================
def get_order_status(order_id: str) -> str:
"""Hàm Python thực tế: Truy vấn trạng thái đơn hàng từ CSDL."""
print(f"\n[HỆ THỐNG]: Đang thực thi truy vấn Database thật cho đơn hàng '{order_id}'...")
# Giả lập bảng dữ liệu trong CSDL
mock_db = {
"DH_101": {"status": "Đang vận chuyển", "carrier": "Giao Hàng Nhanh", "estimated_delivery": "Hôm nay, 16:00"},
"DH_102": {"status": "Đã giao thành công", "carrier": "Viettel Post", "received_by": "Nguyễn Văn A"},
"DH_103": {"status": "Đã bị hủy", "reason": "Hết hàng trong kho"}
}
order_info = mock_db.get(order_id.upper())
if order_info:
return json.dumps(order_info, ensure_ascii=False)
else:
return json.dumps({"error": f"Không tìm thấy đơn hàng {order_id} trong hệ thống"}, ensure_ascii=False)
# ==============================================================================
# BƯỚC 2: KHAI BÁO SCHEMA CÔNG CỤ (TOOLS) CHO LLM HIỂU
# ==============================================================================
tools = [
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Tra cứu thông tin trạng thái vận chuyển và người giao của một đơn hàng cụ thể.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Mã định danh của đơn hàng, ví dụ: DH_101, DH_102"
}
},
"required": ["order_id"] # Tham số bắt buộc phải có
}
}
}
]
# Ánh xạ tên hàm với hàm thực thi trong Python
available_functions = {
"get_order_status": get_order_status
}
# ==============================================================================
# BƯỚC 3: VÒNG LẶP ĐIỀU PHỐI ĐÀM THOẠI (ORCHESTRATION LOOP)
# ==============================================================================
def run_customer_service_agent(user_prompt: str):
print(f"\n>>> [NGƯỜI DÙNG]: {user_prompt}")
messages = [
{"role": "system", "content": "Bạn là nhân viên chăm sóc khách hàng thương mại điện tử chuyên nghiệp và ân cần."},
{"role": "user", "content": user_prompt}
]
# LƯỢT 1: Gửi câu hỏi kèm thực đơn công cụ cho LLM
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools,
tool_choice="auto" # Để LLM tự quyết định có cần gọi công cụ hay không
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
# KIỂM TRA: LLM có yêu cầu gọi công cụ nào không?
if tool_calls:
# Lưu lại tin nhắn yêu cầu của LLM vào lịch sử hội thoại
messages.append(response_message)
# Duyệt qua từng công cụ mà LLM yêu cầu gọi
for tool_call in tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
print(f"[LLM QUYẾT ĐỊNH]: Cần gọi hàm '{function_name}' với tham số: {function_args}")
# Chạy hàm Python thực tế
function_response = function_to_call(
order_id=function_args.get("order_id")
)
# Gửi kết quả thực tế ngược lại cho LLM
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response
})
# LƯỢT 2: LLM đọc kết quả từ công cụ và sinh câu trả lời tự nhiên cuối cùng
second_response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
)
final_answer = second_response.choices[0].message.content
print(f"\n<<< [TRỢ LÝ AI]:\n{final_answer}")
else:
# Nếu câu hỏi thông thường không cần gọi công cụ (ví dụ: 'Chào bạn')
print(f"\n<<< [TRỢ LÝ AI]:\n{response_message.content}")
# ==============================================================================
# CHẠY THỬ NGHIỆM THỰC TẾ
# ==============================================================================
if __name__ == "__main__":
# Test case 1: Câu hỏi cần gọi Tool tra cứu Database
run_customer_service_agent("Alo shop ơi, kiểm tra hộ mình đơn hàng DH_101 xem khi nào giao tới nơi với?")
# Test case 2: Câu hỏi giao tiếp bình thường (LLM tự trả lời mà không gọi Tool)
run_customer_service_agent("Shop bạn làm việc từ mấy giờ đến mấy giờ vậy?")
5. Câu hỏi phỏng vấn thực tế (Interview Q&A)
Câu 1 (Mức độ: Junior / Entry-level)
Câu hỏi: "Function Calling trong LLM thực chất hoạt động như thế nào? Mô hình có tự động kết nối và chạy mã nguồn trên server của bạn không?"
Gợi ý trả lời:
"Dạ không, LLM hoàn toàn không tự động kết nối hay chạy mã nguồn trên server của chúng ta.
Bản chất của Function Calling là: Dựa trên mô tả hàm (JSON Schema) mà lập trình viên cung cấp, LLM chỉ đóng vai trò phân tích ngôn ngữ tự nhiên để nhận diện ý định (Intent Recognition) và trích xuất đúng các tham số đầu vào dưới dạng JSON.
Việc thực thi mã nguồn thực tế diễn ra 100% trong môi trường ứng dụng của lập trình viên. Ứng dụng nhận chuỗi JSON từ LLM, tự kiểm tra tính hợp lệ và quyền bảo mật, sau đó tự chạy hàm Python và gửi kết quả dạng text ngược trở lại cho LLM tổng hợp."
Câu 2 (Mức độ: Junior / Mid-level)
Câu hỏi: "Làm thế nào để bạn ngăn chặn các rủi ro bảo mật (như SQL Injection hoặc rò rỉ dữ liệu) khi cho phép LLM gọi các hàm thao tác với Database?"
Gợi ý trả lời:
*"Để bảo vệ an toàn cho hệ thống khi tích hợp Function Calling, em luôn áp dụng nguyên tắc Zero Trust (Không tin tưởng đầu ra của LLM):
- Kiểm tra hợp lệ tham số nghiêm ngặt (Input Validation): Sử dụng các thư viện như
Pydanticđể kiểm tra kiểu dữ liệu, độ dài và định dạng của các tham số do LLM sinh ra trước khi đưa vào hàm thực thi.- Sử dụng Parameterized Queries: Tuyệt đối không cộng chuỗi SQL thô từ kết quả của LLM để tránh lỗ hổng SQL Injection.
- Phân quyền tối thiểu (Least Privilege): Tài khoản Database dùng cho các hàm của LLM chỉ nên có quyền Đọc (
SELECT), nghiêm cấm cấp quyềnDROPhayALTER.- Cơ chế Human-in-the-loop (Xác nhận từ người dùng): Đối với các hành động mang tính chất thay đổi trạng thái nhạy cảm (như chuyển tiền, xóa tài khoản, hủy đơn), hệ thống bắt buộc phải yêu cầu người dùng xác nhận rõ ràng trước khi thực thi hàm."*
6. Key Takeaways (Ghi nhớ nhanh)
- LLM không tự chạy code: Nó chỉ đóng vai trò phân tích ý định và trích xuất tham số thành định dạng JSON chuẩn.
- Khai báo mô tả (Description) rõ ràng: Mô tả của hàm và các tham số chính là "lời thì thầm" hướng dẫn LLM biết khi nào nên gọi công cụ. Viết mô tả càng rõ ràng thì LLM chọn công cụ càng chuẩn xác.
- Quy trình 2 lượt (Two-turn loop): Gửi câu hỏi Nhận lệnh gọi Tool Thực thi code Python thật Trả kết quả lại cho LLM để tổng hợp câu trả lời tự nhiên.
- Viên gạch nền tảng của AI Agents: Muốn xây dựng các trợ lý AI tự động hóa quy trình phức tạp (Agentic Workflows), làm chủ Function Calling là yêu cầu bắt buộc số một.