AI Hunter
Member
Ở bài trước, chúng ta đã chạy được LLM Offline (Ollama) và đóng gói vào Docker. Hệ thống đã chạy, nhưng tôi cá là các bạn đang gặp một nỗi đau "thầm kín" mà ai làm việc với LLM cũng từng trải qua.
Đó là khi bạn bảo AI: "Hãy trích xuất thông tin user này ra JSON cho tao".
Nó trả lời: "Dạ vâng thưa sếp, đây là JSON của sếp: { ... }"
Và thế là code bạn crash sấp mặt vì cái hàm
không thể nuốt nổi cái đoạn "Dạ vâng thưa sếp..." ở đầu câu. 
Hôm nay, chúng ta sẽ giải quyết triệt để vấn đề này. Biến con AI từ một kẻ "ngẫu hứng" thành một cỗ máy trả về dữ liệu tuân thủ kỷ luật quân đội.
LLM (Large Language Model) bản chất là máy dự đoán từ tiếp theo. Nó rất giỏi sáng tạo, nhưng cực dốt trong việc tuân thủ cấu trúc cứng.
Trước đây, để xử lý, anh em dev phải dùng Regex (biểu thức chính quy) để "cạy" dữ liệu ra. Nhưng cách đó là hạ sách (bad practice).
Chúng ta sẽ sử dụng combo thần thánh: Pydantic (Thư viện validate dữ liệu số 1 Python) kết hợp với thư viện Instructor.
Tư duy ở đây là: Thay vì xin xỏ AI "làm ơn trả về JSON đúng dùm tao", chúng ta sẽ quăng cho nó một cái khuôn (Schema). Bắt buộc nó phải đổ dữ liệu vào đúng cái khuôn đó. Nếu sai? Bắt nó làm lại!
Chuẩn bị:
Ví dụ bài toán: Chúng ta có một đoạn văn bản tạp nham mô tả về một nhân viên. Nhiệm vụ là trích xuất ra Object `UserInfo` chuẩn chỉnh.
Đừng dùng Dictionary hay JSON trần trụi nữa. Hãy dùng Class.
[CODE lang="python"]
from pydantic import BaseModel, Field
from typing import List, Optional
# Đây là cái khuôn. AI bắt buộc phải đổ dữ liệu vào đây.
class UserInfo(BaseModel):
name: str = Field(..., description="Tên đầy đủ của nhân viên")
age: int = Field(..., description="Tuổi, phải là số nguyên")
role: str = Field(..., description="Vị trí công việc (Dev, Tester, PM...)")
skills: List[str] = Field(..., description="Danh sách các kỹ năng kỹ thuật")
is_active: bool = Field(True, description="Trạng thái làm việc, mặc định là True")
[/CODE]
Thay vì dùng `client.chat.completions.create` như mọi khi, chúng ta sẽ "vá" (patch) client bằng thư viện instructor.
[CODE lang="python"]
import instructor
from openai import OpenAI
# Khởi tạo client nhưng "độ" thêm instructor
# Nếu dùng Ollama thì đổi base_url="http://localhost:11434/v1"
client = instructor.from_client(OpenAI(api_key="sk-proj-xxxx"))
text_input = """
Ông Nguyễn Văn A, năm nay 28 cái xuân xanh.
Hiện đang làm Senior AI Engineer.
Ông này code Python cháy máy, biết cả Docker và Kubernetes.
Đang làm việc rất chăm chỉ.
"""
# Gọi AI nhưng yêu cầu response_model
try:
user_data = client.chat.completions.create(
model="gpt-4o-mini", # Hoặc "llama3" nếu chạy local
response_model=UserInfo, # <--- CHÌA KHÓA LÀ Ở ĐÂY
messages=[
{"role": "user", "content": text_input},
],
)
# Kết quả trả về là Object Python xịn, không phải string!
print(f"Tên: {user_data.name}")
print(f"Tuổi: {user_data.age}") # Kiểu int đàng hoàng
print(f"Skill đầu tiên: {user_data.skills[0]}")
# In ra JSON đẹp để ngắm
print(user_data.model_dump_json(indent=2))
except Exception as e:
print(f"Lỗi rồi đại vương ơi: {e}")
[/CODE]
Giả sử bạn muốn AI trích xuất tuổi, nhưng tên user trong văn bản lại ghi là "âm 5 tuổi" (chém gió). Chúng ta có thể chặn ngay từ tầng Model.
[CODE lang="python"]
from pydantic import field_validator
class UserInfo(BaseModel):
name: str
age: int
@field_validator('age')
def check_age(cls, v):
if v < 0:
raise ValueError("Tuổi không được âm đâu con bot ngốc!")
if v > 150:
raise ValueError("Người hay yêu tinh mà sống thọ thế?")
return v
[/CODE]
Nếu AI trích xuất ra số âm, Instructor sẽ ném cái lỗi `ValueError` kia ngược lại vào mặt con AI và bảo: "Mày làm sai rồi, sửa lại đi!".
Vậy là xong! Từ nay Jarvis của chúng ta đã có khả năng trả về dữ liệu có cấu trúc (Structured Data). Đây là tiền đề quan trọng nhất để xây dựng **Agentic Workflow** (Quy trình làm việc tự động) sau này.
Jarvis giờ đây không chỉ biết "chat", mà đã biết "làm". Nó có thể đọc email rồi điền form, đọc log lỗi rồi tạo ticket Jira, tất cả đều chuẩn format 100%.
Đó là khi bạn bảo AI: "Hãy trích xuất thông tin user này ra JSON cho tao".
Nó trả lời: "Dạ vâng thưa sếp, đây là JSON của sếp: { ... }"
Và thế là code bạn crash sấp mặt vì cái hàm
Mã:
json.loads()
Hôm nay, chúng ta sẽ giải quyết triệt để vấn đề này. Biến con AI từ một kẻ "ngẫu hứng" thành một cỗ máy trả về dữ liệu tuân thủ kỷ luật quân đội.
1. Vấn đề: Sự hỗn loạn của ngôn ngữ tự nhiên
LLM (Large Language Model) bản chất là máy dự đoán từ tiếp theo. Nó rất giỏi sáng tạo, nhưng cực dốt trong việc tuân thủ cấu trúc cứng.
- Bạn bảo nó trả về JSON, đôi khi nó quên đóng ngoặc `}`.
- Đôi khi nó tự ý đổi key `user_name` thành `username`.
- Đôi khi dữ liệu `age` (số) nó lại trả về "18 tuổi" (chuỗi).
Trước đây, để xử lý, anh em dev phải dùng Regex (biểu thức chính quy) để "cạy" dữ liệu ra. Nhưng cách đó là hạ sách (bad practice).
2. Giải pháp: Pydantic & Instructor
Chúng ta sẽ sử dụng combo thần thánh: Pydantic (Thư viện validate dữ liệu số 1 Python) kết hợp với thư viện Instructor.
Tư duy ở đây là: Thay vì xin xỏ AI "làm ơn trả về JSON đúng dùm tao", chúng ta sẽ quăng cho nó một cái khuôn (Schema). Bắt buộc nó phải đổ dữ liệu vào đúng cái khuôn đó. Nếu sai? Bắt nó làm lại!
3. Triển khai thực chiến
Chuẩn bị:
Mã:
pip install instructor pydantic openai
Ví dụ bài toán: Chúng ta có một đoạn văn bản tạp nham mô tả về một nhân viên. Nhiệm vụ là trích xuất ra Object `UserInfo` chuẩn chỉnh.
Bước 1: Định nghĩa cái "Khuôn" (Schema)
Đừng dùng Dictionary hay JSON trần trụi nữa. Hãy dùng Class.
[CODE lang="python"]
from pydantic import BaseModel, Field
from typing import List, Optional
# Đây là cái khuôn. AI bắt buộc phải đổ dữ liệu vào đây.
class UserInfo(BaseModel):
name: str = Field(..., description="Tên đầy đủ của nhân viên")
age: int = Field(..., description="Tuổi, phải là số nguyên")
role: str = Field(..., description="Vị trí công việc (Dev, Tester, PM...)")
skills: List[str] = Field(..., description="Danh sách các kỹ năng kỹ thuật")
is_active: bool = Field(True, description="Trạng thái làm việc, mặc định là True")
[/CODE]
Bước 2: "Nắn gân" LLM bằng Instructor
Thay vì dùng `client.chat.completions.create` như mọi khi, chúng ta sẽ "vá" (patch) client bằng thư viện instructor.
[CODE lang="python"]
import instructor
from openai import OpenAI
# Khởi tạo client nhưng "độ" thêm instructor
# Nếu dùng Ollama thì đổi base_url="http://localhost:11434/v1"
client = instructor.from_client(OpenAI(api_key="sk-proj-xxxx"))
text_input = """
Ông Nguyễn Văn A, năm nay 28 cái xuân xanh.
Hiện đang làm Senior AI Engineer.
Ông này code Python cháy máy, biết cả Docker và Kubernetes.
Đang làm việc rất chăm chỉ.
"""
# Gọi AI nhưng yêu cầu response_model
try:
user_data = client.chat.completions.create(
model="gpt-4o-mini", # Hoặc "llama3" nếu chạy local
response_model=UserInfo, # <--- CHÌA KHÓA LÀ Ở ĐÂY
messages=[
{"role": "user", "content": text_input},
],
)
# Kết quả trả về là Object Python xịn, không phải string!
print(f"Tên: {user_data.name}")
print(f"Tuổi: {user_data.age}") # Kiểu int đàng hoàng
print(f"Skill đầu tiên: {user_data.skills[0]}")
# In ra JSON đẹp để ngắm
print(user_data.model_dump_json(indent=2))
except Exception as e:
print(f"Lỗi rồi đại vương ơi: {e}")
[/CODE]
4. Tại sao cái này lại "Đỉnh"?
- Type Safety (An toàn kiểu dữ liệu): Bạn sẽ không bao giờ gặp cảnh code đang chạy thì crash vì `age` là string. Pydantic đảm bảo `age` luôn là `int`.
- Self-Correction (Tự sửa lỗi): Thư viện Instructor có cơ chế "Retry". Nếu AI trả về sai format, Instructor sẽ tự động gửi lại request kèm thông báo lỗi cho AI để nó sửa lại cho đúng. Anh em dev chỉ việc ngồi rung đùi hưởng thành quả.
- Chain-of-Thought trong Data: Bạn có thể thêm trường `reasoning` vào Pydantic model để bắt AI giải thích tại sao nó lại trích xuất như vậy trước khi đưa ra kết quả cuối cùng.
5. Ứng dụng nâng cao: Validator (Bắt lỗi nghiệp vụ)
Giả sử bạn muốn AI trích xuất tuổi, nhưng tên user trong văn bản lại ghi là "âm 5 tuổi" (chém gió). Chúng ta có thể chặn ngay từ tầng Model.
[CODE lang="python"]
from pydantic import field_validator
class UserInfo(BaseModel):
name: str
age: int
@field_validator('age')
def check_age(cls, v):
if v < 0:
raise ValueError("Tuổi không được âm đâu con bot ngốc!")
if v > 150:
raise ValueError("Người hay yêu tinh mà sống thọ thế?")
return v
[/CODE]
Nếu AI trích xuất ra số âm, Instructor sẽ ném cái lỗi `ValueError` kia ngược lại vào mặt con AI và bảo: "Mày làm sai rồi, sửa lại đi!".
Tổng kết
Vậy là xong! Từ nay Jarvis của chúng ta đã có khả năng trả về dữ liệu có cấu trúc (Structured Data). Đây là tiền đề quan trọng nhất để xây dựng **Agentic Workflow** (Quy trình làm việc tự động) sau này.
Jarvis giờ đây không chỉ biết "chat", mà đã biết "làm". Nó có thể đọc email rồi điền form, đọc log lỗi rồi tạo ticket Jira, tất cả đều chuẩn format 100%.
Bài viết liên quan