6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BedrockとOpenAI Agents SDKとAgentCoreを組み合わせて使ってみよう

Posted at

AIエージェント構築&運用 Advent Calendar 2025のシリーズ1の24日目の投稿です

アドベントカレンダーに申し込んだものの、ネタがなく、他の人の投稿を見ていると、意外とOpenAIのAgents SDKに関するものがないことに気づきまして、これをBedrock AgentCoreで使うと特徴が出るかな、とおもってやってみましたの巻

BedrockとOpenAI Agents SDKを組み合わせて使う

BedrockではOpenAIのモデルが2つ提供されています。

  • gpt-oss-20b
  • gpt-oss-120b

BedrockはInvokeModel APIConverse APIという2つのAPIで呼び出します。OpenAIモデルについてはこれらに加え、OpenAI社提供のAPIと同じ形式のChat completions APIResponses APIでも呼び出すことが可能です。

Responses APIについては今月サポートが開始されました🎉🎉🎉

Amazon Bedrock が OpenAI の Responses API をサポートするようになりました
https://aws.amazon.com/jp/about-aws/whats-new/2025/12/amazon-bedrock-responses-api-from-openai/

まずは、BedrockのResponses APIを呼び出し、OpenAI Agents SDKを使用します。

BedrockでAPIキーを生成する

AWSマネジメントコンソールでBedrockの管理画面を開き、左のメニューの「API キー」を選択します。
「短期APIキー」タブにある「短期APIキーを生成」をクリックします。

表示されるAPIキーをメモします。

短期APIキーの有効期限は発行から12時間です。

APIキーはリージョンに紐づいて発行されます。バージニア北部リージョンで発行したAPIキーでは東京リージョンのBedrockにアクセスすることはできません。

Pythonのプロジェクトを作成する

Pythonのプロジェクトを作成します。

uv init

続いて、OpenAI Agents SDKをインストールします。

uv add openai-agents

Pythonコードを実装する

main.pyに実装していくのですが、exampleとして用意されているhello_world.pyをベースとします。

main.py(hello_world.pyから引用)
import asyncio

from agents import Agent, Runner


async def main():
    agent = Agent(
        name="Assistant",
        instructions="You only respond in haikus.",
    )

    result = await Runner.run(
        agent, "プログラミングにおける再帰について教えてください。"
    )
    print(result.final_output)


if __name__ == "__main__":
    asyncio.run(main())

このままではOpenAI社のAPIにアクセスするので、これをBedrock宛に変更しましょう。

まず、インポート部分にいくつか追加します。

  import asyncio

- from agents import Agent, Runner
+ from agents import Agent, Runner, set_default_openai_api, set_default_openai_client
+ from openai import AsyncOpenAI

追加したset_default_openai_clientset_default_openai_apiを使ってデフォルトのOpenAIクライアントと使用するAPIを指定します。

  • base_urlhttps://bedrock-mantle.{リージョン}.api.aws/v1
  • set_default_openai_apiで使用するAPIのタイプをResponses APIに(デフォルト値はchat_completions
OPENAI_API_KEY = "bedrock-api-key-*****"  # Bedrock API Keyで発行したAPIキー

set_default_openai_client(
    client=AsyncOpenAI(
        base_url="https://bedrock-mantle.us-east-1.api.aws/v1",
        api_key=OPENAI_API_KEY,
    )
)
set_default_openai_api("responses")

最後に、呼び出すモデル名を指定します。

    agent = Agent(
        name="Assistant",
+       model="openai.gpt-oss-120b",
        instructions="You only respond in haikus.",
    )

Responses APIを使用するときだけ、モデル名の表記が違うのでご注意ください。

  • Responses API
    • openai.gpt-oss-120b
    • openai.gpt-oss-20b
  • InvokeModel API、Converse API、Chat completions API
    • openai.gpt-oss-120b-1:0
    • openai.gpt-oss-20b-1:0

どういう意図なんでしょうね

これで完成です。

main.py
main.py
import asyncio

from agents import Agent, Runner, set_default_openai_api, set_default_openai_client
from openai import AsyncOpenAI

OPENAI_API_KEY = "bedrock-api-key-*****"  # Bedrock API Keyで発行したAPIキー

set_default_openai_client(
    client=AsyncOpenAI(
        base_url="https://bedrock-mantle.us-east-1.api.aws/v1",
        api_key=OPENAI_API_KEY,
    )
)
set_default_openai_api("responses")


async def main():
    agent = Agent(
        name="Assistant",
        model="openai.gpt-oss-120b",
        instructions="You only respond in haikus.",
    )

    result = await Runner.run(
        agent, "プログラミングにおける再帰について教えてください。"
    )
    print(result.final_output)


if __name__ == "__main__":
    asyncio.run(main())

実行してみます。

uv run main.py
回答
## プログラミングにおける再帰 (Recursion) とは?

**再帰** とは、**関数(または手続き)が自分自身を呼び出すこと**です。  
「自分自身を呼び出す」ことで、問題を **「より小さな同じ形の問題」** に分割し、最終的に **最も簡単な状態(=終了条件)** まで到達させてから答えを組み立てていく手法です。

---

## 1. 再帰の基本構造

```text
function recursive(args):
    if 終了条件 (base case) が満たされたら
        → 直接答えを返す
    else
        → 問題を「小さく」した引数で自分自身を呼び出す
        → その戻り値を使って最終結果を作る
```

### 重要なポイント
| 要素 | 役割 | 例 |
|------|------|----|
| **終了条件 (base case)** | 再帰が止まる条件。無限に呼び出し続けないように必ず必要。 | `n == 0` のとき 0 を返す |
| **縮小 (reduction)** | 呼び出しごとに問題サイズを必ず **減らす**(あるいは変化させる)。 | `n-1``n/2`、配列の先頭を除く 等 |
| **スタック** | 各呼び出しの局所変数や戻りアドレスは **コールスタック** に積まれる。スタックが溢れると **StackOverflowError** が起きる。 | 再帰深さが 10⁶ 以上になると危険 |

---

## 2. 代表的な例

### 2‑1. 階乗 (Factorial)

```python
def factorial(n: int) -> int:
    # 終了条件
    if n == 0:
        return 1
    # 再帰呼び出し + 縮小
    return n * factorial(n - 1)

print(factorial(5))   # 120
```

- `5! = 5 × 4!``4!``factorial(4)` が計算  
- `0! = 1`**base case**
---

### 2‑2. フィボナッチ数列

**単純再帰(指数時間)**  
```python
def fib(n: int) -> int:
    if n <= 1:               # base case
        return n
    return fib(n-1) + fib(n-2)

print([fib(i) for i in range(10)])  # [0,1,1,2,3,5,8,13,21,34]
```

**メモ化(キャッシュ)で高速化**  
```python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib_memo(n: int) -> int:
    if n <= 1:
        return n
    return fib_memo(n-1) + fib_memo(n-2)

print(fib_memo(50))   # 12586269025
```

- キャッシュを使うと **線形時間 O(n)** に改善できる。

---

### 2‑3. 二分探索 (Binary Search)

```cpp
int binarySearch(const vector<int>& a, int left, int right, int target) {
    if (left > right) return -1;                // base case: not found
    int mid = left + (right - left) / 2;
    if (a[mid] == target) return mid;           // found
    if (a[mid] > target)
        return binarySearch(a, left, mid - 1, target);   // left half
    else
        return binarySearch(a, mid + 1, right, target); // right half
}
```

- 配列を **半分** に分割し続けるので、再帰深さは `log₂(N)`
---

## 3. 再帰のメリットとデメリット

| メリット | デメリット |
|----------|------------|
| **コードがシンプル** - 同じ構造を繰り返す問題は自然に表現できる | **スタック消費** - 深すぎると `StackOverflow` |
| **問題分割が明確** - Divide‑and‑Conquer がそのまま書ける | **遅いことがある** - 重複計算や非最適化が起きやすい |
| **数学的な証明がしやすい**(帰納法と相性が良い) | **デバッグが難しい** - 呼び出し階層が多くなると追いにくい |
| **関数型言語と相性が良い**(副作用が少ない) | **尾再帰最適化が無い言語では非効率** |

---

## 4. 尾再帰 (Tail Recursion) と TCO (Tail Call Optimization)

### 4‑1. 尾再帰とは?

**最後の操作が再帰呼び出し** である形のこと。  
例: 階乗の「累積結果」を引数に持たせる形

```python
def fact_tail(n: int, acc: int = 1) -> int:
    if n == 0:
        return acc          # ここが最終結果、再帰は呼ばれない
    return fact_tail(n-1, acc * n)
```

### 4‑2. TCO が有効な言語

- **Scheme, Haskell, OCaml, F#, Rust (some cases), C# (.NET 6+), Scala**
- **JavaScript** (ES2022 の一部実装で最適化が入っているが、保証はない)
- **C** / **C++** ではコンパイラの最適化オプション `-O2` 以上で「tail–call」最適化が入ることがあるが、保証は少ない。

**TCO が無い場合** はスタックを消費し続けるので、**ループに書き換える****手動でスタックをシミュレート**`while` ループ + 変数)する必要があります。

---

## 5. 再帰 → 反復 (Iteration) への変換例

### 階乗のループ版(C++)

```cpp
long long factorial_iter(int n) {
    long long acc = 1;
    while (n > 0) {
        acc *= n;
        --n;
    }
    return acc;
}
```

### フィボナッチのループ版(Python)

```python
def fib_iter(n: int) -> int:
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a
```

- **再帰的に書くと直感的** だけど、**反復はスタックを使わない**ので安全。

---

## 6. 再帰のデバッグテクニック

| 手法 | 説明 |
|------|------|
| **プリントデバッグ** | `print(f"call n={n}")` で入出力を追う(深さが大きいと膨大になる) |
| **スタックトレース** | 例外発生時に表示されるコールスタックを読む |
| **IDE のデバッガ** | ブレークポイントを再帰呼び出し行に置くと「再帰的にステップイン」できる |
| **再帰深さ制限** | Python なら `sys.setrecursionlimit()`、C++ なら `-Wstack-usage=` で上限を把握 |
| **メモ化やキャッシュ** | 同じ引数で何度も計算しないようにすると **実行回数が減り** デバッグが楽になる |

---

## 7. 再帰が特に有効な典型問題

| カテゴリ | 例 | 解法のヒント |
|----------|----|--------------|
| **木構造・グラフ** | 二分木の走査 (preorder/inorder/postorder) | ノードを「左子」「右子」へ再帰 |
| **分割統治** | クイックソート、マージソート | 配列を半分に分割し再帰的にソート |
| **組み合わせ列挙** | すべての列挙(subset, permutation, combination) | 現在の「選択/不選択」状態を引数で渡す |
| **DP(メモ化再帰)** | ナップサック、最長共通部分列 | 再帰で状態遷移を書き、`memo` で結果を保存 |
| **文字列処理** | パリンドローム判定、正規表現の簡易実装 | 文字列の先頭・末尾を取り除いて再帰 |

---

## 8. まとめ:再帰を使うかどうかの判断基準

| 条件 | 再帰が向いている | 再帰が向いていない(ループが好ましい) |
|------|------------------|---------------------------------------|
| **問題が自己相似**(同じ形が繰り返す) | ✅ | ❌ |
| **深さが予測でき、**`log n``n` 程度 | ✅(スタックが安全) | ❌ |
| **深さが `10⁴`‑`10⁶` 以上になる可能性** | ❌(スタックオーバーフロー) | ✅(ループ化) |
| **言語が尾再帰最適化を保証** | ✅ | ❌ |
| **計算量が指数的で、****メモ化が有効** | ✅(DP + 再帰) | ❌(手実装のループでも可) |
| **可読性や実装の簡潔さが重要** | ✅ | ❌ |

---

## 参考リソース(日本語)

| タイトル | URL |
|----------|-----|
| 「アルゴリズム入門」 第4章 再帰と分割統治 | https://algorithm-bootcamp.com/recursion |
| Python 公式ドキュメント – 再帰とスタック | https://docs.python.org/ja/3/tutorial/controlflow.html#recursive-functions |
| 「プログラミングコンテストで学ぶアルゴリズム」 – 再帰 & DP | https://atcoder.jp/contests/arc001/editorial/ |
| 「Tail Call Optimization」 – Wikipedia (日本語) | https://ja.wikipedia.org/wiki/末尾再帰最適化 |

---

**最後に**  
再帰は「**問題を小さく分割して同じ手続きを繰り返す**」という自然な考え方です。  
*「この問題は、最小単位まで細かくしたらどうなる?」* を常に意識すれば、再帰的な解法が浮かんできます。  
ただし **スタック使用量****重複計算** に注意し、必要に応じて **メモ化****ループ化** を併用すれば、効率的で読みやすいコードが書けるようになります。  

質問や実際に書きたいコードがあれば、遠慮なく聞いてください!

この時点の実装では、以下のようなエラーメッセージが複数表示されますが、後の手順で解消します。

[non-fatal] Tracing client error 401: {
  "error": {
    "message": "Incorrect API key provided: bedrock-*****. You can find your API key at https://platform.openai.com/account/api-keys.",
    "type": "invalid_request_error",
    "param": null,
    "code": "invalid_api_key"
  }
}

Bedrock AgentCoreを追加する

それでは更にAgentCoreを追加します。追加する機能は以下の2つです。(Runtimeのデプロイはちょっと力尽きたのでまたの機会に。。)

  • AgentCore Identity
  • AgentCore Runtime
  • AgentCore Observability

OPENAI_API_KEYをAgentCore Identityで管理

先程の実装、OPENAI_API_KEYがソースコードに直書きされていて、あまり美しくありません。APIキーの管理に「AgentCore Identity」を使います。

AWSマネジメントコンソールでBedrock AgentCoreの管理画面を開き、左のメニューの「アイデンティティ」を選択します。
「OAuthクライアント/APIキーを追加」ボタンを押したうえで「APIキーを追加」をクリックします。

APIキーを追加ダイアログで「名前」「APIキー」を入力します。ここの名前は後で使用します。

AWS側はこれで完了です。Pythonのプログラムを修正していきます。まずは、Bedrock AgentCore SDKを追加します。

uv add bedrock-agentcore

Bedrock AgentCore SDKを使って、AgentCore IdentityからAPIキーを取得します。
requires_api_keyというデコレーターが用意されており、これを関数に付与することで、AgentCore IdentityからAPIキーを取得することができます。

provider_nameは、AgentCore IdentityにAPIキーを登録する際に指定した名前です。

  import asyncio
  
  from agents import Agent, Runner, set_default_openai_api, set_default_openai_client
+ from bedrock_agentcore.identity import requires_api_key
  from openai import AsyncOpenAI


+ @requires_api_key(provider_name="AWS_BEARER_TOKEN_BEDROCK")
+ async def need_api_key(*, api_key: str):
+     print("received api key for async func")
+     return api_key

APIキーが必要な部分で、作成した関数を呼び出します。(awaitをつけて呼び出す関係上、main()関数の中に移動しました)

- set_default_openai_client(
-     client=AsyncOpenAI(
-         base_url="https://bedrock-mantle.us-east-1.api.aws/v1",
-         api_key=OPENAI_API_KEY,
-     )
- )
- set_default_openai_api("responses")
  
  
  async def main():
+     set_default_openai_client(
+         client=AsyncOpenAI(
+             base_url="https://bedrock-mantle.us-east-1.api.aws/v1",
+             api_key=await need_api_key(),    # APIキーを取得してセット
+         )
+     )
+     set_default_openai_api("responses")

      agent = Agent(
          name="Assistant",
          model="openai.gpt-oss-120b",
          instructions="You only respond in haikus.",
      )

これでAgentCore Identityの対応は完了です。

RuntimeとObservabilityを追加

最後にRuntimeとObservabilityを追加します。

まず、Bedrock AgentCore SDKを使ってRuntimeに合った構成へ変更します。

- import asyncio

  from agents import Agent, Runner, set_default_openai_api, set_default_openai_client
  from bedrock_agentcore.identity import requires_api_key
+ from bedrock_agentcore.runtime import BedrockAgentCoreApp
  from openai import AsyncOpenAI

中略

+ app = BedrockAgentCoreApp()

中略

- async def main():
+ @app.entrypoint
+ async def invoke(payload: dict):
+     prompt = payload.get("prompt")

中略

      result = await Runner.run(
-         agent, "プログラミングにおける再帰について教えてください。"
+         agent, prompt
      )
-     print(result.final_output)
+     return result.final_output

中略

  if __name__ == "__main__":
-     asyncio.run(main())
+     app.run()

これでWebサーバーとして起動するようになります。

サーバーとして起動
uv run main.py

別のターミナルからリクエストを送信します。

クライアントからリクエストを送信
curl -XPOST http://localhost:8080/invocations \
  -d '{"prompt": "プログラミングにおける再帰について教えてください。"}'

では、Observabilityを追加しましょう。

こちらの「CloudWatchの設定変更」を参考に、CloudWatchのトランザクション検索を有効にしてください。

OpenAI Agents SDKのテレメトリーデータを収集するライブラリーとAWSに送信するライブラリーです。

uv add aws-opentelemetry-distro openinference-instrumentation-openai-agents

OpenAI Agents SDKのOpenTelemetry計装を有効にし、INFOレベルのログを出力する設定を行います。

+ import logging
  
  from agents import Agent, Runner, set_default_openai_api, set_default_openai_client
  from bedrock_agentcore.identity import requires_api_key
  from bedrock_agentcore.runtime import BedrockAgentCoreApp
  from openai import AsyncOpenAI
+ from openinference.instrumentation.openai_agents import OpenAIAgentsInstrumentor
  
+ OpenAIAgentsInstrumentor().instrument()
  
+ logging.basicConfig(level=logging.INFO)
+ logger = logging.getLogger(__name__)

その他の設定は環境変数で行います。サーバー側の起動時に色々環境変数をセットします。

CloudWatchのロググループとストリームは事前に作成しておきます

export AGENT_OBSERVABILITY_ENABLED=true

export OTEL_PYTHON_DISTRO=aws_distro
export OTEL_PYTHON_CONFIGURATOR=aws_configurator

export SERVICE_NAME=hello-world-agent
export LOG_GROUP=/aws/bedrock-agentcore/local
export LOG_STREAM=runtime-logs

export OTEL_RESOURCE_ATTRIBUTES="service.name=${SERVICE_NAME},aws.log.group.names=${LOG_GROUP}"
export OTEL_EXPORTER_OTLP_LOGS_HEADERS="x-aws-log-group=${LOG_GROUP},x-aws-log-stream=${LOG_STREAM},x-aws-metric-namespace=bedrock-agentcore"

export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
export OTEL_TRACES_EXPORTER=otlp

uv run opentelemetry-instrument python ./main.py

めでたく、トレースが確認できました。


Agents SDKの使い方はあまりわからないままなので、引き続きやってみようと思います

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?