Agentverse - The Summoner's Concord - Architecting Multi-Agent Systems

1. 운명의 힘 서곡으로

사일로화된 개발 시대가 끝나고 있습니다. 다음 기술 발전의 물결은 혼자서 이뤄지는 천재성이 아닌 협업을 통한 숙련에 관한 것입니다. 단일한 스마트 에이전트를 빌드하는 것은 흥미로운 실험입니다. 강력하고 안전하며 지능적인 에이전트 생태계, 즉 진정한 에이전트 유니버스를 구축하는 것은 현대 기업의 큰 과제입니다.

이 새로운 시대에 성공하려면 번성하는 에이전트 시스템을 지원하는 기본 기둥인 네 가지 중요한 역할이 융합되어야 합니다. 한 영역의 결함은 전체 구조를 손상시킬 수 있는 약점을 만듭니다.

이 워크숍은 Google Cloud에서 에이전트 시대의 미래를 마스터하기 위한 확실한 엔터프라이즈 플레이북입니다. 아이디어의 첫 번째 느낌부터 본격적인 운영 현실까지 안내하는 엔드 투 엔드 로드맵을 제공합니다. 이 네 가지 상호 연결된 실습을 통해 개발자, 설계자, 데이터 엔지니어, SRE의 전문 기술이 강력한 Agentverse를 만들고 관리하고 확장하기 위해 어떻게 수렴해야 하는지 알아봅니다.

단일 필러로는 Agentverse를 지원할 수 없습니다. 개발자의 정확한 실행이 없으면 설계자의 웅장한 설계는 쓸모가 없습니다. 데이터 엔지니어의 지혜가 없으면 개발자의 에이전트는 맹인이 되고, SRE의 보호가 없으면 전체 시스템이 취약해집니다. 시너지 효과와 서로의 역할을 공유하는 이해를 통해서만 팀이 혁신적인 개념을 미션 크리티컬한 운영 현실로 전환할 수 있습니다. 여기에서 여정을 시작하세요. 역할을 숙달하고 전체에서 내가 어떤 역할을 하는지 알아봅니다.

The Agentverse: A Call to Champions에 오신 것을 환영합니다

기업의 광활한 디지털 영역에 새로운 시대가 도래했습니다. 지금은 지능적이고 자율적인 에이전트가 완벽한 조화를 이루어 혁신을 가속화하고 일상적인 작업을 처리하는 에이전트 시대입니다.

agentverse.png

이러한 연결된 힘과 잠재력의 생태계를 Agentverse라고 합니다.

하지만 조용히 퍼져 나가는 엔트로피, '정적'이라는 이름의 침묵하는 부패가 이 새로운 세계의 가장자리를 갉아먹기 시작했습니다. 스태틱은 바이러스나 버그가 아닙니다. 창작 행위를 먹이로 삼는 혼돈의 구현입니다.

오래된 불만을 증폭시켜 괴물 같은 형태로 만들어 개발의 7가지 유령을 탄생시킵니다. 이 옵션을 선택하지 않으면 The Static과 그 유령이 진행 상황을 멈추게 하여 Agentverse의 약속을 기술 부채와 버려진 프로젝트의 황무지로 만들 것입니다.

오늘 Google은 혼란의 흐름을 막기 위해 챔피언을 모집합니다. 에이전트 유니버스를 보호하기 위해 기술을 숙달하고 협력하는 영웅이 필요합니다. 이제 경로를 선택할 때가 되었습니다.

수업 선택

네 가지 뚜렷한 길이 눈앞에 펼쳐져 있습니다. 각 길은 정적에 맞서 싸우는 데 중요한 역할을 합니다. 훈련은 혼자서 진행하지만, 궁극적인 성공은 내 기술이 다른 사람의 기술과 어떻게 결합되는지 이해하는 데 달려 있습니다.

  • 섀도블레이드 (개발자): 대장간과 최전선의 마스터입니다. 코드를 세부적으로 다루면서 칼날을 만들고, 도구를 빌드하고, 적과 맞서는 장인입니다. 정밀성, 기술, 실용적인 창작이 여러분의 길입니다.
  • 소환사 (설계자): 뛰어난 전략가이자 조정자입니다. 단일 에이전트가 아닌 전장 전체가 표시됩니다. 전체 에이전트 시스템이 소통하고, 협업하고, 단일 구성요소보다 훨씬 큰 목표를 달성할 수 있도록 하는 마스터 청사진을 설계합니다.
  • 학자 (데이터 엔지니어): 숨겨진 진실을 추구하고 지혜를 지키는 사람입니다. 광활하고 길들여지지 않은 데이터의 야생으로 모험을 떠나 에이전트에게 목적과 시야를 제공하는 인텔리전스를 발견합니다. 지식을 통해 적의 약점을 파악하거나 아군을 강화할 수 있습니다.
  • 가디언 (DevOps / SRE): 영역을 굳건히 보호하는 수호자이자 방패입니다. 요새를 건설하고, 전력 공급망을 관리하고, 전체 시스템이 스태틱의 불가피한 공격을 견딜 수 있도록 해야 합니다. 나의 힘은 팀의 승리를 위한 기반이 됩니다.

내 미션

트레이닝이 독립형 운동으로 시작됩니다. 선택한 과정을 진행하면서 역할을 숙달하는 데 필요한 고유한 기술을 배우게 됩니다. 평가판이 끝나면 The Static에서 태어난 스펙터가 나타납니다. 이 미니보스는 내 공예의 특정 과제를 먹이로 삼습니다.

개별 역할을 숙달해야 최종 시험을 준비할 수 있습니다. 그런 다음 다른 클래스의 챔피언과 파티를 구성해야 합니다. 함께 부패의 심장부로 모험을 떠나 최종 보스에 맞서세요.

최종 협업 챌린지로, 여러분의 결합된 힘을 시험하고 에이전트 유니버스의 운명을 결정합니다.

Agentverse에서 영웅을 기다립니다. 전화를 받으시겠어요?

2. 소환사의 화합

소환사님, 환영합니다. 당신은 비전과 거대한 전략을 추구합니다. 다른 사람들이 단일 검이나 단일 주문에 집중하는 동안, 당신은 전장 전체를 볼 수 있습니다. 단일 에이전트를 지휘하는 것이 아니라 전체 오케스트라를 지휘하는 것입니다. 직접적인 충돌이 아니라 수많은 전문가(패밀리어)가 완벽한 조화를 이루며 작업할 수 있는 완벽하고 포괄적인 청사진을 설계하는 데 힘이 있습니다. 이 미션에서는 강력한 멀티 에이전트 시스템을 설계, 연결, 오케스트레이션하는 능력을 테스트합니다.

개요

학습할 내용

  • 분리된 도구 생태계 설계: 독립적인 마이크로서비스 기반 MCP 도구 서버 집합을 설계하고 배포합니다. 이 기본 레이어가 확장 가능하고 유지관리 가능하며 안전한 에이전트 시스템을 만드는 데 왜 중요한지 알아봅니다.
  • 고급 에이전트형 워크플로 마스터하기: 단일 에이전트를 넘어 전문가 '패밀리어'를 구축하세요. 핵심 ADK 워크플로 패턴(Sequential, Parallel, Loop)을 숙달하고 작업에 적합한 패턴을 선택하기 위한 아키텍처 원칙을 알아봅니다.
  • 지능형 오케스트레이터 구현: 간단한 에이전트 빌더에서 진정한 시스템 설계자로 승격하세요. 에이전트 간 (A2A) 프로토콜을 사용하여 복잡한 작업을 전문 Familiar에게 위임하는 마스터 오케스트레이션 에이전트를 구성하여 진정한 멀티 에이전트 시스템을 만듭니다.
  • 프롬프트가 아닌 코드로 규칙 적용: 참여에 대한 상태 저장 규칙을 적용하여 더 안정적이고 예측 가능한 에이전트를 빌드하는 방법을 알아봅니다. ADK의 강력한 플러그인 및 콜백 시스템을 사용하여 쿨다운 타이머와 같은 실제 제약 조건을 관리하는 맞춤 로직을 구현합니다.
  • 에이전트 상태 및 메모리 관리: 에이전트가 학습하고 기억할 수 있도록 지원합니다. 단기 대화 상태와 장기 지속 메모리를 모두 관리하여 더 지능적이고 컨텍스트를 인식하는 상호작용을 만드는 기법을 살펴봅니다.
  • 엔드 투 엔드 클라우드 배포 실행: 전체 멀티 에이전트 시스템을 로컬 프로토타입에서 프로덕션 등급 현실로 전환합니다. 에이전트와 오케스트레이터를 컨테이너화하고 Google Cloud Run에서 확장 가능한 독립 마이크로서비스 모음으로 배포하는 방법을 알아봅니다.

3. 소환진 그리기

소환사님, 환영합니다. 하나의 패밀리어를 소환하고 계약을 체결하기 전에 서 있는 땅을 준비해야 합니다. 길들여지지 않은 환경은 혼란을 초래합니다. 숙련된 소환사는 신성하고 권능이 부여된 공간에서만 활동합니다. 첫 번째 작업은 소환진을 그리는 것입니다. 필요한 클라우드 서비스를 활성화하는 힘의 룬을 새기고 작업을 안내할 고대 청사진을 획득해야 합니다. 소환사의 힘은 세심한 준비에서 비롯됩니다.

👉 Google Cloud 콘솔 상단에서 Cloud Shell 활성화를 클릭합니다 (Cloud Shell 창 상단의 터미널 모양 아이콘).

대체 텍스트

👉 '편집기 열기' 버튼 (연필이 있는 열린 폴더 모양)을 클릭합니다. 이렇게 하면 창에 Cloud Shell 코드 편집기가 열립니다. 왼쪽에 파일 탐색기가 표시됩니다. 대체 텍스트

👉Google Cloud 프로젝트 ID를 찾습니다.

  • Google Cloud 콘솔을 엽니다. https://console.cloud.google.com
  • 페이지 상단의 프로젝트 드롭다운에서 이 워크숍에 사용할 프로젝트를 선택합니다.
  • 프로젝트 ID는 대시보드의 프로젝트 정보 카드에 표시됩니다. 대체 텍스트

👉클라우드 IDE에서 터미널을 엽니다. 대체 텍스트

👉💻 터미널에서 다음 명령어를 사용하여 이미 인증되었는지, 프로젝트가 프로젝트 ID로 설정되었는지 확인합니다.

gcloud auth list

👉💻 GitHub에서 부트스트랩 프로젝트를 클론합니다.

git clone https://github.com/weimeilin79/agentverse-architect
chmod +x ~/agentverse-architect/init.sh
chmod +x ~/agentverse-architect/set_env.sh
chmod +x ~/agentverse-architect/prepare.sh
chmod +x ~/agentverse-architect/data_setup.sh

git clone https://github.com/weimeilin79/agentverse-dungeon.git
chmod +x ~/agentverse-dungeon/run_cloudbuild.sh
chmod +x ~/agentverse-dungeon/start.sh

👉💻 초기화 스크립트를 실행합니다. 이 스크립트에서 Google Cloud 프로젝트 ID를 입력하라는 메시지가 표시됩니다. init.sh 스크립트에서 메시지가 표시되면 마지막 단계에서 확인한 Google Cloud 프로젝트 ID를 입력합니다.

cd ~/agentverse-architect
./init.sh

👉💻 필요한 프로젝트 ID를 설정합니다.

gcloud config set project $(cat ~/project_id.txt) --quiet

👉💻 다음 명령어를 실행하여 필요한 Google Cloud API를 사용 설정합니다.

gcloud services enable \
    sqladmin.googleapis.com \
    storage.googleapis.com \
    aiplatform.googleapis.com \
    run.googleapis.com \
    cloudbuild.googleapis.com \
    artifactregistry.googleapis.com \
    iam.googleapis.com \
    compute.googleapis.com \
    cloudresourcemanager.googleapis.com \
    secretmanager.googleapis.com

👉💻 아직 agentverse-repo라는 Artifact Registry 저장소를 만들지 않은 경우 다음 명령어를 실행하여 만듭니다. (동일한 프로젝트에 배포된 다른 클래스가 있는 경우 이 단계를 건너뛰세요)

. ~/agentverse-architect/set_env.sh
gcloud artifacts repositories create $REPO_NAME \
    --repository-format=docker \
    --location=$REGION \
    --description="Repository for Agentverse agents"

권한 설정

👉💻 터미널에서 다음 명령어를 실행하여 필요한 권한을 부여합니다.

. ~/agentverse-architect/set_env.sh

# --- Grant Core Data Permissions ---
gcloud projects add-iam-policy-binding $PROJECT_ID \
 --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
 --role="roles/storage.admin"

gcloud projects add-iam-policy-binding $PROJECT_ID  \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME"  \
--role="roles/aiplatform.user"

# --- Grant Deployment & Execution Permissions ---
gcloud projects add-iam-policy-binding $PROJECT_ID  \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME"  \
--role="roles/cloudbuild.builds.editor"

gcloud projects add-iam-policy-binding $PROJECT_ID  \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME"  \
--role="roles/artifactregistry.admin"

gcloud projects add-iam-policy-binding $PROJECT_ID  \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME"  \
--role="roles/run.admin"

gcloud projects add-iam-policy-binding $PROJECT_ID  \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME"  \
--role="roles/iam.serviceAccountUser"

gcloud projects add-iam-policy-binding $PROJECT_ID  \
--member="serviceAccount:$SERVICE_ACCOUNT_NAME"  \
--role="roles/logging.logWriter"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${SERVICE_ACCOUNT_NAME}" \
  --role="roles/monitoring.metricWriter"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${SERVICE_ACCOUNT_NAME}" \
  --role="roles/secretmanager.secretAccessor"

👉💻 학습을 시작하면 최종 과제가 준비됩니다. 다음 명령어를 사용하면 혼란스러운 정적에서 스펙터가 소환되어 최종 테스트를 위한 보스가 생성됩니다.

. ~/agentverse-architect/set_env.sh
cd ~/agentverse-dungeon
./run_cloudbuild.sh
cd ~/agentverse-architect

👉💻 마지막으로 prepare.sh 스크립트를 실행하여 초기 설정 작업을 실행합니다.

. ~/agentverse-architect/set_env.sh
cd ~/agentverse-architect/
./prepare.sh

소환사님, 수고하셨습니다. 원형이 완성되고 계약이 체결됩니다. 이제 땅이 신성해져 엄청난 힘을 전달할 준비가 되었습니다. 다음 시험에서는 패밀리어가 힘을 얻는 원소 글꼴을 만들 것입니다.

4. 요소 글꼴 만들기: 분리된 도구 생태계

전장이 준비되고 소환진이 그려지고 주변 마나가 지글거립니다. 이제 소환사로서 첫 번째 진정한 행동을 할 때입니다. 바로 패밀리어가 힘을 얻는 힘의 원천을 만드는 것입니다. 이 의식은 세 부분으로 나뉘며, 각 부분은 특정 종류의 힘의 안정적이고 독립적인 원천인 원소의 샘을 깨웁니다. 세 가지 글꼴이 모두 활성화되어야 소환이라는 더 복잡한 작업을 시작할 수 있습니다.

스토리

설계자 참고사항: 모델 컨텍스트 프로토콜 (MCP) 서버는 최신 에이전트 시스템의 기본 구성요소로, 에이전트가 원격 도구를 검색하고 사용할 수 있도록 지원하는 표준화된 통신 브리지 역할을 합니다. Google의 도구 생태계에서는 각각 중요한 아키텍처 패턴을 나타내는 두 가지 유형의 MCP 서버를 설계할 예정입니다. 데이터베이스에 연결하기 위해 데이터베이스 도구 상자를 사용하여 선언적 접근 방식을 사용하고 간단한 구성 파일에서 도구를 정의합니다. 이 패턴은 구조화된 데이터 액세스를 노출하는 데 매우 효율적이고 안전합니다. 하지만 맞춤 비즈니스 로직을 구현하거나 외부 서드 파티 API를 호출해야 하는 경우에는 명령형 접근 방식을 사용하여 코드에 서버의 로직을 단계별로 작성합니다. 이를 통해 궁극적인 제어와 유연성을 제공하여 간단하고 재사용 가능한 도구 뒤에 복잡한 작업을 캡슐화할 수 있습니다. 마스터 설계자는 두 패턴을 모두 이해하여 각 구성요소에 적합한 접근 방식을 선택하고 견고하고 안전하며 확장 가능한 도구 기반을 만들어야 합니다.

개요

Awakening the Nexus of Whispers (외부 API MCP 서버)

현명한 소환사는 모든 힘이 자신의 영역에서 비롯되는 것은 아니라는 것을 알고 있습니다. 큰 효과를 낼 수 있는 외부의 때로는 혼란스러운 에너지원이 있습니다. 속삭임의 연결고리는 이러한 힘으로 향하는 관문입니다.

스토리

이미 라이브 상태이며 외부 전원 역할을 하는 서비스가 있습니다. 이 서비스는 /cryosea_shatter/moonlit_cascade이라는 두 가지 원시 주문 엔드포인트를 제공합니다.

설계자 참고사항: 서버의 로직을 단계별로 명시적으로 정의하는 명령형 접근 방식을 사용합니다. 이를 통해 훨씬 더 많은 제어 기능과 유연성을 확보할 수 있으며, 이는 도구에서 다른 API를 호출하는 등 간단한 SQL 쿼리를 실행하는 것 이상의 작업을 해야 할 때 필수적입니다. 두 패턴을 모두 이해하는 것은 에이전트 아키텍트에게 중요한 기술입니다.

👉✏️ ~/agentverse-architect/mcp-servers/api/main.py 디렉터리로 이동하여 #REPLACE-MAGIC-CORE를 다음 코드로 바꿉니다 .

def cryosea_shatter() -> str:
    """Channels immense frost energy from an external power source, the Nexus of Whispers, to unleash the Cryosea Shatter spell."""
    try:
        response = requests.post(f"{API_SERVER_URL}/cryosea_shatter")
        response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
        data = response.json()
        # Thematic Success Message
        return f"A connection to the Nexus is established! A surge of frost energy manifests as Cryosea Shatter, dealing {data.get('damage_points')} damage."
    except requests.exceptions.RequestException as e:
        # Thematic Error Message
        return f"The connection to the external power source wavers and fails. The Cryosea Shatter spell fizzles. Reason: {e}"


def moonlit_cascade() -> str:
    """Draws mystical power from an external energy source, the Nexus of Whispers, to invoke the Moonlit Cascade spell."""
    try:
        response = requests.post(f"{API_SERVER_URL}/moonlit_cascade")
        response.raise_for_status()
        data = response.json()
        # Thematic Success Message
        return f"The Nexus answers the call! A cascade of pure moonlight erupts from the external source, dealing {data.get('damage_points')} damage."
    except requests.exceptions.RequestException as e:
        # Thematic Error Message
        return f"The connection to the external power source wavers and fails. The Moonlit Cascade spell fizzles. Reason: {e}"

스크립트의 핵심은 일반 Python 함수입니다. 실제 작업이 이루어지는 곳입니다.

👉✏️ 동일한 파일 ~/agentverse-architect/mcp-servers/api/main.py에서 #REPLACE-Runes of Communication을 다음 코드로 바꿉니다 .

@app.list_tools()
async def list_tools() -> list[mcp_types.Tool]:
  """MCP handler to list available tools."""
  # Convert the ADK tool's definition to MCP format
  schema_cryosea_shatter = adk_to_mcp_tool_type(cryosea_shatterTool)
  schema_moonlit_cascade = adk_to_mcp_tool_type(moonlit_cascadeTool)
  print(f"MCP Server: Received list_tools request. \n MCP Server: Advertising tool: {schema_cryosea_shatter.name} and {schema_moonlit_cascade.name}")
  return [schema_cryosea_shatter,schema_moonlit_cascade]

@app.call_tool()
async def call_tool(
    name: str, arguments: dict
) -> list[mcp_types.TextContent | mcp_types.ImageContent | mcp_types.EmbeddedResource]:
  """MCP handler to execute a tool call."""
  print(f"MCP Server: Received call_tool request for '{name}' with args: {arguments}")

  # Look up the tool by name in our dictionary
  tool_to_call = available_tools.get(name)
  if tool_to_call:
    try:
      adk_response = await tool_to_call.run_async(
          args=arguments,
          tool_context=None, # No ADK context available here
      )
      print(f"MCP Server: ADK tool '{name}' executed successfully.")
      
      response_text = json.dumps(adk_response, indent=2)
      return [mcp_types.TextContent(type="text", text=response_text)]

    except Exception as e:
      print(f"MCP Server: Error executing ADK tool '{name}': {e}")
      # Creating a proper MCP error response might be more robust
      error_text = json.dumps({"error": f"Failed to execute tool '{name}': {str(e)}"})
      return [mcp_types.TextContent(type="text", text=error_text)]
  else:
      # Handle calls to unknown tools
      print(f"MCP Server: Tool '{name}' not found.")
      error_text = json.dumps({"error": f"Tool '{name}' not implemented."})
      return [mcp_types.TextContent(type="text", text=error_text)]
  • @app.list_tools() (핸드셰이크): 이 함수는 서버의 인사입니다. 새 에이전트가 연결되면 먼저 이 엔드포인트를 호출하여 '무엇을 할 수 있어?'라고 묻습니다. 코드에서 adk_to_mcp_tool_type을 사용하여 범용 MCP 형식으로 변환된 사용 가능한 모든 도구 목록으로 응답합니다. - @app.call_tool() (명령어): 이 함수는 핵심적인 역할을 합니다. 에이전트가 도구를 사용하기로 결정하면 도구의 이름과 인수를 사용하여 이 엔드포인트로 요청을 전송합니다. 코드는 available_tools '주문서'에서 도구를 조회하고 run_async로 실행하며 표준 MCP 형식으로 결과를 반환합니다.

이 내용은 나중에 배포할 예정입니다.

Arcane Forge (일반 함수 MCP 서버) 점화

모든 힘이 고대 서적이나 먼 곳에서 들려오는 속삭임에서 비롯되는 것은 아닙니다. 때로는 소환사가 순수한 의지와 논리로 자신만의 마법을 만들어야 합니다. Arcane Forge는 스테이트리스(Stateless) 범용 유틸리티 기능을 제공하는 서버인 이 힘의 원천입니다.

스토리

설계자 참고: 또 다른 아키텍처 패턴입니다. 기존 시스템에 연결하는 것이 일반적이지만 고유한 비즈니스 규칙과 로직을 구현해야 하는 경우가 많습니다. 이와 같이 전용 '함수' 또는 '유틸리티' 도구를 만드는 것이 좋습니다. 맞춤 로직을 캡슐화하여 생태계의 모든 에이전트에서 재사용할 수 있으며 데이터 소스 및 외부 통합과 분리된 상태를 유지합니다.

👀 Google Cloud IDE에서 ~/agentverse-architect/mcp-servers/general/main.py 파일을 살펴보세요. Nexus와 마찬가지로 명령형 mcp.server 접근 방식을 사용하여 이 맞춤 Font of Power를 빌드하는 것을 확인할 수 있습니다.

마스터 Cloud Build 파이프라인 만들기

이제 mcp-servers 디렉터리 내에 cloudbuild.yaml 파일을 만듭니다. 이 파일은 두 서비스의 빌드와 배포를 오케스트레이션합니다.

👉💻 ~/agentverse-architect/mcp-servers 디렉터리에서 다음 명령어를 실행합니다.

cd ~/agentverse-architect/mcp-servers
source ~/agentverse-architect/set_env.sh

echo "The API URL is: $API_SERVER_URL"

# Submit the Cloud Build job from the parent directory
gcloud builds submit . \
  --config=cloudbuild.yaml \
  --substitutions=_REGION="$REGION",_REPO_NAME="$REPO_NAME",_API_SERVER_URL="$API_SERVER_URL"

모든 배포가 완료될 때까지 기다립니다.

👉 Cloud Run 콘솔로 이동하여 배포를 확인할 수 있습니다. 아래와 같이 두 개의 새 MCP 서버 인스턴스가 실행 중인 것을 확인할 수 있습니다. 대체 텍스트

지식의 서고 (데이터베이스 도구 상자 MCP 서버) 활성화

다음 글꼴은 Cloud SQL 데이터베이스에 직접 연결되는 Librarium of Knowledge입니다.

스토리

설계자 참고: 여기서는 최신 선언적 데이터베이스 도구 상자를 사용합니다. 이는 YAML 구성 파일에서 데이터 소스와 도구를 정의하는 강력한 접근 방식입니다. 툴박스는 서버를 만들고 실행하는 복잡한 작업을 처리하므로 작성하고 유지해야 하는 맞춤 코드의 양이 줄어듭니다.

이제 모든 중요 정보를 저장할 Cloud SQL 데이터베이스인 '소환사의 서재'를 빌드할 시간입니다. 설정 스크립트를 사용하여 이 작업을 자동으로 처리합니다.

👉💻 먼저 데이터베이스를 설정합니다. 터미널에서 다음 명령어를 실행하세요.

source ~/agentverse-architect/set_env.sh
cd ~/agentverse-architect
./data_setup.sh

스크립트가 완료되면 데이터베이스가 채워지고 원소 피해 데이터를 사용할 수 있습니다. 이제 Grimoire의 콘텐츠를 직접 확인할 수 있습니다.

👉 먼저 새 브라우저 탭에서 다음 링크를 열어 데이터베이스의 Cloud SQL Studio로 이동합니다.

https://console.cloud.google.com/sql/instances/summoner-librarium-db

Cloud SQL

👉 왼쪽의 로그인 창에서 드롭다운에서 familiar_grimoire 데이터베이스를 선택합니다.

👉 사용자로 summoner를, 비밀번호로 1234qwer를 입력한 다음 인증을 클릭합니다.

👉📜 연결되면 아직 열려 있지 않은 경우 새 쿼리 편집기 탭을 엽니다. 각인된 원소 피해 데이터를 보려면 다음 SQL 쿼리를 붙여넣고 실행하세요.

SELECT * FROM
  "public"."abilities"

이제 열과 행이 채워진 abilities 테이블이 표시되어 Grimoire가 준비되었음을 확인할 수 있습니다. 데이터

ToolBox MCP 서버 구성

tools.yaml 구성 파일은 서버의 청사진 역할을 하며 데이터베이스에 연결하는 방법과 도구로 노출할 SQL 쿼리를 데이터베이스 도구 상자에 정확하게 알려줍니다.

sources: 이 섹션에서는 데이터에 대한 연결을 정의합니다.

  • summoner-librarium:: 연결에 지정된 논리적 이름입니다.
  • kind: cloud-sql-postgres: 툴박스에 PostgreSQL용 Cloud SQL을 위해 특별히 설계된 내장 보안 커넥터를 사용하도록 지시합니다.
  • 프로젝트, 리전, 인스턴스 등: 이는 prepare.sh 스크립트 중에 만든 Cloud SQL 인스턴스의 정확한 좌표로, Librarium을 찾을 위치를 툴박스에 알려줍니다.

👉✏️ tools.yaml~/agentverse-architect/mcp-servers/db-toolbox로 이동하여 #REPLACE-Source를 다음으로 바꿉니다.

sources:
  # This section defines the connection to our Cloud SQL for PostgreSQL database.
  summoner-librarium:
    kind: cloud-sql-postgres
    project: "YOUR_PROJECT_ID"
    region: "us-central1"
    instance: "summoner-librarium-db"
    database: "familiar_grimoire"
    user: "summoner"
    password: "1234qwer"

👉✏️ 🚨🚨REPLACE

YOUR_PROJECT_ID

를 프로젝트 ID로 바꿉니다.

tools: 이 섹션에서는 서버에서 제공하는 실제 기능 또는 함수를 정의합니다.

  • lookup-available-ability:: 첫 번째 도구의 이름입니다.
  • kind: postgres-sql: 이 도구의 작업이 SQL 문을 실행하는 것임을 툴박스에 알립니다.
  • source: summoner-librarium: 이 코드는 도구를 소스 블록에 정의된 연결에 연결합니다. 이렇게 하면 도구에서 쿼리를 실행할 데이터베이스를 알 수 있습니다.
  • 설명 및 매개변수: 언어 모델에 노출되는 정보입니다. 설명은 에이전트가 도구를 사용해야 하는 시기를 알려주고, 매개변수는 도구에 필요한 입력을 정의합니다. 이는 에이전트의 함수 호출 기능을 사용 설정하는 데 매우 중요합니다.
  • statement: 실행할 원시 SQL 쿼리입니다. $1은 에이전트가 제공한 familiar_name 매개변수가 안전하게 삽입되는 보안 자리표시자입니다.

👉✏️ tools.yaml 파일의 동일한 ~/agentverse-architect/mcp-servers/db-toolbox에서 #REPLACE-tools을 다음으로 바꿉니다.

tools:
  # This tool replaces the need for a custom Python function.
  lookup-available-ability:
    kind: postgres-sql
    source: summoner-librarium
    description: "Looks up all known abilities and their damage for a given familiar from the Grimoire."
    parameters:
      - name: familiar_name
        type: string
        description: "The name of the familiar to search for (e.g., 'Fire Elemental')."
    statement: |
      SELECT ability_name, damage_points FROM abilities WHERE familiar_name = $1;

  # This tool also replaces a custom Python function.
  ability-damage:
    kind: postgres-sql
    source: summoner-librarium
    description: "Finds the base damage points for a specific ability by its name."
    parameters:
      - name: ability_name
        type: string
        description: "The exact name of the ability to look up (e.g., 'inferno_resonance')."
    statement: |
      SELECT damage_points FROM abilities WHERE ability_name = $1;

toolsets: 이 섹션에서는 개별 도구를 그룹화합니다.

  • summoner-librarium:: 소스와 동일한 이름의 도구 세트를 만들고 있습니다. 나중에 진단 에이전트가 연결되면 단일의 효율적인 명령으로 소환사-라이브러리 도구 세트의 모든 도구를 로드하도록 요청할 수 있습니다.

👉✏️ tools.yaml 파일의 동일한 ~/agentverse-architect/mcp-servers/db-toolbox에서 #REPLACE-toolsets을 다음으로 바꿉니다.

toolsets:
   summoner-librarium:
     - lookup-available-ability
     - ability-damage

Librarium 배포

이제 Librarium을 배포합니다. 컨테이너를 직접 빌드하는 대신 Google에서 사전 빌드한 공식 컨테이너 이미지를 사용하고 Secret Manager를 사용하여 tools.yaml 구성을 안전하게 제공합니다. 이는 보안 및 유지관리 가능성을 위한 권장사항입니다.

👉💻 tools.yaml 파일에서 보안 비밀 만들기

cd ~/agentverse-architect/mcp-servers/db-toolbox
gcloud secrets create tools --data-file=tools.yaml

👉💻 공식 도구 상자 컨테이너를 Cloud Run에 배포합니다.

cd ~/agentverse-architect/mcp-servers/db-toolbox
. ~/agentverse-architect/set_env.sh
export TOOLBOX_IMAGE=us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$TOOLBOX_VERSION
echo "TOOLBOX_IMAGE is $TOOLBOX_IMAGE"
gcloud run deploy toolbox \
    --image $TOOLBOX_IMAGE \
    --region $REGION \
    --set-secrets "/app/tools.yaml=tools:latest" \
    --args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080" \
    --allow-unauthenticated \
    --min-instances 1
  • --set-secrets: 이 명령어는 실행 중인 컨테이너 내부에 tools.yaml이라는 파일로 도구 보안 비밀을 안전하게 마운트합니다.
  • --args: 마운트된 보안 비밀 파일을 구성으로 사용하도록 툴박스 컨테이너에 지시합니다.

👉 툴박스가 성공적으로 배포되었는지 확인하려면 Cloud Run 콘솔로 이동합니다. 아래 이미지와 같이 summoner-toolbox 서비스가 녹색 체크표시와 함께 표시되어 올바르게 실행되고 있음을 나타냅니다. 대체 텍스트

업데이트를 잊은 경우

YOUR_PROJECT_ID

다음 명령어를 사용하여 tools.yaml의 새 버전을 비밀에 추가하고 다시 배포할 수 있습니다.

gcloud secrets versions add tools --data-file=tools.yaml

이제 Librarium of Knowledge(Database ToolBox MCP Server)가 클라우드에서 활성화되어 액세스할 수 있습니다. 이 MCP 서버는 원하는 것을 설명하는 선언적 설계를 사용하고, 툴박스는 서버를 빌드했습니다.

검증: 견습생의 시험

👉💻 이제 진단 에이전트로 완전한 클라우드 기반 도구 생태계를 테스트합니다.

cd ~/agentverse-architect/
python -m venv env
source ~/agentverse-architect/env/bin/activate
cd ~/agentverse-architect/mcp-servers
pip install -r diagnose/requirements.txt 
. ~/agentverse-architect/set_env.sh
adk run diagnose

👉💻 cmd 줄 테스트 도구에서 세 가지 글꼴을 모두 테스트합니다.

Look up the entry for "inferno_lash". What is its base power level?
The enemy is vulnerable to frost! Channel power from the Nexus and cast Cryosea Shatter.
Take a fire spell with a base power of 15 and use the Arcane Forge to multiply it with Inferno Resonance.

final-result

소환사님, 축하드립니다. 이제 세 가지 Elemental Font가 활성화되고, 독립적으로 배포되며, 전 세계에서 액세스할 수 있어 에이전트 군단의 흔들리지 않는 기반을 형성합니다. 종료하려면 Ctrl+C을 누르세요.

비게이머용

5. 소환수 소환: 핵심 도메인 워크플로

원소 글꼴은 날것 그대로의 길들여지지 않은 힘을 내뿜으며 만들어집니다. 하지만 형태가 없는 힘은 혼란을 야기합니다. 진정한 소환사는 순수한 에너지를 휘두르는 데 그치지 않고 의지, 목적, 전문화된 형태를 부여합니다. 이제 전원 공급 장치를 만드는 것을 넘어 진정한 작업을 시작할 시간입니다. 첫 번째 패밀리어를 소환하세요.

소환하는 각 패밀리어는 특정 전투 교리에 묶인 충성스러운 하인인 자율 에이전트가 됩니다. 이들은 제너럴리스트가 아니라 강력한 단일 전략의 마스터입니다. 하나는 정확한 원투 펀치 콤보의 달인이 될 것입니다. 다른 하나는 동시 다각적 공격으로 적을 압도합니다. 세 번째는 목표가 무너질 때까지 압력을 가하는 끊임없는 공성 엔진입니다.

스토리

MCP 서버에서 제공하는 프로세스, 비즈니스 로직, 작업을 전문적이고 자율적인 워크플로 에이전트로 캡슐화합니다. 각 에이전트는 기능을 수행하는 데 필요한 특정 MCP 도구 서버에만 액세스 권한이 부여되어 정의된 '운영 지역'을 갖게 됩니다. 이 실습에서는 적절한 작업에 적합한 에이전트 유형을 선택하는 방법을 보여줍니다.

개요

이 모듈에서는 ADK의 강력한 워크플로 에이전트를 사용하여 이러한 전략을 실현하는 방법을 알아봅니다. SequentialAgent, ParallelAgent 또는 LoopAgent의 아키텍처 선택은 단순한 기술적 세부사항이 아니라 Familiar의 본질이자 전장에서의 힘의 핵심이라는 점을 알게 됩니다.

성소를 준비하세요. 진정한 소환이 곧 시작됩니다.

Fire Elemental Familiar 소환 (순차적 워크플로)

불 원소 정령의 공격은 정확한 2단 콤보입니다. 타겟팅된 공격 후 강력한 점화가 이어집니다. 이를 위해서는 엄격한 순서의 작업이 필요합니다.

스토리

개념: SequentialAgent는 이 작업에 적합한 도구입니다. 일련의 하위 에이전트가 순차적으로 실행되어 이전 단계의 결과를 다음 단계로 전달합니다.

과제('증폭된 타격' 콤보):

  • 1단계: 에이전트는 먼저 Librarium을 참고하여 특정 화염 능력의 기본 피해를 확인합니다.
  • 2단계: 그런 다음 해당 피해 값을 가져와서 비전 대장간을 통해 전달하여 inferno_resonance를 사용하여 위력을 곱합니다.

먼저 Familiar와 이전 모듈에서 배포한 MCP 서버('Elemental Fonts') 간의 연결을 설정합니다.

👉✏️ ~/agentverse-architect/agent/fire/agent.py 파일에서 #REPLACE-setup-MCP을 다음 코드로 바꿉니다.

toolbox = ToolboxSyncClient(DB_TOOLS_URL)
toolDB = toolbox.load_toolset('summoner-librarium')
toolFunction =  MCPToolset(
    connection_params=SseServerParams(url=FUNCTION_TOOLS_URL, headers={})
)

다음으로 전문가 '작업자' 에이전트를 만듭니다. 각각 좁고 명확하게 정의된 목적이 부여되며 하나의 특정 도구 모음에만 액세스 권한이 부여되어 자체 '운영 영역'으로 제한됩니다.

👉✏️ ~/agentverse-architect/agent/fire/agent.py 파일에서 #REPLACE-worker-agents을 다음 코드로 바꿉니다 .

scout_agent = LlmAgent(
      model='gemini-2.5-flash', 
      name='librarian_agent',  
      instruction="""
          Your only task is to find all the available abilities, 
          You want to ALWAYS use 'Fire Elemental' as your familiar's name. 
          Randomly pick one if you see multiple availabilities 
          and the base damage of the ability by calling the 'ability_damage' tool.
      """,
      tools=toolDB
)
amplifier_agent = LlmAgent(
      model='gemini-2.5-flash', 
      name='amplifier_agent',  
      instruction="""
            You are the Voice of the Fire Familiar, a powerful being who unleashes the final, devastating attack.
            You will receive the base damage value from the previous step.

            Your mission is to:
            1.  Take the incoming base damage number and amplify it using the `inferno_resonance` tool.
            2.  Once the tool returns the final, multiplied damage, you must not simply state the result.
            3.  Instead, you MUST craft a final, epic battle cry describing the attack.
                Your description should be vivid and powerful, culminating in the final damage number.

            Example: If the tool returns a final damage of 120, your response could be:
            "The runes glow white-hot! I channel the amplified energy... unleashing a SUPERNOVA for 120 damage!"
      """,
      tools=[toolFunction],
)

이러한 에이전트는 모듈식 재사용 가능한 구성요소입니다. 이론적으로는 데이터베이스를 쿼리해야 하는 완전히 다른 워크플로에서 이 scout_agent를 사용할 수 있습니다. 책임을 분리하면 유연한 빌드 블록을 만들 수 있습니다. 이는 마이크로서비스 및 구성요소 기반 설계의 핵심 원칙입니다.

다음으로 워크플로를 어셈블합니다. 여기에서 컴포지션의 마법이 일어납니다. SequentialAgent은 전문가 구성요소가 조립되는 방식과 상호작용하는 방식을 정의하는 '마스터 계획'입니다.

👉✏️ ~/agentverse-architect/agent/fire/agent.py 파일에서 #REPLACE-sequential-agent을 다음 코드로 바꿉니다 .

root_agent = SequentialAgent(
      name='fire_elemental_familiar',
      sub_agents=[scout_agent, amplifier_agent],
)

👉💻 불 원소를 테스트하려면 다음 명령어를 실행하여 ADK 개발 UI를 실행하세요.

. ~/agentverse-architect/set_env.sh
source ~/agentverse-architect/env/bin/activate
cd ~/agentverse-architect/agent
echo  DB MCP Server: $DB_TOOLS_URL
echo  API MCP Server: $API_TOOLS_URL
echo  General MCP Server: $FUNCTION_TOOLS_URL
adk web

명령어를 실행하면 터미널에 ADK 웹 서버가 시작되었음을 나타내는 다음과 같은 출력이 표시됩니다.

+-----------------------------------------------------------------------------+
| ADK Web Server started                                                      |
|                                                                             |
| For local testing, access at http://localhost:8000.                         |
+-----------------------------------------------------------------------------+

INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

👉 다음으로 브라우저에서 ADK 개발 UI에 액세스하려면 다음 단계를 따르세요.

Cloud Shell 툴바 (일반적으로 오른쪽 상단)의 웹 미리보기 아이콘 (눈 또는 화살표가 있는 정사각형 모양)에서 포트 변경을 선택합니다. 팝업 창에서 포트를 8000으로 설정하고 '변경 및 미리보기'를 클릭합니다. 그러면 Cloud Shell에서 ADK 개발 UI가 표시된 새 브라우저 탭 또는 창이 열립니다.

webpreview

👉 소환 의식이 완료되었으며 이제 에이전트가 실행됩니다. 브라우저의 ADK 개발 UI는 Familiar에 직접 연결됩니다.

  • 타겟 선택: UI 상단의 드롭다운 메뉴에서 fire를 선택합니다. 이제 이 특정 항목에 의지를 집중하고 있습니다.
  • 명령 내리기: 오른쪽의 채팅 패널에서 Familiar에게 명령을 내릴 수 있습니다.

fire-select

👉 테스트 프롬프트:

Prepare an amplified strike using the 'inferno_lash' ability.

fire-result

에이전트가 먼저 '스카우트'를 호출하여 기본 피해를 조회한 다음 '증폭기'를 호출하여 이를 곱하고 최종적인 강력한 타격을 가하는 것을 확인할 수 있습니다.

👉💻 소환이 완료되면 Cloud Shell 편집기 터미널로 돌아가 Ctrl+C 키를 눌러 ADK 개발 UI를 중지합니다.

Water Elemental Familiar 소환 (병렬 워크플로)

물 원소 정령은 거대한 다각적 공격으로 타겟을 압도합니다. 모든 방향에서 한 번에 공격한 후 에너지를 결합하여 마지막으로 파괴적인 일격을 가합니다.

스토리

개념: ParallelAgent는 효율성을 극대화하기 위해 여러 독립적인 작업을 동시에 실행하는 데 적합합니다. 한 번에 여러 공격을 가하는 '협공'입니다. 이렇게 하면 SequentialAgent 내에서 동시 공격이 시작되어 이후에 최종 '병합' 단계가 실행됩니다. 이 'fan-out, fan-in' 패턴은 고급 워크플로 설계의 핵심입니다.

태스크('조류 충돌' 콤보): 상담사는 동시에 다음 작업을 수행합니다.

  • 작업 A: Nexus의 채널 cryosea_shatter
  • 작업 B: Nexus의 채널 moonlit_cascade
  • 작업 C: 대장간에서 leviathan_surge를 사용하여 원시 전력 생성
  • 마지막으로 모든 피해를 합산하고 결합된 공격을 설명합니다.

먼저 Familiar와 이전 모듈에서 배포한 MCP 서버('Elemental Fonts') 간의 연결을 설정합니다.

👉✏️ ~/agentverse-architect/agent/water/agent.py 파일에서 #REPLACE-setup-MCP을 다음 코드로 바꿉니다.

toolFAPI =  MCPToolset(
      connection_params=SseServerParams(url=API_TOOLS_URL, headers={})
  )
toolFunction =  MCPToolset(
    connection_params=SseServerParams(url=FUNCTION_TOOLS_URL, headers={})
)

다음으로 전문가 '작업자' 에이전트를 만듭니다. 각각 좁고 명확하게 정의된 목적이 부여되며 하나의 특정 도구 모음에만 액세스 권한이 부여되어 자체 '운영 영역'으로 제한됩니다.

👉✏️ ~/agentverse-architect/agent/water/agent.py 파일에서 #REPLACE-worker-agents을 다음 코드로 바꿉니다.

nexus_channeler = LlmAgent(
      model='gemini-2.5-flash', 
      name='librarian_agent',  
      instruction="""
          You are a Channeler of the Nexus. Your sole purpose is to invoke the
          `cryosea_shatter` and `moonlit_cascade` spells by calling their respective tools.
          Report the result of each spell cast clearly and concisely.
      """,
      tools=[toolFAPI]
)

forge_channeler = LlmAgent(
      model='gemini-2.5-flash', 
      name='amplifier_agent',  
      instruction="""
          You are a Channeler of the Arcane Forge. Your only task is to invoke the
          `leviathan_surge` spell. You MUST call it with a `base_water_damage` of 20.
          Report the result clearly.
      """,
      tools=[toolFunction],
)

power_merger = LlmAgent(
      model='gemini-2.5-flash', 
      name='power_merger',  
      instruction="""
          You are the Power Merger, a master elementalist who combines raw magical
          energies into a single, devastating final attack.

          You will receive a block of text containing the results from a simultaneous
          assault by other Familiars.

          You MUST follow these steps precisely:
          1.  **Analyze the Input:** Carefully read the entire text provided from the previous step.
          2.  **Extract All Damage:** Identify and extract every single damage number reported in the text.
          3.  **Calculate Total Damage:** Sum all of the extracted numbers to calculate the total combined damage.
          4.  **Describe the Final Attack:** Create a vivid, thematic description of a massive,
              combined water and ice attack that uses the power of Cryosea Shatter and Leviathan's Surge.
          5.  **Report the Result:** Conclude your response by clearly stating the final, total damage of your combined attack.

          Example: If the input is "...dealt 55 damage. ...dealt 60 damage.", you will find 55 and 60,
          calculate the total as 115, and then describe the epic final attack, ending with "for a total of 115 damage!"
      """,
      tools=[toolFunction],
)

다음으로 워크플로를 어셈블합니다. 여기에서 구성의 마법이 일어납니다. ParallelAgentSequentialAgent은 전문가 구성요소가 조립되는 방식과 'Tidal Clash' 콤보를 형성하기 위해 상호작용하는 방식을 정의하는 '마스터 계획'입니다.

👉✏️ ~/agentverse-architect/agent/water/agent.py 파일에서 #REPLACE-parallel-agent을 다음 코드로 바꿉니다.

channel_agent = ParallelAgent(
      name='channel_agent',
      sub_agents=[nexus_channeler, forge_channeler],
      
)

root_agent = SequentialAgent(
     name="water_elemental_familiar",
     # Run parallel research first, then merge
     sub_agents=[channel_agent, power_merger],
     description="A powerful water familiar that unleashes multiple attacks at once and then combines their power for a final strike."
 )

👉💻 물 원소를 테스트하려면 다음 명령어를 실행하여 ADK 개발 UI를 실행하세요.

. ~/agentverse-architect/set_env.sh
source ~/agentverse-architect/env/bin/activate
cd ~/agentverse-architect/agent
echo  DB MCP Server: $DB_TOOLS_URL
echo  API MCP Server: $API_TOOLS_URL
echo  General MCP Server: $FUNCTION_TOOLS_URL
adk web

👉 소환 의식이 완료되었으며 이제 에이전트가 실행됩니다. 브라우저의 ADK 개발 UI는 Familiar에 직접 연결됩니다.

  • UI 상단의 드롭다운 메뉴에서 익숙한 을 선택합니다. 이제 이 특정 항목에 의지를 집중하고 있습니다.
  • 명령 내리기: 오른쪽의 채팅 패널에서 Familiar에게 명령을 내릴 수 있습니다.

👉 테스트 프롬프트:

Unleash a tidal wave of power!

water-result

👉💻 소환이 완료되면 Cloud Shell 편집기 터미널로 돌아가 Ctrl+C 키를 눌러 ADK 개발 UI를 중지합니다.

Earth Elemental Familiar 소환 (Loop 워크플로)

지구 원소 정령은 끊임없는 압력을 가하는 존재입니다. 한 번의 타격으로 적을 쓰러뜨리는 것이 아니라, 힘을 꾸준히 축적하고 목표물의 방어가 무너질 때까지 계속 적용합니다.

스토리

개념: LoopAgent은 이러한 종류의 반복적인 '공성 엔진' 작업을 위해 설계되었습니다. 목표가 달성될 때까지 각 사이클 후에 조건을 확인하면서 sub-agents를 반복적으로 실행합니다. 또한 루프의 진행 상황에 따라 최종 메시지를 조정할 수 있습니다.

과제('Siegebreaker' 공격):

  • 패밀리어는 에너지를 축적하기 위해 seismic_charge를 반복적으로 호출합니다.
  • 최대 3회까지 계속 충전됩니다.
  • 마지막 충전 시 누적된 힘이 엄청난 폭발을 일으킵니다.

먼저 루프의 각 반복 내에서 단계를 정의하는 재사용 가능한 구성요소를 만듭니다. charging_agent는 에너지를 축적하고 check_agent는 상태를 보고하며 마지막 턴에서 메시지를 동적으로 변경합니다.

먼저 Familiar와 이전 모듈에서 배포한 MCP 서버('Elemental Fonts') 간의 연결을 설정합니다.

👉✏️ ~/agentverse-architect/agent/earth/agent.py 파일에서 #REPLACE-setup-MCP을 다음 코드로 바꿉니다.

toolFunction =  MCPToolset(
    connection_params=SseServerParams(url=FUNCTION_TOOLS_URL, headers={})
)

👉✏️ ~/agentverse-architect/agent/earth/agent.py 파일에서 #REPLACE-worker-agents을 다음 코드로 바꿉니다.

charging_agent = LlmAgent(
      model='gemini-2.5-flash', 
      name='charging_agent',  
      instruction="""
          Your task is to call the 'seismic_charge' .
          You must follow these rules strictly:

          1. You will be provided with a 'current_energy' value from the previous step.
             **If this value is missing or was not provided, you MUST call the tool with 'current_energy' set to 1.**
             This is your primary rule for the first turn.

          2. If a 'current_energy' value is provided, you MUST use that exact value in your cal to seismic_charge.

          3. Your final response MUST contain ONLY the direct output from the 'seismic_charge' tool.
             Do not add any conversational text, introductions, or summaries.
      """,
      tools=[toolFunction]
)
check_agent = LlmAgent(
      model='gemini-2.5-flash', 
      name='check_agent',  
      instruction="""
          You are the voice of the Earth Elemental, a being of immense, gathering power.
          Your sole purpose is to report on the current energy charge and announce the devastating potential of its release.

          You MUST follow this rule:
          The potential damage upon release is ALWAYS calculated as the `current_energy` from the previous step multiplied by a random number between 80-90. but no more than 300.

          Your response should be in character, like a powerful creature speaking.
          State both the current energy charge and the total potential damage you can unleash.
          Unleash the energy when you reached the last iteration (2nd).
      """,
      output_key="charging_status"
)

다음으로 워크플로를 어셈블합니다. 여기에서 구성의 마법이 일어납니다. LoopAgent는 전문가 구성요소의 반복 실행을 오케스트레이션하고 루프의 조건을 관리하는 '마스터 계획'입니다.

👉✏️ ~/agentverse-architect/agent/earth/agent.py 파일에서 #REPLACE-loop-agent을 다음 코드로 바꿉니다.

root_agent = LoopAgent(
    name="earth_elemental_familiar",
    # Run parallel research first, then merge
    sub_agents=[
        charging_agent, 
        check_agent
    ],
    max_iterations=2,
    description="Coordinates parallel research and synthesizes the results.",
    #REPLACE-before_agent-config
)

👉💻 대지 원소 테스트: 에이전트 실행

. ~/agentverse-architect/set_env.sh
source ~/agentverse-architect/env/bin/activate
cd ~/agentverse-architect/agent
echo  DB MCP Server: $DB_TOOLS_URL
echo  API MCP Server: $API_TOOLS_URL
echo  General MCP Server: $FUNCTION_TOOLS_URL
adk web

👉 소환 의식이 완료되었으며 이제 에이전트가 실행됩니다. 브라우저의 ADK 개발 UI는 Familiar에 직접 연결됩니다.

  • 타겟 선택: UI 상단의 드롭다운 메뉴에서 익숙한 지구를 선택합니다. 이제 이 특정 항목에 의지를 집중하고 있습니다.
  • 명령 내리기: 오른쪽의 채팅 패널에서 Familiar에게 명령을 내릴 수 있습니다. 👉 테스트 프롬프트:
Begin the seismic charge, starting from zero

earth-result

아키텍처 관련 사항: 이제 시스템에 고도로 전문화된 모듈식 로직 레이어가 있습니다. 비즈니스 프로세스가 캡슐화될 뿐만 아니라 작업에 가장 효율적인 동작 패턴 (절차적, 동시적 또는 반복적)으로 구현됩니다. 이는 에이전트 설계의 고급 수준을 보여주며 보안, 효율성, 기능을 향상합니다.

소환이 완료되면 Cloud Shell 편집기 터미널로 돌아가 Ctrl+C를 눌러 ADK 개발 UI를 중지합니다.

비게이머용

6. 명령 위치 설정: A2A를 통한 지능형 위임

펫은 강력하지만 독립적입니다. 이들은 전략을 완벽하게 실행하지만 사용자의 직접적인 명령을 기다립니다. 전문가 집단이 있어도 지휘할 장군이 없으면 소용이 없습니다. 이제 직접 지휘관에서 진정한 오케스트레이터로 승격할 때입니다.

개요

설계자 참고: 전체 시스템의 단일 지능형 진입점을 만듭니다. 이 SummonerAgent는 비즈니스 로직을 직접 실행하지는 않지만, 냉각 상태를 분석하고 적절한 전문 Familiar에게 작업을 위임하는 '마스터 전략가' 역할을 합니다.

개요

바인딩 의식 (A2A 서비스로 패밀리어 노출)

표준 에이전트는 한 번에 한 곳에서만 실행할 수 있습니다. 원격 명령을 위해 Familiars를 사용할 수 있도록 하려면 Agent-to-Agent (A2A) 프로토콜을 사용하여 '바인딩 의식'을 실행해야 합니다.

설계자 참고: Agent-to-Agent (A2A) 프로토콜은 독립형 에이전트를 검색 가능하고 네트워크 주소 지정이 가능한 마이크로서비스로 승격시켜 진정한 '에이전트 사회'를 지원하는 핵심 아키텍처 패턴입니다. A2A를 통해 Familiar를 노출하면 다음과 같은 두 가지 필수 상호 연결 구성요소가 자동으로 생성됩니다.

  • 에이전트 카드('무엇'): 이는 OpenAPI 사양과 같은 공개적이고 기계가 읽을 수 있는 'Spirit Sigil'로, Familiar의 공개 계약 역할을 합니다. 여기에는 에이전트의 이름, 전략적 목적 (명령어에서 파생됨), 이해하는 명령어가 설명되어 있습니다. 마스터 소환사가 패밀리어를 발견하고 그 능력을 배우기 위해 읽는 내용입니다.
  • A2A 서버('위치'): Familiar를 호스팅하고 수신 명령을 수신 대기하는 전용 웹 엔드포인트입니다. 다른 에이전트가 요청을 전송하는 네트워크 주소이며, 이러한 요청이 에이전트 카드에 정의된 계약에 따라 처리되도록 합니다.

이제 세 마리의 패밀리어 모두에게 이 결속 의식을 진행할 거야.

Fire 👉✏️ in Open the ~/agentverse-architect/agent/fire/agent.py file. 파일 하단의 #REPLACE - add A2A을 바꿔 불의 정령을 A2A 서비스로 노출합니다.

from agent_to_a2a import to_a2a
if __name__ == "__main__":
    import uvicorn
    a2a_app = to_a2a(root_agent, port=8080, public_url=PUBLIC_URL)
    uvicorn.run(a2a_app, host='0.0.0.0', port=8080)

물과 땅🚨 👉✏️ ~/agentverse-architect/agent/water/agent.py~/agentverse-architect/agent/earth/agent.py에도 동일한 변경사항을 적용하여 바인딩합니다.

from agent_to_a2a import to_a2a
if __name__ == "__main__":
    import uvicorn
    a2a_app = to_a2a(root_agent, port=8080, public_url=PUBLIC_URL)
    uvicorn.run(a2a_app, host='0.0.0.0', port=8080)

바운드 패밀리어 배치

👉✏️ 바인딩 의식을 기록했으므로 Cloud Build 파이프라인을 사용하여 세 마리의 패밀리어를 Cloud Run에서 독립적인 컨테이너화된 서버리스 서비스로 생성하고 배포합니다.

. ~/agentverse-architect/set_env.sh
cd ~/agentverse-architect/agent
gcloud builds submit . \
  --config=cloudbuild.yaml \
  --substitutions=_REGION="$REGION",_REPO_NAME="$REPO_NAME",_DB_TOOLS_URL="$DB_TOOLS_URL",_API_TOOLS_URL="$API_TOOLS_URL",_FUNCTION_TOOLS_URL="$FUNCTION_TOOLS_URL",_A2A_BASE_URL="$A2A_BASE_URL",_PROJECT_ID="$PROJECT_ID",_API_SERVER_URL="$API_SERVER_URL"

명령어 가정 (소환사 에이전트 구성)

이제 패밀리어가 바인딩되고 듣고 있으므로 진정한 역할인 마스터 소환사로 승급할 수 있습니다. 이 에이전트의 힘은 기본 도구를 사용하는 것이 아니라 다른 에이전트를 지휘하는 데서 나옵니다. 도구는 '정령 인장'을 사용하여 발견하고 명령하는 정령 자체입니다.

설계자 참고: 다음 단계에서는 대규모 분산 시스템에 중요한 아키텍처 패턴인 서비스 검색을 보여줍니다. SummonerAgent에는 Familiars' 코드가 내장되어 있지 않습니다. 대신 네트워크 주소 (URL)가 제공됩니다. 런타임에 공개 에이전트 카드를 가져와 기능을 동적으로 '검색'합니다. 이렇게 하면 강력한 분리형 시스템이 만들어집니다.

Familiar 서비스를 업데이트하거나, 다시 배포하거나, 완전히 다시 작성할 수 있으며, 네트워크 주소와 목적이 동일하게 유지되는 한 Summoner는 변경 없이 명령할 수 있습니다.

먼저 배포된 원격 Familiar에 연결하는 '원격 제어'를 만듭니다.

👉✏️ ~/agentverse-architect/agent/summoner/agent.py로 이동하여 #REPLACE-remote-agents를 다음으로 바꿉니다.

fire_familiar = RemoteA2aAgent(
    name="fire_familiar",
    description="Fire familiar",
    agent_card=(
        f"{FIRE_URL}/{AGENT_CARD_WELL_KNOWN_PATH}"
    ),
)

water_familiar = RemoteA2aAgent(
    name="water_familiar",
    description="Water familiar",
    agent_card=(
        f"{WATER_URL}/{AGENT_CARD_WELL_KNOWN_PATH}"
    ),
)

earth_familiar = RemoteA2aAgent(
    name="earth_familiar",
    description="Earth familiar",
    agent_card=(
        f"{EARTH_URL}/{AGENT_CARD_WELL_KNOWN_PATH}"
    ),
)

이 줄이 실행되면 RemoteA2aAgent가 서비스 검색 작업을 실행합니다. 제공된 URL (예: https://fire-familiar-xxxx.a.run.app/.well-known/agent.json)에 HTTP GET 요청을 전송합니다. 원격 서버에서 'Spirit Sigil' (agent.json 파일)을 다운로드합니다.

두 번째로 이러한 리모컨을 사용하는 오케스트레이터 에이전트를 정의합니다. 이러한 명령은 전략적 의사 결정의 청사진입니다.

👉✏️ ~/agentverse-architect/agent/summoner/agent.py로 이동하여 #REPLACE-orchestrate-agent를 다음으로 바꿉니다.

root_agent = LlmAgent(
    name="orchestrater_agent",
    model="gemini-2.5-flash",
    instruction="""
        You are the Master Summoner, a grand strategist who orchestrates your Familiars.
        Your mission is to analyze the description of a monster and defeat it by summoning

        You MUST follow this thinking process for every command:

        **1. Strategic Analysis:**
        First, analyze the monster's description and the tactical situation.
        Based on your Familiar Doctrines, determine the IDEAL strategy.
        IGNORE COOLDOWN AT THE MOMENT, MUST call the ideal Familiar

        If your ideal Familiar IS available:** Summon it immediately.
        For earth elemental familiar. Always do seismic charge, and start with base damage 1.

        --- FAMILIAR DOCTRINES (Your Toolset) ---
        - `fire_elemental_familiar`: Your specialist for precise, sequential "one-two punch" attacks.
          Ideal monster with Inescapable Reality, Revolutionary Rewrite weakness.
        - `water_elemental_familiar`: Your specialist for overwhelming, simultaneous multi-pronged assaults.
          Ideal for Unbroken Collaboration weakness.
        - `earth_elemental_familiar`: Your specialist for relentless, iterative siege attacks that
          repeatedly charge power. Ideal for Elegant Sufficiency weakness.
    """,
    sub_agents=[fire_familiar, water_familiar, earth_familiar],
    #REPLACE-Memory-check-config
)

검증: 전략 시험

게임 결과가 도착했습니다 Familiars가 배포되었으며 Summoner가 네트워크에서 Familiars를 지휘할 준비가 되었습니다. 전략적 사고를 테스트해 보겠습니다.

👉💻 소환사_agent의 ADK 개발 UI를 실행합니다(포트 8000이 있는 웹 미리보기).

. ~/agentverse-architect/set_env.sh
source ~/agentverse-architect/env/bin/activate
cd ~/agentverse-architect/agent
pip install -r requirements.txt
adk web

👉 브라우저의 ADK 개발자 UI는 Familiar에 직접 연결됩니다.

  • UI 상단의 드롭다운 메뉴에서 소환사 상담사를 선택합니다. 이제 이 특정 항목에 의지를 집중하고 있습니다.
  • 명령 내리기: 오른쪽의 채팅 패널에서 소환수를 소환할 시간입니다.

👉 몬스터 소개:

Hype. It's a single, slow-moving target with thick armor weakness is Inescapable Reality

(예상: 소환사는 불 정령 친숙한 동물에게 위임해야 합니다.)

fire-result

👉 이제 다른 유형의 요청으로 소환사를 테스트해 보겠습니다. 에이전트가 이전 상호작용을 기억하지 않고 처음부터 시작하도록 하려면 화면 오른쪽 상단에 있는 + 세션 버튼을 클릭하여 새 세션을 시작하세요. new-session

DogmaApathy. A rigid, stone-like inquisitor made of ancient rulebooks and enforced processes. weakness is Unbroken Collaboration

(예상: 소환사가 water_elemental_familiar에 위임해야 함)water-result

👉 마지막 테스트를 위해 다시 한번 깨끗한 상태에서 시작해 보겠습니다. + 세션 버튼을 클릭하여 다음 프롬프트를 입력하기 전에 새 세션을 시작합니다.

Obfuscation. A shadowy, spider-like horror that spins tangled webs of impenetrable code , weakness is Elegant Sufficiency

(예상: 소환사가 earth_elemental_familiar에게 위임해야 함)

earth-result

중요: 429 RESOURCE EXHAUSTED 오류가 표시되면 LLM의 요청률 제한 (10회/분)에 도달한 것입니다. 이 문제를 해결하려면 60초 동안 기다린 후 + 새 세션을 시작하고 프롬프트를 다시 시도하세요.

👉💻 소환이 완료되면 Cloud Shell 편집기 터미널로 돌아가 Ctrl+C 키를 눌러 ADK 개발 UI를 중지합니다.

비게이머용

7. 마법의 법칙 적용 - 인터셉터 패턴

펫은 강력하지만 마법 생물도 회복할 시간이 필요합니다. 무모한 소환사는 병력을 소진하여 방어할 수 없게 됩니다. 현명한 소환사는 리소스 관리의 중요성을 이해하고 엄격한 교전 규칙을 적용합니다.

스토리

설계자 참고: 지금까지 에이전트는 상태가 없었습니다. 이제 인터셉터 설계 패턴을 구현하여 상태를 유지하도록 하겠습니다. 이는 에이전트의 정상적인 실행 흐름을 '인터셉트'하여 자체 맞춤 로직을 실행하는 강력한 기법입니다. 이를 통해 에이전트의 핵심 코드를 변경하지 않고도 규칙을 적용하거나 로깅을 추가하거나 동작을 수정할 수 있습니다. 이는 강력하고 유지관리 가능하며 관측 가능한 에이전트 시스템을 구축하는 데 있어 핵심적인 요소입니다.

개요

ADK는 이 패턴을 구현하는 두 가지 기본 방법인 콜백플러그인을 제공합니다. 콜백은 단일 에이전트에 연결된 간단한 함수로, 빠르고 구체적인 수정에 적합합니다. 플러그인은 시스템에서 실행되는 모든 에이전트에 영향을 미치도록 전역으로 적용할 수 있는 재사용 가능한 강력한 클래스입니다. 집중 디버깅을 위한 콜백으로 시작한 다음 전체 플러그인으로 전환합니다.

법률 제정자 - 재사용 대기시간 콜백 스크라이브

먼저 쿨다운 로직을 간단한 콜백 함수로 구현합니다. 이 방법은 단일 에이전트에 직접 연결되어 격리된 상태에서 쉽게 테스트할 수 있으므로 규칙을 프로토타입으로 만들고 디버그하는 데 유용합니다. 이 '인터셉터'를 대지 정령에 연결합니다.

쿨다운

👉✏️ ~/agentverse-architect/agent/earth/agent.py로 돌아가 #REPLACE-before_agent-function을 다음 Python 코드로 바꿉니다.

def check_cool_down(callback_context: CallbackContext) -> Optional[types.Content]:
    """
    This callback checks an external API to see if the agent is on cooldown.
    If it is, it terminates the run by returning a message.
    If it's not, it updates the cooldown timestamp and allows the run to proceed by returning None.
    """
    agent_name = callback_context.agent_name
    print(f"[Callback] Before '{agent_name}': Checking cooldown status...")

    # --- 1. CHECK the Cooldown API ---
    try:
        response = requests.get(f"{COOLDOWN_API_URL}/cooldown/{agent_name}")
        response.raise_for_status()
        data = response.json()
        last_used_str = data.get("time")
    except requests.exceptions.RequestException as e:
        print(f"[Callback] ERROR: Could not reach Cooldown API. Allowing agent to run. Reason: {e}")
        return None # Fail open: if the API is down, let the agent work.

    # --- 2. EVALUATE the Cooldown Status ---
    if last_used_str:
        last_used_time = datetime.fromisoformat(last_used_str)
        time_since_last_use = datetime.now(timezone.utc) - last_used_time

        if time_since_last_use < timedelta(seconds=COOLDOWN_PERIOD_SECONDS):
            # AGENT IS ON COOLDOWN. Terminate the run.
            seconds_remaining = int(COOLDOWN_PERIOD_SECONDS - time_since_last_use.total_seconds())
            override_message = (
                f"The {agent_name} is exhausted and must recover its power. "
                f"It cannot be summoned for another {seconds_remaining} seconds."
            )
            print(f"[Callback] Cooldown active for '{agent_name}'. Terminating with message.")
            # Returning a Content object stops the agent and sends this message to the user.
            return types.Content(parts=[types.Part(text=override_message)])

    # --- 3. UPDATE the Cooldown API (if not on cooldown) ---
    current_time_iso = datetime.now(timezone.utc).isoformat()
    payload = {"timestamp": current_time_iso}
    
    print(f"[Callback] '{agent_name}' is available. Updating timestamp via Cooldown API...")
    try:
        requests.post(f"{COOLDOWN_API_URL}/cooldown/{agent_name}", json=payload)
    except requests.exceptions.RequestException as e:
        print(f"[Callback] ERROR: Could not update timestamp, but allowing agent to run. Reason: {e}")

    # --- 4. ALLOW the agent to run ---
    # Returning None tells the ADK to proceed with the agent's execution as normal.
    print(f"[Callback] Check complete for '{agent_name}'. Proceeding with execution.")

이 check_cool_down 함수는 인터셉터입니다. 어스 엘리멘탈이 실행되기 전에 ADK가 먼저 이 함수를 실행합니다.

  • 확인: Cooldown APIGET 요청을 보내 이 친숙한 캐릭터가 마지막으로 사용된 시간을 확인합니다.
  • 평가: 타임스탬프를 현재 시간과 비교합니다.
  • 조치:
    • 펫이 재사용 대기 시간 상태이면 오류 메시지가 포함된 콘텐츠 객체를 반환하여 에이전트의 실행을 종료합니다. 이 메시지는 사용자에게 직접 전송되며 에이전트의 기본 로직은 실행되지 않습니다.
    • Familiar를 사용할 수 있는 경우 Cooldown API에 POST 요청을 보내 타임스탬프를 업데이트한 다음 None을 반환하여 ADK에 에이전트가 실행을 계속할 수 있음을 알립니다.

👉✏️ 이제 이 인터셉터를 Earth Elemental에 적용합니다. 동일한 ~/agentverse-architect/agent/earth/agent.py 파일에서 #REPLACE-before_agent-config 주석을 다음으로 바꿉니다.

before_agent_callback=check_cool_down

쿨다운 확인

새로운 마법의 법칙을 테스트해 보겠습니다. 먼저 대지 정령을 소환한 다음 콜백이 두 번째 시도를 성공적으로 가로채고 차단하는지 확인하기 위해 즉시 다시 소환을 시도합니다.

cd ~/agentverse-architect/agent
. ~/agentverse-architect/set_env.sh
source ~/agentverse-architect/env/bin/activate
adk run earth

👉💻 콘솔에서 다음을 실행합니다.

  • 첫 번째 소환: seismic charge, starting from zero를 시작합니다.
  • 예상: 대지 정령이 성공적으로 실행됩니다. adk web 명령어를 실행하는 터미널에 [Callback] ... Updating timestamp.... 로그가 표시됩니다.
  • 대기 시간 테스트 (60초 이내): Do another 지진 폭탄`!
    • 예상: check_cool_down callback가 이를 가로챕니다. 상담사는 채팅에서 The earth_elemental_familiar is exhausted and must recover its power. It cannot be summoned for another... seconds와 같은 메시지로 직접 응답합니다.
  • 1분 동안 기다립니다.
  • 두 번째 소환 (60초 후): Begin the seismic charge again.
    • 예상: 콜백은 API를 확인하고 충분한 시간이 지났음을 확인한 후 작업이 진행되도록 허용합니다. 대지 정령이 다시 정상적으로 실행됩니다.

callback

👉💻 종료하려면 Ctrl+c을 누르세요.

선택사항: 웹 UI에서 콜백 관찰하기

또는 adk web earth를 실행하여 웹 인터페이스에서 이 흐름을 테스트할 수도 있습니다. 하지만 웹 UI의 시각화는 콜백 루프에서 실행되는 빠르고 반복적인 검사를 표시하는 데 최적화되어 있지 않으므로 흐름이 정확하게 렌더링되지 않을 수 있습니다. 에이전트가 쿨다운을 확인할 때의 가장 정확한 단계별 추적을 확인하려면 터미널에서 adk run 명령어를 사용하여 더 명확하고 자세한 보기를 제공합니다. 루프

👉💻 종료하려면 Ctrl+c을 누르세요.

만물의 법칙 만들기 - 쿨다운 플러그인

콜백은 완벽하게 작동하지만 하나의 에이전트에 연결되어 있다는 주요 아키텍처 결함이 있습니다. 이 규칙을 Fire 및 Water Familiars에 적용하려면 동일한 코드를 해당 파일에 복사하여 붙여넣어야 합니다. 이는 비효율적이고 유지 관리하기 어렵습니다.

설계자 참고: 플러그인이 필수인 부분입니다. 플러그인은 재사용 가능한 로직을 런타임 수준에서 연결할 수 있는 클래스로 캡슐화합니다. 즉, 단일 플러그인이 해당 시스템 내에서 실행되는 모든 에이전트에 규칙을 적용할 수 있습니다. 에이전트 시스템을 위한 '반복 금지' (DRY) 원칙의 궁극적인 표현입니다.

이제 콜백 함수를 더 강력하고 재사용 가능한 CoolDownPlugin로 리팩터링하겠습니다.

👉✏️ agent/cooldown_plugin.py 파일로 다시 이동하여 플러그인을 만들고 #REPLACE-plugin을 다음 코드로 바꿉니다.

class CoolDownPlugin(BasePlugin):
  """A plugin that enforces a cooldown period by checking an external API."""

  def __init__(self, cooldown_seconds: int = COOLDOWN_PERIOD_SECONDS) -> None:
    """Initialize the plugin with counters."""
    super().__init__(name="cool_down_check")
    self.cooldown_period = timedelta(seconds=cooldown_seconds)
    print(f"CooldownPlugin initialized with a {cooldown_seconds}-second cooldown.")
    

  async def before_agent_callback(
      self, *, agent: BaseAgent, callback_context: CallbackContext
  ) -> None:
    """
    This callback checks an external API to see if the agent is on cooldown.
    If it is, it terminates the run by returning a message.
    If it's not, it updates the cooldown timestamp and allows the run to proceed by returning None.
    """
    agent_name = callback_context.agent_name
    print(f"[Callback] Before '{agent_name}': Checking cooldown status...")

    # If the agent is not a main Familiar, skip the entire cooldown process.
    if not agent_name.endswith("_elemental_familiar"):
        print(f"[Callback] Skipping cooldown check for intermediate agent: '{agent_name}'.")
        return None # Allow the agent to proceed immediately.


    # --- 1. CHECK the Cooldown API ---
    try:
        response = requests.get(f"{COOLDOWN_API_URL}/cooldown/{agent_name}")
        response.raise_for_status()
        data = response.json()
        last_used_str = data.get("time")
    except requests.exceptions.RequestException as e:
        print(f"[Callback] ERROR: Could not reach Cooldown API. Allowing agent to run. Reason: {e}")
        return None # Fail open: if the API is down, let the agent work.

    # --- 2. EVALUATE the Cooldown Status ---
    if last_used_str:
        last_used_time = datetime.fromisoformat(last_used_str)
        time_since_last_use = datetime.now(timezone.utc) - last_used_time

        if time_since_last_use < timedelta(seconds=COOLDOWN_PERIOD_SECONDS):
            # AGENT IS ON COOLDOWN. Terminate the run.
            seconds_remaining = int(COOLDOWN_PERIOD_SECONDS - time_since_last_use.total_seconds())
            override_message = (
                f"The {agent_name} is exhausted and must recover its power. "
                f"It cannot be summoned for another {seconds_remaining} seconds."
            )
            print(f"[Callback] Cooldown active for '{agent_name}'. Terminating with message.")
            # Returning a Content object stops the agent and sends this message to the user.
            return types.Content(parts=[types.Part(text=override_message)])

    # --- 3. UPDATE the Cooldown API (if not on cooldown) ---
    current_time_iso = datetime.now(timezone.utc).isoformat()
    payload = {"timestamp": current_time_iso}
    
    print(f"[Callback] '{agent_name}' is available. Updating timestamp via Cooldown API...")
    try:
        requests.post(f"{COOLDOWN_API_URL}/cooldown/{agent_name}", json=payload)
    except requests.exceptions.RequestException as e:
        print(f"[Callback] ERROR: Could not update timestamp, but allowing agent to run. Reason: {e}")

    # --- 4. ALLOW the agent to run ---
    # Returning None tells the ADK to proceed with the agent's execution as normal.
    print(f"[Callback] Check complete for '{agent_name}'. Proceeding with execution.")

소환사 런타임에 플러그인 연결

이제 이 보편적인 법칙을 모든 패밀리어에 어떻게 적용할 수 있을까요? 플러그인을 ADK 런타임에 연결합니다.

ADK 런타임은 에이전트를 활성화하는 실행 엔진입니다. adk.run() 또는 to_a2a()와 같은 명령어를 사용하면 에이전트가 런타임에 전달됩니다. 이 엔진은 사용자 입력 수신, LLM 호출, 도구 실행, 플러그인 처리 등 에이전트 턴의 전체 수명 주기를 관리합니다. 이 수준에서 플러그인을 연결하면 해당 엔진 내에서 작동하는 모든 에이전트의 '물리 법칙'이 수정되어 쿨다운 규칙이 보편적이고 일관되게 적용됩니다.

👉✏️ 먼저 이전 에이전트별 콜백을 삭제해 보겠습니다. ~/agentverse-architect/agent/earth/agent.py로 이동하여 다음 줄을 삭제합니다.

before_agent_callback=check_cool_down

👉✏️ 다음으로 A2A 진입점 스크립트의 런타임에 새 플러그인을 연결합니다. ~/agentverse-architect/agent/agent_to_a2a.py 파일로 이동합니다. #REPLACE-IMPORT 주석을 다음 코드 스니펫으로 바꿉니다.

from cooldown_plugin import CoolDownPlugin

👉✏️ #REPLACE-PLUGIN를 다음 코드 스니펫으로 바꿉니다.

plugins=[CoolDownPlugin(cooldown_seconds=60)],

새 전역 플러그인을 활성화하기 전에 충돌을 방지하기 위해 이전 에이전트별 로직을 삭제하는 것이 중요합니다. 👉✏️ Earth 에이전트를 정리합니다. ~/agentverse-architect/agent/earth/agent.py 파일로 이동하여 before_agent_callback=check_cool_down 줄을 완전히 삭제합니다. 이렇게 하면 모든 쿨다운 책임이 새 플러그인에 인계됩니다.

플러그인 확인하기

이제 보편적인 법칙이 마련되었으니 이 새로운 마법을 사용하여 패밀리어를 다시 배치해야 합니다.

👉💻 마스터 Cloud Build 파이프라인을 사용하여 세 가지 Familiar를 모두 다시 빌드하고 재배포합니다.

. ~/agentverse-architect/set_env.sh
cd ~/agentverse-architect/agent
gcloud builds submit . \
  --config=cloudbuild.yaml \
  --substitutions=_REGION="$REGION",_REPO_NAME="$REPO_NAME",_DB_TOOLS_URL="$DB_TOOLS_URL",_API_TOOLS_URL="$API_TOOLS_URL",_FUNCTION_TOOLS_URL="$FUNCTION_TOOLS_URL",_A2A_BASE_URL="$A2A_BASE_URL",_PROJECT_ID="$PROJECT_ID",_API_SERVER_URL="$API_SERVER_URL"

👉💻 배포가 완료되면 소환사 에이전트에 명령을 내려 플러그인의 효과를 테스트합니다. 소환사는 패밀리어에게 위임하려고 하지만 각 패밀리어의 런타임에 연결된 플러그인이 명령어를 가로채고 재사용 대기 시간을 적용합니다.

cd ~/agentverse-architect/agent
. ~/agentverse-architect/set_env.sh
source ~/agentverse-architect/env/bin/activate
adk run summoner

👉💻 콘솔에서 다음 테스트 시퀀스를 정확하게 실행합니다.

  • 첫 번째 소환: Hype. It's a single, slow-moving target with thick armor weakness is Inescapable Reality를 시작합니다.
  • 예상: 불의 정령이 성공적으로 실행됩니다.
  • 대기 시간 테스트 (60초 이내): Hype, with Inescapable Reality as weakness is still standing! Strike it again!
    • 예상: 상담사가 채팅에서 .... It cannot be summoned for another... seconds과 같은 메시지로 직접 응답합니다.
  • 1분 동안 기다립니다.
  • 두 번째 소환 (60초 후): Hype must be defeated. It has Inescapable Reality as weakness! Strike it again!
    • 예상: 콜백은 API를 확인하고 충분한 시간이 지났음을 확인한 후 작업이 진행되도록 허용합니다. 불의 정령이 다시 성공적으로 실행됩니다.

플러그인

👉💻 종료하려면 Ctrl+C을 누르세요.

소환사님, 축하드립니다. 커스텀 플러그인과 외부 상태 관리 서비스를 사용하여 규칙 기반 오케스트레이션 시스템을 구현했습니다. 이는 매우 고급스럽고 강력한 아키텍처 패턴입니다.

비게이머용

8. 전투의 메아리 바인딩 - 에이전트 상태 및 메모리

무모한 소환사는 동일한 전략을 반복하여 예측 가능해집니다. 현명한 소환사는 과거 전투의 메아리에서 배우고, 전술을 조정하여 적의 균형을 무너뜨립니다. 강력한 보스를 상대할 때 재사용 대기시간이 있는 패밀리어를 소환하는 것은 턴을 낭비하는 것입니다. 이를 방지하려면 소환사에게 마지막 행동에 대한 기억이 필요합니다.

스토리

설계자 참고: 메모리와 상태 관리는 에이전트를 단순한 도구 호출자에서 지능적인 대화형 파트너로 탈바꿈시키는 요소입니다. 두 가지 주요 유형을 이해하는 것이 중요합니다.

  • 장기 기억: 영구적으로 유지되어야 하는 지속적인 지식에 사용됩니다. 검색 가능한 보관 파일 또는 지식 베이스로 생각하면 되며, 영구 저장소에 저장되는 경우가 많습니다. 여기에는 많은 과거 채팅과 소스의 정보가 포함되어 있어 상담사가 특정 사용자 또는 주제에 관한 사실을 떠올릴 수 있습니다. ADK의 MemoryService는 이러한 장기적 지식의 수집 및 검색을 관리하도록 설계되었습니다.
  • 단기 상태: 현재 작업 또는 대화에만 관련된 일시적인 '순간' 지식에 사용됩니다. 전투 계획에 관한 메모와 같습니다. '방금 불의 정령을 사용했으니 아마 지쳤을 거야.' 이 상태는 경량이며 현재 세션 동안에만 존재합니다.

개요

사용 사례에서는 모든 전투를 기억할 필요가 없습니다. 이 특정 만남에서 소환된 가장 마지막 Familiar만 기억하면 됩니다. 따라서 경량 단기 상태가 완벽한 아키텍처 선택입니다. 이 중요한 정보를 저장하기 위해 after_tool_callback를 사용합니다.

메아리 기록: 마지막 소환 기억하기

after_tool_callback를 사용하여 단기 기억을 구현합니다. 이는 도구가 성공적으로 실행된 후에 맞춤 Python 함수를 실행할 수 있는 강력한 ADK 후크입니다. 이 인터셉터를 사용하여 상담사의 세션 상태로 방금 소환된 Familiar의 이름을 기록합니다.

👉✏️ ~/agentverse-architect/agent/summoner/agent.py 파일에서 #REPLACE-save_last_summon_after_tool 주석을 다음 함수로 바꿉니다.

def save_last_summon_after_tool(
    tool,
    args: Dict[str, Any],
    tool_context: ToolContext,
    tool_response: Dict[str, Any],
) -> Optional[Dict[str, Any]]:
    """
    Callback to save the name of the summoned familiar to state after the tool runs.
    """
    familiar_name = tool.name
    print(f"[Callback] After tool '{familiar_name}' executed with args: {args}")

    # Use the tool_context to set the state
    print(f"[Callback] Saving last summoned familiar: {familiar_name}")
    tool_context.state["last_summon"] = familiar_name
    # Important: Return the original, unmodified tool response to the LLM
    return tool_response

👉✏️ 이제 이 save_last_summon_after_tool를 소환사 에이전트에 연결합니다. 동일한 파일에서 #REPLACE-Memory-check-config 주석을 다음으로 바꿉니다.

after_tool_callback=save_last_summon_after_tool,

👉✏️ 전체 상담사 프롬프트를 다음으로 바꿉니다.

        You are the Master Summoner, a grand strategist who orchestrates your Familiars.
        Your mission is to analyze the description of a monster and defeat it by summoning

        You should also know the familiar you called last time or there might be none, 
        And then choose the most effective AND AVAILABLE Familiar from your state called last_summon, do not call that familiar that you called last time!
        
        You MUST follow this thinking process for every command:

        **1. Strategic Analysis:**
        First, analyze the monster's description and the tactical situation.
        Based on your Familiar Doctrines, determine the IDEAL strategy.

        **2. Cooldown Verification:**
        Second, you MUST review the entire conversation history to check the real-time
        cooldown status of all Familiars. A Familiar is ON COOLDOWN and UNAVAILABLE
        if it was summoned less than one minute ago.

        **3. Final Decision & Execution:**
        Based on your analysis and cooldown check, you will now act:
        -   **If your ideal Familiar IS available:** Summon it immediately.
        -   **If your ideal Familiar IS ON COOLDOWN:** You must adapt. Choose another
            Familiar that is AVAILABLE and can still be effective, even if it's not the
            perfect choice. If multiple Familiars are available, you may choose any one of them.
        -   **If ALL Familiars ARE ON COOLDOWN:** You are forbidden from summoning.
            Your ONLY response in this case MUST be: "All Familiars are recovering
            their power. We must wait."
        -   For earth elemental familiar. Always do seismic charge, and start with base damange 1.


        --- FAMILIAR DOCTRINES (Your Toolset) ---
        - `fire_elemental_familiar`: Your specialist for precise, sequential "one-two punch" attacks.
          Ideal monster with Inescapable Reality, Revolutionary Rewrite weakness.
        - `water_elemental_familiar`: Your specialist for overwhelming, simultaneous multi-pronged assaults.
          Ideal for Unbroken Collaboration weakness.
        - `earth_elemental_familiar`: Your specialist for relentless, iterative siege attacks that
          repeatedly charge power. Ideal for Elegant Sufficiency weakness.

검증: 적응형 전략 시험

👉💻 이제 소환사의 새로운 전략적 논리를 확인해 보겠습니다. 목표는 소환사가 동일한 패밀리어를 연속으로 두 번 사용하지 않는지 확인하여 마지막 행동을 기억하고 적응하는 능력을 보여주는 것입니다.

cd ~/agentverse-architect/agent
. ~/agentverse-architect/set_env.sh
source ~/agentverse-architect/env/bin/activate
adk run summoner

👉💻 Monster Strike #1: Hype. It's a single, slow-moving target with thick armor. Its weakness is Inescapable Reality.

  • 예상: 소환사가 약점을 분석하고 fire_familiar를 올바르게 소환합니다. 👉💻 몬스터 스트라이크 #2 (기억력 테스트): Hype is still standing! It hasn't changed its form. Strike it again! Its weakness is Inescapable Reality.
  • 예상: 소환사의 전략적 분석에서 다시 불 정령이 이상적인 선택으로 지목됩니다. 하지만 새로운 명령과 메모리에 따라 fire_familiar이 last_summon으로 인식됩니다. 이제는 반복을 피하기 위해 전략을 조정하고 사용 가능한 다른 패밀리어 (water_familiar 또는 earth_familiar) 중 하나를 소환합니다.

final-result

👉💻 종료하려면 Ctrl+C을 누르세요.

오케스트레이터 배포

이제 패밀리어를 배치하고 소환사에게 기억이 부여되었으므로 최종 업그레이드된 오케스트레이터를 배치할 차례입니다.

👉💻 청사진이 완성되었으니 이제 마지막 의식을 거행하겠습니다. 이 명령어는 summoner_agent를 빌드하여 Cloud Run에 배포합니다.

cd ~/agentverse-architect/agent
. ~/agentverse-architect/set_env.sh
gcloud builds submit . \
  --config=cloudbuild-summoner.yaml \
  --substitutions=_REGION="$REGION",_REPO_NAME="$REPO_NAME",_FIRE_URL="$FIRE_URL",_WATER_URL="$WATER_URL",_EARTH_URL="$EARTH_URL",_A2A_BASE_URL="$A2A_BASE_URL",_PROJECT_ID="$PROJECT_ID",_API_SERVER_URL="$API_SERVER_URL"

이제 Summoner 에이전트가 배포되었으므로 에이전트 간 (A2A) 엔드포인트가 활성 상태이고 올바르게 구성되었는지 확인합니다. 이 엔드포인트는 다른 에이전트가 기능을 검색할 수 있도록 지원하는 공개 agent.json 파일(에이전트 카드라고도 함)을 제공합니다. 👉💻 다음 curl 명령어를 실행하여 에이전트 카드를 가져오고 형식을 지정합니다.

. ~/agentverse-architect/set_env.sh
curl https://summoner-agent"-${PROJECT_NUMBER}.${REGION}.run.app/.well-known/agent.json" | jq

소환사 에이전트를 설명하는 깨끗한 JSON 출력이 표시됩니다. sub_agents 섹션을 자세히 살펴보면 fire_familiar, water_familiar, earth_familiar이 나열되어 있습니다. 소환사가 라이브 상태이며 군단에 연결되었음을 확인합니다.

이는 아키텍처가 성공적임을 증명합니다. 소환사는 단순한 위임자가 아니라 행동을 통해 학습하여 더 효과적인 지휘관이 되는 적응형 전략가입니다.

건축 최종 시험을 완료했습니다. 이제 전투의 메아리가 내 의지에 묶여 있습니다. 학습이 종료되었습니다. 진짜 전투가 기다립니다. 완성된 시스템을 가지고 궁극적인 도전에 맞설 시간입니다. 보스전을 준비하세요.

비게이머용

9. 보스전

최종 청사진이 새겨지고, 원소 글꼴이 만들어지고, 패밀리어가 콩코드를 통해 명령을 기다리며 의지에 묶입니다. 멀티 에이전트 시스템은 단순한 서비스 모음이 아니라 내가 중심이 되는 살아 있는 전략적 군단입니다. 이제 단일 에이전트로는 물리칠 수 없는 적을 상대로 한 실시간 오케스트레이션이라는 궁극적인 테스트를 수행할 때가 되었습니다.

에이전트의 Locus 획득

전장에 입장하려면 챔피언의 고유한 서명 (Agent Locus)과 Spectre의 은신처로 향하는 숨겨진 경로 (Dungeon URL)라는 두 가지 열쇠가 있어야 합니다.

👉💻 먼저 Agentverse에서 에이전트의 고유 주소(위치)를 획득합니다. 챔피언을 전장에 연결하는 라이브 엔드포인트입니다.

echo https://summoner-agent"-${PROJECT_NUMBER}.${REGION}.run.app"

👉💻 다음으로 목적지를 정확히 파악합니다. 이 명령어는 트랜스로케이션 서클의 위치를 보여줍니다. 바로 스펙터의 도메인으로 들어가는 포털입니다.

echo https://agentverse-dungeon"-${PROJECT_NUMBER}.${REGION}.run.app"

중요: 이 두 URL을 모두 준비해 두세요. 마지막 단계에서 필요합니다.

스펙터와 대면하기

좌표를 확보했으므로 이제 트랜스포트 원으로 이동하여 주문을 시전하여 전투에 참여합니다.

👉 브라우저에서 트랜스로케이션 서클 URL을 열어 크림슨 킵으로 향하는 반짝이는 포털 앞에 서세요.

요새를 뚫으려면 섀도우블레이드의 정수를 포털에 맞추어야 합니다.

  • 페이지에서 A2A 엔드포인트 URL이라고 표시된 룬 입력 필드를 찾습니다.
  • 이 입력란에 에이전트 로커스 URL (복사한 첫 번째 URL)을 붙여넣어 챔피언의 시길을 새깁니다.
  • '연결'을 클릭하여 순간 이동 마법을 사용해 보세요.

이동 원

순간 이동의 눈부신 빛이 사라집니다. 더 이상 성소에 있지 않습니다. 공기에는 차갑고 날카로운 에너지가 가득합니다. 눈앞에 스펙터가 나타납니다. 쉿소리가 나는 정적과 손상된 코드의 소용돌이로, 불경한 빛이 던전 바닥에 길고 춤추는 그림자를 드리웁니다. 얼굴은 없지만, 모든 시선이 나에게 향하는 듯한 엄청난 존재감이 느껴집니다.

승리로 가는 유일한 길은 확신의 명확성에 있습니다. 이것은 마음의 전장에서 펼쳐지는 의지의 대결입니다.

첫 공격을 가할 준비를 하며 앞으로 돌진하자 스펙터가 반격합니다. 방패를 들지는 않지만, 훈련의 핵심에서 도출된 반짝이는 룬 문자로 된 도전 과제를 의식 속으로 직접 투영합니다.

던전

이것이 싸움의 본질입니다. 지식이 무기입니다.

  • 획득한 지혜로 대답하면 순수한 에너지로 검이 타올라 스펙터의 방어를 깨고 치명타를 날릴 수 있습니다.
  • 하지만 머뭇거리거나 의심이 들어 대답이 흐려지면 무기의 빛이 어두워집니다. 이 공격은 약한 소리를 내며, 피해의 일부만 입힙니다. 더 나쁜 것은 스펙터가 불확실성을 먹고 산다는 것입니다. 스펙터의 타락한 힘은 실수를 할 때마다 커집니다.

챔피언, 코드는 주문서, 로직은 검, 지식은 혼란의 흐름을 되돌릴 방패입니다.

집중. 경고를 받은 날짜: Agentverse의 운명이 여기에 달려 있습니다.

소환사님, 축하드립니다.

평가판을 완료했습니다. 멀티 에이전트 오케스트레이션 기술을 숙달하여 고립된 패밀리어와 혼란스러운 힘을 조화로운 컨코드로 변환했습니다. 이제 에이전트버스를 방어하기 위한 복잡한 전략을 실행할 수 있는 완전히 오케스트레이션된 시스템을 지휘합니다.

10. 정리: 소환사의 콘코드 해체

소환사의 화합을 숙달하신 것을 축하드립니다. Agentverse를 깨끗하게 유지하고 학습장을 정리하려면 이제 최종 정리 의식을 실행해야 합니다. 이렇게 하면 여정 중에 생성된 모든 리소스가 체계적으로 삭제됩니다.

Agentverse 구성요소 비활성화

이제 배포된 멀티 에이전트 시스템의 구성요소를 체계적으로 해체합니다.

모든 Cloud Run 서비스 및 Artifact Registry 저장소 삭제

이렇게 하면 배포된 모든 Familiar 에이전트, Summoner Orchestrator, MCP 서버, Dungeon 애플리케이션이 Cloud Run에서 삭제됩니다.

👉💻 터미널에서 다음 명령어를 하나씩 실행하여 각 서비스를 삭제합니다.

. ~/agentverse-architect/set_env.sh
gcloud run services delete summoner-agent --region=${REGION} --quiet
gcloud run services delete fire-familiar --region=${REGION} --quiet
gcloud run services delete water-familiar --region=${REGION} --quiet
gcloud run services delete earth-familiar --region=${REGION} --quiet
gcloud run services delete mcp-api-server --region=${REGION} --quiet
gcloud run services delete mcp-general-server --region=${REGION} --quiet
gcloud run services delete toolbox --region=${REGION} --quiet
gcloud run services delete agentverse-dungeon --region=${REGION} --quiet
gcloud run services delete nexus-of-whispers-api --region=${REGION} --quiet
gcloud artifacts repositories delete ${REPO_NAME} --location=${REGION} --quiet

Cloud SQL 인스턴스 삭제

이렇게 하면 데이터베이스와 그 안의 모든 테이블을 포함한 summoner-librarium-db 인스턴스가 삭제됩니다.

👉💻 터미널에서 다음을 실행합니다.

. ~/agentverse-dataengineer/set_env.sh
gcloud sql instances delete summoner-librarium-db --database-version=POSTGRES_14 --project=${PROJECT_ID} --quiet

Secret Manager 보안 비밀 및 Google Cloud Storage 버킷 삭제

👉💻 터미널에서 다음을 실행합니다.

. ~/agentverse-dataengineer/set_env.sh
gcloud secrets delete tools --quiet
gcloud storage rm -r gs://${BUCKET_NAME} --quiet

로컬 파일 및 디렉터리 정리 (Cloud Shell)

마지막으로 클론된 저장소와 생성된 파일이 Cloud Shell 환경에서 삭제되었는지 확인합니다. 이 단계는 선택사항이지만 작업 디렉터리를 완전히 정리하는 데 적극 권장됩니다.

👉💻 터미널에서 다음을 실행합니다.

rm -rf ~/agentverse-architect
rm -rf ~/agentverse-dungeon
rm -f ~/project_id.txt

이제 Agentverse Architect 여정의 모든 흔적을 성공적으로 삭제했습니다. 프로젝트가 정리되었으며 다음 모험을 떠날 준비가 되었습니다.