AWS BedrockからClaude 3 Haikuを使ってPDF要約BOTを作る

## 目的

論文のような文字数が多くPDFで提供されているコンテンツの情報を取得する際に「一旦要約してくれるものが欲しいな」と思ったので[Anthropic社](https://www.anthropic.com/claude)のClaude 3 Haiku LLMを使って作ってみました。

その後、「興味があればちゃんと読む」というフェーズの手前に置いて活用しようと思っています。
## AWS Bedrockの利用

https://aws.amazon.com/jp/bedrock/

AWS Bedrockは

> Amazon Bedrock は、単一の API を通じて AI21 Labs、Anthropic、Cohere、Luma (近日リリース予定)、Meta、Mistral AI、poolside (近日リリース予定)、Stability AI、および Amazon などの大手 AI 企業からの高性能な基盤モデル (FM) の幅広い選択肢を提供するフルマネージドサービス

と記載されている通り、このサービスを通じて提携会社のAIモデルを単一のAPIを通じて使うことができます。

そこで、Authropic社のClaude `Haiku`,`Sonnet`,`Opus`の3モデルを使うことができるので、その中の`Haiku`を使いました。

https://www.anthropic.com/news/claude-3-family

この3モデルの名前はHaiku<Sonnet<Opusの順に性能が良いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635079/54ee34fd-77fa-d583-229f-8c017158d5b5.png)
引用:https://www.anthropic.com/news/claude-3-family

また、ナンバリングもあり、[最新のモデルは3.5](https://www.anthropic.com/news/3-5-models-and-computer-use)になります。

Amazon Bedrockではその中の一部モデルが使えるようになっており、ap-northeast-1リージョンでは以下5つのモデルが使えるようになっています。

- Claude 3.5 Sonnet
- Claude 3 Haiku
- Claude 3 Sonnet
- Claude 2.1
- Claude Instant

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635079/49d37cbf-8f52-15f8-4597-72387b3682f8.png)

使用できるモデルと価格はリージョンによって違うので、多く使いたい方は米国東部 (バージニア北部) と米国西部 (オレゴン)が良いと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635079/8a3dd1f2-b6b7-576c-f49c-7806997b8860.png)


https://aws.amazon.com/jp/bedrock/pricing/

### モデル利用申請

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635079/d71c8507-fb26-44a6-e734-d8edcd75a7fd.png)

モデルを利用するには、上記画像にある通り、「リクエスト可能」なモデルに対してリクエストし、「アクセスが付与されました」の状態にしなければなりません。


Amazon Bedrockモデルアクセスページから利用したいモデルにチェックを入れアクセスをリクエストします。
その際、会社名や利用用と等求められます。私は会社名に「personal use」等書きましたが数分で許可されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635079/2a61ab12-25d5-5844-11f4-192546ee01ed.png)


### AWS Bedrock API

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/conversation-inference.html

Converse APIを使います。これはどのモデルでも統一されたフォーマットで利用できるため、実装が簡単です。
Claudeモデルに特化したAPIを使いたい場合は[invoke_model](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html)のほうが適していますが、そこまで細かく使う予定はないので、簡単にモデル切り替えできるConverse APIにしました。

### Anthropic Claudeを使う理由

#### トークン数

今更ながらこのモデルを使う理由ですが、**入力トークン数が多いから**です。
Claudeでは最大200kの入力トークンまで対応しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635079/3b3cd44d-13f8-b10e-06d4-a9e640ff7627.png)

https://support.anthropic.com/en/articles/7996856-what-is-the-maximum-prompt-length

「トークン」の定義はそれぞれ違うと思いますが、OpenAI社のGPTシリーズではそれぞれ128kになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635079/234507d5-fd47-a16a-e65c-72146bb620ad.png)

Geminiでは7168トークン(1トークン大体4文字で英語なら100トークン=60~80文字)だそうです

https://ai.google.dev/gemini-api/docs/models/gemini#token-size

#### PDF読み込み対応

AWS Bedrockでは2024年6月頃にConverse APIを介してPDFの読み込みが可能になりました。
これによりPDFを突っ込むだけでLLMのSystem側で「要約して」とするだけで要約してくれます。

https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635079/c8b8afa4-e244-c548-b384-fd5bc78a6dae.png)


https://qiita.com/moritalous/items/ff2763bcd9408a1b395a


## 実装

### コード部分

[Streamlit](https://streamlit.io/)を使いPDF受け取り→LLMで要約→返事を返すを作ります。

systemプロンプトとUserプロンプトは事前埋め込みで、

- System : 添付されたPDFファイルを要約し、内容を日本語で返してください。要約については、多くて最大10000文字程度で纏めてください。
- User : PDF Documentの内容を要約して

としています。利用側はPDFをアップロードするだけです。

AWS Bedrock Converse APIリクエストの為のコンテンツは以下ページに詳細があり、今回はこれのdocumentを使います。

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/conversation-inference-call.html



```python:Bedrock API側一部抜粋
class Summary:
    def __init__(self):
        
        if os.getenv("AWS_ACCESS_KEY_ID") is None:
            raise KeyError("AWS_ACCESS_KEY_ID is not set.")
        if os.getenv("AWS_SECRET_ACCESS_KEY") is None:
            raise KeyError("AWS_SECRET_ACCESS_KEY is not set.")
        if os.getenv("AWS_DEFAULT_REGION") is None:
            raise KeyError("AWS_DEFAULT_REGION is not set.")
        if os.getenv("AWS_BEDROCK_MODEL_ID") is None:
            raise KeyError("AWS_BEDROCK_MODEL_ID is not set.")
        try:
            self.bedrock_runtime = boto3.client(
                service_name="bedrock-runtime",
                region_name=os.getenv("AWS_REGION"),
                aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
                aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY")
            )

            self.model_id = os.getenv("AWS_BEDROCK_MODEL_ID")
            self.system = [{"text": "添付されたPDFファイルを要約し、内容を日本語で返してください。要約については、多くて最大10000文字程度で纏めてください。"}]
            
        except ClientError as err:
            raise err
        
    def generate_message(self, pdf_data):
        
        inferenceConfig ={
            "temperature": 0.5,
            "topP": 0.9
        }
        user_message = [{
                "role":"user", 
                "content": [
                    {
                        "document" : {
                            "name": "PDF Document",
                            "format": "pdf",
                            "source": {"bytes": pdf_data},
                        }
                    },
                    {"text": "PDF Documentの内容を要約して"}
                ]
            }]
        
        response = self.bedrock_runtime.converse(
            modelId=self.model_id,
            messages=user_message,
            inferenceConfig=inferenceConfig,
            system=self.system
        )
        return response
```


[file_uploader]()を使ってPDFのみ受け取り許容するようにします。

```python:Streamlit側一部抜粋
summary_instance = Summary()

st.title("PDF 要約BOT")
st.subheader("なが～い論文等のPDFを日本語で要約！")

uploaded_file = st.file_uploader(
    "PDFファイルをアップロードしてください。",
    type=["pdf"],
    key="pdf_upload"
)

if "messages" not in st.session_state:
    st.session_state.messages = []

if uploaded_file is not None:
    try:
        with st.spinner("要約中..."):
            pdf = uploaded_file.read()
            response = summary_instance.generate_message(pdf)
            summary_text = response['output']['message']['content'][0]['text']
            response['filename'] = uploaded_file.name
            logger.info(f"{response}")
            with st.chat_message("assistant"):
                st.markdown(summary_text)
    
    except Exception as e:
        st.error(f"エラーが発生しました: {e}")
```

要約した時にPDF名と要約結果とトークン消費量がログで残るようにしました

```log:app.log
2025-01-20 09:43:28 - my_logger - INFO - {'ResponseMetadata': {'RequestId': '21455403-0b7b-4586-9656-24922885b744', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Mon, 20 Jan 2025 09:43:24 GMT', 'content-type': 'application/json', 'content-length': '1722', 'connection': 'keep-alive', 'x-amzn-requestid': '21455403-0b7b-4586-9656-24922885b744'}, 'RetryAttempts': 0}, 'output': {'message': {'role': 'assistant', 'content': [{'text': 'PDF文書の内容は以下のように要約できます。\n\n本論文では、生存時間解析とカウンターファクチュアル因果推論を組み合わせたカウーザル生存解析について包括的に検討しています。特に、制限平均生存時間(RMST)を用いた平均処理効果(ATE)の推定に焦点を当てています。\n\n主な内容は以下の通りです:\n\n1. 無作為化比較試験(RCT)と観察
研究の両方を対象とし、独立打ち切りと条件付き独立打ち切りの状況を考慮しています。\n\n2. 回帰ベースの方法、加重アプローチ、ハイブリッド手法など、様々なRMST推定量を理論的に分析し、その性質を明らかにしています。\n\n3. 有限標本特性、ニューサンス・パラメータの選択の影響、モデル誤差に対する頑健性などを数値実験により評価しています。\n\n4. 理論的洞察と実践的評価を組み合わせること
で、これらの手法の最新の実装と、適切な推定量の選択に関する実用的なガイドラインを提供しています。\n\n5. G-formula two-learners、AIPCW-AIPTW、Buckley-James推定量、因果生存フォレストなどが特に有望な手法として示されています。\n\n全体として、本論文は理論的な洞察と実践的な評価の両面から、カウーザル生存解析の方法論を包括的に整理し、研究者や実務家に対して有用な知見を提供していま
す。'}]}}, 'stopReason': 'end_turn', 'usage': {'inputTokens': 46669, 'outputTokens': 494, 'totalTokens': 47163}, 'metrics': {'latencyMs': 7981}, 'filename': '2501.05836v1.pdf'}
```

`inputTokens : 46669, outputTokens : 494`なのでこれ1回で`$0.012`程度ですね。
Claude 3 Haikuを選んだのは**安いから**が一番の理由なのですが、性能も十分だと思いました。

### ホスト

AWS Lightsailのインスタンスを使っています。

Web上に公開していますが、今回の記事内容と逸れるので割愛します。

概ね以下記事に近いことをAWSでやっている形です。

https://qiita.com/nijigen_plot/items/0add0092d7b24fe4e4e6#%E8%A8%AD%E5%AE%9A

# 結果

これで対応したURLにアクセスしてPDFをドロップすれば機能を利用することができます！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635079/d9e3b542-37c8-f453-e7f4-c37fd8ab624c.png)


あとはこれを継続して続けていくだけなので、自分次第ですね。頑張ります。