第7部 LLMを使った開発の基礎

第26章会話履歴の管理はプログラム側の仕事

LLMがステートレスであるにもかかわらず、チャットは前の発言を覚えているように振る舞います。その理由は、会話の管理をモデルではなくプログラム側が担っている点にあります。この章では、そのしくみと、 そこで生じる課題への対処を見ていきます。開発でチャット機能を作るときの、中心となる考え方です。

26.1チャットが文脈を覚えているように見える理由

チャットが文脈を覚えて見えるのは、モデルが記憶しているからではなく、プログラムがこれまでの会話をまとめて毎回モデルに渡しているからです。

対話型のLLMを使っていると、前に話した内容を踏まえて応答してくれるため、モデルが会話を覚えているように 感じます。しかし、前章で見たとおり、モデル自身はステートレスで、前回の入力を保持していません。では なぜ文脈が保たれるのか。それは、チャットのしくみを動かしているプログラムが、これまでの会話のやり取りを ためておき、毎回それをまとめてモデルへ渡しているからです。記憶しているように見えるのは、この裏方の 働きによるものです。

つまり、私たちが「LLMが覚えている」と感じている記憶の正体は、モデルの中にあるのではなく、その外側の プログラムが管理している会話履歴です。この事実を知ると、チャットのしくみが一気に見通せるようになります。 記憶しているのはモデルではなく、モデルを呼び出している側なのです。

26.2過去のやり取りを毎回入力に詰め直す

会話を続けるために、プログラムはこれまでの発言の履歴を保持し、新しい発言のたびに、履歴全体をまとめてモデルへ渡し直します。

具体的には、次のような流れになります。利用者が発言するたびに、プログラムはその発言を会話の履歴に 書き足します。そして、モデルを呼び出すときには、これまでの履歴すべてに今回の発言を加えたものを、 ひとまとめの入力として渡します。モデルは、その入力全体を見て応答します。つまり、毎回の呼び出しで、 会話の最初からのやり取りを渡し直しているわけです。次のコードは、この流れをイメージで示したものです。

Python(イメージ)
# 会話の履歴をプログラム側で持っておく
history = []

def chat(user_text):
    history.append({"role": "user", "content": user_text})
    # これまでの履歴すべてを渡してモデルを呼ぶ
    reply = llm(history)
    history.append({"role": "assistant", "content": reply})
    return reply

chat("私の名前はハルです")
chat("私の名前は何でしたか")  # 履歴に名前が含まれるので答えられる
プログラムが持つ会話履歴 利用者:私の名前はハルです 応答:こんにちは、ハルさん 利用者:名前は何でしたか まとめて渡す LLM 応答
図26-1 プログラムが会話履歴を保持し、毎回それをまとめてモデルへ渡すことで、文脈が保たれます。

26.3履歴が増えるとコンテキストを圧迫する

会話が長くなると履歴も長くなり、第23章で見たコンテキストウィンドウを圧迫します。上限を超えると、古い履歴を渡せなくなります。

毎回すべての履歴を渡すやり方には、限界もあります。会話が長くなるほど履歴は積み重なり、渡す入力も どんどん長くなります。第23章で見たとおり、一度に扱える量にはコンテキストウィンドウという上限があります。 履歴が長くなりすぎると、この上限に収まらなくなり、古いやり取りを渡しきれなくなります。加えて、渡す トークンが増えるほど、処理にかかる時間や費用も増していきます。第28章で見るように、毎回渡す履歴が そのまま費用に効いてくるのです。長い会話をどう扱うかは、開発上の実際的な課題です。

26.4履歴の要約・取捨選択という工夫

長い履歴に対しては、古い部分を要約する、重要なやり取りだけを残す、といった工夫で、限られたコンテキストを有効に使います。

履歴がコンテキストウィンドウを圧迫する問題には、いくつかの対処があります。一つは、古いやり取りを 短く要約して、要点だけを残す方法です。もう一つは、会話の中でとくに重要な部分だけを選んで渡す方法です。 いずれも、限られた枠の中に、続きの応答に必要な情報を効率よく収めることを狙っています。何を残し何を 省くかの判断が、会話の自然さを左右します。

実務メモ:履歴管理の代表的なパターン

長い会話を扱うときの工夫には、いくつかの定番があります。直近の何回かのやり取りだけを残して古いものは 捨てる方法、古い部分をLLM自身に要約させて短い要約として持っておく方法、そのやり取りが済んでも 覚えておきたい事実(利用者の名前や設定など)だけを別に取り出して常に渡す方法などです。どれを選ぶかは、 用途しだいです。雑談なら直近だけで十分なこともありますし、長い相談なら要約が要ります。「毎回すべてを 渡す」から一歩進んで、「必要なものだけを賢く渡す」ことが、実用的なチャットづくりの勘所です。

このように、会話の管理はモデルの外側、すなわちプログラム側の仕事です。この視点を持つと、LLMを使った アプリケーションが内部で何をしているのかが見えてきます。次の章では、そのLLMをどこで動かすか、 ローカルとクラウドという選択肢を見ていきます。

この章の要点

  • チャットが文脈を覚えて見えるのは、プログラムが会話履歴を毎回モデルに渡しているからです。
  • 記憶の正体はモデルの中ではなく、外側のプログラムが管理する会話履歴です。
  • 会話が長くなると履歴がコンテキストウィンドウを圧迫し、費用や時間も増えます。
  • 直近だけ残す・要約する・重要な事実だけ取り出す、といったパターンで履歴を賢く管理します。