Twilio Conversational Intelligence と Functions で通話録音を自動文字起こししてサマリと感情分析を取得してみた

Twilio Conversational Intelligence と Functions で通話録音を自動文字起こししてサマリと感情分析を取得してみた

Twilio Conversational Intelligence と Twilio Functions を組み合わせて、電話の録音を自動で文字起こしし、サマリや感情分析、カスタム分類の結果を取得するフローを構築してみました。 Intelligence Service の設定と 3 つの Function の実装手順、およびローカルの Node.js スクリプトから OperatorResults を取得する方法を通して、 Conversational Intelligence の基本的な使い方を確認します。
2025.12.10

はじめに

本記事では Twilio Conversational Intelligence (以下、 Conversational Intelligence) と Twilio Functions を組み合わせて、音声通話の録音を自動で文字起こしし、サマリや感情分析、カスタム分類の結果を取得する手順を紹介します。

Twilio とは

Twilio は、音声通話やメッセージング、メール、コンタクトセンタなどの機能を API として提供するクラウドコミュニケーションプラットフォームです。開発者は Twilio の API を組み込むことで、自社サービスに通話やチャット、認証などの機能を柔軟に追加できます。

対象読者

  • Twilio の Voice API や Twilio Studio を触ったことがあり、通話内容をテキストとして活用したい人
  • コンタクトセンタやキャンペーン窓口の通話を、サマリや感情分析などの形で構造化して分析したい人
  • Conversational Intelligence の全体像と、実際の API コールや Function の構成をまとめて把握したい人

参考

Conversational Intelligence の概要

Conversational Intelligence は、音声通話やメッセージなどの会話を文字起こしし、 AI ベースの言語解析によって構造化データとして扱えるようにするサービスです。文字起こしだけでなく、感情分析や要約、トピック抽出、エンティティ抽出などの処理をまとめて適用できる点が特徴です。解析結果は Transcript や Language Operator の形で API から取得でき、既存の業務システムやダッシュボードに連携できます。

対応チャネルとデータフロー

Conversational Intelligence は、次のチャネルから会話データを取り込みます。

  • Voice (電話)
    • Twilio Recordings (Twilio Programmable Voice の録音ファイル)
    • External Recordings (サードパーティで録音した音声ファイル)
    • Calls (進行中の通話のリアルタイム文字起こし)
    • ConversationRelay (AI エージェントとの通話ログ)
  • Messaging (SMS)
    • Twilio Conversations (SMS、 WhatsApp、 WebChat など)

開発者は、自分のアプリケーション側で Intelligence Service を作成し、対象の通話やメッセージに対して Service を紐付けます。これにより、 Twilio Voice や Conversations API から流れてきた音声やテキストが自動的に Transcript として保存され、後述する Language Operator による解析結果もまとめて取得できます。

Intelligence Service と Language Operator

Conversational Intelligence では、中心となる設定単位として Intelligence Service が用意されています。 Intelligence Service は次のような情報を持ちます。

  • 対象アカウント
  • 使用する言語 (LanguageCode)
  • 自動文字起こしの設定 (AutoTranscribe)
  • 個人情報の自動マスキング設定 (AutoRedaction、 MediaRedaction)
  • データロギングの設定
  • Webhook の URL と HTTP メソッド
  • 暗号化に使用する公開鍵 (EncryptionCredentialSid)
  • 紐付ける Language Operator の一覧

Language Operator は、 Transcript に対して実行される解析処理の単位です。代表的なものとして、次のような Pre-built Operator が提供されています。

  • Conversation Summary
  • Sentiment Analysis
  • Entity Extraction など

さらに、 Generative Custom Operators を利用すると、開発者が任意のプロンプトと JSON スキーマを定義し、 LLM ベースの柔軟な解析や分類を行えます。

Twilio Conversational Intelligence を実際に使ってみた

ここからは、実際に Intelligence Service を作成し、 Twilio Functions から録音と解析を行う構成を紹介します。今回の検証では、次のようなフローを実現します。

  • Twilio の電話番号に発信すると、 Twilio Function が日本語のガイダンスを再生し、発話内容を録音
  • 録音完了時のコールバックから Conversational Intelligence の Transcript API を呼び出し、 Intelligence Service に録音を送信
  • Transcript の生成と解析が完了した後、 Intelligence Service の Webhook から Twilio Function に通知
  • Function のログから TranscriptSid を取得し、ローカルの Node.js スクリプトから Transcript 本体と Language Operator の結果を取得する

Intelligence Service の作成

まずは Conversational Intelligence の基盤となる Intelligence Service を作成します。 Conversational Intelligence > Intelligence Service から Create a Service で Service を作成します。

Create CI Service

  • Unique name: test-ci など任意の識別子
  • Language: 英語

CI Service Config 1

  • Auto transcribe: 今回は録音完了時に手動で Transcript API を呼ぶためオフ
  • PII Redaction: 検証用途のためオフ

CI Service Config 2

Service を作成後、 Webhook タブで下記の設定を行います。

  • Webhook URL : 後述の /ci-webhook Function の URL
  • Webhook HTTP method : POST

webhook setting

Language Operators タブで、 Conversation Summary, Sentiment Analysis を追加します。

add services

add to service button

追加されると Added to Service と表示されます。

Added to Service

次に、 Create custom operator をクリックして、カスタムオペレータを作成します。

Create custom operator

Generarive タイプを選択し、プロンプトに次の内容を入力します。

この会話は顧客からの製品に関する問合せです。 会話の内容から、フィードバックの種類を category として pricing / other のいずれかで分類してください。 ユーザーの感情を emotion として positive / neutral / negative のいずれかで分類してください。

Output format を JSON とし、次の内容を入力します。

{
  "type": "object",
  "properties": {
    "category": {
      "type": "string"
    },
    "emotion": {
      "type": "string"
    }
  }
}

Training examples に次の内容を入力します。

Example conversation Expected output results
How much your product? {"category":"pricing","emotion":"neutral"}
We cannot find your products. {"category":"other","emotion":"negative"}

このように定義しておくと、 Transcript ごとに categoryemotion の 2 つのフィールドを持つ JSON が返ってきます。

operator summary

Service を作成したら、 Service SID (例として GAxxxxxxxx...) を控えておきます。後述の Twilio Function から serviceSid として使用します。

Twilio Function で通話を録音する

下記のように環境変数を設定します。

変数名
RECORDING_STATUS_URL 後述の /ci-create-transcript Function の公開 URL
INTELLIGENCE_SERVICE_SID Intelligence Service の SID

最初の Function /incoming-call では、着信に対してガイダンスを流し、そのまま発話を録音します。録音完了後は次の Function へ RecordingSid を引き渡すため、 recordingStatusCallback を指定しています。

/incoming-call
exports.handler = function (context, event, callback) {
  const twiml = new Twilio.twiml.VoiceResponse();

  // 録音完了後にもう一度呼ばれると RecordingSid が付与される
  if (event.RecordingSid) {
    // 2 回目の呼び出しでは、お礼だけ伝えて通話を切る
    twiml.say(
      {
        language: "ja-JP",
        voice: "woman",
      },
      "ありがとうございました。"
    );
    twiml.hangup();
  } else {
    // 1 回目の呼び出しでは、ガイダンスを流して録音を開始する
    twiml.say(
      {
        language: "ja-JP",
      },
      "ピーという音のあとに話してください。"
    );

    twiml.record({
      // 録音が完了したら /ci-create-transcript(後述)へコールバックする
      recordingStatusCallback: context.RECORDING_STATUS_URL,
      recordingStatusCallbackEvent: ["completed"],
      recordingChannels: "dual",
      timeout: 5,
      maxLength: 120,
      playBeep: true,
    });
  }

  return callback(null, twiml);
};
  • recordingChannels: "dual" を指定することで、話者ごとにチャネルを分けた録音を作成します。 Conversational Intelligence では dual channel の録音から話者識別付きで Transcript を生成できます
  • maxLengthtimeout は検証用途なので短めに設定しています。本番環境では利用シーンに応じて調整してください

Function を作成後、Twilio 購入番号の Voice Configuration からこの Function を A calls comes in として登録します。

voice config

録音完了から Transcript を作成する Function

次の Function /ci-create-transcript では、録音完了時のコールバックから Transcript API を呼び出します。

/ci-create-transcript
exports.handler = async function (context, event, callback) {
  const client = context.getTwilioClient();

  // Voice の recordingStatusCallback から渡される値
  const recordingSid = event.RecordingSid; // 例: REXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  const callSid = event.CallSid;

  console.log("RecordingSid:", recordingSid, "CallSid:", callSid);

  try {
    const transcript = await client.intelligence.v2.transcripts.create({
      serviceSid: context.INTELLIGENCE_SERVICE_SID,
      channel: {
        media_properties: {
          // Twilio Recording を Conversational Intelligence に渡す
          source_sid: recordingSid,
        },
      },
      // 後から検索しやすいように、任意のキーを付けておく
      customerKey: callSid,
    });

    console.log("Queued transcript:", transcript.sid, "status:", transcript.status);

    // Twilio Function としては空のレスポンスを返せば十分です
    return callback(null, {});
  } catch (err) {
    console.error("Failed to create transcript", err);
    return callback(err);
  }
};
  • channel.media_properties.source_sid に RecordingSid を指定することで、 Twilio の録音ファイルを Conversational Intelligence 側に渡しています
  • customerKey に CallSid を入れておくことで、あとから Transcript を検索したり、外部システムと紐付けたりしやすくしています

この呼び出しは非同期で、戻り値の status が queued の段階ではまだ解析は完了していません。 Transcript の生成と Language Operator の実行が完了すると、 Intelligence Service に設定した Webhook にイベントが送信されます。

Webhook から TranscriptSid を受け取る Function

最後の Function /ci-webhook は、 Intelligence Service の Webhook URL として登録しておき、 Transcript の解析が完了したタイミングで呼び出されます。

/ci-webhook
exports.handler = async function (context, event, callback) {
  const eventType = event.event_type;

  // Transcript が完了すると、voice_intelligence_transcript_available が飛んでくる
  if (eventType === "voice_intelligence_transcript_available") {
    console.log("[CI] Transcript available.");
    console.log("  TranscriptSid:", event.transcript_sid);
    console.log("  CustomerKey  :", event.customer_key);
    return callback(null, { ok: true });
  }

  console.log("[CI] Unhandled event_type:", eventType);
  return callback(null, { ok: true });
};
  • event_typevoice_intelligence_transcript_available の場合のみ処理するようにガードを入れています
  • この Function では TranscriptSid と CustomerKey をログに出力するだけにとどめ、実際の Transcript / OperatorResults の取得はローカルの Node.js から行う構成としています

動作確認の流れ

  1. Functions の Live logs を ON にしておきます
    画像17

  2. 実際に電話をかけてガイダンスに従って話し、通話を終了します
    発話内容例: We cannot log in to your account system.

  3. Twilio コンソールの Conversational Intelligence 画面で Transcript が生成されていることを確認します
    Transcript

  4. Functions の Live logs で TranscriptSid が出力されていることを確認します

    Dec 10, 2025, 07:15:44 PM
    Fetching content for /ci-webhook
    Dec 10, 2025, 07:16:03 PM
    Execution started...
    Dec 10, 2025, 07:16:09 PM
    [CI] Transcript available.
    Dec 10, 2025, 07:16:09 PM
    TranscriptSid: GT**** ← これを使用します
    Dec 10, 2025, 07:16:09 PM
    CustomerKey : CA****
    Dec 10, 2025, 07:16:09 PM
    Execution ended in 4.8ms using 107MB
    
  5. ローカル環境から下記のスクリプトを実行し、 Transcript と OperatorResults のメタデータが取得できることを確認します。

ci-dump-operator-results.js
require("dotenv").config();

const twilio = require("twilio");

const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const transcriptSid = process.env.TRANSCRIPT_SID;  // 先ほどの TranscriptSid をここに設定

if (!accountSid || !authToken) {
  console.error("TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN が設定されていません。");
  process.exit(1);
}

const client = twilio(accountSid, authToken);

async function main() {
  console.log("Target TranscriptSid:", transcriptSid);

  // まず Transcript 自体の概要を確認
  try {
    const transcript = await client.intelligence.v2
      .transcripts(transcriptSid)
      .fetch();

    console.log("=== Transcript info ===");
    console.log("sid:", transcript.sid);
    console.log("status:", transcript.status);
    console.log("customerKey:", transcript.customerKey);
    console.log("serviceSid:", transcript.serviceSid);
    console.log("url:", transcript.url);
  } catch (err) {
    console.error("Failed to fetch transcript:", err);
    process.exit(1);
  }

  // 次に OperatorResults 一式を取得して 1 行ずつ出力
  try {
    const operatorResults = await client.intelligence.v2
      .transcripts(transcriptSid)
      .operatorResults.list({ limit: 20 });

    console.log("OperatorResults count:", operatorResults.length);

    operatorResults.forEach((r, i) => {
      console.log(`\n--- OperatorResult[${i}] ---`);
      console.log("operatorType:", r.operatorType);
      console.log("name:", r.name);
      console.log("operatorSid:", r.operatorSid);

      console.log("extractMatch:", r.extractMatch);
      console.log("matchProbability:", r.matchProbability);
      console.log("normalizedResult:", r.normalizedResult);
      console.log("utteranceMatch:", r.utteranceMatch);

      console.log("predictedLabel:", r.predictedLabel);
      console.log("predictedProbability:", r.predictedProbability);

      console.log(
        "labelProbabilities:",
        r.labelProbabilities
          ? JSON.stringify(r.labelProbabilities, null, 2)
          : r.labelProbabilities
      );

      console.log(
        "extractResults:",
        r.extractResults
          ? JSON.stringify(r.extractResults, null, 2)
          : r.extractResults
      );

      console.log(
        "utteranceResults:",
        r.utteranceResults
          ? JSON.stringify(r.utteranceResults, null, 2)
          : r.utteranceResults
      );

      console.log(
        "textGenerationResults:",
        r.textGenerationResults
          ? JSON.stringify(r.textGenerationResults, null, 2)
          : r.textGenerationResults
      );

      console.log(
        "jsonResults:",
        r.jsonResults ? JSON.stringify(r.jsonResults, null, 2) : r.jsonResults
      );

      console.log("transcriptSid:", r.transcriptSid);
      console.log("url:", r.url);
    });
  } catch (err) {
    console.error("Failed to fetch operator results:", err);
    process.exit(1);
  }
}

main();
実行結果の例
Target TranscriptSid: GT****
=== Transcript info ===
sid: GT****
status: completed
customerKey: CA****
serviceSid: GA****
url: https://intelligence.twilio.com/v2/Transcripts/GT****
OperatorResults count: 3

--- OperatorResult[0] ---
operatorType: json
name: test-operator
operatorSid: LY****
extractMatch: null
matchProbability: null
normalizedResult: null
utteranceMatch: null
predictedLabel: null
predictedProbability: null
labelProbabilities: {}
extractResults: {}
utteranceResults: []
textGenerationResults: null
jsonResults: {
  "category": "other",
  "emotion": "negative"
}
transcriptSid: GT****
url: https://intelligence.twilio.com/v2/Transcripts/GT****/OperatorResults/LY****

--- OperatorResult[1] ---
operatorType: text-generation
name: Conversation Summary
operatorSid: LY****
extractMatch: null
matchProbability: null
normalizedResult: null
utteranceMatch: null
predictedLabel: null
predictedProbability: null
labelProbabilities: {}
extractResults: {}
utteranceResults: []
textGenerationResults: {
  "format": "text",
  "result": "The customer is experiencing issues logging into their account. The call center agent informs the customer that they are unable to access the account system. The conversation revolves around troubleshooting the login problem."
}
jsonResults: null
transcriptSid: GT****
url: https://intelligence.twilio.com/v2/Transcripts/GT****/OperatorResults/LY****

--- OperatorResult[2] ---
operatorType: conversation-classify
name: Sentiment Analysis
operatorSid: LY****
extractMatch: null
matchProbability: null
normalizedResult: null
utteranceMatch: null
predictedLabel: neutral
predictedProbability: 1.0
labelProbabilities: {
  "neutral": 1
}
extractResults: {}
utteranceResults: []
textGenerationResults: null
jsonResults: null
transcriptSid: GT****
url: https://intelligence.twilio.com/v2/Transcripts/GT****/OperatorResults/LY****

まとめ

本記事では、 Twilio Conversational Intelligence と Twilio Functions を組み合わせて、音声通話の録音を自動で解析するシンプルな構成を紹介しました。 Intelligence Service に Language Operator を紐付けておくことで、録音ファイルを渡すだけでサマリや感情分析、カスタム分類といったインサイトを一括で得られます。

今回は Transcript 単位の OperatorResults をローカルの Node.js から取得する例を中心に示しましたが、今後は Transcript のセンテンス単位のデータを利用した詳細分析や、他の Twilio プロダクト (ConversationRelay、 Studio、 SendGrid など) と組み合わせたワークフロー自動化なども試していくことで、 Conversational Intelligence の価値をさらに引き出せるでしょう。

この記事をシェアする

FacebookHatena blogX

関連記事