BedrockのCode interpretationを詳細解説（マネコン、SDK、Streamlit化してトレースと生成コードも出力）

AWS Summit New Yorkのタイミングで追加されたBedrockの新機能の一つ、「**Code interpretation**」をガッツリDeep Diveします。

その他のアップデートはこちらをご参照ください。

https://qiita.com/hayao_k/items/6f8c23f62f16e17f45bc

## BedrockのCode interpretationとは

いわゆるCode Interpreterです。

[公式ドキュメント](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-code-interpretation.html)では、以下のように表現されています。

> Code interpretationにより、エージェントは安全なテスト環境でアプリケーション コードを生成、実行、およびトラブルシューティングできます。Code interpretationを使用すると、エージェントの基盤モデルを使用して、基本的な機能を実装するためのコードを生成し、生成 AI アプリケーションの構築に集中できます。

:::note
Bedrockの機能というよりは、Agents for Amazon Bedrockの一部という位置づけです。
:::

## マネジメントコンソールでやってみる

Agents for Amazon Bedrockにオプションが追加されています。「Code Interpreter」の設定を有効にするだけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/a746b437-a096-80dc-f3a5-70300b5b8f33.png)

:::note
24/7/11時点で、ドキュメントでは「Code interpretation」、マネジメントコンソールでは「Code Interpreter」と表記が揺れています。
:::

チャットメッセージを入力する際に、ファイルの添付を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/f4e191b0-939d-3875-aac7-5dab68e20594.png)

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/400abecf-bbaa-d199-0ec0-41f415c91a87.png)

「添付ファイルをグラフにして」のように依頼すると、この通り、グラフにして表示してくれます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/da43cad1-c758-8e3d-6eb7-a6af2658ad37.png)

いい！！！

## SDKで呼び出す

Boto3は1.34.143以降でCode interpretationに対応しています。
リクエスト送信時にファイルを添付します。`useCase`として`CODE_INTERPRETER`を指定します。

```python
import boto3
import uuid

client = boto3.client("bedrock-agent-runtime")

with open("Data.xlsx", mode="rb") as f:
    source_file = f.read()

response = client.invoke_agent(
    agentId="ENGSPYXEUI",
    agentAliasId="TSTALIASID",
    sessionId=str(uuid.uuid4()),
    inputText="添付ファイルをグラフにしてください。グラフにする際はX軸をDateとし、昇順にソートしたうえでグラフ化してください",
    sessionState={
        "files": [
            {
                "name": "Data.xlsx",
                "source": {
                    "byteContent": {
                        "data": source_file,
                        "mediaType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                    },
                    "sourceType": "BYTE_CONTENT",
                },
                "useCase": "CODE_INTERPRETER",
            }
        ]
    },
)
```

レスポンスはストリーミング形式です。「files」の中に生成したファイルが格納され、「chunk」の中に生成したテキストが格納されます。

```python
for event in response.get("completion"):
    if "files" in event:
        files = event["files"]["files"]
        for file in files:
            with open(file["name"], mode="wb") as f:
                f.write(file["bytes"])

    if "chunk" in event:
        chunk = event["chunk"]
        print(chunk["bytes"].decode())
```

> 添付のExcelファイルのデータを使って、X軸をDateとし、Y軸をValueとするグラフを作成しました。ご要望通り、Dateを昇順にソートした上でグラフ化しています。生成されたグラフは graph.png という名前で保存されています。

![graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/a561d51f-c84d-6992-c615-7fd38a956e49.png)

## トレース情報付きでStreamlitにする

「invoke_agent」APIには「enableTrace」というパラメーターがあり、「True」にすることで、マネジメントコンソールでテスト実行した際の右側のエリアのようにトレース情報を取得可能です。

せっかくなので、StreamlitでGUIをつけたアプリにしました。

:::note

<details><summary>Streamlitのソースコードはこちら（折りたたみ表示）</summary>

```python
# Pyhton外部モジュールのインポート
import uuid

import boto3
import streamlit as st

# タイトル
st.title("Bedrock Code Interpreter")

# Bedrock Agent Runtime クライアント
if "client" not in st.session_state:
    st.session_state.client = boto3.client("bedrock-agent-runtime")
client = st.session_state.client

# セッションID
if "session_id" not in st.session_state:
    st.session_state.session_id = str(uuid.uuid4())
session_id = st.session_state.session_id

# メッセージ
if "messages" not in st.session_state:
    st.session_state.messages = []
messages = st.session_state.messages

# ファイルの添付
uploaded_file = st.file_uploader("files", accept_multiple_files=True)


# アップロードファイルをCode Interpreterのフォーマットに変換
def get_files():
    files = []
    for file in uploaded_file:
        files.append(
            {
                "name": file.name,
                "source": {
                    "byteContent": {"data": file.getvalue(), "mediaType": file.type},
                    "sourceType": "BYTE_CONTENT",
                },
                "useCase": "CODE_INTERPRETER",
            }
        )
    return files


# チャット入力欄を定義
if prompt := st.chat_input("何でも聞いてください。"):
    # ユーザーの入力をメッセージに追加
    messages.append({"role": "human", "text": prompt})

    # ユーザーの入力を画面表示
    with st.chat_message("user"):
        st.markdown(prompt)

    response = client.invoke_agent(
        agentId="ENGSPYXEUI",
        agentAliasId="TSTALIASID",
        sessionId=session_id,
        enableTrace=True,
        inputText=prompt,
        sessionState={"files": get_files()},
    )

    with st.chat_message("assistant"):
        for event in response.get("completion"):
            if "trace" in event:
                if "orchestrationTrace" in event["trace"]["trace"]:
                    orchestrationTrace = event["trace"]["trace"]["orchestrationTrace"]

                    if "modelInvocationInput" in orchestrationTrace:
                        with st.expander("modelInvocationInput", expanded=False):
                            st.write(orchestrationTrace)

                    if "rationale" in orchestrationTrace:
                        with st.expander("rationale（根拠）", expanded=True):
                            st.write(orchestrationTrace)

                    if "invocationInput" in orchestrationTrace:
                        if (
                            "codeInterpreterInvocationInput"
                            in orchestrationTrace["invocationInput"]
                            and "code"
                            in orchestrationTrace["invocationInput"][
                                "codeInterpreterInvocationInput"
                            ]
                        ):
                            code = orchestrationTrace["invocationInput"][
                                "codeInterpreterInvocationInput"
                            ]["code"]

                            with st.expander("code", expanded=False):
                                st.write(orchestrationTrace)

                            with st.expander("Python Code", expanded=True):
                                st.markdown(f"```\n{code}\n```")
                        else:
                            with st.expander("invocationInput", expanded=False):
                                st.write(orchestrationTrace)

                    if "observation" in orchestrationTrace:
                        with st.expander("observation（観察）", expanded=True):
                            st.write(orchestrationTrace)

            if "files" in event:
                files = event["files"]["files"]
                for file in files:

                    with open(file["name"], mode="wb") as f:
                        f.write(file["bytes"])

                    st.image(file["bytes"], caption=file["name"])

            if "chunk" in event:
                chunk = event["chunk"]
                answer = chunk["bytes"].decode()

                st.write(answer)
                messages.append({"role": "assistant", "text": answer})
```

</details>

:::

とりあえず動きを先に見てみましょう。
使用しているモデルはClaude 3 Sonnetです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/B6Phju97ZKQ?si=sSlyX45vX4PtSesE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


トレースを出力しながら、画像と最終回答を生成する様子が確認できました。
トレースの内容を追いかけてみましょう。

:::note
トレースにも複数種類ありますが、今回は「OrchestrationTrace」のみを対象とします。

詳細は[ドキュメント](https://docs.aws.amazon.com/bedrock/latest/userguide/trace-events.html)をご確認ください。
:::

* modelInvocationInput
    生成AIに渡すインプット情報

* rationale
    インプットに対して、エージェント（生成AI）が考えた結果
    「コードを書いてみましょう」と考えたので、次のステップでコードを生成します。

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/851e85fd-c44f-b21a-2d00-45e2779dbd19.png)

* codeInterpreterInvocationInput
    コードを生成する部分
    ここにPythonのコードが含まれます
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/ffda7af9-17f8-dad6-8a42-df4dff5a9699.png)

    他のプロンプトに混ざってではなく、コード単体で返却される作りのようです。そのため、この部分を抽出することで、コードを流用することも可能です。

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/3ce99bc9-ba88-e3aa-42ca-8928799d9859.png)

    :::note
    頑張ればClaudeのArtifactsみたいなことができるかも？？
  :::

* observation
    これまでの手順を受けて、ユーザーのリクエストに答えられたかを確認します

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/e8dc28ec-73ab-914c-5cb6-4d1f12395c92.png)

無事、画像も生成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/9b112d8c-09b5-980e-a811-73ad8e08cece.png)

このあと、再度modelInvocationInput、rationale、observationと一巡した後、最終回答が出力されました。
実行するたびに、処理の流れは変わるようでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/c6b4e710-5752-7c68-6f93-44990fbbeaeb.png)


使い道がすごく広がりそうです！