Tavily API × Gemini API でWEB検索 LINE Bot 「けんサ君」を作ってみた（開発～CI/CD）

## はじめに
普段は大学院で機械学習分野の研究に取り組んでいますが、最近は Web アプリケーションの開発にも興味を持つようになりました。機械学習についてただ学ぶだけでなくアプリケーションに盛り込み身近なサービスとして届けられるような技術を身に着けられたらという思いもあり、「Tavily API」によるAI最適化検索と「Gemini API」による要約生成を組み合わせ、LINE上で自然言語検索と要約をシームレスに実現するBotアプリ「けんサ君」を開発しました。この記事では、「けんサ君」の開発に要した一連のプロセスについて紹介します。

また、以下のQRコードから友だち追加いただくと、すぐに「けんサ君」をお試しいただけます。

:::note warn
PCクライアントではリッチメニューが制限されるため、モバイルでのご利用をおすすめします。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856953/9b460491-5142-4c7f-97cf-686aa6f3f156.png)


**目次**
1. アプリの概要
2. 使用技術
    - 2.1．外部API
    - 2.2．機密情報の管理
    - 2.3. CI/CD
3. インフラ構成図
4. 機能
    - 検索機能
    - 言語選択
    - 定期通知
    - モード選択
5. おわりに


## 1. アプリ概要
一言でまとめると、LINE上で自然言語での高度なWEB検索を可能としたアプリです。具体的にはユーザがLINE上で入力した情報を基にTavilyAPIでWEB検索し、検索結果をGeminiを用いて要約しLINE上で返信するといったものです。

WEB上には無駄な情報が多く全てを確認しているといくら時間があっても足りないなんてことはありませんか？そこで、マッチ度の高いような情報を多く提供できるようにすることでできるだけ無駄を省きながら情報収集を効率化できたらなという思いで開発しました。

なぜLINE上で？と思う人もいると思いますが、LINEで検索することのメリットとして履歴として残りやすいといった点やテキスト検索で履歴を遡りやすいといった点、また普段多くの人が連絡手段として使用しており使い方に慣れているといった点が挙げられます。開発したプロジェクトはGithubで公開しています。

https://github.com/kz-ow/Linebot

## 2．使用技術

本プロジェクトでは、主に以下の技術を組み合わせ実現しています。

| 区分  |  技術名称  |
| ---- | ---- |
| フレームワーク   | FastAPI                      |
| フロントエンド    | HTML, TailWind, JavaScript（リッチメニュー）    |
| サーバサイド言語 | Python，   　                 |
| 外部API         | Messaging API，Tavily Search API，Gemini API |
| データベース     | PostgreSQL |
| スケジューリング   | APScheduler                |
| コンテナ  　 　 | Docker, docker-compose   　   |
| インフラ  　    | AWS                           |
| 機密情報の管理方法 | AWS Secrets Manager        |
| CI/CD  　       | Github Actions, AWS IAM(OIDC) |


  上記の内、外部APIと機密情報の管理方法、またCI/CDの自動化について説明します。

### 2.1．外部API ###
- **Messaging API**
LINEプラットフォーム上でユーザーとのメッセージ送受信やプッシュ通知を行うためのRESTful APIです。Webhookを利用してユーザーからのメッセージや各種イベントをリアルタイムにバックエンドへ通知でき、返信用のエンドポイントを呼び出すことでBotとしてダイレクトにメッセージを返すことが可能です。公式のPython SDKが用意されており、少量のコードでWebhook設定からメッセージ送信処理まで実装できます。（詳しくは[ドキュメント](https://developers.line.biz/ja/docs/messaging-api/overview/)を参照）
　
- **Tavily API**
AIエージェント向けに最適化された検索プラットフォームで、内部的に高速な Web スクレイピングとコンテンツ解析を行います。公式の Python SDK が提供されており、取得した API キーを使うと以下のようにわずか数行で検索結果を取得できます。（別の機能は[ドキュメント](https://docs.tavily.com/documentation/api-reference/introductio)を参照）
 
    ```python:python
    from tavily import AsyncTavilyClient
    
    client =  AsyncTavilyClient(api_key=TAVILY_API_KEY)
    response = await client.search(query=text)
    ```
　
- **Gemini API**
Google の大規模言語モデル「Gemini」を REST API 経由で利用できるサービスです。公式の Python SDK が提供されており、取得した API キーを指定するだけでプロンプトを送信し、生成結果を取得できます。（別の機能については[ドキュメント](https://ai.google.dev/gemini-api/docs?hl=ja&authuser=1)を参照）
    ```python:python
    from google import genai
    from google.genai import types
    
    client = genai.Client(api_key=GEMINI_API_KEY)
    response = client.models.generate_content(
        model="gemini-2.0-flash-001",
        contents=prompt,
    )
    ```
    
### 2.2． 機密情報の管理方法 ###
機密情報（APIキーやデータベースのユーザ、またAWSの認証情報など）はGithub SecretとAWS Secret Managerで管理しており、コンテナ内には機密情報が保存されないような構成となっています。docker-compose時にDBコンテナのみ.envファイルを参照しますが、CI/CD時に動的に作成/削除を行う設定になっているため、EC2インスタンス内には残らない仕組みになっています。

### 2.3．CI/CD ##
リポジトリへのプッシュをトリガーに自動でデプロイを行う CI/CD パイプラインを構築しています。GitHub Actions のワークフローでは、OIDC（OpenID Connect）を使って GitHub から発行される一時的なトークンを AWS IAM ロールの引き受けに利用し、長期的なアクセスキーをリポジトリに置くことなく安全に認証を行います。認証後は AWS CLI を介して EC2 インスタンスに対してデプロイ用のコマンドを実行し、コードのフェッチ、コンテナの再ビルド、環境変数ファイルの動的生成と削除までを一連のスクリプトで自動化します。これにより、EC2インスタンスへの不要なポート開放や鍵の管理をなくし、最小権限の IAM ポリシーでセキュアかつ高速なデプロイを実現しています。


## 3. アーキテクチャ
構築したアプリケーションの大まかなアーキテクチャが以下になります。（※ユーザが利用した場合のフローのみを落とし込んでいるためCI/CDは省略しています）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856953/55155996-badc-4d0f-bb9d-55c35d9bf17a.png)

MessagingAPIで登録可能なWebhookエンドポイントは **https://** のみなので、独自ドメインを取得しAWS Certificate Managerで証明書を発行しELBに関連付けることで、Meesaging API-ELB間はHTTPS通信が行われるようにしました。デプロイ時にはできる限り無料枠内に抑えるために必要最小限の構成となっています。

また、今回は単一のインスタンスに3つのDockerコンテナを立ち上げていますが、ポートを公開しているのはWebhookのエンドポイントのみであり、スケジューラは外向きの通信のみ許可、DBはDocker仮想ブリッジ内でのみの通信を許可しています。そのため、EC2のインバウンドは送信元がELBとメンテナンス用のSSH接続を行うEC2 Connect Instance Endpointのみを許可しています。そのため、EC2インスタンスと外部との通信を最小限に抑える構成になっています。


## 4．アプリ機能
### 基本機能
- **検索機能**
ユーザが入力した情報を基にTavilyがWEB検索し、最もスコアの高い（関連度が高いと思われる）記事が3つFlexMessage形式で返答されます。入力時は自然言語での入力が可能であり、よりチャットに近い形式での検索も実現できます。また，「詳細を見る」をクリックすると元URLのページに遷移することができ、より詳細な情報を確認できます。

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856953/51722b19-62e6-4829-ab52-aeb4ce1bf34c.png)

### リッチメニュー
- **言語選択**
検索機能の使用時に出力される際の言語を切り替えることができるようになります。言語切り替えの対象言語は日本語、英語、フランス語、ドイツ語、スペイン語、韓国語、中国語、ロシア語の8種類に対応しています。そのため、その言語の学習をしたいときなどに言語選択を行うと検索しながら言語を学習することもできるようになります。
 
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856953/a7da161b-0498-40e5-a13a-44d5966e667a.png)
    　
- **定期通知**
毎朝8:00に、ユーザーが登録した Web ページを自動で巡回し、前回チェック時点から更新があった場合はその差分を LINE メッセージでお知らせします。たとえば、推しのアーティストのライブ情報サイトやブログの更新、公式ニュースリリースのチェックなどに活用できます。（現在メンテナンス中のためごりよういただけまｓ）

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856953/6f14b06d-0d23-40d2-989b-1a6a2e5ab86f.png)
　
- **モード選択**
検索機能の利用時におけるモードを切り替えることが可能になります。モードは「一般（デフォルト）」と「ニュース」の二つがあります。「一般（デフォルト）」では、最適化したWEB検索を提供しますが、「ニュース」ではニュースに特化した検索を行い、最新のニュースを幅広く提供することが可能になります。通勤時や通学時に友人などにLINEで連絡した後に、ふとニュースを検索しチャット上に残しておくことができるようになります。

## 5. おわりに
今回はTavily API × Gemini APIを活用してWEB検索LINE Bot「けんサ君」を開発し、実際にAWSにデプロイをしてみました。設計、開発、テスト、デプロイまでの一連のプロセスを経験したのは初めてであったので不慣れな部分も多く開発に少し時間がかかってしまいました。しかし、実際にWEBアプリケーションを作成し開発してみることでモノづくりの楽しさに触れることができました。今後もこの経験を通じていろいろなサービスを作ってみます。

また、はじめてのWEBアプリケーション開発ということもあり試行錯誤を重ねての実装であったため、はじめに作成した仕様からの変更（使用するAPIやディレクトリ構造の変更などなど．．）が多々ありました。そのため、次回のWEBアプリケーション開発時には「仕様」や「設計」の部分により重点をおいて開発してみたいと思います。

※はじめての投稿のため見にくい部分もあったと思いますが、最後まで見ていただきありがとうございます！

## 参考文献
> FastAPIやDocker、各APIやAWSのドキュメント

> 今さら聞けないAWSでロードバランサーを使ったWEBサイト構築手順シリーズ（@ksawada1979）

https://qiita.com/ksawada1979/items/74d4bb1815220aec2ffd