1. ChatGPT App における follow‑up とは何か、なぜ必要か
まずはマーケティング用語ではなく、分かりやすい言葉で定義から始めましょう。ChatGPT App の文脈での follow‑up とは、会話におけるプログラム主導の「次の一手」です。通常はボタンとして短い提案を提示し、クリックするとそれがチャット内のユーザーからの新しいテキスト投稿になり、人間とAIの会話シナリオの続きが走り出します。
モデルの観点では、follow‑up は単なるもう一つのユーザーメッセージです。魔法はありません。例えばユーザーがあなたの「もっと安い候補を表示」ボタンをクリックすると、チャット履歴には「もっと安いプレゼントを見せて」のような内容が入ります。モデルはそれを通常のユーザー発話として見て、system‑prompt や tools の記述を踏まえ、どのツールを呼ぶかを判断し、場合によっては新しいデータであなたのウィジェットを再レンダリングします。
なぜ重要か:
- モデルはテキストで「考えます」。 クリックに応じてメッセージなしでツールを直接呼ぶと、システムの「頭脳」をバイパスし、文脈の一部を失います。follow‑up は会話の進捗を履歴に残し、何が起きているかをモデルが理解しやすくします。
- ユーザーはチャットという馴染みのパラダイムに留まります。自分でメッセージを書くか、提案をタップするか。follow‑up は現代のメッセンジャーにある「クイックリプライ」と同じ役割です。
- これはあなたのUIとChatGPTのテキスト部分をつなぐ主要な橋です。follow‑up がなければ、ウィジェットは無口な孤島になりがちです。ユーザーはクリックしても「この先どうするのか」を掴めません。
follow‑up は、あなたが用意した「AIへの次の質問」ボタンだと捉えると分かりやすいでしょう。以降では、follow‑up を単なる「UIの小機能」ではなく、ウィジェットの周囲で対話を構築する主な手段として扱います。
2. 「ウィジェットを中心とした対話」: conversational sandwich
follow‑up が「ウィジェットを中心とした対話」をどう助けるかを捉えやすくするため、会話を3層のサンドイッチとして考えると便利です。
- 上段はウィジェットの前にあるChatGPTのテキスト(pre‑text)
- 中段はあなたのウィジェット(UI)
- 下段は follow‑up とその後のメッセージ(post‑interaction)
図解:
sequenceDiagram
participant U as ユーザー
participant G as ChatGPT
participant W as ウィジェット
U->>G: "妹向けのプレゼントを100ドル以内で選んで"
G->>G: アプリを呼び出すと判断
G->>U: Pre-text: "今から GiftGenius を開いてアイデアを選びます"
G->>W: レンダリング用の toolOutput を渡す
W-->>U: プレゼントカード + follow-up ボタン
U->>W: 「もっと安い候補を表示」をクリック
W->>G: sendFollowUpMessage("50ドル以下の安いプレゼントを見せて")
G->>G: モデルの再実行、tools を呼び出す
G->>W: 新しい toolOutput、更新されたウィジェット
Pre‑text は通常、system‑prompt とコンテキストにもとづいてモデルが自動生成します。たとえば「今からプレゼント選びを手伝うアプリを開きます」のように「これから何が起きるか」を説明します。これをコントロールする方法は、後のインストラクションのモジュールで扱います。
ウィジェットは、あなたにとって馴染みの React コンポーネントです。toolOutput を描画し、widgetState を使い、ボタンや選択肢を提供します。
Post‑interaction 層は、今日取り上げる部分です。follow‑up とメッセージ送信によって、「高価格帯を表示」「予算を変更」「選び直す」「購入手続きへ」といった次の進路を明確に示します。
要するに、follow‑up は UI → テキスト → 新しい UI という循環を閉じるための制御発話です。
3. 技術モデル: follow‑up のクリックが新しい tool‑call に変わるまで
「舞台裏」のイベント連鎖を詳しく見ていきましょう。クリック → API → 履歴上のテキスト → 新しいモデル判断 → 新しいツール呼び出し → 更新されたUI、というハイブリッドな相互作用サイクルだと考えると分かりやすいです。
おおよその流れ:
- ユーザーがウィジェット内のボタンをクリックする。
- ウィジェットが API の window.openai.sendFollowUpMessage または React 層の便利なフック useSendMessage(以下の例ではこのフックを使用)を呼び出す。引数は通常の文字列、すなわち「ユーザーが入力したかのようなテキスト」。
- ChatGPT はこのメッセージを新しい user_message として履歴に追加する。
- モデルが新しいパスを実行する。直前の toolOutput を含む全履歴を考慮し、どのツールをどの引数で呼ぶかを判断する。
- あなたの backend/MCP が tool‑call を実行し、toolOutput を返す。
- ChatGPT が新しい応答を表示する。再びウィジェット(新しいデータつき)か、テキストか、あるいはその組み合わせかもしれない。
重要なのは、ウィジェットからモデルが呼んだのと同じツールを、このサイクルを迂回して直接叩きたくなる場面があっても、ほとんどの場合それは避けたいという点です。そうするとモデルが「一手」を失い、履歴に再実行を引き起こした要求が残らなくなります。長いシナリオでは後から文脈が混乱しやすくなります。
短い式にするとこうなります:
User Click → useSendMessage("...") → ChatGPT (LLM) → Tool Call → New toolOutput → Widget rerender
4. follow‑up の種類と、その発想源
クリック後に「舞台裏」で何が起きるかは理解できました。次に、どんな種類の follow‑up をユーザーに提示すべきか、そしてシナリオの中でどのような役割を果たすのかを見ていきます。
現実のアプリには、複数の「ファミリー」に属する follow‑up があります。静的 vs 動的、役割(drill‑down、pivot、commit など)といった軸で分類できます。
実務上は表にするのが分かりやすいでしょう。
| タイプ | 役割 | テキスト/ボタン例 |
|---|---|---|
| 提案型(Suggestive) | ユーザーが次に何を聞けば良いか分からないときに助ける | 「他のアイデアを表示」「興味で絞り込む」 |
| 詳細化(Drill-down / Parametric) | 前のリクエストをパラメータで絞り込む | 「より安く」「デジタルのプレゼントのみ」「Nike のみ」 |
| 方向転換(Pivot) | シナリオの分岐を変える | 「選び直す」「子ども向けのプレゼントを表示」 |
| ナビゲーション(Navigation) | プロセスの別ステップへ移動する | 「購入手続きへ」「選択に戻る」 |
| 確定(Commit) | アクションを確定する | 「このプレゼントを注文」「セレクションを保存」 |
発想源の観点では、大きく2つのグループがあります。
第一に、アプリ自身が提案する follow‑up。これはUIロジックやデータに紐づくウィジェット内のボタンです。たとえば「[プレゼント名] に似たものを表示」や「興味が travel のものだけで絞る」。こうした提案は固定(static)でも、toolOutput に基づいて動的(dynamic)に生成しても構いません。
第二に、ChatGPT の「ネイティブ」な提案です。メッセージ下に出る小さなチップで、モデルが自動で考えます。 これは直接コントロールできません。頼れる仕組みではなく「無料のボーナス」と捉えてください。あなたのアプリはそれらがなくても正しく動く必要があります。
この講義では第一のグループに重点を置きます。すなわち、あなたが React コンポーネントの中で描画し、クリックで sendFollowUpMessage を送るボタンや提案です。
5. React における follow‑up 実装: GiftGenius を例に
プレゼントを提案する仮のアプリ GiftGenius を続けます。ツール get_gift_ideas を呼び出した後、ウィジェットはプレゼントの一覧を含む toolOutput を受け取ります。前回までにカードのグリッドは作成済みとしましょう。ここに follow‑up セクションを追加します。
SDK に useWidgetProps と useSendMessage というフックがあると仮定します。名前は仮ですが、リファレンス実装とコンセプトは一致します。
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>プレゼントが見つかりませんでした。クエリを変更してみてください。</div>;
}
const handleCheaper = () => {
sendMessage('50ドル以下の安いプレゼントを見せて');
};
const handleDigital = () => {
sendMessage('デジタルのプレゼント(ギフト券・サブスクなど)だけを見せて');
};
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">次はどうしますか?</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"
>
より安く
</button>
<button
onClick={handleDigital}
className="px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200"
>
デジタルのみ
</button>
</div>
</div>
</div>
);
};
ここで重要な点がいくつかあります。
第一に、sendMessage は文字列を受け取り、そのテキストが「ユーザーが入力したかのように」チャットに表示されます。アプリが「内部プロトコル」を模倣する必要はありません。モデルに伝わる自然言語で簡潔に書きましょう。
第二に、follow‑up セクションは(上部ボーダーや小さな「次はどうしますか?」などで)視覚的に分離します。カード自体の要素ではなく、会話の続きであることをユーザーに伝えるためです。
第三に、ボタンは少なめにします——2つ程度。UXの観点では2〜4個が目安です。助けになる一方で、過剰な負担になりません。
データに基づく動的な follow‑up
特定のプレゼントを起点に掘り下げたい(「これに似たものを表示」)としましょう。その場合、follow‑up のテキストに対象オブジェクトを含め、動的に生成できます。
const handleShowSimilar = (giftTitle: string) => {
sendMessage(
`「${giftTitle}」に似たプレゼントを見せて。同じ予算か、少し高めでもOK`
);
};
カード側では:
<button
onClick={() => handleShowSimilar(gift.title)}
className="mt-2 text-xs text-blue-600 underline"
>
似ているものを表示
</button>
このようにして、toolOutput の具体的なデータに紐づく動的な follow‑up を実装できます。こうしたボタンこそが、単なる「次へ/戻る」の羅列ではない「賢い」対話を実現します。
6. なぜテキストを送るのか——ツールを直接呼ばない理由
フロントエンド開発者がよく思うのは「useCallTool があるなら、わざわざテキストを送らずに、get_gift_ideas を別のパラメータで直接叩けばいいのでは?」ということです。確かにそれが必要な場合もあります(ウィジェットからツールを呼ぶモジュールで詳しく扱います)。しかし、原則としてはテキストの follow‑up を経由するほうが良いです。実務的な理由があります。
第一に、チャット履歴の一貫性が保てます。外から見ると、ユーザーが「より安いプレゼントを見せて」と自分で書いたように見えます。一週間後に履歴を開いても、何が起きていたのかが分かります。直接のツール呼び出しだけで進めると、ユーザーのメッセージの間に理由不明でウィジェットが出現することになります。
第二に、モデルが追加判断を行う余地が生まれます。たとえば、同じツールを再度呼ぶ前に「予算を5ドルに下げますか?少なくとも20ドルにしませんか?」と確認したほうが良いと判断するかもしれません。クリック → 同じツールに別引数という固定ロジックでは、こうした柔軟なシナリオが不可能です。
第三に、モデルが全く別のツールを呼ぶこともあります。たとえばユーザーが「サポートに連絡」を押したとき、system‑prompt が create_support_ticket を呼ぶよう学習していれば、get_gift_ideas ではなくそちらを選ぶでしょう。テキストとしての follow‑up は、モデルにツール切り替えの自由度を与えます。
したがってモジュール3の実践ルールはこうです。UI のクリックでは、たいていの場合テキストの follow‑up を送る。ツールの直接呼び出しは、履歴に新しいユーザー歩を追加したくない特殊ケースに限る。
7. follow‑up と状態管理の連携: UI とテキストをずらさない
厄介なのは、UI とチャットのテキストが「別々に生きてしまう」問題です。クリックでウィジェット内のフィルタを変更し、同時に follow‑up を送ったとします。UI だけ更新して widgetState に固定しなかった場合、次のレンダリングで ChatGPT は古い widgetState を復元します。結果として、履歴には「安くして」とあるのに、ウィジェットは古いフィルタを再表示してしまう、という奇妙な体験になります。
そこで良いパターンは、follow‑up のクリックで同時に:
- widgetState を更新する
- follow‑up メッセージを送る
例:
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('おおよそ50ドルまでの安いプレゼントを見せて');
};
// ...
};
これで ChatGPT と UI の双方がフィルタ変更を認識します。後でモデルがウィジェットを作り直す場合でも、更新済みの widgetState を参照し、たとえば「より予算を抑えたセグメントのアイデアです」といった新しい pre‑text を生成できます。
8. 良い follow‑up を設計する
優れた follow‑up は UX 成功の半分を占めます。単に「見た目が良い」だけでなく、ユーザーの思考負荷を減らします。
実務的な原則をいくつか掲げておきます。
第一に、簡潔さ。follow‑up は長文を書く場所ではありません。UI の文脈なしでも分かる短いフレーズが理想です。「より安く」「プレミアムのみ」「受取人を変更」など。長くなるなら、それはボタンではなく通常のGPTテキストにすべきかもしれません。
第二に、行動志向。何が起きるかが明確になるように表現しましょう。「さらにアイデア」は悪くありませんが、「2番目の案について詳しく」は「2番目の候補について詳しく教えて」にすると、UI がなくてもモデルが意図を理解しやすくなります。
第三に、シナリオの「続き」であって「繰り返し」ではないこと。「もう一度プレゼントを選ぶ」よりも「予算を変える」「受取人の趣味を変える」のほうが良い場合が多いです。follow‑up はユーザーを前へ、あるいは横へ進めるべきで、理由なく出発点へ戻すべきではありません。
第四に、数を絞る。ウィジェットの下に2〜4つのボタンで十分です。10個並べると「選択の試験」になって、ユーザーは戸惑い、結局何も押しません。
最後に、トーン&マナーを合わせる。アプリ全体がフレンドリーなのに、突然「注文を確定」のような強い口調(しかも全部大文字)だと違和感があります。follow‑up はモデルのテキストと同じ対話の一部です。スタイルは一致させましょう。
9. 「ウィジェットを中心とした対話」全体像: 役割分担
ウィジェットを「主役」、ChatGPT を「その外枠」と捉えないことが重要です。実際は逆に近いのです。会話を進めるのはモデルであり、ウィジェットはデータを表示・調整する一手段に過ぎません。
GiftGenius の典型的なシナリオ:
- ユーザー: 「妹はIT系。100ドル以内でプレゼントが必要」
- モデル: テキストの導入(pre‑text)——GiftGenius を開いて何をするかを説明
- ウィジェット: アイデアのセレクションを表示し、follow‑up を提示
- ユーザー: 自分でメッセージを書くか、follow‑up ボタン(例:「デジタルのプレゼントのみ」)を押す
- モデル: それをテキストとして解釈し、必要なツールを呼び、必要に応じて結果をコメント(post‑text)するか、再びウィジェットを表示
- このループを、選択の確定・注文などの完了まで続ける
ここで follow‑up は、各ループをつなぎ合わせる「のり」です。これがなければ、ウィジェットの後でユーザーは宙ぶらりんになります。見栄えは良くても、「次に何をすべきか」が見えません。
より複雑なシナリオ(ワークフローやエージェント)でも、follow‑up は多段のファネルをモデル化するのに役立ちます。「まず受取人を選ぶ」「次に予算を絞る」「最後に選択を確定」といった流れです。しかしこのモジュールで大切なのは、最もシンプルな一段のアプリでさえ、よく練られた少数の提案で大いに改善される、という点を理解することです。
10. 実践: いま何をすべきか
良い練習として、いまの学習用ウィジェットを改善してみましょう。
すでに結果リスト(プレゼント、ホテル、ドキュメントなど)があるなら、その下に「次は?」という小さなブロックを置き、2〜3個のボタンを並べましょう。上の表のタイプに対応させるのがコツです。1つは詳細化(drill‑down。「より安く」など)、もう1つは方向転換(pivot。「受取人を変更」)、必要であればナビゲーション(navigation。「購入手続きへ」)を。
クリックハンドラの中では useSendMessage を使い、自然言語で意味の通るテキストを送ります。必要に応じて widgetState の更新も忘れずに。その後、ChatGPT でシナリオを再実行し、ウィジェット後の対話がどれだけ分かりやすくなったかを観察してください。
あえて悪い follow‑up を作って比較するのもおすすめです。長すぎる、曖昧、選択肢が多すぎる——この違いはすぐに体感できます。
11. follow‑up でよくある間違い
間違い1: 「無言」のウィジェット。
優れたUIを作っても、follow‑up を全く提示しないケースです。ユーザーはカードを見て「良さそう」と思っても、「もっと安くして」「受取人を変えて」といった指示ができるとは気づきません。多くの人は気づかずに離脱します。最低でも1〜2個の「次に何をするか」の提案があれば、この問題は解決します。
間違い2: ボタンが多すぎる。
逆に、10個近い選択肢でユーザーをスパムするパターンです。「予算変更」「興味変更」「通貨変更」「セレクション保存」「友人と共有」「似たものを表示」「サポートへ問い合わせ」等々。心理的なビュッフェ状態になり、選べなくなります。まずは頻度の高い2〜3個に絞り、残りはモデルや通常のテキストに任せましょう。
間違い3: 対話ロジックをフロントエンドだけで持つ。
「最適化」と称して、useSendMessage(や低レベルの sendFollowUpMessage)で follow‑up を送る代わりに、ウィジェットから同じツールを直接叩いてUIだけ更新する、というやり方です。チャット履歴には何が起きたか一言も残りません。数ステップ後にはモデルもあなたも混乱します。正しいやり方は、対話ロジックをテキストとツールのレイヤーに置き、ウィジェットは薄いUIレイヤーに留めることです。
間違い4: 分かりにくい、曖昧な文言。
文脈なしの「もっと」では、もっとプレゼント?もっとテキスト?もっと予算?何でもありです。「再計算」「再生成」も、モデルにもユーザーにも不明確です。良い follow‑up は具体的です。「この予算でさらに候補を表示」「デジタルのプレゼントのみを表示」など。
間違い5: UI とテキストの非同期。
典型例です。「より安く」をクリックしてUIのフィルタだけ更新し、follow‑up を送らない、または widgetState を更新しないパターン。履歴に予算変更のステップが残らず、次のレンダリングでフィルタが「巻き戻る」。壊れたUIの印象になります。setWidgetState + sendMessage の組み合わせで、テキストとUIの歩調を合わせましょう。
間違い6: ChatGPT の「ネイティブ」提案を制御しようとする。
ChatGPT がメッセージ下に期待どおりの follow‑up チップを生成する前提で、自前の提案を入れないケースです。しかしこれらのチップは保証もコントロールもできません。心地よいボーナスと捉えつつ、必須の follow‑up ボタンは常にウィジェット側で用意してください。
GO TO FULL VERSION