DelphiでAIチャットボットを構築する: 会話メモリとストリーミング

· コンポーネント

手っ取り早い答え: チャットボットとは、チャット補完にメモリを加えたものです。sgcWebSocketsは、メッセージ履歴全体をあなたに代わって保持する専用の TsgcAIChat コンポーネントを提供します。これにより、各ターンがそれまでの会話とともに送信され、モデルは文脈に沿って回答できます。コンポーネントを配置し、APIキーとモデルを設定したら、返答には Chat を、ライブなトークン単位のタイピング効果には ChatStream を呼び出します。会話のクリアは ClearHistory を1回呼ぶだけです。

すでにDelphiからLLMを呼び出したことがあるなら、おそらくいらだたしいことに気づいているはずです。フォローアップの質問をすると、モデルはまるであなたと一度も話したことがないかのように振る舞います。それはバグではなく、一回限りの補完の仕組みそのものです。それを本物の会話型アシスタントに変えることが、おもちゃとチャットボットの違いであり、それは1つのアイデアに帰着します。それがメモリです。

一回限りの補完 vs チャットボット

LLM APIはステートレスです。各リクエストは独立しているので、モデルはそのリクエストに入れたものしか知りません。一回限りの呼び出しは、単一のプロンプトを送信します。

// User: "What is the capital of France?"  ->  "Paris."
// User: "And its population?"             ->  "Population of what?"

2番目の質問が失敗するのは、サーバーが最初の質問から何も保持しなかったからです。チャットボットは、やり取り全体を毎回再送することでこれを解決します。システムメッセージ、すべてのユーザーのターン、そしてすべてのアシスタントの返答を、順番に送ります。モデルは履歴を読み、「its」がパリを指していると認識し、正しく回答します。これにはデータベースもセッションサーバーも必要なく、会話とともに成長するメッセージリストだけがあればよいのです。本当に必要な作業は、そのリストを構築して維持することだけで、それこそがチャットボットコンポーネントがあなたに代わって行うことです。

TsgcAIChatコンポーネント: メモリはお任せ

メッセージ配列を手作業で配線する代わりに、TsgcAIChat をフォームに配置します。これは会話履歴を内部で所有し、各ユーザーメッセージと各アシスタントの返答を自動的に追加し、呼び出しのたびに蓄積されたコンテキストを送信します。プロバイダー、APIキー、モデルを設定したら、あとは Chat を呼び出すだけです。

uses
  sgcAI, sgcAI_Chat;

var
  Bot: TsgcAIChat;
begin
  Bot := TsgcAIChat.Create(nil);
  Bot.Provider := aicpOpenAI;
  Bot.ChatOptions.ApiKey := 'sk-...';
  Bot.ChatOptions.Model  := 'gpt-4o-mini';
  Bot.SystemMessage := 'You are a concise assistant for Delphi developers.';

  // Each call adds to the same conversation:
  ShowMessage(Bot.Chat('What is the capital of France?'));  // "Paris."
  ShowMessage(Bot.Chat('And its population?'));             // answers about Paris
end;

コンポーネントが最初のターンを記憶しているので、フォローアップはそのまま機能します。SystemMessage はアシスタントのペルソナを設定し、すべてのリクエストに含まれます。新しい会話を始めたいときは Bot.ClearHistory を呼び出します。話された内容を調べたり永続化したりするには、Bot.GetHistory がメッセージリストを返します。MaxHistoryMessages でメモリに上限を設けることもでき、長いチャットが無制限に成長しないようにできます(古いターンは自動的に刈り取られます)。

同じコンポーネントが、sgcWebSocketsのサポートするすべてのプロバイダーと対話します。ProvideraicpAnthropicaicpGeminiaicpDeepSeekaicpOllamaaicpGrokaicpMistral に切り替え、モデル名を変えれば、チャットボットコードの残りはまったく同じままです。ChatBotコンポーネントのページAI & LLM コンポーネントハブをご覧ください。

ライブな返答をストリーミングする

回答全体が現れるまで数秒待つのは遅く感じられます。本物のチャットボットは返答をストリーミングし、単語が生成されるにつれて表示されます。おなじみのタイピング効果です。TsgcAIChat はこれを ChatStreamOnChatStream イベントを通じて公開しており、テキストのチャンクが届くたびに発生します。

Bot.OnChatStream := BotChatStream;
Bot.ChatStream('Explain WebSockets in two sentences.');

procedure TForm1.BotChatStream(Sender: TObject; const aChunk: string;
  var Cancel: Boolean);
begin
  Memo1.Text := Memo1.Text + aChunk;  // append each token as it arrives
  // set Cancel := True to stop the response early
end;

チャンクは内部でServer-Sent Eventsを通じて段階的に配信されますが、SSEの配管に触れることは一切ありません。ストリームが終了すると、完全なアシスタントの返答が、ストリーミングしない呼び出しと同じように履歴に追加されるので、次のターンにも完全なコンテキストがあります。Cancel パラメーターを使えば、「生成を停止」ボタンを実装できます。組み立てられた最終メッセージのための OnChatMessage や、API障害を表面化するための OnChatError もあります。

音声チャットボット、最初から最後まで

アシスタントに聞かせて話させたいなら、TsgcAIOpenAIChatBot コンポーネントがループ全体をラップします。マイクの音声をキャプチャし、Whisperで文字起こしし、そのテキストをChat Completionsに送り、回答をテキスト読み上げプロバイダーを通じて話して返します。オーディオレコーダーとテキスト読み上げエンジンを差し込み、キーを設定して、Start を呼び出します。

uses
  sgcAI, sgcAI_OpenAI_Audio_ChatBot,
  sgcAI_AudioRecorder_MCI, sgcAI_TextToSpeech_System;

var
  ChatBot: TsgcAIOpenAIChatBot;
begin
  ChatBot := TsgcAIOpenAIChatBot.Create(nil);
  ChatBot.OpenAIOptions.ApiKey := 'sk-...';
  ChatBot.AudioRecorder := TsgcAudioRecorderMCI.Create(nil);
  ChatBot.TextToSpeech  := TsgcTextToSpeechSystem.Create(nil);
  ChatBot.OnChatCompletion := ChatBotChatCompletion;

  ChatBot.Start;                          // begin listening; Stop ends it
  ChatBot.ChatAsUser('Tell me a joke');   // or push a turn programmatically
end;

OnChatCompletion イベントは各返答のロールと内容を与え、OnTranscription を使えば、送信される前に聞き取られた内容を調べたり編集したりできます。これは TsgcAIChat と同じ会話のアイデアで、ただ両端に音声が加わっただけです。

リストを自分で管理したいですか?

チャットボットコンポーネントを使う必要はありません。完全に制御したいなら、自分自身の { role, content } メッセージのリストを保持し、呼び出しのたびに TsgcHTTP_API_OpenAI._CreateChatCompletion で送信します。ユーザーメッセージを追加し、配列を送信し、次のターンの前にアシスタントの返答を同じリストに追加し直します。それはまさに TsgcAIChat が内部で行っている記帳作業なので、ほとんどの人はコンポーネントに任せます。低レベルAPIについてはDelphiのOpenAI APIチュートリアルとOpenAIコンポーネントのページで扱っています。

はじめに

ここで扱ったものはすべてsgcWebSocketsに付属しています。無料トライアルを入手し、TsgcAIChat をフォームに配置し、APIキーとモデルを設定すれば、数行でフォローアップの質問に答えるコンテキスト対応のチャットボットが手に入ります。準備ができたら、ライブなタイピング効果のために ChatStream を追加してください。

ご質問、フィードバック、あるいはアプリへの組み込みのお手伝いが必要ですか? お問い合わせください — コードを書いた本人たちから返信が届きます。

関連