A2A의 확장

A2A의 확장 (Extensions)

Agent2Agent(A2A) 프로토콜은 에이전트 간 통신을 위한 강력한 기반을 제공해요. 하지만 특정 도메인이나 고급 사용 사례는 종종 일반 메서드 너머의 추가 구조, 커스텀 데이터, 또는 새로운 상호작용 패턴을 요구해요. 확장(Extensions)은 기본 프로토콜 위에 새 역량을 덧입히는 A2A의 강력한 메커니즘이에요. 확장은 A2A 프로토콜을 새 데이터, 요구사항, RPC 메서드, 상태 머신으로 확장할 수 있게 해요. 에이전트는 Agent Card에서 특정 확장 지원을 선언하고, 클라이언트는 에이전트에 하는 요청의 일부로 확장이 제공하는 동작에 옵트인할 수 있어요. 확장은 URI로 식별되고 자체 스펙으로 정의돼요. 누구나 확장을 정의하고, 게시하고, 구현할 수 있어요. 확장의 유연성 덕분에 핵심 표준을 쪼개지 않고도 A2A를 커스터마이즈할 수 있어, 혁신과 도메인 특화 최적화를 촉진해요.

출처: 문서

본문

확장의 범위(Scope of Extensions)

확장을 사용할 수 있는 가능한 방식들의 정확한 집합은 의도적으로 넓게 열려 있어, A2A를 알려진 사용 사례 너머로 확장하는 것을 용이하게 해요. 그래도 예상 가능한 애플리케이션이 몇 가지 있어요.

  • 데이터 전용 확장(Data-only Extensions): 요청-응답 흐름에 영향을 주지 않는 새롭고 구조화된 정보를 Agent Card에 노출해요. 예를 들어 확장이 에이전트의 GDPR 준수에 대한 구조화된 데이터를 추가할 수 있어요.
  • 프로필 확장(Profile Extensions): 핵심 요청-응답 메시지 위에 추가 구조와 상태 변화 요구사항을 덧입혀요. 이 유형은 사실상 핵심 A2A 프로토콜 위의 프로필처럼 작동해서 허용되는 값의 범위를 좁혀요(예: 모든 메시지가 특정 스키마를 따르는 DataParts를 사용하도록 요구). 메타데이터를 사용해 태스크 상태 머신의 기존 상태를 보강하는 것도 포함할 수 있어요. 예를 들어 확장이 TaskStatus.state가 'working'이고 TaskStatus.message.metadata["generating-image"]가 true일 때 'generating-image' 하위 상태를 정의할 수 있어요.
  • 메서드 확장(확장 스킬, Method Extensions): 프로토콜이 정의한 핵심 집합 너머에 완전히 새로운 RPC 메서드를 추가해요. 확장 스킬(Extended Skill)은 에이전트가 새 RPC 메서드를 정의하는 확장 구현을 통해 얻거나 노출하는 역량 또는 함수를 말해요. 예를 들어 task-history 확장이 이전 태스크 목록을 가져오는 tasks/search RPC 메서드를 추가해서, 에이전트에 새롭고 확장된 스킬을 효과적으로 제공할 수 있어요.
  • 상태 머신 확장(State Machine Extensions): 태스크 상태 머신에 새 상태나 전이를 추가해요.

예시 확장 목록

[테이블 생략]

확장 거버넌스(Extension Governance)

A2A 조직은 확장이 어떻게 제안, 개발, 승격, 유지되는지에 대한 공식 거버넌스 프레임워크를 사용해요. 공식 확장은 https://a2a-protocol.org/extensions/ URI 접두사를 사용하고, a2aproject 조직 아래 ext- 저장소 접두사로 호스팅돼요(실험 확장은 experimental-ext-를 사용).

URI 네임스페이스 https://a2a-protocol.org/extensions/ 접두사는 Agent Card와 프로토콜 메시지에서 사용되는 전역 고유 확장 식별자를 위한 표준 네임스페이스예요. 이 접두사 아래의 개별 URI, 예를 들어 https://a2a-protocol.org/extensions/{name}/v1은 특정 확장과 버전을 식별해요. 이 URI들은 식별자이며 HTTP 접근이 기대되지 않아요. 자세한 내용은 거버넌스 문서의 URI namespaces를 참고하세요.

전체 거버넌스 과정 — 계층(tiers), 수명주기, SDK 지원, 법적 요구사항 포함 — 은 Extension and Protocol Binding Governance 페이지를 참고하세요.

한계(Limitations)

확장이 허용하지 않는 프로토콜 변경이 몇 가지 있어요. 주로 핵심 타입 검증을 깨뜨리는 것을 방지하기 위해서예요.

  • 핵심 데이터 구조의 정의 변경: 예를 들어 프로토콜이 정의한 데이터 구조에 새 필드를 추가하거나 필수 필드를 제거하는 것. 확장은 커스텀 속성을 핵심 데이터 구조에 있는 metadata 맵에 넣어야 해요.
  • Enum 타입에 새 값 추가: 확장은 기존 enum 값을 사용하고 metadata 필드에 추가 의미를 주석으로 달아야 해요.

확장 선언(Extension Declaration)

에이전트는 AgentCapabilities 객체 안에 AgentExtension 객체를 포함시켜 Agent Card에서 확장 지원을 선언해요. 에이전트가 지원하는 프로토콜 확장의 선언이에요. [테이블 생략] 다음은 확장이 있는 Agent Card의 예시예요.

{
  "name": "Magic 8-ball",
  "description": "An agent that can tell your future... maybe.",
  "version": "0.1.0",
  "url": "https://example.com/agents/eightball",
  "capabilities": {
    "streaming": true,
    "extensions": [
      {
        "uri": "https://example.com/ext/konami-code/v1",
        "description": "Provide cheat codes to unlock new fortunes",
        "required": false,
        "params": {
          "hints": [
            "When your sims need extra cash fast",
            "You might deny it, but we've seen the evidence of those cows."
          ]
        }
      }
    ]
  },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "skills": [
    {
      "id": "fortune",
      "name": "Fortune teller",
      "description": "Seek advice from the mystical magic 8-ball",
      "tags": ["mystical", "untrustworthy"]
    }
  ]
}

필수 확장(Required Extensions)

확장은 일반적으로 선택적 기능을 제공하지만, 일부 에이전트는 더 엄격한 요구사항을 가질 수 있어요. Agent Card가 확장을 required: true로 선언하면, 이는 확장의 어떤 측면이 요청이 구성되거나 처리되는 방식에 영향을 준다는 것과 클라이언트가 이를 반드시 따라야 한다는 것을 클라이언트에게 알려요. 에이전트는 데이터 전용 확장을 필수로 표시해서는 안 돼요. 클라이언트가 필수 확장의 활성화를 요청하지 않거나 그 프로토콜을 따르지 않으면, 에이전트는 적절한 오류로 들어오는 요청을 거부해야 해요.

확장 스펙(Extension Specification)

확장의 상세 동작과 구조는 그 스펙으로 정의돼요. 정확한 형식은 강제되지 않지만, 적어도 다음을 포함해야 해요.

  • 확장을 식별하는 특정 URI.
  • AgentExtension 객체의 params 필드에 지정된 객체의 스키마와 의미.
  • 클라이언트와 에이전트 간에 통신되는 추가 데이터 구조의 스키마.
  • 확장을 구현하는 데 필요한 새 요청-응답 흐름, 추가 엔드포인트, 또는 기타 로직의 세부사항.

확장 의존성(Extension Dependencies)

확장은 다른 확장에 의존할 수 있어요. 필수 의존성(종속 대상 없이는 확장이 작동할 수 없음)일 수도, 선택적 의존성(다른 확장이 있으면 추가 기능이 활성화됨)일 수도 있어요. 확장 스펙은 이러한 의존성을 문서화해야 해요. 확장과 그 스펙에 나열된 모든 필수 의존성을 활성화하는 것은 클라이언트의 책임이에요.

확장 활성화(Extension Activation)

확장은 기본적으로 비활성 상태로, 확장을 모르는 클라이언트에게 기준 경험을 제공해요. 클라이언트와 에이전트는 특정 요청에 대해 어떤 확장이 활성인지 결정하기 위해 협상해요.

  • 클라이언트 요청(Client Request): 클라이언트는 에이전트로 가는 HTTP 요청에 A2A-Extensions 헤더를 포함시켜 확장 활성화를 요청해요. 값은 클라이언트가 활성화할 확장 URI의 쉼표로 구분된 목록이에요.
  • 에이전트 처리(Agent Processing): 에이전트는 요청에서 지원되는 확장을 식별하고 활성화를 수행할 책임이 있어요. 에이전트가 지원하지 않는 요청된 확장은 무시할 수 있어요.
  • 응답(Response): 에이전트가 모든 활성화된 확장을 식별하고 나면, 응답에 해당 요청에 대해 성공적으로 활성화된 모든 확장을 나열하는 A2A-Extensions 헤더를 포함시켜야 해요.

확장 활성화를 보여주는 요청 예시:

POST /agents/eightball HTTP/1.1
Host: example.com
Content-Type: application/json
A2A-Extensions: https://example.com/ext/konami-code/v1
Content-Length: 519
{
  "jsonrpc": "2.0",
  "method": "SendMessage",
  "id": "1",
  "params": {
    "message": {
      "messageId": "1",
      "role": "ROLE_USER",
      "parts": [{"text": "Oh magic 8-ball, will it rain today?"}]
    },
    "metadata": {
      "https://example.com/ext/konami-code/v1/code": "motherlode"
    }
  }
}

활성화된 확장을 반영하는 해당 응답:

HTTP/1.1 200 OK
Content-Type: application/json
A2A-Extensions: https://example.com/ext/konami-code/v1
Content-Length: 338
{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "message": {
      "messageId": "2",
      "role": "ROLE_AGENT",
      "parts": [{"text": "That's a bingo!"}]
    }
  }
}

구현 고려사항(Implementation Considerations)

A2A 프로토콜이 확장의 기능을 정의하는 동안, 이 섹션은 확장 구현에 대한 지침 — 작성, 버전 관리, 배포의 모범 관행 — 을 제공해요.

버전 관리(Versioning):

  • 확장 스펙은 진화해요. 클라이언트와 에이전트가 호환 구현을 협상할 수 있도록 명확한 버전 관리 전략이 중요해요.
  • 권장사항: 확장의 URI를 주요 버전 식별자로 사용하고, 가급적 버전 번호를 포함하세요(예: https://example.com/ext/my-extension/v1).
  • 파괴적 변경(Breaking Changes): 확장의 로직, 데이터 구조, 또는 필수 매개변수에 파괴적 변경을 도입할 때는 반드시 새 URI를 사용해야 해요.
  • 불일치 처리(Handling Mismatches): 클라이언트가 에이전트가 지원하지 않는 버전을 요청하면, 에이전트는 그 확장의 활성화 요청을 무시해야 해요. 다른 버전으로 폴백해서는 안 돼요.

발견 가능성과 게시(Discoverability and Publication):

  • 스펙 호스팅(Specification Hosting): 확장 스펙 문서는 확장의 URI에 호스팅해야 해요.
  • 영구 식별자(Permanent Identifiers): 작성자는 w3id.org 같은 영구 식별자 서비스를 확장 URI에 사용해 끊어진 링크를 방지하는 것을 권장해요.
  • 커뮤니티 레지스트리(Community Registry): A2A Extension Governance 프레임워크가 a2aproject 조직 아래 호스팅되는 공식·실험 확장을 위한 계층 시스템을 정의하며, 확장 제안·승격의 수명주기를 포함해요.

패키징과 재사용성(A2A SDK와 라이브러리):

  • 채택을 촉진하려면 확장 로직을 기존 A2A 클라이언트·서버 애플리케이션에 통합할 수 있는 재사용 가능한 라이브러리로 패키징해야 해요.
  • 확장 구현은 언어 생태계의 표준 패키지(Python용 PyPI 패키지, TypeScript/JavaScript용 npm 패키지처럼)로 배포해야 해요.

목표는 개발자에게 매끄러운 통합 경험을 제공하는 것이에요. 잘 설계된 확장 패키지는 개발자가 최소한의 코드로 서버에 추가할 수 있게 해야 해요. 예를 들어:

import logging
import os
import sys

import click
import uvicorn

from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
from agent import ReimbursementAgent
from agent_executor import ReimbursementAgentExecutor
from dotenv import load_dotenv

load_dotenv()

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

@click.command()
@click.option('--host', default='localhost')
@click.option('--port', default=10002)
def main(host: str, port: int) -> None:
    """Start the reimbursement agent server."""
    # Check for API key only if Vertex AI is not configured.
    if os.getenv('GOOGLE_GENAI_USE_VERTEXAI') != 'TRUE' and not os.getenv(
        'GEMINI_API_KEY'
    ):
        logger.error(
            'GEMINI_API_KEY environment variable not set and '
            'GOOGLE_GENAI_USE_VERTEXAI is not TRUE.'
        )
        sys.exit(1)

    try:
        capabilities = AgentCapabilities(streaming=True)
        skill = AgentSkill(
            id='process_reimbursement',
            name='Process Reimbursement Tool',
            description='Helps with the reimbursement process for users given the amount and purpose of the reimbursement.',
            tags=['reimbursement'],
            examples=[
                'Can you reimburse me $20 for my lunch with the clients?'
            ],
        )
        agent_card = AgentCard(
            name='Reimbursement Agent',
            description='This agent handles the reimbursement process for the employees given the amount and purpose of the reimbursement.',
            url=f'http://{host}:{port}/',
            version='1.0.0',
            default_input_modes=ReimbursementAgent.SUPPORTED_CONTENT_TYPES,
            default_output_modes=ReimbursementAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=[skill],
        )
        request_handler = DefaultRequestHandler(
            agent_executor=ReimbursementAgentExecutor(),
            task_store=InMemoryTaskStore(),
        )
        server = A2AStarletteApplication(
            agent_card=agent_card, http_handler=request_handler
        )

        uvicorn.run(server.build(), host=host, port=port)
    except Exception:
        logger.exception('An error occurred during server startup')
        sys.exit(1)

if __name__ == '__main__':
    main()

이 예시는 Python의 a2a.server 같은 A2A SDK나 라이브러리가 A2A 에이전트와 확장의 구현을 어떻게 용이하게 하는지 보여줘요.

보안(Security): 확장은 A2A 프로토콜의 핵심 동작을 수정하므로 새 보안 고려사항을 도입해요.

  • 입력 검증(Input Validation): 확장이 도입한 모든 새 데이터 필드, 매개변수, 메서드는 엄격히 검증해야 해요. 외부 당사자의 확장 관련 데이터는 모두 신뢰할 수 없는 입력으로 취급해요.
  • 필수 확장의 범위(Scope of Required Extensions): Agent Card에서 확장을 required: true로 표시할 때 주의해야 해요. 이는 모든 클라이언트에 하드 의존성을 만들며, 에이전트의 핵심 기능과 보안에 기본적인 확장(예: 메시지 서명 확장)에만 사용해야 해요.
  • 인증과 권한 부여(Authentication and Authorization): 확장이 새 메서드를 추가하면, 구현은 이 메서드가 핵심 A2A 메서드와 동일한 인증·권한 부여 검사를 받도록 해야 해요. 확장은 에이전트의 주요 보안 제어를 우회하는 방법을 제공해서는 안 돼요.

자세한 내용은 A2A Extensions: Empowering Custom Agent Functionality 블로그 게시글을 참고하세요.

더 알아보기 (Learn more)