CodeGym /Các khóa học /ChatGPT Apps /Tương tác với ChatGPT: follow‑up và “cuộc hội thoại xoay ...

Tương tác với ChatGPT: follow‑up và “cuộc hội thoại xoay quanh widget”

ChatGPT Apps
Mức độ , Bài học
Có sẵn

1. Follow‑up trong ChatGPT App là gì và vì sao cần chúng

Bắt đầu bằng một định nghĩa “dễ hiểu” chứ không quảng cáo. Follow‑up trong ngữ cảnh ChatGPT App là bước tiếp theo trong đối thoại được khởi phát bằng lập trình. Thường đó là một gợi ý ngắn được hiển thị như một nút: khi nhấn, nó biến thành một tin nhắn văn bản mới từ phía người dùng trong cuộc chat và kích hoạt việc tiếp tục kịch bản giữa con người và AI.

Từ góc nhìn của mô hình, follow‑up chỉ là thêm một tin nhắn người dùng. Không có phép màu nào: khi người dùng nhấp vào nút “Hiển thị lựa chọn rẻ hơn” của bạn, lịch sử chat nhận được một nội dung kiểu “Hãy hiển thị quà rẻ hơn”. Mô hình xem đây là câu nói bình thường của người dùng, áp dụng system‑prompt và mô tả các tools, quyết định gọi công cụ nào, và có thể lại render widget của bạn với dữ liệu khác.

Vì sao điều này quan trọng:

  • Mô hình tư duy bằng văn bản. Nếu bạn chỉ gọi tool trực tiếp khi nhấp (không có tin nhắn), bạn đã đi vòng qua “bộ não” chính của hệ thống và mất một phần ngữ cảnh. Follow‑up lưu tiến trình hội thoại vào lịch sử và giúp mô hình hiểu chính xác điều đang diễn ra.
  • Người dùng vẫn ở trong mô hình chat quen thuộc: hoặc họ gõ tin nhắn, hoặc họ nhấn gợi ý. Follow‑up là các “trả lời nhanh” (quick replies) giống như trong các ứng dụng nhắn tin hiện đại.
  • Đây là chiếc cầu chính giữa UI của bạn và phần văn bản của ChatGPT. Không có follow‑up, widget trở thành một “hòn đảo câm”: người dùng bấm vài cái và không thật sự hiểu tiếp theo phải làm gì.

Bạn có thể xem follow‑up như nút “Câu hỏi tiếp theo cho AI”, chỉ khác là do bạn soạn sẵn. Từ đây, chúng ta sẽ coi follow‑up không phải là “một tính năng UI nữa”, mà là cách chính để xây dựng đối thoại xoay quanh widget.

2. “Cuộc hội thoại xoay quanh widget”: conversational sandwich

Để dễ thấy follow‑up giúp xây dựng “cuộc hội thoại xoay quanh widget” như thế nào, hãy hình dung đối thoại như một chiếc sandwich ba lớp:

  • phía trên là phần văn bản ChatGPT trước widget (pre‑text),
  • ở giữa là widget của bạn (UI),
  • bên dưới là các follow‑up và những tin nhắn tiếp theo (post‑interaction).

Sơ đồ:

sequenceDiagram
    participant U as Người dùng
    participant G as ChatGPT
    participant W as Widget

    U->>G: "Hãy gợi ý quà cho em gái trong tầm 100$"
    G->>G: Quyết định gọi App
    G->>U: Pre-text: "Mình sẽ mở GiftGenius và gợi ý vài ý tưởng"
    G->>W: Truyền toolOutput để render
    W-->>U: Thẻ quà tặng + các nút follow‑up
    U->>W: Nhấp vào "Hiển thị lựa chọn rẻ hơn"
    W->>G: sendFollowUpMessage("Hãy hiển thị quà rẻ hơn, tối đa 50$")
    G->>G: Một vòng chạy mới của mô hình, gọi tools
    G->>W: toolOutput mới, widget được cập nhật

Pre‑text thường do mô hình tự sinh dựa trên system‑prompt và ngữ cảnh: ở đó nó “giải thích” sắp làm gì (“Mình sẽ mở ứng dụng giúp chọn quà”). Chúng ta sẽ kiểm soát lớp này sau, trong mô‑đun về chỉ dẫn (instructions).

Widget là React component quen thuộc của bạn: render toolOutput, dùng widgetState, cung cấp nút bấm và lựa chọn.

Lớp post‑interaction là phần chúng ta làm hôm nay. Thông qua follow‑up và việc gửi tin nhắn, bạn vạch ra tuyến tiếp theo rõ ràng: “Hiển thị đắt hơn”, “Thay đổi ngân sách”, “Bắt đầu lại việc chọn”, “Chuyển sang đặt hàng”.

Nói ngắn gọn, follow‑up là câu điều hướng khép kín vòng lặp: UI → văn bản → UI mới.

3. Mô hình kỹ thuật: một cú nhấp follow‑up biến thành tool‑call mới như thế nào

Hãy xem kỹ chuỗi sự kiện “bên dưới nắp capo”. Có thể coi đây là chu trình tương tác lai: nhấp → API → văn bản vào lịch sử → quyết định mới của mô hình → gọi tool mới → UI cập nhật.

Trình tự xấp xỉ như sau:

  1. Người dùng nhấn một nút trong widget của bạn.
  2. Widget gọi API window.openai.sendFollowUpMessage hoặc, ở lớp React, hook tiện dụng useSendMessage (trong các ví dụ dưới đây, chúng ta dùng hook này). Đối số là một chuỗi bình thường: văn bản như thể người dùng vừa gõ.
  3. ChatGPT thêm tin nhắn này vào lịch sử như một user_message mới.
  4. Mô hình chạy một vòng mới: tính đến toàn bộ lịch sử, bao gồm toolOutput trước đó, rồi quyết định có gọi tool hay không, gọi tool nào và với đối số gì.
  5. Backend/MCP của bạn thực thi tool‑call và trả về toolOutput.
  6. ChatGPT hiển thị câu trả lời mới: có thể lại là widget (với dữ liệu mới), có thể là văn bản, hoặc kết hợp cả hai.

Điều quan trọng là gần như bạn không muốn từ widget gọi thẳng lại cùng công cụ mà mô hình vừa dùng, bỏ qua chu trình này. Nếu không, mô hình sẽ “mất” một bước: lịch sử không có yêu cầu đã kích hoạt lần gọi lại. Về sau trong các kịch bản dài, điều này dẫn đến ngữ cảnh rối rắm.

Có thể cô đọng thành công thức ngắn:

User Click → useSendMessage("...") → ChatGPT (LLM) → Tool Call → New toolOutput → Widget rerender

4. Các loại follow‑up và ai nghĩ ra chúng

Ta đã hiểu điều gì xảy ra “bên dưới” sau một cú nhấp follow‑up. Giờ hãy xem những loại follow‑up nào đáng đưa cho người dùng và chúng đóng vai trò gì trong kịch bản.

Trong App thực tế có vài “họ” follow‑up. Tôi chia theo các chiều: tĩnh vs động, theo vai trò (drill‑down, pivot, commit, v.v.).

Bảng tóm tắt bên dưới tiện thực hành hơn.

Loại Tác dụng Ví dụ văn bản/nút
Gợi ý (Suggestive) Giúp khi người dùng không biết nên hỏi gì tiếp theo “Hiển thị thêm ý tưởng”, “Thu hẹp theo sở thích”
Làm rõ (Drill‑down / Parametric) Thu hẹp yêu cầu trước theo tham số “Rẻ hơn”, “Chỉ quà kỹ thuật số”, “Chỉ Nike”
Chuyển hướng (Pivot) Đổi nhánh kịch bản “Bắt đầu chọn lại”, “Hiển thị quà cho trẻ em”
Điều hướng (Navigation) Chuyển sang bước khác của quy trình “Chuyển sang đặt hàng”, “Quay lại lựa chọn”
Xác nhận (Commit) Xác nhận hành động “Đặt món quà này”, “Lưu bộ gợi ý”

Xét theo nguồn ý tưởng có hai nhóm lớn.

Thứ nhất, các follow‑up do chính ứng dụng đề xuất. Đó là các nút trong widget, gắn với logic UI và dữ liệu: ví dụ “Hiển thị tương tự với [Tên quà tặng]” hoặc “Lọc theo sở thích travel”. Các gợi ý như vậy có thể được mã hoá cứng (static) hoặc tạo động dựa trên toolOutput (dynamic).

Thứ hai, gợi ý “native” của ChatGPT — các chip nhỏ dưới tin nhắn, do mô hình tự nghĩ ra. Bạn không kiểm soát trực tiếp chúng; hãy coi đó là phần thưởng miễn phí, không phải cơ chế đáng tin cậy. Ứng dụng của bạn vẫn phải hoạt động tốt ngay cả khi không có chúng.

Trong bài này chúng ta quan tâm hơn đến nhóm đầu: các nút và gợi ý bạn vẽ trong React component của mình và khi nhấp sẽ gửi sendFollowUpMessage.

5. Triển khai follow‑up trong React: ví dụ GiftGenius

Tiếp tục với App giả định GiftGenius chuyên gợi ý quà tặng. Sau khi gọi tool get_gift_ideas, widget nhận toolOutput chứa danh sách quà. Ở các chủ đề trước ta đã làm lưới thẻ. Giờ thêm một phần follow‑up.

Giả sử SDK có các hook useWidgetPropsuseSendMessage. Tên chỉ minh hoạ, nhưng khái niệm khớp với bản tham chiếu:

import { useWidgetProps, useSendMessage } from '@/openai-apps';

export const GiftSuggestions: React.FC = () => {
  const { toolOutput } = useWidgetProps();
  const sendMessage = useSendMessage();

  const gifts = toolOutput?.data?.gifts ?? [];

  if (gifts.length === 0) {
    return <div>Không tìm thấy quà tặng. Hãy thử thay đổi yêu cầu.</div>;
  }

  const handleCheaper = () => {
    sendMessage('Hãy hiển thị quà rẻ hơn, tối đa 50$');
  };

  const handleDigital = () => {
    sendMessage('Chỉ hiển thị quà kỹ thuật số: voucher, đăng ký, v.v.');
  };

  return (
    <div className="flex flex-col gap-4">
      <div className="grid grid-cols-2 gap-2">
        {gifts.map((gift: any) => (
          <GiftCard key={gift.id} item={gift} />
        ))}
      </div>

      <div className="border-t pt-3 text-sm">
        <div className="text-xs text-gray-500 mb-2">Tiếp theo làm gì?</div>
        <div className="flex flex-wrap gap-2">
          <button
            onClick={handleCheaper}
            className="px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200"
          >
            Rẻ hơn
          </button>
          <button
            onClick={handleDigital}
            className="px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200"
          >
            Chỉ đồ số
          </button>
        </div>
      </div>
    </div>
  );
};

Vài điểm quan trọng.

Thứ nhất, sendMessage nhận một chuỗi — văn bản này sẽ xuất hiện trong chat như thể người dùng nhập. App của bạn không cần “giả lập” giao thức hệ thống nội bộ; chỉ cần diễn đạt câu sao cho mô hình hiểu.

Thứ hai, phần follow‑up được tách biệt trực quan (đường viền trên, dòng chữ nhỏ “Tiếp theo làm gì?”) để người dùng hiểu: đây là phần tiếp nối cuộc hội thoại hơn là thành phần của chính thẻ.

Thứ ba, số nút ít — hai. Khuyến nghị UX là giữ khoảng từ hai đến bốn lựa chọn: đủ để trợ giúp mà không gây quá tải.

Follow‑up động dựa trên dữ liệu

Giả sử bạn muốn cho người dùng đào sâu vào một món quà cụ thể: “Hiển thị tương tự với cái này”. Khi đó, văn bản follow‑up nên nhắc đến đối tượng cần thiết, và bạn có thể tạo chuỗi ngay tại thời điểm bấm.

const handleShowSimilar = (giftTitle: string) => {
  sendMessage(
    `Hãy hiển thị các món quà tương tự với "${giftTitle}", có thể trong cùng ngân sách hoặc hơi cao hơn`
  );
};

Và trong thẻ:

<button
  onClick={() => handleShowSimilar(gift.title)}
  className="mt-2 text-xs text-blue-600 underline"
>
  Hiển thị tương tự
</button>

Như vậy bạn triển khai follow‑up động gắn với dữ liệu cụ thể từ toolOutput. Chính những nút như vậy làm cho đối thoại “thông minh”, chứ không chỉ là bộ “Tiếp/Trước” chung chung.

6. Vì sao chúng ta gửi văn bản thay vì gọi tool trực tiếp

Suy nghĩ điển hình của frontend developer: “Đã có useCallTool, sao phải gửi văn bản? Mình có thể gọi ngay get_gift_ideas với tham số khác mà.” Đôi khi đúng là cần (sẽ nói thêm trong mô‑đun về gọi tools từ widget), nhưng mặc định tốt hơn là đi qua follow‑up dạng văn bản. Lý do rất thực tế.

Thứ nhất, lịch sử chat vẫn liền mạch. Bề ngoài trông như người dùng tự viết: “Hãy hiển thị quà rẻ hơn”. Một tuần sau mở lại lịch sử, họ vẫn hiểu chuyện gì đã xảy ra. Nếu bạn làm mọi thứ qua các lời gọi tool trực tiếp, giữa các tin nhắn người dùng sẽ bất ngờ xuất hiện các widget khác nhau “không vì lý do gì thấy được”.

Thứ hai, mô hình có thể đưa ra quyết định bổ sung. Ví dụ, thay vì gọi lại cùng tool, nó hiểu rằng tốt hơn nên hỏi lại ngân sách: “Bạn chắc chắn muốn giảm ngân sách xuống 5$? Hay giữ ít nhất 20$?” Những kịch bản linh hoạt như vậy là không thể nếu bạn mã hoá cứng “nhấp → cùng tool với đối số khác”.

Thứ ba, mô hình có thể gọi hẳn một tool khác. Giả sử người dùng nhấn “Liên hệ hỗ trợ”, còn system‑prompt của bạn dạy mô hình rằng trường hợp đó cần gọi create_support_ticket, chứ không phải get_gift_ideas. Follow‑up dạng văn bản cho mô hình quyền tự do chuyển sang công cụ khác.

Vì vậy, quy tắc thực hành của mô‑đun 3: khi nhấp trong UI, đa số trường hợp hãy gửi follow‑up dạng văn bản, không gọi tool trực tiếp. Gọi tool thẳng từ widget chỉ giữ cho các trường hợp đặc thù, khi bạn chắc chắn không cần thêm một bước người dùng trong lịch sử.

7. Kết hợp follow‑up với trạng thái: tránh lệch pha giữa UI và văn bản

Một vấn đề thú vị: UI “sống” theo cách của nó, còn văn bản trong chat theo cách khác. Giả sử khi nhấp, bạn đổi bộ lọc trong widget và đồng thời gửi follow‑up. Nếu bạn chỉ cập nhật UI mà không ghi nhận trạng thái mới qua widgetState, ở lần render sau ChatGPT sẽ khôi phục widgetState cũ. Kết quả là widget lại hiển thị bộ lọc cũ, dù lịch sử chat đã có bước “về rẻ hơn”. Trải nghiệm này khá kỳ lạ.

Vì thế một mẫu tốt là: khi nhấp follow‑up hãy đồng thời:

  1. cập nhật widgetState,
  2. gửi tin nhắn follow‑up.

Ví dụ:

import { useWidgetState, useSendMessage } from '@/openai-apps';

type GiftWidgetState = {
  priceFilter?: 'any' | 'cheap' | 'premium';
};

export const GiftFollowups: React.FC = () => {
  const [widgetState, setWidgetState] = useWidgetState<GiftWidgetState>();
  const sendMessage = useSendMessage();

  const handleCheaper = () => {
    setWidgetState({ ...widgetState, priceFilter: 'cheap' });
    sendMessage('Hãy hiển thị quà rẻ hơn, khoảng đến 50$');
  };

  // ...
};

Giờ đây cả ChatGPT lẫn UI của bạn đều biết bộ lọc đã thay đổi. Nếu mô hình dựng lại widget sau đó, nó sẽ thấy widgetState đã được cập nhật và có thể, chẳng hạn, tạo pre‑text mới kiểu “Dưới đây là các ý tưởng ở phân khúc tiết kiệm hơn”.

8. Thiết kế follow‑up tốt

Một follow‑up tốt là nửa thành công về UX. Nó không chỉ “đẹp” — nó tiết kiệm công sức suy nghĩ của người dùng.

Có vài nguyên tắc thực dụng đáng dán ngay trên màn hình của bạn.

Thứ nhất, ngắn gọn. Follow‑up không phải nơi để “làm thơ”. Một câu ngắn, dễ hiểu ngay cả khi không nhìn UI, thường là lý tưởng: “Rẻ hơn”, “Chỉ premium”, “Đổi người nhận”. Nếu cần dài hơn, hãy cân nhắc biến nó thành văn bản GPT thông thường thay vì nút.

Thứ hai, định hướng hành động. Diễn đạt sao cho rõ điều gì sẽ xảy ra. “Thêm ý tưởng” — được. “Chi tiết hơn về mục 2” — tốt hơn đổi thành “Hãy nói chi tiết hơn về phương án số hai” (để mô hình hiểu ngay cả khi không có UI).

Thứ ba, là phần tiếp nối kịch bản chứ không lặp lại nó. Thay vì “Chọn quà lại lần nữa”, tốt hơn “Thay đổi ngân sách” hoặc “Đổi sở thích của người nhận”. Follow‑up nên đẩy người dùng tiến lên hoặc rẽ nhánh, chứ không đưa về điểm xuất phát nếu không cần.

Thứ tư, số lượng có hạn. Hai–bốn nút ở cuối widget gần như luôn đủ. Một dải mười lựa chọn biến thành “bài thi chọn số phận”: người dùng bối rối và không bấm gì cả.

Cuối cùng, chú ý giọng điệu. Nếu toàn ứng dụng trò chuyện thân thiện, thì gắn một nút “XÁC NHẬN ĐƠN HÀNG” viết hoa toàn bộ sẽ rất lạc lõng. Follow‑up là một phần của cùng cuộc đối thoại với văn bản của mô hình; phong cách phải nhất quán.

9. “Cuộc hội thoại xoay quanh widget” tổng thể: ai chịu trách nhiệm điều gì

Đừng coi widget là “nhân vật chính”, còn ChatGPT chỉ là “khung bao quanh”. Ngược lại: mô hình tiếp tục dẫn dắt đối thoại, còn widget chỉ là một cách để hiển thị và tinh chỉnh dữ liệu.

Kịch bản điển hình cho GiftGenius như sau:

  1. Người dùng: “Cần một món quà cho em gái làm IT trong tầm 100$”.
  2. Mô hình: phần dạo đầu (pre‑text) — giải thích rằng sẽ mở GiftGenius và nó làm gì.
  3. Widget: hiển thị bộ ý tưởng, cung cấp các follow‑up.
  4. Người dùng: hoặc tự gõ tin nhắn, hoặc nhấn nút follow‑up (ví dụ “Chỉ hiển thị quà kỹ thuật số”).
  5. Mô hình: diễn giải như văn bản, gọi tool cần thiết, khi cần thì bình luận kết quả (post‑text) hoặc hiển thị lại widget.
  6. Và cứ thế lặp lại cho đến khi nhiệm vụ được giải quyết — thậm chí đến mức xác nhận lựa chọn, đặt hàng, v.v.

Follow‑up ở đây là chất keo gắn kết mỗi vòng lặp. Không có chúng, sau widget người dùng rơi vào trạng thái lửng lơ: mọi thứ trông đẹp nhưng “tiếp theo làm gì” thì không rõ.

Trong các kịch bản phức tạp hơn (workflow, agent), follow‑up giúp mô hình hoá các phễu nhiều bước: “Đầu tiên chọn người nhận”, “Giờ hãy làm rõ ngân sách”, “Bây giờ xác nhận lựa chọn”. Nhưng ở mô‑đun này, điều quan trọng là thấy rằng: ngay cả một App đơn giản một bước cũng được lợi rất nhiều từ vài gợi ý được suy nghĩ kỹ.

10. Thực hành: nên làm gì ngay bây giờ

Bài tập hay để củng cố — nâng cấp widget học tập hiện tại của bạn.

Nếu bạn đã có danh sách kết quả (ví dụ quà tặng, khách sạn hoặc tài liệu), hãy thêm bên dưới một khối nhỏ “Tiếp theo làm gì?” với hai–ba nút. Cố gắng để các nút tương ứng với các loại trong bảng trên: một nút làm rõ (drill‑down, chẳng hạn “Rẻ hơn”), một nút chuyển hướng (pivot, “Đổi người nhận”), nút thứ ba — nếu cần — điều hướng (navigation, “Chuyển sang đặt hàng”).

Bên trong handler nhấp, hãy gọi useSendMessage với câu chữ tự nhiên, có ý nghĩa; đừng quên cập nhật widgetState khi cần. Sau đó chạy lại kịch bản trong ChatGPT và quan sát cuộc đối thoại: sau widget mọi thứ có rõ ràng hơn không.

Hãy thử cố tình làm các follow‑up “tệ”: dài dòng, mơ hồ, một chục lựa chọn — rồi so sánh cảm giác. Đó là cách nhanh để tự cảm nhận sự khác biệt.

11. Các lỗi điển hình khi làm việc với follow‑up

Lỗi số 1: widget “im lặng”.
Lập trình viên làm UI rất tốt nhưng hoàn toàn không đưa ra follow‑up. Người dùng thấy các thẻ, nghĩ “cũng hay”, rồi phải tự đoán rằng có thể yêu cầu: “Hiển thị rẻ hơn” hoặc “Đổi người nhận”. Đa số sẽ không đoán ra và rời đi. Ít nhất một–hai gợi ý “tiếp theo làm gì” dưới widget sẽ giải quyết vấn đề này.

Lỗi số 2: Quá nhiều nút.
Cực đoan ngược lại — spam người dùng bằng cả chục lựa chọn: “Đổi ngân sách”, “Đổi sở thích”, “Đổi tiền tệ”, “Lưu bộ gợi ý”, “Chia sẻ với bạn”, “Hiển thị tương tự”, “Hỏi hỗ trợ”, v.v. Thành “tiệc buffet” tâm lý nơi việc chọn trở nên khó khăn. Hãy bắt đầu với hai–ba hành động phổ biến nhất, phần còn lại để cho mô hình và văn bản tự do.

Lỗi số 3: Logic hội thoại chỉ ở frontend.
Đôi khi người ta “tối ưu” bằng cách thay vì gửi follow‑up qua useSendMessage (hoặc sendFollowUpMessage cấp thấp), họ gọi thẳng cùng tool từ widget và cập nhật UI. Lịch sử chat khi đó không có chữ nào về điều đã xảy ra. Sau vài bước, mô hình bắt đầu rối — và bạn cũng vậy. Cách đúng: giữ logic hội thoại ở tầng văn bản và công cụ; widget chỉ là lớp UI mỏng.

Lỗi số 4: Câu chữ mơ hồ hoặc đa nghĩa.
Nút “Thêm” không ngữ cảnh có thể là bất cứ thứ gì: thêm quà, thêm văn bản, thêm tiền? Tương tự, các câu như “Tính lại” hay “Dựng lại” đều mơ hồ với cả mô hình lẫn người dùng. Các follow‑up tốt thì cụ thể: “Hiển thị thêm lựa chọn trong ngân sách này”, “Chỉ hiển thị quà kỹ thuật số”.

Lỗi số 5: UI và văn bản không đồng bộ.
Điển hình: khi nhấp “Rẻ hơn” bạn đã đổi bộ lọc UI nhưng không gửi follow‑up vào chat hoặc không cập nhật widgetState. Kết quả là lịch sử không có bước đổi ngân sách, còn lần render widget tiếp theo thì bộ lọc “bật về” như cũ. Cảm giác giao diện “bị lỗi”. Hãy dùng kết hợp setWidgetState + sendMessage để văn bản và UI đi cùng nhịp.

Lỗi số 6: Cố gắng kiểm soát các gợi ý “native” của ChatGPT.
Đôi khi lập trình viên kỳ vọng ChatGPT sẽ tự sinh các chip follow‑up cần thiết dưới tin nhắn và không thêm gợi ý của riêng mình. Nhưng các gợi ý đó không được đảm bảo và không do ứng dụng điều khiển. Hãy coi chúng là phần thưởng dễ chịu, nhưng luôn cung cấp các nút follow‑up quan trọng của riêng bạn trong widget.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION