Agent Output Is Not UX: Rendering Layer Your LLM Pipeline Is Missing
AI Engineer World's Fair 2026: Online Track · Video gốc
Lớp generative UI giữa model và màn hình: rendering contract có version, streaming vào typed component, và BFF giữ cho mobile client an toàn.
1. Đặt một bàn ăn: model làm đúng, trải nghiệm thì chưa
Bala mở đầu bằng một tình huống rất đời thường. Anh nhờ AI assistant của mình giúp đặt một bàn ở một nhà hàng đông khách. Và đây là thứ nó trả về: một khối chữ dài. Phải nói ngay là câu trả lời đó không sai. Số điện thoại có đủ. Giờ mở cửa đúng. Nó thậm chí còn biết chuyện quầy hàu ở phía trước nhà hàng chỉ nhận khách vãng lai, không cần đặt chỗ. Model đã làm phần việc thật sự, phần tìm kiếm và tổng hợp thông tin.
Nhưng hãy nhìn vào kết quả cuối cùng. Người dùng vẫn phải tự đi tìm hiểu, tự làm từng bước để thật sự đặt được cái bàn đó. Bala nhận xét rằng chúng ta đã ở trong tình trạng này khá lâu rồi.

Giờ hãy tưởng tượng bạn build một thứ như vậy cho khách hàng của mình. Bạn sẽ muốn nó làm gì? Bala đưa ra cùng một câu trả lời đó, nhưng được render theo một cách khác: một ô chọn ngày, một ô chọn giờ, vài cú chạm, và xong. Không còn đoạn văn nào phải đọc.

Thông điệp của Bala: sản phẩm agentic bạn đang build đã có đủ tool để đáp ứng nhu cầu của khách hàng. Thứ duy nhất bạn cần tập trung là lớp nằm giữa model và một thứ mà con người có thể tương tác được. Lớp đó chính là chủ đề của talk. Model và agent sẽ còn ở lại lâu dài, và chúng ta nên học cách làm cho chúng thân thiện với con người.
2. Bala Ramdoss và Amazon Lens

Bala tự giới thiệu. Anh đã build các ứng dụng hướng tới khách hàng (customer-facing app) hơn mười năm, và sáu năm gần đây anh dành cho việc build Amazon Lens. Slide giới thiệu ghi chức danh của anh là Sr SDE tại Amazon Search.
Amazon Lens là bộ tính năng camera chạy bằng AI của Amazon. Nó cho phép bạn mua sắm bằng hình ảnh, screenshot và mã vạch để tìm ra những sản phẩm trông giống nhau. Nếu bạn có cài app Amazon, anh khuyến khích bạn thử. Talk này đến từ kinh nghiệm của anh khi build những sản phẩm customer-facing chạy trên hàng triệu, hàng triệu thiết bị mobile.

Trước khi đi tiếp, anh đưa ra một lời miễn trừ nhanh: anh trình bày talk này với tư cách cá nhân. Những quan điểm trong talk là của anh, không phải của công ty nơi anh làm việc.
3. Bức tường khi build UX cho agentic AI
Khi nghĩ tới việc build UX cho một agentic AI, đương nhiên ChatGPT là cái tên hiện ra đầu tiên. Nhìn vào những màn hình như vậy, câu hỏi lập tức xuất hiện: bạn vẽ những tấm thẻ (card) này như thế nào? Khi nào chọn carousel, khi nào chọn list? Bạn thiết kế ra sao để con người hiểu và tương tác tốt hơn?

Khi thử build một thứ như vậy, bạn sẽ đâm vào một bức tường. Có những vấn đề phải được giải ngay từ những giai đoạn rất sớm của hệ thống. Trải nghiệm đó sẽ nhanh nhạy hay chậm chạp? Chúng ta nhận toàn bộ thông tin ngay lập tức, hay từng thứ một? Và làm sao scale điều này cho mobile app, nơi phiên bản app và khả năng của thiết bị phân mảnh đến vậy?

Nếu để ý, bạn sẽ thấy không vấn đề nào trong số đó do chính model gây ra. Model làm tốt phần việc của nó. Đây là những vấn đề về delivery (đưa kết quả tới người dùng), và chúng nằm ở khoảng giữa output của model và thứ hiện trên màn hình. Theo Bala, chính lớp đó quyết định sản phẩm của bạn thành công hay không.
4. Lớp này giờ đã có tên: Generative UI và A2UI
Bala kể rằng nếu hai năm trước có ai hỏi anh về lớp này, anh sẽ không biết gọi nó là gì. Lúc đó anh đã build vài tính năng AI, và lần nào anh cũng giải phần này lại từ đầu: một pattern may đo, được uốn theo đúng hệ thống mà anh đang làm. Không có một bộ từ vựng chung nào cho nó cả.
Điều khiến anh thích thú là giờ thứ này đã có tên: Generative UI. Và đã có một spec mở cho nó: A2UI của Google. Thay vì agent đưa cho bạn một đoạn text thô hay HTML, nó mô tả UI dưới dạng dữ liệu, tức là một danh sách component, và client render những component đó bằng chính các widget native của mình. Một vấn đề trước đây anh phải tự giải một mình giờ đang trở thành điểm xuất phát chung cho các nhóm, và đó chính là thứ anh hào hứng muốn đào sâu.

5. Từ data API sang UI-shaping API: ba nấc
Một API bình thường trả về dữ liệu, và client tự quyết định vẽ dữ liệu đó ra sao. Model của bạn giỏi tool use, giỏi viết code, giỏi cả những task phức tạp khác. Nó cũng làm được UI, và Generative UI nói về đúng điều đó.
Đây là một dải phổ (spectrum). CopilotKit, một công ty làm chủ yếu về mảng này, chia nó thành ba nấc:
- Controlled (ở dưới cùng): model chọn một component dựng sẵn, ví dụ một product card. Nó không bao giờ tự nghĩ ra thứ gì mới.
- Declarative (ở giữa): model ghép UI từ một catalog, ví dụ một ô ngày, một ô giờ, một nút submit. A2UI nằm ở nấc này.
- Open-ended (trên cùng): model sinh ra một UI hoàn toàn mới ngay lúc chạy, ví dụ MCP Apps.

Càng leo lên cao, client của bạn càng phải tin tưởng nhiều hơn vào bất cứ thứ gì model đưa cho nó. Phần lớn mobile app trong production sống ở hai nấc dưới, vì đó là chỗ an toàn, và talk này cũng tập trung vào hai nấc đó.
6. Mobile thêm một tầng phức tạp
Làm cho việc build UX phức tạp hơn nữa là vai trò quan trọng của mobile app. Trên web, nếu renderer hỏng, bạn ship một bản sửa và nó lên live trong vài phút. Mobile app không làm được như vậy. Bạn đang nhìn vào hàng trăm triệu lượt cài đặt, và bạn không kiểm soát được khi nào bất kỳ máy nào trong số đó cập nhật.

Vì thế, khi một client gặp một loại nội dung (content type) mà nó chưa từng thấy, nó không suy giảm một cách êm ái (gracefully degrade). Nó crash, và nó cứ tiếp tục crash trong nhiều ngày hoặc nhiều tuần, trên tay những người chưa cập nhật app.
Nên với mobile client, có một quy tắc chi phối mọi thứ phía sau: bạn không thể patch client một cách có ý nghĩa. Mọi thiết kế ở các phần sau đều xuất phát từ ràng buộc này.
7. Toàn cảnh rendering layer và ba pattern
Hệ thống trông như thế nào khi nhìn từ xa? Bala đưa ra toàn bộ pipeline ở dạng đơn giản hoá:
- Một context phase có nhận biết phiên bản (version-aware) để nạp thông tin cho model. Đúng vậy, anh nói, đó chính là context engineering.
- Model xuất ra typed UI intent, tức là ý định UI có kiểu rõ ràng.
- Intent đó chảy qua một BFF (Backend for frontend).
- Rồi tới client renderer, nơi vẽ nó ra và lùi về phương án an toàn (fallback) khi không vẽ được.

Anh chia hệ thống thành ba pattern:
- Rendering contract.
- Streaming, dòng chảy nằm giữa các thành phần.
- BFF, lớp nằm ở giữa.
Talk đi qua từng pattern một, bắt đầu với contract.
8. Pattern 1: rendering contract
Contract là chuyện làm cho model biết được client có những khả năng gì. Bạn bảo đảm model luôn bám đúng client mà nó đang vẽ UI cho. Điều contract bảo đảm là trách nhiệm không đổ lên client hay rendering layer, bắt chúng phải nhìn vào token output rồi tự suy ra nên hiển thị gì.

Bạn cũng duy trì một repository chứa version map, ánh xạ phiên bản app với các khả năng (capability). Ví dụ, bạn giới thiệu một UI flight card mới ở phiên bản 2.0. Hãy bảo đảm chỉ đưa nó ra cho model từ phiên bản 2.0 trở đi khi bạn build context.
Bài học ở đây: model sẽ là bên chọn CX (customer experience), còn bạn là bên cung cấp đúng thông tin trong context của nó.
9. Phần contract nhìn từ phía model
Bala đưa ra một ví dụ, và nói rằng đây thường là phần khó khi build hệ thống này. Đây là nửa của contract hướng về phía model. Bạn không muốn model trả về text. Bạn muốn nó stream về từng khối (block) UI component: một conversation block cho những gì nó nói bằng chữ, và một UI block cho thứ nó muốn client render.
Contract thậm chí còn mã hoá cả quy tắc layout. Trong ví dụ này: một đến ba chuyến bay thì dùng carousel vuốt ngang; bốn chuyến trở lên thì dùng list dọc. Model chọn intent.

Model streams: conversation_block + ui_block
Rule: 1-3 results -> flight_carousel · 4+ -> flight_list
"flight_carousel": {
"layout": "horizontal_swipeable",
"max_items": 3,
"data_spec": { "airline": "string", "price": "number", "depart": "time" }
},
"flight_list": {
"layout": "vertical_scrollable",
"data_spec": { "airline": "string", "price": "number", "depart": "time" }
}Và hãy để ý điều model không bao giờ làm: nó không bao giờ tự sáng chế ra một component. Nó chọn từ một menu cố định mà bạn đưa cho nó. Hiển nhiên menu đó sẽ không có tới hàng triệu món, chỉ vài món thôi. Hoá ra mọi thứ khó lên đáng kể khi bạn scale các tính năng ra nhiều bề mặt (surface) hơn. Bala để khán giả tự nghĩ xem nên engineer context như thế nào để bảo đảm model chọn đúng một component.
Đó là phần contract.

10. Pattern 2: streaming vào typed UI
Bước sang phần streaming, Bala nói đây là điều làm anh "mở mắt" khi lần đầu gặp nó. Theo cách truyền thống, app gọi một API rồi chờ cho tới khi có gì đó xảy ra. Khi có LLM tham gia, pattern này trở nên kém hiệu quả, vì latency thường cao hơn do chính bản thân model. Thêm vào đó, còn vô số bước kiểm tra bổ sung để bảo đảm an toàn và những thứ tương tự. Và chúng ta còn đưa vào một lớp mới, khá phức tạp, để làm dynamic UI.
Streaming giúp ở đây bằng cách không bắt client chờ toàn bộ response. Nó render từng phần (chunk) một.

Bala cho xem một ví dụ minh hoạ, khi bạn phải đưa thông tin ra từng chunk một: đầu tiên hiện một khung xương (skeleton), rồi điền một phần, rồi hoàn chỉnh. Có thể mất ba đến bốn giây để tới được bước cuối, nhưng sự chờ đợi đó chịu được.
Điều đó thay đổi thứ bạn đo cho một app. Bạn ngừng đuổi theo tổng latency, việc mà chúng ta đã làm suốt hơn một thập kỷ. Bạn bắt đầu đuổi theo time to first chunk, tức là thứ hữu ích đầu tiên mà người dùng nhìn thấy. Vì lý do này, loading spinner truyền thống không còn dùng được cho các tính năng AI.

11. Latency là một quyết định sản phẩm
Đôi khi bạn phải sáng tạo và thiết kế tính năng của mình xoay quanh latency. Cách này có thể không hợp với mọi trường hợp, nhưng Bala đưa ra một ví dụ sản phẩm. Khi bạn biết sẽ mất thời gian để render một thứ gì đó, hãy giữ người dùng bận rộn với việc khác. Lens Live cho phép người dùng lấy nét vào những thứ khác nhau, chạm vào một vật họ quan tâm trong lúc chờ kết quả.

Nếu bạn không kiểm soát toàn bộ màn hình, bạn có thể hiện một thinking CX, kiểu trải nghiệm "đang suy nghĩ", nhưng hãy dùng nó một cách tiết kiệm. Người dùng AI nói chung đã ra khỏi giai đoạn dễ dãi, và giờ họ muốn biết chuyện gì đang diễn ra.

Khi đã có cách stream về các trạng thái khác nhau, bạn biết cách cho người dùng thấy agent của mình đang làm gì. Bala đưa ra một ví dụ anh lấy từ Gemini: khi bắt đầu làm một task, nó cho người dùng thấy thoáng qua agent đang làm gì. Dù mất tới 10 giây, anh vẫn thấy ổn nếu anh biết chuyện gì đang xảy ra, và nhờ vậy có thể tin vào output cuối cùng của agent.

Vậy là xong phần streaming.
12. Pattern 3: server-driven UI và BFF
Bala chuyển sang phần cuối cùng, và theo anh là phần quan trọng nhất: BFF mới của bạn. Anh tóm lại mạch của ba pattern: pattern một nói về cách UI được chọn. Pattern hai nói về cách các chunk được giao tới. Pattern ba nói về cách các chunk trở thành những phần tử UI có ý nghĩa.

Đây là khái niệm server-driven UI (SDUI), và nó cũng là một dải phổ về mức kiểm soát của server: server kiểm soát càng nhiều, bạn càng phải build nhiều. BFF là một tập con của nó và quyết định cách render. Nó nắm các quy tắc riêng của từng nền tảng, như Android khác iOS, những thứ kiểu như vậy. Nó cũng giúp client phải ra ít quyết định hơn và chỉ việc vẽ thứ được trao cho nó. Toàn bộ ý tưởng là như vậy.

Đọc thêm về pattern BFF gốc: Backends For Frontends của Sam Newman.
13. BFF làm nhiều hơn layout
BFF không chỉ ship layout. Nó làm thêm một chút. Ngoài format và context, nó làm hydration và gắn thêm action. Mỗi phần tử được render đều mang theo một action payload, quy định một cú chạm sẽ làm gì, mở deep link nào. Nó thậm chí có thể đặt tên cho impression metric cần log.

action_payload on_tap: open flight_details deeplink: app://flights/AC214 on_long_press: save_to_trip impression: flight_row_view
BFF cũng mang conversational context qua các lượt hội thoại, để response tiếp theo biết điều gì đã xảy ra trước đó.
Và điểm hay là bạn có thể áp dụng điều này cho chính những app đang có. Bạn có thể tái sử dụng các đơn vị CX (CX unit) sẵn có: dòng chuyến bay, product card, những component mà app của bạn đã ship trong production. Bạn không phải dựng một kiểu giao diện agentic mới tinh trong app, và đó là một cách tốt để build cho con người. Cùng thương hiệu, cùng mật độ thông tin, cùng cảm giác quen thuộc. Nó trông và cảm giác như native.
14. Takeaways: agent output không phải là CX
Điều đó dẫn tới phần takeaways.

- Model đã rất có năng lực. Bạn đưa cho nó một contract có kiểu rõ ràng, có version đàng hoàng, để nó tự chọn đúng CX.
- Stream vào typed component, không phải text. Cho người dùng biết agent của bạn đang làm gì.
- Để BFF hấp thụ output của model, nhờ vậy client có thể giữ "dumb" và an toàn.
Không điều nào trong số này nói về model. Model vốn đã ổn. Chính lớp này mới là thứ ship sản phẩm.
Quay lại điểm xuất phát của talk: output của agent không phải là CX. Bạn build trên nó. Bala cảm ơn khán giả, và nếu muốn tìm hiểu thêm hay kết nối với anh, có một mã QR trên slide cuối.

Nguồn và liên kết
- Trang talk trên ai.engineer · Video gốc
- A2UI: spec mở của Google để agent mô tả UI dưới dạng dữ liệu, client render bằng widget native; mã nguồn: a2ui-project/a2ui
- Generative UI spectrum của CopilotKit: ba nấc controlled, declarative, open-ended
- MCP Apps: ví dụ cho nấc open-ended; spec và SDK: modelcontextprotocol/ext-apps
- Server-Sent Events (MDN): cơ chế stream chunk từ server về client nhắc trên slide
- Backends For Frontends, Sam Newman: nguồn gốc pattern BFF
- Amazon Lens: tính năng tìm sản phẩm bằng camera trong app Amazon (amazon.com/lens)
- Bài giới thiệu apps trong ChatGPT trên blog OpenAI: openai.com/index/introducing-apps-in-chatgpt
- LinkedIn của Bala Ramdoss: linkedin.com/in/bala-ramdoss