Hướng dẫn Pydantic AI: Xây dựng LLM Agent an toàn kiểu dữ liệu, từ chương trình đầu tiên đến khi lên production

Ai từng làm ứng dụng LLM đều biết, điều đau đầu nhất không phải là gọi API, mà là kết quả model trả về mỗi lần lại một kiểu. Pydantic AI mang tính năng kiểm tra kiểu dữ liệu của Pydantic vào quá trình phát triển Agent, giúp đầu ra có cấu trúc và các công cụ có thể được kiểm tra tĩnh. Bài viết này sẽ hướng dẫn bạn từ cài đặt đến nâng cao, kèm theo những cạm bẫy mà chính tôi từng vấp phải.

Lời nói đầu: Tại sao kết quả trả về từ mô hình lại khác nhau mỗi lần?

Nếu bạn từng làm việc với API của LLM, chắc hẳn bạn không còn lạ gì cảnh tượng này: Bạn bảo mô hình "hãy trả về một JSON chứa namescore", chín lần đầu mọi thứ đều suôn sẻ, nhưng đến lần thứ mười, nó chèn thêm câu "Vâng, đây là kết quả:" ngay trước JSON, và hàm json.loads() của bạn lập tức "toang". Sau đó, bạn bắt đầu viết một đống biểu thức chính quy (regex) để dọn dẹp chuỗi, rồi viết thêm một đống câu lệnh if để kiểm tra xem các trường có tồn tại hay không — cuối cùng, 70% mã nguồn trong "ứng dụng AI" của bạn chỉ dùng để vật lộn với các đầu ra không ổn định từ mô hình.

Bản thân tôi từng duy trì một dịch vụ phân loại nội bộ, và chỉ riêng việc xử lý tình huống mô hình thỉnh thoảng thiếu mất một trường dữ liệu đã khiến tôi phải làm thêm ba ngày liền. Sau đó, khi chuyển sang Pydantic AI, đoạn mã phòng thủ đó gần như được xóa sạch, bởi vì việc xác thực đã được framework gánh vác trực tiếp. Bài viết này sẽ trình bày chi tiết cách sử dụng nó.

Pydantic AI là gì?

Pydantic AI là một Agent framework Python do đội ngũ Pydantic phát triển. Bạn có thể đã từng thấy cái tên Pydantic ở đâu đó — phần lớn việc xác thực dữ liệu bên dưới của các SDK chính thức từ OpenAI, Anthropic, Google, cũng như LangChain và LlamaIndex, đều dựa vào nó. Nói cách khác, xét về mảng xác thực, họ là những người có đủ tư cách nhất trong hệ sinh thái này.

Triết lý thiết kế của nó rất giống với FastAPI: sử dụng các kiểu dữ liệu nguyên thủy của Python (type hints) để định nghĩa rõ ràng hành vi, còn lại hãy để framework lo. Cốt lõi chỉ có vài khái niệm — Agent, Tools (Công cụ), Dependencies (Tiêm phụ thuộc), và Structured Output (Đầu ra có cấu trúc). Bạn không cần phải nhớ một đống lớp trừu tượng do nó tự sáng chế ra, viết code trông hoàn toàn giống Python thông thường, nhưng IDE sẽ hỗ trợ bạn tự động gợi ý code (autocomplete), và các công cụ kiểm tra kiểu dữ liệu (như Pyright, mypy) có thể phát hiện lỗi ngay từ trước khi bạn chạy chương trình.

Nó mang tính chất model-agnostic, tức là không gắn chặt với một nhà cung cấp mô hình cụ thể nào. Hơn chục nhà cung cấp như OpenAI, Anthropic, Google, Groq,Cohere, Mistral, Ollama đều được hỗ trợ, và việc chuyển đổi mô hình thường chỉ cần thay đổi một chuỗi ký tự. Nếu bạn muốn hiểu rõ sự khác biệt giữa Agent và lệnh gọi API thông thường, trước tiên bạn có thể xem bài viết Agent AI là gì.

Có thể dùng để làm gì?

Nói một cách dễ hiểu, bất kỳ kịch bản nào "cần mô hình trả về kết quả đáng tin cậy" đều phù hợp:

  • Trích xuất có cấu trúc: Đưa một bức thư khiếu nại vào, yêu cầu nó xuất ra ba trường cảm xúc (sentiment), phân loại (category), mức độ khẩn cấp (urgency), đồng thời đảm bảo kiểu dữ liệu chính xác.
  • Phân loại và gán nhãn: Xử lý số lượng lớn tài liệu cần gán nhãn, với đầu ra bị giới hạn trong các Enum do bạn định nghĩa; nếu mô hình trả lời linh tinh sẽ bị chặn lại.
  • Agent dạng công cụ: Cho phép mô hình gọi các hàm của bạn — truy vấn cơ sở dữ liệu, gọi API thời tiết, tính toán toán học. Framework chịu trách nhiệm chuyển đổi các kiểu dữ liệu của hàm thành mô tả công cụ mà mô hình có thể hiểu được.
  • Hỏi đáp RAG: Kết hợp với tìm kiếm vector để xây dựng hệ thống hỏi đáp có cơ sở. Phần này bạn có thể tham khảo Hướng dẫn thực thi RAG của chúng tôi.

So với các framework lớn ôm đồm mọi thứ như LangChain, Pydantic AI được thiết kế có chủ ý rất gọn nhẹ. Nếu bạn chỉ đơn thuần muốn làm cho đầu ra của mô hình trở nên đáng tin cậy chứ không muốn phải học thuộc cả một hệ sinh thái chỉ vì một tính năng nhỏ, đường cong học tập của nó sẽ thân thiện hơn rất nhiều.

Cách sử dụng: Bắt đầu làm quen lần đầu

1. Cài đặt

bash
pip install pydantic-ai

Khuyên bạn nên tạo một môi trường ảo (virtual environment). Phiên bản Python từ 3.9 trở lên sẽ an toàn hơn.

2. Thiết lập khóa API

Lấy Anthropic làm ví dụ, hãy thiết lập một biến môi trường:

bash
export ANTHROPIC_API_KEY=khoa_api_cua_ban

Nếu dùng OpenAI thì thiết lập OPENAI_API_KEY, và tương tự cho các bên khác.

3. Viết Agent đầu tiên

python
from pydantic_ai import Agent

agent = Agent('anthropic:claude-sonnet-4-6')
result = agent.run_sync('Giải thích cơ sở dữ liệu vector bằng một câu')
print(result.output)

Tham số đầu tiên chính là tên mô hình, theo định dạng nha_cung_cap:mo_hinh. Nếu muốn đổi sang OpenAI, chỉ cần sửa thành 'openai:gpt-4o' hoặc tương tự, phần code còn lại không cần thay đổi — đây chính là lợi ích của tính năng model-agnostic.

4. Tạo cấu trúc cho đầu ra

Đây mới là điểm mấu chốt. Bạn định nghĩa một mô hình Pydantic để làm định dạng đầu ra:

python
from pydantic import BaseModel
from pydantic_ai import Agent

class Review(BaseModel):
sentiment: str # positive / negative / neutral
score: int # từ 1 đến 5
summary: str

agent = Agent('anthropic:claude-sonnet-4-6', output_type=Review)
result = agent.run_sync('Đồ ăn ở quán này ngon nhưng phải đợi gần một tiếng, hơi quá đáng')
print(result.output.score) # Nhận trực tiếp số nguyên, không cần tự parse
print(result.output.sentiment) # Nhận trực tiếp chuỗi ký tự

Nếu nội dung mô hình trả về không khớp với kiểu dữ liệu của Review, framework sẽ tự động gửi thông báo lỗi ngược lại và yêu cầu mô hình thử lại. Khi bạn nhận được result.output, nó đã là một đối tượng Python được xác thực, và IDE cũng sẽ giúp bạn gợi ý các trường dữ liệu.

5. Cung cấp công cụ cho Agent

python
from pydantic_ai import Agent

agent = Agent('anthropic:claude-sonnet-4-6')

@agent.tool_plain
def get_weather(city: str) -> str:
"""Tra cứu thời tiết hiện tại của thành phố được chỉ định"""
return f'{city} hiện tại 28 độ, trời quang'

result = agent.run_sync('Thời tiết Đài Bắc hiện tại thế nào?')
print(result.output)

Phần docstring không phải viết cho đẹp — nó sẽ trở thành phần mô tả công cụ mà mô hình nhìn thấy. Chú thích kiểu dữ liệu của hàm (city: str) cũng sẽ được chuyển đổi thành thông số kỹ thuật tham số mà mô hình hiểu được, và các tham số này đồng thời cũng được xác thực thông qua Pydantic.

Kỹ thuật nâng cao

Tiêm phụ thuộc (Dependency Injection) là tính năng bị đánh giá thấp nhất của nó. Bạn có thể sử dụng RunContext để truyền các đối tượng như kết nối cơ sở dữ liệu, danh tính người dùng, API client vào Agent và các công cụ một cách an toàn về mặt kiểu dữ liệu:

python
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext

@dataclass
class Deps:
user_id: int
db: object # Kết nối cơ sở dữ liệu của bạn

agent = Agent('anthropic:claude-sonnet-4-6', deps_type=Deps)

@agent.tool
def get_orders(ctx: RunContext[Deps]) -> str:
return f'Truy vấn đơn hàng của người dùng {ctx.deps.user_id}'

Khi viết kiểm thử (test), bạn chỉ cần thay thế db bằng một đối tượng giả lập mà không cần chạm đến cơ sở dữ liệu thật. Điều này cực kỳ quan trọng đối với việc viết unit test.

Truyền phát (Streaming): Để tạo hiệu ứng gõ chữ thời gian thực (real-time typing), hãy sử dụng agent.run_stream(). Nó sẽ vừa tạo ra vừa xác thực đầu ra có cấu trúc, giúp trải nghiệm người dùng tốt hơn rất nhiều.

Khả năng quan sát (Observability): Pydantic AI tích hợp rất mượt mà với Logfire — một công cụ cùng nhà phát triển. Sau khi kết nối, mỗi lần gọi mô hình, mỗi lần kích hoạt công cụ, số lượng token tiêu thụ, số giây thực thi đều có thể nhìn thấy rõ ràng. Việc khó debug nhất trong ứng dụng LLM chính là "tại sao mô hình lại trả lời như thế này", và với công cụ này, bạn sẽ không phải đoán mò nữa. Nếu bạn muốn lập kế hoạch cho Agent một cách toàn diện hơn, hãy đọc kết hợp cùng Hướng dẫn phát triển Agent AI của chúng tôi.

Lỗi thường gặp và những điểm cần lưu ý

  • Nghĩ rằng cứ thêm output_type là an toàn 100%: Framework sẽ yêu cầu mô hình thử lại khi xác thực thất bại, nhưng việc thử lại có giới hạn của nó. Nếu liên tục thất bại, nó sẽ văng ra ngoại lệ (exception), và bạn vẫn phải dùng try/except. Xác thực kiểu dữ liệu giúp giảm thiểu việc "dữ liệu bẩn lọt vào hệ thống", chứ không có nghĩa là "mô hình không bao giờ mắc lỗi".
  • Viết docstring cho công cụ một cách tùy tiện: Mô hình hoàn toàn dựa vào docstring để phán đoán thời điểm nên gọi công cụ. Nếu viết mơ hồ, mô hình sẽ gọi lung tung hoặc không gọi. Hãy coi nó như một cuốn sách hướng dẫn sử dụng được viết cho mô hình đọc.
  • Nhồi nhét quá nhiều logic vào công cụ nhưng không xử lý ngoại lệ: Nếu mã bên trong công cụ gặp lỗi, thông báo lỗi sẽ được trả về cho mô hình, khiến mô hình có thể cứ xoay vòng quanh lỗi đó và đốt sạch một lượng lớn token. Hãy tự xử lý các ngoại lệ cần chặn.
  • Bỏ qua chi phí: Việc thử lại đầu ra có cấu trúc và gọi công cụ qua nhiều vòng sẽ tiêu thụ token nhanh hơn bạn nghĩ. Trước khi đưa lên môi trườngProduction, nhất là phải kết nối hệ thống quan sát để kiểm tra chi phí thực tế.
  • Sử dụng nó như một framework lớn: Nó được thiết kế cố ý theo hướng gọn nhẹ. Nếu bạn cần điều phối đa bước phức tạp hoặc có sẵn một đống các trình kết nối (connector), có lẽ LlamaIndex hoặc các giải pháp khác sẽ tiết kiệm công sức hơn, đừng cố gượng ép.

Đánh giá từ TheAI學院

Thành thật mà nói, các framework Agent trên thị trường nhiều đến mức khiến người ta bị "rối loạn lựa chọn", nhưng Pydantic AI giải quyết được một vấn đề cực kỳ cụ thể và là cái bẫy mà mọi nhà phát triển LLM đều từng dính phải: Đầu ra không đáng tin cậy. Nó không cố gắng trở thành "framework mạnh nhất vũ trụ", mà chỉ đơn giản mang một việc mà cộng đồng Python vốn dĩ đã coi trọng — "an toàn kiểu dữ liệu" — bước một cách gọn gàng vào lĩnh vực phát triển AI. Đối với những ai đã quen với cách viết của FastAPI và Pydantic, hầu như không có chi phí học tập nào cả.

Nó không làm cho mô hình của bạn thông minh hơn, nhưng nó làm cho mã nguồn của bạn đáng tin cậy hơn — và điều sau mới thực sự là thứ cứu rỗi bạn sau khi đưa ứng dụng lên môi trường Production.

Nếu bạn chỉ làm bản demo để nghịch cho vui, bạn có thể sẽ không cảm nhận sâu sắc điều này; nhưng miễn là sản phẩm của bạn thực sự được đưa lên môi trường trực tuyến, được người dùng sử dụng và cần bảo trì lâu dài, giá trị của hai yếu tố "an toàn kiểu dữ liệu" và "khả năng quan sát" sẽ ngày càng trở nên rõ rệt theo thời gian.

Nguồn tham khảo

Câu hỏi thường gặp

Pydantic AI khác gì với LangChain, nên chọn cái nào?

Khác biệt lớn nhất là "trọng lượng". LangChain là một hệ sinh thái lớn với rất nhiều trình kết nối, tích hợp và lớp trừu tượng, phù hợp cho các dự án lớn cần điều phối phức tạp, nhưng đường cong học tập lại dốc. Pydantic AI được thiết kế có chủ ý là mỏng nhẹ, cốt lõi chỉ gồm một vài khái niệm như Agent, tool, Dependency Injection và cấu trúc đầu ra, chuyên về tính an toàn kiểu dữ liệu. Nếu nhu cầu của bạn là "làm cho đầu ra của model đáng tin cậy hơn, cách viết gần gũi với Python thuần", Pydantic AI dễ tiếp cận hơn nhiều; nếu bạn cần rất nhiều tích hợp sẵn có, LangChain sẽ tiện hơn. Cả hai không xung đột nhau, hãy tùy theo quy mô dự án mà lựa chọn.

Nhất định phải dùng model của OpenAI à? Có kết nối được model nội bộ (local) không?

Không cần. Pydantic AI mang tính trung lập về model (model-agnostic), hỗ trợ hơn chục nhà cung cấp như OpenAI, Anthropic, Google, Groq, Mistral,Cohere, Ollama... Việc đổi model thường chỉ cần sửa chuỗi ký tự đó lúc khởi tạo Agent. Muốn chạy model local, bạn có thể thông qua Ollama, trỏ chuỗi ký tự model về dịch vụ local là được, các phần khác của chương trình không cần thay đổi.

Đầu ra có cấu trúc có thực sự đảm bảo model không trả lời lung tung không?

Không thể đảm bảo bản thân model không sai sót, nhưng có thể đảm bảo "dữ liệu không khớp với kiểu bạn định nghĩa sẽ không lọt được vào hệ thống". Khi thứ model trả về không vượt qua được bước kiểm chứng của Pydantic, framework sẽ tự động trả thông báo lỗi ngược lại yêu cầu nó thử lại. Tuy nhiên, việc thử lại có giới hạn số lần, nếu liên tục thất bại sẽ văng ngoại lệ (exception), nên bạn vẫn phải dùng try/except để xử lý tình huống xấu nhất. Thứ mà nó giảm thiểu là rủi ro dữ liệu bẩn, chứ không phải vấn đề trí tuệ của model.

Người mới chưa từng viết Pydantic bao giờ, học cái này có khó không?

Nếu bạn biết Python cơ bản và chú thích kiểu dữ liệu (type hints), rào cản không cao. Cốt lõi của Pydantic là "dùng class để định nghĩa dữ liệu trông như thế nào", cách viết rất trực quan. Khuyên bạn nên dành 10 phút xem qua cách Pydantic định nghĩa BaseModel trước rồi hẵng quay lại viết Agent, sẽ mượt hơn rất nhiều. Rào cản về tư duy thực sự lại là tư duy thiết kế Agent và tool, bạn có thể học kèm theo hướng dẫn phát triển Agent của chúng tôi.

繁體中文版 →