1
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?

どうもこんにちは。

今回は、Ruby製のLLMクライアントライブラリである RubyLLM を、Amazon Bedrock AgentCore Runtime 上にデプロイして動かしてみます。

Amazon Bedrock AgentCore Runtime は、Strands Agents、LangGraph、CrewAI などのエージェントフレームワークをサーバーレスにホストできる実行環境です。

公式の導線としては Python SDK や AgentCore CLI がかなり整っていますが、AgentCore Runtime 自体は HTTP プロトコル契約に従ったコンテナも動かせます。

つまり、Ruby でもコンテナとして /invocations/ping を実装すれば、AgentCore Runtime に載せられるはずです。

ということで、今回は Rails や Sinatra を使わず、Rack + WEBrick + RubyLLM で最小構成の Runtime コンテナを作ってみました。

もうこの時点で「Rubyistっぽいこと言ってるぅ!!」と思いながら記事を書いてます。(全然そんなことないのに。)

この記事は誰向け?

この記事は以下のような方向けに執筆しています。

  • RubyLLM を Amazon Bedrock で使ってみたい方
  • Ruby 製のアプリケーションを Bedrock AgentCore Runtime に載せたい方
  • AgentCore Runtime の HTTP プロトコル契約を自前実装したい方
  • AWS CDK(Python)で AgentCore Runtime へコンテナをデプロイしたい方
  • Strands Agents 以外の実装を AgentCore Runtime に載せる検証をしたい方

デモコードの場所

今回の検証コードは以下のリポジトリに置いてあります。

この検証でやりたいこと

今回のゴールは次の3点です。

  • RubyLLM を使って Amazon Bedrock を呼び出す
  • Ruby アプリを AgentCore Runtime の HTTP コンテナとして動かす
  • AWS CDK で ECR、IAM Role、AgentCore Runtime をまとめて作成する

構成としては以下のようになります。

User
  |
  v
InvokeAgentRuntime
  |
  v
AgentCore Runtime
  |
  | HTTP /invocations
  v
Ruby container
  |
  | RubyLLM
  v
Amazon Bedrock

ぶっちゃけ、AgentCore Runtime から見ると、コンテナ内の実装がなんの言語で実装されているのかは関係ありません。重要なのは、コンテナが AgentCore Runtime の HTTP プロトコル契約を満たしていることです。

Bedrock AgentCore runtime上にデプロイされるエージェントは、AWSネイティブなStrands Agentsがよく使用されているイメージがありますが、HTTP プロトコル契約を満たしていればなんでも動きます。

前提

今回の検証では ap-northeast-1 にデプロイしています。

手元では以下を利用しました。

  • Ruby 3.4 container image
  • RubyLLM 1.16.0
  • AWS CDK CLI
  • AWS CDK v2
  • Docker
  • AWS CLI
  • CDK bootstrap 済みの AWS アカウント

相変わらず、CDKはPythonで書いてます!
TypeScriptの型宣言の文字数の分だけトークン数節約できるんじゃね?という安易な考えです。

また、Amazon Bedrock のモデルアクセスが有効化されている必要があります。

今回は、Claude Sonnet 4.6を使用しています。

jp.anthropic.claude-sonnet-4-6

Sonnet 4.6ならリリースされて日が経ってるからRubyLLMでもサポートされているよな?という意図で4.6にしてます。

実装の流れ

0. 作業プロジェクトの用意

mkdir ruby-llm-demo
cd ruby-llm-demo

最終的な構成は次のとおりです。

.
├── agent/
│   ├── .dockerignore
│   ├── Dockerfile
│   ├── Gemfile
│   ├── config.ru
│   └── ruby_llm_agent/
│       ├── app.rb
│       └── bedrock_config.rb
├── infra/
│   ├── __init__.py
│   └── ruby_llm_agentcore_stack.py
├── tests/
│   └── test_stack.py
├── app.py
├── cdk.json
├── pytest.ini
└── requirements.txt

agent/ が Runtime 上で動く Ruby アプリです。

infra/ が AgentCore Runtime、ECR、IAM Role を作る CDK コードです。

1. CDK プロジェクトの雛形を整える

1-1. 依存パッケージ

CDK 側の依存関係は以下です。

requirements.txt

aws-cdk-lib>=2.210.0
constructs>=10.0.0,<11.0.0
cdk-ecr-deployment>=4.0.0

cdk-ecr-deployment は、CDK がビルドした Docker image asset を、Runtime 用に作成した専用 ECR Repository へコピーするために使います。

Python 仮想環境を作成します。

python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt pytest

1-2. CDK エントリーポイント

app.py は通常の CDK エントリーポイントです。

app.py
#!/usr/bin/env python3
import os

import aws_cdk as cdk

from infra.ruby_llm_agentcore_stack import RubyLLMAgentCoreStack


app = cdk.App()

RubyLLMAgentCoreStack(
    app,
    "RubyLLMAgentCoreStack",
    env=cdk.Environment(
        account=os.environ.get("CDK_DEFAULT_ACCOUNT"),
        region=os.environ.get("CDK_DEFAULT_REGION", "us-east-1"),
    ),
)

app.synth()

このサンプルでは CDK_DEFAULT_REGION が未指定の場合は us-east-1 になります。

実際には ap-northeast-1 にデプロイしたので、以下のように指定して実行しています。

export CDK_DEFAULT_REGION=ap-northeast-1

1-3. CDK 実行設定

cdk.json
{
  "app": "python3 app.py",
  "context": {
    "@aws-cdk/core:newStyleStackSynthesis": true
  }
}

2. RubyLLM アプリを実装する

2-1. Ruby 側の依存関係

agent/Gemfile
source "https://rubygems.org"

ruby ">= 3.2"

gem "aws-sdk-core", "~> 3.0"
gem "rack", "~> 3.0"
gem "rackup", "~> 2.0"
gem "ruby_llm", "~> 1.16"
gem "webrick", "~> 1.8"

group :test do
  gem "rack-test", "~> 2.1"
  gem "rspec", "~> 3.13"
end

RubyのWebアプリケーションフレームワークである、Rails や Sinatra は使っていません。

AgentCore Runtime の HTTP プロトコル契約を満たすだけなら、Rack を使えば十分です。

2-2. Rack エントリーポイント

Rackのエントリーポイントを用意します。後々出てきますが、Dockerfileでこのエントリーポイントを呼び出しています。

agent/config.ru
# frozen_string_literal: true

require_relative "ruby_llm_agent/app"

run RubyLLMAgent::App.new

2-3. AgentCore Runtime 用の HTTP API

AgentCore Runtime の HTTP プロトコル契約では、主に以下の endpoint が必要です。

  • POST /invocations
  • GET /ping

今回の Rack アプリではこの2つを実装しています。

agent/ruby_llm_agent/app.rb
# frozen_string_literal: true

require "json"
require "logger"
require "rack"
require_relative "bedrock_config"

# AgentCore上ではログがCloudWatchへ転送されるため、バッファリングを避ける。
$stdout.sync = true
$stderr.sync = true

module RubyLLMAgent
  class App
    def initialize(agent: Agent.new, logger: Logger.new($stdout))
      @agent = agent
      @logger = logger
    end

    def call(env)
      request = Rack::Request.new(env)

      # AgentCore RuntimeのHTTP契約で必要なエンドポイントだけを実装する。
      case [request.request_method, request.path_info]
      when ["GET", "/ping"], ["POST", "/ping"]
        json_response(200, status: "Healthy")
      when ["POST", "/invocations"]
        invoke(request)
      else
        json_response(404, error: "not_found")
      end
    end

    private

    def invoke(request)
      payload = parse_json(request.body.read)
      @logger.info("agent_invocation_started")
      response = @agent.invoke(payload)
      @logger.info("agent_invocation_completed")
      json_response(200, response)
    rescue InvalidPayload => e
      json_response(400, error: e.message)
    rescue StandardError => e
      @logger.error(
        "agent_invocation_failed class=#{e.class} message=#{e.message} " \
        "backtrace=#{Array(e.backtrace).first(5).join(" | ")}"
      )
      # 原因切り分け時だけ、AgentCoreに500で丸められないよう200で例外内容を返す。
      status = ENV["DEBUG_RESPONSE_ERRORS"] == "1" ? 200 : 500
      json_response(status, error: "agent_invocation_failed", errorClass: e.class.name, detail: e.message)
    end

    def parse_json(raw_body)
      raise InvalidPayload, "request body must not be empty" if raw_body.nil? || raw_body.empty?

      JSON.parse(raw_body)
    rescue JSON::ParserError
      raise InvalidPayload, "request body must be valid JSON"
    end

    def json_response(status, body)
      [status, { "content-type" => "application/json" }, [JSON.generate(body)]]
    end
  end
end

Runtime の入力は次の形式とします。

{
  "prompt": "こんにちは!"
}

2-4. RubyLLM から Bedrock を呼ぶ

RubyLLM の Bedrock provider を使って、Amazon Bedrock の Claude を呼び出します。

agent/ruby_llm_agent/bedrock_config.rb
# frozen_string_literal: true

require "aws-sdk-core"
require "ruby_llm"
require "securerandom"
require "time"

module RubyLLMAgent
  class InvalidPayload < StandardError; end

  class Agent
    DEFAULT_MODEL_ID = "jp.anthropic.claude-sonnet-4-6"

    def initialize(model_id: ENV.fetch("BEDROCK_MODEL_ID", DEFAULT_MODEL_ID))
      @model_id = model_id
      @configured = false
    end

    def invoke(payload)
      prompt = payload["prompt"]
      raise InvalidPayload, "payload.prompt must be a non-empty string" unless prompt.is_a?(String) && !prompt.strip.empty?

      # RubyLLMのグローバル設定は初回呼び出し時にだけ行う。
      configure_ruby_llm
      request_id = SecureRandom.uuid
      started_at = monotonic_milliseconds

      # Bedrock inference profileはRubyLLMのモデルレジストリ未登録の場合がある。
      chat = RubyLLM.chat(
        model: @model_id,
        provider: :bedrock,
        assume_model_exists: true
      )
      answer = chat.ask(prompt.strip)

      {
        response: answer.content,
        evidence: {
          requestId: request_id,
          provider: "bedrock",
          modelId: @model_id,
          rubyLlmVersion: RubyLLM::VERSION,
          durationMs: monotonic_milliseconds - started_at
        }
      }
    end

    private

    def configure_ruby_llm
      return if @configured

      RubyLLM.configure do |config|
        config.bedrock_region = ENV.fetch("AWS_REGION", "us-east-1")
        config.bedrock_api_base = ENV["BEDROCK_API_BASE"] if present?(ENV["BEDROCK_API_BASE"])

        # ruby_llm 1.16.0にはbedrock_credential_providerがないため、解決済み認証情報を渡す。
        credentials = resolve_aws_credentials
        config.bedrock_api_key = credentials.access_key_id
        config.bedrock_secret_key = credentials.secret_access_key
        config.bedrock_session_token = credentials.session_token
      end
      @configured = true
    end

    def resolve_aws_credentials
      # AgentCore Runtime上では実行ロール由来の一時認証情報がここで解決される。
      provider = Aws::CredentialProviderChain.new.resolve
      raise Aws::Errors::MissingCredentialsError, "unable to resolve AWS credentials" unless provider

      provider.credentials
    end

    def present?(value)
      value && !value.empty?
    end

    def monotonic_milliseconds
      (Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).round
    end
  end
end

重要なのは次の2点です。

assume_model_exists: true

Bedrock の inference profile 形式のモデル ID は、RubyLLM のモデルレジストリに未登録の場合があります。

そのため、RubyLLM 側のモデル存在チェックを bypass しています。

もう1つは認証情報の渡し方です。

credentials = resolve_aws_credentials
config.bedrock_api_key = credentials.access_key_id
config.bedrock_secret_key = credentials.secret_access_key
config.bedrock_session_token = credentials.session_token

RubyLLM のドキュメント上は bedrock_credential_provider が紹介されていますが、RubyGems の ruby_llm 1.16.0 ではまだこの setter がありませんでした。

そのため、AWS SDK for Ruby の Aws::CredentialProviderChain で一時認証情報を解決し、RubyLLM には静的キー形式で渡しています。

AgentCore Runtime 上では、ここで Runtime 実行ロール由来の一時認証情報が解決されます。

3. Ruby アプリを Docker 化する

AgentCore Runtime では ARM64 コンテナが必要です。

agent/Dockerfile
FROM public.ecr.aws/docker/library/ruby:3.4

ENV APP_HOME=/app
ENV BUNDLE_WITHOUT=test
ENV PORT=8080
ENV RACK_ENV=production

WORKDIR ${APP_HOME}

COPY Gemfile ./
RUN bundle install

COPY config.ru ./
COPY ruby_llm_agent ./ruby_llm_agent

EXPOSE 8080

CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "--port", "8080", "config.ru"]

最初は ruby:3.4-slim を使って実装を進めていたのですが、jsonbigdecimalnative extension build でコンパイラが必要になったので、ruby:3.4を使っています。

ローカルビルドします。

docker build -t ruby-llm-agentcore-demo:local agent

ローカル起動します。

docker run --rm -p 18080:8080 ruby-llm-agentcore-demo:local

ヘルスチェックします。

curl -s http://localhost:18080/ping

レスポンス:

{"status":"Healthy"}

ローカルで /invocations まで成功させるには、Bedrock を呼べる AWS 認証情報が必要です。

curl -s http://localhost:18080/invocations \
  -H 'content-type: application/json' \
  -d '{"prompt":"こんにちは!"}'

ここまででうまくいけば、Ruby側での実装は終わりです。

4. ECR リポジトリとイメージ配布を実装する

CDK スタックでは、Runtime 用の専用 ECR Repository を作成します。

infra/ruby_llm_agentcore_stack.py
image_repository = ecr.Repository(
    self,
    "AgentImageRepository",
    repository_name="ruby-llm-agentcore-demo",
    image_scan_on_push=True,
    image_tag_mutability=ecr.TagMutability.IMMUTABLE,
    lifecycle_rules=[
        ecr.LifecycleRule(
            description="Keep the ten most recent agent images",
            max_image_count=10,
        )
    ],
    removal_policy=RemovalPolicy.DESTROY,
    empty_on_delete=True,
)

続いて、Docker image asset を ARM64 でビルドします。

image_asset = ecr_assets.DockerImageAsset(
    self,
    "AgentImageAsset",
    directory=str(Path(__file__).resolve().parents[1] / "agent"),
    platform=ecr_assets.Platform.LINUX_ARM64,
)

CDK の Docker image asset は、CDK bootstrap 用の ECR に push されます。

今回は Runtime 用に専用 ECR Repository を作っているため、cdk-ecr-deployment でイメージをコピーします。

image_tag = image_asset.asset_hash
runtime_image_uri = image_repository.repository_uri_for_tag(image_tag)

image_deployment = ecr_deployment.ECRDeployment(
    self,
    "DeployAgentImage",
    src=ecr_deployment.DockerImageName(image_asset.image_uri),
    dest=ecr_deployment.DockerImageName(runtime_image_uri),
)

5. Runtime 用 IAM Role を実装する

Runtime 実行ロールを作成します。

runtime_role = iam.Role(
    self,
    "AgentRuntimeRole",
    assumed_by=iam.ServicePrincipal(
        "bedrock-agentcore.amazonaws.com",
        conditions={
            "StringEquals": {"aws:SourceAccount": Aws.ACCOUNT_ID},
            "ArnLike": {
                "aws:SourceArn": (
                    f"arn:{Aws.PARTITION}:bedrock-agentcore:"
                    f"{Aws.REGION}:{Aws.ACCOUNT_ID}:*"
                )
            },
        },
    ),
    description="Execution role for the RubyLLM AgentCore demo runtime.",
)

このロールに以下の権限を付与します。

  • ECR からのイメージ取得
  • CloudWatch Logs への出力
  • Bedrock モデル呼び出し

Bedrock 呼び出し権限は以下です。

runtime_role.add_to_policy(
    iam.PolicyStatement(
        actions=[
            "bedrock:InvokeModel",
            "bedrock:InvokeModelWithResponseStream",
        ],
        resources=self._model_resource_arns(model_id),
    )
)

モデル ARN は次のようにしています。

def _model_resource_arns(self, model_id: str) -> list[str]:
    # 明示ARNが渡された場合は、そのモデルまたはinference profileだけに絞る。
    if model_id.startswith("arn:"):
        return [model_id]
    return [
        f"arn:{Aws.PARTITION}:bedrock:*::foundation-model/*",
        (
            f"arn:{Aws.PARTITION}:bedrock:{Aws.REGION}:{Aws.ACCOUNT_ID}:"
            "inference-profile/*"
        ),
    ]

foundation model と inference profile の両方を許可しています。

6. AgentCore Runtime を作成する

AgentCore Runtime を作成します。

runtime = bedrock_agentcore.CfnRuntime(
    self,
    "AgentRuntime",
    agent_runtime_name="RubyLLMAgentCoreDemoRuntime",
    description="RubyLLM agent hosted on Amazon Bedrock AgentCore Runtime.",
    agent_runtime_artifact=bedrock_agentcore.CfnRuntime.AgentRuntimeArtifactProperty(
        container_configuration=bedrock_agentcore.CfnRuntime.ContainerConfigurationProperty(
            container_uri=runtime_image_uri,
        )
    ),
    network_configuration=bedrock_agentcore.CfnRuntime.NetworkConfigurationProperty(
        network_mode="PUBLIC",
    ),
    protocol_configuration="HTTP",
    role_arn=runtime_role.role_arn,
    environment_variables={
        "AWS_REGION": self.region,
        "AWS_DEFAULT_REGION": self.region,
        "BEDROCK_MODEL_ID": model_id,
        "APP_TIMEZONE": "Asia/Tokyo",
    },
    lifecycle_configuration=bedrock_agentcore.CfnRuntime.LifecycleConfigurationProperty(
        idle_runtime_session_timeout=900,
        max_lifetime=3600,
    ),
)

ポイントは以下です。

protocol_configuration="HTTP"

今回は Python SDK の BedrockAgentCoreApp を使っていないため、コンテナ側で HTTP プロトコル契約を自前実装しています。

そのため、Runtime 側は HTTP プロトコルとして作成します。

環境変数の用途は次のとおりです。

環境変数 用途
AWS_REGION RubyLLM / AWS SDK が Bedrock を呼ぶリージョン
AWS_DEFAULT_REGION AWS SDK の region fallback
BEDROCK_MODEL_ID RubyLLM が呼び出す Bedrock モデル
APP_TIMEZONE アプリ側で日時処理が必要になった場合のタイムゾーン

CloudFormation Outputs も出しておきます。

CfnOutput(self, "AgentRuntimeArn", value=runtime.attr_agent_runtime_arn)
CfnOutput(self, "AgentRuntimeId", value=runtime.attr_agent_runtime_id)
CfnOutput(self, "AgentImageRepositoryUri", value=image_repository.repository_uri)
CfnOutput(self, "AgentImageUri", value=runtime_image_uri)

7. テストする

CDK テンプレートの最低限のテストを書いています。(本当はもっとちゃんとテストは書いてね!)

tests/test_stack.py
from aws_cdk import App
from aws_cdk.assertions import Match, Template

from infra.ruby_llm_agentcore_stack import RubyLLMAgentCoreStack


def test_runtime_uses_http_protocol_and_container_artifact():
    app = App()
    stack = RubyLLMAgentCoreStack(app, "TestStack")
    template = Template.from_stack(stack)

    template.has_resource_properties(
        "AWS::BedrockAgentCore::Runtime",
        {
            "ProtocolConfiguration": "HTTP",
            "NetworkConfiguration": {"NetworkMode": "PUBLIC"},
        },
    )


def test_runtime_role_can_invoke_bedrock_models():
    app = App()
    stack = RubyLLMAgentCoreStack(app, "TestStack")
    template = Template.from_stack(stack)

    template.has_resource_properties(
        "AWS::IAM::Policy",
        {
            "PolicyDocument": {
                "Statement": Match.array_with(
                    [
                        Match.object_like(
                            {
                                "Action": [
                                    "bedrock:InvokeModel",
                                    "bedrock:InvokeModelWithResponseStream",
                                ],
                                "Effect": "Allow",
                            }
                        )
                    ]
                )
            }
        },
    )

テスト実行:

env JSII_RUNTIME_PACKAGE_CACHE_ROOT=/tmp/jsii-package-cache \
  JSII_SILENCE_WARNING_UNTESTED_NODE_VERSION=1 \
  .venv/bin/pytest -q

自分のローカル環境では jsii~/Library/Caches に書き込もうとして失敗したため、JSII_RUNTIME_PACKAGE_CACHE_ROOT/tmp に向けています。

8. デプロイする

対象リージョンを指定します。

export CDK_DEFAULT_REGION=ap-northeast-1

初回だけ bootstrap します。

cdk bootstrap

デプロイします。

cdk deploy

モデルを指定したい場合は context で渡します。

cdk deploy -c modelId=jp.anthropic.claude-sonnet-4-6

デプロイが完了すると、以下のような Output が出ます。

RubyLLMAgentCoreStack.AgentRuntimeArn = arn:aws:bedrock-agentcore:ap-northeast-1:123456789012:runtime/RubyLLMAgentCoreDemoRuntime-xxxxxxxxxx
RubyLLMAgentCoreStack.AgentRuntimeId = RubyLLMAgentCoreDemoRuntime-xxxxxxxxxx
RubyLLMAgentCoreStack.AgentImageRepositoryUri = 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ruby-llm-agentcore-demo
RubyLLMAgentCoreStack.AgentImageUri = 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ruby-llm-agentcore-demo:...

9. Runtime を呼び出す

CloudFormation Output の AgentRuntimeArn を使います。

export AGENT_RUNTIME_ARN="arn:aws:bedrock-agentcore:ap-northeast-1:123456789012:runtime/RubyLLMAgentCoreDemoRuntime-xxxxxxxxxx"

Runtime を呼び出します。

aws bedrock-agentcore invoke-agent-runtime \
  --region ap-northeast-1 \
  --agent-runtime-arn "$AGENT_RUNTIME_ARN" \
  --runtime-session-id "$(uuidgen | tr '[:upper:]' '[:lower:]')" \
  --payload '{"prompt":"こんにちは!"}' \
  /tmp/ruby-llm-agentcore-response.json

runtime-session-id は 33 文字以上が必要です。

UUID を使っておけば問題ありません。

レスポンスファイルを確認します。

cat /tmp/ruby-llm-agentcore-response.json

実際に返ってきたレスポンス例です。

{
  "response": "こんにちは!\n\nお元気ですか?何かお手伝いできることはありますか?",
  "evidence": {
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "provider": "bedrock",
    "modelId": "jp.anthropic.claude-sonnet-4-6",
    "rubyLlmVersion": "1.16.0",
    "durationMs": 1310
  }
}

AWSマネジメントコンソール上からも呼び出すことができました。

スクリーンショット 2026-07-08 0.04.11.png

RubyLLM が AgentCore Runtime 上で動き、Amazon Bedrock を呼び出せました。

ハマりどころ

RubyLLM 1.16.0 には bedrock_credential_provider がなかった

RubyLLM のドキュメントを見て、最初は次のように書いていました。

config.bedrock_credential_provider = Aws::CredentialProviderChain.new.resolve

しかし、RubyGems から入る ruby_llm 1.16.0 ではこの setter が存在せず、以下のエラーになりました。

NoMethodError: undefined method 'bedrock_credential_provider=' for an instance of RubyLLM::Configuration

そのため、AWS SDK で認証情報を解決し、RubyLLM には bedrock_api_keybedrock_secret_keybedrock_session_token として渡しました。

credentials = resolve_aws_credentials
config.bedrock_api_key = credentials.access_key_id
config.bedrock_secret_key = credentials.secret_access_key
config.bedrock_session_token = credentials.session_token

実装が完了してから、以下の記述を見つけたので、以下に記載されているような方法では試せていないです...

model_anthropic = RubyLLM.models.find('claude-sonnet-4-6', :anthropic)
model_bedrock = RubyLLM.models.find('claude-sonnet-4-6', :bedrock)

https://rubyllm.com/models/#provider-specific-resolution

AgentCore の 500 は詳細が見えづらい

Runtime 内で例外が起きると、CLI やコンソールでは以下のように丸められることがあります。

Received error (500) from runtime. Please check your CloudWatch logs for more information.

ただ、AgentCore のアプリケーショントレースだけでは、Ruby 側の例外が見えないこともありました。

そのため、切り分け用に以下の環境変数を用意しました。

DEBUG_RESPONSE_ERRORS=1

これを設定すると、例外時に HTTP 200 でエラー内容を JSON として返します。

{
  "error": "agent_invocation_failed",
  "errorClass": "NoMethodError",
  "detail": "undefined method ..."
}

ただし、これはデバッグ用途です。

例外内容をクライアントへ返すため、確認が終わったら無効にします。

片付け

検証が終わったら destroy します。

cdk destroy

今回の構成では ECR Repository も以下の設定にしています。

removal_policy=RemovalPolicy.DESTROY
empty_on_delete=True

そのため、スタック削除時に ECR Repository も一緒に削除されます。

まとめ

RubyLLM を Amazon Bedrock AgentCore Runtime 上にデプロイし、Runtime 経由で Bedrock の Claude を呼び出してみました。

ポイントは以下です。

  • AgentCore Runtime は HTTP プロトコル契約に従えば Ruby コンテナも動かせる
  • Rails や Sinatra は必須ではなく、Rack + WEBrick でも最小構成は作れる
  • Runtime コンテナは 0.0.0.0:8080/invocations/ping を提供する
  • RubyLLM 1.16.0 では Bedrock 認証情報を静的キー形式で渡す必要があった
  • Bedrock inference profile を使う場合は assume_model_exists: true が実用上必要だった
  • ap-northeast-1 では jp.anthropic.claude-sonnet-4-6 を使うと動作確認できた

AgentCore Runtime は Strands Agents 向けの印象が強いですが、HTTP プロトコル契約に合わせれば Ruby のような別言語の実装も載せられます。

次は RubyLLM の streaming を AgentCore Runtime の SSE や WebSocket に載せる構成も試してみたいと思います。

参考

1
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
1
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?