Khi tích hợp AI vào hệ thống backend, chúng ta không thể sử dụng kết quả dạng văn bản tự do của LLM để làm đầu vào cho các khối xử lý logic phần mềm tiếp theo. Nếu AI trả về lời dẫn giải thích lê thê, hệ thống lập tức bị lỗi khi phân tích cú pháp (Parsing Error).
Để biến LLM thành một mắt xích hoạt động tin cậy trong đường ống phần mềm, chúng ta cần hai vũ khí tối thượng: Structured Outputs (Đầu ra có cấu trúc) và Function Calling (Gọi hàm hệ thống). Bài học này sẽ hướng dẫn bạn cách ép mô hình trả về dữ liệu định dạng JSON chuẩn theo Schema định sẵn, cơ chế "mượn tay" API hệ thống để cập nhật thế giới thực thông qua Function Calling, và chu trình gọi hàm 4 bước khép kín.
12.1 Ép LLM trả về dữ liệu có cấu trúc: JSON Mode & Structured Outputs
Trong lập trình truyền thống, ta cần các kiểu dữ liệu tĩnh và cấu trúc đối tượng tường minh (như Class, Struct) để trao đổi thông tin giữa các dịch vụ. Mặc định, LLM sinh từ tự do và thích giao tiếp bằng ngôn ngữ tự nhiên. Để giải quyết bài toán này, các nhà phát triển API cung cấp hai tùy chọn:
-
JSON Mode: Cấu hình hệ thống bắt buộc mô hình phải trả về một chuỗi có cú pháp JSON hợp
lệ (ví dụ: mở ngoặc nhọn
{và đóng ngoặc nhọn}đầy đủ). Tuy nhiên, JSON Mode không đảm bảo cấu trúc các trường bên trong (keys) sẽ đúng 100% như bạn mong muốn. - Structured Outputs (Đầu ra có cấu trúc bắt buộc): Đây là cơ chế cao cấp hơn. Nhà phát triển gửi kèm một sơ đồ đặc tả JSON Schema chi tiết. Mô hình được cam kết toán học chỉ chọn các từ sinh ra sao cho khớp tuyệt đối với định dạng JSON Schema này (thông qua kỹ thuật hướng dẫn ngữ pháp - grammar-constrained decoding ở cấp độ phân phối xác suất logits).
response_format của OpenAI), mô hình thỉnh thoảng vẫn trả về lời dẫn dắt
kèm mã markdown:
Dưới đây là kết quả của bạn:
```json
{
"sender": "example@domain.com"
}
```
Khi đó, hàm json.loads() hoặc JSON.parse() trong code backend sẽ ngay lập tức
bị vỡ và ném ra ngoại lệ (exception) làm crash dịch vụ.
sender, urgency,
summary. JSON Mode trả về chuỗi:
{"sender": "a@b.com", "summary": "nội dung"} (thiếu urgency). Chạy thử kiểm
chứng bằng Python:
-
json.loads()chuỗi trên: thành công — đây là cú pháp JSON hợp lệ. -
Nhưng kiểm tra lại danh sách trường bắt buộc: thất bại — báo lỗi thiếu trường
['urgency'].
12.2 Cầu nối với thế giới ngoài: Cơ chế Function Calling
Một mô hình ngôn ngữ lớn (LLM) thuần túy giống như một bộ óc bị cô lập: nó không thể tự truy cập Internet thời gian thực, không thể đọc cơ sở dữ liệu nội bộ của bạn, không thể gửi email hay thực hiện giao dịch ngân hàng.
Function Calling (Gọi hàm) là cơ chế cho phép ta mô tả các hàm phần mềm cục bộ (tên hàm, chức năng, danh sách tham số) cho LLM biết. Khi người dùng đưa ra một câu hỏi yêu cầu hành động hoặc thông tin thời gian thực, LLM tự động nhận diện và thay vì trả lời trực tiếp bằng văn bản, nó sẽ trả về một yêu cầu gọi hàm (Tool Call).
Đầu ra của mô hình lúc này là một cấu trúc JSON chỉ định:
- Tên hàm cần gọi (ví dụ:
send_email_notification). -
Các giá trị đối số (arguments) được trích xuất trực tiếp từ câu hỏi của người dùng (ví
dụ:
{"to": "customer@gmail.com", "subject": "Cảnh báo khẩn cấp"}).
tools gửi lên APIsend_alert_email tồn tại, lập trình viên phải mô tả nó dưới dạng JSON
Schema và gửi kèm trong tham số tools của request — không phải gửi mã nguồn Python thật:
{
"type": "function",
"function": {
"name": "send_alert_email",
"description": "Gửi email cảnh báo khẩn cấp tới quản trị viên hệ thống.",
"parameters": {
"type": "object",
"properties": {
"recipient_email": { "type": "string", "description": "Địa chỉ email người nhận cảnh báo." },
"subject": { "type": "string", "description": "Tiêu đề email cảnh báo." },
"alert_content": { "type": "string", "description": "Nội dung chi tiết sự cố." }
},
"required": ["recipient_email", "subject", "alert_content"]
}
}
}
LLM chỉ đọc phần mô tả (description) và cấu trúc tham số (parameters) này để
quyết định có nên gọi hàm hay không và điền giá trị nào vào từng trường — nó không hề nhìn thấy, và cũng
không cần biết, bên trong hàm send_alert_email thực sự làm gì.
12.3 Chu trình Tool Call hoàn chỉnh (Loop)
Để hoàn thành một tác vụ gọi hàm, hệ thống của bạn phải vận hành theo một vòng lặp 4 bước khép kín như sau:
🔄 Sơ đồ chu trình gọi hàm 4 bước:
- Bước 1 (User -> LLM): Người dùng gửi yêu cầu kèm theo danh sách mô tả các công cụ (Tools) mà ứng dụng cung cấp.
-
Bước 2 (LLM -> App): LLM nhận diện cần dùng công cụ, trả về cấu
trúc yêu cầu gọi hàm
tool_calls(chứa tên hàm và tham số JSON). Trạng thái suy luận tạm dừng. -
Bước 3 (App -> Local DB/API): Code của bạn phân tích
tool_calls, thực thi hàm thực tế trong môi trường an toàn và thu về kết quả thô (Observation). -
Bước 4 (App -> LLM): Gửi kết quả thô của hàm ngược lại cho LLM
kèm nhãn vai trò
toolđể LLM tổng hợp, đọc hiểu và sinh ra câu trả lời tự nhiên cuối cùng cho người dùng.
12.4 Dự án thực hành bài 12: Trình phân loại email và tự động lưu Database
Dự án thực hành của bài học này là lập trình một Trình xử lý Email thông minh bằng Python.
Chương trình nhận vào nội dung email thô từ khách hàng, ép mô hình LLM trích xuất thông tin có cấu trúc (người gửi, nội dung tóm tắt, độ khẩn cấp). Nếu phát hiện độ khẩn cấp là High (Nguy cấp), hệ thống sẽ kích hoạt cơ chế Function Calling để tự động chạy hàm gửi cảnh báo khẩn cấp đến quản trị viên và ghi nhận vào lịch sử hệ thống.
import json
# 1. Định nghĩa các hàm công cụ (Tools) cục bộ của hệ thống
def send_alert_email(recipient_email, subject, alert_content):
"""
Hàm cục bộ thực hiện hành động gửi email cảnh báo thật.
"""
print(f"\n⚡ [HÀNH ĐỘNG HỆ THỐNG] Đang gửi Email cảnh báo nguy cấp...")
print(f" - Gửi tới: {recipient_email}")
print(f" - Tiêu đề: {subject}")
print(f" - Nội dung: {alert_content}")
print(f"✔️ Gửi email thành công!")
return json.dumps({"status": "delivered", "recipient": recipient_email})
# 2. Giả lập Client API hỗ trợ cả Structured Outputs và Function Calling
class MockStructuredLLM:
def __init__(self):
pass
def process_request(self, messages, tools=None):
# Lấy nội dung email của User gửi lên
user_content = messages[-1]["content"]
# Giả lập phân tích nội dung email để trả về dữ liệu có cấu trúc (Structured Output)
print("\n[AI] Đang phân tích email bằng Structured Outputs...")
# Mẫu 1: Email bình thường
if "cảm ơn" in user_content.lower() or "hỏi thông tin" in user_content.lower():
structured_data = {
"sender": "khachhang_normal@gmail.com",
"urgency": "low",
"summary": "Khách gửi email cảm ơn dịch vụ và hỏi thêm thông tin khóa học mới."
}
return {"type": "structured", "content": structured_data}
# Mẫu 2: Email nguy cấp (Có lỗi hệ thống hoặc yêu cầu hoàn tiền gấp)
else:
structured_data = {
"sender": "dev_alerts@company.com",
"urgency": "high",
"summary": "Hệ thống gặp sự cố Database sập nguồn nghiêm trọng lúc 3h sáng."
}
# Nếu khẩn cấp ở mức High, AI quyết định kích hoạt Function Calling
print("[AI] Phát hiện email khẩn cấp! Quyết định kích hoạt Tool Call...")
tool_call = {
"name": "send_alert_email",
"arguments": {
"recipient_email": "admin_ops@company.com",
"subject": "CẢNH BÁO NGUY CẤP: " + structured_data["summary"][:40],
"alert_content": f"Báo cáo từ {structured_data['sender']}: {structured_data['summary']}"
}
}
return {
"type": "tool_call",
"content": structured_data,
"tool_call": tool_call
}
# 3. Quản lý chu trình xử lý (Loop)
class EmailProcessingPipeline:
def __init__(self):
self.model = MockStructuredLLM()
# Đăng ký các hàm thực thi cục bộ
self.available_tools = {
"send_alert_email": send_alert_email
}
def run(self, raw_email_body):
print(f"\n--- Đang xử lý email mới ---")
print(f"Nội dung thô: '{raw_email_body}'")
# Khởi tạo tin nhắn gửi lên AI
messages = [
{"role": "system", "content": "Bạn là bộ lọc email phân loại thông tin chuẩn xác."},
{"role": "user", "content": raw_email_body}
]
# Bước 1 & 2: Gửi yêu cầu và nhận phản hồi của AI
ai_response = self.model.process_request(messages)
# In ra thông tin có cấu trúc đã trích xuất
metadata = ai_response["content"]
print(f"📊 [Dữ liệu cấu trúc trích xuất]:")
print(f" - Sender: {metadata['sender']}")
print(f" - Urgency: {metadata['urgency']}")
print(f" - Summary: {metadata['summary']}")
# Bước 3: Kiểm tra xem AI có yêu cầu gọi hàm (Function Call) hay không
if ai_response["type"] == "tool_call":
tool_call_request = ai_response["tool_call"]
func_name = tool_call_request["name"]
func_args = tool_call_request["arguments"]
# Truy vấn hàm cục bộ đã đăng ký
if func_name in self.available_tools:
local_func = self.available_tools[func_name]
# Thực thi hàm cục bộ với các đối số do AI cung cấp
observation = local_func(
recipient_email=func_args["recipient_email"],
subject=func_args["subject"],
alert_content=func_args["alert_content"]
)
# Bước 4: Gửi kết quả quan sát ngược lại cho AI để tổng hợp câu trả lời cuối
messages.append({
"role": "assistant",
"content": f"Yêu cầu gọi hàm {func_name} với đối số {func_args}"
})
messages.append({
"role": "tool",
"name": func_name,
"content": observation
})
print("\n[AI] Đang tổng hợp câu trả lời cuối dựa trên kết quả gọi hàm...")
final_answer = f"Đã trích xuất thông tin email thành công và tự động kích hoạt gửi cảnh báo khẩn cấp tới {func_args['recipient_email']}. Trạng thái: {observation}."
print(f"Phản hồi cuối của hệ thống: {final_answer}")
else:
print(f"Lỗi: Không tìm thấy hàm cục bộ nào có tên '{func_name}'.")
else:
print("\n[AI] Email bình thường. Không cần thực thi hành động khẩn cấp.")
print("Phản hồi cuối của hệ thống: Đã lưu thông tin email bình thường vào database.")
if __name__ == "__main__":
pipeline = EmailProcessingPipeline()
# Kịch bản 1: Email hỏi đáp bình thường
normal_email = "Xin chào, tôi cảm ơn đội ngũ kỹ thuật rất nhiều. Tôi muốn hỏi thêm thông tin về lịch khai giảng khóa sau."
pipeline.run(normal_email)
# Kịch bản 2: Email báo lỗi hệ thống khẩn cấp
critical_email = "Cảnh báo khẩn cấp: Cơ sở dữ liệu chính của hệ thống đang bị treo và sập nguồn, không thể kết nối từ 3 giờ sáng!"
pipeline.run(critical_email)
Tóm tắt bài học & Cầu nối kiến thức
- Đạt được: Ép LLM sinh đầu ra có cấu trúc chuẩn xác theo JSON Schema định nghĩa sẵn.
- Đạt được: Làm chủ cơ chế gọi hàm (Function Calling) giúp LLM tự động kích hoạt các công cụ và kết nối với cơ sở dữ liệu.
Cầu nối bài tiếp theo: Việc gọi API của OpenAI/Gemini rất tốn phí. Để làm chủ dòng chảy dữ liệu nội bộ và bảo mật tuyệt đối, chúng ta cần học cách chạy các LLM cục bộ (Local LLM) qua Ollama ở Bài số 13.
Tải file code thực hành minh họa bài học
File Python email_processor.py — mã nguồn xây dựng quy trình phân loại email có cấu trúc và
tích hợp chu trình gọi hàm khẩn cấp (chạy python email_processor.py):
📖 Tài liệu tham khảo
- Structured Outputs — Hướng dẫn đặc tả cấu hình ép kiểu dữ liệu đầu ra JSON Schema (OpenAI Docs)
- JSON Schema Specification — Trang chủ đặc tả tiêu chuẩn cấu trúc dữ liệu JSON Schema chính quy
- Function Calling via API — Tài liệu hướng dẫn thiết lập danh sách công cụ và chu trình xử lý tool loop (OpenAI Docs)
Bình luận