Build agent Python tự sửa lỗi output của Gemini

Build agent Python gọi Gemini, validate JSON bằng Pydantic và retry kèm thông báo lỗi khi response sai schema.

Đã duyệt độc lập · cập nhật 25/10/2026 · 9 min

1. Khi AI quên một field: bài toán structured output

Video mở đầu bằng một câu hỏi: nếu bạn có thể bắt AI trả dữ liệu theo một format cụ thể, thay vì chỉ đưa lại một khối văn bản, thì sao? Người trình bày của kênh Coding Hives đặt tình huống: bạn đang build một ứng dụng AI và cần response chứa đủ bốn thứ là topic, summary, key points và next steps. Chuyện gì xảy ra nếu AI quên một field, hoặc trả về sai kiểu dữ liệu? Ứng dụng của bạn có thể hỏng, đúng không? (video đăng ngày 14/10/2026)

Để giải bài toán đó, video build một structured output AI agent bằng Python, Gemini API của Google và Pydantic. Agent sẽ validate response của AI, xử lý lỗi, và tự động retry khi response không đạt yêu cầu. Theo lời giới thiệu, project được build và giải thích trong Kiro, IDE AI của AWS (video đăng ngày 14/10/2026).

Response của AI topic ✓summary ✓key_points: "..." (sai kiểu)next_steps (thiếu) Kiểm tratrước khi dùng Ứng dụng không kiểm tra:có thể hỏng có kiểm tra: chỉ nhậndữ liệu đúng cấu trúc
Hình 1.1Bài toán mở đầu video: một response thiếu field hoặc sai kiểu dữ liệu có thể làm hỏng ứng dụng, nên cần một bước kiểm tra đứng giữa AI và phần còn lại của code (theo video đăng ngày 14/10/2026).

2. Demo nhanh: "How does HTTP work?"

Trước khi vào code, người trình bày cho xem thành phẩm. Họ nhập một câu hỏi đơn giản: "How does HTTP work?". Output có đủ topic, summary, difficulty (mức độ khó), key points, một example và next steps (demo trong video đăng ngày 14/10/2026).

Nhưng điểm quan trọng nằm ở chỗ khác: ứng dụng không đơn giản tin vào bất cứ thứ gì Gemini trả về. Nó kiểm tra response với một Pydantic model trước khi hiển thị. Trong log có dòng "Validation succeeded", nghĩa là response đã qua các quy tắc validation ngay ở lần thử đầu tiên. Nếu response không đạt các quy tắc đó, logic retry có thể thử sửa lại (demo trong video đăng ngày 14/10/2026).

Log INFO: Request started với model gemini-2.5-flash và Validation succeeded on attempt 1, rồi khung Validated Response
Đầu phần output: hai dòng log "Request started" (model gemini-2.5-flash) và "Validation succeeded on attempt 1" với difficulty "Beginner", rồi khung "Validated Response" in JSON đã qua kiểm tra (demo trong video đăng ngày 14/10/2026).

3. Luồng xử lý: hỏi, parse, validate, retry

Tiếp theo là cách mọi thứ vận hành. Đầu tiên, người dùng nhập một topic. Sau đó, ứng dụng gửi câu hỏi tới Gemini và yêu cầu response theo một format JSON cụ thể. Người trình bày giải thích JSON là một cách tổ chức dữ liệu bằng các field và value. Nhưng JSON hợp lệ không tự động có nghĩa là dữ liệu đáp ứng các yêu cầu của mình (theo video đăng ngày 14/10/2026).

Ví dụ, ứng dụng có thể yêu cầu difficulty phải là beginner, intermediate hoặc advanced. Nếu response chứa một giá trị khác, Pydantic có thể từ chối nó. Khi parse hoặc validation thất bại, agent có thể gửi lỗi ngược lại cho Gemini và yêu cầu một response đã sửa. Số lần thử có giới hạn để ứng dụng không retry mãi mãi. Nếu validation thành công thì hiển thị kết quả; nếu mọi lần thử đều thất bại thì hiện một thông báo lỗi hữu ích. Theo người trình bày, đó là ý tưởng cốt lõi của project (video đăng ngày 14/10/2026).

Sơ đồ trong Word: User enters a question, Gemini generates a response, Parse JSON and validate with Pydantic, rồi Valid hoặc Invalid
1. người dùng nhập câu hỏi, 2. Gemini tạo response, 3. parse JSON và validate bằng Pydantic, rồi rẽ hai nhánh: Valid thì hiển thị kết quả, Invalid thì sửa và thử lại (theo màn hình trong video đăng ngày 14/10/2026).

4. Cấu trúc project: mỗi file một trách nhiệm

Project được chia thành các file riêng để mỗi phần có một trách nhiệm rõ ràng (theo video đăng ngày 14/10/2026):

  • schemas.py: định nghĩa cấu trúc mà response của AI bắt buộc phải theo.
  • config.py: giữ các setting và Gemini API key.
  • agent.py: nơi chứa request tới Gemini, phần parse response, validation và logic retry (theo video đăng ngày 14/10/2026).
  • main.py: giao diện terminal, nơi người dùng nhập câu hỏi.
  • requirements.txt: các dependency, và thư mục tests cho test tự động.

Người trình bày cho rằng tách các trách nhiệm như vậy giúp ứng dụng dễ hiểu, dễ test và dễ bảo trì hơn (video đăng ngày 14/10/2026).

File requirements.txt mở trong Kiro với google-genai, pydantic, python-dotenv và pytest
File requirements.txt trên màn hình: comment ghi các version đã kiểm tra với Python 3.14 trên Windows, rằng google-generativeai (SDK cũ) đã deprecated và project dùng google-genai; các dòng pin là google-genai==2.30.1, pydantic==2.14.1, python-dotenv==1.2.4, pytest==9.1.1 (theo màn hình trong video đăng ngày 14/10/2026). Cây thư mục bên trái có đủ các file vừa kể.

Comment đầu file ghi lệnh cài toàn bộ dependency (theo màn hình trong video đăng ngày 14/10/2026):

pip install -r requirements.txt

5. schemas.py: Pydantic như một bản hợp đồng

Bắt đầu với schema. Người trình bày giới thiệu Pydantic là một thư viện Python giúp validate dữ liệu, và gợi ý nghĩ về schema như một bản hợp đồng: nó quy định response phải chứa những gì và giá trị nào được chấp nhận (theo video đăng ngày 14/10/2026).

Trong project này, response có các field topic, summary, difficulty, key points, example và next steps. Mỗi field có một kiểu xác định, ví dụ topic là một string, còn key points là một list các string. Field difficulty bị giới hạn ở ba giá trị cho phép là beginner, intermediate và advanced; nếu Gemini trả về một giá trị ngoài ba lựa chọn đó thì validation phải thất bại. Cũng có thể thêm quy tắc về số phần tử trong một list. Theo người trình bày, điều này hữu ích vì ứng dụng không chỉ cần một câu trả lời, nó cần một câu trả lời có cấu trúc đoán trước được (video đăng ngày 14/10/2026).

Class TopicExplanation trong schemas.py với các Field topic, summary, difficulty
Class TopicExplanation(BaseModel): topic là field bắt buộc (dấu ... nghĩa là không có giá trị mặc định) với min_length=1; summary cần ít nhất 10 ký tự, "không chỉ là Yes hay OK"; difficulty phải đúng một trong 'Beginner', 'Intermediate', 'Advanced' (theo màn hình trong video đăng ngày 14/10/2026).

Phần quy tắc về độ dài list được viết bằng custom validator. Ví dụ dưới đây là validator cho next_steps, chép từ màn hình (video đăng ngày 14/10/2026):

@classmethod
def next_steps_must_have_2_to_4_items(cls, value: List[str]) -> List[str]:
    """
    Custom validator: next_steps must contain between 2 and 4 items.
    """
    if len(value) < 2:
        raise ValueError(
            f"next_steps must have at least 2 items, but got {len(value)}"
        )
    if len(value) > 4:
        raise ValueError(
            f"next_steps must have at most 4 items, but got {len(value)}"
        )
    return value

Người trình bày dừng lại để giải thích một khác biệt quan trọng: JSON parsing kiểm tra xem văn bản có đúng cú pháp JSON hay không, còn Pydantic kiểm tra xem dữ liệu đã parse có đáp ứng schema hay không. Đó là hai bước kiểm tra khác nhau, và cần hiểu cả hai (theo video đăng ngày 14/10/2026).

Văn bản từ AIchuỗi ký tự 1. JSON parsingđúng cú pháp JSON chưa? 2. Pydanticđúng schema chưa? lỗi cú phápthiếu field, sai kiểu, sai giá trị
Hình 5.1Hai bước kiểm tra người trình bày phân biệt: JSON hợp lệ mới qua bước một; dữ liệu còn phải khớp schema thì mới qua bước hai (theo video đăng ngày 14/10/2026).

6. config.py: API key nằm ngoài code

Tiếp theo là config. Ứng dụng cần một API key để giao tiếp với Gemini. Thay vì đặt key thẳng vào code Python, project nạp nó từ biến môi trường, thường được cấu hình qua một file local. File .env chứa secret, còn .gitignore giúp ngăn file đó bị commit lên Git. Cấu hình như tên model Gemini cũng được gom vào một chỗ. Người trình bày gọi đây là một thói quen đơn giản nhưng quan trọng khi build ứng dụng AI (theo video đăng ngày 14/10/2026).

Class Config trong config.py với GEMINI_API_KEY, GEMINI_MODEL, MAX_RETRIES và LOG_FILE
Class Config: GEMINI_API_KEY là bắt buộc, nạp từ .env, "never hardcoded, never logged"; GEMINI_MODEL mặc định gemini-2.5-flash và có thể ghi đè trong .env; MAX_RETRIES mặc định 3; LOG_FILE mặc định agent.log (theo màn hình trong video đăng ngày 14/10/2026).

Phần cấu hình trên màn hình, chép nguyên văn (video đăng ngày 14/10/2026). Lưu ý dòng comment phía trên vẫn nhắc gemini-2.0-flash, trong khi giá trị mặc định thực tế trong code là gemini-2.5-flash, đúng với model hiện trong log của demo:

# --- Required ---
# The Gemini API key. Loaded from .env — never hardcoded, never logged.
GEMINI_API_KEY: str = _require_env("GEMINI_API_KEY")

# --- Optional with defaults ---
# Which Gemini model to use. Can be overridden in .env with GEMINI_MODEL=...
# gemini-2.0-flash is fast, affordable, and supports structured output.
GEMINI_MODEL: str = os.environ.get("GEMINI_MODEL", "gemini-2.5-flash")

# How many total attempts to make (1 initial + up to 2 retries = 3 total)
MAX_RETRIES: int = int(os.environ.get("MAX_RETRIES", "3"))

# Path to the log file. Automatically created on first run.
LOG_FILE: str = os.environ.get("LOG_FILE", "agent.log")

7. agent.py: gọi Gemini, xin JSON và vẫn validate

Người trình bày gọi agent.py là "bộ não" của project. Đầu tiên, nó khởi tạo Gemini client bằng API key đã cấu hình. Sau đó nó chuẩn bị prompt nói cho Gemini biết cần thông tin gì và phải trả về cấu trúc nào (theo video đăng ngày 14/10/2026).

Theo người trình bày, với model Gemini và SDK đã chọn, có thể yêu cầu structured JSON output trực tiếp (video đăng ngày 14/10/2026). Trong code trên màn hình, lệnh gọi generate_content truyền một GenerateContentConfig, kèm comment "Ask Gemini to return JSON. This is a strong hint". Dù vậy, project vẫn validate response cuối cùng bằng Pydantic. Vì sao? Người trình bày trả lời: yêu cầu một format từ model AI và validate format đó trong ứng dụng của mình là hai việc phục vụ những mục đích khác nhau (video đăng ngày 14/10/2026).

Prompt đầu tiên mô tả khung JSON cần trả về rồi liệt kê luật. Phần luật trên màn hình, chép nguyên văn (video đăng ngày 14/10/2026):

Rules:
- key_points must contain between 3 and 5 strings.
- next_steps must contain between 2 and 4 strings.
- difficulty must be exactly "Beginner", "Intermediate", or "Advanced" (case-sensitive).
- Do not add extra fields.
- Return only the JSON object — no explanation, no markdown.
Hàm _build_initial_prompt với phần Rules và hàm _build_correction_prompt trong agent.py
Cuối hàm _build_initial_prompt là khung JSON mẫu và phần Rules; bên dưới là _build_correction_prompt(user_topic, bad_response, error_message), với docstring nói rằng nó cho Gemini biết chính xác sai ở đâu, gửi kèm response hỏng và thông báo lỗi cụ thể để model có đủ context sửa lại (theo màn hình trong video đăng ngày 14/10/2026).

Tiếp theo, agent parse response. Nếu JSON không parse được, lỗi đó được xử lý. Nếu JSON hợp lệ nhưng không thoả Pydantic schema, lỗi đó cũng được xử lý (theo video đăng ngày 14/10/2026).

8. Retry có giới hạn, kèm phản hồi sửa lỗi

Nếu parse hoặc validation thất bại, lần thử tiếp theo mang theo phản hồi về lỗi trước đó và yêu cầu Gemini sửa response của nó. Nếu response đã sửa qua được validation, agent trả nó về. Nếu cả ba lần thử đều thất bại, agent dừng lại và báo lỗi (theo video đăng ngày 14/10/2026).

Vòng for attempt trong get_topic_explanation: lần đầu dùng initial prompt, các lần sau dùng correction prompt
Hàm get_topic_explanation: vòng for attempt in range(1, Config.MAX_RETRIES + 1); lần 1 gửi initial prompt, các lần sau ghi log cảnh báo "Retry %d/%d | Previous error: %s" rồi gửi correction prompt gồm topic, response thô lần trước và thông báo lỗi lần trước. Bước 1 là gọi _client.models.generate_content với model trong Config (theo màn hình trong video đăng ngày 14/10/2026).

Người trình bày lưu ý rằng agent không retry mù quáng mọi lỗi. Ví dụ, một API key không hợp lệ thường cần sửa cấu hình, chứ không phải gửi thêm một request y hệt. Một lỗi server hay lỗi mạng tạm thời có thể cần xử lý khác. Vì vậy project cũng log các sự kiện quan trọng, để khi có gì hỏng thì hiểu được chuyện gì đã xảy ra (theo video đăng ngày 14/10/2026).

Các nhánh except genai_errors.ServerError và genai_errors.APIError trong agent.py
Xử lý lỗi trong vòng retry: ServerError (các response 5xx, "transient, worth retrying") thì chờ theo exponential back-off 2 ** attempt giây (2 s, 4 s) rồi thử lại, hết lượt thì báo "Gemini server remained unavailable"; APIError khác thì kiểm tra _is_fatal_error, nếu fatal thì log "Fatal API error (will not retry)" và dừng ngay (theo màn hình trong video đăng ngày 14/10/2026).

Trong repo công khai của project, danh sách mã lỗi được chia hai nhóm (đọc ngày 25/10/2026): nhóm fatal, retry không giúp được vì đó là vấn đề cấu hình, gồm 401, 403, 400, 404; và nhóm retry được vì là lỗi tạm thời phía server, gồm 429, 500, 503, 504.

Theo người trình bày, đó là lợi ích chính của một structured output agent: nó không chỉ yêu cầu dữ liệu theo một format nhất định, mà còn kiểm tra kết quả trước khi đưa vào phần còn lại của ứng dụng (video đăng ngày 14/10/2026).

9. main.py: giao diện terminal cố ý đơn giản

Tiếp theo là main.py, phần người dùng tương tác trực tiếp. Ứng dụng hiện một lời chào và hỏi topic. Nó chuyển topic đó cho hàm của agent, nhận về kết quả đã validate và in JSON ra theo format dễ đọc. Một vòng lặp cho phép hỏi nhiều câu mà không phải khởi động lại chương trình. Các lỗi cũng được xử lý, để ứng dụng hiển thị một thông báo hữu ích cho người dùng (theo video đăng ngày 14/10/2026).

Hàm main trong main.py với vòng while True và input Enter a topic
Hàm main(): docstring ghi vòng lặp chạy tới khi người dùng gõ 'quit' hoặc 'exit', mỗi vòng hỏi một topic, gọi agent và hiện kết quả; input("Enter a topic: ") nằm trong try, còn Ctrl+C hoặc Ctrl+D thì in "Goodbye! Happy learning." và thoát êm (theo màn hình trong video đăng ngày 14/10/2026).

Giao diện terminal được làm đơn giản một cách có chủ ý. Trọng tâm của project là structured output và validation, không phải build một giao diện phức tạp (theo video đăng ngày 14/10/2026).

10. Chạy thật: "How does the internet work?" và file log

Giờ tới phần chạy ứng dụng thật. Người trình bày hỏi Gemini một câu khác: "How does the internet work?". Agent gửi request, nhận response, validate rồi hiển thị kết quả có cấu trúc (demo trong video đăng ngày 14/10/2026). Lệnh chạy trên màn hình:

python main.py
JSON trả lời How does the internet work với summary, difficulty Beginner và key_points
Response cho "How does the internet work?": summary mô tả internet là một mạng toàn cầu chia dữ liệu thành các packet, difficulty "Beginner", và các key point về "network of networks", data packet, router, server và protocol (demo trong video đăng ngày 14/10/2026).
Cuối output: next_steps và bảng tóm tắt Topic, Difficulty, Key Points 5 items, Next Steps 3 items
Cuối output: ba next step (IP address và DNS, TCP/IP và HTTP, client-side và server-side), rồi bảng tóm tắt "Key Points: 5 items, Next Steps: 3 items", đúng trong khoảng schema cho phép, và dòng "Press Enter to ask another question, or type 'quit' to exit." (demo trong video đăng ngày 14/10/2026).

Để xem log, trong PowerShell người trình bày chạy lệnh đọc file log (video đăng ngày 14/10/2026):

Get-Content agent.log

Log cho thấy lúc request bắt đầu và validation có thành công hay không. Ở lần chạy này, response qua ngay ở lần thử đầu tiên, nên không cần retry nào (demo trong video đăng ngày 14/10/2026). Người trình bày nhấn mạnh điều quan trọng: nếu một response không qua validation, ứng dụng có sẵn một quy trình xác định để xử lý, thay vì chấp nhận dữ liệu một cách mù quáng.

11. Tóm tắt và một giới hạn quan trọng

Người trình bày tóm lại những gì đã build (theo video đăng ngày 14/10/2026):

  • Kết nối Python với Gemini API.
  • Định nghĩa một hợp đồng dữ liệu (data contract) bằng Pydantic.
  • Hiểu khác biệt giữa JSON parsing và schema validation.
  • Thêm retry có giới hạn kèm phản hồi sửa lỗi.
  • Cài đặt logging, xử lý lỗi và test tự động.
  • Giữ API key ở ngoài source code.
Terminal chạy python -m pytest tests/ -v với các test TestListLengths và TestFieldLengths đều PASSED
Bộ test tự động chạy bằng python -m pytest tests/ -v: các test như test_next_steps_with_5_items_raises_error hay test_short_summary_raises_error đều PASSED, dòng tổng kết ghi 54 passed kèm một deprecation warning từ thư viện google.genai (demo trong video đăng ngày 14/10/2026).
python -m pytest tests/ -v

Rồi người trình bày nêu một giới hạn quan trọng: Pydantic có thể cho biết response có khớp schema hay không, nhưng nó không thể đảm bảo mọi phát biểu Gemini tạo ra là đúng sự thật. Vì vậy validation, testing và xử lý lỗi có trách nhiệm đều quan trọng khi build ứng dụng AI (theo video đăng ngày 14/10/2026).

Pydantic kiểm tra được đủ field, đúng kiểu, đúng giá trị cho phép, số phần tử của list nằm trong khoảng Pydantic không đảm bảo được nội dung Gemini viết ra là đúng sự thật (cần thêm testing và xử lý lỗi)
Hình 11.1Giới hạn người trình bày nêu ở cuối video: validation bảo đảm cấu trúc, không bảo đảm sự thật (theo video đăng ngày 14/10/2026).

Người trình bày mong project giúp những ai đang học Python và AI agent hiểu cách làm output của AI dễ đoán trước hơn, cho biết link GitHub của project nằm trong phần mô tả video, rồi cảm ơn người xem (video đăng ngày 14/10/2026).

Nguồn và liên kết 6 nguồn

  1. Tên gốc: I Built an AI Agent That Fixes Its Own Errors
  2. Sự kiện: YouTube
  3. Video gốc: https://www.youtube.com/watch?v=9yalmgVkHmw youtube.com
  4. Kênh YouTube Coding Hives: youtube.com/channel/UCOBTx45SrFNUzrORTbENB_Q youtube.com
  5. Repo structured-output-agent, source code của project trong video (README mô tả luồng parse, validate, retry tối đa 3 lần và cách xem agent.log, đọc ngày 25/10/2026): github.com/raseenaanwar/structured-output-agent github.com
  6. Kiro, IDE dùng trong video (đọc ngày 25/10/2026): kiro.dev kiro.dev