【全世界待望】AIの最新情報を自動でキャッチアップする！

### はじめに
AIの進歩は目まぐるしいですね！
みなさん、AIの最新情報をキャッチアップできていますか？:dizzy_face:

### AIの最新情報キャッチアップのここが大変...
* 毎日のように情報がアップデートされている
* 知らない単語が増えてくる
* キャッチアップするにも何をキャッチアップすればよいかわからない
* 毎日調べるのは手間がかかる

これ、解消しましょう！！

### AIの最新情報を自動でキャッチアップする！
どうやって？

以下の仕組みを考えてみました！

* 「**AIの最新情報を**」 :arrow_right: **Google Trendsから取得**
* 「**自動で**」 :arrow_right: **Azure Logic Appsでワークフロー化**
* 「**キャッチアップする**」 :arrow_right: **Amazon Bedrockで要約**

:::note info
後述しますが、Azure Logic Appsを利用しているのは、普段Teamsを利用しており、簡単にTeamsと連携できるためとなります。他のツール（Slackなど）と連携する場合は、Azure Logic Appsに限る必要はありません。
:::
:::note info
後述しますが、Amazon Bedrockを利用しているのは、簡単にマルチエージェントでWeb検索ができるためとなります。他のやり方でも問題ありません。
:::

#### 構成

ざっくりとした構成は以下:point_down:となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/e4f046e4-877f-46a1-b920-8c5a258f3473.png)



詳細をご説明します。

#### Google Trendsから取得

* AWS LambdaからAPIをたたいてGoogle Trendsの関連キーワードを取得する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/86fae264-afd3-4c92-94f2-98b9eca4685c.png)

**Google Trendsとは**

> Google Trends（グーグルトレンド）は、Google 検索での上位の検索クエリを分析し、トレンドをグラフで見ることができるツール。
> リアルタイムの検索トレンドとして急上昇ワードを見ることができるだけでなく、検索キーワードから1年を振り返るなどで、長いスパンでの検索トレンドから毎日のニュースまで幅広く知ることができる。
> https://ja.wikipedia.org/wiki/Google_Trends

https://trends.google.com/trends/

検索キーワード「AI」で探す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/1305fe9b-e889-4e00-a4b9-0b7943734957.png)

「関連トピック」や「関連キーワード」もわかる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/11247df9-c4fc-4e20-80c3-ce79641d8446.png)

**AIの最新情報はGoogle Trendsで「AI」の「関連キーワード」から取得します。**
情報の取得は SERP API を利用することにしました。

https://serpapi.com/

SERP APIでアカウント登録をして、API Keyを取得します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/3c3be2e6-fc8d-43f2-8ebc-bc4e07c0f509.png)

:::note info
アカウント登録で、無料で 100回/月 までAPI実行が可能です。（2025/5/20時点）
:::

:::note warn
他にもGoogle Trendsの情報を取得する以下を確認してみましたが、いずれもGoogleのアクセス制限により情報取得ができませんでした。（2025/5/20時点）
* Playwright （Web操作を自動で行うツール。）
* pytrends （Google TrendsをPythonで取得する。）
:::


AWS Lambdaは以下のように実装します。
* ランタイム　Python 3.12
（追加するレイヤーのrequestsのバージョンに合わせています。）
* アーキテクチャ　x86_64

```lambda_function.py
import json
import os
import requests

def lambda_handler(event, context):
    api_key = os.getenv("SERPAPI_API_KEY")
    if not api_key:
        return {
            "statusCode": 500,
            "body": json.dumps({"error": "API key not found"})
        }

    url = "https://serpapi.com/search"
    params = {
        "engine": "google_trends",
        "date": "now 1-d",
        "q": "AI",
        "data_type": "RELATED_QUERIES",
        "hl": "ja",
        "geo": "JP",
        "tz": -540,
        "api_key": api_key
    }

    response = requests.get(url, params=params)
    data = response.json()

    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json;charset=UTF-8"},
        "body": json.dumps(rising_queries, ensure_ascii=False)
    }
```

---
「設定」タブの「一般設定」で「タイムアウト」を「30秒」に指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/306889e7-a24c-415a-8bef-bc41630ee9e0.png)

---
「設定」タブの「関数URL」で「関数URLを作成」を行います。

* `認証タイプ`：`NONE`
* `呼び出しモード`：`BUFFERED（デフォルト）`
* `オリジン間リソース共有（CORS）設定`：`チェック`
* `許可オリジン`：`*`
* `許可メソッド`：`*`

:::note info 
関数URLをAzure Logic Appsから呼び出すためとなります。
:::
:::note warn 
必要に応じて関数URLの認証タイプ等を設定しセキュアにしてください。
:::

---
「設定」タブの「環境変数」で取得したSERP APIのAPI Keyを指定します。
キー：`SERPAPI_API_KEY`
値：`取得したSERP APIのAPI Key`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/d251ac7f-ed21-415f-8915-86e1dd4f16d2.png)

:::note warn
今回は 個人利用 かつ 検証のため 環境変数にAPI Keyを設定していますが、本格利用する場合は、AWS Secret Manager等を用いてセキュアに保存しましょう。
:::

---
「コード」タブの下部より「レイヤーの追加」してrequestsを使用できるようにします。

`arn:aws:lambda:ap-northeast-1:770693421928:layer:Klayers-p312-requests:13`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/6d46d820-5545-47cd-b54b-ad36c3fba357.png)

https://api.klayers.cloud//api/v2/p3.12/layers/latest/ap-northeast-1/html

---
**コードの補足説明**

```（抜粋）lambda_function.py
    params = {
        "engine": "google_trends",
        "date": "now 1-d",
        "q": "AI",
        "data_type": "RELATED_QUERIES",
        "hl": "ja",
        "geo": "JP",
        "tz": -540,
        "api_key": api_key
    }
```
* `engine`：Google Trendsを指定します。`google_trends`
* `date`：「過去1日」を指定します。`now 1-d`
* `q`：検索キーワード「AI」を指定します。`AI`
* `data_type`：検索タイプ「関連キーワード」を指定します。`RELATED_QUERIES`
* `hl`：言語「日本語」を指定します。`ja`
* `geo`：ロケーション「日本」を指定します。`JP`
* `tz`：タイムゾーン「Asia/Tokyo」を指定します。`-540`
* `api_key`：API Keyを指定します。`api_key`

https://serpapi.com/google-trends-api

#### Amazon Bedrockで要約
* Google Trendsから取得した関連キーワードをWeb検索して最新情報を取得する。
* 取得した最新情報をAmazon Bedrockで要約する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/33026f8b-fcaa-4887-9d9e-3d4aeb9e472e.png)

より柔軟な回答を生成するため、マルチエージェント構成としました。
こちら:point_down:の記事が参考になります:bow:

https://qiita.com/minorun365/items/1f4dbf5842a47bd175cc

実装した構成は以下となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/4b4e1855-7f0b-4a10-91f4-615aa4dbf3ce.png)

AWS Lambdaは以下のように実装します。
* ランタイム　Python 3.13
* アーキテクチャ　x86_64

```lambda_function.py
import json
import uuid
import boto3
from botocore.exceptions import ClientError

agent_id = "XXX" # 作成したエージェントIDを指定します。
agent_alias_id = "YYY" # 作成したエージェントエイリアスIDを指定します。

def initialize_session():
    client = boto3.client("bedrock-agent-runtime")
    session_id = str(uuid.uuid4())
    messages = []
    return client, session_id, messages

def invoke_bedrock_agent(client, session_id, prompt):
    try:
        response = client.invoke_agent(
            agentId=agent_id,
            agentAliasId=agent_alias_id,
            sessionId=session_id,
            enableTrace=True,
            inputText=prompt
        )
        return response
    except Exception as e:
        print(f"Error in invoke_bedrock_agent: {str(e)}")
        return None

def handle_agent_response(response):
    try:
        completion_events = response.get("completion", [])
        results = []
        for event in completion_events:
            if "trace" in event:
                trace_data = event.get("trace", {})
                print(f"Trace: {trace_data}")
            if "chunk" in event:
                answer = event["chunk"]["bytes"].decode()
                print(f"Chunk: {answer}")
                results.append(answer)
        return results
    except Exception as e:
        print(f"Error processing response: {str(e)}")
        raise

def lambda_handler(event, context):
    client, session_id, messages = initialize_session()
    # print("Received event:", json.dumps(event, ensure_ascii=False))

    try:
        query = event.get("body")
        input_text = f"""
AI関連キーワードの「{query}」について概要（「{query}」とは？）とその詳細を説明してください。
説明口調で、かつ、見やすいようにマークダウン形式で出力してください。関連リンクは不要です。
        """

        response = invoke_bedrock_agent(client, session_id, input_text)
        results = handle_agent_response(response)
        print("results:", results)

        return {
            "statusCode": 200,
            "headers": {
                'Access-Control-Allow-Headers': 'Authorization, Content-Type',
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'OPTIONS, POST',
                'Content-Type': 'application/json;charset=UTF-8'
            },
            "body": json.dumps({"summary": ", ".join(results)}, ensure_ascii=False)
        }
    except Exception as e:
        print("Lambda Error:", str(e))
        return {
            "statusCode": 500,
            "headers": {
                'Access-Control-Allow-Headers': 'Authorization, Content-Type',
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'OPTIONS, POST',
                'Content-Type': 'application/json;charset=UTF-8'
            },
            "body": json.dumps({"error": str(e)}, ensure_ascii=False)
        }
```

---
「設定」タブの「一般設定」で「タイムアウト」を「**2分**」に指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/dfa9fa67-51ff-4ae6-9f37-4d48a975fec2.png)

:::note info
マルチエージェント構成のため、1分以上 回答生成に時間がかかります。
また、Azure Logic Appsのタイムアウトが 2分 のため、その値に合わせています。
:::

---
「設定」タブの「関数URL」で「関数URLを作成」を行います。

* `認証タイプ`：`NONE`
* `呼び出しモード`：`BUFFERED（デフォルト）`
* `オリジン間リソース共有（CORS）設定`：`チェック`
* `許可オリジン`：`*`
* `許可メソッド`：`*`

:::note info 
関数URLをAzure Logic Appsから呼び出すためとなります。
:::
:::note warn 
必要に応じて関数URLの認証タイプ等を設定しセキュアにしてください。
:::

---
**コードの補足説明**

```（抜粋）lambda_function.py
        input_text = f"""
AI関連キーワードの「{query}」について概要（「{query}」とは？）とその詳細を説明してください。
説明口調で、かつ、見やすいようにマークダウン形式で出力してください。関連リンクは不要です。
        """
```
上記でプロンプトを指定しています。
思った回答が得られない場合は、こちらのプロンプトを修正してみてください。

#### Azure Logic Appsでワークフロー化

* Azure Logic Appsで定期実行する。
* 生成された情報をTeamsに投稿する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/37141ec3-a30c-4c11-99c5-aa529da29871.png)

---

**Logic Apps ワークフロー補足**

**【前半部分】**
* `Recurrence`：毎週 月、火、水、木、金に実行するように指定する。
* `HTTP`：Google Trendsから取得するLambdaの関数URLを指定する。
* `チャットまたはチャネルでメッセージを投稿する`：取得したGoogle Trendsの結果をTeamsに投稿する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/88a1f7d2-184e-4896-97c7-7d9035e97c3c.png)

**【後半部分】**
* `For each`：Google Trendsで取得したクエリは複数取得している。そのため、並列実行でLogic Appsの処理時間を短縮する。
* `HTTP 1`：Google Trendsで取得したクエリに対して、Web検索して要約をするLambdaの関数URLを指定する。
* `チャットまたはチャネルでメッセージを投稿する`：生成された要約をTeamsに投稿する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/77940ce2-2031-4d64-a82f-3c0f1fab8020.png)

<details><summary>ロジックアプリコード（全体）</summary>

```json
{
    "definition": {
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "contentVersion": "1.0.0.0",
        "triggers": {
            "Recurrence": {
                "recurrence": {
                    "interval": 1,
                    "frequency": "Week",
                    "timeZone": "Tokyo Standard Time",
                    "schedule": {
                        "hours": [
                            8
                        ],
                        "minutes": [
                            0
                        ],
                        "weekDays": [
                            "Monday",
                            "Tuesday",
                            "Wednesday",
                            "Thursday",
                            "Friday"
                        ]
                    }
                },
                "evaluatedRecurrence": {
                    "interval": 1,
                    "frequency": "Week",
                    "timeZone": "Tokyo Standard Time",
                    "schedule": {
                        "hours": [
                            8
                        ],
                        "minutes": [
                            0
                        ],
                        "weekDays": [
                            "Monday",
                            "Tuesday",
                            "Wednesday",
                            "Thursday",
                            "Friday"
                        ]
                    }
                },
                "type": "Recurrence"
            }
        },
        "actions": {
            "HTTP": {
                "runAfter": {},
                "type": "Http",
                "inputs": {
                    "uri": "[Google Trendsから取得するLambdaの関数URL]",
                    "method": "POST",
                    "retryPolicy": {
                        "type": "none"
                    }
                },
                "runtimeConfiguration": {
                    "contentTransfer": {
                        "transferMode": "Chunked"
                    }
                }
            },
            "Parse_JSON": {
                "runAfter": {
                    "HTTP": [
                        "Succeeded"
                    ]
                },
                "type": "ParseJson",
                "inputs": {
                    "content": "@body('HTTP')",
                    "schema": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "query": {
                                    "type": "string"
                                },
                                "value": {
                                    "type": "string"
                                }
                            },
                            "required": [
                                "query",
                                "value"
                            ]
                        }
                    }
                }
            },
            "Compose": {
                "runAfter": {
                    "Parse_JSON": [
                        "Succeeded"
                    ]
                },
                "type": "Compose",
                "inputs": "@body('Parse_JSON')"
            },
            "チャットまたはチャネルでメッセージを投稿する": {
                "runAfter": {
                    "Create_HTML_table": [
                        "Succeeded"
                    ]
                },
                "type": "ApiConnection",
                "inputs": {
                    "host": {
                        "connection": {
                            "name": "@parameters('$connections')['teams']['connectionId']"
                        }
                    },
                    "method": "post",
                    "body": {
                        "recipient": "XXX",
                        "messageBody": "<p class=\"editor-paragraph\">■■■■</p><p class=\"editor-paragraph\"><b><strong class=\"editor-text-bold\">【 最新（</strong></b>@{convertFromUtc(utcNow(),'Tokyo Standard Time','yyyy/MM/dd')}<b><strong class=\"editor-text-bold\">）</strong></b><b><strong class=\"editor-text-bold\">のAIトレンド】</strong></b></p><br><p class=\"editor-paragraph\">@{body('Create_HTML_table')}</p>"
                    },
                    "path": "/beta/teams/conversation/message/poster/Flow bot/location/@{encodeURIComponent('Group chat')}"
                }
            },
            "Create_HTML_table": {
                "runAfter": {
                    "Compose": [
                        "Succeeded"
                    ]
                },
                "type": "Table",
                "inputs": {
                    "from": "@outputs('Compose')",
                    "format": "HTML"
                }
            },
            "チャットまたはチャネルでメッセージを投稿する_1": {
                "runAfter": {
                    "Join": [
                        "Succeeded"
                    ]
                },
                "type": "ApiConnection",
                "inputs": {
                    "host": {
                        "connection": {
                            "name": "@parameters('$connections')['teams']['connectionId']"
                        }
                    },
                    "method": "post",
                    "body": {
                        "recipient": "XXX",
                        "messageBody": "<p class=\"editor-paragraph\">■■■■</p><p class=\"editor-paragraph\"><b><strong class=\"editor-text-bold\">【 最新（</strong></b>@{convertFromUtc(utcNow(),'Tokyo Standard Time','yyyy/MM/dd')}<b><strong class=\"editor-text-bold\">）</strong></b><b><strong class=\"editor-text-bold\">の</strong></b><b><strong class=\"editor-text-bold\">AIトレンドの要約】</strong></b></p><br><p class=\"editor-paragraph\">@{body('Join')}</p>"
                    },
                    "path": "/beta/teams/conversation/message/poster/Flow bot/location/@{encodeURIComponent('Group chat')}"
                }
            },
            "For_each": {
                "foreach": "@outputs('Compose')",
                "actions": {
                    "HTTP_1": {
                        "type": "Http",
                        "inputs": {
                            "uri": "https://6fpuzjrqps6guvzmbu7pbhmckm0jprvi.lambda-url.ap-northeast-1.on.aws/",
                            "method": "POST",
                            "body": "@items('For_each')['query']",
                            "retryPolicy": {
                                "type": "none"
                            }
                        },
                        "runtimeConfiguration": {
                            "contentTransfer": {
                                "transferMode": "Chunked"
                            }
                        }
                    },
                    "Append_to_array_variable": {
                        "runAfter": {
                            "Parse_JSON_2": [
                                "Succeeded"
                            ]
                        },
                        "type": "AppendToArrayVariable",
                        "inputs": {
                            "name": "variable1",
                            "value": "@body('Parse_JSON_2')?['summary']"
                        }
                    },
                    "Parse_JSON_2": {
                        "runAfter": {
                            "HTTP_1": [
                                "Succeeded"
                            ]
                        },
                        "type": "ParseJson",
                        "inputs": {
                            "content": "@body('HTTP_1')",
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "summary": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    }
                },
                "runAfter": {
                    "Initialize_variables": [
                        "Succeeded"
                    ]
                },
                "type": "Foreach"
            },
            "Initialize_variables": {
                "runAfter": {
                    "チャットまたはチャネルでメッセージを投稿する": [
                        "Succeeded"
                    ]
                },
                "type": "InitializeVariable",
                "inputs": {
                    "variables": [
                        {
                            "name": "variable1",
                            "type": "array"
                        }
                    ]
                }
            },
            "Join": {
                "runAfter": {
                    "For_each": [
                        "Succeeded"
                    ]
                },
                "type": "Join",
                "inputs": {
                    "from": "@variables('variable1')",
                    "joinWith": "<br><br><br><br>==========<br><br><br><br>"
                }
            }
        },
        "outputs": {},
        "parameters": {
            "$connections": {
                "type": "Object",
                "defaultValue": {}
            }
        }
    },
    "parameters": {
        "$connections": {
            "type": "Object",
            "value": {
                "teams": {
                    "id": "XXX",
                    "connectionId": "XXX",
                    "connectionName": "teams"
                }
            }
        }
    }
}
```
</details>

#### 実行結果

定刻になるとTeamsに通知が来ます。
お試しのため、以下は関連キーワード Top 3 のみを取得しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/9dbe6df5-3df8-4560-bb60-ed818c7c058f.png)

ひとつひとつの関連キーワードについて要約を生成しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/f0409790-ad0d-4ff0-aca3-4311a57555ca.png)

おおー！いい感じですね:thumbsup:

### おわりに
* AIの最新情報を毎日自動でキャッチアップできるようになった！
* Azure、Google、AWSの構成でやってみた。
**Azure**：簡単にTeamsと連携できる。
**Google**：検索の関連キーワードを取得する。
**AWS**：簡単にマルチエージェント構成ができる。
:::note info
Azure Logic Appsを利用しているのは、普段Teamsを利用しており、簡単にTeamsと連携できるためとなります。他のツール（Slackなど）と連携する場合は、Azure Logic Appsに限る必要はありません。
:::
:::note info
Amazon Bedrockを利用しているのは、簡単にマルチエージェントでWeb検索ができるためとなります。他のやり方でも問題ありません。
:::
* この構成は私が検証してみたものですので、もっとセキュアに、もっと効率よく変更してください！