AWS re:Invent 2023の新サービス&アップデートを速報する生成系AIボットを導入した話

# はじめに
2023/11/26-12/1の間、AWS re:Invent 2023で大量の新サービスとアップデートが登場しましたが、年々物量が増え、キャッチアップに時間と体力が掛かるようになりました。

せっかくラスベガスのAWS re:inventの現地に行ったのに、ブログを書いたり社内に速報するために時間を使いすぎてしまっては、Breakout SessionやGame Dayなどの現地でしか経験できないイベントに時間が使えません。

そこで、今年はAWS re:Inventの大量の新サービスとアップデートを生成系AIに解説させ、社内に超速報としてポストする仕組みを開発しました。

ラスベガスに向けて出発する前日の11/24(金)にこの仕組みを実装して動作確認を行い、現地時間の11/26(日)に有効化して、大量にやって来るアップデートを待ち構えました。

# アーキテクチャ図
[会社でAWSの技術者コミュニティをMicrosoft Teamsを使用して運営](https://www.tis.co.jp/news/2023/tis_info/20230929_2.html)しており、このTeamsのチャネルにアップデート情報を投稿します。

Power AutomateのRSSコネクタを使い、AWS News Blogの最新ニュース用RSS(https://aws.amazon.com/about-aws/whats-new/recent/feed/) を読み込みます。

現地時間の11/26(日)以降、RSSコネクタでAWS re:Inventの期間中に登場した新サービスとアップデート記事のフィードを受信し、フィード中のURLをLambda関数で開き、OpenAIのFunction Calling機能を利用して、GPT-4-Trubo(gpt-4-1106-preview)に要約させました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/3650463d-cd7f-0345-ac6d-19e60cb00b73.png)

# Lambda関数のコード
ポイントを絞って解説していきます。

## 1. Lambda関数のURLでPOSTリクエストを受信
`input_url`パラメータでURLをPOSTします。

```Python
if ('body' in event):
        body = json.loads(event['body'])
        if (('input_url' in body) and (body['input_url'] != None) and (body['input_url'] != '')):
            input_url = body['input_url']
```

## 2. プロンプトを作成
社内に速報するにあたっては、その技術が既存に比べてどう優れているかを示しつつ、ビジネス上の着想が得られるようにした方が良いでしょう。

工夫を凝らした結果、プロンプトは以下のようになりました。

```
question = f"""
あなたはAWSに習熟したプリンシパルです。
URL「{input_url}」を開き、日本語で記事を要約して文章を作成してください。
その後、この記事に対する見解を日本語で作成してください。
意見には以下のポイントを含めてください。
* 既存の技術またはサービスに比べて何がどのように優れているのか。
* この技術またはサービスが解決する技術的課題についての説明。
* この技術またはサービスは、市場に対してどのようなインパクトをもたらすか。
* 否定表現は使用しないでください。
"""
```

## 3. OpenAIにプロンプトをクエリする
`functions`で定義した関数を呼び出して、記事の内容を読み込み、OpenAIにクエリして要約させます。

```Python
def get_article_body(url: str) -> str:
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as res:
        body = res.read()
    
    # check encording
    chardet_result = chardet.detect(body)
    encording = chardet_result['encoding']
    
    html_doc = body.decode(encording)
        
    soup = BeautifulSoup(html_doc, 'html.parser')
    contents = soup.find('body')
    texts = [c.get_text() for c in contents.find_all('p')]
    texts = "\n\n".join(texts)

    return texts[:4000]

functions=[
    {
        "name": "get_article_body",
        "description": "URLを開き、記事の内容を取得する",
        "parameters": {
            "type": "object",
            "properties": {
                "url": {
                    "type": "string",
                    "description": "記事のURL",
                },
            },
            "required": ["url"],
        },
    }
]
```
OpenAIにクエリする時に`functions`で関数を指定し、`function_call`は関数を呼び出せるように`auto`とします。

```Python
function_call_mode = "auto"
        
# Quering to OpenAI.
response = openai.ChatCompletion.create(
    model = "gpt-4-1106-preview",
    messages = [
        {"role": "user", "content": question},
        *message_history,
    ],
    functions = functions,
    function_call = function_call_mode,
)
```

# Power Automateのフロー図
OpenAI (Lambda関数) にクエリする部分にはカスタムコネクタを使用します。
改行コードを置換しないと表示が崩れるため、置換用の変数コネクタを作っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/274deeb2-4cf8-3f05-4f80-909cb7406bc3.png)

## RSSフィードからURLを抽出
RSSフィードからURLを抽出する部分の、Power Automate変数(式)の書き方です。
```
replace(triggerOutputs()?['body/title'], '"', '\"')
```

## 記事の要約結果から改行コードを置換する
同じくPower Automateの変数(式)で書きます。
```
replace(items('Apply_to_each')?['message/content'], variables('LFcode'), '<br />')
```

# 実行結果

フローを有効化して放置しておくだけで、以下のようにアップデートが出力されました。
210件というとてつもない数ですが、淡々と解説してくれます。
これを人間が解説しようとすると、1件あたり5分かかるとして、休憩なしで17時間近くかかる計算です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/f087d113-0b22-71d1-798d-e3bfa2db1787.png)

例えば「Amazon Q」の解説は次の通りです。
やや日本語が難しいポイントがありますが、読み解けないことはなく、速報レベルであれば、満足できるクオリティでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/016f9e93-f6cb-e18d-0e05-d7d8f19d0eac.png)

# まとめ

大量の新サービスを全てキャッチアップするのは大変な作業です。
**「せめてAWS re:Inventでラスベガスに出張している期間は、社員の時間を現地イベントに出来るだけ集中させたい」**
このような目的で、このボットは開発されました。

私はこのボットのおかげで、セッション参加数もExpo探索時間も、例年より多く割くことが出来ました。
昨年までは会場でブログを書くための時間を1日に数時間と取っていましたが、このボットの働きを見て、ラスベガスのAWS re:Invent会場にいる間は、やはり現地でしかできないことを体験するために時間を費やすのが一番であり、また、その体験を日本にいた方々にフィードバックすることに意義があると考え直しました。

また、AWS re:Invent終了後、日本に帰国した後に、この生成系AIが出力した要約を全件読み返し、腹落ちさせようとする作業を行っていますが、こちらについても、人手で1件1件開くよりも効率的に行えるという効果が得られています。

なお、この仕組みを作った時点では、まだAmazon BedrockのAgent機能 (Agents for Amazon Bedrock) がリリースされていなかったことを最後に補足しておきます。
参考までに、Agents for Amazon Bedrockを使用して要約する方法を書きましたので、以下をご参考ください。
「**[Agents for Amazon BedrockでWeb上のブログやニュースを要約する](https://qiita.com/nasuvitz/items/c34419150eaee78544cc)**」


# 参考記事
* [Function calling](https://platform.openai.com/docs/guides/function-calling)