PubNubとChatGPT / OpenAIでチャットボットを構築する

PubNub Developer Relations - Mar 11 - - Dev Community

ごく最近まで、チャットボットを作ろうと思えば、「自然言語処理」(NLP)を使ってユーザーが何を求めているかを理解・解釈し、バックエンドのロジックを使ってその要求を満たそうとしていました。DialogflowやMicrosoft Bot frameworkのようなソリューションは、機械学習に基づいた会話AIを提供し、チャットボットを作成する開発者に伝統的に人気があります。

OpenAIとOpenAIのChatGPTは、人工知能を搭載したAIチャットボットをこれまで以上に簡単に作成できるようにします。OpenAIの動力源である大規模言語モデル(LLM)は公開データセットに基づいているため、公開知識ベースに存在すると合理的に予想されるあらゆる情報を提供することで、アプリのユーザーエクスペリエンスを簡単に向上させることができます。

PubNubは、以下のような幅広い業界やユースケースのためのリアルタイム体験を提供します。 カスタマーサポートおよび アプリ内チャットこれらのリアルタイム体験はすべて、ユーザーが読めるメッセージやソリューションに関するメタデータなどのデータによって提供されています

実際の使用例をいくつか挙げてみましょう:

  • よくあるカスタマーサポートの質問に対する回答を提供する

  • 既存のデータを追加コンテンツで補強する。例えば、指定された場所の近くで食事ができる場所を提案する。

  • メッセージ履歴に基づいて会話の要約を提供する。

私たちは 記事その古い記事は今でも正しいが、業界の動きは非常に速く、数ヶ月というのは長い。

この記事では、私たちの PubNubショーケースアプリケーションの中で動いているものと同じような、OpenAIで動くチャットボットの作り方を説明します。

The showcase's AI model has been primed to mention PubNub, where appropriate

ハイレベルなアーキテクチャ

ChatGPTのようなチャットボットを作成するには、 OpenAIのチャットAPIを使用し、アカウントにサインアップして APIキーを作成する必要があります。 OpenAIのAPIはRESTエンドポイントとしてアクセスできるので、ほとんどのプログラミング言語から呼び出すことができます。

PubNubからデータを抽出し、チャットボットとの会話を開始または継続するための最良の方法は、Functionsを使用することです。 Functionsは、Node.JSで実行されるサーバレスJavaScriptコンテナを提供し、チャットメッセージの受信など、PubNubネットワーク上でイベントが発生するたびにビジネスロジックを実行することができます。 PubNub Functionsは、リアルタイムオンザフライ言語翻訳、冒涜フィルタリング、またはOpenAIのようなサードパーティのサービスを呼び出すために頻繁に使用されます。

この例は次のように動作します:

  • 既存のチャットアプリケーションはPubNubを使ってリアルタイムでメッセージを送受信している。

  • PubNubファンクションは、特定のチャネルに送信されたメッセージをリッスンします。

  • 単一のメッセージだけではChatGPTにコンテキストを提供しないので、進行中の対話を可能にするために会話履歴の一部もキャッシュされます。

  • PubNub関数はOpenAI APIを呼び出し、レスポンスを取得します。

  • レスポンスはチャットボットに関連付けられているPubNubチャンネルに公開されます。

下のシーケンス図を見てください:

Example sequence diagram for implementing a ChatGPT-like chatbot with PubNub and OpenAI

  1. 50以上のSDKを持つPubNubは、大多数のアプリケーションをサポートすることができ、メッセージはPubNubネットワーク上のチャットボットを表すチャネルに公開されます。

  2. PubNubの関数は、チャネル上でパブリッシュイベントが発生した後に呼び出されるように構成されています。 メッセージを受信すると、チャネルの履歴が取得され、会話にいくつかのコンテキストが提供されます。この履歴は、ユーザー入力といくつかの追加のコンテキストと一緒に、チャット完了APIを介してChatGPTに提供されます。

  3. ChatGPT APIは会話の流れに適したレスポンスを生成します。

  4. PubNub関数内で、ChatGPTから応答が受信され、それが解析されてPubNubネットワークに パブリッシュされます。

  5. PubNubからのメッセージを受信するようにサブスクライブしたアプリケーションは、チャットボットの応答を表示します。

PubNub関数の設定

以下のようにPubNub関数を作成します:

前提条件:PubNubを初めて利用する場合は、ドキュメントに従ってアカウントを設定するか、ツアーに参加してPubNubがどのようなものかを理解してください。

  1. 管理ダッシュボードに移動します

  2. 左側のメニューから関数を選択し、関数を作成したい適切なキーセットを選択します。

  3. 新規モジュールの作成を選択します。

  4. Module NameとModule Descriptionを入力し、キーセットを選択してCreateをクリックします。

  5. 作成したモジュールを選択します。

  6. Create New Functionを選択します。

  7. ファンクションに名前を付け、イベントタイプとしてAfter PublishまたはFireを選択します。 このファンクションはメッセージがPubNubに送信された後に呼び出されます。この場合、OpenAIに送信するメッセージのコピーをキャプチャします。関数の種類についての詳細はドキュメントを参照してください。

  8. 関数のチャンネルは、OpenAI に送信したいメッセージを公開しているチャンネルと一致させる必要があります。 ここではワイルドカードを使うことができるので、例えばchatgpt.*は、chatgpt を開始するすべてのチャンネルと一致します。 この関数は同時に複数の人と会話したいので、ワイルドカードを使うことで、アクティブな会話に参加しているユーザを分けることができ、適切なチャンネルで応答できるようになります。複数の人が同時にOpenAIと会話している場合、PubNubはあなたの関数の複数のインスタンスを作成します。 例えば、chatgpt.daveと chatgpt.simonは2つの異なるチャンネルですが、どちらもchatgpt.*でリッスンしているPubNub関数を呼び出します。PubNubがどのようにチャンネルワイルドカードを処理するかはドキュメントで詳しく説明しています。

  9. マイシークレットを選択し、OpenAIAPIキー用の新しいシークレットを作成します。以下のコードでは、このシークレットがOPENAI_API_KEYという名前であると仮定しています。

以下はOpenAI / ChatGPTを呼び出すPubNub関数の例です。

const pubnub = require('pubnub');
const xhr = require('xhr');
const vault = require('vault');

// Process all messages posted to the private ChatGPT channel
// This will be unique for each user, so chat is 1:1 with ChatGPT
export default request => {
  const inboundChannel = request.channels[0];
  if (request.message.content.type == "text" && request.message.sender != "OpenAI")
  {
    //  user’s query
    let naturalCommand = request.message.content.text;
   // Always provide OpenAI with some context of how you want questions answered
    let historicalMsgs = 
      [{"role": "system", "content": "You are a helpful assistant, expert in PubNub"}];
    return getOpenaiApiKey().then(apikey => {
      // Read the last messages from the conversation to provide conversation context
      return pubnub.history({
        channel: inboundChannel,
        count: 10
        }).then((response) => {
          response['messages'].forEach((value, index) => {
            // The message role will vary depending on whether it was sent or received 
            if (value.entry.sender && value.entry.sender == "OpenAI")
              historicalMsgs.push({"role": "assistant", 
                "content": value.entry.content.text})
            else
              historicalMsgs.push({"role": "user", 
                "content": value.entry.content.text})
          });

          // Depending on timing, last published message may or may not yet be stored
          if (historicalMsgs[historicalMsgs.length - 1].content != naturalCommand)
          {
            historicalMsgs.push({"role": "user", 
              "content": naturalCommand});
          }
          return openAI(naturalCommand, historicalMsgs).then(aiResponse => {
            // Respond back by publishing the message to PubNub
            pubnub.publish({
              channel: inboundChannel,  //  In the showcase, this is unique per user
              message: {
                content: {
                  type: "text",
                  text: aiResponse
                },
                sender: "OpenAI",
              },
            });
            return request.ok();
          });
        })
      });
    }
    return request.ok();
  };


  // Get API Key for OpenAI
  // Key is populated via Vault Secret Manager  
  let OPENAI_API_KEY = null;
  function getOpenaiApiKey() {
    // Use cached key
    if (OPENAI_API_KEY) {
      return new Promise(resolve => resolve(OPENAI_API_KEY));
    }
    // Fetch key from vault
    return vault.get("OPENAI_API_KEY").then(apikey => {
      OPENAI_API_KEY = apikey;
      return new Promise(resolve => resolve(OPENAI_API_KEY));
    });  
  }

  // API Call to OpenAI asking the AI to run functions if it thinks it needs to
  function openAI(naturalCommand, msgs) {
    // Generate Function Instructions for the AI
    const url = 'https://api.openai.com/v1/chat/completions';
    const http_options = {
      'method': 'POST',
      'headers': {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${OPENAI_API_KEY}`,
      },
    'body': JSON.stringify({
      "model": "gpt-3.5-turbo",
      "messages": msgs,
      "max_tokens": 300
    }),
    timeout: 9500, // PubNub functions timeout limit can be raised if needed
    retries: 0
  };

  return xhr.fetch(url, http_options).then((resp) => {
    const body = JSON.parse(resp.body);
    return body.choices[0].message.content;
  })
  .catch((err) => {
    console.log(err);
    return "Timed out. Please try again or ask a simpler question";
  });
}
Enter fullscreen mode Exit fullscreen mode

OpenAI APIを呼び出す際の実用的な考慮事項

チャット補完APIのプロパティ

この記事で紹介するコードはOpenAI Chat Completion APIを使用していますが、上記で紹介するよりも多くの設定が可能です。

streamをtrueに指定すると、OpenAIのレスポンスがサーバから送信されたイベントのデータストリームとして返されます。 実用的には、ChatGPTのウェブインタフェースがどのように動作するかに似て、ユーザはより即時のレスポンスを見ることができるので、これは知覚的な待ち時間を減らすことができます。

modelは、応答を生成するために使用される言語モデルを指定します。 API は、異なる機能と価格帯を持つモデルファミリによって提供され、モデルは頻繁にアップグレードされ、新しいモデルが追加されます。

max_tokensは、APIが出力を生成する際に使用するトークンの数を制限します。 一般的に、トークンの数が少なければ少ないほど、出力は複雑でなくなり、コンテキストの追跡も少なくなりますが、レスポンスはより速く提供されます。

メッセージの役割は、メッセージの作成者を指定することができます。 この記事の例では、OpenAPIにいくつかの指示を提供するためにsystemを使用し、エンドユーザーによって送信されたメッセージにはuserを使用し、OpenAIによって以前に提供された応答にはassistantを使用しています。

このモデルは、temperature/top_pfrequency_penalitypresence_penalty を指定することで、さらに微調整することができます。 これらの詳細については、 OpenAPI のドキュメントを参照してください。

このAPIでは、モデルの機能を拡張するカスタム関数を定義して指定することもできますが、このブログの範囲外です。 もっと詳しく知りたい場合は、ChatGPTがあなたの代わりにPubNubにパブリッシュする方法を説明した 別の投稿があります。"また、ChatGPTが独自のビジネスAPIを呼び出すことを許可して、例えば、製品情報や注文ステータス情報を顧客に直接提供することもできます。

チャットにコンテキストを提供する

HTTPリクエストはステートレスなので、OpenAIと長く続く会話をするためには、APIにコンテキストを与え、これまでの会話を説明し、その会話の周りのメタデータを提供する方法が必要です。

チャット完了APIは メッセージの配列を受け付け、AIモデルが曖昧な質問から意味を導き出すことを可能にします。 この例を考えてみましょう:

> ユーザー] 地球の大きさは?

> OpenAI] 地球の直径は約12,742kmです。

> [User]地球の重さは?

> OpenAI] 地球の質量は約5.97x10^24kgです。

明らかに、"How heavy is it "という質問に答えるために、AIは "it "が何であるかを理解する必要があった。

PubNubを利用したチャットボットを構築する際には、どの程度の会話コンテキストを提供すべきかを決定する必要があります。 PubNubの機能を使用している場合、チャネルのメッセージ履歴全体にアクセスすることができ、この例では会話の前の10件のメッセージを提供しています。 もちろん、ニーズに応じて、必要であればもっと多くのメッセージを提供することもできます:

// Read in the last messages from the conversation to provide some context to Chat GPT
pubnub.history({
  channel: inboundChannel,
  count: 10
}).then((response) => {
  response['messages'].forEach((value, index) => {
    if (value.entry.sender && value.entry.sender == "ChatGPT")
      historicalMsgs.push({"role": "assistant", "content": value.entry.content.text})
    else
      historicalMsgs.push({"role": "user", "content": value.entry.content.text})
});
Enter fullscreen mode Exit fullscreen mode

PubNub内にチャットのコンテキストをすでに持っていれば、OpenAIにそのコンテキストを提供するのは簡単です。 メッセージがOpenAIに送信されたか、OpenAIから受信されたかによって役割が異なることに注意してください。

長時間クエリ

ChatGPTのようなチャットボットを開発し、AIモデルによって使用されるパラメータを設定するとき、クエリに対する予想応答時間を考慮してください。 デフォルトでは、PubNub関数は、呼び出しごとに10秒の最大実行時間を提供し、サポートに連絡することによって増加させることができます。 一般的に、ユーザの質問に応答するのに時間がかかりすぎるチャットボットは、ユーザによって否定的に認識されるので、前述のように、応答性と能力の間のバランスを達成するためにモデルのパラメータをダイヤルすることは良いプラクティスです。

OpenAIのユースケースによっては、秒単位ではなく分単位でのレスポンスを期待することができます。 OpenAIをPubNubソリューションに統合していて、レスポンスの時間がデフォルトのFunctionランタイムを超えることが予想される場合は、弊社にご連絡ください。

要約すると

PubNubとOpenAI / ChatGPTの両方を組み合わせることで、あらゆるオンライン体験にAIとチャットを追加する非常に強力なリアルタイムソリューションを迅速に作成することができます。

始める準備ができたら、私たちの管理ダッシュボードにアクセスして、無料のPubNubキーのセットをサインアップしてください。 私たちは、あらゆる業界をカバーする多くのチュートリアルや デモを用意しています。また、PubNubショーケースGithubでも利用可能)で、OpenAIを利用したチャットボットの例をご覧ください。

目次

ハイレベルなアーキテクチャPubNub関数を設定するPubNub関数を次のように作成します:OpenAI / ChatGPTPを呼び出すPubNub関数の例は次のとおりです:OpenAI APIChatCompletion APIプロパティチャットへのコンテキストの提供長時間実行するクエリまとめ

PubNubはどのようにお役に立てるでしょうか?

この記事はPubNub.comに掲載されたものです。

PubNubのプラットフォームは、開発者がWebアプリ、モバイルアプリ、IoTデバイス向けにリアルタイムのインタラクティブ機能を構築、提供、管理できるように支援します。

私たちのプラットフォームの基盤は、業界最大かつ最もスケーラブルなリアルタイムエッジメッセージングネットワークです。世界15か所以上で8億人の月間アクティブユーザーをサポートし、99.999%の信頼性を誇るため、停電や同時実行数の制限、トラフィックの急増による遅延の問題を心配する必要はありません。

PubNubを体験

ライブツアーをチェックして、5分以内にすべてのPubNub搭載アプリの背後にある本質的な概念を理解する

セットアップ

PubNubアカウントにサインアップすると、PubNubキーに無料ですぐにアクセスできます。

始める

PubNubのドキュメントは、ユースケースやSDKに関係なく、あなたを立ち上げ、実行することができます。

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .