7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GA版AgentCoreRuntimeにA2A対応AIエージェントをデプロイして詰まったこと!

Posted at

吾輩は人間である

前書き

AgentCore発表から3ヶ月、ようやくGAしました。

A2Aランタイムとgatewayのリモートmcpが追加され、対応リージョンが増えたのは良かったのですが、TypeScriptのSDKが依然として提供されていないのは残念です:frowning2:

g7ty6r6hmtcabzxsobu4.jpg

A2Aはだいぶ前から発表されていたものの、ちゃんとデプロイしたことがなかったので、重い腰を上げてAgentCoreRuntimeにA2A対応AIエージェントをデプロイしてみます:relaxed:

公式ドキュメント通りにやってましたが、認証に詰まって数時間を溶かしてしまったので記録しておきます。

対象ドキュメント

実際にやった公式ドキュメントはこちら。

単体のStrandsAgents製AIエージェントをAgentCoreRuntimeにデプロイして、Agent Cardsを取得したり、ローカルから呼び出したりする流れで、順調にいけば15分以内で終わる内容でした:point_up_tone1:

Cognitoの準備

AgentCoreRuntimeにデプロイする手順の前に、
認証用のCognitoユーザープールを設定するステップがあったのですが、あまり詳しい説明がなく、下記のドキュメントが置かれてるだけでした。

ドキュメントには下記のようなシェルスクリプトがあり、source setup_cognito.shで実行すれば、Cognitoのユーザープールやクライアント、テストユーザーが作成できるようになっていました。

setup_cognito.sh
#!/bin/bash

# Create User Pool and capture Pool ID directly
export POOL_ID=$(aws cognito-idp create-user-pool \
  --pool-name "MyUserPool" \
  --policies '{"PasswordPolicy":{"MinimumLength":8}}' \
  --region us-west-2 | jq -r '.UserPool.Id')

# Create App Client and capture Client ID directly
export CLIENT_ID=$(aws cognito-idp create-user-pool-client \
  --user-pool-id $POOL_ID \
  --client-name "MyClient" \
  --no-generate-secret \
  --explicit-auth-flows "ALLOW_USER_PASSWORD_AUTH" "ALLOW_REFRESH_TOKEN_AUTH" \
  --region us-west-2 | jq -r '.UserPoolClient.ClientId')

# Create User
aws cognito-idp admin-create-user \
  --user-pool-id $POOL_ID \
  --username "testuser" \
  --temporary-password "TEMP_PASSWORD" \
  --region us-west-2 \
  --message-action SUPPRESS > /dev/null

# Set Permanent Password
aws cognito-idp admin-set-user-password \
  --user-pool-id $POOL_ID \
  --username "testuser" \
  --password "PERMANENT_PASSWORD" \
  --region us-west-2 \
  --permanent > /dev/null

# Authenticate User and capture Access Token
export BEARER_TOKEN=$(aws cognito-idp initiate-auth \
  --client-id "$CLIENT_ID" \
  --auth-flow USER_PASSWORD_AUTH \
  --auth-parameters USERNAME='testuser',PASSWORD='PERMANENT_PASSWORD' \
  --region us-west-2 | jq -r '.AuthenticationResult.AccessToken')

# Output the required values
echo "Pool id: $POOL_ID"
echo "Discovery URL: https://cognito-idp.us-west-2.amazonaws.com/$POOL_ID/.well-known/openid-configuration"
echo "Client ID: $CLIENT_ID"
echo "Bearer Token: $BEARER_TOKEN"

実際に実行したところ、下記の値が出力されます。

Pool id: us-west-2_xxx
Discovery URL: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_xxxx/.well-known/openid-configuration
Client ID: xxx
Bearer Token: xxx

Discovery URLClient IDはこの後設定で使うため、メモる必要がありますが:writing_hand_tone1:

Bearer Tokenはシェルの中ですでにexport済みなので、同じターミナルで実行する場合、特に何もしなくていいです。

軽く説明すると、ドキュメントでデプロイ用に使われているのは、計算機ツールを持たせたシンプルなStrandsAgents製のAIエージェントです。

my_a2a_server.py
import logging  
import os  
from strands_tools.calculator import calculator  
from strands import Agent  
from strands.multiagent.a2a import A2AServer  
import uvicorn  
from fastapi import FastAPI  

logging.basicConfig(level=logging.INFO)  

# Use the complete runtime URL from environment variable, fallback to local  
runtime_url = os.environ.get('AGENTCORE_RUNTIME_URL', 'http://127.0.0.1:9000/')  

logging.info(f"Runtime URL: {runtime_url}")  

strands_agent = Agent(  
    name="Calculator Agent",  
    description="基本的な算術演算を実行できる計算機エージェント.",  
    tools=[calculator],  
    callback_handler=None  
)  

host, port = "0.0.0.0", 9000  

# Pass runtime_url to http_url parameter AND use serve_at_root=True  
a2a_server = A2AServer(  
    agent=strands_agent,  
    http_url=runtime_url,  
    serve_at_root=True  # Serves locally at root (/) regardless of remote URL path complexity  
)  

app = FastAPI()  

@app.get("/ping")  
def ping():  
    return {"status": "healthy"}  

app.mount("/", a2a_server.to_fastapi_app())  

if __name__ == "__main__":  
    uvicorn.run(app, host=host, port=port)

詰まったポイント

デプロイ設定ファイル生成時に、下記のコマンドを実行して設定項目を入力していきます。

agentcore configure -e my_a2a_server.py --protocol A2A

基本的には全てEnterで問題ありませんが、

Configuring Bedrock AgentCore...
✓ Using file: my_a2a_server.py

🏷️  Inferred agent name: my_a2a_server
Press Enter to use this name, or type a different one (alphanumeric without '-')
Agent name [my_a2a_server]:
✓ Using agent name: my_a2a_server

🔍 Detected dependency file: requirements.txt
Press Enter to use this file, or type a different path (use Tab for autocomplete):
Path or Press Enter to use detected dependency file: requirements.txt
✓ Using requirements file: requirements.txt

🔐 Execution Role
Press Enter to auto-create execution role, or provide execution role ARN/name to use existing
Execution role ARN/name (or press Enter to auto-create):
✓ Will auto-create execution role

🏗️  ECR Repository
Press Enter to auto-create ECR repository, or provide ECR Repository URI to use existing
ECR Repository URI (or press Enter to auto-create):

Authorization Configurationの箇所は先ほど作成したCognitoを使用するため、discovery URLとclient IDsにはCognitoのものを入力し、OAuth audienceは空欄にしてください:point_up_tone1:

🔐 Authorization Configuration
By default, Bedrock AgentCore uses IAM authorization.
Configure OAuth authorizer instead? (yes/no) [no]: yes

📋 OAuth Configuration
Enter OAuth discovery URL: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_xxxx/.well-known/openid-configuration
Enter allowed OAuth client IDs (comma-separated): xxx
Enter allowed OAuth audience (comma-separated):

次のRequest Header Allowlistは、特にカスタマイズがなければnoで構いません。

🔒 Request Header Allowlist
Configure which request headers are allowed to pass through to your agent.
Common headers: Authorization, X-Amzn-Bedrock-AgentCore-Runtime-Custom-*
Configure request header allowlist? (yes/no) [no]:no

その後、下記のコマンドで実際にデプロイすれば動作するはずです。

agentcore launch

終わり

私は何も知らずに、OAuth audienceにもclient IDsを入れてしまったため、認証が通らず数時間苦戦しました。
Tokenをデコードして必要な情報を比較して、ようやく原因が分かりましたが。公式ドキュメントにもう少し詳しく書いてあれば助かったんですけどね:frowning2:

最初Cognitoから発行されるAccessTokenは1時間有効であるため、その時間内であれば、
export AGENT_ARN=""を設定すれば、下記のコード使って、デプロイ済みのAIエージェントのAgent Cardsも取得できるはずです。

get_card.py
import os
import json
import requests
from uuid import uuid4
from urllib.parse import quote

def fetch_agent_card():
    # Get environment variables
    agent_arn = os.environ.get('AGENT_ARN')
    bearer_token = os.environ.get('BEARER_TOKEN')

    if not agent_arn:
        print("Error: AGENT_ARN environment variable not set")
        return

    if not bearer_token:
        print("Error: BEARER_TOKEN environment variable not set")
        return

    # URL encode the agent ARN
    escaped_agent_arn = quote(agent_arn, safe='')

    # Construct the URL
    url = f"https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/{escaped_agent_arn}/invocations/.well-known/agent-card.json"

    # Generate a unique session ID
    session_id = str(uuid4())
    print(f"Generated session ID: {session_id}")

    # Set headers
    headers = {
        'Accept': '*/*',
        'Authorization': f'Bearer {bearer_token}',
        'X-Amzn-Bedrock-AgentCore-Runtime-Session-Id': session_id
    }

    try:
        # Make the request
        response = requests.get(url, headers=headers)
        response.raise_for_status()

        # Parse and pretty print JSON
        agent_card = response.json()
        print(json.dumps(agent_card, indent=2))

        return agent_card

    except requests.exceptions.RequestException as e:
        print(f"Error fetching agent card: {e}")
        return None

if __name__ == "__main__":
    fetch_agent_card()
% uv run get_card.py 
AGENT_ARN: arn:aws:bedrock-agentcore:us-west-2:xxx:runtime/my_a2a_server-sFnBtc3ZE7
Fetching agent card from URL: https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-west-2%3Axxxx%3Aruntime%2Fmy_a2a_server-sFnBtc3ZE7/invocations/.well-known/agent-card.json
Generated session ID: de479125-f608-4c11-b773-233643ce1c2d
{
  "capabilities": {
    "streaming": true
  },
  "defaultInputModes": [
    "text"
  ],
  "defaultOutputModes": [
    "text"
  ],
  "description": "\u57fa\u672c\u7684\u306a\u7b97\u8853\u6f14\u7b97\u3092\u5b9f\u884c\u3067\u304d\u308b\u8a08\u7b97\u6a5f\u30a8\u30fc\u30b8\u30a7\u30f3\u30c8.",
  "name": "Calculator Agent",
  "preferredTransport": "JSONRPC",
  "protocolVersion": "0.3.0",
  "skills": [
    {
      "description": "Calculator powered by SymPy for comprehensive mathematical operations.\n\nThis tool provides advanced mathematical functionality through multiple operation modes,\nincluding expression evaluation, equation solving, calculus operations (derivatives, integrals),\nlimits, series expansions, and matrix operations. Results are formatted with appropriate\nprecision and can be displayed in scientific notation when needed.\n\nHow It Works:\n------------\n1. The function parses the mathematical expression using SymPy's parser\n2. Based on the selected mode, it routes the expression to the appropriate handler\n3. Variables and constants are substituted with their values when provided\n4. The expression is evaluated symbolically and/or numerically as appropriate\n5. Results are formatted based on precision preferences and value magnitude\n6. Rich output is generated with operation details and formatted results\n\nOperation Modes:\n--------------\n- evaluate: Calculate the value of a mathematical expression\n- solve: Find solutions to an equation or system of equations\n- derive: Calculate derivatives of an expression\n- integrate: Find the indefinite integral of an expression\n- limit: Evaluate the limit of an expression at a point\n- series: Generate series expansion of an expression\n- matrix: Perform matrix operations\n\nCommon Usage Scenarios:\n---------------------\n- Basic calculations: Evaluating arithmetic expressions\n- Equation solving: Finding roots of polynomials or systems of equations\n- Calculus: Computing derivatives and integrals for analysis\n- Engineering analysis: Working with scientific notations and constants\n- Mathematics education: Visualizing step-by-step solutions\n- Data science: Matrix operations and statistical calculations\n\nArgs:\n    expression: The mathematical expression to evaluate, such as \"2 + 2 * 3\",\n        \"x**2 + 2*x + 1\", or \"sin(pi/2)\". For matrix operations, use array\n        notation like \"[[1, 2], [3, 4]]\".\n    mode: The calculation mode to use. Options are:\n        - \"evaluate\": Compute the value of the expression (default)\n        - \"solve\": Solve an equation or system of equations\n        - \"derive\": Calculate the derivative of an expression\n        - \"integrate\": Find the indefinite integral of an expression\n        - \"limit\": Calculate the limit of an expression at a point\n        - \"series\": Generate a series expansion of an expression\n        - \"matrix\": Perform matrix operations\n    precision: Number of decimal places for the result (default: 10).\n        Higher values provide more precise output but may impact performance.\n    scientific: Whether to use scientific notation for numbers (default: False).\n        When True, formats large and small numbers using scientific notation.\n    force_numeric: Force numeric evaluation of symbolic expressions (default: False).\n        When True, tries to convert symbolic results to numeric values.\n    variables: Optional dictionary of variable names and their values to substitute\n        in the expression, e.g., {\"a\": 1, \"b\": 2}.\n    wrt: Variable to differentiate or integrate with respect to (required for\n        \"derive\" and \"integrate\" modes).\n    point: Point at which to evaluate a limit (required for \"limit\" mode).\n        Use \"oo\" for infinity.\n    order: Order of derivative or series expansion (optional for \"derive\" and\n        \"series\" modes, default is 1 for derivatives and 5 for series).\n\nReturns:\n    Dict containing status and response content in the format:\n    {\n        \"status\": \"success|error\",\n        \"content\": [{\"text\": \"Result: <calculated_result>\"}]\n    }\n\n    Success case: Returns the calculation result with appropriate formatting\n    Error case: Returns information about what went wrong during calculation\n\nNotes:\n    - For equation solving, set the expression equal to zero implicitly (x**2 + 1 means x**2 + 1 = 0)\n    - Use 'pi' and 'e' for mathematical constants\n    - The 'wrt' parameter is required for differentiation and integration\n    - Matrix expressions use Python-like syntax: [[1, 2], [3, 4]]\n    - Precision control impacts display only, internal calculations use higher precision\n    - Symbolic results are returned when possible unless force_numeric=True",
      "id": "calculator",
      "name": "calculator",
      "tags": []
    }
  ],
  "url": "https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-west-2%3Axxx%3Aruntime%2Fmy_a2a_server-sFnBtc3ZE7/invocations/",
  "version": "0.0.1"
}
7
2
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
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?