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

[Coding Hives](https://homus.dev/speakers/coding-hives) · [Coding Hives](https://homus.dev/orgs/coding-hives)

> 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.

Topics: Agents, Dev Tools, Workflow

Canonical: https://homus.dev/talks/i-built-an-ai-agent-that-fixes-its-own-errors

## 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).

Bà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](https://homus.dev/photos/9yalmgVkHmw-0123.jpg)

Đầ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](https://homus.dev/photos/9yalmgVkHmw-0142.jpg)

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](https://homus.dev/photos/9yalmgVkHmw-0229.jpg)

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](https://homus.dev/photos/9yalmgVkHmw-0323.jpg)

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)  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).

Hai 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](https://homus.dev/photos/9yalmgVkHmw-0443.jpg)

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](https://homus.dev/photos/9yalmgVkHmw-0523.jpg)

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](https://homus.dev/photos/9yalmgVkHmw-0543.jpg)

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](https://homus.dev/photos/9yalmgVkHmw-0603.jpg)

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](https://homus.dev/photos/9yalmgVkHmw-0643.jpg)

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](https://homus.dev/photos/9yalmgVkHmw-0803.jpg)

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](https://homus.dev/photos/9yalmgVkHmw-0823.jpg)

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](https://homus.dev/photos/9yalmgVkHmw-0723.jpg)

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).

Giớ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

- Tên gốc: I Built an AI Agent That Fixes Its Own Errors
- Sự kiện: [YouTube](https://homus.dev/events/youtube)
- Video gốc: [https://www.youtube.com/watch?v=9yalmgVkHmw](https://www.youtube.com/watch?v=9yalmgVkHmw)

- Kênh YouTube Coding Hives: [youtube.com/channel/UCOBTx45SrFNUzrORTbENB_Q](https://www.youtube.com/channel/UCOBTx45SrFNUzrORTbENB_Q)

- 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](https://github.com/raseenaanwar/structured-output-agent)

- Kiro, IDE dùng trong video (đọc ngày 25/10/2026): [kiro.dev](https://kiro.dev)

Nguồn: https://homus.dev/talks/i-built-an-ai-agent-that-fixes-its-own-errors · cập nhật 2026-10-25
