👩‍🎓

AWS英会話力をつけたい!Amazon Bedrock&Transcribe&Pollyで一言英文会話アプリ

2024/05/10に公開

はじめに

AWSは大量の英語ドキュメントと英語の動画があります。昨今の翻訳システムや皆さんの日本語記事のおかげで英語が苦手な私でもなんとか内容をキャッチしていけているものの、英語力があればもっとスムーズにカッコよくキャッチアップできるなと日々感じております。

そこで、英語力をつけるために本アプリを考案しました。

サービス全体図

構成は以下の通りです。

英会話はスピーキングとリスニングが大事です。そのため、音声をAmazon Transcribeで取り込み、Amazon Bedrockで解析し、返答をAmazon Pollyにしゃべらせる流れになっております。

作成手順

1.BeautifulSoupとS3を用いてのRAG用資材の整備

今回はKnowledge Bases for Amazon Bedrockを採用しているため、RAGやベクトルデータベース(今回はOpenSearch)はコンソールを指示通りに操作するだけで簡単に作成できます。

なお、RAGでとりあつかうドキュメントは、S3にアップロードするだけでよいので、Webサイトをスクレイピングして自動的にS3にアップロードするスクリプトをEC2上に作成しました。

    #sitemap.xmlからpage一覧を取得
    url = "https://docs.aws.amazon.com/lambda/latest/dg/sitemap.xml"
    pages = parse_sitemap(url)
    
    s3 = boto3.client('s3')

    for page in pages:
        print(page)
        html = requests.get(page)
        soup2 = BeautifulSoup(html.content, 'html.parser')
        
        #各URLの文字列内の"/"を"_"に変換し、それをHTMLのファイル名としてS3に格納
        filename=page.replace('/','_')
        with open("html/"+filename, 'w') as fp:
            fp.write(soup2.prettify())
        s3.upload_file("html/"+filename,"study-engilsh-knowledge",filename)

一応目的は英語学習であるため、すべてのドキュメントのスクレイピングはせずに、今回はAWS LambdaのDeveloperGuideに絞ってスクレイピングしました。
https://docs.aws.amazon.com/lambda/latest/dg/sitemap.xml

スクレイピングして判明したのですが、AWS LambdaのDeveloperGuide(英語版)だけでも300ページ以上あります。さらに、これらのガイドは様々な言語に翻訳されているのでAWSさんのガイド作成量に感動しますね。

※ちなみに余談ですが、sitemap選定前に何も考えないでスクレイピングしたら、間違えてドイツ語のドキュメントを取り込んでしまいました。自動化する際は、ソースコード以外の部分も含めて中身をちゃんと確認する必要があることを改めて痛感しました。

スクレイピング時にはBeautifulSoupを活用しました。
これは、HTMLやXMLを解析し、中身のデータを取り出せるライブラリです。

    req.urlretrieve(url, tmp)

    xml = open(tmp, "r", encoding="utf-8").read()
    soup = BeautifulSoup(xml, 'xml')

    pages = [] 

    #sitemap内の<url>内の文字列から、<loc>を除外したものを抽出
    for i in soup.find_all("url"):
        loc = i.find('loc').string.strip()
        pages.append(loc)

こちらを活用することで、sitemapから<url><loc>を除去した一覧を作成し、一つ一つのhtmlをダウンロードすることができました。なお、後々生成AIの回答のトレーシングをしたい場合に備えて、S3にhtmlを格納するときは、極力元のURLを保持した名称で格納することを推奨します。(ドキュメント数が増えると、トレーシング時に元ファイルを探すのが大変なので)

2.Amazon Bedrockの整備

RAG用資材をS3に格納したら、Amazon Bedrock上に作成したナレッジベースに反映します。ナレッジベースのデータソース欄にてデータソースとしてS3を指定し、S3のアップデート次第「同期」を行います。

「同期」ボタンを押下するだけで、OpenSearch(ベクトルデータベース)に格納するデータをAmazon Bedrock側で自動で整備してくれるため、とても便利です。

ナレッジベースを取得して、質問をLLMで解析する実装は、他の事例でも一般的なLangChainを利用しました。

https://python.langchain.com/v0.1/docs/integrations/retrievers/bedrock/

現時点では特別な工夫はせずに、英語で質問された文章を、そのままRAGを用いて英語の回答文を作成するようにしました。

3.Amazon Transcribeの整備

英会話を練習するためには、声に出して英語を話すことです。Amazon TranscribeのSDKを活用するとストリーミングされたデータを文字おこしできます。

https://docs.aws.amazon.com/ja_jp/transcribe/latest/dg/getting-started-sdk.html

マイクから音声を入力した際の文字起こし方法について、ありがたいことに公式からサンプルが用意されていたため、実装にあたってはこちらをがっつり使わせていただきました。

https://github.com/awslabs/amazon-transcribe-streaming-sdk/blob/develop/examples/simple_mic.py

4.Amazon Pollyの整備

英会話において難しいのが「同じ英文を話しても、国によってイントネーションや発音が変わることがある」点です。

そのため、Amazon Pollyの出番です。Amazon Pollyの英語音声には、様々なレパートリー(国・性別)があります。本アプリは多様な英語を活用したかったため、下記のVoiceIdを採用しました。

voice_id_and_language_code = [
    #オーストラリア
    ['Olivia','en-AU'],
    #イギリス
    ['Emma','en-GB'],
    ['Brian','en-GB'],
    ['Amy','en-GB'],
    ['Arthur','en-GB'],
    #アイルランド
    ['Niamh','en-IE'],
    #インド
    ['Kajal','en-IN'],
    #アメリカ
    ['Danielle','en-US'],
    ['Salli','en-US'],
    ['Kimberly','en-US'],
    ['Joanna','en-US'],
    ['Ivy','en-US'],
    ['Ruth','en-US'],
    ['Gregory','en-US'],
    ['Matthew','en-US'],
    ['Justin','en-US'],
    ['Joey','en-US'],
    ['Stephen','en-US'],
    #ニュージーランド
    ['Aria','en-NZ'],
    #南アフリカ
    ['Ayanda','en-ZA'],
]

Amazon PollyもSDKを活用することで、該当する文章を音声で出すことができます。今回はリアルタイムにレスポンスが欲しかったため、SynthesizeSpeechを利用しています。

https://docs.aws.amazon.com/ja_jp/polly/latest/dg/get-started-what-next.html

https://docs.aws.amazon.com/ja_jp/polly/latest/dg/SynthesizeSpeechSamplePython.html

この際、アプリで多様な英語を触れることでリスニング力を向上させたかったため、Amazon Bedrockから回答を受領次第、都度上記VoiceIdリストの中からランダムで音声を選ぶようにしました。

    def read(self,text):
        #どのVoiceIdを使うのかをランダムに選択
        selection=random.choice(voice_id_and_language_code)
        
        response = polly.synthesize_speech(
            Text=text,
            Engine=polly_config['Engine'],
            OutoutFormat=polly_config['output_format'],
            VoiceId=selection[0],
            LanguageCode=selection[1],
        )

なお、上記レスポンスを取得後の音声の保存と再生は。PyAudioを利用しました。

https://people.csail.mit.edu/hubert/pyaudio/docs/

さいごに

アプリのレスポンス速度改善や、エラーハンドリング部分実装などなど、まだまだできていないので、このアプリを徐々にブラッシュアップしながら英語力もブラッシュアップしようと思います!

また、個人的に東京リージョンで試したかったのでClaude 2を採用しましたが、Claude 3が東京に上陸したら、ぜひ試したいと思います。

Discussion