

# AgentCore 런타임에 A2A 서버 배포
<a name="runtime-a2a"></a>

Amazon Bedrock AgentCore AgentCore 런타임을 사용하면 AgentCore 런타임에서 에이전트 Agent-to-Agent(A2A) 서버를 배포하고 실행할 수 있습니다. 이 가이드에서는 첫 번째 A2A 서버를 생성, 테스트 및 배포하는 방법을 안내합니다.

이 섹션에서는 다음을 배웁니다.
+ Amazon Bedrock AgentCore가 A2A를 지원하는 방법
+ 에이전트 기능을 사용하여 A2A 서버를 생성하는 방법
+ 로컬에서 서버를 테스트하는 방법
+ 에 서버를 배포하는 방법 AWS 
+ 배포된 서버를 호출하는 방법
+ 검색을 위해 에이전트 카드를 검색하는 방법

A2A에 대한 자세한 내용은 [A2A 프로토콜 계약을](runtime-a2a-protocol-contract.md) 참조하세요.

**Topics**
+ [Amazon Bedrock AgentCore가 A2A를 지원하는 방법](#runtime-a2a-how-agentcore-supports)
+ [AgentCore 런타임에서 A2A 사용](#runtime-a2a-steps)
+ [부록](#runtime-a2a-appendix)

## Amazon Bedrock AgentCore가 A2A를 지원하는 방법
<a name="runtime-a2a-how-agentcore-supports"></a>

Amazon Bedrock AgentCore의 A2A 프로토콜 지원을 통해 투명한 프록시 계층 역할을 하여 A2A 서버와 원활하게 통합할 수 있습니다. A2A에 대해 구성된 경우 Amazon Bedrock AgentCore는 컨테이너가 기본 A2A 서버 구성과 일치하는 루트 경로(`0.0.0.0:9000/`)의 포트`9000`에서 상태 비저장, 스트리밍 가능한 HTTP 서버를 실행할 것으로 예상합니다.

이 서비스는 프로토콜 투명성을 유지하면서 엔터프라이즈급 세션 격리를 제공합니다. [InvokeAgentRuntime](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_InvokeAgentRuntime.html) API의 JSON-RPC 페이로드는 수정 없이 A2A 컨테이너로 직접 전달됩니다. 이 아키텍처는 및 JSON-RPC 통신에서 에이전트 카드를 통한 기본 제공 에이전트 검색`/.well-known/agent-card.json`과 같은 표준 A2A 프로토콜 기능을 유지하면서 엔터프라이즈 인증(SigV4/OAuth 2.0) 및 확장성을 추가합니다.

다른 프로토콜과의 주요 차별화 요소는 포트(HTTP의 경우 9000 대 8080), 탑재 경로( `/` 대 `/invocations` ), 표준화된 에이전트 검색 메커니즘이므로 Amazon Bedrock AgentCore는 프로덕션 환경의 A2A 에이전트에 이상적인 배포 플랫폼입니다.

다른 프로토콜과의 주요 차이점:

 **포트**   
포트 9000에서 실행되는 A2A 서버(HTTP의 경우 8080, MCP의 경우 8000)

 **경로**   
A2A 서버는 `/` (HTTP`/invocations`의 경우 , MCP`/mcp`의 경우 )에 탑재됩니다.

 **에이전트 카드**   
A2A는에서 에이전트 카드를 통해 기본 제공 에이전트 검색을 제공합니다. `/.well-known/agent-card.json` 

 **프로토콜**   
agent-to-agent 통신에 JSON-RPC 사용

 **Authentication**   
SigV4 및 OAuth 2.0 인증 체계 모두 지원

자세한 내용은 [https://a2a-protocol.org/](https://a2a-protocol.org/) 단원을 참조하십시오.

## AgentCore 런타임에서 A2A 사용
<a name="runtime-a2a-steps"></a>

이 자습서에서는 A2A 서버를 생성, 테스트 및 배포합니다.

**Topics**
+ [사전 조건](#runtime-a2a-prerequisites)
+ [1단계: A2A 프로젝트 생성](#runtime-a2a-create-server)
+ [2단계: 로컬에서 A2A 서버 테스트](#runtime-a2a-test-locally)
+ [3단계: A2A 서버를 Bedrock AgentCore 런타임에 배포](#runtime-a2a-deploy)
+ [4단계: 에이전트 카드 가져오기](#runtime-a2a-step-4)
+ [5단계: 배포된 A2A 서버 호출](#runtime-a2a-step-5)

### 사전 조건
<a name="runtime-a2a-prerequisites"></a>
+ Python 3.10 이상 설치 및 Python에 대한 기본 이해
+ Node.js 18 이상 설치됨( AgentCore CLI에 필요)
+ AgentCore CLI가 설치되었습니다. `npm install -g @aws/agentcore` 
+ 적절한 권한과 로컬 자격 증명이 구성된 AWS 계정
+ A2A 프로토콜 및 agent-to-agent 통신 개념 이해

### 1단계: A2A 프로젝트 생성
<a name="runtime-a2a-create-server"></a>

이 예제에서는 Strands Agents를 사용하지만 AgentCore CLI는 LangChain/LangGraph 및 Google ADK를 사용하는 A2A 프로젝트도 지원합니다.

#### 프로젝트 스캐폴드
<a name="runtime-a2a-scaffold-project"></a>

다음 명령을 실행하고 메시지가 표시되면 프레임워크로 *Strands*를 선택합니다.

```
agentcore create --protocol A2A
```

CLI는 필요한 모든 종속성과 구성이 포함된 전체 프로젝트를 스캐폴드합니다. 생성된 에는 A2A 서버가 `main.py` 포함되어 있습니다.

```
from strands import Agent, tool
from strands.multiagent.a2a.executor import StrandsA2AExecutor
from bedrock_agentcore.runtime import serve_a2a
from model.load import load_model

@tool
def add_numbers(a: int, b: int) -> int:
    """Return the sum of two numbers."""
    return a + b

tools = [add_numbers]

agent = Agent(
    model=load_model(),
    system_prompt="You are a helpful assistant. Use tools when appropriate.",
    tools=tools,
)

if __name__ == "__main__":
    serve_a2a(StrandsA2AExecutor(agent))
```

#### 코드 이해
<a name="runtime-a2a-understanding-code"></a>

 **Strands 에이전트**   
특정 도구 및 기능을 사용하여 에이전트를 생성합니다.

 **StrandsA2AExecutor**   
Strands 에이전트를 래핑하여 A2A 프로토콜 호환성 제공

 **serve\_a2a**   
Bedrock 호환 A2A 서버를 시작하는 Amazon Bedrock AgentCore SDK 헬퍼입니다. `/ping` 상태 엔드포인트, 에이전트 카드 서비스, `AGENTCORE_RUNTIME_URL` 환경 변수, Bedrock 헤더 전파를 처리하고 기본적으로 포트 9000에서 실행됩니다.

 **포트 9000**   
A2A 서버는 AgentCore 런타임에서 기본적으로 포트 9000에서 실행됩니다.

이 에이전트를 사용자 지정하려면 `add_numbers` 도구를 자체 도구로 바꾸고 시스템 프롬프트를 업데이트합니다.

### 2단계: 로컬에서 A2A 서버 테스트
<a name="runtime-a2a-test-locally"></a>

로컬 개발 환경에서 A2A 서버를 실행하고 테스트합니다.

#### A2A 서버 시작
<a name="runtime-a2a-start-server"></a>

AgentCore CLI를 사용하여 로컬에서 A2A 서버를 시작합니다.

```
agentcore dev
```

그러면 웹 브라우저에서 AgentCore 에이전트 검사기가 열립니다. 대신 터미널 기반 TUI를 사용하려면를 사용합니다`agentcore dev --no-browser`.

또는 서버를 직접 실행할 수 있습니다.

```
python main.py
```

서버가 포트에서 실행 중임을 나타내는 출력이 표시되어야 합니다`9000`.

#### 에이전트 호출
<a name="runtime-a2a-invoke-agent"></a>

```
curl -X POST http://localhost:9000/ \
-H "Content-Type: application/json" \
-d '{
  "jsonrpc": "2.0",
  "id": "req-001",
  "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "parts": [
        {
          "kind": "text",
          "text": "what is 101 * 11?"
        }
      ],
      "messageId": "12345678-1234-1234-1234-123456789012"
    }
  }
}' | jq .
```

#### 에이전트 카드 검색 테스트
<a name="runtime-a2a-test-agent-card"></a>

에이전트 카드 엔드포인트를 로컬에서 테스트할 수 있습니다.

```
curl http://localhost:9000/.well-known/agent-card.json | jq.
```

A2A 인스펙터를 사용한 [원격 테스트에 설명된 대로 A2A 인스펙터를 사용하여 배포된 서버를 테스트](https://github.com/a2aproject/a2a-inspector)할 수도 있습니다.

### 3단계: A2A 서버를 Bedrock AgentCore 런타임에 배포
<a name="runtime-a2a-deploy"></a>

#### 인증을 위한 Cognito 사용자 풀 설정
<a name="runtime-a2a-setup-cognito"></a>

배포하기 전에 배포된 서버에 대한 보안 액세스를 위한 인증을 구성합니다. 자세한 Cognito 설정 지침은 [인증을 위한 Cognito 사용자 풀 설정을](runtime-mcp.md#runtime-mcp-appendix-a) 참조하세요. 이렇게 하면 배포된 서버에 대한 보안 액세스에 필요한 OAuth 토큰이 제공됩니다.

#### 에 배포 AWS
<a name="runtime-a2a-deploy-aws"></a>

에이전트를 배포합니다.

```
agentcore deploy
```

이 명령은 다음을 수행합니다.

1. 에이전트 코드 및 종속성 패키징

1. Amazon S3에 배포 아티팩트 업로드

1. Amazon Bedrock AgentCore 런타임 생성

1. 에 에이전트 배포 AWS 

배포 후 다음과 같은 에이전트 런타임 ARN을 받게 됩니다.

```
arn:aws:bedrock-agentcore:us-west-2:accountId:runtime/my_a2a_server-xyz123
```

### 4단계: 에이전트 카드 가져오기
<a name="runtime-a2a-step-4"></a>

에이전트 카드는 A2A 서버의 자격 증명, 기능, 기술, 서비스 엔드포인트 및 인증 요구 사항을 설명하는 JSON 메타데이터 문서입니다. A2A 에코시스템에서 자동 에이전트 검색을 활성화합니다.

#### 환경 변수 설정
<a name="runtime-a2a-step-4-setup-environment-variables"></a>

환경 변수 설정

1. 보유자 토큰을 환경 변수로 내보냅니다. 보유자 토큰 설정은 [보유자 토큰 설정을](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-mcp.html#runtime-mcp-appendix) 참조하세요.

   ```
   export BEARER_TOKEN="<BEARER_TOKEN>"
   ```

1. 에이전트 ARN을 내보냅니다.

   ```
   export AGENT_ARN="arn:aws:bedrock-agentcore:us-west-2:accountId:runtime/my_a2a_server-xyz123"
   ```

#### 에이전트 카드 검색
<a name="retrieve-agent-card"></a>

```
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()
```

에이전트 카드에서 URL을 가져온 후 환경 변수`AGENTCORE_RUNTIME_URL`로 내보냅니다.

```
export AGENTCORE_RUNTIME_URL="https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/<ARN>/invocations/"
```

### 5단계: 배포된 A2A 서버 호출
<a name="runtime-a2a-step-5"></a>

클라이언트 코드를 생성하여 배포된 Amazon Bedrock AgentCore A2A 서버를 호출하고 메시지를 전송하여 기능을 테스트합니다.

배포된 A2A 서버를 호출`my_a2a_client_remote.py`할 새 파일을 생성합니다.

```
import asyncio
import logging
import os
from uuid import uuid4

import httpx
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
from a2a.types import Message, Part, Role, TextPart

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

DEFAULT_TIMEOUT = 300  # set request timeout to 5 minutes

def create_message(*, role: Role = Role.user, text: str) -> Message:
    return Message(
        kind="message",
        role=role,
        parts=[Part(TextPart(kind="text", text=text))],
        message_id=uuid4().hex,
    )

async def send_sync_message(message: str):
    # Get runtime URL from environment variable
    runtime_url = os.environ.get('AGENTCORE_RUNTIME_URL')

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

    # Add authentication headers for Amazon Bedrock AgentCore
    headers = {"Authorization": f"Bearer {os.environ.get('BEARER_TOKEN')}",
        'X-Amzn-Bedrock-AgentCore-Runtime-Session-Id': session_id}

    async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT, headers=headers) as httpx_client:
        # Get agent card from the runtime URL
        resolver = A2ACardResolver(httpx_client=httpx_client, base_url=runtime_url)
        agent_card = await resolver.get_agent_card()

        # Agent card contains the correct URL (same as runtime_url in this case)
        # No manual override needed - this is the path-based mounting pattern

        # Create client using factory
        config = ClientConfig(
            httpx_client=httpx_client,
            streaming=False,  # Use non-streaming mode for sync response
        )
        factory = ClientFactory(config)
        client = factory.create(agent_card)

        # Create and send message
        msg = create_message(text=message)

        # With streaming=False, this will yield exactly one result
        async for event in client.send_message(msg):
            if isinstance(event, Message):
                logger.info(event.model_dump_json(exclude_none=True, indent=2))
                return event
            elif isinstance(event, tuple) and len(event) == 2:
                # (Task, UpdateEvent) tuple
                task, update_event = event
                logger.info(f"Task: {task.model_dump_json(exclude_none=True, indent=2)}")
                if update_event:
                    logger.info(f"Update: {update_event.model_dump_json(exclude_none=True, indent=2)}")
                return task
            else:
                # Fallback for other response types
                logger.info(f"Response: {str(event)}")
                return event

# Usage - Uses AGENTCORE_RUNTIME_URL environment variable
asyncio.run(send_sync_message("what is 101 * 11"))
```

## 부록
<a name="runtime-a2a-appendix"></a>

**Topics**
+ [인증을 위한 Cognito 사용자 풀 설정](#runtime-a2a-setup-cognito-appendix)
+ [A2A 검사기를 사용한 원격 테스트](#runtime-a2a-remote-testing)
+ [문제 해결](#runtime-a2a-troubleshooting)

### 인증을 위한 Cognito 사용자 풀 설정
<a name="runtime-a2a-setup-cognito-appendix"></a>

자세한 Cognito 설정 지침은 MCP 설명서의 [인증을 위한 Cognito 사용자 풀](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-mcp.html#set-up-cognito-user-pool-for-authentication) 설정을 참조하세요.

### A2A 검사기를 사용한 원격 테스트
<a name="runtime-a2a-remote-testing"></a>

[https://github.com/a2aproject/a2a-inspector](https://github.com/a2aproject/a2a-inspector)을(를) 참조하세요.

### 문제 해결
<a name="runtime-a2a-troubleshooting"></a>

 **일반적인 A2A-specific 문제** 

발생할 수 있는 일반적인 문제는 다음과 같습니다.

포트 충돌  
A2A 서버는 AgentCore 런타임 환경의 포트 9000에서 실행되어야 합니다.

JSON-RPC 오류  
클라이언트가 올바른 형식의 JSON-RPC 2.0 메시지를 전송하고 있는지 확인합니다.

권한 부여 방법 불일치  
요청이 에이전트가 구성된 것과 동일한 인증 방법(OAuth 또는 SigV4)을 사용하는지 확인합니다.

 **예외 처리** 

오류 처리를 위한 A2A 사양: [https://a2a-protocol.org/latest/specification/#81-standard-json-rpc-errors](https://a2a-protocol.org/latest/specification/#81-standard-json-rpc-errors) 

A2A 서버는 HTTP 200 상태 코드와 함께 오류를 표준 JSON-RPC 오류 응답으로 반환합니다. 내부 런타임 오류는 프로토콜 준수를 유지하기 위해 JSON-RPC 내부 오류로 자동 변환됩니다.

이제 서비스는 표준화된 JSON-RPC 오류 코드와 함께 적절한 A2A-compliant 오류 응답을 제공합니다.


| JSON-RPC 오류 코드 | 런타임 예외 | HTTP 오류 코드 | JSON-RPC 오류 메시지 | 
| --- | --- | --- | --- | 
| 해당 사항 없음 |  `AccessDeniedException`  | 403 | 해당 사항 없음 | 
| -32501 |  `ResourceNotFoundException`  | 404 | 리소스를 찾을 수 없음 - 요청된 리소스가 존재하지 않음 | 
| -32502 |  `ValidationException`  | 400 | 검증 오류 - 잘못된 요청 데이터 | 
| -32503 |  `ThrottlingException`  | 429 | 속도 제한 초과 - 요청이 너무 많음 | 
| -32503 |  `ServiceQuotaExceededException`  | 429 | 속도 제한 초과 - 요청이 너무 많음 | 
| -32504 |  `ResourceConflictException`  | 409 | 리소스 충돌 - 리소스가 이미 있음 | 
| -32505 |  `RuntimeClientError`  | 424 | 런타임 클라이언트 오류 - 자세한 내용은 CloudWatch 로그를 확인하세요. | 