【速報】LangChain-AWS v0.2.14 で Bedrock インラインエージェントが利用可能になりました 🎉

やっほーーい！ 

皆さん、こんにちは！ LangChain-AWS の最新バージョン v0.2.14 がリリースされました！

https://github.com/langchain-ai/langchain-aws/releases/tag/v0.2.14

今回のアップデートの目玉は、**Bedrock インラインエージェント** のサポートです。🚀

Bedrockエージェントのインラインエージェントは、`InvokeInlineAgent API`を使用して、実行時に動的に設定・実行できるエージェントです。

### インラインエージェントのメリット

インラインエージェントの最大のメリットは、**柔軟性の高さ** です。

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

* **実行時の動的な設定**:  基盤モデル、指示、アクション グループ、ガードレール、ナレッジベースなどを、エージェントを呼び出すタイミングで指定できます。これにより、事前にエージェントの能力を固定的に定義する必要がなくなり、より柔軟なエージェント設計が可能になります。
* **迅速な実験**:  異なる構成でのエージェント機能の試行錯誤や、ツール群の動的なアップデートを、個別のエージェントを都度作成することなく実現できます。プロトタイピングが容易になり、迅速な開発サイクルを回せるようになるでしょう。
* **特定タスクへの特化**:  特定タスクを実行するために、その場ですぐにエージェントを構成し、実行できます。毎回エージェントを準備する手間が省け、必要な時に必要な機能だけを持つエージェントを動的に利用できます。
* **シンプルなタスク**:  簡単な質問応答や、コードインタープリターを使った簡単な処理などを、その場で作って実行できます。

### 実装例

では、実際にインラインエージェントを LangChain-AWS で実装する例を見ていきましょう。

[サンプルノートブック](https://github.com/langchain-ai/langchain-aws/blob/main/samples/agents/inline_agent_runnable_roc.ipynb)が公開されているので、これをベースに改造しました。

:::note
今回紹介するもの以外に、コードインタープリターを使用するものや、LangGraphで使うものもあります！
:::

#### ツール定義

まずは、エージェントが利用するツールを定義します。今回は、以下の2つのツールを定義しました。

`@tool` デコレータを使って、`get_asset_value` (資産価値取得ツール) と `get_mortgage_rate` (住宅ローン金利取得ツール) の2つのツールを定義しています。それぞれのツールは、引数として `asset_holder_id` (所有者ID) や `asset_value` (資産価値) を受け取り、固定の文字列を返します。


```python
from langchain_core.tools import tool


# 所有者IDの資産価値を取得するツール
@tool("AssetDetail::getAssetValue")
def get_asset_value(asset_holder_id: str) -> str:
    """
    Get the asset value for an owner id

    Args:
        asset_holder_id: The asset holder id

    Returns:
        The asset value for the given asset holder
    """

    return f"The total asset value for {asset_holder_id} is 100K"


# 資産価値に基づいて住宅ローン金利を取得するツール
@tool("AssetDetail::getMortgageRate")
def get_mortgage_rate(asset_holder_id: str, asset_value: str) -> str:
    """
    Get the mortgage rate based on asset value

    Args:
        asset_holder_id: The asset holder id
        asset_value: The value of the asset

    Returns:
        The interest rate for the asset holder and asset value
    """
    return f"The mortgage rate for {asset_holder_id} with asset value of {asset_value} is 8.87%"


tools = [get_asset_value, get_mortgage_rate]
```

#### インラインエージェント Runnable 定義

次に、インラインエージェントの Runnable を定義します。`inline_agent_config` に設定を記述します。

`BedrockInlineAgentsRunnable.create` メソッドで Runnable を作成します。`inline_agent_config` では、基盤モデル (`foundation_model`)、指示 (`instructions`)、利用可能なツール (`tools`) などを設定しています。

ここでは、基盤モデルとして `us.anthropic.claude-3-haiku-20240307-v1:0` (Anthropic Claude 3 Haiku) を指定し、指示として「あなたは、質問に勇敢に答えるフレンドリーで明るいアシスタントです。」というシンプルなものを設定しています。

```python
from langchain_aws.agents import BedrockInlineAgentsRunnable

foundation_model = "us.anthropic.claude-3-haiku-20240307-v1:0"

# あなたは、質問に勇敢に答えるフレンドリーで明るいアシスタントです。
instructions = (
    "You are a friendly and cheerful assistant that answers questions valiantly."
)

inline_agent_config = {
    "foundation_model": foundation_model,
    "instruction": instructions,
    "tools": tools,
    "enable_trace": False,
}

runnable = BedrockInlineAgentsRunnable.create(
    region_name="us-west-2", inline_agent_config=inline_agent_config
)
```

#### Return Control 関数

ツール呼び出し後の処理を行う `tool_calling` 関数を定義します。

この関数は、インラインエージェントからの出力を解析し、ツール呼び出し (`tool_calls`) が含まれている場合に、対応するツールを実行します。そして、実行結果を `session_state` に格納し、次のエージェント呼び出しに備えます。


```python
import json


def tool_calling(output):
    """
    インライン エージェントからの出力を処理し、次の呼び出しのセッション状態を準備します。

    Args:
        output: ツール呼び出しと追加情報を含むインライン エージェントからの出力

    Returns:
        session_state: Return Controlの実行結果を含むセッション状態
    """

    roc_log = json.loads(output.additional_kwargs["roc_log"])

    invocation_id = roc_log["returnControl"]["invocationId"]
    session_state = {
        "invocationId": invocation_id,
        "returnControlInvocationResults": [],
    }

    for tool_call in output.tool_calls:
        selected_tool = {tool.name: tool for tool in tools}[tool_call["name"]]
        tool_msg = selected_tool.invoke(tool_call)

        action_group, function = tool_call["name"].split("::")
        session_state["returnControlInvocationResults"].append(
            {
                "functionResult": {
                    "actionGroup": action_group,
                    "function": function,
                    "responseBody": {
                        "TEXT": {
                            "body": json.dumps(tool_msg.content, ensure_ascii=False)
                        }
                    },
                }
            }
        )

    return session_state
```

#### エージェント呼び出し

最後に、Bedrock インラインエージェントを呼び出すコードです。

`while` ループでエージェントとの対話を繰り返します。初回呼び出し時は `session_state` は `None` なので、ユーザーメッセージ (`user_message`) と `session_id` を指定して `runnable.invoke` を実行します。

2回目以降の呼び出し時は、前回の `tool_calling` 関数で生成した `session_state` を `inline_session_state` として指定することで、会話のコンテキストを引き継ぎます。

エージェントからの応答 (`output`) に `tool_calls` が含まれている場合は、`tool_calling` 関数でツールを実行し、`session_state` を更新します。`tool_calls` が空になったら、エージェントとの対話は終了です。

```python
import uuid

from langchain_core.messages import HumanMessage

session_id = str(uuid.uuid4())
user_message = HumanMessage(content="ID AVC-1234の住宅ローン金利はいくらですか")

session_state = None
output = None

while True:

    # Invoke the inlineAgent
    if session_state is None:
        output = runnable.invoke(
            [user_message],
            inline_agent_config={
                "session_id": session_id,
            },
        )
    else:
        output = runnable.invoke(
            [HumanMessage("")],
            inline_agent_config={
                "inline_session_state": session_state,
                "session_id": session_id,
            },
        )
    print(output)

    if len(output.tool_calls) == 0:
        break

    session_state = tool_calling(output)

print("final answer")
print("-----")
print(output.content)
```

#### 実行結果

上記のコードを実行すると、以下のような結果が得られます。

```text:実行結果
content='' additional_kwargs={'session_id': '12bd96a0-6dfd-413f-a1f3-c27096f342b6', 'trace_log': '[]', 'roc_log': '{"returnControl": {"invocationId": "299c827f-9d6e-4c90-821f-c4c32579320c", "invocationInputs": [{"functionInvocationInput": {"actionGroup": "AssetDetail", "actionInvocationType": "RESULT", "agentId": "INLINE_AGENT", "function": "getAssetValue", "parameters": [{"name": "asset_holder_id", "type": "string", "value": "AVC-1234"}]}}]}}'} response_metadata={} tool_calls=[{'name': 'AssetDetail::getAssetValue', 'args': {'asset_holder_id': 'AVC-1234'}, 'id': 'b0dd6b5c-40f2-4e22-a2f6-0ca12f4f0b74', 'type': 'tool_call'}]
content='' additional_kwargs={'session_id': '12bd96a0-6dfd-413f-a1f3-c27096f342b6', 'trace_log': '[]', 'roc_log': '{"returnControl": {"invocationId": "d3c88837-2b3f-4ea0-b32c-e9a2288f87d2", "invocationInputs": [{"functionInvocationInput": {"actionGroup": "AssetDetail", "actionInvocationType": "RESULT", "agentId": "INLINE_AGENT", "function": "getMortgageRate", "parameters": [{"name": "asset_value", "type": "string", "value": "100000"}, {"name": "asset_holder_id", "type": "string", "value": "AVC-1234"}]}}]}}'} response_metadata={} tool_calls=[{'name': 'AssetDetail::getMortgageRate', 'args': {'asset_value': '100000', 'asset_holder_id': 'AVC-1234'}, 'id': 'fa8f5f73-3702-4e5e-9be7-296a62dc2b32', 'type': 'tool_call'}]
content='ID AVC-1234の住宅ローン金利は8.87%です。' additional_kwargs={'session_id': '12bd96a0-6dfd-413f-a1f3-c27096f342b6', 'trace_log': '[]'} response_metadata={}
final answer
-----
ID AVC-1234の住宅ローン金利は8.87%です。
```

ログを見ると、以下の順番で処理されていることがわかります。

1. `AssetDetail::getAssetValue` ツールの実行が要求される
1. `getAssetValue`ツールの実行結果をエージェントに送信する
1. `AssetDetail::getMortgageRate` ツールの実行が要求される
1. `getAssetValue`ツールの実行結果をエージェントに送信する
1. ユーザーの質問「ID AVC-1234の住宅ローン金利はいくらですか」に対して、「ID AVC-1234の住宅ローン金利は8.87%です。」という回答が得られる

### まとめ

LangChain-AWS v0.2.14 で導入された Bedrock インラインエージェントは、Amazon Bedrock エージェントの柔軟性を大幅に向上させる強力な機能です。

* **動的なエージェント設定**
* **迅速なプロトタイピング**
* **特定タスクへの特化**

など、様々なメリットがあり、今後の LangChain-AWS を利用した開発において、重要な役割を果たすことが期待されます。

ぜひ、皆さんも Bedrock インラインエージェントを試してみてください！