ADK + MCP Agent

/

ADK (Agent Development Kit) 소개

아래 영상은 로컬에서 `adk web`으로 실행한 모습입니다.

AI 에이전트는 복잡한 질문에 답하고 작업을 완료하는 데 도움을 주는 대화형 파트너입니다. Google Agent Development Kit(ADK)는 개발자가 다중 에이전트 시스템을 신속하게 구축하고 맞춤설정할 수 있도록 지원하는 클라이언트 측 Python SDK입니다.

ADK는 복잡한 다중 에이전트 시스템의 개발을 간소화하도록 설계되었습니다. 에이전트 간의 통신, 대화 기록, 상태 공유와 같은 어려운 문제들을 처리해주므로, 개발자는 인프라에 대한 고민 없이 에이전트의 핵심 로직과 상호작용을 구축하는 데 집중할 수 있습니다.

ADK Components

주요 특징

  • 코드 중심 접근 방식: 별도의 AI 전문 지식 없이도 Python 개발자라면 누구나 쉽게 에이전트를 개발할 수 있습니다.
  • 다중 에이전트 시스템 지원: 순차, 병렬, 루프 등 다양한 워크플로우 에이전트를 통해 여러 에이전트를 계층적으로 구성하고 복잡한 작업을 자동화할 수 있습니다.
  • 강력한 도구 생태계: Google 검색과 같은 사전 빌드된 도구를 사용하거나, 커뮤니티 도구를 활용하고, 직접 맞춤형 도구를 만들어 에이전트의 능력을 무한히 확장할 수 있습니다.
  • 상태 저장 대화 (Session Memory): 세션 메모리를 통해 대화의 맥락을 기억하고, 여러 턴에 걸쳐 이어지는 상태 저장(stateful) 상호작용을 지원합니다.
  • 통합된 평가 시스템: 에이전트의 최종 응답 품질뿐만 아니라, 목표 달성을 위해 거치는 과정(trajectory)과 도구 사용의 적절성까지 체계적으로 평가할 수 있습니다.
  • Agent Engine으로의 배포: 개발한 에이전트를 Vertex AI 기반의 완전 관리형 런타임인 Agent Engine에 배포하여, 안정적이고 확장 가능한 운영 환경을 손쉽게 구축할 수 있습니다.

Agent Engine은 개발자가 프로덕션 환경에서 AI 에이전트를 손쉽게 배포, 관리, 확장할 수 있도록 지원하는 완전 관리형 Google Cloud 서비스입니다. Agent Engine이 인프라 관리를 처리하므로 개발자는 지능적이고 강력한 애플리케이션을 만드는 데 더 집중할 수 있습니다.

주요 기능:

  • 완전 관리형: VPC-SC 규정 준수, 포괄적인 엔드 투 엔드 관리, 성능 모니터링 및 Google Cloud Trace를 통한 추적 등 강력한 보안 및 관리 기능을 제공합니다.
  • 품질 및 평가: 통합된 Gen AI Evaluation Service를 사용하여 에이전트의 품질을 보장하고 지속적으로 개선할 수 있습니다.
  • 간소화된 개발: 애플리케이션 서버, 인증, IAM 구성과 같은 복잡한 하위 수준 작업을 추상화하여 개발자가 에이전트의 고유한 동작, 도구, 모델 파라미터에 집중할 수 있도록 합니다.
  • 프레임워크 독립성: LangGraph, LangChain, AG2, CrewAI 등 다양한 Python 프레임워크를 지원하여 유연한 배포가 가능합니다.
Agent Engine Architecture

ADK (Agent Development Kit) 설치 및 실행

ADK 설치

ADK를 사용하기 위해서는 먼저 패키지를 설치해야 합니다. 터미널을 열고 다음 명령어를 실행하여 ADK를 설치하세요.

pip install google-adk

Tip: 가상 환경 내에 ADK를 설치하는 것을 권장합니다. 이렇게 하면 프로젝트별로 의존성을 관리할 수 있습니다.

ADK 실행

ADK로 개발한 에이전트는 다음 두 가지 방법으로 실행할 수 있습니다.

1. 대화형으로 실행 (CLI):

터미널에서 에이전트와 직접 대화하며 테스트하고 싶을 때 사용합니다.

adk run 

2. 웹 인터페이스로 실행:

웹 브라우저에서 사용할 수 있는 UI와 함께 에이전트를 실행합니다.

adk web

실습 준비 (macOS)

본격적인 에이전트 개발에 앞서, macOS 환경에서 실습을 진행하기 위한 준비 과정을 안내합니다. 모든 과정은 **가상 환경** 내에서 진행하여 프로젝트의 독립성을 보장합니다.

사전 요구사항

이 가이드를 따라하기 위해서는 Python 3.9 이상이 설치되어 있어야 합니다. 터미널을 열고 다음 명령어로 버전을 확인하세요.

python3 --version

1. 프로젝트 폴더 및 가상 환경 설정

먼저 프로젝트를 위한 폴더를 만들고, 그 안에 파이썬 가상 환경을 설정합니다.

# 1. 'adk_project' 라는 프로젝트 폴더를 만들고 이동합니다.
mkdir adk_project
cd adk_project

# 2. '.adk-venv' 라는 이름의 가상 환경을 생성합니다.
python3 -m venv .adk-venv

# 3. 생성한 가상 환경을 활성화합니다.
source .adk-venv/bin/activate

Tip: 터미널 프롬프트 앞에 `(.adk-venv)`가 표시되면 가상 환경이 성공적으로 활성화된 것입니다. 앞으로의 모든 명령어는 이 상태에서 실행해야 합니다.

2. `requirements.txt` 파일 생성 및 라이브러리 설치

실습에 필요한 모든 라이브러리를 `requirements.txt` 파일에 정의하고 한 번에 설치합니다. 아래 내용으로 프로젝트 루트에 파일을 생성하세요.

경로: adk_project/requirements.txt

google-adk
python-dotenv
google-cloud-logging
langchain-community
wikipedia

이제 `pip`을 사용하여 이 파일에 명시된 모든 라이브러리를 설치합니다.

# '(.adk-venv)'가 보이는 상태에서 다음 명령어를 실행합니다.
pip install -r requirements.txt

3. 에이전트 프로젝트 구조 생성

ADK는 정해진 디렉토리 구조를 따릅니다. 프로젝트 폴더 내에 에이전트 폴더를 생성합니다.

# 'adk_project' 폴더 내에서 다음 폴더를 생성합니다.
mkdir custom_agent

모든 폴더와 파일 생성이 완료되면, 프로젝트의 전체 구조는 다음과 같아집니다.

custom_agent/
├── __init__.py
├── agent.py
└── .env

ADK + MCP Agent

기존의 독립적인 Python 스크립트(Streamlit 기반)를 ADK 프레임워크와 호환되는 에이전트로 변환하는 과정을 알아봅니다. 이 에이전트의 핵심은 ADK를 사용하여 외부 MCP(Model Context Protocol) 서버와 통신하는 것입니다. 이 MCP 서버는 Google BigQuery와 연동되어, 최종적으로 호텔 데이터를 조회하고 예약 관련 작업을 수행하는 백엔드 역할을 합니다.

1. 프로젝트 구조

먼저, `custom_agent`라는 새 에이전트를 위한 디렉토리 구조를 생성합니다. ADK는 이 구조를 기반으로 에이전트를 인식합니다.

custom_agent/
├── __init__.py
├── agent.py
└── .env

2. 환경 변수 설정 (`custom_agent/.env`)

에이전트가 사용할 모델과 클라우드 설정을 `.env` 파일에 정의합니다. ADK는 에이전트 실행 시 해당 디렉토리의 `.env` 파일을 자동으로 로드합니다.

GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT=your-google-cloud-project-id
GOOGLE_CLOUD_LOCATION=us-central1
MODEL=gemini-2.0-flash-001

3. 에이전트 로직 구현 (`custom_agent/agent.py`)

에이전트의 핵심 로직입니다. MCP 서버와 통신하는 도구(Tool)들과, 이 도구들을 사용하는 메인 에이전트(Agent)를 정의합니다.

핵심 개념:
- FunctionTool: 일반 Python 함수를 ADK가 인식할 수 있는 도구로 변환하는 클래스입니다.
- Type Hinting: `Optional[str]` 와 같이 정확한 타입 힌트를 사용하여 ADK가 함수의 매개변수를 올바르게 파싱하도록 합니다. 이는 에러를 방지하는 데 매우 중요합니다.
- Agent Instruction: 에이전트에게 역할을 부여하고, 어떤 상황에서 어떤 도구를 사용해야 하는지 명확하게 지시하는 시스템 프롬프트입니다.

import os
import requests
import json
import uuid
import datetime
from typing import Optional
from google.adk.agents import Agent
from google.adk.tools.function_tool import FunctionTool

# --- 1. 설정 ---
MODEL_NAME = os.environ.get("MODEL", "gemini-2.0-flash-001")
TOOLBOX_MCP_ENDPOINT_BASE = "http://your-mcp-server-endpoint/mcp/my-toolset"

# --- 2. Toolbox MCP 서버 호출 헬퍼 함수 ---
def call_mcp_tool(tool_name: str, tool_args: dict) -> dict:
    """지정된 도구 이름과 인수로 Toolbox MCP 서버를 호출하는 중앙 함수."""
    mcp_session_id = str(uuid.uuid4())
    endpoint_with_session = f"{TOOLBOX_MCP_ENDPOINT_BASE}?sessionId={mcp_session_id}"
    payload = {
        "jsonrpc": "2.0",
        "method": "tools/call",
        "params": {"name": tool_name, "arguments": tool_args},
        "id": str(uuid.uuid4())
    }
    headers = {"Content-Type": "application/json"}
    try:
        response = requests.post(endpoint_with_session, headers=headers, json=payload, timeout=15)
        response.raise_for_status()
        if not response.text:
            return {"status": "error", "message": f"MCP server returned an empty response for tool {tool_name}."}
        return response.json()
    except Exception as e:
        return {"status": "error", "message": f"Exception during MCP server call: {str(e)}"}

# --- 3. ADK 도구 정의 ---
def search_hotels_by_name_func(name: str, location: Optional[str] = None) -> dict:
    """호텔 이름을 기준으로 호텔을 검색합니다. 위치 정보를 함께 제공하여 검색 범위를 좁힐 수 있습니다."""
    return call_mcp_tool("search-hotels-by-name", {"name": name, "location": location})

def search_hotels_by_location_func(location: str) -> dict:
    """위치(도시)를 기준으로 호텔을 검색합니다."""
    return call_mcp_tool("search-hotels-by-location", {"location": location})

def book_hotel_func(hotel_id: int) -> dict:
    """사용자가 특정 호텔 ID를 제공하고 예약을 명시적으로 요청할 때, 해당 호텔 ID를 사용하여 호텔을 예약합니다."""
    return call_mcp_tool("book-hotel", {"hotel_id": hotel_id})

def update_hotel_func(hotel_id: int, checkin_date: str, checkout_date: str) -> dict:
    """사용자가 특정 호텔 ID에 대해 체크인 또는 체크아웃 날짜 변경을 요청할 때 사용합니다."""
    return call_mcp_tool("update-hotel", {"hotel_id": hotel_id, "checkin_date": checkin_date, "checkout_date": checkout_date})

def cancel_hotel_func(hotel_id: int) -> dict:
    """호텔 ID를 사용하여 호텔 예약을 취소합니다."""
    return call_mcp_tool("cancel-hotel", {"hotel_id": hotel_id})

# FunctionTool로 각 함수를 래핑합니다.
search_hotels_by_name = FunctionTool(func=search_hotels_by_name_func)
search_hotels_by_location = FunctionTool(func=search_hotels_by_location_func)
book_hotel = FunctionTool(func=book_hotel_func)
update_hotel = FunctionTool(func=update_hotel_func)
cancel_hotel = FunctionTool(func=cancel_hotel_func)

# --- 4. ADK 에이전트 정의 ---
system_instruction = """당신은 사용자의 호텔 검색, 예약, 예약 변경, 예약 취소를 돕는 AI 호텔 예약 에이전트입니다.
대화의 맥락을 주의 깊게 파악하고, 사용자가 이미 제공한 정보(예: 호텔 ID, 날짜)를 다시 묻지 마세요.
사용자의 요청을 이해하고 다음 지침에 따라 적절한 도구를 사용하세요:
- 호텔 검색: 사용자가 호텔 이름이나 위치로 검색을 요청하면 'search_hotels_by_name_func' 또는 'search_hotels_by_location_func' 도구를 사용하세요.
- 호텔 예약: 사용자가 특정 호텔 ID를 명시하고 '예약해줘' 또는 유사한 예약 의사를 밝히면, 해당 ID로 'book_hotel_func' 도구를 사용하세요.
- 예약 변경 (날짜 업데이트): 사용자가 특정 호텔 ID에 대해 체크인 또는 체크아웃 날짜 변경을 요청하면, 호텔 ID, 새로운 체크인 날짜, 새로운 체크아웃 날짜를 모두 파악한 후 'update_hotel_func' 도구를 사용하세요. 모든 정보가 갖춰지지 않았다면 필요한 정보를 사용자에게 요청하세요.
- 예약 취소: 사용자가 특정 호텔 ID로 예약 취소를 요청하면 'cancel_hotel_func' 도구를 사용하세요.
항상 사용자의 요청을 명확히 이해한 후 도구를 사용하세요. 도구를 사용하기 전에는 사용자에게 어떤 작업을 수행할 것인지 간단히 언급해주세요 (예: '호텔을 검색해 보겠습니다.', '예약을 진행하겠습니다.'). 도구 사용 후에는 그 결과를 사용자에게 친절하게 안내하세요."""

root_agent = Agent(
    name="custom_agent",
    description="사용자 지정 호텔 예약 에이전트",
    model=MODEL_NAME,
    instruction=system_instruction,
    tools=[
        search_hotels_by_name,
        search_hotels_by_location,
        book_hotel,
        update_hotel,
        cancel_hotel
    ]
)

4. 패키지 초기화 (`custom_agent/__init__.py`)

ADK가 `custom_agent` 디렉토리를 파이썬 패키지로 인식하고 `root_agent`를 찾을 수 있도록 `__init__.py` 파일을 생성합니다.

from .agent import root_agent

5. 실행

이제 모든 준비가 완료되었습니다. 터미널에서 다음 명령어를 실행하면 `adk web` UI의 드롭다운 메뉴에 `custom_agent`가 나타나며, 선택하여 상호작용할 수 있습니다.

# (.adk-venv)가 활성화된 상태에서 실행
adk web

참고 자료: 이 예제는 다음 BigQuery Quickstart 문서를 기반으로 합니다.
https://googleapis.github.io/genai-toolbox/samples/bigquery/local_quickstart/

Streamlit에서 ADK로 변환하기

기존의 Streamlit으로 구현된 에이전트를 ADK 프레임워크로 전환하면서 어떤 부분이 변경되었는지 비교해봅니다. 이 과정은 독립 실행형 스크립트를 표준화되고 재사용 가능한 ADK 에이전트로 만드는 과정을 보여줍니다.

1. 도구(Tool) 정의 방식의 변경

가장 큰 변화는 도구를 정의하는 방식입니다. 기존에는 Google `genai` 라이브러리의 `FunctionDeclaration`을 직접 사용했지만, ADK에서는 `FunctionTool` 클래스로 Python 함수를 감싸는 방식을 사용합니다. 이를 통해 ADK는 함수의 시그니처와 독스트링(docstring)을 자동으로 분석하여 도구 스키마를 생성합니다.

변경 전 (Streamlit + GenAI)

# genai 라이브러리를 직접 사용
from google.generativeai.types import FunctionDeclaration

Hotels_by_name_fd = FunctionDeclaration(
    name="search-hotels-by-name",
    description="호텔 이름을 기준으로 호텔을 검색...",
    parameters={
        "type": "object",
        "properties": {
            "name": {"type": "string", "description": "검색할 호텔 이름"},
            "location": {"type": "string", "description": "선택 사항. 호텔 위치"}
        }, 
        "required": ["name"]
    }
)
# ... 모든 도구를 이렇게 정의 ...

변경 후 (ADK)

# ADK의 FunctionTool 사용
from google.adk.tools.function_tool import FunctionTool
from typing import Optional

# 일반 Python 함수로 정의
def search_hotels_by_name_func(
    name: str, 
    location: Optional[str] = None
) -> dict:
    """호텔 이름을 기준으로 호텔을 검색합니다..."""
    return call_mcp_tool(...)

# FunctionTool로 함수를 래핑
search_hotels_by_name = FunctionTool(
    func=search_hotels_by_name_func
)

2. 에이전트 실행 로직의 변화

Streamlit 앱에서는 UI 코드와 대화 로직(턴 관리, 기록 관리)이 복잡하게 얽혀있습니다. ADK는 이러한 실행 흐름을 `Agent` 클래스와 내부 `Runner`가 모두 처리하므로, 개발자는 에이전트의 핵심 지시사항(instruction)과 사용할 도구 목록만 정의하면 됩니다.

변경 전 (Streamlit)

# Streamlit UI 코드
st.title("🏨 LLM 호텔 예약 에이전트")
user_input_prompt = st.chat_input(...)

if user_input_prompt:
    # 세션 기록 직접 관리
    st.session_state.messages.append(...)
    
    # 대화 루프를 직접 구현
    for i in range(MAX_TURNS):
        # 모델 호출, 함수 호출, 결과 처리 등
        # 모든 로직을 수동으로 관리
        ...

변경 후 (ADK)

# ADK Agent 정의
from google.adk.agents import Agent

system_instruction = """
당신은 사용자의 호텔 예약을 돕는 AI입니다...
"""

# 에이전트의 역할과 도구만 정의
root_agent = Agent(
    name="custom_agent",
    model="gemini-2.0-flash-001",
    instruction=system_instruction,
    tools=[
        search_hotels_by_name,
        book_hotel,
        # ... 다른 도구들
    ]
)

# 실행은 `adk web` 명령어로 처리
# UI나 대화 루프 코드가 필요 없음

3. 설정 및 구성

기존 코드의 하드코딩된 API 키와 같은 설정 값들은 ADK의 표준 방식인 `.env` 파일로 이전되었습니다. 이를 통해 코드에서 민감한 정보를 분리하고 환경별로 다른 설정을 쉽게 관리할 수 있습니다.

결론: ADK로의 전환의 이점
- 코드 간소화: UI 및 대화 관리 로직이 프레임워크에 위임되어, 에이전트의 핵심 기능에만 집중할 수 있습니다.
- 표준화 및 재사용성: 모든 에이전트가 일관된 구조를 가지므로 재사용과 유지보수가 용이합니다.
- 확장성: `adk web` UI, 평가 도구, Agent Engine으로의 배포 등 ADK 생태계의 모든 기능을 활용할 수 있습니다.