AWSのbedrockにclaude3.5が出たのでPythonで呼び出してみた

## 事前準備

- aws configureでiamをcliに登録しておく
- bedrockでClaude3.5 sonnetを有効かしておく

## モデルIDを取得する

下のコードでmodel_idにclaude-3-5が入っているidを取得できる。

```python
def search_model(query):
    response = client.list_foundation_models()

    for model in response['modelSummaries']:
        if query in model['modelId']:
            print(model['modelId'])

search_model("claude-3-5")

```


## サンプルコード

取得したIDをしたのように埋め込む

```python
from pydantic import BaseModel, Field, ValidationError
import boto3
import json

class ChatWithClaudeParams(BaseModel):
    prompt: str = Field(..., min_length=1)
    max_tokens: int = Field(1000, ge=800, le=4096)
    temperature: float = Field(0.7, ge=0.0, le=1.0)
    llm_model_id: str = Field("anthropic.claude-3-5-sonnet-20240620-v1:0")

def chat_with_claude(params):
    if not isinstance(params, ChatWithClaudeParams):
        raise TypeError("params must be an instance of ChatWithClaudeParams")

    bedrock = boto3.client('bedrock-runtime', region_name='us-east-1')
    
    body = json.dumps({
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": params.max_tokens,
        "messages": [{"role": "user", "content": params.prompt}],
        "temperature": params.temperature
    })

    response = bedrock.invoke_model(
        body=body,
        modelId=params.llm_model_id,
        contentType="application/json",
        accept="application/json"
    )

    response_body = json.loads(response['body'].read())
    return response_body['content'][0]['text']


try:
    params = ChatWithClaudeParams(prompt="Hello, Claude!")
    result = chat_with_claude(params)
    print(result)
except ValidationError as e:
    print(e.json())
except TypeError as e:
    print(str(e))

```

## 画像の内容について質問してみる

```python
from pydantic import BaseModel, Field, ValidationError
import boto3
import json
import base64

class ChatWithClaudeParams(BaseModel):
    prompt: str = Field(..., min_length=1)
    max_tokens: int = Field(1000, ge=800, le=4096)
    temperature: float = Field(0.7, ge=0.0, le=1.0)
    llm_model_id: str = Field("anthropic.claude-3-5-sonnet-20240620-v1:0")
    image_path: str = Field(None)  # 画像ファイルのパスを追加

def chat_with_claude(params):
    if not isinstance(params, ChatWithClaudeParams):
        raise TypeError("params must be an instance of ChatWithClaudeParams")

    bedrock = boto3.client('bedrock-runtime', region_name='us-east-1')
    
    messages = [{"role": "user", "content": params.prompt}]

    # 画像が指定されている場合、Base64エンコードしてメッセージに追加
    if params.image_path:
        with open(params.image_path, "rb") as image_file:
            image_data = base64.b64encode(image_file.read()).decode('utf-8')
        messages[0]["content"] = [
            {"type": "text", "text": params.prompt},
            {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": image_data}}
        ]

    body = json.dumps({
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": params.max_tokens,
        "messages": messages,
        "temperature": params.temperature
    })

    response = bedrock.invoke_model(
        body=body,
        modelId=params.llm_model_id,
        contentType="application/json",
        accept="application/json"
    )

    response_body = json.loads(response['body'].read())
    return response_body['content'][0]['text']

params = ChatWithClaudeParams(
    prompt="この画像について説明してください。",
    image_path="cute_dog.jpeg"
)
response = chat_with_claude(params)
print(response)

```

### 使った画像

![cute_dog.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681799/d5ec8c3d-fad2-5778-677f-27f69cc8997e.jpeg)


### 出力結果

```
この画像は、公園のような屋外の風景の中で、かわいい子犬が描かれています。

犬は茶色と白の毛色で、大きな目と笑顔を見せています。舌を出して幸せそうな表情をしており、とても愛らしい雰囲気です。

背景には青い空と白い雲、緑の芝生や木々が描かれています。遠くには街並みが見え、公園のベンチやゴミ箱も配置されています。

全体的に明るく温かみのある色使いで、cartoon風のイラストスタイルで描かれています。犬の表情や姿勢、背景の細かい描写など、細部まで丁寧に描かれた可愛らしいイラストです。

この絵は、のどかな公園での楽しいひとときや、ペットとの触れ合いの喜びを感じさせるような、温かい雰囲気を醸し出しています。
```

## LangChainでやってみる

```
pip install -U langchain langchain-community langchain-aws
```

## LangChainで出力制御もする

```python
from langchain_aws import ChatBedrock
from langchain.schema import HumanMessage
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List

# Pydanticモデルの定義（変更なし）
class WeatherResponse(BaseModel):
    temperature: float = Field(description="気温（摂氏）")
    conditions: str = Field(description="天気の状態")
    precipitation_chance: float = Field(description="降水確率（%）")
    recommendations: List[str] = Field(description="おすすめの活動リスト")

# 出力パーサーの作成（変更なし）
parser = PydanticOutputParser(pydantic_object=WeatherResponse)

# Bedrockの設定
bedrock_chat = ChatBedrock(
    model_id="anthropic.claude-v2",
    client=None,  # AWS SDKのクライアントを設定
    region_name="us-east-1",  # 適切なリージョンを指定
)

# プロンプトテンプレートの作成（変更なし）
template = ChatPromptTemplate.from_messages([
    ("human", "今日の{location}の天気を教えてください。{format_instructions}"),
])

# チャット関数
def get_weather(location: str):
    messages = template.format_messages(
        location=location,
        format_instructions=parser.get_format_instructions()
    )
    response = bedrock_chat.invoke(messages)
    return parser.parse(response.content)

# 使用例
try:
    weather = get_weather("東京")
    print(f"気温: {weather.temperature}°C")
    print(f"天気: {weather.conditions}")
    print(f"降水確率: {weather.precipitation_chance}%")
    print("おすすめの活動:")
    for rec in weather.recommendations:
        print(f"- {rec}")
except Exception as e:
    print(f"エラーが発生しました: {e}")
```