2024年07月16日

Gemini AI 要約機能を使ったビデオ通話アプリの構築

Gemini AI 要約機能を使ったビデオ通話アプリの構築

※この投稿は、Agoraの日本代理店であるブイキューブが、Agoraブログを翻訳した記事です。

 

AIが世界を支配することに対し、抵抗は無駄でしょう。今、私たちは避けられないものと戦うか、それに屈するかのどちらかです。
この記事では、AIとAgoraを組み合わせ、AIを使って即座に要約するビデオ通話アプリを作る方法を説明します。

build-a-video-call-app-with-gemini-ai-summuraization02

前提条件

1. Flutter
2. Agoraの開発者アカウント
3. Agora Speech-to-Textサーバー (このサンプルサーバーを使用できます)
4. Gemini APIキー

プロジェクトのセットアップ

まず、Agoraを使って作られたシンプルなビデオ通話アプリから始めます。この記事では、あなたがAgoraを使用したシンプルなビデオ通話がどのように動作するかの基本的な理解を持っていることを前提としています。

もしAgoraの基礎を理解していない場合は、ドキュメント内のFlutterクイックスタートガイドを参照するか、Agora Flutterを使ったビデオ通話のコースでより深く学ぶことができます。

この記事はシンプルなスタータービデオ通話をベースにしています。

スターターコードにはランディング画面があり、通話への参加を促すボタンが1つだけあります。この通話はtestと呼ばれる1つのチャンネルで行われます(デモです、大丈夫です)。通話画面には、リモートユーザーのビデオ、自分のローカルビデオ、通話終了ボタンがある。イベントハンドラを使用して、ユーザーをビューに追加したり削除したりします。


Speech to Text

Agoraには、特定のチャネルの通話の文字起こしを開始するために有効にできるリアルタイム文字起こしという製品があります。

Real-Time Transcription は、AI マイクロサービスを使用して通話に接続し、話し言葉の音声を書き起こす RESTful API です。この書き起こしは、onStreamMessage イベントを使用してビデオ通話に直接ストリーミングされます。オプションで、クラウドプロバイダーに書き出すこともできます。

バックエンド

Gemini AI Summarizationでビデオ通話アプリを構築する

build-a-video-call-app-with-gemini-ai-summuraization03

リアルタイム翻訳は、いくつかの理由からビジネスサーバーに実装する必要がある。バックエンドがマイクロサービスを制御することで、リアルタイム翻訳のインスタンスが各チャネル内で1つしか実行されないようにすることができます。また、transcription サービスにトークンを渡す必要があるので、バックエンドでそれを行うことで、クライアント側でそのトークンを公開する必要がなくなります。

このサーバーをバックエンドとして使用します。このサーバーは2つのエンドポイントを公開しています。1つは書き起こしの開始用、もう1つは終了用です。

リアルタイム翻訳をスタート

/start-transcribing/<--Channel Name→

成功した応答には、タスクIDとビルダートークンが含まれます。このトークンは、翻訳を停止するために使用する必要があるため、アプリに保存しておく必要があります。

{taskId: <--Task ID Value-->, builderToken: <--Builder Token Value-->}

リアルタイム翻訳をストップ

/stop-transcribing/<--Channel Name-->/<--Task ID-->/<--Builder Token-->

コール内で翻訳を開始する

Flutterアプリケーションからネットワークコールをするには、httpパッケージを使うことができます。アプリとバックエンドサーバーの両方で同じApp IDを使っていることを確認してください。次に、APIを呼び出して翻訳を開始します。

call.dartファイル内に、このstartTranscription関数を追加します:

Future startTranscription({required String channelName}) async {
 final response = await post(
   Uri.parse('$serverUrl/start-transcribing/$channelName'),
 );

 if (response.statusCode == 200) {
   print('Transcription Started');
   taskId = jsonDecode(response.body)['taskId'];
   builderToken = jsonDecode(response.body)['builderToken'];
 } else {
   print('Couldn\'t start the transcription : ${response.statusCode}');
 }
}

最初のユーザがチャネルに参加するとすぐにこの関数が開始されるように、 join コールメソッドの直後にこの関数をコールします。成功したレスポンスの一部として、タスク ID と Builder Token を受け取ります。これらは保存しておきましょう。翻訳を停止するときに使う必要があるからです。

トランスクリプションが正常に開始されると、「ボット」が通話に参加したものとして動作します。これは実際のユーザーではありませんが、バックエンドサーバー内で定義された独自のUIDを持っています。上でリンクしたサーバーを使用している場合、UIDは101です。onUserJoinedイベントで、このUIDをリモートユーザーのリストから除外することができます。

onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
 if (remoteUid == 101) return;

 setState(() {
   _remoteUsers.add(remoteUid);
 });
}

翻訳の終了

翻訳を終了するには、開始関数と同様の関数を使用します。この関数は stopTranscription と呼ばれ、リアルタイム翻訳サービスを停止するために Task ID と Builder Token を渡す必要があります。

Future stopTranscription() async {
 final response = await post(
   Uri.parse('$serverUrl/stop-transcribing/$taskId/$builderToken'),
 );
 if (response.statusCode == 200) {
   print('Transcription Stopped');
 } else {
   print('Couldn\'t stop the transcription : ${response.statusCode}');
 }

コール・スクリーンのdisposeメソッドでstopTranscriptionメソッドを呼び出します。これにより、チャンネルを離れる前に翻訳が停止され、エンジン・リソースが解放されます。

翻訳の取得

イベントハンドラーの onStreamMessage イベントを使用すると、ビデオ通話中に翻訳にアクセスできます。

onStreamMessage: (RtcConnection connection, int uid, int streamId,
   Uint8List message, int messageType, int messageSize) {
 print(message);
}

上のコードは、あなたが全知全能のAIである場合にのみ意味を持つ数字の配列を出力していることに気づくでしょう。これらの数値は、Googleのプロトコル・バッファ(プロトバッファとも呼ばれる)を使って生成されています。

プロトコル・バッファは、プラットフォームに依存しない方法でデータをエンコードします。つまり、アプリやソフトウェアはこのデータを取得し、その言語に従ってシリアライズすることができます。

翻訳を解読

プロトコル・バッファーを使ってメッセージをデコードする。この場合、乱数を Message というオブジェクトにシリアライズします。

まず、次のような内容の .proto ファイルを作ります:

syntax = "proto3";

package call_summary;

message Message {
 int32 vendor = 1;
 int32 version = 2;
 int32 seqnum = 3;
 int32 uid = 4;
 int32 flag = 5;
 int64 time = 6;
 int32 lang = 7;
 int32 starttime = 8;
 int32 offtime = 9;
 repeated Word words = 10;
}
message Word {
 string text = 1;
 int32 start_ms = 2;
 int32 duration_ms = 3;
 bool is_final = 4;
 double confidence = 5;
}

このファイルを新しいフォルダ lib/protobuf/file.proto に置きます。これは、ジェネレーターが Message オブジェクトを作成するための入力ファイルです。

protobufを使用するには、コンピューターにprotobufコンパイラーをインストールする必要があります。Mac用パッケージマネージャー(brew install protobuf)とLinux用パッケージマネージャー(apt install -y protobuf-compiler)から入手できます。Windowsまたは特定のバージョンが必要な場合は、Prottobufのダウンロードページを確認してください。

また、flutter pub add protobufを使用して、プロジェクト内にprotobuf dartパッケージをインストールする必要があります。

ターミナルで次のコマンドを実行してください。同じlib/protobufフォルダに4つのファイルが生成されるはずです。

protoc --proto_path= --dart_out=. lib/protobuf/file.proto  

protobufがセットアップされたので、新しいMessageオブジェクトを使って英語での書き起こしを取り出すことができます。このオブジェクトには、書き起こされた文を含むwords配列が含まれています。isFinal変数を使って、文が終わるたびにprint文をトリガーします。

onStreamMessage: (RtcConnection connection, int uid, int streamId,
   Uint8List message, int messageType, int messageSize) {
 Message text = Message.fromBuffer(message);
 if (text.words[0].isFinal) {
   print(text.words[0].text);
 }
},

翻訳の保存

ここまでで、テープ起こしの部分は終わりました。次に、書き起こしたテキストを取得して保存し、それを使ってAIに要約を促す必要があります。Real-Time Transcriptionサービスは、翻訳されたオーディオをチャンク単位で送信するため、オーディオチャンクが処理されると、シリアル化されたデータがバースト単位で送信され、それぞれがonStreamMessageイベントをトリガーします。これを保存する最も単純な方法は、長い応答文字列を連結することです。もっと洗練された方法もあるが、このデモではこれで十分。

transcriptionと呼ばれる文字列を保持し、最終的にテキストを追加すればよいのです。

onStreamMessage: (RtcConnection connection, int uid, int streamId,
Uint8List message, int messageType, int messageSize) {
 Message text = Message.fromBuffer(message);
 if (text.words[0].isFinal) {
print(text.words[0].text);
transcription += text.words[0].text;
 }
},

要約の取得

main.dartでは、APIキーを使用してGeminiに接続し、ビデオ通話の要約を促すことができます。このレスポンスを受信したら、setStateを呼び出し、summary変数を更新することで、メインページに変更が反映されます。

このアプリをテストしているときに、レスポンスが私が渡したトランスクリプトについて言及するのが好きなことに気づきました。そのため、トランスクリプトについて言及しないように、追加のプロンプトをいくつか追加しました。

late final GenerativeModel model;

@override
void initState() {
 super.initState();
 model = GenerativeModel(model: 'gemini-pro', apiKey: apiKey);
}

void retrieveSummary(String transcription) async {
 final content = [
   Content.text(
     'This is a transcript of a video call that occurred. Please summarize this call in a few sentences. Dont talk about the transcript just give the summary. This is the transcript: $transcription',
   ),
 ];
 final response = await model.generateContent(content);
 setState(() {
   summary = response.text ?? '';
 });
}

retrieveSummaryにトランスクリプト文字列を渡す必要がある場合は、call.dartに関数を渡し、呼び出しが終了したときに呼び出します。

終了

これで、誰かがチャンネルに参加するとすぐにリアルタイム翻訳サービスをトリガーするアプリケーションを構築しました。そして、このトランスクリプトはクライアント側に保存され、Geminiに要約を促し、ユーザーと共有することができます。

おめでとうございます!あなたはAIの支配者に屈する道を歩んでいます。

完全なコードはここで見ることができます。また、このガイドを基に、Real-Time Transcriptionのドキュメントを読んでみてください。

お読みいただきありがとうございました!

ブイキューブ

執筆者ブイキューブ

Agoraの日本総代理店として、配信/通話SDKの提供だけでなく、導入支援から行い幅広いコミュニケーションサービスに携わっている。

関連記事

先頭へ戻る