TS初めてのTypeScript

Day 6AIをフロントから呼ぶ(この教材の看板)

ストリーミングの考え方(応答を逐次表示する)

全部そろうまで待たず、届いた分から少しずつ表示するストリーミングの考え方をつかむ。

「全部待つ」か「届いた分から出す」か

ChatGPTのように、AIの返事が1文字ずつ流れて出てくるのを見たことがあると思います。あれが「ストリーミング」です。普通のやり方は答えが全部そろってからまとめて表示しますが、ストリーミングは届いた文字から順に表示していきます。体感の待ち時間が大きく変わるので、AIプロダクトではよく使われます。

非ストリーミング: 全部そろうまで無言

LLMが1000文字ぶんの返事を作り終えるまで、画面には何も出ない。ユーザーは「固まった?」と不安になる。

ストリーミング: 届いた分から表示

最初の数文字が来た瞬間から表示が始まる。全体の完成は同じでも、待っている感覚がぐっと減る。

なぜストリーミングにするのか

LLMは長い文章ほど生成に時間がかかります。完成を待つと数秒〜十数秒の無言時間が生まれます。ストリーミングなら最初の一片が来た瞬間に表示が始まるので、同じ生成時間でも「速い」と感じてもらえます。UX(使い心地)を上げる定番の手法です。

考え方: 川の水のように「少しずつ流れてくる」

非ストリーミングでは「返事(1つの文字列)」を丸ごと受け取ります。ストリーミングでは、返事が小さなかたまり(チャンク)に分かれて、時間差で次々と届きます。プログラム側は「かたまりが届くたびに、画面に追記する」というループを回すだけです。

TYPESCRIPT
// LLMがストリーミング対応で返してくると、res.body は
// 「少しずつ読める入れ物」になっている
export async function POST(req: Request): Promise<Response> {
  const { prompt } = (await req.json()) as { prompt: string };

  const llmRes = await fetch("https://api.example-llm.com/v1/messages", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer " + process.env.LLM_API_KEY
    },
    body: JSON.stringify({ prompt, stream: true }) // 流して返してと頼む
  });

  // 届いた流れ(body)をそのまま画面へ受け渡す
  return new Response(llmRes.body, {
    headers: { "Content-Type": "text/plain; charset=utf-8" }
  });
}
届いた分を順に読むイメージ(サーバー側)

画面側は、届いたチャンクを読みながら で管理している文字列にどんどん足していきます。すると、足すたびに再描画されて、1文字ずつ増えていくように見えます。

TSX
"use client";
import { useState } from "react";

export function Chat() {
  const [answer, setAnswer] = useState<string>("");

  async function send(prompt: string) {
    setAnswer("");
    const res = await fetch("/api/chat", {
      method: "POST",
      body: JSON.stringify({ prompt })
    });

    // res.body から少しずつ読み、届いた分を足していく
    const reader = res.body!.getReader();
    const decoder = new TextDecoder();
    while (true) {
      const { value, done } = await reader.read();
      if (done) break; // もう届かない
      setAnswer((prev) => prev + decoder.decode(value));
    }
  }

  return <p>{answer}</p>;
}
画面側: 届いた分を足していく(概念)

AIにはこう聞く

「Next.jsのroute.tsで、LLMのストリーミング応答をそのままクライアントに中継したい。サーバー側とクライアント側(useStateで逐次追記)の最小サンプルをTypeScriptで」と頼むと、この形のたたき台が得られます。まず非ストリーミングで動かしてから、ストリーミングに差し替えると理解しやすいです。

最初は無理にストリーミングしない

ストリーミングは体感を良くしますが、実装は少し複雑です。学習段階ではまず非ストリーミング(全部そろってから表示)で正しく動くことを確認し、その後で置き換えるのが安全です。動く土台があると、AIに差分だけ頼めます。

読んでも腑に落ちない所は、AIに噛み砕いてもらいましょう。

理解度チェック

答えられれば、面接で説明できる状態に一歩近づきます。

Q1

ストリーミングで応答を表示する主な利点はどれ?

Q2

画面側でストリーミングされた文字を表示するとき、届いたチャンクをどう扱うのが典型的?

Q3

ストリーミングとは何か、非ストリーミングとの違いと、学習段階での進め方を含めて説明してください。

記述式です。まず自分の言葉で答えを考えてから、模範解答を開いて自己採点してください。