Agent không thể hành động nếu không hiểu trang đang có gì. Bài này dạy cách biểu diễn trạng thái trang cho LLM: khác biệt giữa DOM thô, selector, và accessibility tree — và tại sao cách biểu diễn quyết định độ chính xác.
Ba cách agent "nhìn" một trang
- Screenshot (pixel): ảnh chụp màn hình. Dùng cho computer-use hoặc khi trang phức tạp về thị giác. Nhược: LLM phải đoán tọa độ, dễ sai, tốn token ảnh.
- DOM thô (HTML): đầy đủ nhưng khổng lồ, đầy thẻ nhiễu — nhồi hết vào LLM sẽ vượt context và lẫn thông tin.
- Accessibility tree: cây mô tả các phần tử tương tác (button, link, textbox) kèm nhãn — gọn, có ngữ nghĩa, lý tưởng cho LLM.
# Lấy accessibility tree gọn cho LLM đọc
snapshot = page.accessibility.snapshot()
# -> { role: 'button', name: 'Đăng nhập', ... }
Selector: cách trỏ chính xác vào phần tử
Playwright ưu tiên locator bền vững thay vì CSS/XPath dễ gãy:
page.get_by_role("button", name="Export") # theo vai trò + nhãn (bền nhất)
page.get_by_label("Email") # theo label form
page.get_by_text("Tải xuống") # theo text hiển thị
page.locator("#export-btn") # theo id (khá bền)
page.locator("div.x1a > span:nth-child(3)") # CSS sâu (DỄ GÃY)
Thứ tự ưu tiên: role > label > text > id > CSS/XPath. Càng gần cách con người mô tả nút, càng bền khi giao diện đổi.
Gắn nhãn phần tử cho agent chọn
Mẫu phổ biến: đánh số các phần tử tương tác rồi cho LLM chọn theo số — giảm rủi ro so với đoán tọa độ.
[0] button "Đăng nhập"
[1] textbox "Email"
[2] textbox "Mật khẩu"
[3] link "Quên mật khẩu?"
LLM trả về: action=click, index=1 (nhập email)
Các bước xây lớp "quan sát"
- Sau mỗi hành động, chờ trang ổn định (
page.wait_for_load_state). - Trích accessibility tree, lọc bỏ phần tử ẩn/không tương tác.
- Gán index cho từng phần tử còn lại.
- Kèm URL hiện tại + tiêu đề trang để LLM biết bối cảnh.
- Nếu trang nặng thị giác (canvas, bản đồ) → bổ sung screenshot.
Khung tư duy: "Ngôn ngữ nào cho tác vụ nào"
Form, bảng, nút rõ ràng -> accessibility tree (rẻ, chính xác)
Canvas, ảnh, bố cục thị giác -> screenshot
Hai cái trên đều thiếu -> kết hợp cả tree + screenshot
Ví dụ cụ thể
Tác vụ "bấm nút Export tháng 6": nếu chỉ dùng screenshot, LLM có thể trả tọa độ lệch 10px và bấm nhầm ô bên cạnh. Dùng accessibility tree, agent thấy rõ [7] button "Export" và chọn index 7 — chính xác, không phụ thuộc độ phân giải.
Kỹ thuật rút gọn trạng thái để tiết kiệm token
Một trang thật có thể có hàng trăm phần tử. Nhồi hết vào prompt vừa đắt vừa nhiễu. Hãy áp dụng các mẹo sau khi xây lớp observe:
- Chỉ giữ phần tử TƯƠNG TÁC (button, link, input, select, checkbox)
- Bỏ phần tử ẩn (display:none, visibility:hidden, opacity:0)
- Cắt bớt nhãn quá dài (giữ ~80 ký tự đầu)
- Gộp nhóm lặp lại (vd 50 dòng bảng -> mô tả "bảng 50 dòng" + vài dòng mẫu)
- Ưu tiên phần tử đang trong viewport nếu trang quá dài
Mục tiêu: cho LLM đủ thông tin để quyết định, không hơn. Trạng thái càng gọn, quyết định càng nhanh, rẻ và ít nhiễu.
Đồng bộ giữa index và hành động
Một lỗi tinh vi: nếu bạn đánh index rồi trang thay đổi (một phần tử mới chèn vào giữa) trước khi agent hành động, index cũ trỏ nhầm phần tử. Cách phòng: chụp lại và đánh index ngay trước mỗi hành động, không tái dùng index từ vòng trước. Luôn giữ ánh xạ index → locator trong cùng một lần observe.
Sai lầm thường gặp
- Nhồi toàn bộ HTML thô vào LLM: vượt context, tốn tiền, và LLM lạc giữa rừng thẻ.
- Chỉ dựa vào tọa độ pixel cho web có DOM: mong manh, gãy khi đổi độ phân giải hay zoom.
- Dùng CSS selector sâu (
div>div>span:nth-child(4)): đổi layout một chút là gãy. - Không lọc phần tử ẩn: agent chọn nút
display:nonerồi click thất bại khó hiểu. - Quên cập nhật trạng thái sau hành động: agent quyết định dựa trên ảnh cũ của trang.