OpenAIのChatGPTとTwilio Programmable Voice/Functionsの統合

March 23, 2023
執筆者
レビュー担当者

映画『ターミネーター2』のT-800は、自分のCPUはニューラルネットプロセッサーで、自分は学習コンピューターであり、人間と接触することで人間から学ぶのだと説明するが、ジョン・コナーは信じられないという様子で顔に触れる。

ChatGPTを使用して対話型音声チャットボットを強化することは、単に目新しいだけではありません。有益なビジネスインテリジェンス情報を獲得できます。その一方、人間だけがサポートできる会話には、コストのかかる専用の、シングルスレッドの人間によるオペレーターを用意しましょう。最近では、人々は常にロボットに話しかけたり、ロボットの話を聞いたりコラボレーションしたりしています。しかし、1台のロボットとのやりとりよりもクールなことがあります。3台とのやりとりです。

この投稿では、Twilioのネイティブな音声認識機能とAmazon Polly Neuralのテキスト読み上げ機能をChatGPTと組み合わせることにより、音声起動チャットボットを作成する方法を紹介します。これらはすべて、Twilioのサーバーレス関数環境でホストされます。また、Call Event APIを使用して発信者が何を質問しているかを解析し、ボットからのレスポンスを確認することにより、これらのやりとりから取得された豊富なファーストパーティデータを活用し、Segmentなどの顧客エンゲージメントプラットフォームにデータを送信できます。プラットフォームでは、顧客プロフィールを構築し、顧客の好みを把握し、顧客が望むパーソナライズされたエクスペリエンスを作成することが可能です。

開始前にデモをお試しになりたい場合は、1-989-4OPENAI (467-3624)までお電話ください。

ロボット#1: 音声認識を使用した人間の声のデコード

TwilioのTwiML動詞<Gather>を使用した音声認識は、電話で話された言葉をテキストに変換する強力なツールであり、優れた精度、低レイテンシー、多数の言語と方言のサポートを提供します。Twilioの開発者は、インタラクティブ音声応答(IVR)やその他のセルフサービス自動化ワークフローをナビゲートする方法として音声認識を使用してきましたが、新しい実験的な音声モデルのリリースにより、唯一の制限は✨ご自身の想像力✨のみとなりました。

ロボット#2: Amazon Pollyのニューラル音声でロボットに声を与える

TwilioはTwiML動詞<Say>を使用し、テキスト読み上げ(TTS)機能を提供します。この機能では、ディープラーニングを活用し人間のような音声を合成する、Amazon Pollyボイスを使用しています。Pollyのニューラル音声は、より自然で生き生きとしたサウンドを実現し、ユーザーに魅力的な環境を提供します。Twilioは複数の言語、幅広い音声、SSMLのサポートを備えており、チャットボットの音声をブランドのアイデンティティに合わせてカスタマイズできます。

ロボット#3: OpenAIのChatGPT会話コンパニオン

ChatGPTOpenAIが開発した高度な言語モデルであり、与えられた入力に基づいて人間らしいテキストを生成できます。状況を理解し、適切な応答を提供し、物語や詩を書くといった創造的なタスクに取り組むことも可能です。OpenAI APIを活用することにより、このAIをアプリケーションに直接統合し、よりインタラクティブで魅力的なサービスをユーザーに提供できます。

秘伝のソース: Twilio Functions

この3台のロボットどうし、または発信者との会話はどのようにして実現されるのでしょう?Twilio Functionsを使用します。関数を使用することにより、ご自身のサーバーを立ち上げることなく概念実証を実施できるだけでなく、Twilio内部でお客様のコードを実行し、自動スケーリング機能、セキュリティの強化、レイテンシーの削減を実現できます。もちろん、ご自身のサーバーがどこかで起動中であれば、Javascriptを少し編集するだけで、簡単にnode.js環境で動作させることができます。

材料を準備できたところで、レシピを確認しましょう。CLIGUIの2種類のフレーバーを使用します。

ロボットが踊り、ハービー・ハンコックが歌を歌う『Rockit』のミュージックビデオ。

成功するための前提条件

統合プロセスへ進む前に、次のものを準備する必要があります。

関数

まず、バックエンドを準備しましょう。新しいサーバーレスプロジェクトを作成します。素晴らしいオープンソースのサーバーレスツールキットをインストールしたため、ターミナルにコマンドを渡すことにより、コード1行で作成できます。

twilio serverless:init <project-name>

<project-name>を好きな名前に置き換えてください。私はthree-robot-rhumbaを使用しています。

Twilioサーバーレスプロジェクトの初期設定を示すターミナルウィンドウ

cdによりプロジェクトのディレクトリへ移動し、.envファイルを更新します。Twilio認証トークンをAUTH_TOKENとして、OpenAI APIキーをOPENAI_API_KEYとして提供してください。TwilioアカウントSIDが自動的に入力されます。.envファイルが次のように表示されていることを確認します(XXXXXプレースホルダーが各キーに置き換えられます)。

ACCOUNT_SID=XXXXX
AUTH_TOKEN=XXXXX
OPENAI_API_KEY=XXXXX

Twilioサーバーレス関数は単なるNode.jsアプリであるため、package.jsonに書き込みを行うパッケージマネージャーを使用して依存関係を追加できます。私は基本のnpmを使用しています。ターミナルに戻り、次のように入力してOpenAI NPMパッケージをインストールします。

npm install openai@3.3.0

環境変数を設定し、依存関係を追加しました。次に、2つの関数を作成します。TwilioのTwiML動詞<Gather>を使用し、Twilioの音声認識により話し言葉をChatGPTが理解できるテキストに変換する/transcribe関数と、音声認識により生成されたテキストを受け取り、OpenAI APIに送信し、TwiML動詞<Say>を使用してTwilioのAmazon Polly Neural搭載のテキスト読み上げエンジンにレスポンスを渡す/respond関数です。

新しい関数を作成するにはプロジェクトディレクトリのfunctionsフォルダを開き、JavaScriptファイルを作成します。フォルダ内にtranscribe.jsファイルとrespond.jsファイルを作成し、/transcribe関数と/respond関数を作成します。

テキスト化

transcribe.jsを開き、次のコードを追加します。

exports.handler = function(context, event, callback) {
    // Create a TwiML Voice Response object to build the response
    const twiml = new Twilio.twiml.VoiceResponse();

    // If no previous conversation is present, or if the conversation is empty, start the conversation
    if (!event.request.cookies.convo) {
        // Greet the user with a message using AWS Polly Neural voice
        twiml.say({
                voice: 'Polly.Joanna-Neural',
            },
            "Hey! I'm Joanna, a chatbot created using Twilio and ChatGPT. What would you like to talk about today?"
        );
    }

    // Listen to the user's speech and pass the input to the /respond Function
    twiml.gather({
        speechTimeout: 'auto', // Automatically determine the end of user speech
        speechModel: 'experimental_conversations', // Use the conversation-based speech recognition model
        input: 'speech', // Specify speech as the input type
        action: '/respond', // Send the collected input to /respond 
    });

    // Create a Twilio Response object
    const response = new Twilio.Response();

    // Set the response content type to XML (TwiML)
    response.appendHeader('Content-Type', 'application/xml');

    // Set the response body to the generated TwiML
    response.setBody(twiml.toString());

    // If no conversation cookie is present, set an empty conversation cookie
    if (!event.request.cookies.convo) {
        response.setCookie('convo', '', ['Path=/']); 
    }

    // Return the response to Twilio
    return callback(null, response);
};

関数の操作に慣れていない方のために、何が起きているかを説明します。/transcribe関数は、TwilioのNode.jsヘルパーライブラリーに基づいて音声応答を生成するTwiMLを作成します。そして会話が存在しない場合は会話を開始し、ユーザー入力をリスンし、その入力と会話履歴を/respondエンドポイントに渡して処理を進めます。

6行目で、アプリケーションはconvoというCookieが存在するかどうかを確認します。存在しない場合、または空である場合は会話がまだ開始していないと解釈できるため、TwiML動詞<Say>を使用して最初の挨拶を開始します。

次に、twiml.gatherメソッドを使用してユーザー入力をキャプチャします。gatherのパラメータは次のとおりです。

  • speechTimeout: 'auto': ユーザーが話すのを止めたタイミングを自動的に判断します。正の整数を設定できますが、このユースケースの場合はautoが最適です
  • speechModel: "experimental_conversations": 会話のユースケースに最適化された音声認識モデルを使用します
  • input: 'speech': 入力タイプをspeechに設定し、すべてのキー入力を無視します(DTMF)
  • action: '/respond': ユーザーの音声入力と会話履歴を/respondエンドポイントに渡します

次に、convo Cookieの生成方法を作成し、OpenAI APIとPollyのニューラル音声の間で受け渡される会話履歴を保存する場所を/respond関数に設定します。そのため、アプリはTwilio.Response();オブジェクトを初期化する必要があり、25行目でそれを実行しています。

Twilio.twiml.VoiceResponse();Twilio.Response();の両方をハンドラーに渡すことはできないため、先ほど作成したレスポンスを使用してリクエストにヘッダーを追加し、<Gather>を使用して生成したTwiMLをそれぞれ本文の28行目と31行目に設定します。

完了後、35行目でresponse.setCookie();を使用してCookieを設定してから、レスポンスをハンドラーに渡し、39行目でサーバーレスインフラストラクチャが実行できるようにします。このファイルを保存して閉じます。

通話とレスポンス

次にrespond.jsを開き、次のコードを追加します。

// Import the OpenAI module
const { OpenAI } = require("openai");

// Define the main function for handling requests
exports.handler = async function(context, event, callback) {
  // Set up the OpenAI API with the API key from your environment variables
   const openai = new OpenAI({
    apiKey: context.OPENAI_API_KEY, 
  });


  // Set up the Twilio VoiceResponse object to generate the TwiML
  const twiml = new Twilio.twiml.VoiceResponse();

  // Initiate the Twilio Response object to handle updating the cookie with the chat history
  const response = new Twilio.Response();

  // Parse the cookie value if it exists
  const cookieValue = event.request.cookies.convo;
  const cookieData = cookieValue ? JSON.parse(decodeURIComponent(cookieValue)) : null;

  // Get the user's voice input from the event
  let voiceInput = event.SpeechResult;

  // Create a conversation object to store the dialog and the user's input to the conversation history
  const conversation = cookieData?.conversation || [];
  conversation.push({role: 'user', content: voiceInput});

  // Get the AI's response based on the conversation history
  const aiResponse = await createChatCompletion(conversation);


  // Add the AI's response to the conversation history
  conversation.push(aiResponse);

  // Limit the conversation history to the last 20 messages; you can increase this if you want but keeping things short for this demonstration improves performance
  while (conversation.length > 20) {
      conversation.shift();
  }

  // Generate some <Say> TwiML using the cleaned up AI response
  twiml.say({
          voice: "Polly.Joanna-Neural",
      },
      aiResponse
  );

  // Redirect to the Function where the <Gather> is capturing the caller's speech
  twiml.redirect({
          method: "POST",
      },
      `/transcribe`
  );

  // Since we're using the response object to handle cookies we can't just pass the TwiML straight back to the callback, we need to set the appropriate header and return the TwiML in the body of the response
  response.appendHeader("Content-Type", "application/xml");
  response.setBody(twiml.toString());

  // Update the conversation history cookie with the response from the OpenAI API
  const newCookieValue = encodeURIComponent(JSON.stringify({
      conversation
  }));
  response.setCookie('convo', newCookieValue, ['Path=/']);

  // Return the response to the handler
  return callback(null, response);

  // Function to generate the AI response based on the conversation history
  async function generateAIResponse(conversation) {
      const messages = formatConversation(conversation);
      return await createChatCompletion(messages);
  }

  // Function to create a chat completion using the OpenAI API
  async function createChatCompletion(messages) {
      try {
        // Define system messages to model the AI
        const systemMessages = [{
                role: "system",
                content: 'You are a creative, funny, friendly and amusing AI assistant named Joanna. Please provide engaging but concise responses.'
            },
            {
                role: "user",
                content: 'We are having a casual conversation over the telephone so please provide engaging but concise responses.'
            },
        ];
        messages = systemMessages.concat(messages);

        const chatCompletion = await openai.chat.completions.create({
            messages: messages,
            model: 'gpt-4',
            temperature: 0.8, // Controls the randomness of the generated responses. Higher values (e.g., 1.0) make the output more random and creative, while lower values (e.g., 0.2) make it more focused and deterministic. You can adjust the temperature based on your desired level of creativity and exploration.
              max_tokens: 100, // You can adjust this number to control the length of the generated responses. Keep in mind that setting max_tokens too low might result in responses that are cut off and don't make sense.
              top_p: 0.9, // Set the top_p value to around 0.9 to keep the generated responses focused on the most probable tokens without completely eliminating creativity. Adjust the value based on the desired level of exploration.
              n: 1, // Specifies the number of completions you want the model to generate. Generating multiple completions will increase the time it takes to receive the responses.
        });

          return chatCompletion.choices[0].message.content;

      } catch (error) {
          console.error("Error during OpenAI API request:", error);
          throw error;
      }
  }
}

上記と同様、このコードで何が起きているのかについてのガイドツアーを用意しました。まず、必要なモジュールをインポートし(2行目)、リクエストを処理するメイン関数を定義します(5行目)。

7~8行目ではOpenAI APIをAPIキーで設定します。11行目ではTwilio Voice Responseオブジェクトを作成し、ChatGPTレスポンスを発信者の通話に変換するためのTwiMLを生成します。14行目でTwilioレスポンスオブジェクトを開始し、会話履歴のCookieを更新して、TwiMLがレスポンスに渡されるようにヘッダーとボディを設定します。

Cookie値が存在する場合は17~20行目で解析し、23行目では/transcribe関数から取得したSpeechResultイベントからユーザーの音声入力を取得します。26~27行目では、対話を保存するための会話変数を作成し、会話履歴にユーザーの入力を追加します。

30行目では会話履歴に基づいてAIのレスポンスを生成し、33行目では不要なロール名(assistant、Joanna、user)を削除することでAIのレスポンスをクリーニングします。36行目ではクリーニングしたAIレスポンスを会話履歴に追加します。

39~41行目では、会話履歴を最後の10メッセージに制限してパフォーマンスを改善し、Cookieを適度なサイズに保ちながら、有用なレスポンスを提供するのに十分なコンテキストをチャットボットに提供しています。必要に応じてこれを増やす(または減らす)ことができますが、保存された履歴はリクエストごとにOpenAI APIに渡されます。この値が大きくなるほど、アプリケーションはより多くのトークンを消費することになります。44~48行目ではクリーニングされたAIレスポンスを使用して<Say> TwiMLが生成され、51~55行目では<Gather>が発信者の通話をキャプチャしている/transcribe関数に通話をリダイレクトします。

/transcribeと同様、レスポンスを使用してTwiMLを提供する必要があります。58~59行目では適切なヘッダーを設定し、レスポンスの本文でTwiMLを返します。62~67行目ではOpenAI APIからのレスポンスで会話履歴のCookieを更新し、70行目ではハンドラーにレスポンスを返します。

このgenerateAIResponse関数(73~76行目)は、OpenAI APIを使用して会話をフォーマットし、チャットの完了を作成します。createChatCompletion関数(81~88行目)はOpenAI APIにリクエストを送信し、GPT-3.5-turboモデルと指定されたパラメータを使用してレスポンスを生成します。OpenAI APIから500を受け取った場合、会話をそのまま失いたくないため、APIからのエラーを<Say>で処理し、90~107行目で会話を/transcribe関数にリダイレクトします。

OpenAIへのリクエストが単に古くなりタイムアウトする場合もあるため、タイムアウトをうまく処理し、/transcribeにリダイレクトして再試行するためのキャッチが109~131行目に追加されます。

最後に、formatConversation関数(136~158行目)がassistantuserのロールを交互に切り替えることにより、OpenAI APIが理解できる形式に会話履歴をフォーマットします。

これで、コードの更新、依存関係の設定、環境変数の設定が終了し、デプロイの準備が整いました。Twilioサーバーレスを使用すると、わずか1つのコマンドで済むため、これまでにないほど簡単になります。

twilio serverless:deploy

デプロイ後、作成した関数を使用して発信者からの音声入力をキャプチャし、それをテキストに変換してChatGPT APIに送信し、AIが生成した音声の形式で発信者に対してレスポンスを再生できます。3台のロボットが貴社だけのために連携

電子音楽バンドKraftwerkがライブで演奏

モデル

上記の例では、OpenAIのgpt-3.5-turboモデルを使用しています。これは開発目的や概念実証には優れた(そして安価な)モデルですが、特定のユースケースには他のモデルの方が適していると感じるかもしれません。GPT-4は限定ベータ版としてリリースされたばかりであり、Twilioでもまだ調査していませんが、発表と同時に公開された開発者向けライブストリームを見る限りでは、過去数か月にわたり人々を驚かせてきたバージョン3.5よりも大幅にアップグレードされているようです。

GPT-3は3.5(そして当然ながら4)に置き換わりましたが、GPT-3のモデルには微調整機能があり、執筆時点で、この機能は新しいモデルには装備されていません。たとえば、トレーニングデータが古い場合でも、Curieを使用することでより迅速なレスポンスを得ることができ、センチメント分析レスポンススタイルなどを利用できます。ご自身に適したモデルをお選びください。

電話番号

関数をデプロイし、準備ができました。次に通話を発信してテストできますが、最初に、作成した関数を使用するための電話番号を設定する必要があります。CLIを使用すると、すばやく簡単に実行できます。お使いのターミナルに次のように入力し、アカウントの電話番号を一覧表示します(前提条件に従い、事前に電話番号を取得していると想定しています)。

twilio phone-numbers:list

アカウントの電話番号SID、電話番号、フレンドリー名のリストが返されます。リクエストには、SIDまたは完全なE.164形式の電話番号のいずれかを使用できます。

twilio phone-numbers:update <PN SID> –voice-url=<The URL for the /transcribe Function>
映画『スター・ウォーズ』のオビ=ワン・ケノービは、コマンドラインインターフェイスを使用したことがないと言い、R2-D2は悲しいビープ音で応答します。

CLIに問題がある場合、上記で説明したすべてをTwilio Consoleで直接行うことができます。まず、左側のナビゲーションの[Develop](開発)タブで、[Functions and Assets](FunctionsとAssets)セクションを選択し、[Services](サービス)をクリックします。[Create Service](サービスを作成)をクリックします。

Twilio Voice ChatGPTデモ - サービスを作成する

サービスに名前を付けます。ここではvoice-chatgpt-demoという名前にします。[Next](次へ)をクリックします。

Twilio Voice ChatGPTデモ - サービスに名前を付ける

Consoleの[Services](サービス)画面の左側のナビゲーションには[Functions](関数)、[Assets](アセット)、[Environment Variables](環境変数)、[Dependencies](依存関係)が表示されます。また、コードを編集してログを監視するためのテキストエディタとコンソールも表示されます。まず、環境変数を設定します。右下隅にある[Environment Variables](環境変数)をクリックします。

Twilio Voice ChatGPTデモ - サービスを設定する

TwilioアカウントのSIDと認証トークンは環境変数として事前に入力されているため、必要なのはOpenAI APIキーの追加だけです。サンプルコードでは、OPENAI_API_KEYとして参照されるため、編集せずにコピー&ペーストを行いたい場合は、同じ名前を付けるようにしてください。完了後、[Add](追加)をクリックします。

Twilio Voice ChatGPTデモ - 環境変数を追加する

次に、依存関係を更新してOpenAI npmモジュールを含め、OpenAI APIにリクエストできるようにする必要があります。[Dependencies](依存関係)をクリックし、[Module](モジュール)テキストボックスにopenai、[Version](バージョン)テキストボックスに3.3.0と入力します。必ず[Add](追加)をクリックしてください。

これで、関数の作成を開始できます。次の2つの関数を作成します。音声認識の視点から面倒な作業をすべて行う/transcribeと、文字起こししたテキストをChatGPT APIに渡し、Amazon Polly Neuralのテキスト読み上げ音声を使用して発信者に対してレスポンスを読み上げる/respondです。

Add](追加)ボタンをクリックし、ドロップダウンから[Add Function](関数の追加)を選択して新しい関数を作成し、/transcribeという名前を付けます。

Twilio Voice ChatGPTデモ - 関数を追加する

新しい関数の内容をこちらのコードスニペットと置き換えて、[Save](保存)をクリックします。今作成した関数は、完了すると次のようになります。

Twilio Voice ChatGPTデモ - テキスト化を行う関数

次に別の関数を作成し、これを/respondと呼びます。この新しい関数の内容をこちらのコードスニペットと置き換えて、再度[Save](保存)をクリックします。これらの例の動作を正確に知りたい場合は、この投稿のCLIセクションをご覧ください。コードを詳しく説明しています。

次に、[Deploy](デプロイ)ボタンをクリックすると、保存した関数がデプロイされ、着信電話番号設定で使用できるようになります。/transcribeの横にある縦三点リーダーをクリックし、URLをコピーします。これはすぐに必要になります。

Twilio Voice ChatGPTデモ - 電話番号のセットアップ

Develop](開発)タブで[Phone Numbers](電話番号)セクションに移動し、[Manage](管理)、[Active Numbers](アクティブな番号)の順に選択します。使用したい番号を見つけたら下へスクロールして[Voice & Fax](音声とファックス)セクションに移動し、[A Call Comes In](次で通話を受信)で[Function](関数)を選択します。[Service](サービス)では、先ほど作成した関数サービス、voice-chatgpt-demoを選択します。次に、[Environment](環境)にuiを、[Function Path](関数パス)に/transcribeを選択します(通話が最初にここにルーティングされるため)。

新たに設定した電話番号に電話をかけ、テストしてみましょう。

統合のメリット

映画『ショート・サーキット』のジョニー5が「No Disassemble」(解体は嫌だ)と言い、取り乱している

この統合の特に素晴らしい点は、入力とレスポンスの両方を開発者が利用できることです。つまり、SpeechResultパラメータの形式で/respond関数に渡す音声認識テキストと、通話時に実行される<Say> TwiML形式のChatGPT生成レスポンスです。つまり、これらの会話はビジネスインテリジェンスのクローズドボックスではないということです。これらの関数がTwilioサーバーレス環境で実行されていても、Call Events APIを使用して会話の内容を手に入れることができます。Twilio CLIを使用して詳細を入手する方法を以下に示します。

twilio api:core:calls:events:list --call-sid <The call SID you are interested in> -o json

このAPIを使用すると、リクエスト、レスポンス、関連パラメータを取得し、それらを内部システムに直接送信することにより、電話がつながる前に発信者の質問内容をオペレーターに通知したり、データを使用してSegmentなどの顧客データプラットフォームで顧客プロフィールを装飾したりすることが可能です。

ロボットのロック

ベースギターとドラムを演奏するダフト・パンク(『Get Lucky』のミュージックビデオより)

McKinseyForbesなどの発行物では、ChatGPTなどの生成AIテクノロジーを利用してビジネスの問題を解決するにはどうすればよいかがすでに論じられています。これで3台のロボットを利用できるようになりましたが、このような統合により、実際に何ができるのでしょうか?ITデスクトップサポートの最前線にいるオペレーターについて考えてみましょう。ChatGPTにGoogleを検索させることにより、コストの高いIT部門が検索を行う必要がなくなり、発信者とChatGPTによる解決が不可能な場合には、オペレーターに電話をつなぎます。ヘルスケア専門家を長時間待たせていませんか?重篤でない一般的な病気については、発信者にスムーズジャズを聞かせる代わりに、ChatGPTの知恵を提供しましょう。

まとめ

Twilioの音声認識、Twilio Functions、Amazon Pollyのニューラル音声、OpenAIのAPIの助けを借りて、独自のインタラクティブな音声チャットボットを作成しました。Twilioを使用して活用できる会話型AIとチャットボットの機能の進歩を注視しましょう。

Michael Carpenter(またの名をMC): テレコムAPIの専門家。2001年からソフトウェアによる通話技術に取り組む。TwilioのProgrammable Voiceのプロダクトマネージャーであり、現在はAPI、SIP、WebRTC、モバイルSDKの交わりに強く関心を寄せている。Depeche Modeの大ファン。お問い合わせは、mc (at) twilio.comまたはLinkedInまで。

Dhruv Patelの実に先進的な投稿、GPT-3とTwilio VoiceやTwilio Functionsを使用してAIの友人に電話をかける方法に多大な謝意を表します。Dhruvはこの投稿で、コードの技術的なレビューも提供しました。Dhruv PatelはTwilioのDeveloper Voicesチーム所属の開発者です。お問い合わせは、コーヒーショップで水出しコーヒーを飲みながら仕事しているところを見つけるか、dhrpatel [at] twilio.comまたはLinkedInまで。