JSON-RPC와 HTTP 기반 통신 이해하기
에이전트 협업도 결국 네트워크 호출이다
앞 장까지는 A2A가 업무를 어떻게 표현하는지 보았다. Agent Card는 "이 에이전트가 무엇을 할 수 있는가"를 알려 주는 명함이고, Task는 맡긴 업무의 티켓이며, Message는 대화의 한 턴, Artifact는 공식 결과물이다.
이제 질문은 더 구체적이다. 클라이언트 에이전트는 이 정보를 실제로 어떻게 보낼까? 원격 에이전트는 어떤 형식으로 응답할까?
A2A는 이 지점에서 익숙한 웹 기술을 사용한다. 핵심은 JSON-RPC 2.0 over HTTP(S)다. JSON-RPC는 "네트워크 너머의 상대에게 특정 메서드를 호출하는 방식"을 JSON 문서로 표현하는 규칙이다. 쉽게 말해, 전화로 일을 부탁하는 대신 정해진 양식의 업무 요청서를 보내는 방식이다. HTTP(S)는 그 요청서를 실어 나르는 도로다. HTTPS는 암호화된 안전한 도로라고 보면 된다.
A2A 최신 사양은 데이터 모델, 추상 오퍼레이션, 프로토콜 바인딩이라는 층으로 나뉜다. Task, Message, Artifact 같은 개념은 데이터 모델에 속하고, "메시지 보내기", "태스크 조회하기", "태스크 취소하기" 같은 행위는 오퍼레이션에 속한다. JSON-RPC는 이 오퍼레이션을 실제 네트워크 호출로 매핑하는 바인딩 중 하나다.1
AI 생성 이미지
JSON-RPC 봉투: method, params, id
JSON-RPC 요청은 단순하다. 중요한 필드는 네 가지다.2
{
"jsonrpc": "2.0",
"id": "req-001",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [
{ "kind": "text", "text": "이번 분기 매출 보고서를 요약해 줘." }
],
"messageId": "msg-001"
}
}
}jsonrpc는 사용하는 JSON-RPC 버전이다. A2A가 JSON-RPC 2.0을 쓴다면 값은 "2.0"이어야 한다.
id는 요청과 응답을 맞추기 위한 번호표다. 클라이언트가 req-001이라는 요청을 보냈다면, 서버 응답에도 같은 id가 돌아와야 한다. 동시에 여러 요청을 보내는 시스템에서는 이 값이 중요하다. 단, 이 id는 A2A의 Task ID가 아니다. id는 RPC 요청 식별자이고, Task ID는 업무 티켓 식별자다.
method는 호출할 작업의 이름이다. A2A 1.0 계열에서 일반 메시지를 보내는 대표 메서드는 message/send다. 태스크 상태를 확인할 때는 tasks/get, 실행 중인 태스크 취소를 요청할 때는 tasks/cancel 같은 메서드를 사용한다. 스트리밍은 다음 장에서 다룰 message/stream이나 태스크 구독 흐름과 연결된다.
params는 메서드에 전달할 실제 내용이다. A2A에서는 여기에 Message, Task 조회 조건, 취소할 Task ID 같은 값이 들어간다.
result는 응답이고, error는 실패의 언어다
JSON-RPC 응답은 성공과 실패가 분명히 나뉜다. 성공하면 result가 있고, 실패하면 error가 있다. 둘이 동시에 나오면 안 된다.2
짧은 질문을 보냈고 원격 에이전트가 즉시 답할 수 있다면, 응답은 바로 Message일 수 있다.
{
"jsonrpc": "2.0",
"id": "req-001",
"result": {
"kind": "message",
"role": "agent",
"messageId": "msg-002",
"parts": [
{ "kind": "text", "text": "보고서는 매출 성장, 비용 증가, 갱신율 하락을 핵심으로 요약할 수 있습니다." }
]
}
}반대로 시간이 걸리는 업무라면 서버는 Task를 만들어 돌려줄 수 있다. 이 경우 응답은 "업무가 끝났다"가 아니라 "업무 티켓을 만들었고 처리를 시작했다"는 뜻이다.
{
"jsonrpc": "2.0",
"id": "req-001",
"result": {
"kind": "task",
"id": "task-789",
"contextId": "ctx-456",
"status": {
"state": "working"
}
}
}이제 클라이언트는 task-789를 기억해야 한다. 나중에 tasks/get으로 상태를 조회하거나, 필요하면 tasks/cancel로 취소를 요청할 수 있다.
오류는 다음처럼 표현된다.
{
"jsonrpc": "2.0",
"id": "req-002",
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"reason": "message.parts must not be empty"
}
}
}code는 오류 종류를 기계가 읽기 쉽게 나타내고, message는 사람이 이해할 수 있는 짧은 설명이다. data에는 추가 세부 정보를 넣을 수 있다. JSON-RPC 표준 오류에는 잘못된 JSON을 뜻하는 -32700, 유효하지 않은 요청을 뜻하는 -32600, 존재하지 않는 메서드를 뜻하는 -32601, 잘못된 파라미터를 뜻하는 -32602, 내부 오류를 뜻하는 -32603 등이 있다.2
A2A 구현에서는 여기에 태스크 관련 오류가 더해진다. 예를 들어 없는 Task ID를 조회하면 Task not found 계열 오류가 될 수 있고, 이미 완료된 태스크를 취소하려 하면 취소 불가 오류가 될 수 있다. 중요한 점은 오류도 협업의 일부라는 것이다. 클라이언트는 실패를 단순 예외로 버리지 말고, 재시도할지, 사용자에게 추가 입력을 요청할지, 다른 에이전트로 라우팅할지 판단해야 한다.
동기식 request/response가 잘 맞는 경우
HTTP 기반 JSON-RPC 호출은 기본적으로 요청과 응답의 짝으로 이해하기 쉽다. 클라이언트가 요청을 보내고, 서버가 하나의 응답을 돌려준다. 이 동기식 request/response 방식은 다음 상황에 잘 맞는다.
첫째, 짧은 질의다. 예를 들어 "이 문장을 영어로 번역해 줘", "이 고객 등급을 계산해 줘", "이 JSON이 유효한지 확인해 줘" 같은 작업은 오래 기다릴 필요가 없다.
둘째, 즉시 검증이 필요한 작업이다. 요청 형식이 맞는지, 인증이 되었는지, 해당 에이전트가 입력 형식을 지원하는지 확인하는 데 request/response는 단순하고 명확하다.
셋째, 결과가 작고 한 번에 전달 가능한 작업이다. 작은 텍스트 답변이나 짧은 구조화 데이터는 스트리밍 없이 result로 돌려받아도 충분하다.
하지만 모든 작업을 동기식으로 처리하려고 하면 문제가 생긴다. 긴 보고서 작성, 대량 파일 분석, 외부 승인 대기, 여러 도구 호출이 필요한 업무는 시간이 오래 걸린다. 이런 경우 서버는 즉시 최종 결과를 만들기보다 Task를 반환하고, 이후 상태 조회·스트리밍·푸시 알림으로 진행 상황을 전달하는 편이 낫다. A2A가 Task를 중심에 두는 이유가 여기에 있다.
HTTP(S)에서 고려할 것들
A2A 요청은 Agent Card에서 발견한 엔드포인트로 보낸다. 클라이언트는 원격 에이전트의 주소를 코드에 박아 두기보다 Agent Card를 읽고, 지원하는 인터페이스와 보안 요구사항을 확인한 뒤 호출해야 한다.
일반적인 JSON-RPC over HTTP 호출은 HTTP POST와 JSON 본문을 사용한다. 요청 헤더에는 보통 Content-Type: application/json이 들어간다. 인증이 필요한 경우에는 Agent Card가 안내한 방식에 따라 Authorization 같은 HTTP 헤더를 사용한다. 인증 토큰을 JSON-RPC params 안에 넣는 방식은 피하는 것이 좋다. 보안 정보는 프로토콜 본문보다 표준 HTTP 보안 체계에서 다루는 편이 운영과 감사에 유리하다.
버전 관리도 중요하다. A2A는 발전 중인 공개 표준이고, 사양 버전이나 SDK 버전에 따라 메서드 이름, 필드 이름, 지원 기능이 달라질 수 있다. 따라서 구현자는 세 가지를 확인해야 한다.
이 서버가 어떤 A2A 사양 버전을 기준으로 하는가
Agent Card의 스키마와 기능 선언이 클라이언트가 기대하는 것과 맞는가
스트리밍, 푸시 알림, 특정 입력 모드처럼 선택적 기능을 실제로 지원하는가
URL에 /v1/ 같은 경로 버전을 넣을지는 각 서비스 설계의 문제다. 다만 클라이언트 입장에서는 "이 주소가 영원히 같은 의미를 가진다"고 가정하면 안 된다. Agent Card를 다시 읽고, 서버가 알리는 버전과 기능을 기준으로 동작해야 한다.
네트워크 오류와 프로토콜 오류를 구분하기
운영 환경에서는 실패가 여러 층에서 발생한다. 이 층을 구분하지 않으면 장애 대응이 어려워진다.
첫 번째는 네트워크 오류다. DNS가 실패하거나, 연결 시간이 초과되거나, TLS 인증서 검증이 실패하는 경우다. 이때는 JSON-RPC 응답 자체를 받지 못한다. 클라이언트는 타임아웃, 재시도 횟수, 백오프 전략을 가져야 한다.
두 번째는 HTTP 오류다. 예를 들어 401 Unauthorized, 403 Forbidden, 429 Too Many Requests, 503 Service Unavailable 같은 응답이다. 이 경우에는 인증 갱신, 권한 확인, 속도 제한 대기, 다른 인스턴스로 우회 같은 판단이 필요하다.
세 번째는 JSON-RPC 오류다. HTTP 요청은 도착했고 서버도 JSON-RPC 응답을 만들었지만, 메서드가 없거나 파라미터가 잘못되었거나 내부 처리에 실패한 경우다. 이때는 응답의 error.code와 error.data를 읽어야 한다.
네 번째는 A2A 업무 상태다. JSON-RPC 호출 자체는 성공했지만 Task 상태가 failed, canceled, input-required일 수 있다. 이것은 전송 실패가 아니라 업무 처리 흐름의 결과다. 예를 들어 input-required는 에이전트가 추가 정보를 기다린다는 뜻이지, 네트워크 장애가 아니다.
재시도는 조심스럽게 설계한다
실패하면 재시도하면 된다고 생각하기 쉽다. 그러나 message/send는 새로운 Task를 만들 수 있다. 같은 요청을 무작정 다시 보내면 원격 에이전트가 같은 일을 두 번 시작할 수 있다.
그래서 구현자는 식별자를 잘 설계해야 한다. JSON-RPC id는 요청과 응답을 맞추는 데 쓰고, messageId는 메시지 중복을 판단하는 데 도움을 줄 수 있으며, taskId와 contextId는 이어지는 업무 흐름을 추적하는 데 쓰인다. 서비스에 따라 별도의 idempotency key를 HTTP 헤더나 메타데이터로 둘 수도 있다. 멱등성은 "같은 요청을 여러 번 보내도 결과가 한 번 처리된 것과 같게 만드는 성질"이다.
A2A가 이 문제를 자동으로 없애 주지는 않는다. 프로토콜은 공통 언어를 제공하지만, 중복 처리, 저장소 트랜잭션, 재시도 정책, 타임아웃 값은 구현자가 책임져야 한다.
호출 구조를 이해하면 구현이 단순해진다
A2A의 네트워크 호출은 복잡해 보이지만 기본 구조는 명확하다.
클라이언트는 Agent Card로 엔드포인트와 기능을 확인한다. 그런 다음 HTTP(S)로 JSON-RPC 요청을 보낸다. method에는 하고 싶은 일을 적고, params에는 Message나 Task ID 같은 데이터를 넣는다. 서버는 result로 Message 또는 Task를 돌려주거나, error로 실패 이유를 알려 준다.
이 구조를 이해하면 A2A 구현의 큰 틀이 보인다. 짧은 일은 message/send의 동기식 응답으로 처리한다. 시간이 걸리는 일은 Task를 받고 tasks/get으로 조회하거나, 다음 장에서 볼 스트리밍과 비동기 알림으로 이어 간다. HTTP(S)와 JSON-RPC는 그 위에서 에이전트 협업이 안정적으로 오가도록 해 주는 기본 통신 언어다.
참고
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.